This is oldish code but anyway...
I am trying to filter a store, and listen to the event in a comboBox, so I can refresh it.
My doQuery event didnt work, ( well actually it did work, but returned random result sets, leaving an overall wtf feeling )
config.store.filterBy(function Filter(record){
//this works
if (record.data.field != ""){
return true;
}
else {return false;}
});
However this does not automagically update the combobox.
So I tried various versions of
cbx = new Ext.getCmp(this);
debugger; //scope right here
this.getStore().on("datachanged",function refresh(){
cbx.reset();// store's scope
});
But the scope of cbx always seems to be the store, instead of the combobox.
Anyone have any a clue how to do add said a listener for the data change event in a store to a comboBox?
The default scope of an event callback is the object that triggered the event. In this case that would be the store. The third parameter of the method on() allows you to override the scope. Set the third parameter to this and your good to go.
From the documentation (3.x and 4.x):
on( String eventName, Function handler, [Object scope], [Object options] )
So something like this should do the trick:
this.getStore().on("datachanged", function refresh() {
cbx = Ext.getCmp(this);
cbx.reset();
}, this); // <-- Provide scope for the callback function
You can always set the scope of a function in ExtJS (I assume you're using ExtJS 3.x) with Function.createDelegate.
this.getStore().on("datachanged",function() {
cbx.reset();
}).createDelegate(this);
Although from the code sample you've provided, I'm not sure what the problem is since cbx.reset() shouldn't have any scope problems.
Related
I have created the following function.
function showAllSelectOpts(select)
{
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
When it is called directly on a select element like this showAllSelectOpts(mySelect); it works fine, but when called within another function, as below using the keyword "this", it returns the error. Type error: select.children not a function
$('select').on('focus',function(){
showAllSelectOpts(this);
})
Is this a scope issue or what, and how can I resolve it?
In an event handler, this is a reference to the DOM element, not a jQuery object. But your showAllSelectOpts expects its argument to be a jQuery object.
Either change the call to wrap the DOM element with $():
showAllSelectOpts($(this));
...or update showAllSelectOpts to do so itself:
function showAllSelectOpts(select)
{
select = $(select); // ***
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
Side note: As A.Wolff points out, your function attaches a new focusout handler to the select every time it's called. You only want one.
I'd remove that part of the handler entirely, and replace it with a single focusout:
function showAllSelectOpts(select)
{
var selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
}
$('select')
.on('focus',function(){
showAllSelectOpts($(this));
})
.on('focusout', function(){
$(this).attr('size', '1');
});
Also note that I added a var for selectLength in showAllSelectOpts (although actually, you could just remove the variable entirely); without one, the code is falling prey to The Horror of Implicit Globals (that's a post on my anemic little blog). Be sure to declare your variables.
jQuery event listener callbacks set this as the HTMLElement that the event was fired on.
In your callback you are expecting a jQuery object, but you have the HTMLElement.
You can pass the HTMLElement to a jQuery constructor and pass it into the showAllSelectOpts function
$('select').on('focus',function(){
showAllSelectOpts($(this));
})
Try this one -
$('select').on('focus',function() {
showAllSelectOpts($(this)); })
Try this:
var myselect = $('select');
$('select').on('focus',function(){
showAllSelectOpts(myselect);
})
A better way could be:
$('select').on('focus',function(event){
showAllSelectOpts($(event.target));
})
Why your code not working?
$('select').on('focus',function(){
//Here `this` is bound with the dom html element, not the $('select') object.
showAllSelectOpts(this);
})
These previous answers fix it. I'd just add here to create it as an extension since $(this) refers to a prototype of one method call.
$.fn.showAllSelectOpts=function() {
$(this).on('focus',()=>{
$(this)
.css('height','auto')
.attr('size',$(this).children().length)
.focusout(()=>{
$(this).attr('size','1');
});
});
};
$('select').showAllSelectOpts();
As we all know when we create a class in javascript a normal function returns the class object but events return the event object and the class object gets lost
function class(a){
this.name=a;
document.addEventListener('click',this.click,false);
xhr.addEventListener('load',this.xhr,false);
this.normal()
}
class.prototype={
click:function(e){
//e=event,this=theDocument //can't access class
},
xhr:function(e){
//e=event,this=theXHR //can't access class
},
normal:function(e){
//e=null,this=class
}
}
Whats the best way to bind those events to our class?
by best way i mean no or just a tiny reference, the ability to remove the events in a native way(removeEventListener) and to absolutely not create memory leaks.
1.to remove an eventlistener you need to pass the function as a reference, so addEventListener('click',function(){alert('something')},false) does not work.
2.i read references like var that=this inside functions create leaks, true?.
Known ways
function class(a){
this.name=a;
var that=this;// reference
//simply reference the whole object as a variable.
var bindedClick=this.click.bind(this);//bind the click to the class
//(js 1.85)
//can i use removeEventListener('click',bindedClick,false) later?
//apply && call (js 1.3)
}
As i'm not shure if var that=this (as it is the whole object) creates leaks, sometimes i minimize this by saving the info into an array and as reference i use a id..
var class={};
var id='ID'+Date.now();
class[id].info={here i store all the info i need later text only}
//this will be stored also in a cookie / Localstorage to reuse later.
class[id].dom={here i store the dom references}
class[id].events{here i store the xhr events}//if needed
//this are Temp only
and to get the info i just pass the id by adding it to the event element
class[id].events.xhr.id=id;
class[id].events.xhr.onload=class.f
class.prototype.f=function(e){
//this.response,class[this.id]<- access everything.
this.removeEventListener('load',class.f,false);
delete class[this.id].events.xhr;
delete this.id
}
class[id].dom.id=id;
class[id].dom.onclick=class.f2
class.prototype.f2=function(e){
//class[e.target.id],class[this.id]<- access everything.
this.removeEventListener('click',class.f2,false);
delete class[this.id].dom;
delete this.id
}
As you can see in this example above i have acess to everything and the reference is just a small string...
I store the dom because i define the dom references on load so i don't have to call getElementById() everytime;
I store the events like XHR in the class as i want to be able to call xhr.abort() from outside.. and also able to call removeEventListener.
I need to minimize the impact to the memory but at the other side i need to control many elements that have multiple simultaneous events to control the garbage collector "manually" by removing all events and references.
To make sure you understand that the problem is bigger thn it looks...:
it's a download manager for chrome. input field for download url:
1.xhr to get the fileinfo (size,name,acceptranges,mime)
2.store info in localstorage and cached array
2.create visual dom elements to show progress (event:progress,click,mouseover..)
3.xhr request a chunk 0-1000(event:load)
4.onprogress display progress data (event:progress,error)
5.request filesystem(event:ok,error)
6.readfile/check file (event:ok,error)
7.request filewriter (event:ok,error)
8.append to file (events=ok,error)
9.on ok start again from 3. until file is finished
10.when finished i delete all the **references / events / extra info**
//this to help the garbage collector.
11.change the dom contents.
Every file has sooo many events every sec.
Which of these 3 is the best solution or are there any better solutions?
bind();//or call / apply
var that=this; //reference to the whole object
var id=uniqueid; // reference to the object's id
BASED ON the answers:
(function(W){
var D,dls=[];
function init(){
D=W.document;
dls.push(new dl('url1'));
dls.push(new dl('url2'));
}
function dl(a){
this.MyUrl=a;
var that=this;
var btn=D.createElement('button');
btn.addEventListener('click',this.clc,false);
D.body.appendChild(btn);
}
dl.prototype={
clc:function(e){
console.log(that)
}
}
W.addEventListener('load',init,false);
})(window)
var that=this does not work.
this works... but i need alot of checks , swich if and execute multiple functions.
(function(W){
var D,dls=[];
function init(){
D=W.document;
dls.push(new dl('url1'));
dls.push(new dl('url2'));
}
function dl(a){
this.MyUrl=a;
this.btn=D.createElement('button');
btn.addEventListener('click',this,false);
D.body.appendChild(btn);
}
dl.prototype={
handleEvent:function(e){
e.target.removeEventListener('click',this,false);//does this the work?
return this.clc(e);
},
clc:function(e){
console.log(this,e)
}
}
W.addEventListener('load',init,false);
})(window)
BIND :
(function(W){
var D,dls=[];
function init(){
D=W.document;
dls.push(new dl('url1'));
dls.push(new dl('url2'));
}
function dl(a){
this.MyUrl=a;
this.clcB=this.clc.bind(this);
this.btn=D.createElement('button');
this.btn.addEventListener('click',this.clcB,false);
D.body.appendChild(this.btn);
}
dl.prototype={
clc:function(e){
e.target.removeEventListener('click',this.clcB,false);//does this the work?
delete this.clcB;
console.log(this)
}
}
W.addEventListener('load',init,false);
})(window)
Better solution is to have your "class" implement the EventListener interface.
You do this by adding a handleEvent method to MyClass.prototype. This allows you to pass the object directly to .addEventListener() instead of passing a handler.
When an event occurs, the handleEvent() method will be invoked, with your object as the this value. This allows you to have access to all the properties/methods of the object.
function MyClass(a) {
this.name = a;
// pass the object instead of a function
document.addEventListener('click', this, false);
xhr.addEventListener('load', this, false); // where did `xhr` come from?
this.normal()
}
MyClass.prototype = {
// Implement the interface
handleEvent: function(e) {
// `this` is your object
// verify that there's a handler for the event type, and invoke it
return this[e.type] && this[e.type](e);
},
click: function (e) {
// `this` is your object
},
load: function (e) {
// `this` is your object
},
normal: function (e) {
// `this` is your object
}
}
Notice that I changed the name of your xhr method to load. This makes it easier to call the proper method based on the event type.
Then when it comes time to call .removeEventListener(), just do it like normal from the element, but again pass the object instead of the handler.
I read references like var that=this inside functions create leaks
Wrong. They create references that are not garbage-collected until the function is, but that's exactly what you want. It's not a leak.
It might cause problems in very old browsers (IE6) that cannot handle cyclic references, but simply don't care about those. Also, by calling removeEventListener you even destroy that cyclic reference so everything is fine.
I minimize this by saving the info into an array and the reference is just a small string...
No. The reference is your class array which will much more likely create a leak if you forget to delete the ids from id. Don't overcomplicate this.
Which of these 3 is the best solution or are there any better solutions?
var that=this; //reference to the whole object
The standard approach. Very fine.
.bind();
Can be more concise than a that variable, and has the same reference layout (no difference in garbage collection). Notice that a native bind is not available on old browsers, so some people frown upon this method. Fine as well, but might need a shim.
var id=uniqueid; // reference to the object's id
Don't do that. It's too complicated, and you easily make mistakes that really lead to huge leaks.
The event listener interface with a handleEvent method (presented by #CrazyTrain)
Very elegant, but unknown to the most people. Low memory footprint since no bound, privileged functions need to be created. Works very well for classes that need to handle only one event type, but needs some kind of delegation when supporting different events or different targets with the same listener instance (and can become more complex than the other approaches). Contra: The handlerEvent method is public and everything that has access to your instance can "fire" (spoof) events.
var that=this does not work.
You're using it wrong. The point of this approach is to create a closure in which new functions have access to the that variable. The closure scope is your constructor, you cannot access it from the prototype.
var that=this;
btn.addEventListener('click',function(e){that.clc(e);},false);
// use `this` in the prototype
handleEvent works... but i need alot of checks , swich if and execute multiple functions.
No. Your instances do only handle the click of the button, so this approach is fine for you. You even could put all of your code from clc directly into handleEvent.
Let's say I have a jquery plugin that has an onSelect attribute. The user sets it to a function, and when that function is called, this refers to the object the plugin is applied to. All is good.
If I want to write a plugin that wraps this plugin, in order to inject some code into the onSelect, I do something like this:
// Get whatever the user put in
var extOnSelect = options['onSelect'];
// delete the onSelect attribute
delete options['onSelect'];
// re-add onSelect as an anonymous function that calls my method and the user's
var options = $.extend({
'onSelect': function() { onSelect(); extOnSelect(); }
}, options);
// call the plugin that I am wrapping / injecting extra onSelect code into
$(this).externalPlugin(options);
Then it will pass in my own onSelect code, while preserving what the user entered.
The only problem is, within each of those two functions this no longer refers to the object, it now refers to I think the generic inline function.
What's the best solution to fix this?
Use apply and the arguments object:
extOnSelect.apply(this, arguments);
to call extOnSelect exactly like the current function.
You could use call, but then you would need to pass the event object (and other possibe arguments) explicitly.
I think you need to use call method of javascript functions.
var options = $.extend({
'onSelect': function() { onSelect.call(this); extOnSelect.call(this); }
}, options);
I'm not using eval, and I'm not sure what the problem is that Crockford has with the following. Is there a better approach to solve the following problem or is this just something I need to ignore (I prefer to perfect/improve my solutions if there is areas for improvement).
I'm using some pixel tracking stuff and in this case a client has bound a JS function to the onclick property of an HTML image tag which redirects off the site. I need to track the clicks reliably without running into race conditions with multiples of event listeners on the image. The strategy is to override the event at run time, copying and running it in my own function. Note this is being applied to a site I do not control and cannot change. So the solution looks something like:
...
func = Function(img.attr('onclick'));
...
img.attr('onclick', '');
... //some custom tracking code
func.call(this);
and the JSLint checker throws the eval is evil error.
Is there a better way to avoid race conditions for multiple events around href actions?
You're implicitly using eval because you're asking for the callback function as it was specified as an attribute in the HTML as a string and then constructing a Function with it.
Just use the img.onclick property instead, and you will directly obtain the function that the browser built from the attribute that you can then .call:
var func = img.onclick; // access already compiled function
img.onclick = null; // property change updates the attribute too
... // some custom tracking code
func.call(img, ev); // call the original function
or better yet:
(function(el) {
var old = el.onclick;
el.onclick = function() {
// do my stuff
..
// invoke the old handler with the same parameters
old.apply(this, arguments);
}
})(img);
The advantage of this latter method are two fold:
it creates no new global variables - everything is hidden inside the anonymous closure
It ensures that the original handler is called with the exact same parameters as are supplied to your replacement function
var oldClick = myImg.onclick;
myImg.onclick = function(evt){
// Put you own code here
return oldClick.call( this, evt );
};
I don't know if I'm saying this right, so I'll just ask by explaining with an example.
Let's say I've written a jQuery plugin with an onShowEdit callback.
I later use my plugin and add a bunch of other default functions/methods to the event:
$('.editable_module:not(.custom)').editable({
onShowEdit: function(el){
initRequired();
$(':radio, :checkbox', el).prettyCheckboxes();
initDatePickers();
initChosen();
initMaskedInputs();
$('.dynamic_box.tabs').dynamicBoxTabs();
$('.trigger_dynamic_box').triggerDynamicBox('true');
}
});
So now I have a basic/default element (.editable_module) that calls the plugin and has some methods/functions that are going to be used in all instances.
My question comes when I have a need to add something to this for a 'one time' kind of deal (I need to add some behavior to this callback/event but not something that is used normally). Is it possible to extend or add to this callback/event without overwriting it? I mean, I know I can go in and do this:
$('#new_selector').editable({
onShowEdit: function(el){
initRequired();
$(':radio, :checkbox', el).prettyCheckboxes();
initDatePickers();
initChosen();
initMaskedInputs();
$('.dynamic_box.tabs').dynamicBoxTabs();
$('.trigger_dynamic_box').triggerDynamicBox('true');
//ADD SOME NEW STUFF HERE
}
});
But is that really my only option?
Thanks in advance for any input/suggestions.
You could consider jQuery's own event system as follows: http://jsfiddle.net/VQqXM/1/. You can integrate this in your $.fn function pretty easily - just pass the appropriate function as property of the object instead of a function literal.
$("input").on("foo", function() {
alert(1);
});
// later
$("input").on("foo", function() {
alert(2);
});
// later
$("input").trigger("foo"); // alerts 1 and 2
You can simply use .on/.off to bind and unbind events, and trigger them all with .trigger. jQuery also supports namespacing of the event names to make sure you're not using an already used event.
You could use the new $.Callbacks() method
var $onShowEditCBObj = $.Callbacks();
function onShowEditHandler() {
$onShowEditCBObj.fire();
}
$('#new_selector').editable({
onShowEdit: onShowEditHandler
});
// add default event to callbacks obj
$onShowEditCBObj.add(function(){
initRequired();
$(':radio, :checkbox', el).prettyCheckboxes();
initDatePickers();
initChosen();
initMaskedInputs();
$('.dynamic_box.tabs').dynamicBoxTabs();
$('.trigger_dynamic_box').triggerDynamicBox('true');
});
// add a one time method to the callbacks obj
function oneTimeEvent () {
alert("worky");
$onShowEditCBObj.remove(oneTimeEvent);
}
$onShowEditCBObj.add(oneTimeEvent)
With this setup, you can change what callbacks will be fired without having to do anything extra to the editable plugin.
Edit: I didn't realize that you wrote the plugin. With that in mind, pimvdb's answer is more robust than requiring the developer to code a certain way.
If I understand the question correctly, the key word here is "factory".
jQuery is itself a factory but to get what you describe, you need your plugin also to be a factory within the factory. That requires the plugin to be written in a certain way.
Probably the easiest approach is to use jQuery's UI widget factory. Read about it here.
Defining a separate function for onShowEdit should work.
var myOnShowEdit = function(el, extra_fn) {
//standard functionality goes here
if (typeof extra_fn==='function') extra_fn(); //support for extra stuff
}
$('.editable_module:not(.custom)').editable({
onShowEdit: function(el) {
myOnShowEdit(el);
}
});
$('#new_selector').editable({
onShowEdit: function(el) {
myOnShowEdit(el, function(){console.log('hi');});
}
});
This will give you fair flexibility to add whatever functionality you need in addition to the standard stuff. Just be aware of how this may shift contexts.