Access view property when calling render as a callback - javascript

I have to use the guid variable in the render() function, but I can pass it only to the constructor. I this code:
app.views.CompanyView = Backbone.View.extend({
el: '#company-view',
guid: '',
initialize: function (options) {
this.guid = options.guid;
},
render: function () {
var guid = this.guid;
}
});
I create my view like this:
app.currentView = new app.views.CompanyView({guid: guid});
Then I pass the render() function as a parameter to use it as a callback:
function call(callback){
callback();
}
call(app.currentView.render);
I tried this.guid, options and this.options too, but all of them were undefined. Is there a way to pass this variable to the render() function without using it's arguments or global variables? Here is a JsFiddle example.

When you call render through this:
function call(callback){
callback();
}
You're calling it as a plain function so this inside render will be window. Remember that this in JavaScript depends on how the function is called, not how it is defined (unless of course you're playing with bound functions).
You have some options:
Bind render to the view using _.bindAll, _.bind, $.proxy, Function.bind, ...
initialize: function() {
_.bindAll(this, 'render');
}
Demo: http://jsfiddle.net/ambiguous/GsUfY/
The more common approach these days is to pass a context with the function and then whoever calls the callback uses the appropriate context using call or apply:
function call(callback, context){
callback.apply(context);
}
Demo: http://jsfiddle.net/ambiguous/LnwPr/
Do it yourself by hand:
call(function() { v.render() });
This one usually takes the form of var _this = this; followed by an anonymous function that uses _this.some_method() instead of just passing this.some_method as a callback.
Demo: http://jsfiddle.net/ambiguous/K2Xj4/
I prefer the second option.

I see. When your render() is called by the callback function, the caller of the method is no longer the view itself, so the "this" inside your render will be the caller of the call function().
see this fiddle:
http://jsfiddle.net/cn8nN/2/
var CompanyView = Backbone.View.extend({
initialize: function (options) {
this.guid = options.guid;
},
render: function () {
console.log('hello');
console.log(this);
}
});
var v = new CompanyView({guid: 'the guid'});
function call(callbcak) {
callbcak();
}
call(v.render);
if you open the console, you will see "this " is actually the window.
to work around this, you want to bind the context to the view it self.
to do that, use _.bindAll();
initialize: function (options) {
_.bindAll(this, "render");
this.guid = options.guid;
}
jsfiddle: http://jsfiddle.net/cn8nN/3/

Related

React.js and debouncing onChange event is not working

I have implemented react component on change event like this:
NewItem = React.createClass({
componentWillMount: function() {
this._searchBoxHandler = debounce(this._searchBoxHandler, 500);
},
_searchBoxHandler: function(event) {
this.context.flux.getActions('order').setOrders(...);
},
render: function () {
...
var _self = this;
return (<TextField onChange={_self._searchBoxHandler} />)
})
});
I've done this implemention by checking this answer (Good idea section): https://stackoverflow.com/a/28046731/842622
But it's not working. I'm always having 'Cannot read property 'value' of null' from event object.
Am I missing something here?
You need to bind your context or use a closure to maintain scoping:
NewItem = React.createClass({
componentWillMount: function() {
this._searchBoxHandler = debounce(this._searchBoxHandler.bind(this), 500);
},
_searchBoxHandler: function(event) {
this.context.flux.getActions('order').setOrders(...);
},
render: function () {
...
var _self = this;
return (<TextField onChange={_self._searchBoxHandler} />)
})
});
A common mistake for new JavaScript programmers is to extract a method from an object, then to later call that function and expect it to use the original object as its this (e.g. by using that method in callback-based code). Without special care however, the original object is usually lost. Creating a bound function from the function, using the original object, neatly solves this problem.. - MDN
Some nice docs on binding and context:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
or you could use a 'fat-arrow' function to access parent class scope:
debounce((event) => { this._searchBoxHandler(event); }, 500);
Note: I wouldn't overwrite the declared function property in the componentWillMount invocation, instead you could store the debounced instance in another property like _debouncedSearchBoxHandler

Javascript OOP use function inside object

I have JS object
var widget = {
check_balance: function(){...},
function_called_in_init: function(){
.....
this.check_balance();
};
};
this is code screenshot for better understanding..
and when it try to call this.check_balance(); it returns me error TypeError: this.check_balanceis not a function
the question would be - how to call function inside object which was also created inside object?
Also I can't init this function at the moment when all object is inited, becouse this is a recursion with ajax callback.
Its a little tricky to see what you are asking but the gist of it is you are looking to have the correct context. The tool for that is the whatever.bind(theContext) function. You pass in theContext to the object and that makes theContext object the context of whatever.
var parent = {
foo: function () {
var widget = {
check_balance: function(){ console.log('checking'); },
function_called_in_init: function(){
this.bar();
}.bind(this),
};
widget.function_called_in_init();
},
bar: function () {
console.log('bar');
},
};
parent.foo();
see fiddle
bind documentation
Use private function and closure
var widget = (function() {
var check_balance = function() {
//do what your check_balance has to do
}
return {
check_balance: check_balance,
function_called_in_init: function(){
.....
check_balance();
};
};
})();

Calling super class method in Backbone

var BaseView = Backbone.View.extend({
localizedTemplate : function (element) {
template = _.template(element.html());
return function (data) {
return template($.extend({}, data, resource));
};
}
});
var DerivedView = BaseView.extend({
initialize: function (options) {
this.model = options.model;
this.template = function () {
return this.localizedTemplate($("#someTemplate"));
};
},
render: function () {
var output = this.template(this.model.toJSON());
this.$el.append(output);
return this;
}
});
Why the above code is not working? why I am not able to call the someFunction in DerivedView? is there any way to achieve this?
I am using Backbone latest version.
When you do this:
this.template = function () {
return this.localizedTemplate($("#someTemplate"));
};
You're assigning a function to this.template. Note that localizedTemplate also returns a function:
return function (data) {
return template($.extend({}, data, resource));
};
That means that this.template is a function which returns a function and that second function is the one that wants this.model.toJSON() as an argument.
You're doing this:
var output = this.template(this.model.toJSON());
The function in this.template ignores its arguments and returns a function, that leaves you with this function:
function () {
return this.localizedTemplate($("#someTemplate"));
}
in output. You probably think output is a chunk of HTML at this point so you hand that to append:
this.$el.append(output);
But output is a function and what does append do when called with a function as its argument? jQuery calls that function like this:
function(index, html)
Type: Function()
A function that returns an HTML string, DOM element(s), or jQuery object to insert at the end of each element in the set of matched elements. Receives the index position of the element in the set and the old HTML value of the element as arguments. Within the function, this refers to the current element in the set.
So the output function will be called by jQuery's append and append will supply arguments that the compiled template function doesn't understand. The result is a big pile of confusion.
If you really want to do things like this then you'll want to call all the functions yourself so that you can get the right arguments to the right places:
var output = this.template()(this.model.toJSON());
// -----------------------^^
Demo: http://jsfiddle.net/ambiguous/YyJLR/
Or better, don't bother with all the extra wrappers at all. Say this in your view's initialize:
this.template = this.localizedTemplate($("#someTemplate"));
and then this in render:
var output = this.template(this.model.toJSON());
Demo: http://jsfiddle.net/ambiguous/BQjhS/
Also note that you don't need to this.model = options.model, the view constructor will do that for you:
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes.
var DerivedView = BaseView.extend({
someVariable: function(someData) {
return this.someFunction(someData);
}
});

Backbone event callback is called when bound, not triggered

I am trying to implement a simple mediator pattern for events, but my event callbacks are being called when I register them with the mediator.
The Mediator is simply:
define(function(require){
'use strict';
var _ = require('underscore');
var Backbone = require('backbone');
return _.extend( Backbone.Events);
});
I have created a simple View with no backing model or template
define(function (require) {
'use strict';
var Backbone = require('backbone');
var mediator = require('mediator');
var Sandbox = Backbone.View.extend({
el: '.sandbox',
initialize: function () {
this.render();
this.bindEvents();
},
bindEvents:function(){
mediator.on("sandbox:change", this.changeText(), this );
},
render: function () {
$(this.el).html("SANDBOX VIEW IS WORKING");
},
changeText: function () {
$(this.el).html("THE TEXT HAS CHANGED");
}
});
return Sandbox;
});
When the view is loaded, the sandbox:change event is fired off and the changeText function is called even though nothing has called mediator.trigger('sandbox:change')
Why is the callback invoked when it is simply being bound to the mediator object?
When the browser evaluates your sandbox change event binding mediator.on("sandbox:change", this.changeText(), this ); the changeText function is being executed because the () are included. What you really want to do is pass the changeText function object to the event binding:
mediator.on("sandbox:change", this.changeText, this );
Remember that changeText, is an object that is a member of your view.
var Sandbox = Backbone.View.extend({
...
changeText: function () {...}
});
When you want to execute the function, use the parenthesis. When you want to refer to the object, omit the the parenthesis.

Assigning scope amongst jQuery.getJSON and a JS.Class

I'm trying to assign some JSON data to a property of a JS.Class instance.
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, function(data) {
Assign(data);
});
function Assign(data) { this.content = data; };
}
});
var m = new MyClass("uuid_goes_here");
m.write();
The JSON is received asynchronously, which is why there's a function call within the $.getJSON callback.
The problem I have now is that the this.content within the Assign function is not within the scope of the instance method named write. So whereas this.uuid returns correctly, this.content remains undefined (as you would expect).
Any ideas on how to correct this? I've tried using a global variable as a workaround but the async call doesn't allow for that (plus it's a crappy solution).
Some points to note, in case they matter: I have to use JSONP, so the "?callback=?" has to stay, and I'd like to keep it async.
I would usually go for either czarchaic's version, or replace Accept with a bound method from the object. What you have to bear in mind is that calling Accept() like that (as a function call rather than a method call) will bind this to the global object, i.e. window. I'd try this:
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, this.method('setContent'));
},
setContent: function(data) {
this.content = data;
}
});
See http://jsclass.jcoglan.com/binding.html for more info.
You should cache the current instance in the write method and update it after ajax.
write: function() {
var self=this;
$.getJSON(url+"?callback=?", {}, function(data) {
self.data=data;
});
}

Categories

Resources