I'm trying to call a few functions when the window is resized. The changePosition function is called just fine, but the tryPrint function is suffering from a "Uncaught TypeError: Object [object global] has no method 'tryPrint'" error. I don't understand why this is the case. I tried adding a "this" parameter into the resize listener but that didn't lead me anywhere...
class App.Views.Pages.WorkView extends Backbone.View
template: JST["backbone/templates/pages/work"]
initialize: () ->
this.render()
$(window).on('resize', this.changePosition)
tryPrint: ->
console.log("TRYING")
changePosition: ->
this.tryPrint()
render: =>
$(#el).html(#template())
return this
The problem is that the value of this (AKA # in CoffeeScript) inside a function depends on how the function is called unless you've bound the function to a specific object. You're doing this to bind the function to an event:
$(window).on('resize', this.changePosition)
jQuery's on will set this to window when calling changePosition in this case:
When jQuery calls a handler, the this keyword is a reference to the element where the event is being delivered; for directly bound events this is the element where the event was attached and for delegated events this is an element matching selector.
If you want this to be the view then use a fat-arrow (=>) when defining the method:
changePosition: =>
#tryPrint()
Other options:
Use a bound function:
$(window).on('resize', #changePosition.bind(#))
$(window).on('resize', _(#changePosition).bind(#))
$(window).on('resize', $.proxy(#changePosition, #))
#...
Manually set this:
that = #
$(window).on('resize', -> that.changePosition())
Using => is probably the easiest as you'll want to off the handler when destroying your view and => will keep function references right.
Related
I'm learning backbone from the docs, can you provide an example of how the context works for Events? I don't really understand what they mean by the below:
To supply a context value for this when the callback is invoked, pass
the optional last argument: model.on('change', this.render, this) or
model.on({change: this.render}, this).
This is the event listeners.
For example,
model.on('change', this.render, this)
The first parameter is the event type: For example in this case, "change" if event has happened.
The second parameter is the function which you want to call. Let's say if a model has changed, call this function.
Third is the context. ('this') represents the context that you're clicking. For example if you are clicking in an input box, this.$el will be input box. If you don't mention this. Your 'this' will be window object.
For example:
var SomeModelView = Backbone.View.extend({
initialize: function() {
this.model.on('change', this.render, this);
},
render: function() {
// render a template
console.log("Template Rendered");
}
});
JSFiddle: http://jsfiddle.net/8AH3t/169/
Warning:
It's always recommended to use this.listenTo instead of using this.model.on.
this.listenTo(this.model, 'change', this.render);
Why?
When calling the .remove() method, the "change" event handler (our
render function) is still bound. So while the DOM element may be
removed, the view object itself is never released from memory.
Source: http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html
I have a backbone view which is associated with model. View is listening for change method and it calls render if model is changed.
this.listenTo(this.model, 'change', this.render);
I'm having a problem that my backbone view's render method is getting called multiple times. I'm trying to debug this problem. For this purpose I added console.log statement in the render method:
render: function(data) {
if(this.model){
console.log("Render Method:",data," For model:",this.model.cid);
}
}
Now this data value is getting printed as undefined sometimes or something like model. Does anyone know what is the argument passed to a model change listener?
Note that : I'm not passing anything to render method.
and backbone documentation mentions nothing about this: http://documentcloud.github.io/backbone/#View-render
The change event passes the model and a hash of options
In backbone sources:
this.trigger('change', this, options);
And so in the documentation as mu is too short has commented:
"change" (model, options) — when a model's attributes have changed.
As far as I am aware the render function is not supposed to have any parameters pass to it
Event.Listen=Backbone.View.extend({
model: 'something'
initialize:function(){
this.listenTo(this.model,'change',this.render);
}
render:function(){
//is called when the listen to event is triggered
//if(this.model) does not make a lot of sense?? Does it need to be
//true ornull
//call another object
new Event.DoSomething();
}
});
From the Backbone site "Tell an object to listen to a particular event on an "other" object"
Looks like externally to the View there are other calls to render(additional to the event listener).
if you try this(listen to other method instead of render):
this.listenTo(this.model, 'change', this.customMethod);
And then declare this in the view below render:
customMethod: function() {
console.log(arguments, '<==== is there arguments?');
if(this.model){
console.log("Custom Render Method For model:",this.model.cid);
}
}
So please review in your code, where additional to the model.change listener, you are calling the render method outside the view.
The event callback gets called multiple times if the binding happens multiple times i.e.
this.listenTo(this.model, 'change', this.render);
is being executed multiple times.
It can also happen if the change is triggered multiple times. eg. you setting each of the attributes of a model in a for loop rather than at once.
Any call back receives an event as argument.
In your case
render: function(data) {
if(this.model){
console.log("Render Method:",data," For model:",this.model.cid);
}
}
data will be logged as undefined when view.render() is called.
data will be an event object when triggered by a change event
I would like to render a view for a model when the model is first fetched but not on every change.
My setup is as follows:
var m = new $.model.Customer({id: customer});
var v = new $.view.GeneralEditView({el: $("#general"), model: m});
m.fetch();
Then in the view initialize I bind the change event to the render method to render when the model is loaded:
this.model.bind('change', this.render);
The problem is that the view then renders on every change. I'd like to only render after the fetch. Unfortunately I'm not aware of any event that's fired after a fetch for a model other than change.
Is there something like 'reset' for collections that I can bind to?
EDIT:
Perhaps to put it more succinctly, for Backbone models is there a way to distinguish when a model is loaded from the server versus changed locally?
There are a bunch of different ways to approach this (these all assume var view = this; somewhere in your view code):
Call .fetch() with a one-time success callback:
m.fetch({
success: function() {
view.render();
}
});
Bind to change but unbind in the handler:
function handle() {
view.render();
view.model.off('change', handle);
}
this.model.bind('change', handle);
Use _.once to limit handler calls:
this.model.bind('change', _.once(function() {
view.render();
}));
Use a .ready() pattern for your models - example here. I like this option in cases where multiple views need to know when the model is loaded, and when you need to want to be able to write the same code without worrying about whether your model is loaded yet. The downside of this is that it requires you to add a model method like .isFullyLoaded() to test every time; the upside is that using a test function, rather than setting a flag, allows models to be loaded in bulk as part of a collection without having to change your code.
Models
You can make the change event specific to a certain key changing, such as the uniqueId (if you have one):
this.model.bind('change:id', this.render, this);
By default, fetch does not fire any event directly, but indirectly fires the change event once new data is loaded using set
If that is not an option, you can always trigger an event in your fetch function:
initialize: function () {
this.model.bind("fetch", this.update, this);
}
fetch: function () {
// do stuff
this.model.trigger("fetch", this);
}
update: function () {
// your refresh stuff here
}
I may have a general solution from https://github.com/documentcloud/backbone/pull/1468#issuecomment-6766096. I overwrote the sync method on Backbone as follows:
Backbone.Model.prototype.sync = function(method, model, options) {
var succ = options.success;
var customSuccess = function(resp, status, xhr) {
//call original
succ(resp, status, xhr);
model.trigger('synced', model, options);
}
options.success = customSuccess;
Backbone.sync(method, model, options);
}
To save the original success method as I don't want to mess that unless I need to, pass the custom success method. When the custom success method is invoked the custom event is triggered as suggested by #Austin and then the original success method in invoked.
In addOne(todo) I'm having trouble understanding what the parameter is being referenced from:
In the AppView:
initialize: function(){
Todos.bind('add', this.addOne, this); //So "this" keyword is being passed, which refers to AppView itself
//More code here
},
then in the addOne function,
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
},
See annotated source here: http://backbonejs.org/docs/todos.html
So isn't AppView being passed into addOne(todo)? Shouldn't a model be passed into model: todo in addOne()?
Thanks
The this that's being passed as the third parameter to the bind function is used to bind the context of the function to the current context, its not being passed as a parameter. Under the hood bind uses underscore.js's _bind().
EDIT:
I think what's confusing you is that it's not obvious where the addOne method is being called. Basically the add event is fired when a new item is added to the collection, you are able to bind to this event and backbone will pass the newly added model to the method that is bound to this event (so in this case the addOne method is bound to this event and the todo is the new model). What's triggering the add event in this example is when a new todo is created through the Todos.create({title: this.input.val()}); in the AppView's createOnEnter method.
I am using backbone.js and jQuery templates. What I'd like to do is set the view's template to a dom element. It looks something like this:
<script id="templateElement" type="text/x-jQuery-tmpl">
<div class='foo'>bar</div>
</script>
When the view is initialized, it will see if the template exists with $.isFunction. If it doesn't it will get it from an external file and append the returned dom element to the body element and then set this.template to that dom element.
The next time the view is called, that dom element should exist so there should be no reason to issue the AJAX call again. However, I'm finding that although this template is no longer null after the AJAX call, it is undefined even though setting it is part of the callback. as a result my AJAX call is issued every time the view is rendered even though #templateElement is part of the page.
What's going on?
BbView = Backbone.View.extend({
template: $('#templateElement').template(),
initialize:
//this.template is null at this point
if(!($.isFunction(this.template))){
$.get('template/file.html', function(templates){
$('body').append(templates);
this.template = $('#templateElement').template();
});
}
//this.template is undefined at this point
...
Right. You've lost 'this' which is not your view in the call to $.get().
You can use underscore.bind to call the success callback in the context of your view.
However, you don't actually need to put the template into the DOM.
You can set the compiled template on the View prototype and it will be there for the next instance of this view. For example, something like...
BbView = Backbone.View.extend({
initialize: function() {
if(!($.isFunction(this.template))) {
$.get('template/file.html', _.bind(function(templates) {
BbView.prototype.template = $(templates).template();
}), this);
}
// Now you have this.template, for this and all subsequent instances
}
My best guess is that your this.template inside of the $.get is out of scope.
You might want to do
initialize:
var self = this;
//this.template is null at this point
if(!($.isFunction(self.template))){
$.get('template/file.html', function(templates){
$('body').append(templates);
self.template = $('#templateElement').template();
});
}