Backbone collection fetch (add:true) does not update the collection - javascript

loadMore: function(){
var $this = this;
console.log(this.Messages); //SAME AS AFTER
this.Messages.url = '/js/messages/?start=' + this.Messages.length
this.Messages.fetch({'add':true,
success:function(){
console.log($this.Messages); //SAME AS BEFORE??
},
error:function(){
}
});
},
The collection is not updated. After this function, the events are fired, and the new items are drawn on the screen. The problem is that the collection did not add the new models.

As was mentioned in a previous answer the add option was removed in 1.0.0. You can accomplish the same thing by passing remove: false instead. From the docs:
The behavior of fetch can be customized by using the available set
options. For example, to fetch a collection, getting an "add" event
for every new model, and a "change" event for every changed existing
model, without removing anything: collection.fetch({remove: false})

Backbone.Collection.fetch():
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var success = options.success;
options.success = function(collection, resp, options) {
var method = options.update ? 'update' : 'reset';
collection[method](resp, options);
if (success) success(collection, resp, options);
};
return this.sync('read', this, options);
},
So what's up here is, your passed in function is assigned to var succees.
collection[method](resp, options); Is called and in your case method is 'reset'.
collection.reset has to go through and add all your models, fire all the events on the way. I don't know exactly what's going on but it goes through collection.reset, collection.add, model.add, etc...I didn't follow it all.
I'm not sure what the problem is exactly, I'm sorry about that. I hope I can at least help you try some things so maybe we can figure it out. The line if (success) success(collection, resp, options) is the call to your succes function. What you might try doing is having your success callback accept the passed back arguments and do some consoling of those out:
success: function(collection, resp, options) {
console.log(collection); // this might do the trick.
// if not, you could try the following
collection.on("reset", function(c, options) {
console.log(c); // see what that gives ya.
});
}
Another thing is, I couldn't find anywhere in the source or the docs where collection.fetch takes an add option. If I missed it, please let me know I'd like to look it over.
Good luck, let me know what you find. It might be worth trailing through with a step through debugger too.
Shit, it strikes me also that console has often showed me the most up to date version of collection objects when it shouldn't have.
try consoling out the lenghts of the collections instead or something:
var len = $this.Messages.length;
console.log(len);
//...
// or in the success callback
var len = collection.length;
console.log(len);

Backbone 1.0 removes this feature, breaking code that depends on this:
http://backbonejs.org/#Collection-fetch
Compare with:
"If you'd like to add the incoming models to the current collection, instead of replacing the collection's contents, pass {add: true} as an option to fetch."
http://htmlpreview.github.com/?https://raw.github.com/documentcloud/backbone/0.9.2/index.html#Collection-fetch
I suggest reverting to an older version of Backbone until this issue is fixed.

in backbone 1.0, you have to trigger reset by hand:
youColloection.fetch({reset: true});

Related

Cancel create model in collection. Fight with duplicate

need create a handler, which not give create duplicate.
Collection = Backbone.Collection.extend({
model : Model,
initialize : function() {
this.on('add', this.actionCreate, this);
},
actionCreate : function(model, collection, param) {
// handler...
}
});
collection = new Collection();
collection.add({ chanel : 'starladder' });
// Should not be created because the parameter 'chanel' already exists with this value
collection.add({ chanel : 'starladder});
I all ready try in handler write somsing like:
if( this.where({ chanel : model.get('chanel').length } ) { model.destroy(); }
but not working.
There are many ways to go about that :)
First, you could simply do the check before the add! Then no need to worry about using Collection.remove() or Model.destroy()
if(!this.collection.findWhere({chanel: 'starladder'}))
...add...
As a side note, here you probably want Collection.remove() rather than Model.destroy() because your newly added Model doesn't seem to have an id, so there is no need for a possible server round-trip.
Second, your handler seems to have a few issues. .where() will return an array, so you need to do a check on the .length of the return value to see if it's > 0. Or you can use findWhere as above which will do the same as where but return the first value of that array, so you don't need to worry about doing a [0] to get to that model.
It's hard to tell what would be the best way to do what you want to achieve without more details, but it seems you are using chanel as a kind of unique ID...? so why not use the idAttribute property to let Backbone know that this is what defines your model in an unique way? Obviously, that only works if you don't real IDs to begin with...
If you need both id and chanel to be unique, then I'd just do a check before the add as you proposed and be on my way + obviously add the same uniqueness condition on the server side, because you never know + don't allow any way in the GUI for the user to pick the same value twice (remove values from select/dropdown/autocomplete, whatever).

dojox/mobile/SearchBox 'onSearch' event runs twice on webkit

I'm using Dojo 1.9.
It happens that the onSearch event runs twice instead of once in Safari and Chrome. In Firefox it runs OK.
SearchBox.onSearch = function(){
console.log("it ran");
}
I need it to run once. How can I manage to do that?
This jsfiddle reproduce this issue.
I think you are using the SearchBox in an unsupported case, that is without specifying its "store" property nor its "list" property.
I've put here: http://jsfiddle.net/adrian_vasiliu/g4yLQ/2/ a modified variant of your code. By setting the store property (here, to an empty dojo/store/Memory):
var store = new Memory(); // empty store
var sb = new SearchBox({store: store, ...});
I get onSearch() called only once (tested in Chrome32/Win7). Since in practice SearchBox is supposed to be used with a store, I don't think this is really a bug.
Such uncanny behaviour seems like a Dojo bug, to me.
You can always try a workaround, something like this:
require(["dojox/mobile/SearchBox", "dojo/dom-construct"],
function (SearchBox, domConstruct) {
var sb = new SearchBox(
{
placeHolder: "search",
incremental: false
},
domConstruct.create("input", { type: "search" },
"searchDiv")
);
sb.startup();
sb.onSearch = function () {
// "Remove" the onSearch callback, don't forget to add it
// once again, before searching.
this.onSearch = function () {};
alert("ran");
};
}
);

Simple event aggregator

I'm looking for a simple event aggregator that works with require.js. I have two modules, one containing a view model and another with a "listener" of some sort:
// view model
define(['lib/knockout-2.2.1', 'events/aggregator'], function(ko, events){
var squareViewModel = function(contents) {
this.click = function(){
events.publish('squareClicked', this);
};
};
return squareViewModel;
});
// some listener of some kind
define(['events/aggregator'], function(events){
events.subscribe('squareClicked', function(e){
alert("hurrah for events");
});
});
Is there anything out there that does this? Is this kind of architecture even a good idea? This is my first foray into client-side architecture.
This is similar to what you posted, but I've had good luck with extending Backbone events (you don't actually have to use anything else about Backbone for this to work), something like this:
define(['underscore', 'backbone'], function( _, Backbone ) {
var _events = _.extend({}, Backbone.Events);
return _events;
});
And then all your viewmodels (or any code really) can use it:
define(['knockout', 'myeventbus'], function(ko, eventBus){
return function viewModel() {
eventBus.on('someeventname', function(newValue) {
// do something
});
this.someKOevent = function() {
eventBus.trigger('someotherevent', 'some data');
};
};
});
The original idea came from this article by Derick Bailey. One of my other favorites on client-side events is this one by Jim Cowart
Again, it amounts to nearly the same thing as what you posted. However, the one thing I don't like about the approach of tying it to jQuery document DOM node is that you might have other types of events flying through there as well, bubbled up from child nodes, etc. Whereas by extended backbone events you can have your own dedicated event bus (or even more than one if you e.g. wanted to have separate data events vs. UI events).
Note regarding RequireJS in this example: Backbone and Underscore are not AMD-compatible, so you'll need to load them using a shim config.
I ended up using jQuery to make my own:
define([], function(){
return {
publish: function (type, params){
$(document.body).trigger(type, params);
},
subscribe: function(type, data, callback){
$(document.body).bind(type, data, callback);
},
};
});
It works for what I want it for, but it has not been extensively tested.
As explunit points out in his answer, this will capture any events on document.body. I also discovered a scoping issue when accessing this inside of a passed callback function.

Tracing knockout events

I have a jQuery grid plugin I am creating based on KnockoutJS 2.2.1. So far it is coming along well, but when the plugin is initialized on an element, the 'computed' loadGrid method invokes 3 times.
Just for a little context I am including the loadGrid method and some other related code. (The actual plugin is quite large so for brevity I only am including part of the plugin)
function GridDataModel() {
var self = this;
self.gridState = {
currentPage: ko.observable(opts.gridState.currentPage),
pageSize: ko.observable(opts.gridState.pageSize),
totalPages: ko.observable(opts.gridState.totalPages),
orderBy: ko.observable(opts.gridState.orderBy),
};
self.loadGrid = ko.computed({
read: function () {
console.log('load grid');
if (opts.dataUrl != '') {
var requestData = self.gridState;
if (self.columns.length == 0) requestData.needColumns = true;
$.getJSON(opts.dataUrl, requestData, function (data, textStatus, jqXHR) {
self.loadData(data);
});
}
},
owner: this,
deferEvaluation: false
});
}
gridDataModel = new GridDataModel();
ko.applyBindings(gridDataModel);
Notice the only dependency this computed has is on self.gridState which isn't changing to my knowledge.
I need to determine what is causing the initialization to call the load 3 times. I know loadGrid gets called when defined (b/c deferEvaluation == false), but I need to find out what is causing the other two events to fire.
So for the question...What is a way to trace what event causes a computed to reevaluate?
On another note, I set deferEvaluation : true but when I issue
gridDataModel.gridState.currentPage.valueHasMutated()
The computed does not fire. So the only way I can even get the computed to work is if deferEvaluation == false.
Chrome developer tools on the 'Sources' tab might be able to help. Just check out the panels on the right that will let you set breakpoints on various DOM elements.
See this overview of the scripts panel (now named the 'Sources' panel) or this overview of creating breakpoints on DOM events for more help.
I use the knockoutjs chrome plugin and I use messages for KO, that way you can display stuff to the console. Example of what I did in the past.
self.messages.push(response.msg);

How can I tell if a model has not been changed in Backbone.js?

This may be a result of misuse of the component, though I don't think so.
I have an issue where a View updates a model in Backbone JS and calls the model's Set method so that it may verify it's input.
In theory there are two results to such an action: Error and Change.
Both events work as prescribed.
But in fact there is a third event: No change.
That is, if the input has not been changed at all, I can't tell after calling Set because no error will be thrown but nor will a change event, as nothing has actually changed- but I still want to know about such a case.
Is there a way for me to do this?
The reason is that there is an action I want performed only if no error occurs, but there is no way for me to know (without a change event) that the model has attempted to set the new values and ended with no result as it all happens asynchronously.
Thanks!
Every Backbone model has a hasChanged method:
hasChanged model.hasChanged([attribute])
Has the model changed since the last "change" event? If an attribute is passed, returns true if that specific attribute has changed.
Perhaps you can use that to check your third possibility.
BTW, the callbacks aren't asynchronous. The error and changed callbacks are triggered and return before set returns:
set : function(attrs, options) {
//...
// Run validation.
if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
//...
// Update attributes.
for (var attr in attrs) {
var val = attrs[attr];
if (!_.isEqual(now[attr], val)) {
now[attr] = val;
delete escaped[attr];
this._changed = true;
if (!options.silent) this.trigger('change:' + attr, this, val, options);
}
}
The _performValidation call triggers the error callbacks, the this.trigger calls will call the per-attribute callbacks.
In this case, you may need to dance around Model.set() a little bit to get where you want. If you are using this functionality, then you should have defined a validate() method on your model.
http://documentcloud.github.com/backbone/#Model-validate
So you can call this method directly...
// something happens and we need to update the model to "newvalues"
if (model.validate(newvalues)) {
model.trigger('error')
} else {
model.trigger('change')
}
model.set(newvalues)
That way you will always at least get 'change' or 'error' out of it, even if it's the same. You will also still get the existing events from set.

Categories

Resources