How can I know which attribute of the view model is changed in the render function? (In the render function, "e" is the model, but I need only the attribute which is changed.) I need to know this to know which template to use. Or is there another method to do this?
window.Person = Backbone.Model.extend({});
window.Njerzit = Backbone.Collection.extend({
model: Person,
url: '/Home/Njerzit'
});
window.PersonView = Backbone.View.extend({
tagName: 'span',
initialize: function () {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
},
render: function (e) {
//if model name is changed, I need to render another template
this.template = _.template($('#PersonTemplate').html());
var renderContent = this.template(this.model.toJSON());
$(this.el).html(renderContent);
return this;
}
});
I believe the changedAttributes function is what you're looking for
changedAttributesmodel.changedAttributes([attributes])
Retrieve a hash of only the model's attributes that have changed. Optionally,
an external attributes hash can be passed in, returning the attributes
in that hash which differ from the model. This can be used to figure
out which portions of a view should be updated, or what calls need to
be made to sync the changes to the server.
or to check if a specific attribute has changed use the hasChanged function
hasChangedmodel.hasChanged([attribute])
Has the model changed since the last "change" event? If an attribute is passed, returns true
if that specific attribute has changed.
var nameChanged = this.model.hasChanged("name");
From Backbone Docs
You can bind to change:name if you only want to notify if the name has changed: http://documentcloud.github.com/backbone/#Model-set
Related
I have a base model, that is creating a view with several div's. It is not actually a form; but it is acting as a form. I have variables being set with defaults as well. Here's my model right now:
var BaseModel = require('base-m');
var SomeModel = BaseModel.extend({
defaults: function() {
return {
FirstName : null,
LastName : null,
Age : null,
State : null
};
}
update: function() {
return {
FirstName : $('[name="FirstName]').val()
};
console.log(FirstName);
}
});
I am trying to update the model with the particular value of whatever is entered. Do I need to use an event? I am doing this because I want to retrieve the updated variable for output purposes.
Also; (if it's different), lets say it's a drop down menu like states..? Would I update it similar to a text field like first name?
Thanks
It appears your model is accessing the DOM. Usually, your view would deal with the DOM, extracting information then updating the model.
So for example, create a view with a constructor that:
Creates your input elements and put them in an attribute called $el; then
Adds $el to the DOM; then
Binds event listeners to $el.
These event listeners can update model attributes via a reference to the model, e.g. this.model in the view's context.
The view can also watch the model for changes and update itself accordingly.
For example:
var SomeView = Backbone.View.extend({
// Store HTML of DOM node in template. Easy to change in future.
template: [
'<div class="blah">',
'<input type="text" class="hello" />',
'</div>'
].join(''),
initialize: function() {
// Create DOM node, add to DOM
this.$el = $(_.template(this.template)());
$("body").append(this.$el);
this.hello = this.$el.find('.hello');
// Update model when view changes
this.hello.on('keydown', this.updateModel);
// Update view when model changes
this.model.on('change', this.updateView);
},
updateModel: function(evt) {
this.model.set('hello', this.hello.val());
},
updateView: function() {
this.hello.val(this.model.get('hello'));
}
});
The code that creates your model could also create this view and pass the model reference to the view constructor, e.g.
var myModel = new SomeModel();
var myView = new SomeView({model: myModel});
Of course, all of the specifics will vary according to your situation.
If you would like to use an existing DOM node as $el, remove the first two lines of code that create and append $el. Then instantiate your view like:
var existingJqNode = $('#existing'); // find existing DOM node, wrap in jQuery object
var myView = new SomeView({
model: myModel,
$el: existingJqNode
});
Above all, think about how best to set this up. Does using an already existing DOM element as $el create an advantage? If you want to create more of these views in the future, what code is responsible for creating/adding the $els before each view is instantiated?
Hope that helps.
The goal
Call UserModel within AuthenticationView with Backbone + Sprockets.
The problem
I just don't know a good way to do that.
The scenario
This is my view (assets/js/views/AuthenticationView.js):
var AuthenticationView = Backbone.View.extend({
el: $('.authentication-form'),
events: {
'keyup input[name=email]' : 'validationScope',
'keyup input[name=password]' : 'validationScope',
'submit form[data-remote=true]' : 'authenticate'
},
render: function() {
},
authenticate: function() {
// Here I'm going interact with the model.
}
});
And that's my model (assets/js/models/UserModel.js):
var UserModel = Backbone.Model.extend({
url: '/sessions'
});
The question
How can I make the interaction between the view and the model?
Remember: they're in separated files.
Getting the constructors together would be step 1 -- separate files don't matter, you can use Browserify/requirejs or just throw these things in global scope. From there, since passing an object into a view constructor with the property name 'model', automatically assigns the value to the view's this.model. So if we have an initialize method in our view, we can see:
initialize: function (options) {
console.log(this.model); // User instance
this.model.on('update', function () {});
}
And so we can pass in an instantiated model into the view via an object's model property:
var model = new UserModel();
var view = new AuthenticationView({ model: model });
http://www.joezimjs.com/javascript/lazy-loading-javascript-with-requirejs/ here is great article about js modules lazy loading with examples used backbonejs and requirejs
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.
I'm starting out with backbone and I'm trying to create a simple view that alerts whenever my model changes. Right now the initialize function in the view is being called, but the render function is not being called when my model changes (my model is being changed).
I've attempted two ways of binding to the change event (in the initialize function and the events property). I feel like I'm missing something obvious.
The #jsonPreview id exists in the html.
// Create the view
var JSONView = Backbone.View.extend({
initialize: function(){
this.bind("change", this.render);
},
render: function() {
alert("change");
},
events:
{
"change":"render"
}
});
// Create the view, and attach it to the model:
var json_view = new JSONView({ el: $("#jsonPreview"), model: documentModel });
Thanks in advance.
It looks like you are binding to the change event on the view rather than the view's model. think you need to bind to the model event something like this:
initialize: function(){
this.model.bind("change", this.render);
},
So i'm very new to backbone.js and not so good at JavaScript in general, so I was wondering if someone could explain to me why
I cannot define my EL property, and Template property in my view, and then use this.template in my render. Instead I have to define the template and el in my render function.
var ProductView = Backbone.View.extend({
el: $('#product-list'),
initialize: function() {
this.el.html('<span style="color:white">loading...</span>');
}, // end initialize
render: function(collection) {
// // assign the template
this.template = $('#product_template');
// Where the template will be placed
this.el = $('#product-list');
// Add the collection to the main object
this.collection = collection;
// add tthe data to the html variable
var html = this.template.tmpl(this.collection.toJSON());
// place the html in the element.
this.el.html(html);
// not even sure what the hell this is.
return this;
} // end render
});
The problem isn't in the way you're defining el or template, it's in how you're setting the call back. In Workspace, your router, you're setting the callback for your collection refresh event like this:
// Bind the view and collection
// So when the collection is reset, the view executes the render method
Products.bind("reset", this.view.render);
The problem is, you're setting a method as a callback, but you're not providing a context object as the third argument to bind - so the method is called, but this in the method refers to the global object, not the view. So this.el is undefined, because it's not looking at the view instance at all. Try:
// Bind the view and collection
// So when the collection is reset, the view executes the render method
Products.bind("reset", this.view.render, this.view);
and see how that goes.
(I made a jsFiddle to demonstrate that the el and template were set properly under normal circumstances, though it doesn't actually include the fix above, which is hard to mock up without the server-side data: http://jsfiddle.net/nrabinowitz/QjgS9/)
You can't do this:
var ProductView = Backbone.View.extend({
el: $('#product-list'),
// ...
and get anything useful in el as #product-list probably isn't even present in the DOM when your ProductView is built; so trying to use $('#product-list') for el is simply the classic "I forgot to use $(document).ready()" problem dressed up in Backbone. Using $('#product-list') for el should work if #product-list is around when you define your ProductView though.
You can do this though:
var ProductView = Backbone.View.extend({
el: '#product-list',
// ...
and then say $(this.el) when you need to do things inside your view methods. Not only is $(this.el) the usual way of using el but it also works and that's sort of important.
The same issues apply to #product_template.
Looking at your code I see this:
// INstantiate the view
this.view = new ProductView();
// Bind the view and collection
// So when the collection is reset, the view executes the render method
Products.bind("reset", this.view.render);
Presumably the render is being triggered by the reset event. But, and this is a big but, the render method isn't bound to the right this anywhere so this won't be the ProductView when render is called and this won't have anything that you expected it to; hence your bizarre "undefined" error.
You could use _.bindAll in your initialize:
initialize: function() {
_.bindAll(this, 'render');
// ...
but usually you'd want to give the view a collection when you create it and the view would bind itself to the events so your structure will still be a bit odd.
You can also supply a context (AKA this) when you call bind:
collection.bind('reset', this.render, this);