I'm trying to organize my ExtJS javascript a little better. I've an ExtJS object like this:
Ext.define('QBase.controller.ControlModelConfigurationController', {
extend: 'Ext.app.Controller',
views: [
'ControlModelConfiguration'
],
init: function() {
console.log('Initialized ControlModelConfigurationController');
this.control({
'#testBtn': {
click: this.loadModel
}
});
},
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes,handleNode);
}
}
});
},
handleNode: function(items) {
console.log(item.name);
}
});
The posted code above is not working. Ext.Array.forEach(nodes,handleNode) causes trouble. Instead of using an anonymous function like :
...
Ext.Array.forEach(nodes,function(item) {
console.log(item)});
}
...
I'd like to extract the anonymous function as a named external one. Unfortunately I'm unable to figure out the right syntax to establish a code structure as shown above.
Meanwhile, I figured out, that putting
function handleNode(item) {
{console.log(item)}
}
at the very end of the file works. Is it possible to make the handleNode method an object - "member" of the controller?
Thanks in advance
Chris
handleNode is a member of the containing object. When loadModel is called, this contains the right object, but at the time the callback is invoked, it will not point to the one we are interested in. You can save this to the local variable self, and use it instead.
loadModel: function() {
var self = this
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes, self.handleNode);
}
}
});
},
The solution posted by vhallac is not entirely correct. It assumes that handleNode doesn't reference the current object through this variable. Maybe just a typo, but additionally it's not really the ExtJS way...
Whenever ExtJS provides a callback parameter, there is nearly always a scope parameter to set the value of this within the callback function.
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response) {
if (success) {
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml, parent);
Ext.Array.forEach(nodes, this.handleNode, this);
}
},
scope: this
});
},
handleNode: function(node) {
// From within here you might want to call some other method.
// Won't work if you leave out the scope parameter of forEach.
this.subroutine();
}
Just like forEach uses a scope parameter, the request method uses a scope config option. This is ExtJS convention for passing around the scope. You can alternatively create an extra local variable and reference the scope from there, but in the context of ExtJS this style will feel awkward, plus (I'm pretty sure) it's a lot more bug-prone.
Related
While migrating to requirejs I have run into a problem that I cannot seem to find the answer to.
The problem is that my nested functions do not have access to the parameters passed to the requirejs callback.
for example:
define(['knockout', 'knockoutmapping', 'other'], function(ko, mapping, other) {
var Something = function() {
var self = this;
self.items = ko.observableArray([]);
self.doSomeStuff = function(data) {
// I would think I would still be able to access
// parameters ko, mapping and other
// trouble is, I cannot. I only have
// a valid 1st parameter, "ko".
var d = ko.toJSON(self.items()); // this works fine
self.items(mapping.fromJS(data));// this does not work (undefined)
// do not have access to "other" either.
};
};
return {
Something: Something,
};
});
Any help with this would be appreciated. I know I could put these into variables in the callback, but that doesn't seem like the correct approach to me.
Thank you in advance.
in define, callback is first argument and it have one parameteter require function, try:
define(function(require) {
var knockout = require('knockout');
var knockoutmapping = require('knockoutmapping');
var other = require('./other');
var Something = function() {
...
};
return {
Something: Something,
};
});
I have a config.json that I am going to load into my app as a Backbone Model like:
var Config = Backbone.Model.extend({
defaults: {
base: ''
},
url: 'config.json'
});
Other models should be dependent on some data contained in Config like:
var ModelA = Backbone.Collection.extend({
initialize: function(){
//this.url should be set to Config.base + '/someEndpoint';
}
});
In above example, ModelA's url property is dependent on Config's base property's value.
How do I go about setting this up properly in a Backbone app?
As I see it, your basic questions are:
How will we get an instance of the configuration model?
How will we use the configuration model to set the dependent model's url?
How can we make sure we don't use the url function on the dependent model too early?
There are a lot of ways to handle this, but I'm going to suggest some specifics so that I can just provide guidance and code and "get it done," so to speak.
I think the best way to handle the first problem is to make that configuration model a singleton. I'm going to provide code from backbone-singleton GitHub page below, but I don't want the answer to be vertically long until I'm done with the explanation, so read on...
var MakeBackboneSingleton = function (BackboneClass, options) { ... }
Next, we make a singleton AppConfiguration as well as a deferred property taking advantage of jQuery. The result of fetch will provide always(callback), done(callback), etc.
var AppConfiguration = MakeBackboneSingleton(Backbone.Model.extend({
defaults: {
base: null
},
initialize: function() {
this.deferred = this.fetch();
},
url: function() {
return 'config.json'
}
}));
Now, time to define the dependent model DependentModel which looks like yours. It will call AppConfiguration() to get the instance.
Note that because of MakeBackboneSingleton the follow is all true:
var instance1 = AppConfiguration();
var instance2 = new AppConfiguration();
instance1 === instance2; // true
instance1 === AppConfiguration() // true
The model will automatically fetch when provided an id but only after we have completed the AppConfiguration's fetch. Note that you can use always, then, done, etc.
var DependentModel = Backbone.Model.extend({
initialize: function() {
AppConfiguration().deferred.then(function() {
if (this.id)
this.fetch();
});
},
url: function() {
return AppConfiguration().get('base') + '/someEndpoint';
}
});
Now finally, putting it all together, you can instantiate some models.
var newModel = new DependentModel(); // no id => no fetch
var existingModel = new DependentModel({id: 15}); // id => fetch AFTER we have an AppConfiguration
The second one will auto-fetch as long as the AppConfiguration's fetch was successful.
Here's MakeBackboneSingleton for you (again from the GitHub repository):
var MakeBackboneSingleton = function (BackboneClass, options) {
options || (options = {});
// Helper to check for arguments. Throws an error if passed in.
var checkArguments = function (args) {
if (args.length) {
throw new Error('cannot pass arguments into an already instantiated singleton');
}
};
// Wrapper around the class. Allows us to call new without generating an error.
var WrappedClass = function() {
if (!BackboneClass.instance) {
// Proxy class that allows us to pass through all arguments on singleton instantiation.
var F = function (args) {
return BackboneClass.apply(this, args);
};
// Extend the given Backbone class with a function that sets the instance for future use.
BackboneClass = BackboneClass.extend({
__setInstance: function () {
BackboneClass.instance = this;
}
});
// Connect the proxy class to its counterpart class.
F.prototype = BackboneClass.prototype;
// Instantiate the proxy, passing through any arguments, then store the instance.
(new F(arguments.length ? arguments : options.arguments)).__setInstance();
}
else {
// Make sure we're not trying to instantiate it with arguments again.
checkArguments(arguments);
}
return BackboneClass.instance;
};
// Immediately instantiate the class.
if (options.instantiate) {
var instance = WrappedClass.apply(WrappedClass, options.arguments);
// Return the instantiated class wrapped in a function so we can call it with new without generating an error.
return function () {
checkArguments(arguments);
return instance;
};
}
else {
return WrappedClass;
}
};
Lately I've been working on a project that requires me to make numerous AJAX calls to a Symfony backend. Since each AJAX call is made to a different URI, I've ended up with a script that's really long, but with numerous .on('event', function(){...}) code blocks, like this:
$(document).ready(function(){
$('.class').on('click', function(e){
e.preventDefault();
//AJAX call
This is basically duplicated over and over again, but because of slight variations in the selector and the type of data to be received, I keep writing this same block of code over and over again.
I've been thinking of using a builder pattern (is it even possible in JS?) to trim the code. I'm not very good at javascript, so any help would be much appreciated.
UPDATE:
/**
* AJAX prototype
*
* #param options
* #constructor
*/
//set TestProtObj properties in the constructor
var AjaxProt = function (options) {
this.ajaxCallType = options.ajaxCallType;
this.targetEl = options.targetEl;
this.event = options.event;
this.method = options.method;
this.htmlFactory = options.htmlFactory;
};
//add methods to the object prototype
AjaxProt.prototype = {
init: function () {
var targetEl = this.targetEl;
targetEl.on(this.event, function(e) {
e.preventDefault();
this.ajaxCall();
})
},
modalCallback: function(successData) {
var modal = this.htmlFactory.createHtml({
title: 'Bet: Detailed View',
id: '#bet-detailed-model',
htmlType: 'modal'
});
if (successData.success = true) {
$('#content').prepend(modal);
$('#bet-detailed-model').modal({show:
true
});
} else {
$('#content').prepend(modal);
$('#bet-detailed-model').modal({
show: true
});
$('.modal-body').append(alert);
}
},
ajaxCall: function() {
var url = this.targetEl.attr('href'),
method = this.method,
ajaxCallType = this.ajaxCallType;
switch (ajaxCallType) {
case 'modalGet':
var callback = this.modalCallback();
break;
}
$.ajax({
url: url,
type: method,
success: function(data) {
callback(data)
}
});
}
};
//initialize client code
$(document).ready(function () {
// initialize new AjaxPro
var AjaxBetDetailed = new AjaxProt ({
ajaxCallType: 'modalGet',
targetEl: $('.ajax-ticket-view'),
event: 'click',
method: 'GET',
htmlFactory: new HtmlFactory()
});
//initialize concrete object
AjaxBetDetailed.init();
});
Unfortunately, it appears that my event handler is not binding, such that e.preventDefault is not working - all it does is follow the link. I'm really not used to writing classes in this way, so any help would be greatly appreciated.
UPDATE 2:
I've also written a proof of concept class in jsfiddle that tries to replicate the behaviour I want to achieve. It is also failing to bind the event handler - so that must be the problem. I don't seem to be able to solve it.
JSFiddle: Click Me Please!
You could reuse the same function with your "slight variations" as parameters:
function registerClick(className, url, param) {
$('.' + className).on('click', function(e) {
// Ajax call using url and param, for instance
});
}
And then use it:
registerClick('class', '/api/foo', 'bar');
registerClick('toto', '/api/foo', 'buzz');
That's not specific to JS but any kind of programming/scripting language: put all reusable code into a function (or an object if you want it oriented object, or a prototype if you want it oriented prototype, etc, but the idea is the same).
I am having an "this" issue and would appreciate any help
this is my basic code
function xxx(val)
{
this.x = val;
this.change = function() {
var self = this;
$.ajax({
blah: '',
blah: '',
success: function(data) { self.x = 5; },
});
};
}
var newX = new x(1);
newX.change();
console.log(newX.x);
Hopefully that makes sense,
What I am trying to do is update the original object on the jquery ajax response. I do not have access to 'this' so i tried to pass it off to the 'self' variable but the object values does not seem to be updating even though the code is running as expected.
I am sure there is a simple answer, i just dont know it.
Any help is greatly appreciated.
So the way to resolve this is take a look at your function declarations. Each function declaration will give you a new this object. Store the one you are interested in the correct spot. If I'm correct, it looks like you actually want to access the scope of the original xxx function scope. So instead of storing this in the change function, store it above that in the original scope. Something like this:
function xxx(val)
{
var self = this;
this.x = val;
this.change = function() {
var that = this;
$.ajax({
blah: '',
blah: '',
success: function(data) { self.x = 5; },
});
};
}
var newX = new x(1);
newX.change();
console.log(newX.x);
The other issue here is that you are using AJAX to make that call so you either need a Deferred object or you could add a callback to that function that gets triggered at the right time like so:
function xxx(val)
{
var self = this;
this.x = val;
this.change = function(callback) {
var that = this;
$.ajax({
blah: '',
blah: '',
success: function(data) {
self.x = 5;
if (typeof callback === "function"){
callback.call(this);
}
}
});
};
}
var newX = new xxx(1);
newX.change(function(){
console.log(newX.x);
});
In knockout... you'll have to do something like that:
function Xxx(val)
{
var self = this;
this.x = ko.observable(val);
this.change = function() {
// "that" may be different to "self" in some
// cases...
var that = this;
$.ajax({
url: '...',
type: 'get',
success: function(data) {
self.x(5);
},
error: function(a) {
console.log('got an error');
}
});
};
}
var newX = new Xxx(1);
newX.change();
ko.computed(function () {
// This will get called everytime
// newX.x is changed
console.log(newX.x());
});
When you create a variable, that may change, you have to create it as an observable. The observable is in fact a function that you call. When called, it will update its inner value and it will also trigger any changes wherever the observable is "observed"
In no way you should try to do this.x = 5. It will override the actual observable object and thus it will never trigger every observer of a change.
edit
In case you're interested to understand how does computed works. A computed variable is a function that will listen to observables. When the computed is created, it will be called once to check which observables where called from within it. It's a way to "track" dependencies. In this example, you should see at least two console log. one with 1, and then with 5.
In my case, the computed variable is kind of anonymous since it isn't affected anywhere. Also in some case, you may need to observe one variable but use multiple observables. To prevent update on any other used observables. There are some ways to do that. You can either return after you "watched" the observables you needed.
Or you can create a sub function that will be triggered a little after the computed with setTimeout(..., 0);. There are a couple of ways to achieve some really nice tricks.
Developing an app where all tabular data is returned as an object.
Some cells will have onclick events on them.
The JSON object is coming back fine and there is a key in the object call 'cb'.
This is set by the logic on the server.
My question is the object key cb will contain a string, how can I run that as a valid function without using eval()
Example:
var archive = function() {
console.log('archiving');
}
new Request.JSON ({
...
onSuccess: function(r){
//r.cb: 'archive'
docuemnt.id(td).addEvent('click', r.cb);
}
...
});
docuemnt.id(td).addEvent('click', eval(r.cb)); // works looking for alternative
I know i am over thinking this and it should not be that difficult.
Must not have had enough coffee yet today.
Use square bracket notation. If your function is in the global scope, use window[r.cb]:
new Request.JSON ({
...
onSuccess: function(r) {
//r.cb: 'archive'
document.id(td).addEvent('click', window[r.cb]);
}
...
});
If your function is not in the global scope, move your functions into an object:
var callbacks = {
archive: function () { ... },
foo: function () { ... },
...
}
Then use callbacks[r.cb].