Ember.js: this._super in a callback function - javascript

I'm trying to figure out how to use this._super when the Ember's object method is called from a callback.
I know that I could assign var _super = this._super before the callback is called but I don't like it.
I want to have the this object containing proper _super method inside the callback.
My code is here: http://emberjs.jsbin.com/hasehija/6/edit.
App.BaseMixin = Ember.Mixin.create({
init: function() {
console.log("base");
}
});
App.Utils = Ember.Object.extend({
callbackMethod: function(callback, ctx) {
// asynchronous callback
Ember.run(function() {
callback.call(ctx);
});
}
});
App.MyObject = Ember.Object.extend(App.BaseMixin, {
init: function() {
console.log("MyObject");
var _super = this._super;
App.Utils.create().callbackMethod(function() {
this._super(); // this._super is undefined here
// _super() would work
}, this);
}
});
App.ApplicationController = Ember.Controller.extend({
init: function() {
new App.MyObject();
}
});
Do you know any way to fix it?
UPDATE:
It turned out that it was fixed in Ember 1.5.0 (#GJK: thank you for the answer) and I was using Ember 1.4.0.

extend defines a class
App.Utils = Ember.Object.extend({
callbackMethod: function(callback, ctx) {
callback.call(ctx);
}
});
create builds an instance of the class
App.Utils = Ember.Object.create({
callbackMethod: function(callback, ctx) {
callback.call(ctx);
}
});
or
App.Utils.create().callbackMethod(function() {
this._super();
}, this);
http://emberjs.jsbin.com/hasehija/7/edit
Or avoid overriding init
App.ApplicationController = Ember.Controller.extend({
doSomething: function() {
new App.MyObject();
}.on('init')
});

Related

Calling a private/nested javascript function from an outer scope

I have my javascript code like this . Inside that I have an init() function and in that function I have an options JSON object and in that object I have a function defined as objectselected(). How I call that function in a button click event
I have tried like this WorkFlow.init().options.Objectselected() but it is not working,
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
},
}
How to call that function.
One way around is you can return options and than call it.
init: function () {
var options = {
...your code..}
return options;
},
and call it than
var options = WorkFlow.init();
options.Objectselected();
As it stands, you have no access to options because it's a local variable - that is, local to its scope.
To access its contents, you'll need to return it from init().
Think about it:
WorkFlow.init()
Currently this returns undefined, because your init() returns nothing. You're trying to chain like in jQuery, but that relies on the API always returning the instance. Your path finds a dead-end at init().
To fix this, have init() return options - or at least the part of it you want to access from outside - an "export".
So (basic example)
init: function() {
var options {
my_func: function() { }, //<-- we want outside access to this
private: 'blah' //<-- this can stay private - leave it out of the export
}
//return an export, exposing only what we need to
return {
my_func: options.my_func
}
}
You need to return options as it is inside init function's scope
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
return options;
},
}
And call it as WorkFlow.init().objectSelected();
Building on Patrick's comment, you'd need to return options from the init function:
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
...
options.initialize();
return options;
},
}

Console logging "this" returns "null"

I am trying to create a flux store for a React app I am building. I am using an object-assign polyfill npm package and Facebook's Flux library.
Initially I was getting the error "Cannot read property '_data' of null' error in the console which was refering to var currIds = this._data.map(function(m){return m.id;});. That method is currently the only one being called directly. I then did console.log(this) which returned "null".
I find this strange. What is going on?
My code:
var Assign = require('object-assign');
var EventEmitterProto = require('events').EventEmitter.prototype;
var CHANGE_EVENT = 'CHANGE';
var StoreMethods = {
init: function() {},
set: function (arr) {
console.log(this);
var currIds = this._data.map(function(m){return m.id;});
arr.filter(function (item){
return currIds.indexOf(item.id) === -1;
}).forEach(this.add.bind(this));
},
add: function(item){
console.log(this);
this._data.push(item);
},
all: function() {
return this._data;
},
get: function(id){
return this._data.filter(function(item){
return item.cid === id;
})[0];
},
addChangeListener: function(fn) {
this.on(CHANGE_EVENT, fn);
},
removeChangeListener: function(fn) {
this.removeListener(CHANGE_EVENT, fn);
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
bind: function(actionType, actionFn) {
if(this.actions[actionType]){
this.actions[actionType].push(actionFn);
} else {
this.actions[actionType] = [actionFn];
}
}
};
exports.extend = function(methods) {
var store = {
_data: [],
actions: {}
};
Assign(store, EventEmitterProto, StoreMethods, methods);
store.init();
require('../dispatcher').register(function(action){
if(store.actions[action.actionType]){
store.actions[action.actionType].forEach(function(fn){
fn.call(null, action.data);
})
}
});
return store;
};
I can't see where set is called, however your this can be null if the function is invoked through call (see here) or apply, and your first argument is null.
This also happens in your require.register callback:
fn.call(null, action.data) //First parameter is your 'this'.

Backbonejs usage of el

I've created 2 separate views, 1 to render the template and the other one is where I bind the events, then I tried merging them into one in which case it causes an Uncaught TypeError: Object [object Object] has no method 'template'. It renders the template and the events are working as well, but I get the error.
edit.js, this is the combined view, which I think it has something to do with their el where the error is coming from
window.EditView = Backbone.View.extend ({
events: {
"click #btn-save" : "submit"
},
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
},
submit: function () {
console.log('editing');
$.ajax({ ... });
return false;
}
});
var editView = new EditView();
signin.js, this is the view that I can't merge because of the el being used by the ajax call and in SigninView's $(this.el) which causes the rendering of the templates faulty
window.toSigninView = Backbone.View.extend ({
el: '#signin-container',
events: {
"click #btn-signin" : "submit"
},
initialize: function() {
console.log('Signin View');
},
submit: function() {
$.ajax({ ... });
return false;
}
});
var toSignin = new toSigninView();
window.SigninView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
and I use utils.js to call my templates
window.utils = {
loadTpl: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('templates/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
In my Router.js, this is how I call the rendering of templates
editProfile: function() {
if (!this.editView) {
this.editView = new EditView();
}
$('#global-container').html(this.editView.el);
},
utils.loadTpl (['SigninView', 'EditView'],
function() {
appRouter = new AppRouter();
Backbone.history.start();
});
I think that I figured out your problem.
First merge your views and delete the line var toSignin = new toSigninView();
Second modify your utils.js code like this :
window[view].prototype.template = _.template(data);
new window[view]();

Javascript callback not firing when using this

Hello I have this code which works fine
var app = {
callback: null,
jqmReady: null,
pgReady: null,
// Application Constructor
initialize: function(callback) {
this.callback = callback;
this.jqmReady = $.Deferred();
this.pgReady = $.Deferred();
this.bindEvents();
},
bindEvents: function() {
document.addEventListener('deviceready', app.pgReady.resolve, false);
$(document).on("pageinit", app.jqmReady.resolve);
$.when(app.jqmReady, app.pgReady).then(app.isReady);
},
isReady: function() {
app.callback();
}
};
code is being initialized like this:
app.initialize(function(){
navigator.notification.alert('Hello there!', function(){}, 'Notify', 'Ok');
});
however my isReady function was like this at first and the callback was not called:
isReady: function() {
this.callback();
}
Why is this happening ? isn't the scope of this = app inside isReady() like in the initialize() function ?
Could someone explain to me why it doesn't work with this.callback() ?
You've created an object, not a class or an instance of a class. Change your this to app throughout your initialize function. You're doing that already in your isReady and bindEvents functions. So keep that going in initialize.

Setting correct this-value in requestAnimationFrame

I have an app-object constructer that looks like this:
var app = function(loadedsettings) {
return {
init: function() {
this.loop();
},
loop: function() {
this.update();
window.requestAnimationFrame(this.loop);
},
update: function() {
//loop through settings and call update on every object.
},
settings: [
//array of settings objects, all with update methods.
]
};
}
Then when I do:
var theApp = app(settings);
theApp.init();
I get:
Uncaught TypeError: Object [object global] has no method 'update'
because when requestAnimationFrame is called, the this-value inside the loop function is set to window.
Does anybody know how to call requestAnimatinFrame with the 'theApp' object set as the this-value?
You can create a bound function (with a fixed this), and pass that to requestAnimationFrame:
var app = function(loadedsettings) {
return {
init: function() {
this.loop();
},
loop: function() {
this.update();
window.requestAnimationFrame(this.loop.bind(this));
},
update: function() {
//loop through settings and call update on every object.
},
settings: [
//array of settings objects, all with update methods.
]
};
}
I think that a browser which supports requestAnimationFrame will also support Function.prototype.bind, but in case you come across one that doesn't, there are polyfills available.
You need to cache a reference to this:
var app = function(loadedsettings) {
var self = this;
return {
init: function() {
self.loop();
},
loop: function() {
self.update();
window.requestAnimationFrame(self.loop);
},
** snip **
...

Categories