Setting Model id With id Property - backbone.js

http://documentcloud.github.io/backbone/#Model-id indicates that the id property of a model is special because if my_model.set("id", <new_id>) is called, my_model.id will have that new value. This property is not commutative, however. Calling my_model.id = 4 followed by my_model.get("id") will not result in 4.
Is there a way to have my_model.id=4 set the value of my_model.attributes.id so that my_model.get("id") will result in 4?

To achieve what you want, you can override the get method of Backbone.Model, but that is not a very good proposition cause there is a reason why model id and id property of attribute are separated from each other, id of model is something local to backbone and id property of the attribute is something that might be used by the remote servers when you sync your model.
So in usual cases, overriding the get function of Model can cause trouble in future.

You can achieves what you want like this :
Backbone.Model.prototype.get = function(attr) {
if (attr == 'id' && this.attributes[attr] != this.id) {
this.attributes[attr] = this.id;
}
return this.attributes[attr];
};

Related

Iterate over an array or object with underscore

Why title return null !! , is there any confusion with anything !
Here is my code
_.each(collection.models, function(element, index, list){
console.log(JSON.stringify(element)); //{"title":"Dipped Bunny Blossom","id":49,"created_at":"2015-03-24T10:16:17Z","updated_at":"2015-03-24T13:56:12Z","type":"simple","status":"publish","downloadable":false,"virtual":false,"permalink":"http://beta-it.com/ticarttest/shop/dipped-bunny-blossom/","sku":"","price":"50.00","regular_price":"50.00","sale_price":null,"price_html":"<span class=\"amount\">$50.00</span>","taxable":false,"tax_status":"taxable","tax_class":"","managing_stock":false,"stock_quantity":0,"in_stock":true,"backorders_allowed":false,"backordered":false,"sold_individually":false,"purchaseable":true,"featured":true,"visible":true,"catalog_visibility":"visible","on_sale":false,"weight":"1.00","dimensions":{"length":"50","width":"50","height":"50","unit":"cm"},"shipping_required":true,"shipping_taxable":true,"shipping_class":"","shipping_class_id":null,"description":"","short_description":"","reviews_allowed":true,"average_rating":"4.50","rating_count":2,"related_ids":[34,43,45,47,120],"upsell_ids":[],"cross_sell_ids":[],"parent_id":0,"categories":["BIRTHDAY","BUSINESS GIFTS","DIPPED FRUIT"],"tags":[],"images":[{"id":50,"created_at":"2015-03-03T10:16:10Z","updated_at":"2015-03-03T10:16:10Z","src":"http://beta-it.com/ticarttest/wp-content/uploads/2015/03/Dipped-Bunny-Blossom.jpg","title":"Dipped-Bunny-Blossom","alt":"","position":0}],"featured_src":"http://beta-it.com/ticarttest/wp-content/uploads/2015/03/Dipped-Bunny-Blossom.jpg","attributes":[],"downloads":[],"download_limit":0,"download_expiry":0,"download_type":"","purchase_note":"","total_sales":1,"variations":[],"parent":[]}
console.log(element.id); // return 49 ok
console.log(element.title); //return null !!!!
// We are looping through the returned models from the remote REST API
// Implement your custom logic here
});
For a backbone collection, your code should look more like this:
collection.each(function(model) {
console.log(model.attributes);
console.log(model.id);
console.log(model.get('title'));
});
Most attributes in backbone models must be accessed using the get method. id is a special case that can be accessed via get or as a direct id property. According to the docs:
If you set the id in the attributes hash, it will be copied onto the model as a direct property.

My model ids collide with cids ( eg: "c7" )

I am new to backbone.
After much confusion about not being able to add some of my models to a collection and sometimes getting the wrong model using collection.get(id) I found out that my model ids are colliding with backbones cids.
My model ids are something like "c7" or "c5e6". While the later is no problem "c7" is backbones own cid for the seventh element of the collection.
So if I ask for collection.get('c7') and expect null I instead get the element that was given the cid "c7" by backbone. And if I add an element with id "c7" I will never get it back with get("c7").
I wonder if I am the first one with this problem, I did not find anything about a syntax restriction of backbone ids, is there a way to solve this? As a workaround I will save my own ids in a custom attribute, and have to use collection.where instead of collection.get.
Any better ideas?
If you look at Backbone source code, you will see that the cid in a model is determined in the constructor by
this.cid = _.uniqueId('c');
c is an arbitrary prefix which means you could disambiguate your ids by overriding _.uniqueId, something like
_._uniqueId = _.uniqueId;
_.uniqueId = function(prefix) {
if (prefix === 'c') {
prefix = 'cc';
}
return _._uniqueId(prefix);
};
Without the override : http://jsfiddle.net/nikoshr/KmNSr/ and with it : http://jsfiddle.net/nikoshr/KmNSr/1/
Unfortunately, this does look like an edge case problem with no real solution. Looking at the Backbone source, we can see in the Backbone.Collection.set method that Backbone mixes both your IDs and their internal CIDs in the same object:
set: function(models, options) {
// ...
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
// ...
return this;
},
The _byId object holds all IDs which causes your issue. Here is the Backbone.Collection.get method:
get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id != null ? obj.id : obj.cid || obj];
},
When you call it using a non-existent ID (of your own) like "c7", the return ... line becomes return this._byId["c7"];. Since _byId has references to yours and Backbone's IDs, you're getting their entry returned when you expected null.
nikoshr has a great solution in the answer below.

Ordering an observable collection with Reactiveui

I am having some difficulty with ordering an observable collection in my ViewModel.
Here is my situation:
In my view model, I have the following list:
public List<TicketModel> Tickets
{
get { return _Tickets.Value; }
set
{
{
this.RaiseAndSetIfChanged(c => c.Tickets, value);
}
}
}
private ObservableAsPropertyHelper<List<TicketModel>> _Tickets;
This list is populated using a ReactiveAsyncCommand:
LoadTickets.RegisterAsyncFunction(x => loadTickets())
.ToProperty(this, x => x.Tickets);
All works so far.
I have another command, SortByCommand which gets called whenever the user wants to sort a collection. The command looks like this:
SortByCommand = new ReactiveCommand(this.WhenAny(c => c.Tickets, ((tickets) => tickets.Value != null && tickets.Value.Count > 0)));
SortByCommand.Subscribe(c => sortTickets((SortByModel)c));
The command also calls a function that orders the collection using an order by clause:
private void sortTickets(SortByModel model)
{
Tickets = Tickets.OrderBy(model.Selector).ToList();
}
Whenever the sortTickets function is called, there is an exception thrown which says:
Unable to cast object of type 'ReactiveUI.ObservableAsPropertyHelper`1[System.Collections.Generic.List`1[Bugmine.Modules.MyPage.Models.TicketModel]]' to type 'System.Collections.Generic.List`1[Bugmine.Modules.MyPage.Models.TicketModel]'.
I have several questions:
1) Why can't I set directly the Tickets model? Do I need to first convert the result of the OrderBy to some sort of observable collection?
2) Is there a better way of doing this?
EDIT: Clarification
The approach I am taking right now is:
The Tickets collection gets reset every x seconds.
As soon as the sortTickets function is called, I will sort and reset this collection by:
Tickets = Tickets.OrderBy(c => c.Name).ToList(); //for example
When the Tickets collection is loaded again, I will check if it should be sorted and sort it before setting the Tickets property.
This feels a bit hacky because I am basically setting the collection at two points - upon loading and upon sorting. Moreover, upon loading I am using the ReactiveUI helper -> ToProperty:
LoadTickets.RegisterAsyncFunction(x => loadTickets())
.ToProperty(this, x => x.Tickets);
Whereas, upon sorting I am doing that myself:
Tickets = Tickets.OrderBy(model.Selector).ToList();
I am wondering if there is a better way to do the sorting using the ReactiveUI approach which I already use upon loading.
Thanks in advance!
Another way to solve this is via CreateDerivedCollection:
SortedTickets = Tickets.CreateDerivedCollection(
x => new TicketViewModel(x),
orderer: (l,r) => SortModel.Selector(l, r), // Returns CompareTo() result
signalReset: this.WhenAny(x => x.SortModel, x => x.Value)); // Reorder on SortModel change
Note that this breaks down if Tickets is set repeatedly (which in this case it is) - you might change your model to initializing Tickets in the ctor, then Clearing and Adding all the items, i.e.
LoadTickets.RegisterAsyncFunction(x => loadTickets())
.Subscribe(x => {
// TODO: Make sure Tickets is a ReactiveCollection
Tickets.Clear();
Tickets.AddRange(x); // Will trigger resorting of SortedTickets
});
Just had a look on Ana's blog http://blog.paulbetts.org/index.php/2010/07/05/reactivexaml-series-implementing-search-with-observableaspropertyhelper/
//
// This is the canonical way to make a read-only property whose value
// is backed by an IObservable
//
ObservableAsPropertyHelper<List<FlickrPhoto>> _Photos;
I think the read-only is the important point there.
Instead you could try using a normal observable where you can use OnNext to push in the new value
private Observable<List<TicketModel>> _Tickets = new Observable<Lis<TicketModel>>();
_Tickets.OnNext(newValue);
Or use a ObservableForProperty<> and just use the property normally
public List<TicketModel> _Tickets { get;set;}
private Observable<List<TicketModel>> _ticketsObservable= ObservableForProperty<..>(x=>x.Tickets);
Both of these methods expose an Observable which we can use later in the sort.
Why not try handling the two inputs into your sort in the same way, then it wont feel so hacky. That way you'll also have an
public SortModel SortModel {get;set;}
Your sort command implementation becomes
SortByCommand.Subscribe(c => _Sort = c));
but then you subsribe to both ticket changes AND sort criteria changes in one, see http://rxwiki.wikidot.com/101samples#toc44 for CombineLatest
new ObservableForProperty<..>(x=>x.SortModel)
.CombineLatest(_ticketsObservable)
.Subscribe( (x,y)=>
{
//Refactor to SortMethod
_tickets = y.OrderBy(x.Selector);
});
I'm pretty sure that the result of OrderBy(..) is an IEnumerable instead of an ObservableCollection. Fortunately though, it has a constructor that can do the conversion, i.e.
Tickets = new ObservableCollection<...>(Tickets.OrderBy(...));
"Better" can be very subjective measure. Firstly you haven't explained the full lifespan of the Tickets collection and the tickets it contains, so we can't really tell.
You might consider using a SortedTickets field/property instead of overwriting your tickets property which you might find you wouldn't need to be an observable collection as you would know when it needs to NotfyProperryChanged from the Tickets observable. It all depends on how frequently your tickets is likely to change.
P.S. Also make sure you have tested what happens to your sorted list when you add another ticket as you can't just

specify return fields for all find calls on model in cakephp

I can't believe I don't remember how to do this, but how do I specify in my model the default fields that are returned with the find() methods? I can't find on google how to do this, or at least I don't know the wording to search for.
What you will most likely need to do, is to check if the fields key exists in a beforeFind() method in your model.
If the fields key is not set, you can set it to $this->fields in your native models, and create the beforeFind() in your AppModel, then you can instruct that method to use the $this->fields array from your models.
UPDATE
// AppModel.php
parent function beforeFind($queryData = array()) {
if (empty($queryData['fields']) && !empty($this->fields)) {
$queryData['fields'] = $this->fields;
}
return $queryData;
}
// And in your Model:
public $fields = array(
'Alert.id'
);
This will check for existence of a fields array, and will then check for existence of a $this->fields property. If it does exist, it will apply it to the query data and return that modified query data to the beforeFind() - this will change your find.
Adjust it to fit your needs, and good luck!

Save Backbone.js Model and update entire Collection

I would like to be able to call save() on a backbone model and have the backend return the entire collection of this model instead of only the changed attributes of the model. I would then like backbone to update the entire returned collection. The use case for this is the following:
A user has multiple addresses and can choose a shipping address from this collection. If she chooses a different shipping address from the collection the previous shipping address should be updated to the state of 'just another plain address'. For this the entire collection has to be updated instead of only the changed model.
Is this somehow possible in backbone.js?
Thanks a lot in advance!
models that are bound to collections, contain their collection parent as a property. Also, since your returning a list of models, we can assume that it is always in a list.
mymodel = Backbone.Model.extend({
parse: function (data) {
if(this.collection && typeof(data) === 'array') {
this.collection.reset(data);
}
return data;
}
});
I do not think that overriding sync or breaking the expectations of what save returns is necessary here.
It would be simpler I guess to override save on the model, something on the lines of:
save: function (key, value, options) {
var p = Model.prototype.save.call(this, key, value, options),
self=this;
if (this.collection) {
p.done(function () { self.collection.fetch(); });
}
return p;
}
which will save using the normal save obtaining its promise, and then if saving was successful and the model is part of a collection, it will fetch the collection from the server.
Another way would be to bind to the model's change event, check if it belongs to a collection and fetch, but that would also happen on set.
Yep, it's possible. You'll need to override the sync function on the model
MyModel = Backbone.Model.extend({
sync: function(method, model) {
if (method === 'create') {
//perform save logic and update the model's collection
} else if (method === 'update') {
//perform save logic and update the model's collection
} else if (method === 'read') {
...
} else if (method === 'destroy') {
...
}
}
});
Take a look at the Backbone.sync function for more information.
What your use case actually calls for is the updating of two models, not the updating of an entire collection. (Other than fetching, collections don't interact with the server in Backbone.) Assuming you have addresses A, B, and C, with A as the current shipping address and C as the new shipping address, your code can simply:
Update C to be the new shipping address.
Update A to be 'just another address.'
If your problem is that you don't know which address is the current address (i.e., address A), then you can just search inside your collection for it (i.e., "give me the current shipping address"), update C, and then update the returned address (address A).
If you absolutely need to update an entire collection (you don't), cycle through collection.models and update each one individually. There simply is no concept in Backbone of a collection being acted upon RESTfully.
Edit: I may have misread your question. If you meant update the collection on the client (i.e., you didn't intend to update the collection on the server), then the code is still similar, you just update both the old model and the new one.

Resources