i have this result from the restful service i have:
NOTE: Response is in JSON format it's the plugin from chrome which displays it like that.
if you look at image two [the one above this] the models attributes is Items then each item are under Items. What should i do to access item?
my problem is i can't access or retrieve data of each item from this result. i must not change anything from the server side though. I'm using backbone with this code.
window.Item = Backbone.Model.extend();
window.ItemCollection = Backbone.Collection.extend({
model: Item,
url: 'http://localhost/InterprisePOS/Product/loaditembycategory/Event Materials'
});
window.ItemListView = Backbone.View.extend({
tagName : 'ul',
initialize: function(){
this.model.bind("reset",this.render,this);
},
render: function(eventName){
_.each(this.model.models.Items, function(item){
$(this.el).append(new ItemListItemView({model:item}).render.el);
},this);
return this;
}
});
window.ItemListItemView = Backbone.View.extend({
template : _.template($("#item-list").html()),
render: function(eventName){
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
var AppRouter = Backbone.Router.extend({
routes:{
"":"list"
},
list:function(){
this.itemList = new ItemCollection();
this.itemListView = new ItemListView({model:this.itemList});
this.itemList.fetch();
$("#itemContainer").html(this.itemListView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
UPDATE
I was able to correct the my problem with nested json objects. Now the Models attrib or my Collection is populated with individual items. But still the problems is it doesn't work and doesn't display my views.
This is the code i added:
parse: function(response) {
return response.Items;
}
UPDATE
I Finally answered my question! horray! somehow i forgot to put "()" on render in my ItemListview. and also $("#ItemContainer") doesn't seem to work so i made it to $('#ItemContainer) now i'm displaying the Details from my model.
I'm fairly certain that Backbone by default uses JSON for all requests and has no idea what to do with XML, you'll probably need to override the sync method for the collection in order to us use XML.
The following should be helpful in addressing your issue: http://newcome.wordpress.com/2011/02/20/consuming-xml-web-services-in-backbone-js-using-jath/
They use a 3rd party library xml parser in the sync parse operation which converts the model to JSON which Backbone can use natively.
Make sure the response is returned as JSON. Backbone works on JSON data by default.
Related
I've been diving into the scary stuff recently.. :) scary stuff being the source of popular js frameworks like backbone.js, angular.js, vue.js and so on.
I'll take Backbone as an example here. I am trying to figure out how is a model attached to the view?
Here is the code and if someone could just point out the part where this is happening, would be awesome!
https://github.com/jashkenas/backbone/blob/master/backbone.js
Actually, the part I don't understand is that there is not innerHTML called anywhere, so how is the element being populated with the data?
Backbone is not Angular, it doesn't bind model to html for you, also it does not actually renders views for you, you have to implement render methods in your views and call them when you find appropriate. In fact, I think it might be confusing to developer coming from 2-way binding frameworks. Backbone is all about giving all the control to the developer, but you have to do all the work yourself.
Minimal model => view flow example would be something like
var MyModel = Backbone.Model.extend({
defaults: {
test: 'test',
},
initialize: function (options) {
console.log(this.get('test'));
},
});
var MyView = Backbone.View.extend({
el: '#your-el',
initialize: function (options) {
this.template = _.template('<div><%= test %></div>');
this.listenTo(this.model, 'change', this.render);
},
render: function () {
// rendering happens here, innerHTML, $.html(), whichever you prefer
// and you can pass model data as this.model.toJSON()
// or you can pass an object with data
// also, you probably will need a templating library or use
// bundled underscore _.template(html, data) method to render blocks of html
// for example, with underscore
this.$el.html(this.template(this.model.toJSON()));
return this; // for chaining
},
});
var myModel = new MyModel();
var myView = new MyView({
model: myModel,
});
myModel.set('test', 'newValue'); // view should render after this call
Check the list of built-in events at backbonejs.org.
I have a webapp based on Backbone.js with a list. The entries of the list are coming from a REST API. This list (JSON array) updates from time to time. I want to update my list in the frontend too, without reloading the page.
I thought about using a poller to update the file list with every new object returned by the API. However, the poller is not the problem here, I first need a function to add just the new models to the file list.
The API returns a JSON list, based on this model:
Xff = Backbone.Model.extend({
defaults: {
id: null,
name: "",
language: "en",
timestamp: 0,
status: null,
progress: 10,
duration: 0
}
});
This is the collection. restUri points to the REST API and with /files it gets the complete file list.
XffCollection = Backbone.Collection.extend({
model: Xff,
comparator: function(a, b) {
return (a.get("timestamp") > b.get("timestamp") ? -1 : 1);
},
url: restUri + "files"
});
This is the AppView object. It uses the XffCollection, as explained above.
app = new AppView({
collection: new XffCollection()
});
AppView is a regular backbone view...
AppView = Backbone.View.extend({ .... })
Using app.collection.fetch() I can fire the request (visible in firebug), but the list is not updated. I also have a addAll() function, but then it just appends the new file list to the old file list.
The addAll:
addAll: function() {
this.collection.chain().sort().each(this.addOne, this);
}
This is the addOne:
addOne: function(xff) {
var v = new XffView({model: xff});
this.xffViews.push(v);
$("#xffs").append(v.render().el);
}
How can I add just the new entries to the list?
UPDATE
While kindasimples anwser works now, the filelist in the frontend is not sorted anymore using the comparator defined in the collection (with the timestamp). Using addAll() in the bottom of the comparator, the list is sorted.
To provide additional information, here are more parts of the overall backbone code: http://pastebin.com/rR39x3Y1
From the backbone.js docs:
collection.sort([options])
Force a collection to re-sort itself. You don't need to call this under normal circumstances, as a collection with a comparator will sort itself whenever a model is added. To disable sorting when adding a model, pass {sort: false} to add. Calling sort triggers a "sort" event on the collection.
But it does not sort itself. Also calling app.collection.sort() right after the fetch does not help.
UPDATE 2
I fixed it by sorting the array in the API before returning it. That's not how it was meant to be but at least it works now.
You have the right idea. addOne() will render individual items when you do your initial setup after a fetch to populate items. You can add a listener to the collection events to add the new items. Collection.Fetch does what you want by adding new models to the collection and leaving the old in tact (as long as you don't pass the {reset:true} flag as a parameter)
So, on your view add the listener to the initialize hook
initialize: function() {
this.listenTo(this.collection, "add", this.addOne)
}
You will probably want to define the idAttribute on your Xff Model so that backbone can identify new items properly.
I am wondering if there are any pointers on the best way of "fetching" and then binding a collection of data to a view within Backbone.js.
I'm populating my collection with the async fetch operation and on success binding the results to a template to display on the page. As the async fetch operation executes off the main thread, I one loses reference to the backbone view object (SectionsView in this case). As this is the case I cannot reference the $el to append results. I am forced to create another DOM reference on the page to inject results. This works but I'm not happy with the fact that
I've lost reference to my view when async fetch is executed, is there a cleaner way of implementing this ? I feel that I'm missing something...Any pointers would be appreciated.
SectionItem = Backbone.Model.extend({ Title: '' });
SectionList = Backbone.Collection.extend({
model: SectionItem,
url: 'http://xxx/api/Sections',
parse: function (response) {
_(response).each(function (dataItem) {
var section = new SectionItem();
section.set('Title', dataItem);
this.push(section);
}, this);
return this.models;
}
});
//Views---
var SectionsView = Backbone.View.extend(
{
tagName : 'ul',
initialize: function () {
_.bindAll(this, 'fetchSuccess');
},
template: _.template($('#sections-template').html()),
render: function () {
var sections = new SectionList();
sections.fetch({ success: function () { this.SectionsView.prototype.fetchSuccess(sections); } }); //<----NOT SURE IF THIS IS THE BEST WAY OF CALLING fetchSuccess?
return this;
},
fetchSuccess: function (sections) {
console.log('sections ' + JSON.stringify(sections));
var data = this.template({
sections: sections.toJSON()
});
console.log(this.$el); //<-- this returns undefined ???
$('#section-links').append(data); //<--reference independent DOM div element to append results
}
}
);
darthal, why did you re-implement parse? It seems to do exactly what Backbone does by default (receive an array of models from the AJAX call and create the models + add them to the collection).
Now on to your question... you are supposed to use the reset event of the Collection to do the rendering. You also have an add and remove when single instances are added or deleted, but a fetch will reset the collection (remove all then add all) and will only trigger one event, reset, not many delete/add.
So in your initialize:
this.collection.on("reset", this.fetchSuccess, this);
If you are wondering where the this.collection is coming from, it's a param you need to give to your view when you create it, you can pass either a model or a collection or both and they will automatically be added to the object (the view)'s context. The value of this param should be an instance of SectionList.
You'll also have to update fetchSuccess to rely on this.collection instead of some parameters. Backbone collections provide the .each method if you need to iterate over all the models to do stuff like appending HTML to the DOM.
In most cases you don't need a fetchSuccess, you should just use your render: when the collection is ready (on 'reset'), render the DOM based on the collection.
So to summarize the most general pattern:
The collection should be independent from the view: you give the collection as a param to the view creation, the collection shouldn't be created from a specific view.
You bind the View to the collection's reset event (+add, remove if you need) to run a render()
this.collection.on("reset", this.render, this);
You do a fetch on the collection, anytime (probably when you init your app).
A typical code to start the app would look something like this:
var sections = new SectionList();
var sectionsView = new SectionsView({collection: sections});
sections.fetch();
Because you bound the reset event in the view's initialize, you don't need to worry about anything, the view's render() will run after the fetch.
Trying to populate a Collection from a list of values, I am getting an error about the Collection's model's prototype being undefined. Looking at this question about a similar problem, I have checked that the Model is actually created before the collection is instanced, to the best of my ability.
The error is being thrown in one of the event handlers of the Marionette CompositeView that holds the Collection, after fetching the data from the server and trying to reset the collection with the list of values from the data which should be populated into it.
Note: Using Backbone 0.9.10
The Model
MyItemModel = Backbone.Model.extend({});
The Collection
MyCollection = Backbone.Collection.extend({
model: MyItemModel
});
The CompositeView's relevant code
MyCompositeView = Backbone.Marionette.CompositeView.extend({
initialize: function(options) {
_.bindAll(this);
this.model = new MyCompositeViewModel();
this.collection = new MyCollection();
},
//This event handler gets properly fired and run.
on_event: function() {
var that = this;
// The data comes through fine with this `fetch`
this.model.fetch({success: function() {
var collection_results= that.model.get("collection_results");
// The error fires in this line
that.collection.reset(collection_results);
that.render();
});
}
})
The error
The error happens in the add function in Backbone, when doing a get for the model object, checking to see if it is a duplicate. The failing code is here:
// Get a model from the set by id.
get: function(obj) {
if (obj == null) return void 0;
// The error originates from this line
this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
},
this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
Here, the this.model.prototype.idAttribute fails because the prototype for the model is not defined.
Why is this happening, and how can it be fixed?
Thanks a lot!
The reason is, in Babkbone 0.9.10, if you call collection.reset(models) without options, the models will be passed to collection.add() which strictly needs real models as argument.
But, in fact, the arguments you passed are not real models. They are just an array of hash attributes.
Two options to fix:
Option 1: Call the reset with a parse option
that.collection.reset(collection_results, {parse: true});
Then reset will parse the array of hashes and set them as model.
Option 2: Upgrade to latest version Backbone 1.1.0.
Here reset() no longer pass responsibility to add() but use set() smartly. This option is recommended. And you don't need options here.
that.collection.reset(collection_results)
Another point
May I suggest you not to define model in CompositeView? CompositeView is for collection, not model. Of course I understand the model here is just to hold and fetch some data, but it would be really confusing for the code to be read by another developer, as well as your own maintaining.
To get bootstrapped data, you can load the data at first request and use conventional way to put it into collection. http://backbonejs.org/#FAQ-bootstrap
Can I somehow let my empty view know about my collection, so that I can potentially add to my collection from the empty view?
Is it not best practice if I do something like :
EmailsView = Backbone.Marionette.CollectionView.extend({
emptyView: EmptyMealView,
itemView : EmailView,
itemViewOptions : function(){
return {collection : this.collection}
},
});
So I can do something like this in the empty view:
EmptyMealView = Backbone.Marionette.ItemView.extend({
events:{
'click .sendEmail' : 'sendEmail',
},
sendFakeEmail: function(e){
var myEmail = new Email({
Date : new Date(),
Type : 3
});
var that = this;
myEmail.save(null,{
success:function(model){
that.collection.add(model);
}
});
}
});
What you have is perfectly fine. ItemViewOptions is created so that you could pass data from the parent view to its child views.
The emptyView is actually a fully qualified child of the collectionView.
In backbone, an empty collection actually has a weird blank model in it, and your empty view actually gets this empty model. In addition, the empty view will get options passed in by the itemviewOptions method. You may need to implement serializeData in your empty view in order to retrieve data you pass in as options that isn't part of the model.