How to handle events in jQuery UI widgets - javascript

I'm trying to write a jQuery widget following the model given here.
Here is a snapshot of the widget:
(function ($) {
$.widget("ui.notification", {
_create: function () {
if (!this.element.hasClass("ntfn")) {
this.element.addClass("ntfn");
}
this.elTitle = this.element.append("<div class='ntfn-title'>Notifications</div>");
this.elTitle.click(this._titleClick)
},
_titleClick: function () {
console.log(this);
}
});
})(jQuery);
Here the problem is with the scope of "this" inside the _titleClick method, inside the method this points to the title element. But I need it to point to the widget element.
I think one way of doing it will be to use a wrapper class like
var that = this;
this.elTitle.click(function() {
that._titleClick.apply(that, arguments);
});
Is this the best way to solve this problem or is there any general pattern to solve this issue?

Use the this._on() method to bind the handler. This method is provided by the jQuery UI widget factory and will make sure that within the handler function, this always refers to the widget instance.
_create: function () {
...
this._on(this.elTitle, {
click: "_titleClick" // Note: function name must be passed as a string!
});
},
_titleClick: function (event) {
console.log(this); // 'this' is now the widget instance.
},

You should look to jQuery.proxy() http://api.jquery.com/jQuery.proxy/
el.bind('evenname', $.proxy(function () {
this.isMyScope.doSomething();
}, scope));

I wrote a method my own to solve this issue
_wrapCallback : function(callback) {
var scope = this;
return function(eventObject) {
callback.call(scope, this, eventObject);
};
}

In your create, init (or somewhere in your instance) function do this:
_create: function() {
...
// Add events, you will notice a call to $.proxy in here. Without this, when using the 'this'
// property in the callback we will get the object clicked, e.g the tag holding the buttons image
// rather than this widgets class instance, the $.proxy call says, use this objects context for the the 'this'
// pointer in the event. Makes it super easy to call methods on this widget after the call.
$('#some_tag_reference').click($.proxy(this._myevent, this));
...
},
Now define your objects event hander like this:
_myevent: function(event) {
// use the this ptr to access the instance of your widget
this.options.whatever;
},

define var scope=this, and use scope in event handler.
_create: function () {
var scope = this;
$(".btn-toggle", this.element).click(function () {
var panel = $(this).closest(".panel");
$(this).toggleClass("collapsed");
var collapsed = $(this).is(".collapsed");
scope.showBrief(collapsed);
});
},

Another way to do the same thing without using closure, is to pass the widget as a part of the event data like so:
// using click in jQuery version 1.4.3+.
var eventData = { 'widget': this };
// this will attach a data object to the event,
// which is passed as the first param to the callback.
this.elTitle.click(eventData, this._titleClick);
// Then in your click function, you can retrieve it like so:
_titleClick: function (evt) {
// This will still equal the element.
console.log(this);
// But this will be the widget instance.
console.log(evt.data.widget);
};

It used to be via the jquery bind method now on is favoured.
As of jQuery 1.7, the .on() method is the preferred method for
attaching event handlers to a document. For earlier versions, the
.bind() method is used for attaching an event handler directly to
elements. Handlers are attached to the currently selected elements in
the jQuery object, so those elements must exist at the point the call
to .bind() occurs. For more flexible event binding, see the discussion
of event delegation in .on() or .delegate().
_create: function () {
var that = this;
...
elTitle.on("click", function (event) {
event.widget = that; // dynamically assign a ref (not necessary)
that._titleClick(event);
});
},
_titleClick: function (event) {
console.log(this); // 'this' now refers to the widget instance.
console.log(event.widget); // so does event.widget (not necessary)
console.log(event.target); // the original element `elTitle`
},

Related

ES6 - How to access `this` element after binding `this` class?

How can I access this element after binding this class?
For example, without binding this:
$(".button-open").click(function(event) {
console.log(this); // Open
this.openMe();
});
With binding this:
$(".button-open").click(function(event) {
console.log(this); // Polygon {windowHeight: 965, scrollNum: 0}
this.openMe();
}.bind(this));
How can I get and access Open again after binding this?
Full code:
class Polygon {
constructor() {
this.windowHeight = $(window).height();
this.scrollNum = 0;
}
// Simple class instance methods using short-hand method
// declaration
init() {
var clickMe = this.clickMe.bind(this);
return clickMe();
}
clickMe() {
$(".button-open").click(function(event) {
console.log(this);
this.openMe();
}.bind(this));
$(".button-close").click(function(event) {
this.closeMe();
}.bind(this));
}
openMe() {
console.log(this.scrollNum); // 0
this.scrollNum = 200;
console.log(this.scrollNum); // 200
return false;
}
closeMe() {
console.log(this.scrollNum); // 200
return false;
}
}
export { Polygon as default}
Any ideas?
EDIT:
The same issue with jQuery animate:
$(".element").animate({}, 'fast', 'swing', function(event) {
console.log(this); // the element
}.bind(this));
After binding:
$(".element").animate({}, 'fast', 'swing', function(event) {
console.log(this); // undefined
}.bind(this));
Any global or bulletproof way of getting the element again?
1. The best option would be to store the context in a variable and don't overwrite this:
var context = this;
$('.element').on('click', function(event) {
// context would be the this you need
// this is the element you need
});
2. If you're only targeting a single element, you can do the reverse from above and save the element on which you're binding the handler into a variable and then use the variable inside the handler:
var el = $('.element');
el.on('click', function(event) {
// use el here
}.bind(this));
Since you tagged the question with ES6, it might be better to bind the context with an arrow function because using bind is more verbose and also creates an additional function:
var el = $('.element');
el.on('click', (event) => {
// this is the same as in the outer scope
// use el here
});
3. Another option is to use the target property of the event object but this can also be any child within your element (the target is the element that dispatches the event, not the element on which you bounded the handler), thus it might require traversing up the DOM tree to find the element you need, which is less efficient.
var el = $('.element');
el.on('click', ({ target }) => {
while (target.parentNode && !target.classList.contains('element')) {
target = target.parentNode;
}
// here the target should be the element you need
});
There is no generic way to get access to what the value of this would have been if you didn't use .bind(). Javascript doesn't have a way to unbind and get back what this would have been. Instead, you have to look at each individual situation and see if there is some other way to get to the whatever this would have been.
For example, as several of us have said, in a click handler, you can access event.target.
The jQuery animate does not pass any arguments to its callback so if you override this, then there is no generic way to get back to the triggering element. You'd have to go back to the selector again or have saved the value in a containing closure (folks commonly use a variable named self for that).
The only generic way to avoid this issue is to not use .bind() so the value of this is not replaced. You can do something like this:
clickMe() {
var self = this;
$(".button-open").click(function(event) {
// self is our ES6 object
// this is the item that triggered the event
console.log(this);
self.openMe();
});
If you bound your handler, then you can still get the item that was clicked on through event.target within the handler.
https://api.jquery.com/on/
As an alternative you can simply do
const self = this;
or
const me = this;
before any of your declarations of event listeners and without binding any functions. Then within handlers you can both use this to refer to the current element and self or me to refer to the parent scope.
It is already answered, but here is the pattern which I usually use:
If there is single '.element', the below code will work
var el = $('.element');
el.click(function(target, event){
// target is the original this
// this is the scope object
}.bind(this, el[0]));
But if '.element' refers to multiple elements then below code will handle that
var clickHandler = function(target, event){
// target is the original this
// this is the scope object
}.bind(this);
$('.element').click(function(e) {
return clickHandler(this, e);
});

jQuery $(this) not working when inside a function

I have this simple function that copies some html, and places it in another div.
If I put the code for the function in the click event it works fine, but when I move it into a function (to be used in multiple places) it no longer works.
Do you know why this is?
If I console.log($(this)); in the function it returns the window element.
function addHTMLtoComponent () {
var wrapper = $(this).closest(".wrapper");
var component = $(wrapper).find(".component");
var componentCodeHolder = $(wrapper).find('.target');
$(componentCodeHolder).text(component.html())
//console.log($(this));
}
$(".js_show_html").click(function () {
addHTMLtoComponent();
});
codepen here - http://codepen.io/ashconnolly/pen/ebe7a5a45f2c5bbe58734411b03e180e
Should i be referencing $(this) in a different way?
Regarding other answers, i need to put the easiest one:
$(".js_show_html").click(addHTMLtoComponent);
since you called the function manually the function doesn't know the "this" context, therefore it reverted back to use the window object.
$(".js_show_html").click(function () {
addHTMLtoComponent();
});
// Change to this
$(".js_show_html").click(function () {
// the call function allows you to call the function with the specific context
addHTMLtoComponent.call(this);
});
Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
this in the context of the click() event is the element clicked. In the context of the function addHTMLtoComponent this no longer is a reference to the element clicked.
Try passing the clicked object to the function to maintain the object reference.
function addHTMLtoComponent ($obj) {
var $wrapper = $obj.closest(".wrapper");
var $component = $wrapper.find(".component");
var $componentCodeHolder = $wrapper.find('.target');
$componentCodeHolder.text($component.html());
}
$(".js_show_html").click(function () {
addHTMLtoComponent($(this));
});
The special keyword this, when you call a function by itself, is the window object (which is what you observed). For the behavior you need, just add a parameter to the function that loads the appropriate context:
function addHTMLtoComponent(context) {
var wrapper = $(context).closest(".wrapper");
var component = $(wrapper).find(".component");
var componentCodeHolder = $(wrapper).find('.target');
$(componentCodeHolder).text(component.html())
//console.log($(context));
}
$(".js_show_html").click(function() {
addHTMLtoComponent(this);
});
One thing you could consider is that addHTMLtoComponent() could be made into a jQuery function itself:
$.fn.addHTMLtoComponent = function() {
return this.each(function() {
var wrapper = $(this).closest(".wrapper");
var component = $(wrapper).find(".component");
var componentCodeHolder = $(wrapper).find('.target');
componentCodeHolder.text(component.html())
});
}
Now you can call it like any other jQuery method:
$(".js_show_html").click(function () {
$(this).addHTMLtoComponent();
});
The value of this in a jQuery method will be the jQuery object itself, so you don't need to re-wrap it with $(). By convention (and when it makes sense), jQuery methods operate on all elements referred to by the root object, and they return that object for further chained operations. That's what the outer return this.each() construction does.
Inside the .each() callback, you've got a typical jQuery callback situation, with this being set successively to each member of the outer jQuery object.
You have to pass the element as parameter to this function.
eg:
<div onclick="addHTMLtoComponent ($(this))"></div>

How to reference widget property from methods called from another context

My jquery-ui widget has some properties that I need to access on a callback. The problem is the context is transient.
Everything I've read says to create my variables in _create constructor and to preserve a reference to the widget in that:
(function ($) {
$.widget("tsp.videoWrapper", {
options: {
value: 0,
playBtnObj: null,
timeboxElement: null,
chapterNavElement: null,
segmentBarElement: null,
positionViewElement : null
},
_create: function () {
var that = this;
var thatElm = $(that.element);
that.Video = thatElm.children("video")[0];
if (that.Video == null) {
console.log("Video element not found.");
return;
}
that._addHandlers();
},
_addHandlers: function () {
this.Video.addEventListener("loadedmetadata", this._videoInited, false);
if (this.Video.readyState >= this.Video.HAVE_METADATA) {
this._videoInited.apply(this.Video); // missed the event
}
},
_videoInited: function (evt) {
console.log(this);
console.log(this.Video.textTracks[0]);
});
}(jQuery));
Trying to reference that in _videoInit creates an error:
Use of an implicitly defined global variable
But the:
console.log(this);
in _videoInit refers to the video itself so calling
console.log(this.Video.textTracks[0]);
fails to because a video doesn't have a Video property. I've omitted a bunch of other code for simplicity but after this call I actually need a reference to the widget to do something with the cues loaded into the video so just doing this:
console.log(this.textTracks[0]);
is not an option.
How do i access the context to get at the video and then do something with it using the properties of the widget instance?
So for instance how do I do this?
_videoInited: function (evt) {
// pretend up in _create I had: that.Cues=[]
that.Cues = that.Video.textTracks[0].cues;
});
I can't use that because of the implicit error as above and I can't use this because this is a video element reference not a videoWrapper widget reference. And i can't do:
that.Cues = that.Video.textTracks[0].cues;
in _create because the cues and other meta data aren't initiated at that point. It seems like such a basic thing to want to do "access an objects properties from it's methods".
Ok so from this preserving-a-reference-to-this-in-javascript-prototype-functions I got the jquery bind method. That question is talking about Prototypes which I thought were like static methods but it seems to work.
Setting up the handler:
var that = this;
$(this.Video).bind("loadedmetadata", function (event) {
event.widget = that; that._videoInited(event);
});
The bind page says to now use the jquery on method
var that = this;
$(this.Video).on("loadedmetadata", function (event) {
event.widget = that; that._videoInited(event);
});
And then using as I wanted:
_videoInited: function (evt) {
console.log(evt); // has a new dynamic widget property
console.log(this); // refers to the widget
Feels a bit weird and loose but seems to work as expected.

How to detect when an .html() function is called in jQuery?

The problem is simple. I have a massive javascript application. And there are lot of times in the app where I use code which looks something like this -
$('#treat').html(new_data);
....
....
$('#cool').html(some_html_data);
....
....
$('#not_cool').html(ajax_data);
So what I want to do is, everytime this html() function is called I want to execute a set of functions.
function do_these_things_every_time_data_is_loaded_into_a_div()
{
$('select').customSelect();
$('input').changeStyle();
etc.
}
How do I do this? Thank you.
You can use the custom event handlers for that:
$('#treat').html(new_data);
// Trigger the custom event after html change
$('#treat').trigger('custom');
// Custom event handler
$('#treat').on('custom', function( event) {
// do_these_things_every_time_data_is_loaded_into_a_div
alert('Html had changed!');
});
UPDATE
Based on answer over here and with some modifications you can do this:
// create a reference to the old `.html()` function
$.fn.htmlOriginal = $.fn.html;
// redefine the `.html()` function to accept a callback
$.fn.html = function (html, callback) {
// run the old `.html()` function with the first parameter
this.htmlOriginal(html);
// run the callback (if it is defined)
if (typeof callback == "function") {
callback();
}
}
$("#treat").html(new_data, function () {
do_these_things_every_time_data_is_loaded_into_a_div();
});
$("#cool").html(new_data, function () {
do_these_things_every_time_data_is_loaded_into_a_div();
});
Easily maintainable and less code as per your requirements.
You can overwrite the jQuery.fn.html() method, as described in Override jQuery functions
For example, use this:
var oHtml = jQuery.fn.html;
jQuery.fn.html = function(value) {
if(typeof value !== "undefined")
{
jQuery('select').customSelect();
jQuery('input').changeStyle();
}
// Now go back to jQuery's original html()
return oHtml.apply(this, value);
};
When html() is called it usually make the DOM object changes, so you can look for DOM change event handler, it is called whenever your HTML of main page change. I found
Is there a JavaScript/jQuery DOM change listener?
if this help your cause.
You can replace the html function with your own function and then call the function html:
$.fn.html = (function(oldHtml) {
var _oldHtml = oldHtml;
return function(param) {
// your code
alert(param);
return _oldHtml.apply(this, [param]);
};
})($.fn.html);
I have a little script for you. Insert that into your javascript:
//#Author Karl-André Gagnon
$.hook = function(){
$.each(arguments, function(){
var fn = this
if(!$.fn['hooked'+fn]){
$.fn['hooked'+fn] = $.fn[fn];
$.fn[fn] = function(){
var r = $.fn['hooked'+fn].apply(this, arguments);
$(this).trigger(fn, arguments);
return r
}
}
})
}
This allow you to "hook" jQuery function and trigger an event when you call it.
Here how you use it, you first bind the function you want to trigger. In your case, it will be .html():
$.hook('html');
Then you add an event listener with .on. It there is no dynamicly added element, you can use direct binding, else, delegated evets work :
$(document).on('html', '#threat, #cool, #not_cool',function(){
alert('B');
})
The function will launch everytime #threat, #cool or #not_cool are calling .html.
The $.hook plugin is not fully texted, some bug may be here but for your HTML, it work.
Example : http://jsfiddle.net/5svVQ/

What's the easiest way i can pass an element as a first argument to event handlers in JavaScript?

I know that having the value of this being changed to the element receiving the event in event handling functions is pretty useful. However, I'd like to make my functions always be called in my application context, and not in an element context. This way, I can use them as event handlers and in other ways such as in setTimeout calls.
So, code like this:
window.app = (function () {
var that = {
millerTime: function () {},
changeEl: function (el) {
el = el || this;
// rest of code...
that.millerTime();
}
};
return that;
}());
could just be like this:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
this.millerTime();
}
};
}());
The first way just looks confusing to me. Is there a good easy way to pass the element receiving the event as the first argument (preferably a jQuery-wrapped element) to my event handling function and call within the context of app? Let's say I bind a bunch of event handlers using jQuery. I don't want to have to include anonymous functions all the time:
$('body').on('click', function (event) {
app.changeEl.call(app, $(this), event); // would be nice to get event too
});
I need a single function that will take care of this all for me. At this point I feel like there's no getting around passing an anonymous function, but I just want to see if someone might have a solution.
My attempt at it:
function overrideContext (event, fn) {
if (!(this instanceof HTMLElement) ||
typeof event === 'undefined'
) {
return overrideContext;
}
// at this point we know jQuery called this function // ??
var el = $(this);
fn.call(app, el, event);
}
$('body').on('click', overrideContext(undefined, app.changeEl));
Using Function.prototype.bind (which I am new to), I still can't get the element:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
console.log(this); // app
this.millerTime();
}
};
}());
function overrideContext (evt, fn) {
var el = $(this); // $(Window)
console.log(arguments); // [undefined, app.changeEl, p.Event]
fn.call(app, el, event);
}
$('body').on('click', overrideContext.bind(null, undefined, app.changeEl));
Using $('body').on('click', overrideContext.bind(app.changeEl)); instead, this points to my app.changeEl function and my arguments length is 1 and contains only p.Event. I still can't get the element in either instance.
Defining a function like this should give you what you want:
function wrap(func) {
// Return the function which is passed to `on()`, which does the hard work.
return function () {
// This gets called when the event is fired. Call the handler
// specified, with it's context set to `window.app`, and pass
// the jQuery element (`$(this)`) as it's first parameter.
func.call(window.app, $(this) /*, other parameters (e?)*/);
}
}
You'd then use it like so;
$('body').on('click', wrap(app.changeEl));
For more info, see Function.call()
Additionally, I'd like to recommend against this approach. Well versed JavaScript programmers expect the context to change in timeouts and event handlers. Taking this fundamental away from them is like me dropping you in the Sahara with no compass.

Categories

Resources