Avoid serialization of properties - angularjs

When I require to add a private property to an object (for view or logic control) that will be submitted to a rest api latter, is valid prefix the property with $$? This is tricky in cases when I have an object with a list of children and each child requires a private property that should not be sent.
{
name: 'my object',
items: [
{
name: 'my child',
$$editing: true
},
{
name: 'my other child',
$$editing: true
}
]
}

Yes, angularjs $http service uses the angular.toJson method by default.
All properties with $$ are filtered out, because angular uses such properties internally. (e.g. you may have seen the $$hashKey property, which is added by angular)
You can try:
console.log(angular.toJson({a:1, $$b:2, c: {x:2,$$_y:3}}))
results in "{"a":1,"c":{"x":2}}"

Related

BackboneJs - Retain events on a collection inside a model when model changes

i've a Collection inside a Model as illustrated below:
var itemModel = Backbone.Model.extend({
defaults:{
name:"",
brand:"",
priceCollection:[]
}
})
There are change listeners attached to the itemModel and also change listeners attached to collection as
this.listenTo(itemModel.get('priceCollection'),'change',this.dosomething) in a view.
The problem is that the change listeners on the collection work fine as long as the parent model hasn't changed , if the model is given a set of new attributes via itemModel.set(newattributes) the event bound on itemModel.get('priceCollection') is lost.
How do i retain this event? or should i rebind this event everytime the Model is change? or Should i move the listener on the collection from the view to the Model and trigger a custom Backbone event?
It should be noted that this model is singleton
Keep in mind that Backbone assumes a Collection and Model should be mapped 1:1 to a server side resource. It makes clear assumtions on the API layout and data structures - refer to Model.url, Model.urlRoot and Collection.url.
Proposal
You said the model is a singleton. In this case I'd suggest to maintain the model and collection separately.
Since a SomeModel is not accompanied by a certain collection SomeCollection which have a tight relationship it's not necessary to relate them on an attribute level. The effort needed to establish event listeners and sync the data is only at one place.
// some controller (app main)
var model = new SomeSingletonModel();
var collection = new SomeSingletonCollection();
var view = new SomeView({
model: model,
collection: collection
});
Probably the resource that is mapped to SomeSingletonModel will deliver an array.
What are the benefits of using a collection as model attribute (that's what model.get("name") is) over using a plain array? Syncing and change events. Both are probably only necessary when a View updates the Collection's Models. When the View only renders, a Collection does not provide any benefit in many cases.
If the data of that array needs to be updated, using a Collection is probably the right choice because of Backbone's synching mechanisms.
But how to keep the collection sync with the model (you ask)?
Your controller needs to listen to the model and update the collection on sync and reset:
model.on("sync reset", function() {
// "priceCollection" is a model attribute
collection.reset(model.get("priceCollection"));
// optionally unset "priceCollection" on the model
this.unset("priceCollection", { silent: true });
});
This will initialize the collection.
Any change to the Collection's Models will then only be part of the Collection's or Model's syncing mechanisms.
Foreword
Also see my other answer which is probably the better choice when the model is a singleton.
Note the very first statement on Backbone's assumptions on the API design on that answer.
Proposals using a coupling between Model and Collection
Note: if necessary, in all these implementations the Collection's url or Model's (the Collection's Model) url/rootUrl may get (re-)defined upon sync to control the syncing.
Update internal reference on change/sync/reset
This implementation removes the model attribute and updates an object attribute with its data.
The object attribute is one Collection instance that is only reset, not recreated, upon model change.
var CustomModel = Backbone.Model.extend({
defaults: {
// defaults go here - "children" may be contained here
},
// implement constructor to act before the parent constructor is able to
// call set() (L402 in v1.3.0) with the initial values
// See https://github.com/jashkenas/backbone/blob/1.3.0/backbone.js#L402
constructor: function() {
// create children collection as object attribute - replaces model attr.
this.children = new Backbone.Collection();
// listen to changing events to catch away that attribute an update the
// object attribute
this.listenTo(this, "change:children sync reset", this.onChangeColl);
// apply original constructor
Backbone.Model.apply(this, arguments);
},
onChangeColl: function() {
// check for presence since syncing will trigger "set" and then "sync",
// the latter would then empty the collection again after it has been updated
if (this.has("children")) {
// update "children" on syncing/resetting - this will trigger "reset"
this.children.reset(this.get("children"));
// remove implicitly created model attribute
// use silent to prevent endless loop due to change upon change event
this.unset("children", { silent: true });
}
}
});
Example usage when testing in a Fiddle or console:
var c = new CustomModel({ a: 1, children: [{ x: 1 }, { x: 5 }] });
c.set({a: 8, children: [{ x: 50 }, { x: 89 }]});
c.url = "/dummy"
// replace sync() only for fetch() demo - the implementation does what sync() would do on success
c.sync = function(method, coll, opts){ if (method == "read") { opts.success({ a: 100, children: [{ x: 42 }, { x: 47 }] }); } }
c.fetch();
Pro
listening to collection events is easier to implement since there's one instance through model lifetime
Contra
code is more complex
collection data is not part syncing without further implementations
Replace on change/sync/reset
This implementation intercepts model attribute changes and replaces its data with a Collection instance that has been initialized (reset) with the raw data.
var CustomModel = Backbone.Model.extend({
defaults: {
// this is optional
children: new Backbone.Collection()
},
initialize: function() {
// listen to model attribute changing events to swap the raw data with a
// collection instance
this.listenTo(this, "change:children sync reset", this.onChangeColl);
},
onChangeColl: function() {
if (this.has("children")) {
// use silent to prevent endless loop due to change upon change event
this.set("children", new Backbone.Collection(this.get("children")), { silent: true });
}
}
});
Example usage when testing in a Fiddle or console:
var c = new CustomModel({ a: 1, children: [{ x: 1 }, { x: 5 }] });
c.set({ a: 8, children: [{ x: 50 }, { x: 89 }] });
c.url = "/dummy";
// replace sync() only for fetch() demo - the implementation does what sync() would do on success
c.sync = function(method, coll, opts){ if (method == "read") { opts.success({ a: 100, children: [{ x: 42 }, { x: 47 }] }); } }
c.fetch();
Pro
straightforward implementation
Contra
data included in sync, excluding it takes more effort
listening to the Collection impractical: since all consumers would need to unbind/bind
Note: depending on your requirements and API design you may not want children being synced to the server automatically. In this case this solution is limited. You could overwrite toJSON() of the Model but this may limit its usage for other parts of the application (like feeding the data into a view).
Inverse relation: Collection has a Model
Maybe your primary data is actually the Collection. So decorating a Collection with additional data is another approach. This implementation provides one model along the Collection that will be updated upon collection sync.
This implementation is only best suited for fetch of collection data along with attributes (e.g. fetching directory contents with attributes of the directory itself).
var CustomCollection = Backbone.Collection.extend({
initialize: function() {
// maintain decorative attributes of this collection
this.attrs = new Backbone.Model();
},
parse: function(data, opts) {
// remove "children" before setting the remainder to the Model
this.attrs.set(_.omit(data, "children"));
// return the collection content only
return data.children;
}
});
Example usage when testing in a Fiddle or console:
var c = new CustomCollection({ a: 1, b: 2, children: [{ x: 2 }, { x: 3 }] }, { parse: true });
c.reset({ a: 9, b: 11, children: [{ x: 5 }, { x: 10 }] } , { parse: true });
// replace sync() only for fetch() demo - the implementation does what sync() would do on success
c.sync = function(method, coll, opts){ if (method == "read") { opts.success({ a: 100, b: 124, children: [{ x: 42 }, { x: 47 }] }) } }
c.fetch();
Pro
straight forward implementation
delegating model events is easier
Contra
collection data is not part syncing without further implementations
requires parse() to be implemented which
-- in turn requires parse: true to always be passed to reset() and set() and
-- requires parse() to be called with the collection as scope (this) (this could be circumvented by defining parse within initialize bound to this using `bind()´)

Custom data unwrapping in ampersand.js model

I have a model - Configuration:
var Configuration = Model.extend({
props: {
name: 'string'
}
});
In the database, configuration model / table has 3 columns -> id, name and fields. The latter stores site config as a serialized array. When retrieving the entry from the database, I unserialize it and then pass it to the front end, so the front end receives this:
{
"id": 1,
"name": 'global',
"fields": {
"enabled": true,
"site_name": "Test"
}
};
What I want to do is to set whatever is inside fields object as properties on my model, or maybe session so that things get triggered throughout the site when they are updated. To visualize it, I want to achieve something like this:
var Configuration = Model.extend({
props: {
enabled: 'boolean',
site_name: 'string'
}
});
So basically, is there are a way to 'unwrap' stuff in fields object somehow?
The parse method is what you're looking for in this case. See https://github.com/AmpersandJS/ampersand-state/blob/master/ampersand-state.js#L93-L98 It allows you to transform incoming props.

Extending UI-Router-Tabs To Display Form Validation State

I'm pretty new to Angular and having a problem figuring out how to bind a value from a data service to a customization of the https://github.com/rpocklin/ui-router-tabs project that I have created.
The customization is to display an icon in the tab heading that toggles based on the validation state of the corresponding ui-view. I've wired up a data service that is updated when validation succeeds or fails, and the binding is being "seen" by the controller that renders the tabs.
The problem arises when I attempt to pass this value into the tab data as "options". The value change/icon toggle is not being processed as the validation state changes. If I pass a hard-coded "true" into the tab data, the desired icon is displayed.
vm.tabs = [
{
heading: "View 1",
route: "manage.view1",
options: {
status: vm.stateService.getForm1State()
}
},
{
heading: "View 2",
disable: vm.disableTabs,
route: "manage.view2",
options:{
status: true
}
}
];
This doesn't seem like something that should be that difficult, so I think I'm just missing something obvious about the scoping. Here is a plunk http://plnkr.co/edit/iefvwcffSZmpfy83NGde?p=preview that demonstrates the issue.
Note: should be tagged as ui-router-tabs, but I lack the reputation to create the tag.
The problem lies here:
options: {
status: vm.stateService.getForm1State()
}
Since stateService.getForm1State() returns a boolean value, this value will be copied and assigned as the value of the status property.
So when this code has executed once it will for example be:
options: {
status: false
}
The options object won't react to any changes within the stateService.
An easy solution is to have stateService.getForm1State() return an object instead.
For example:
function stateService() {
var form1IsValid = {
status: false
};
return {
getForm1State: getForm1State,
setForm1State: setForm1State
};
function getForm1State() {
return form1IsValid;
}
function setForm1State(newValue) {
form1IsValid.status = newValue;
return;
}
Then make the options object refer to the same object:
options: vm.stateService.getForm1State()
Now whenever you have:
options.status
It will be the same as:
vm.stateService.getForm1State().status
And it will correctly reflect the changes.
Demo: http://plnkr.co/edit/KjXVSSMJbLw7uod0ac6O?p=preview

How create a new instance of a Model that is registered with a Controller?

What is the idiomatic way to create a new Instance of an associated Model inside a Controller?
Motivation:
If I change the Class of my Model the code below doesn't get updated and I have to find all the breakages and fix them manually.
If I could use the the association in the controller, it gets updated automatically, but I can't find any documentation on the magical incantation to create a new instance from the controller reference.
Here is the relevant parts of my controller:
Ext.define('AdminApp.controller.SelectFilesController', {
extend: 'Ext.app.Controller',
models: [
'File'
],
// lots of stuff snipped for brevity
onFilefieldChange: function(filefield, value, eOpts) {
Ext.each(Ext.getDom('select-upload-button-fileInputEl').files, function(f) {
var fm = new AdminApp.model.File({
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
}
});
// lots more stuff snipped for brevity
}
What I want to know:
I found this in the documentation:
It’s important to note that the getters for both views and models
return a reference to the class (requiring you to instantiate your own
instances), while the getters for stores and controllers return actual
instances.
I can't find any examples of what the code to instantiate an instance should look like.
After much trial and error and just plain educated guessing ...
I figured out what the syntax should be.
First you need to create a reference to the class of the Model.
By convention this is the name of the model prefixed with get and suffixed with Model.
var fmc = this.getFileModel();
I had to do this outside the Ext.each() function that visits each item because the this reference pointed to an HTML5 File object instead of my Controller.
Then you simply use a regular Ext.create(fmc, { /* config */ } to get a new instance like so.
var fm = Ext.create(fmc, {
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
So the correct version of the onFilefieldChange function in the question should look like:
onFilefieldChange: function(filefield, value, eOpts) {
var fmc = this.getFileModel();
Ext.each(Ext.getDom('select-upload-button-fileInputEl').files, function(f) {
var fm = Ext.create(fmc,{
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
}
});
}

Path notification for changes to an object within an array?

We have a large model object that is owned and managed outside of polymer. We want to expose this model to polymer elements through a proxy element that exposes computed parts of it.
For example, the model may have:
{
blocks: {
...
properties: [
{ ... }, // prop0
{ ... }, // prop1
]
},
}
We are using recursive object observers and array observers to monitor changes to the model and notify polymer appropriately (either using .notifyPath(path, ...) for object changes and ._notifySplice(...) for array changes). However, there doesn't seem to be a good way for us to notify of changes to an object within an array, e.g. prop0 changed in the example above.
Is there? What should the path be?
Here's an example using binding to show an updated Polymer property nested inside an object and array:
code:
<div>
<h1>[[blocks.properties[0]]]</h1>
<p>this updated property should read: Apple</p>
</div>
script:
Polymer({
is: "my-page",
properties: {
blocks: {
type: Object,
value: { properties : ["acorn","banana","carrot"] },
notify: true
},
},
attached: function() {
console.log("update");
this.set('blocks.properties[0]', "Apple");
},
If you need to do array functions you should also use this.push(path, value) or this.splice(path, value) to notify changes.

Resources