Bug in jQuery's element creation? - javascript

$(document).ready(function(){
var _new_li = $('', {
'id': 'p',
'text': 'CLICKME',
click: function(){
alert('fired');
},
data: {
'somedata': 'somedata',
}
});
_new_li.appendTo($("#example"));
});
I receive an "Uncaught TypeError: Cannot read property 'click' of undefined", when I try to click the element which I created like so.
But, if you switch click: and data: it works.
$(document).ready(function(){
var _new_li = $('<li/>', {
'id': 'p',
'text': 'CLICKME',
data: {
'somedata': 'somedata',
},
click: function(){
alert('fired');
}
});
_new_li.appendTo($("#example"));
});
any explanation for that behavior?
Kind Regards
--Andy
PS:
I posted a similar behavior earlier in the jQuery Core Development forum, Mr. Swedberg answered there:
I'm pretty sure this is happening because you're setting data with an object, which >(until 1.4.2) would overwrite the event object. Not sure which version of jQuery you're >using in your project, but it looked like the jsbin example was using 1.4. Try upgrading >to 1.4.2 and see if that helps.
But it seems like the problem still exists in 1.4.2

Post rewrited.
You shouldn't put that comma after last (and only) element in data.
After trying some stuff I got to this:
$(document).ready(function(){
var fun=function(){
alert('fired');
};
var parms={
'id': 'p',
'text': 'CLICKME',
'click':fun,
'data': {
'somedata': 'somedata'
}
};
console.log(parms);
var _new_li = $('<li/>',parms);
_new_li.appendTo($("#example"));
});
Everything works fine until I click on the li element. Then I get e is undefined (jquery line 55). Works well when click and data are swapped.
Still investigating
AND FOUND IT
jquery development version, line 1919
var events = jQuery.data(this,
"events"), handlers = events[
event.type ];
events is undefined.
jquery overwrites events stored in data.
so this IS a bug. It should just extend.
I've submited a bug report.

folks.. this is no bug.. what's happening is that you need to use the event data argument
.click( [ eventData ], handler(eventObject) )
eventDataA map of data that will be passed to the event handler.
to know why that's necessary you can review the discussion in the jquery documentation of .bind().. you may also want to review the concept of "closure" in javascript.. it provides the rationale behind this whole thing
this part is especially relevant (taken from http://api.jquery.com/bind/):
Passing Event Data
The optional eventData parameter is not commonly used. When provided, this argument allows us to pass additional information to the handler. One handy use of this parameter is to work around issues caused by closures. For example, suppose we have two event handlers that both refer to the same external variable:
var message = 'Spoon!';
$('#foo').bind('click', function() {
alert(message);
});
message = 'Not in the face!';
$('#bar').bind('click', function() {
alert(message);
});
Because the handlers are closures that both have message in their environment, both will display the message Not in the face! when triggered. The variable's value has changed. To sidestep this, we can pass the message in using eventData:
var message = 'Spoon!';
$('#foo').bind('click', {msg: message}, function(event) {
alert(event.data.msg);
});
message = 'Not in the face!';
$('#bar').bind('click', {msg: message}, function(event) {
alert(event.data.msg);
});
This time the variable is not referred to directly within the handlers; instead, the variable is passed in by value through eventData, which fixes the value at the time the event is bound. The first handler will now display Spoon! while the second will alert Not in the face!

I ended up modifying the source code slightly, so it would not randomly blow up here.
handle: function( event ) {
...
// tbn modified source
try {
var events = jQuery.data(this, "events"), handlers = events[ event.type ];
} catch(e) {}
// tbn modified source
..then re-minified the source
I have not had any problems with this since (yet).

Not sure if this helps, but I was running into a similar issue. I was trying to bind events like mouseout, click, etc... to an new LI element. I kept getting the error 'Uncaught TypeError: Cannot read property 'mouseout' of undefined'. Turns out, you have to bind events like click, mouseout, etc... AFTER you've appended the element to the DOM. Not before. Rough example:
newLI = $('<li />');
$(document.body).append(newLI); // Correct
newLI.click(...);
newLI.mouseout(...);
instead of
newLI = $('<li />');
newLI.click(...);
newLI.mouseout(...);
$(document.body).append(newLI); // Incorrect

Related

Understanding `this` in jquery event handlers

Javascript newbie here. I'm trying to understand this and bind within the context of jquery event handlers. I'm reviewing a piece of code from the todoMVC code here, and have a question.
Let's look at line 56:
$('#new-todo').on('keyup', this.create.bind(this));
Code excerpt for context:
var App = {
init: function () {
this.todos = util.store('todos-jquery');
this.todoTemplate = Handlebars.compile($('#todo-template').html());
this.footerTemplate = Handlebars.compile($('#footer-template').html());
this.bindEvents();
bindEvents: function () {
$('#new-todo').on('keyup', this.create.bind(this));
$('#toggle-all').on('change', this.toggleAll.bind(this));
$('#footer').on('click', '#clear-completed', this.destroyCompleted.bind(this));
$('#todo-list')
.on('change', '.toggle', this.toggle.bind(this))
.on('dblclick', 'label', this.edit.bind(this))
.on('keyup', '.edit', this.editKeyup.bind(this))
.on('focusout', '.edit', this.update.bind(this))
.on('click', '.destroy', this.destroy.bind(this));
},
create: function (e) {
var $input = $(e.target);
var val = $input.val().trim();
if (e.which !== ENTER_KEY || !val) {
return;
}
this.todos.push({
id: util.uuid(),
title: val,
completed: false
});
$input.val('');
this.render();
},
My question
I understand that when using jquery, this refers by default to “the element we called the method on” (#new-todo in this case), so in this code, we want to explicitly bind this to the object App instead.
In the example, both thiss appear to follow the “left of the dot rule” and refer to App. So far, so good.
From this behavior, I expect that this, if not inside the callback function must refer to the parent app, (and this inside the callback function must default to the element with ID #new-todo unless bound to some other value).
Therefore, if I call this.create without binding it to anything, this should still refer to App, right? WRONG.
As you can see, the first this now refers to the element with ID #new-todo. (And the this in other event listeners below also refer to the jquery wrapped object!)
Can someone help me understand why?
I discovered the problem (with my question), so I thought I'd post the resolution in case it helps anyone in the future.
I realized that my initial understanding of this and bind() was correct, so I must be wrong about something else.
It turns out that
(1) I made an incorrect assumption about how the debugger works
(2) I needed to review when each part of the code ran
(1) Debugger
In the debugger, when I pause and linger my mouse over a variable, the debugger shows me a preview of that variable's value. I erroneously assumed that when the debugger pauses code execution, I'd see various values of this displayed, depending on the context (so a this in one method would differ from the this in another.) I see now that's not how it works. I believe this will show up as the same value everywhere and that value will be the value of this at the point where the script was paused. See attached gif for example.
(2) Code execution timing
When code execution paused for my breakpoint, I was already inside the create method, so the this in this.create and .bind(this) was reflecting the value of this within the create method.

Inline Editing of Textarea – How to get the id?

I use jqInlineEdit for inline editing on a web page. Everything works, except I don't know how to get the id of the item which I need for saving the change to the database(via Django).
The HTML looks like this:
<div id="remark14756" class="remark" data-cid="14756">
Sample Text
</div>
That's the JavaScript:
<script src="/static/inline-edit.jquery.js"></script>
<script>
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (e, text, html) {
// Executes when exiting inline edit mode and a change has been made
c_id = $(this).attr("data-cid");
alert("Test: ", c_id)
}
});
</script>
Obviously, $(this) does not work in this context. I tried everything and searched a lot but I can't find how to do it the right way. Does anybody know the answer?
The inlineEdit docs say:
onChange(this, text, html) - Executes when exiting inline edit mode and a change has been made
with the use of this being quite misleading.
therefore the first param is actually the Element.
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (elem, text, html) {
// `this` refers to inlineEdit instance plugin
// `elem` is the currently edited element
const c_id = $(elem).attr("data-cid");
alert(c_id); // 14756
}
});
That plugin is not performing in an expected "jQuery Plugin" way.
Usually properly written plugins should:
bind all methods to the Element callee,
(in case of Event methods) the first parameter should always refer to the original Event.
allowing a developer to reference it using the this keyword to get the native JS Element or either doing $(this) inside the exposed public Methods just like we're expected from native jQuery Methods, and to have accessible the Event (i.e: useful in case we use arrow functions to extract the currentTarget since the inexistence of this keyword)
$someElem.on('click', function(evt) {
const $el = $(this); // what we're used to
});
$someElem.on('click', (evt) => {
const $el = $(evt.currentTarget); // the importance of always passing the Event as first param
});
clearly not implemented in that plugin.

How to trigger an event with a custom event (specifically a custom dataTransfer property)?

I'm currently attempting to test some code that uses drag-and-drop. I found some other questions that were kinda related to this, but they were way too specific to help me, or not related enough.
This being a test, I'm struggling on trying to automatically execute code inside a .on('drop',function(e){....} event. The main issue is not that I can't run the code inside, but it's that I can't transfer the dataTransfer property, and I can't seem to fake it because it's read-only. Is there anyway to fake the dataTransfer property or otherwise get around it?
I came up with this JSFiddle that serves as a template of what I'm trying to do: https://jsfiddle.net/gnq50hsp/53/
Essentially if you are able to explain to me (if this is at all possible) how I can possibly fake the dataTransfer property, I should be all set.
Side notes:
I'm totally open to other ways of somehow getting inside that code, like for example, maybe its possible to trigger the event and pass in a fake event object with a fake dataTransfer object.
To see the drag-drop behavior, change the JavaScript load type from no-wrap head to on-Load, then you should see what I'm trying to simulate.
Important to note that I cannot modify any of the code inside the event handlers, only inside the outside function
Using Karma/Jasmine so use of those tools are also possible like spies
Also, I'm using Chrome.
Thanks in advance, and let me know for any questions/clarifications!
You should be able to override pretty much everything you want using Object.defineProperty. Depending on what you want to test it can be very simple or very complex. Faking the dataTransfer can be a bit tricky, since there's a lot of restrictions and behaviors linked to it, but if you simply want to test the drop function, it's fairly easy.
Here's a way, this should give you some ideas as to how to fake some events and data:
//Event stuff
var target = $('#target');
var test = $('#test');
test.on('dragstart', function(e) {
e.originalEvent.dataTransfer.setData("text/plain", "test");
});
target.on('dragover', function(e) {
//e.dataTransfer.setData('test');
e.preventDefault();
e.stopPropagation();
});
target.on('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
});
//What I want to simulate:
target.on('drop', function(e) {
console.log(e)
//Issue is that I can't properly override the dataTransfer property, since its read-only
document.getElementById('dataTransferDisplay').innerHTML = e.originalEvent.dataTransfer.getData("text");
});
function simulateDrop() {
// You'll need the original event
var fakeOriginalEvent = new DragEvent('drop');
// Using defineProperty you can override dataTransfer property.
// The original property works with a getter and a setter,
// so assigning it won't work. You need Object.defineProperty.
Object.defineProperty(fakeOriginalEvent.constructor.prototype, 'dataTransfer', {
value: {}
});
// Once dataTransfer is overridden, you can define getData.
fakeOriginalEvent.dataTransfer.getData = function() {
return 'test'
};
// TO have the same behavior, you need a jquery Event with an original event
var fakeJqueryEvent = $.Event('drop', {
originalEvent: fakeOriginalEvent
});
target.trigger(fakeJqueryEvent)
}
https://jsfiddle.net/0tbp4wmk/1/
As per jsfiddel link you want to achieve drag and drop feature. jQuery Draggable UI already provides this feature why you can not use that?
For create custom event on your way you have to follow two alternative ways
$('your selector').on( "myCustomEvent", {
foo: "bar"
}, function( event, arg1, arg2 ) {
console.log( event.data.foo ); // "bar"
console.log( arg1 ); // "bim"
console.log( arg2 ); // "baz"
});
$( document ).trigger( "myCustomEvent", [ "bim", "baz" ] );
On above example
In the world of custom events, there are two important jQuery methods: .on() and .trigger(). In the Events chapter, we saw how to use these methods for working with user events; for this chapter, it's important to remember two things:
.on() method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.
.trigger() method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object.
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
OR
jQuery.event.special.multiclick = {
delegateType: "click",
bindType: "click",
handle: function( event ) {
var handleObj = event.handleObj;
var targetData = jQuery.data( event.target );
var ret = null;
// If a multiple of the click count, run the handler
targetData.clicks = ( targetData.clicks || 0 ) + 1;
if ( targetData.clicks % event.data.clicks === 0 ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = handleObj.type;
return ret;
}
}
};
// Sample usage
$( "p" ).on( "multiclick", {
clicks: 3
}, function( event ) {
alert( "clicked 3 times" );
});
On above example
This multiclick special event maps itself into a standard click event, but uses a handle hook so that it can monitor the event and only deliver it when the user clicks on the element a multiple of the number of times specified during event binding.
The hook stores the current click count in the data object, so multiclick handlers on different elements don't interfere with each other. It changes the event type to the original multiclick type before calling the handler and restores it to the mapped "click" type before returning:

Catch Javascript CustomEvent by jQuery on() preserving custom properties at first "level"

I have a setup theoretically like this [see fiddle -> http://jsfiddle.net/GeZyw/] :
var EventTest = function(element) {
this.element = element;
this.element.addEventListener('click', elementClick);
function elementClick() {
var event = document.createEvent('CustomEvent');
event.initEvent('myevent', false, false);
event['xyz']='abc';
event.customData='test';
console.log(event);
this.dispatchEvent(event);
}
}
var element = document.getElementById('test');
var test = new EventTest(element);
$(document).ready(function() {
$("#test").on('myevent', function(e) {
console.log('myevent', e);
});
});
What I want is to create a CustomEvent in pure Javascript, enrich it with some properties and trigger that event so it can be cached also by a library like jQuery.
As you can see in the fiddle, the CustomEvent is triggered well and it is actually populated with custom properties - but when it reaches jQuery on() the custom properties is gone from the first level. My custom properties is now demoted to e.originalEvent.xyz and so on.
That is not very satisfactory. I want at least my own properties to be at the first level.
Also, in a perfect world, I would like to get rid of most of the standard properties in the dispatched event, so it contained (theoretically optimal) :
e = {
xyz : 'abc',
customData : 'test'
}
Is that possible at all? If so, how should I do it?
I have run into the same issue, couple of months ago, the point is:
When an event is received by jQuery, it normalizes the event properties before it dispatches the event to registered event handlers.
and also:
Event handlers won't be receiving the original event. Instead they are getting a new jQuery.Event object with properties copied from the raw HTML event.
Why jQuery does that:
because it can't set properties on a raw HTML event.
I had decided to do the same, I started to do it with a nasty way, and my code ended up so messy, at the end I decided to use jQuery.trigger solution, and pass my event object as the second param, like:
$("#test").bind("myevent", function(e, myeventobj) {
alert(myeventobj.xyz);
});
var myobj = {"xyz":"abc"};
$("#test").trigger("myevent", myobj);
for more info check this link out: .trigger()

OpenLayers eventListener attribute

I found in an example for OpenLayers this code to register a listener on OpenLayers.Control.SelectFeature
var report = function(e) {
OpenLayers.Console.log(e.type, e.feature.id);
};
var highlightCtrl = new OpenLayers.Control.SelectFeature(vectors, {
hover: true,
highlightOnly: true,
renderIntent: "temporary",
eventListeners: {
beforefeaturehighlighted: report,
featurehighlighted: report,
featureunhighlighted: report
}
});
Now I'm wondering what exactly e is. What type is e and what other attributes besides type and feature doese e have? Where can I find the documentation for this?
you can find other attributes of e by using IE to debug
In your example, the 'report' method is called a "callback method", which gets triggered when the event occurs. If you look at the SelectFeature control highlight method, you'll see that "beforefeaturehighlighted" gets triggered first. Take a look at the arguments of the method:
var cont = this.events.triggerEvent("beforefeaturehighlighted", {
feature : feature
});
First one is the name of the event, second one is the parameters to send to the callback method. So, if you inspect your 'e' variable, as Begisen suggested, you'll see that e.feature is available.
That's what your e variable is.

Categories

Resources