In my background script:
var collection = Backbone.Collection.extend({});
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
sendResponse(new collection());
}
In my browser_action's javascript:
chrome.tabs.getSelected(null, function(tab) {
chrome.extension.sendRequest({
action: "someAction",
tab: tab
},
function(collection) {
// collection is now a JS array, rather than Backbone.Collection
});
});
As mentioned in the comment above the 'collection' argument in the sendRequest callback turns out to be a regular JS array, rather than Backbone.Collection.
Is this a sanitisation artefact / security measure taken by chromium? Is there any way to pass a Backbone.Collection via sendRequest?
From the onRequest documentation it says that the argument to sendResponse should be a JSON-ifiable object, so I'm assuming that the Collection's toJSON method is being called, leaving you with just the data.
If you have the Collection definition in the target script you could instantiate a new object with the same data.
Related
I have a problem while initializing a Backbone model with some data coming from Jackson.
The received data happens to have a listPropertyValue, which is originally a Java List of objects. When doing the initialize() method I make it a Backbone collection without much problem.
But the final SomeModel constructor also adds an attribute called listPropertyValue as a JavaScript array, which I don't want.
How may I discard or reject this array and which is the right way to do it?
Here is my code:
var SomeModel = Backbone.Model.extend({
defaults : {
id:null,
name:'',
order:null,
isRequired:null,
}
initialize : function(options) {
if(options.listPropertyValue !== undefined) {
this.set('collectionPropertyValue', new PropertyValueCollection(options.listPropertyValue))
}
// I thought of doing this. Don't know if it's the right thing to do
// this.unset('listPropertyValue', { silent: true });
}
My concern is not only how to do it, but how to do it in a proper Backbone way.
(I assume you're getting this data from an API somewhere.)
You should define a parse method in your model to return only the data you're interested in:
parse: function(response){
return _.omit(response, "listPropertyValue");
}
Backbone will do the rest for you: every time it receives API from the data it will call parse automatically.
For more info: http://backbonejs.org/#Model-parse
I finally did it. I used the same code I published but it didn't work until I used backbone with version 1.1.2 (I was using 1.0.0 or similar).
var SomeModel = Backbone.Model.extend({
defaults : {
id:null,
name:'',
order:null,
isRequired:null,
}
initialize : function(options) {
if(options.listPropertyValue !== undefined) {
this.set('collectionPropertyValue', new PropertyValueCollection(options.listPropertyValue));
}
this.unset('listPropertyValue', {
silent : true
});
}
}
I want to fire fetch method on Backbone Collection which would pass an Id parameter similar to what happens in Model.fetch(id)
E.g.
var someFoo= new Foo({id: '1234'});// Where Foo is a Backbone Model
someFoo.fetch();
My Backbone collection:-
var tasks = backbone.Collection.extend({
model: taskModel,
url: '/MyController/GetTasks',
initialize: function () {
return this;
}
});
In my View when I try to fetch data:-
var _dummyId = 10; //
// Tried approach 1 && It calls an api without any `id` parameter, so I get 500 (Internal Server Error).
this.collection.fetch(_dummyId);
// Tried approach 2 && which fires API call passing Id, but just after that
// I am getting error as below:- Uncaught TypeError: object is not a function
this.collection.fetch({
data: {
id: _dummyId
}
});
Found it very late : To cut short the above story I want something like Get /collection/id in backbone.
Thank you for your answers, finally I got the solution from Backbone.js collection options.
Apologies that I couldn't explain the question properly while for same requirement others have done brilliantly and smartly.
Solution : I can have something like :-
var Messages = Backbone.Collection.extend({
initialize: function(models, options) {
this.id = options.id;
},
url: function() {
return '/messages/' + this.id;
},
model: Message,
});
var collection = new Messages([], { id: 2 });
collection.fetch();
Thanks to nrabinowitz. Link to the Answer
As mentioned by Matt Ball, the question doesn't make sense: either you call fetch() on a Collection to retrieve all the Models from the Server, or you call fetch() on a Model with an ID to retrieve only this one.
Now, if for some reason you'd need to pass extra parameters to a Collection.fetch() (such as paging information), you could always add a 'data' key in your options object, and it may happen that one of this key be an id (+add option to add this fetched model rather than replace the collection with just one model)... but that would be a very round-about way of fetching a model. The expected way is to create a new Model with the id and fetch it:
this.collection = new taskCollection();
newTask = this.collection.add({id: 15002});
newTask.fetch();
In your code however, I don't see where the ID is coming from, so I am wondering what did you expect to be in the 'ID' parameter that you wanted the collection.fetch() to send?
I'm using Rivets.js for two two-way data binding in a Backbone project and would like to implement iteration binding. The documentation suggests iteration binding is possible, but there are no examples available. I am using a simple Rails API to send JSON to the client and want to iterate over the contents. Has anyone had any success getting this functionality working in Rivets.js?
Reference material: Simple Example using Backbone.js and Rivets.js
jsFiddle here: http://jsfiddle.net/rhodee/3qcYQ/1/
From the Rivets.js site
Iteration Binding
Even though a binding routine for each-item will likely be included in Rivets.js, you
can use the data-html binding along with a set of formatters in the interim to do
sorting and iterative rendering of collections (amongst other cool things).
<ul data-html="model.tags | sort | tagList"></ul>
Expanding on this answer:
As of 0.3.2 there is now a data-each-[item] binding for exactly this purpose.
Note that you will need to specifically modify your Rivets adapter to work with a Backbone Collection, as the out-of-the-box examples on the Rivets.js site do not fly with this use case.
You'll need something like this in your rivets.configure({ adapter: ... }):
...
read: function( obj, keypath ) {
return obj instanceof Backbone.Collection
? obj["models"]
: obj.get(keypath)
}
And the JS Fiddle: http://jsfiddle.net/tigertim719/fwhuf/70/
For bonus points, Collections embedded in Models will require additional handling in your adapter.
For more information, check this post on Rivets.js Github issues:
Binding to Backbone.Collection with the data-each- binding
As of 0.3.2 there is now a data-each-[item] binding for exactly this purpose.
<ul>
<li data-each-todo="list.todos">
<input type="checkbox" data-checked="todo.done">
<span data-text="todo.summary"></span>
</li>
<ul>
For previous versions of Rivets.js, the work-around that you've referred to is to implement the iterative rendering with a formatter — for example you would have a data-html binding with model.items | itemList where the itemList formatter just loops over the array and returns some rendered HTML.
rivets.formatters.itemList = (array) ->
("<li>#{item.name}</li>" for item in array).join ''
UnderscoreJS is integrated in Backbone so you can use its native methods like _.each() or use the integrated Backbone Collection underscore methods.
Is it this what you are looking for?
cayuu's answer was correct.
But the rivets.js reference in the fiddle was not working, so the result is not displaying.
Check out the version below to see the action.
http://jsfiddle.net/tigertim719/fwhuf/70/
rivets.configure({
adapter: {
subscribe: function(obj, keypath, callback) {
obj.on('change:' + keypath, callback);
},
unsubscribe: function(obj, keypath, callback) {
obj.off('change:' + keypath, callback);
},
read: function(obj, keypath) {
return obj instanceof Backbone.Collection ? obj["models"] : obj.get(keypath);
},
publish: function(obj, keypath, value) {
obj.set(keypath, value);
}
}
});
The most important part is
read: function(obj, keypath) {
return obj instanceof Backbone.Collection ? obj["models"] : obj.get(keypath);
},
That tells rivets how to read your collection and model from Backbone.
*UPDATE: See final answer code in the last code block below.*
Currently I am having an issue displaying a collection in a collection view. The collection is a property of an existing model like so (pseudo code)
ApplicationVersion { Id: 1, VersionName: "", ApplicationCategories[] }
So essentially ApplicationVersion has a property called ApplicationCategories that is a javascript array. Currently when I render the collection view associated with ApplicationCategories nothing is rendered. If I debug in Chrome's javascript debugger it appears that the categories have not been populated yet (so I assume ApplicationVersion has not been fetched yet). Here is my code as it stands currently
ApplicationCategory Model, Collection, and Views
ApplicationModule.ApplicationCategory = Backbone.Model.extend({
urlRoot:"/applicationcategories"
});
ApplicationModule.ApplicationCategories = Recruit.Collection.extend({
url:"/applicationcategories",
model:ApplicationModule.ApplicationCategory,
initialize: function(){
/*
* By default backbone does not bind the collection change event to the comparator
* for performance reasons. I am choosing to not preoptimize though and do the
* binding. This may need to change later if performance becomes an issue.
* See https://github.com/documentcloud/backbone/issues/689
*
* Note also this is only nescessary for the default sort. By using the
* SortableCollectionMixin in other sorting methods, we do the binding
* there as well.
*/
this.on("change", this.sort);
},
comparator: function(applicationCategory) {
return applicationCategory.get("order");
},
byName: function() {
return this.sortedBy(function(applicationCategory) {
return applicationCategory.get("name");
});
}
});
_.extend(ApplicationModule.ApplicationCategories.prototype, SortableCollectionMixin);
ApplicationModule.ApplicationCategoryView = Recruit.ItemView.extend({
template:"application/applicationcategory-view-template"
});
ApplicationModule.ApplicationCategoriesView = Recruit.CollectionView.extend({
itemView:ApplicationModule.ApplicationCategoryView
});
ApplicationCategory template
<section id="<%=name%>">
<%=order%>
</section>
ApplicationVersion Model, Collection, and Views
ApplicationModule.ApplicationVersion = Backbone.Model.extend({
urlRoot:"/applicationversions"
});
ApplicationModule.ApplicationVersions = Recruit.Collection.extend({
url:"/applicationversions",
model:ApplicationModule.ApplicationVersion
});
ApplicationModule.ApplicationVersionLayout = Recruit.Layout.extend({
template:"application/applicationversion-view-template",
regions: {
applicationVersionHeader: "#applicationVersionHeader",
applicationVersionCategories: "#applicationVersionCategories",
applicationVersionFooter: "#applicationVersionFooter"
}
});
ApplicationModule.ApplicationVersionController = {
showApplicationVersion: function (applicationVersionId) {
ApplicationModule.applicationVersion = new ApplicationModule.ApplicationVersion({id : applicationVersionId});
var applicationVersionLayout = new Recruit.ApplicationModule.ApplicationVersionLayout({
model:ApplicationModule.applicationVersion
});
ApplicationModule.applicationVersion.fetch({success: function(){
var applicationVersionCategories = new Recruit.ApplicationModule.ApplicationCategoriesView({
collection: ApplicationModule.applicationVersion.application_categories
});
applicationVersionLayout.applicationVersionCategories.show(applicationVersionCategories);
}});
// Fake server responds to the request
ApplicationModule.server.respond();
Recruit.layout.main.show(applicationVersionLayout);
}
};
Here is my ApplicationVersion template
<section id="applicationVersionOuterSection">
<header id="applicationVersionHeader">
Your Application Header <%= id %>
</header>
<section id="applicationVersionCategories">
</section>
<footer id="applicationVersionFooter">
Your footer
</footer>
One thing to note I am currently using Sinon to mock my server response, but I don't think this is causing the issues as it is responding with the information as I expect looking through the javascript debugger (and like I said it is displaying ApplicationVersion id correctly). I can provide this code as well if it helps
It is currently displaying the application version id (id in the template), so I know it is fetching the data correctly for normal properties, it just is not rendering my ApplicationCategories javascript array property.
So ultimately I am binding to the success of the fetch for ApplicationVersion, then setting up the view for the ApplicationCategories. Since this isn't working like I expect I am wondering if there is a better way to create this collection view?
Thanks for any help
UPDATE: Working code example that Derek Bailey lead me too.
ApplicationModule.ApplicationVersionController = {
showApplicationVersion: function (applicationVersionId) {
ApplicationModule.applicationVersion = new ApplicationModule.ApplicationVersion({id : applicationVersionId});
var applicationVersionLayout = new Recruit.ApplicationModule.ApplicationVersionLayout({
model:ApplicationModule.applicationVersion
});
ApplicationModule.applicationVersion.fetch();
// Fake server responds to the request
ApplicationModule.server.respond();
Recruit.layout.main.show(applicationVersionLayout);
var applicationVersionCategories = new Recruit.ApplicationModule.ApplicationCategoriesView({
collection: new Backbone.Collection(ApplicationModule.applicationVersion.get('application_categories'))
});
applicationVersionLayout.applicationVersionCategories.show(applicationVersionCategories);
}
};
Marionette's CollectionView requires a valid Backbone.Collection, not a simple array. You need to create a Backbone.Collection from your array when passing it to the view:
new MyView({
collection: new Backbone.Collection(MyModel.Something.ArrayOfThings)
});
My Code:
I am new to Backbone.js and trying to build an app with Backbone.js and PHP. When I am trying to call add in the router, I am getting error:
Uncaught TypeError: Object [object Object] has no method 'set'.
Please help me to find my mistake.
Thanks.
// Models
window.Users = Backbone.Model.extend({
urlRoot:"./bb-api/users",
defaults:{
"id":null,
"name":"",
"email":"",
"designation":""
}
});
window.UsersCollection = Backbone.Collection.extend({
model:Users,
url:"./bb-api/users"
});
// Views
window.AddUserView = Backbone.View.extend({
template:_.template($('#new-user-tpl').html()),
initialize:function(){
this.model.bind("click", this.render, this);
},
render:function(){
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events:{
"click .add":"saveUser"
},
saveUser:function(){ alert('saveUser');
this.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(this.model.isNew()){
this.model.create(this.model);
}
return false;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"welcome",
"users":"list",
"users/:id":"userDetails",
"add":"addUser"
},
addUser:function(){
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
$('#content').html(this.addUserView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
As suggested in the comments, the problem starts here here:
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
and finishes here:
saveUser:function(){ alert('saveUser');
this.model.set({
By passing a collection in place of a model you create confusion, and as a result later in the saveUser function you try to call a Backbone.Model method (set) on a Backbone.Collection instance.
Note: As of version 1.0.0 Backbone.Collection now has a set method. In previous versions, such as the one used by the question's author, that method was instead called update.
There are several steps you can take to clarify this code. For starters, I would rename your model and collection classes so that it's clear that the model is the singular form and the collection is the plural form:
window.Users => window.User
window.UsersCollection => window.Users
Next, I would create a new User model, instead of a Users collection, and pass that to your view:
this.addUserModel = new User();
this.addUserView = new AddUserView({model:this.addUserModel});
Finally, I'd remove these lines:
if(this.model.isNew()){
this.model.create(this.model);
}
For one thing, the model will always be new (as you just created it before passing it in), but more importantly you don't need to call the Collection's create method because that method creates a new model, when you already have one created. Perhaps what you should add instead is :
this.model.save();
if your intent is to save the model to your server.
Since you already specified a urlRoot for the model, that should be all you need to create a new model, pass it to your view, have your view fill in its attributes based on DOM elements, and finally save that model's attributes to your server.
I think you are facing problem with object scope. When event fired it send to event object to that function. Just try this it may work
Declare global variable with the current view inside the initialize
initialize : function(){ self = this; }
then change this to self,
saveUser:function(){ alert('saveUser');
self.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(self.model.isNew()){
self.model.create(this.model);
}
return false;
}