ExtJS 4 - async callback to callParent throws exception - javascript

I want to prepare some Json Store before processing to callParent(), then it throws an error.
However, me.callParent() works fine outside without async callback.
Ext.define('My.desktop.AppExt', {
extend: 'Ext.ux.desktop.App',
someStore: null,
init: function() {
var me = this;
me.someStore = Ext.create('My.store.SomeStore');
me.someStore.load({
scope: this,
url: 'some/json/url',
callback: function(records, opt, success) {
if (success) {
me.callParent(); // BOOM! ERROR HERE
}
}
});
}
});
ERROR:
Unhandled exception at line 4245, column 17 in //localhost/js/ext-all-debug.js
0x800a138f - JavaScript runtime error:
Unable to get property 'superclass' of undefined or null reference

callParent relies on the context to call the right method, so if you're not actually calling it "directly" from a subclass method, you'll need to invoke it manually:
Ext.define('A', {
foo: function(){
console.log('foo', 'a');
}
});
Ext.define('B', {
extend: 'A',
bar: function(){
this.self.superclass.foo.call(this);
}
});
Ext.onReady(function(){
var o = new B();
o.bar();
});

The best solution for this purpose is to get link to parentMethod like at callParent() function, but without invoking it:
/**
* Returns reference to the parent class method. Works as {#link Ext.Base#callParent}, but doesn't invoke the
* method.
* #return {Function} Parent class method.
*/
getParentMethod: function() {
var method,
superMethod = (method = this.getParentMethod.caller) && (method.$previous ||
((method = method.$owner ? method : method.caller) && method.$owner.superclass[method.$name]));
return superMethod;
},
sampleMethod: function() {
var parentMethod = this.getParentMethod();
var parentArguments = arguments;
someAsyncFunc(function() {
parentMethod.apply(this, parentArguments); // calls parent.sampleMethod(...)
}, this);
}

One of the ways I know is to use extra parameter, that indicates that parent method should be called:
init: function(callParent) {
if (callParent) {
this.callParent();
}
var me = this;
me.someStore = Ext.create('My.store.SomeStore');
me.someStore.load({
scope: this,
url: 'some/json/url',
callback: function(records, opt, success) {
if (success) {
this.init(true);
}
}
});
}
If you use this.self.superclass.init.call(this) it will be ok only until somebody will create child for your class. this.self.superclass points to superclass of instance's class, so it would points to My.desktop.AppExt instead of Ext.ux.desktop.App.
Updated 24.08.2016: Published smarter solution (see another answer by me).

Related

How to use a variable inside itself

I want to use the variable inside itself and I see other people do it but why does it not work for me?
This is my ES6 file
// Setup module
// ------------------------------
var FullCalendarAdmin = function () {
//
// Setup module components
//
var _componentRender = function () {
// Basic calendar
var _componentFullCalendarAdmin = function (events) {
// Define element
var calendarAgendaViewElement = document.querySelector('.fullcalendar-agenda-admin');
// Initialize
if (calendarAgendaViewElement) {
var calendarAgendaViewInit = new FullCalendar.Calendar(calendarAgendaViewElement, {
plugins: ['dayGrid', 'timeGrid', 'interaction'],
select: function (start, end) {
var title = prompt("Add event:");
var data;
if (title != '') {
data = {
title: title,
start: start,
end: end
};
calendarAgendaViewInit.addEvent(data);
}
}).render();
}
};
//
// Return objects assigned to module
//
return {
init: function () {
_componentRender();
}
}
}();
// Initialize module
// ------------------------------
document.addEventListener('DOMContentLoaded', function () {
FullCalendarAdmin.init();
});
How can I use the calendarAgendaViewInit to call the addEvent function without getting function as an undefined error?
Thanks in advance!
The problem is that you invoke .render immediately.
So your calendarAgendaViewInit is not an instance of FullCalendar.Calendar but the result of the render method.
What you can do is first define the calendarAgendaViewInit variable
var calendarAgendaViewInit = new FullCalendar.Calendar(calendarAgendaViewElement, {
plugins: ['dayGrid', 'timeGrid', 'interaction'],
select: function (start, end) {
var title = prompt("Add event:");
var data;
if (title != '') {
data = {
title: title,
start: start,
end: end
};
calendarAgendaViewInit.addEvent(data);
}
});
and then call calendarAgendaViewInit.render().
This is sort of an expanded explanation to the comment above. It looks like calendarAgendaViewElement is simply a DOM element that you've found and assigned to a variable. The problem here is that you can only call methods on class instantiations that are now objects with methods inside. If you had seen others call addEvent like that, then they were likely calling it on an instantiation of a class meaning that addEvent had been previously declared as part of that class and they are simply calling that method.
See the example below,
If I declare a class as follows:
class Sample {
sayHello(){
console.log('hello')
}
}
Then instantiate a new object of the 'Sample' class:
var sampleClass = new Sample()
Then I can call 'sayHello' by referring to the method inside the object
sampleClass.sayHello() // hello
Hope that helps

PubsubJs and *this* is undefined when subscribing to a message

I have the following code:
someClass1 = function () {
this.doStuff = function () {
PubSub.publish('topic1', { id: 1 });
}
}
someClass2 = function () {
this.forename = 'bob2';
PubSub.subscribe("topic1", function (msg, data) {
log(msg, data, this.forename);
});
}
function log() {
console.log(arguments);
}
var c1 = new someClass1();
var c2 = new someClass2();
c1.doStuff();
and I am using the pubsubjs library (https://github.com/federico-lox/pubsub.js)
The code is simple - publish a message and handle it in another class (someClass2) using PubSub
My question is that when I publish a message and handle it in someClass2, this is undefined. This occurs at the line: log(msg, data, this.forename);
This means I cant access any of the someClass2 properties/functions. What do I need to do to get the this to not be undefined? Is this possible? Are they other libraries that will help? Am I doing it wrong....
All help apprenticed! Thanks
You're passing an unbound function to subscribe. Such a function has "no idea" about this. You have to bind it:
PubSub.subscribe("topic1", (function (msg, data) {
log(msg, data, this.forename);
}).bind(this));
this is not what you expect inside the callback, just cache a copy of this as another variable outside:
someClass2 = function () {
this.forename = 'bob2';
var that = this;
PubSub.subscribe("topic1", function (msg, data) {
log(msg, data, that.forename);
});
}

Pass "this" to a bootbox callback with Backbone

I'm using Backbone and bootbox.
This is my code inside a view:
confirm : function(result) {
if (result === true) {
var that = this;
this.model.set({completed: '1'}); // Exception here
this.model.save(
null, {
success: function (model, response) {
Backbone.history.navigate("index", true);
},
error: function(model, response) {
that.model.set({completed: '0'});
var responseObj = $.parseJSON(response.responseText);
bootbox.alert(responseObj.message);
}
});
}
},
completeProcess : function(event) {
event.preventDefault();
this.model.set({completed: '1'});
bootbox.confirm("Confirm?", this.confirm );
}
I'm getting this error:
Uncaught TypeError: Cannot call method 'set' of undefined
Is there a way to pass the reference from the view?
As underscore is a dependency of backbone you could use its _.bind feature:
_.bind(function, object, [*arguments])
Bind a function to an object, meaning that whenever the function is
called, the value of this will be the object.
Optionally, pass
arguments to the function to pre-fill them, also known as partial
application.
In your case this could look like this:
completeProcess : function(event) {
event.preventDefault();
this.model.set({completed: '1'});
bootbox.confirm("Confirm?", _.bind(this.confirm, this));
}
Alternatively, you could do something like this to hang on to the original 'this':
var _this = this;
bootbox.confirm({
message: Message ,
callback: function(result) {
console.log(this + " != " + _this);
},

Scope Error in Javascript. Calling a method within a different method of that object

I've got a variable timekeep.
var timeKeep;
and I define it thusly:
timeKeep = Class.create({
initialize: function() {
this.initObservers();
},
initObservers: function() {
$$('input').each( function(el) {
el.observe('keypress', function(ev) {
// the key code for 'enter/return' is 13
if(ev.keyCode === 13){
timeKeep.submit();
// Uncaught TypeError: Object function klass() {
// this.initialize.apply(this, arguments);
// } has no method 'submit'
}
});
});
},
submit: function() {
alert('Submitted!');
}
})
The error I am getting is commented out below the line that it occurs. It's got something to do with calling a timeKeep method within a different scope I think?
Is there a problem calling timeKeep.method() inside a foreach statement?
Problem is with you OOP style. Use a closure so you call the current instance of your class.
initObservers: function () {
var that = this;
$$('input')
.each(function (el) {
el.observe('keypress', function (ev) {
// the key code for 'enter/return' is 13
if (ev.keyCode === 13) {
that.submit();
}
});
});
},
You could also look at bind
initObservers: function() {
var submit = this.submit.bind(this);
$$('input')
.each(function (el) {
el.observe('keypress', function (ev) {
// the key code for 'enter/return' is 13
if (ev.keyCode === 13) {
submit();
}
});
});
},
You are assuming that Class.create returns an instance of an object of the type you are defining, but no, it returns a constructor function for creating instances of the class you are defining.
You can add the new keyword to the assignment and then you will have in timeKeep what you want to:
timeKeep = new Class.create({
...
})()
As suggested - using Function#bind() will solve your problem but there is a cleaner way so that you can continue to use this inside the class scope.
Also look into the invoke() method as this is a perfect opportunity to use it. $$() returns a list of elements and if you want to perform the same function on all of the elements invoke() will handle iterating over the list.
timeKeep = Class.create({
initialize: function() {
this.initObservers();
},
initObservers: function() {
$$('input').invoke('observe','keypress',function(ev) {
// the key code for 'enter/return' is 13
if(ev.keyCode === 13){
timeKeep.submit();
// Uncaught TypeError: Object function klass() {
// this.initialize.apply(this, arguments);
// } has no method 'submit'
}
//Add the bind method to the closure to bind 'this' inside that function scope
//to 'this' of the class
//the binding will allow you to call this.submit() instead of timeKeep.submit
//as well as access any of the class properties and methods inside this closure
}.bind(this));
} ,
submit: function() {
alert('Submitted!');
}
});
PrototypeJS documentation on observers http://api.prototypejs.org/dom/Event/observe/ - look at the heading Using an Instance Method as a Handler

Why has the author of this code used the .call method? Surely he could have just accessed the prototype?

Im looking through some code (unfortunatly the author isnt around anymore) and im wondering why he has used the .call method.
hmlPlaylist.prototype.loadVideos = function () {
var scope = this;
this.config.scriptUrl = '_HMLPlaylistAjax.aspx?' + Math.random();
jQuery.ajax({
type: 'GET',
url: this.config.scriptUrl,
success: function (d, t, x) {
scope.loadVideos_callback.call(scope, d);
},
error: function () {
}
});
};
hmlPlaylist.prototype.loadVideos_callback = function (data) {
var jsonData = '';
var jsonError = false;
try {
jsonData = eval("(" + data + ")");
} catch (jError) {
jsonError = true;
}
if (!jsonError) {
if (jsonData.playlists.length > 0) {
this.buildPlaylistList(jsonData.playlists);
}
if (jsonData.videos.length > 0) {
this.buildVideoList(jsonData.videos);
this.bindVideoNavs();
}
}
else {
// no json returned, don't do anything
}
};
Obviously he seems to have used it to pass a 'this' reference to the loadVideos_callback method but why? The 'loadVideos_callback' method is attached to the prototype of 'hmlplaylist' which is the 'class'. So if you access this inside the 'loadVideos_callback' method you get to the same thing dont you?
yes, I think you are right (I can't see the code in action). You still need the closure around scope, but in this case the use of call is not necessary.
To pull some of the comments into this answer, this is always the context on which the method was invoked. So if a new instance of htmlPlayList was created, and the method invoked on that instance, this would be a reference to that instance.

Categories