Given this Backbone Collection
define [
'underscore',
'backbone',
'cs!models/floor'
], ( _, Backbone, Floor ) ->
return Backbone.Collection.extend
model: Floor
url: ->
return '/api/hotels/' + #hotelId + '/floors'
initialize: (models, options) ->
if ( options.hotelId )
#hotelId = options.hotelId
#.fetch()
parse: (response) ->
response.floors
alreadyExist: ->
#.filter( (floor) ->
return floor.get('number') == #.attrs.get('number')
)
and adding a new Model from a view the way below, how can I validate if the model already exist within the collection ?
add_floor: (e) ->
console.log ' Saving Floor '
e.preventDefault()
floorNumber = $('input[name=floorNumber]').val()
floorDescription = $('input[name=floorDescription]').val()
return new NoticeView({ message: "Please enter a Floor Number.", displayLength: 10000 }) unless floorNumber
if ! #collection.add({ number: floorNumber}).alreadyExist()
#collection.create({ number: floorNumber, description: floorDescription }, {
error: (model, response) ->
# $(e.target).removeClass('waiting');
new ErrorView({ message: "Problem saving Floor " + response.responseText, displayLength: 10000 })
success : (model, response) ->
console.log model
console.log response
new NoticeView({ message: "Floor successfully saved.", displayLength: 10000 })
})
else
new ErrorView({ message: "Floor already exist." + response.responseText, displayLength: 10000 })
Backbone collections proxy the Underscore.js iteration functions which are useful in these cases.
If you have an existing model instance, to check whether it exists in the collection you can just do something like:
var myModel = new Floor();
// the following line logs true if model is in collection, false if not.
console.log(myCollection.contains(myModel));
If you do not have an existing instance of the model, which from your example suggests may be the case, you can use the find or findWhere underscore functions, for example:
var floorNumber = $('input[name=floorNumber]').val()
var myModel = myCollection.findWhere({ floorNumber: floorNumber });
If find or findWhere return a model, which you could easily check using a typeof comparison then you will know whether the model exists in the collection or not.
findWhere on a collection will not result in JavaScript error but will not not find a model neither. A proper way of checking if a collection contains a model is to use underscore's find like this:
var model = _.find(collection.models, function (model) { return model.id ==
id_to_be_searched; });
var found = collection.contains(model);
if (found) {
// do stuff
}
else {
// do stuff
}
Related
Is there a way to stop backbone validation of colelction if one model fails validation? Currently, the code does this (taken from Backbone.js 1.1.0):
for (i = 0, l = models.length; i < l; i++) {
attrs = models[i];
if (attrs instanceof Model) {
id = model = attrs;
} else {
id = attrs[targetModel.prototype.idAttribute];
}
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(id)) {
if (remove) modelMap[existing.cid] = true;
if (merge) {
attrs = attrs === model ? model.attributes : attrs;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
}
models[i] = existing;
// If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
model = models[i] = this._prepareModel(attrs, options);
if (!model) continue;
toAdd.push(model);
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
if (order) order.push(existing || model);
}
So if _prepareModel returns false, which it will if a model is invalid, it will skip to next model and try to validate and add that.
I want to stop if one of the models fails validation? The app I'm working on is a tablet app and thousands of models are returned from the server (represented of course as JSON) so if the first model fails validation, I do not want all the others to be validated too, as the entire collection will be disguarded.
Any ideas how I can do this?
I would override the fetch method on your collection. Like this:
youCollection.fetch = function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var success = options.success;
var collection = this;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
if (success) {
if (success(collection, resp, options))
collection[method](resp, options);
} else
collection[method](resp, options);
collection.trigger('sync', collection, resp, options);
};
wrapError(this, options);
return this.sync('read', this, options);
}
Here's what's happening. Focus on the line that starts with options.success =. When your server comes back with your requested models, a trigger invokes the function defined by options.success passing your models in as a parameter. In the original code, options.success looks like this:
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
collection[method](resp, options);
if (success) success(collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
What matters here that collection[method](resp, options); invokes set on the collection that called the fetch. And only then you have the option to invoke your own callback. Once collection.set is called, the validation loop on your models begins and there's nothing we could do.
We want to change that up. We want to first call our own success function. This function will run a for loop checking if each model.isValid(), and will return false as soon as one fails. If one does fail we never reach collection[method](resp, options); and set never happens.
To get a success function into your fetch call simply drop a reference to it in the options passed into fetch
yourCollection.fetch({ success: checkValidate });
I would additionally point you to Backbone.validate so that you can read about the 'invoke' trigger that gets fired when a model is invalid. That way you can handle another fetch or whatever.
I want to use backbone.localStorage.js plugin in my app and here is a code sample:
Module.Vehicles = Backbone.Collection.extend({
initialize : function(options) {
this.customerId = options.customerId;
},
url : function() {
var url = App.Config.RESTPath + '/vehicle';
if(this.customerId) {
url = url + '?customerId='+this.customerId;
}
return url;
},
localStorage: new Backbone.LocalStorage("vehicles"),
model : Module.Vehicle,
parse : function(response) {
this.allVehiclesNumber = response.allVehiclesNumber;
this.customers = response.customers;
return response.vehicles;
}
});
Module.getVehicles = function(customerId) {
var result = new Module.Vehicles({
'customerId' : customerId
});
result.fetch();
return result;
};
Everything works great (collection has proper records) if I add a comment in the line:
localStorage: new Backbone.LocalStorage("vehicles"),
But if it is not commentend there are no recordsfetch.
What I missed?
BR, Tomasz.
If you check Backbone.localStorage source code, you will see that it overrides the way Backbone syncs its data : if you have a localStorage declared in you model/collection, the normal sync is discarded and replaced by a local storage.
You can alter this behavior by providing your own custom Backbone.sync. For example, this will use both versions:
Backbone.sync = function(method, model, options) {
if(model.localStorage || (model.collection && model.collection.localStorage)) {
Backbone.localSync.call(this, method, model, options);
}
return Backbone.ajaxSync.call(this, method, model, options);
};
And a Fiddle to play with http://jsfiddle.net/nikoshr/F7Hkw/
I have a basic application using Backbone.js that is not making PUT calls (updating model). From the front-end, I calling a models save function doesn't make a PUT call; however, if I replace it with destroy, it does make a DELETE call to the back-end. Anyone have any idea what might be the issue? The function that is not firing a PUT request is the saveTask function.
App.Views.Task = Backbone.View.extend({
template: _.template("<label>ID:</label><input type='text' id='taskId' name='id' value='<%= _id %>' disabled /><br><label>Title:</label><input type='text' id='title' name='title' value='<%= title %>' required/><br><label>Content:</label><input type='text' id='content' name='content' value='<%= content %>'/><br><button class='save'>Save</button>"),
events: {
"change input":"change",
"click .save":"saveTask"
},
render: function(eventName){
$(this.el).html(this.template(this.model.toJSON()));
//console.log(this.generateTemplate());
return this;
},
change: function(event){
var target = event.target;
console.log('changing ' + target.id + ' from: ' + target.defaultValue + ' to: ' + target.value);
change[target.name] = target.value;
this.model.set(change);*/
},
saveTask: function(){
this.model.set({
title:$("#title").val(),
content:$("#content").val()
});
if(this.model.isNew()){
App.taskList.create(this.model);
} else {
this.model.save({});
}
}
});
If your model is new, then at the time you save it it will fire a post method.
If your model however is not new and you are updating it, it will fire a PUT.
if this is not working for you it may be because your model does not have an id property, in case you are using an id with a different name, for example taskID, then in your model you have to set the idAttribute to taskID so backbone uses this property as the Id and everything will be normal.
like this:
var Task= Backbone.Model.extend({
idAttribute: "taskId"
});
here is the link to the documentation on Idattibute
http://backbonejs.org/#Model-idAttribute
also another problem could be the {} in your save call
try just
this.model.save();
instead of
this.model.save({});
I believe model is always expecting options parameter and also probably the callbacks
this.model.save(null, {
success: function (model, response) {
//
},
error: function () {
//
}
});
If you look at Backbone src, you will notice that too...
======
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function (key, val, options) {
var attrs, method, xhr, attributes = this.attributes;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = _.extend({
validate: true
}, options);
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;
}
// Set temporary attributes if `{wait: true}`.
if (attrs && options.wait) {
this.attributes = _.extend({}, attributes, attrs);
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function (resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch') options.attrs = attrs;
xhr = this.sync(method, this, options);
// Restore attributes.
if (attrs && options.wait) this.attributes = attributes;
return xhr;
},
In my case it fails due to validations.As i save the model it validates all the attributes of the model and the collection which i am using for listing interface doesn't required all the attributes of the model.
I was facing the same issues and search in Google and found your question and read the solution and comments.Than i realize that in updated backbone specifications it is mentioned that when model.save() executes before model requests,it first call validate and if validate succeeds than it will go ahead other wise fails, and that is the reason why it doesn't showing any network request in chrome debugger network tab.
I have write the solution for the case which i am facing,other might be facing different issues.
Backbone's sync function is what I wound up using. You have to pass in 'update' as the first parameter (the 'method' parameter).
I am using this plugin for my autocomplete form:
http://www.planbox.com/blog/news/updates/jquery-autocomplete-plugin-for-backbone-js.html
Instead of checking only one item, as in the code below (if (inputVal == 'bakaxel')),
I would like to check the selected value against the entire collection
var collection = new Backbone.Collection([
{id:"AB", name:"Alberta"},
{id:"AD", name:"Album"},
{id:"BA", name:"barn"},
{id:"BC", name:"bak"},
{id:"BD", name:"baby"},
{id:"BE", name:"band"},
{id:"BF", name:"bakaxel"},
{id:"BG", name:"batteri"},
{id:"BH", name:"barbie"},
{id:"MB", name:"Manitoba"},
{id:"AP", name:"Armed Forces Pacific"}
]);
$('input.search').autocomplete({
collection: collection,
attr: 'name',
noCase: true,
ul_class: 'search_options tr_list',
ul_css: {'z-index':1234}
});
$('input.search').each(function(){
$(this).blur(function(){
var inputVal = $('input.search').val();
if (inputVal == 'bakaxel') {
$('#search_result_page').load('searchResult.html');
$('#searchPage').addClass('hidden');
}
});
});
I tried this, but I'd rather not create the ar array again, just use the backbone collection:
$('input.search').each(function(){
$(this).blur(function(){
var inputVal = $('input.search').val();
var ar = ["Alberta", "Album", "barn", "bak", "baby", "band", "bakaxel", "batteri", "barbie", "Manitoba", "Armed Forces Pacific"];
if (jQuery.inArray(inputVal, ar) != -1) {
$('#search_result_page').load('searchResult.html');
$('#searchPage').addClass('hidden');
}
});
});
Backbone proxies Underscore functions and most notably in your case http://underscorejs.org/#where
where _.where(list, properties)
Looks through each value in the list, returning an array of all the values that contain all of the
key-value pairs listed in properties.
Your test could be written as
var matches = collection.where({
name: inputVal
});
if (matches.length>0) {
...
}
Or as #mu suggested in the comments, you could just check the existence of the input with http://underscorejs.org/#find
var found = collection.find(function(model) {
return model.get('name') === inputVal
});
if (found) {
...
}
in my simple backbone application, I am trying to update a model and every time it send a put request instead of post.
Well, this is my model named categoryModel
define(['Backbone'], function (Backbone) {
var CategoryModel = Backbone.Model.extend({
defaults: {
ID: '',
Name: 'Empty',
TagID: '0',
GID: '0'
},
idAttribute: "ID",
initialize: function () {
if (!this.get('Name')) {
this.set({ 'Name': this.defaults.Name });
}
}
});
return CategoryModel;
});
this is the collection
define(['Backbone','../../models/categories/categoryModel'], function (Backbone, categoryModel) {
var CategoryCollection = Backbone.Collection.extend({
url: '/parentcategory/Actions',
model: categoryModel
});
return new CategoryCollection;
});
here are my methods in the view
on a keychange event
createNewItem: function (e) {
var $this = $(e.currentTarget);
$('#selectedCategoryName').html($this.val());
//it creates a new model
globals.NewCategory = new CategoryModel({ Name: $this.val() });
}
on handleDrop event
handleDropEvent: function (event, ui) {
var draggable = ui.draggable;
//check if name has set
if (!globals.NewCategory) {
alert("Please write a category name");
$('#createNewCategory').focus();
return;
}
//get itemID
var itemID = draggable.attr("id").split('_')[1];
var itemDesc = draggable.attr("id").split('_')[0];
//check items category
if (itemDesc == "Tag") {
//check if tagID already exists
if (globals.NewCategory.TagID) {
alert("you have already specify a tag from this category");
return;
}
globals.NewCategory.set("TagID", itemID);
} else if (itemDesc == "gTag") {
if (globals.NewCategory.GID) {
alert("you have already specify a tag from this category");
return;
}
globals.NewCategory.set("GID", itemID);
}
categoriesCollection.create(globals.NewCategory, {
silent: true,
wait: true,
success: function (model, response) {
model.set("ID", response);
alert(model.id);
}
});
}
The categoriesCollection.create is called twice. Firstly for setting the TagID (on a success request it gets an ID ) and secondly for setting the GID.
Since the ID has been set, shouldn't had sent a POST request instead of PUT on the second call?
What am I doing wrong?
Thanks
The standard behaviour is to send a POST if the model is new ( doesn't have an ID attributed ) and send a PUT if the model id is set.
In your case it's working as designed, if you want it to use POST to send UPDATES you have to override Backbone.sync to work as you need, but I think it's easier for you to make your backend RESTful and create a PUT listener controller method for updates.
Another thing, if I got it right you are using create() to update models in your collection, I would advise you not to do that and instead use the save() directly in the model you want to update, the code will be a lot more readable.
Cheers.