In my backbone function, while the name get change the change function not at all triggering.. any one suggest me the right way to get it.. (actually i need to get changed stuff and need to update);
code :
(function($){
var list = {};
list.model = Backbone.Model.extend({
defaults:{
name:'need the name'
},
initialize:function(){
this.bind('change:name', function(model) {
console.log('Model->change()', model);
});
}
});
list.collect = Backbone.Collection.extend({
model:list.model,
url : 'data/names.json',
initialize:function(){
this.fetch({update:true});
this.keepUpdate();
},
keepUpdate:function(){
var that = this;
var updateData = function(){
that.fetch({update:true});
myTimeout = setTimeout(updateData,10000);
}
var myTimeout = setTimeout(updateData,10000);
}
});
list.view = Backbone.View.extend({
initialize:function(){
this.collection = new list.collect();
this.collection.on("update", this.render, this);
this.collection.bind("change:name", function(model, attributes){
console.log(model,attributes,'property changed'); // this is not triggering at all..
});
},
render:function(data){
_.each(this.collection.models, function(data){
//console.log(data.get('name')); it works fine
})
},
updateName:function(){
console.log('updated called');
}
});
var newView = new list.view();
})(jQuery)
Collection.fetch doesn't trigger the change event. You only get the reset event. If you need more granular events, consider calling fetch with the options {update:true}.
that.fetch({update:true});
That will trigger change event for every model that was already in the collection, and add if the model was previously not in the collection.
Try removing keepUpdate from the collection and put a setTimeout in the initialize function of the view at the end. I suggest that fetch is called from the view as well as this.collection.fetch() instead of the collection's initialize function. Makes your code more reusable.
I'm not sure I understand your question. What are you trying to achieve ?
I don't think that fetch accepts {add:true} as a parameter (I just checked the source code and it does not appear anywhere).
When fetch completes, it only triggers a reset event (not an add). You should listen to that if you want to do something when the content of the collection changes. You can also simplify listen to change.
Related
I have a single page app built with Marionette that has a main view with a list of subviews.
The JSON which holds all application data is updated constantly. I've tried to separate region show code so that it will be run just once and not on every render.
Now the render event is fired on every timeout loop even though the JSON is static data and therefore change event should not call render. What is wrong? I assume it has something to do with .set but is there any other way to load the response from an array variable to the subview collection, since fetch will allow only url attribute and will not accept array variable?
This example is an extremely simplified version of the application code to concentrate on this specific problem.
Controller:
var Controller = {
showMainView: function(id){
// create model and fetch data on startup
var mainElement = new mainElement();
var mainElementFetched = mainElement.fetch({url: 'http://json.json'});
// fetch done, create view, show view in region, setTimeout
mainElementFetched.done(function(data){
var mainElementView = mainElementView({model:mainElement});
App.mainRegion.show(mainElementView);
setTimeout(updateJSON, 5000);
}
// timeOut loop to check for JSON changes
var updateJSON = function(){
mainElement.fetch({url: 'http://json.json'});
App.timeOut = setTimeout(updateJSON, 5000);
}
}
}
MainElement Model:
MainElement = Backbone.Model.extend({
parse : function(response){
// parsing code
return response;
}
});
MainElementView (Layout):
MainElementView = Backbone.Marionette.Layout.extend({
template: "#main-template",
initialize:function(){
//create collection for subelements
this.subElementCollection = new SubElementCollection();
//listen to change event, and fire callback only when change in model is detected
this.model.on('change', this.render, this);
},
regions:{
subsection : ".subsection"
},
onShow: function(){
// show subelements in subsection region when mainelementview is shown on screen, but not show on every render
this.subsection.show(new SubElementCompositeView({collection:this.subElementCollection}))
},
onRender : function(){
var response = this.model.response;
// get subelements when change event fires and parse the response
this.subElementCollection.set(response,{parse:true});
}
});
SubElement Model, Collection, ItemView, CompositeView:
SubElement = Backbone.Model.extend({});
SubElementCollection = Backbone.Collection.extend({
model:SubElement,
comparator : function(model){
var price = model.get('price');
return -(price);
},
parse:function(response){
// parsing code to get data to models from differents parts of JSON
return response;
}
});
SubElementItemView = Backbone.Marionette.ItemView.extend({
template: "#subelement-template",
tagName: "tr"
});
SubElementCompositeView = Backbone.Marionette.CompositeView.extend({
template: "#subelements-template",
tagName : "table",
itemView:SubElementItemView,
itemViewContainer : "tbody",
initialize: function(){
this.collection.on('change', this.render, this);
},
appendHtml : function(collectionView,itemView,index){
// SORTING CODE
},
onRender:function(collectionView,itemView,index){
// ADD IRRELEVANT EXTERNAL STUFF TO TEMPLATE AFTER RENDER
}
});
Check out the documentation. It says:
A "change" event will be triggered if the server's state differs from the current attributes.
In your MainElementView where you bind to your model's change event: this.model.on('change', this.render, this);, you are actually saying every time your model changes, call render.
If re-drawing the whole view is too slow, due to the amount of changes. Why don't you make your rendering more fine-grained? For example you could listen for specific change events and just change the DOM elements which need changing:
this.model.on('change:Name', function () {
this.$('[name=Name]').html(this.model.get('Name'));
}, this);
It is more work to set this up, but you could make it a bit cleverer by matching the model property names to your DOM element name or something.
In my backbone function, while the name get change the change function not at all triggering.. any one suggest me the right way to get it.. (actually i need to get changed stuff and need to update);
code :
(function($){
var list = {};
list.model = Backbone.Model.extend({
defaults:{
name:'need the name'
},
initialize:function(){
this.bind('change:name', function(model) {
console.log('Model->change()', model);
});
}
});
list.collect = Backbone.Collection.extend({
model:list.model,
url : 'data/names.json',
initialize:function(){
this.fetch({update:true});
this.keepUpdate();
},
keepUpdate:function(){
var that = this;
var updateData = function(){
that.fetch({update:true});
myTimeout = setTimeout(updateData,10000);
}
var myTimeout = setTimeout(updateData,10000);
}
});
list.view = Backbone.View.extend({
initialize:function(){
this.collection = new list.collect();
this.collection.on("update", this.render, this);
this.collection.bind("change:name", function(model, attributes){
console.log(model,attributes,'property changed'); // this is not triggering at all..
});
},
render:function(data){
_.each(this.collection.models, function(data){
//console.log(data.get('name')); it works fine
})
},
updateName:function(){
console.log('updated called');
}
});
var newView = new list.view();
})(jQuery)
Collection.fetch doesn't trigger the change event. You only get the reset event. If you need more granular events, consider calling fetch with the options {update:true}.
that.fetch({update:true});
That will trigger change event for every model that was already in the collection, and add if the model was previously not in the collection.
Try removing keepUpdate from the collection and put a setTimeout in the initialize function of the view at the end. I suggest that fetch is called from the view as well as this.collection.fetch() instead of the collection's initialize function. Makes your code more reusable.
I'm not sure I understand your question. What are you trying to achieve ?
I don't think that fetch accepts {add:true} as a parameter (I just checked the source code and it does not appear anywhere).
When fetch completes, it only triggers a reset event (not an add). You should listen to that if you want to do something when the content of the collection changes. You can also simplify listen to change.
I wish to read a whole database table to fill a Backbone.js Collection, before updating a View.
I am using fetch and listening to the reset event.
My problem is the reset event fires up before the http request is made to the server.
My question is: how can I render the view AFTER the data is received back from the server on a fetch?
Here is a jsfiddle showing the problem (with a debugger placed at reset):
http://jsfiddle.net/GhaPF/16/
The code:
$(document).ready(function() {
var Item = Backbone.Model.extend({
urlRoot : './items'
});
var ItemList = Backbone.Collection.extend({
model: Item,
url: './items/',
});
var ItemListView = Backbone.View.extend({
el: 'body',
initialize: function(myitemList) {
this.itemlist = myitemList;
this.itemlist.bind('reset', this.debuggThis());
},
debuggThis: function() {
debugger;
},
render: function() {
},
events: {
"keypress #new-item": "createOnEnter"
},
createOnEnter: function(e) {
}
});
$("#new-item").focus();
var itemlist = new ItemList();
var myitemListView = new ItemListView(itemlist);
itemlist.fetch();
});
The following code works, but it just doesn't feel like proper backbone.js (MVC) code since it would be placed outside of the View definition:
itemlist.fetch().complete(function(){
Maybe the issue is this line:
this.itemlist.bind('reset', this.debuggThis());
Should actually be:
this.itemlist.bind('reset', this.debuggThis);
Your debugThis function was getting run at the time you set up the listener for the 'reset' event - not when the event is triggered. This was telling JavaScript that you wanted debugThis to return a callback function instead of having debugThis "be" the callback function.
Also, orangewarp's comment about passing 'this' as the third parameter is probably relevant too. Sot it would end up as:
this.itemlist.bind('reset', this.debuggThis, this);
That's strange. When you fetch() the reset event should be triggered AFTER your collection is populated. So I'm thinking the phenomena that reset happens before the http request is fired up may not be what you think it is.
Instead of using the complete... you could always just use the success callback option like this:
itemlist.fetch({
success: function() {
// Whatever code you want to run.
itemlist.debuggThis();
}
});
Also, when binding your reset you probably want this:
this.itemlist.bind('reset', this.debuggThis, this);
I'm learning Backbone.js for the first time and I'm having an issue trying to get a custom event from triggering (or from the View from recognising when it's been triggered)?
You can see my Collection code here: https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/App/main.js#L72-86 which when initialized it triggers a custom collection:init event.
var Contacts = Backbone.Collection.extend({
model: Contact,
initialize: function(){
this.trigger('collection:init');
this.bind('add', this.model_added, this);
},
model_added: function(){
console.log('A new model has been created so trigger an event for the View to update the <select> menu');
}
});
But later on in my View where I'm listening for that event I can't get the function populate to fire: https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/App/main.js#L90-107
var ContactsView = Backbone.View.extend({
initialize: function(){
console.log(contacts.models, 'get initial model data and populate the select menu?');
},
events: {
'collection:init': 'populate',
'change select': 'displaySelected'
},
populate: function(){
console.log('populate the <select> with initial Model data');
},
displaySelected: function (event) {
console.log('get model data and display selected user', event);
}
});
Any ideas what I'm doing wrong?
The events hash in a view is used to bind events from the DOM to your view, e.g. events raised by the elements in your rendered view. To listen to events raised by your collection, you will have to set them manually:
var ContactsView = Backbone.View.extend({
initialize: function(){
contacts.on("collection:init",this.populate,this);
}
...
});
Note that you are using a global contacts variable, I would advise to use Backbone mechanisms and pass your collection to the constructor, as you do with the el:
var ContactsView = Backbone.View.extend({
initialize: function(){
console.log(this.collection.models);
this.collection.on("collection:init",this.populate,this);
}
...
});
var contacts_view = new ContactsView({
el: $('#view-contacts'),
collection:contacts
});
As #mu said in the comments, as is, your event won't do anything since you trigger it in the initialize method of the collection, which is automatically called by the constructor of the collection therefore before you can bind anything in the view. See this Fiddle to visualize the call order : http://jsfiddle.net/yRuCN/
Trigger it elsewhere, or, if I read correctly your intent, you (probably) want to use the built in reset event:
var ContactsView = Backbone.View.extend({
initialize: function(){
this.collection.on("reset",this.populate,this);
}
...
});
See http://jsfiddle.net/yRuCN/1/ for an example with potential uses.
I've been staring at this for a while and trying various tweaks, to no avail.
Why am I getting a "this.model is undefined" error at
$(function(){
window.Sentence = Backbone.Model.extend({
initialize: function() {
console.log(this.toJSON())
}
});
window.Text = Backbone.Collection.extend({
model : Sentence,
initialize: function(models, options){
this.url = options.url;
}
});
window.SentenceView = Backbone.View.extend({
initialize : function(){
_.bindAll(this, 'render');
this.template = _.template($('#sentence_template').html());
},
render : function(){
var rendered = this.template(this.model.toJSON());
$(this.el).html(rendered);
return this;
}
})
window.TextView = Backbone.View.extend({
el : $('#notebook') ,
initialize : function(){
_.bindAll(this, 'render');
},
render : function(){
this.collection.each(function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
return this;
}
});
function Notebook(params){
this.text = new Text(
// models
{},
// params
{
url: params.url
}
);
this.start = function(){
this.text.fetch();
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
};
}
window.notebook = new Notebook(
{ 'url': 'js/mandarin.js' }
);
window.notebook.start();
})
There's an online version wher eyou can see the error in a console at:
http://lotsofwords.org/languages/chinese/notebook/
The whole repo is at:
https://github.com/amundo/notebook/
The offending line appears to be at:
https://github.com/amundo/notebook/blob/master/js/notebook.js#L31
I find this perplexing because as far as I can tell the iteration in TextView.render has the right _.each syntax, I just can't figure out why the Sentence models aren't showing up as they should.
var view = new SentenceView({model: sentence});
I'm pretty sure when you pass data to a backbone view constructor, the data is added to the Backbone.View.options property.
Change this line
var rendered = this.template(this.model.toJSON());
to this
var rendered = this.template(this.options.model.toJSON());
and see if it works
UPDATE:
From the doco:
When creating a new View, the options you pass are attached to the view as this.options, for future reference. There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, and tagName
So, disregard the above advice - the model should by default be attached directly to the object
Things to check next when debugging:
confirm from within the render() method that this is actually the SentenceView object
confirm that you are not passing in an undefined sentence here:
var view = new SentenceView({model: sentence});
UPDATE2:
It looks like the collection is borked then:
this.textView = new TextView({
collection: this.text
});
To debug it further you'll need to examine it and work out what's going on. When I looked in firebug, the collection property didn't look right to me.
You could have a timing issue too. I thought the fetch was asynchronous, so you probably don't want to assign the collection to the TextView until you are sure it has completed.
Backbone surfaces underscore.js collection methods for you so you can do this. See if this works for you:
this.collection.each(function(sentence) {
// YOUR CODE HERE
});
I think the problem is on line 48 of notebook.js shown below:
render : function(){
_(this.collection).each(function(sentence){
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
The problem is you are wrapping the collection and you don't have to. Change it to
this.collection.each(function(sentence){ ...
hope that fixes it
EDIT:
OK i'm going to take another crack at it now that you mentioned timing in one of your comments
take a look at where you are fetching and change it to this:
this.start = function(){
this.text.fetch({
success: _.bind( function() {
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
}, this)
);
};
I typed this manually so there may be mismatching parentheses. The key is that fetch is async.
Hope this fixes it
try using _.each
_.each(this.collection, function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
},this);