Am trying to bind two functions in my backbone.js view but don't know why its not working..
any one can advise?
define([
'jquery',
'underscore',
'backbone',
'libs/jquery_ui_dependency',
'vehicle/js/collections/MakeSet',
'vehicle/js/collections/ModelSet',
'vehicle/js/collections/TrimSet',
'vehicle/js/collections/YearSet',
'vehicle/js/views/Paginator'
], function($, _, Backbone, JQueryUi, MakeSet, ModelSet, TrimSet, YearSet, Paginator){
'use strict';
var BrowseVehicleView = Backbone.View.extend({
el: $('#vehicle-browse-form'),
initialize: function(){
JQueryUi.init();
_.bindAll(this, 'render', 'onMakeChange');
//define array of elements to be used in DOM manipulations
this.elements = {
"make" : $('#id_make', this.el),
"model" : $('#id_model', this.el),
"trim" : $('#id_trim', this.el),
"year_from" : $('#id_year_from', this.el),
"year_to" : $('#id_year_to', this.el),
"price_from" : $('#id_price_from', this.el),
"price_to" : $('#id_price_to', this.el)
}
},
events: {
"change #id_make" : "onMakeChange",
"change #id_model" : "onModelChange",
"change #id_trim" : "onTrimChange"
},
render: function(){
Paginator.filter({
model__make: this.elements.make.val(),
model__model: this.elements.model.val(),
model__trim: this.elements.trim.val()
});
},
onMakeChange: function(event) {
this.resetElement([
this.elements.model,
this.elements.trim,
this.elements.year_from,
this.elements.year_to
]);
// load models
this.RenderCollection(ModelSet, {make: this.elements.make.val()}, [this.elements.model]);
},
what am trying to achieve here, is that every time onMakeChange is invoked it should invoke the render function as well to render the Paginator.filter()
_.bindAll ensures that when methods are called, the value of 'this' is set to the value you specify. So, _.bindAll(this, 'render', 'onMakeChange') specifies that when render or onMakeChange is called, the BrowseVehicleView is set as the value of 'this'.
Like mu stated, you probably want to call this.render() manually inside of onMakeChange(), or bind this.render as an event handler to one of the events raised by your model.
Related
It's my first post here so please be nice ;) I'm trying to create a Backbone+requireJS sample app and I have stumbled upon a strange problem. In my views initialize function I bind to models change event by
this.model.on("change", this.change());
Event is triggered correctly when data is loaded and all is fine. In my view I also bind to a click event. On click I try to change one of the properties and I was hoping for a change event, but it never came.
I was trying some stuff recommended here on stackoverflow (here, here, and there) but with no luck.
In my guts I feel it has to do with event binding. When I tried to bind again to models on change event inside click callback it started to work. So now I sit here and I'm, hm, confused. Could someone shed some light on this issue?
My View:
define(
[
'jquery',
'underscore',
'backbone',
'text!templates/news/newsListItem.html'
],
function($, _, Backbone, newsListItemTemplate)
{
var NewsListItemViewModel = Backbone.View.extend({
tagName: 'li',
events: {
"click a" : "onLinkClick"
},
initialize: function(){
this.model.on("change", this.changed());
},
render: function()
{
this.$el.html(_.template(newsListItemTemplate, this.model.toJSON()));
},
changed: function()
{
this.render();
console.log("changed");
},
//GUI functions
onLinkClick: function(e)
{
console.log("click!");
this.model.toggle();
this.model.on("change", this.changed());
}
});
var init = function(config){
return new NewsListItemViewModel(config);
}
return {
init : init
}
}
);
My Model:
define(
['jquery', 'underscore', 'backbone'],
function($, _, Backbone){
var NewsListItemModel = Backbone.Model.extend({
toggle: function() {
this.set('done', !this.get('done'));
this.trigger('change', this);
}
});
var init = function(data)
{
return new NewsListItemModel(data);
}
return {
init: init,
getClass: NewsListItemModel
};
}
);
Thanks for any input :)
First, you should use a function as event handler - not its result.
Hence, change the line into this:
this.model.on("change", this.changed.bind(this));
As it stands now, you actually fire this.changed() function just once - and assign its result (which is undefined, as the function doesn't have return statement) to be the model's change event handler.
Second, you shouldn't rebind this handler in onLinkClick: once bound, it'll stay here. It looks like it's more appropriate to trigger this event instead:
onLinkClick: function(e)
{
console.log("click!");
this.$el.toggle();
this.model.trigger('change');
}
I have a measure model, that is made up of two collections, a beats collections, and a measureRep[resentation] collection. Each collection is made of beat models and representation models respectively.
Whenever a measureRep[resentation] collection changes (by addition or subtraction of a representation model), I want the measureView (which has the measure model, and therefore the measureRep[resentation] collection) to re-render itself using the render function().
I am adding a new model in another View by the following function:
var representationModel = new RepresentationModel({representationType: newRepType});
StageCollection.get(cid).get('measures').models[0].get('measureRepresentations').add(representationModel);
I can see that before and after the addition that the measure and its measureRep collection are getting added correctly, however, the bind call on the measureView is not registering the change and calling the render function. I even put the bind on the model, to show that the backend is getting updated, however, it doesn't respond. This leads me to believe that the View and the models are decoupled, but that doesn't make sense, since it originally renders from the model. Here are the relevant files:
measureView.js [View]:
define([...], function(...){
return Backbone.View.extend({
initialize: function(options){
if (options) {
for (var key in options) {
this[key] = options[key];
}
this.el = '#measure-container-'+options.parent.cid;
}
window.css = this.collectionOfRepresentations; //I use these to attach it to the window to verify that the are getting updated correctly
window.csd = this.model; // Same
_.bindAll(this, 'render'); // I have optionally included this and it hasn't helped
this.collectionOfRepresentations.bind('change', _.bind(this.render, this));
this.render();
},
render: function(){
// Make a template for the measure and append the MeasureTemplate
var measureTemplateParameters = { ... };
var compiledMeasureTemplate = _.template( MeasureTemplate, measureTemplateParameters );
// If we are adding a rep, clear the current reps, then add the template
$(this.el).html('');
$(this.el).append( compiledMeasureTemplate )
// for each rep in the measuresCollection
_.each(this.collectionOfRepresentations.models, function(rep, repIndex) {
var measureRepViewParamaters = { ... };
new MeasureRepView(measureRepViewParamaters);
}, this);
return this;
},
...
});
});
measure.js [Model]:
define([ ... ], function(_, Backbone, BeatsCollection, RepresentationsCollection) {
var MeasureModel = Backbone.Model.extend({
defaults: {
beats: BeatsCollection,
measureRepresentations: RepresentationsCollection
},
initialize: function(){
var logg = function() { console.log('changed'); };
this.measureRepresentations.bind('change', logg);
this.bind('change', logg);
}
});
return MeasureModel;
});
representations.js [Collection]:
define([ ... ], function($, _, Backbone, RepresentationModel){
var RepresentationsCollection = Backbone.Collection.extend({
model: RepresentationModel,
initialize: function(){
}
});
return RepresentationsCollection;
});
I have also tried registering the bind on the measure model, and not its child collection, but neither work.
_.bindAll(this, 'render');
this.model.bind('change', _.bind(this.render, this));
see: https://stackoverflow.com/a/8175141/1449799
In order to detect additions of models to a collection, you need to listen for the add event (not the change event, which will fire when a model in the collection is changed http://documentcloud.github.io/backbone/#Events-catalog ).
so try:
this.measureRepresentations.bind('add', logg);
my current code looks like this:
define([
'jquery',
'underscore',
'backbone',
'marionette',
'templates',
'gridView',
'detailView',
'detailModel'
], function ($, _, Backbone, Marionette, JST, GridView, DetailView, DetailModel) {
'use strict';
return Marionette.Layout.extend({
el: '#main',
template: JST['app/scripts/templates/main.ejs'],
initialize: function() {
this.render();
},
onRender: function () {
var Layout = Marionette.Layout.extend({
el: 'div',
template: _.template(""),
regions: {
grid: '#grid',
detail: '#detail'
}
});
this.layout = new Layout();
this.layout.render();
},
showGrid: function () {
var detailModel = new DetailModel();
var g = new GridView(detailModel);
var d = new DetailView(detailModel);
this.layout.grid.show(g);
this.layout.detail.show(d);
}
});
});
What I do not understand is why I need an extra layout in my onRender method to make this work. The '#grid' and '#detail' divs are part of the main.ejs template, but the following does not work:
return Marionette.Layout.extend({
el: '#main',
template: JST['app/scripts/templates/main.ejs'],
regions: {
grid: '#grid',
detail: '#detail'
},
initialize: function() {
this.render();
},
onRender: function () {
var detailModel = new DetailModel();
var g = new GridView(detailModel);
var d = new DetailView(detailModel);
this.grid.show(g);
this.detail.show(d);
}
});
It seems that the layout only works if the elements specified in the region object already exist when the layout is created. But the documentation says that this is not the case.
I'm probably doing something wrong. But what ?
Regards
Roger
In your second code example, try using onShow instead of onRender.
In addition, in Marionette you usually don't call render yourself, since the framework will call that method when you pass view/layouts to the show method.
You can see a different take on what you're trying to accomplish here :
https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/list/list_controller.js (particularly lines 43-46)
As an additional warning, calling .show() in the onRender method can negatively impact anything nested below that layout, especially if you are trying to use onShow later down the line to ensure that a view's DOM subtree is jQuery accessible.
.show() triggers a "show" event across any subviews of that layout and can mean that onShow() is called in those subviews (which listen for the "show" event) before those subviews have rendered and inserted their content.
I have this collection view
define([
'jquery',
'underscore',
'backbone',
'views/project',
'collections/project-collection',
'templates'
], function ($, _, Backbone, ProjectView, ProjectCollection, JST) {
'use strict';
var ProjectListView = Backbone.View.extend({
template: JST['app/scripts/templates/projectList.ejs'],
el: $('#content'),
render: function() {
var projectCollection = new ProjectCollection();
projectCollection.fetch();
projectCollection.each(this.addOne(),this);
return this;
},
addOne: function(project) {
console.log('addOne function');
var projectView = new ProjectView({model: project});
this.$el.html( projectView.render().el);
}
});
return ProjectListView;
});
No matter what I try the model never gets passed through to the addOne function so the in the view that is instantiated by this method the call to
this.model.toJSON()
results in the old 'cannot call method .toJSON of undefined' error. I tried to inject the collection when this collection view was instantiated and that didn't work either. Obviously here it is in the dependency array and that doesn't work either. The model is definitely there as I can log projectCollection.model to the console inside the render function. I'm stumped.
I see two problems with your render: one you know about and one you don't.
The first problem is right here:
projectCollection.each(this.addOne(), this);
The parentheses on this.addOne() call the addOne method right there rather than passing the this.addOne function to each as a callback. You want this:
projectCollection.each(this.addOne, this);
The second problem is that you have to wait for the collection's fetch to return before anything will be in the collection. You can use the fetch's callbacks:
var _this = this;
projectCollection.fetch({
success: function() {
projectCollection.each(_this.addOne, _this);
}
});
or you can use the various events that fetch will fire, see the fetch documentation for details.
I'm trying to get the view to self render by binding a change event to the views model. I've created a custom function on PageView which takes a model from the Pages collection as a paramater and uses this.model.set(data) to trigger the model change event. This is triggered when I pass it a Page model from the Pages collection in the AppView via this.page.load(Pages.at(index)). I'm running into a few problems though.
The change event is only fired once when switching back and forth between 2 different models, which I can resolve by running this.model.clear({silent:true}), but that's not ideal.
this.model is always undefined in any function other than the load() function within PageView which is really the main issue because I obviously can't fire this.render if the model is undefined. Hence the test:function().
Anyway here's the code for my AppView and PageView functions. Thanks in advance for any help.
define([
// These are path alias that we configured in our bootstrap
'jquery',
'underscore',
'backbone',
'handlebars',
'views/pageView',
'views/menuView',
'collections/pages'
], function($, _, Backbone, Handlebars,PageView,MenuView,Pages){
var AppView = Backbone.View.extend({
//Stores a reference to our main menu
mainMenu:{},
//Stores reference to current page
page:{},
//The parent div
el:'#app',
//Events that are being listened to on #app
events:{"click" : "active"},
//Process to run when this view is initialized
initialize:function(){
//Load our Pages Collection
Pages.reset(this.model.pages);
//Load the main menu
this.mainMenu = new MenuView;
this.mainMenu.model = this.model.main_menu;
this.mainMenu.render();
//Loads the page view
this.page = new PageView({model:Pages.at(0)});
},
//Currently just renders a page view with a designated model from the Pages collection
render:function(index){
this.page.load(Pages.at(index));
},
active:function(event){
$('.menu a').removeClass('active');
$('.menu a[href="' + event.target.hash + '"]').addClass('active');
}
});
return AppView;
// What we return here will be used by other modules
});
define([
// These are path alias that we configured in our bootstrap
'jquery',
'underscore',
'backbone',
'handlebars',
'text!templates/page.html',
], function($, _, Backbone, Handlebars, pageViewTemplate){
var PageView = Backbone.View.extend({
//Define the default template
template: Handlebars.compile(pageViewTemplate),
//The parent div for this element
el:'#content',
initialize:function(){
this.model.bind('change',this.test);
},
load:function(data){
this.model.set(data);
},
//Render function
render:function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
},
test:function(){
console.log(this.model);
console.log('change');
}
});
return PageView;
// What we return here will be used by other modules
});
does setting the context in your bind call fix things for you?
initialize:function(){
this.model.bind('change',this.test, this);
},