Javascript - Add specific keypress event inside a loop - javascript

I am attempting to assign a keypress event to an element within a for loop. I know that there are issues with assigning events while in a for loop dynamically, and I have solved that for the "click" event however I am at a loss for how it should work for the keypress. (probably because I don't really understand how the "click" one works to begin with... closure avoidance is not something I fully get)
The basic setup is that there is a for loop that will print out a number of different textareas and a div underneath them. Pressing the div will send the text in the text area to the right person. What I would like to have happen is that the same message should be sent if the enter button is pressed within the text area.
for( var i in people){
var message = $('<textarea></textarea>').appendTo(container);
message.on( "keypress", function(e) {
if(e.keyCode==13){
// code does make it in here ...
sendMessage(people[i].name); // but this never gets run
}
});
var messageButton= $('<div>Send</div>').appendTo(container);
messageButton.on( "click", sendMessage(people[i].name) );
}
var sendMessage = function(to) {
return function(){
/* do the sending of the message to the right person */
}
}
Can anyone help me understand the following?
Why does the click function work in the first place? I am not understanding why we have to put return around the function block.
Why doesn't the keypress function work similarly?
On a more general level, how does keypress work to begin with. The function(e) should not work because 'e' isn't anything, where does that even get set?

The problem with keypress in the code is that it will always send the message to latest person in people as at the moment when it is executed, i will have the latest value in it.
I probably would use forEach instead:
people.forEach(function (person) {
var message = $('<textarea></textarea>').appendTo(container);
// you can use keypress - http://api.jquery.com/keypress/#keypress-eventData-handler
// see the examples in the reference
message.keypress(function (e) {
if (e.which === 13) {
// here you should invoke the function returned by the sendMessage
sendMessage(person.name)();
}
});
var messageButton= $('<div>Send</div>').appendTo(container);
messageButton.click(sendMessage(person.name));
});
with this approach you do not need to wrap the function in the sendMessage and can just call the original function in the corresponding event handler.

Clean example using jQuery. You should read more about jQuery and closures for iterations so you can easily understand what is going on.
$.each(people, function (person) {
var $message = $('<textarea></textarea>').appendTo(container);
var $button = $('<div>Send</div>').appendTo(container);
var send = sendMessage(person.name);
// Keypress handler
$message.keypress(function (e) {
if (e.which === 13) { // on enter do the following
send();
}
});
$button.click(send);
});

Here's another solution using a handwritten closure:
http://jsfiddle.net/M5NsS/1/
var people = {
'p1': {
name: 'john'
},
'p2': {
name: 'bob'
},
'p3': {
name: 'jim'
}
};
var container = $('#container');
for (var i in people) {
(function (name) {
var message = $('<textarea></textarea>').appendTo(container);
message.keypress(function (e) {
if (e.keyCode == 13) {
sendMessage(name);
}
});
var messageButton = $('<div>Send</div>').appendTo(container);
messageButton.click(function () {
sendMessage(name)
});
})(people[i].name);
}
function sendMessage(to) {
console.log(to);
}
As others have stated, the issue is that the event is bound with the last reference to 'i' in the loop. Using a closure solves this issue while still allowing you to use your for..in loop.
Another thing to note is that if you are not dynamically appending these elements to the DOM after binding, there is no reason to use jquery's .on(). You can directly bind .keypress() and .click() handlers to the elements, as seen in my fiddle and on #AlexAtNet's answer.
But it's clunky, and I would just use jquerys $.each as others have already suggested.

Related

Click events from two scripts on same element?

Edit: I think I got the solution! I want to try and fix this myself before I ask for further help = )
First script inhibits the second one from functioning as the click event from the first one overides the second one. Because the second one does not function it is impossible to open the drop down menu to select a list item to trigger the first scripts click.
What I tried was replacing all return false statements with event.stopPropagation(). Didnt work however. Tried re-ordering my scripts but that failed as well. I was thinking of making my second script target another parent div but that didnt work either.I also tried event.stopImmediatePropagation() and .bind methods.
Any idea?
First script that makes the drop down function. Contains click event.
function DropDown(el) {
this.f = el;
this.placeholder = this.f.children('span');
this.opts = this.f.find('ul.dropdown > li');
this.val = '';
this.index = -1;
this.initEvents();
}
DropDown.prototype = {
initEvents : function() {
var obj = this;
obj.f.on('click', function(event){
$(this).toggleClass('active');
return false;
});
obj.opts.on('click',function(){
var opt = $(this);
obj.val = opt.text();
obj.index = opt.index();
obj.placeholder.text(obj.val);
});
},
getValue : function() {
return this.val;
},
getIndex : function() {
return this.index;
}
}
$(function() {
var f = new DropDown( $('#f') );
$(document).click(function() {
// all dropdowns
$('.filter-buttons').removeClass('active');
});
});
Second script that does the filtering, also contains click event:
jQuery(document).ready(function(e) {
var t = $(".filter-container");
t.imagesLoaded(function() {
t.isotope({
itemSelector: "figure",
filter: "*",
resizable: false,
animationEngine: "jquery"
})
});
$(".filter-buttons a").click(function(evt) {
var n = $(this).parents(".filter-buttons");
n.find(".selected").removeClass("selected");
$(this).addClass("selected");
var r = $(this).attr("data-filter");
t.isotope({
filter: r
});
evt.preventDefault();
});
$(window).resize(function() {
var n = $(window).width();
t.isotope("reLayout")
}).trigger("resize")
});
html structure
<div id="f" class="filter-buttons" tabindex="1">
<span>Choose Genre</span>
<ul class="dropdown">
<li>All</li>
<li>Electronic</li>
<li>Popular</a></li>
</ul>
</div>
This doesn't really solve your problem but I was bored while drinking my coffee and felt like helping you write your dropdown plugin a little nicer
My comments below are inline with code. For uninterrupted code, see DropDown complete paste.
We start with your standard jQuery wrapper (function($){ ... })(jQuery)
(function($) {
// dropdown constructor
function DropDown($elem) {
First we'll make some private vars to store information. By using this.foo = ... we expose things (probably) unnecessarily. If you need access to these vars, you can always create functions to read them. This is much better encapsulation imo.
// private vars
var $placeholder = $elem.children("span");
var $opts = $elem.find("ul.dropdown > li")
var value = "";
var index = -1;
Now we'll define our event listeners and functions those event listeners might depend on. What's nice here is that these functions don't have to access everything via this.* or as you were writing obj.f.* etc.
// private functions
function onParentClick(event) {
$elem.toggleClass("active");
event.preventDefault();
}
function onChildClick(event) {
setValue($(this));
event.preventDefault();
}
function setValue($opt) {
value = $opt.text();
index = $opt.index();
$placeholder.text(value);
}
Here's some property descriptors to read the index and value
// properties for reading .index and .value
Object.defineProperty(this, "value", {
get: function() { return value; }
});
Object.defineProperty(this, "index", {
get: function() { return index; }
});
Lastly, let's track each instance of DropDown in an array so that the user doesn't have to define a special listener to deactivate each
// track each instance of
DropDown._instances.push(this);
}
This is the array we'll use to track instances
// store all instances in array
DropDown._instances = [];
This event listener deactivate each "registered" instance of DropDown
// deactivate all
DropDown.deactiveAll = function deactiveAll(event) {
$.each(DropDown._instances, function(idx, $elem) {
$elem.removeClass("active");
});
}
Here's the document listener defined right in the plugin! The user no longer has to set this up
// listener to deactiveAll dropdowns
$(document).click(DropDown.deactiveAll);
Might as well make it a jQuery plugin since everything in our DropDown constructor relies upon jQuery. This let's the user do var x = $("foo").dropdown();
// jQuery plugin
$.fn.dropdown = function dropdown() {
return new DropDown($(this));
};
Close the wrapper
})(jQuery);
Now here's how you use it
$(function() {
var x = $('#f').dropdown();
// get the value
f.value;
// get the index
f.index;
});
Anyway, yeah I know this doesn't really help you with your click listeners, but I hope this is still useful information to you. Off to the Post Office now!
I think you're going to need to simplify this to figure out what's going on. There's actually not enough information to see what elements the events are being attached to here.
For argument's sake, open the console and try the following:
$(document).on('click', function() { console.log('first'); return false; });
$(document).on('click', function() { console.log('second'); return false; });
Then click in the page. You'll see that both events are triggered. It might well be that your code is actually attaching the events to different elements (you don't say anywhere). If that's the case then you need to understand how event bubbling works in the DOM.
When you trigger an event, say a click on an element, that event will fire on that element, and then on it's parent, then grandparent etc all the way to the root node at the top.
You can change this behaviour by calling functions in the event itself. evt.stopPropagation tells the event to not bubble up to the ancestor nodes. evt.preventDefault tells the browser not to carry out the default behaviour for a node (eg, moving to the page specified in the href for an A tag).
In jQuery, return false from an event handler is a shortcut for, evt.preventDefault and evt.stopPropagation. So that will stop the event dead in its tracks.
I imagine you have something like:
<div event_two_on_here>
<a event_one_on_here>
</div>
If the thing that handles event_one_on_here calls stopPropagation then event_two_on_here will never even know it has happened. Calling stopPropagation explicitly, or implicitly (return false) will kill the event before it travels to the parent node/event handler.
UPDATE: In your case the issue is that the handler on .filter-buttons a is stopping the propagation (so #f doesn't get to run its handler).
$(".filter-buttons a").click(function(evt) {
// your code here...
// Don't do this - it stops the event from bubbling up to the #f div
// return false;
// instead, you'll probably just want to prevent the browser default
// behaviour so it doesn't jump to the top of the page ('url/#')
evt.preventDefault();
});

Change-Event for div [duplicate]

I want to run a function when a user edits the content of a div with contenteditable attribute. What's the equivalent of an onchange event?
I'm using jQuery so any solutions that uses jQuery is preferred. Thanks!
2022 update
As pointed out in the comments, this doesn't answer the question asked, which wanted the equivalent of the change event rather than the input event. However, I'll leave it here as is.
Original answer
I'd suggest attaching listeners to key events fired by the editable element, though you need to be aware that keydown and keypress events are fired before the content itself is changed. This won't cover every possible means of changing the content: the user can also use cut, copy and paste from the Edit or context browser menus, so you may want to handle the cut copy and paste events too. Also, the user can drop text or other content, so there are more events there (mouseup, for example). You may want to poll the element's contents as a fallback.
UPDATE 29 October 2014
The HTML5 input event is the answer in the long term. At the time of writing, it is supported for contenteditable elements in current Mozilla (from Firefox 14) and WebKit/Blink browsers, but not IE.
Demo:
document.getElementById("editor").addEventListener("input", function() {
console.log("input event fired");
}, false);
<div contenteditable="true" id="editor">Please type something in here</div>
Demo: http://jsfiddle.net/ch6yn/2691/
Here is a more efficient version which uses on for all contenteditables. It's based off the top answers here.
$('body').on('focus', '[contenteditable]', function() {
const $this = $(this);
$this.data('before', $this.html());
}).on('blur keyup paste input', '[contenteditable]', function() {
const $this = $(this);
if ($this.data('before') !== $this.html()) {
$this.data('before', $this.html());
$this.trigger('change');
}
});
The project is here: https://github.com/balupton/html5edit
Consider using MutationObserver. These observers are designed to react to changes in the DOM, and as a performant replacement to Mutation Events.
Pros:
Fires when any change occurs, which is difficult to achieve by listening to key events as suggested by other answers. For example, all of these work well: drag & drop, italicizing, copy/cut/paste through context menu.
Designed with performance in mind.
Simple, straightforward code. It's a lot easier to understand and debug code that listens to one event rather than code that listens to 10 events.
Google has an excellent mutation summary library which makes using MutationObservers very easy.
Cons:
Requires a very recent version of Firefox (14.0+), Chrome (18+), or IE (11+).
New API to understand
Not a lot of information available yet on best practices or case studies
Learn more:
I wrote a little snippet to compare using MutationObserers to handling a variety of events. I used balupton's code since his answer has the most upvotes.
Mozilla has an excellent page on the API
Take a look at the MutationSummary library
non jQuery quick and dirty answer:
function setChangeListener (div, listener) {
div.addEventListener("blur", listener);
div.addEventListener("keyup", listener);
div.addEventListener("paste", listener);
div.addEventListener("copy", listener);
div.addEventListener("cut", listener);
div.addEventListener("delete", listener);
div.addEventListener("mouseup", listener);
}
var div = document.querySelector("someDiv");
setChangeListener(div, function(event){
console.log(event);
});
I have modified lawwantsin 's answer like so and this works for me. I use the keyup event instead of keypress which works great.
$('#editor').on('focus', function() {
before = $(this).html();
}).on('blur keyup paste', function() {
if (before != $(this).html()) { $(this).trigger('change'); }
});
$('#editor').on('change', function() {alert('changed')});
Two options:
1) For modern (evergreen) browsers:
The "input" event would act as an alternative "change" event.
https://developer.mozilla.org/en-US/docs/Web/Events/input
document.querySelector('div').addEventListener('input', (e) => {
// Do something with the "change"-like event
});
or
<div oninput="someFunc(event)"></div>
or (with jQuery)
$('div').on('click', function(e) {
// Do something with the "change"-like event
});
2) To account for IE11 and modern (evergreen) browsers:
This watches for element changes and their contents inside the div.
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
var div = document.querySelector('div');
var divMO = new window.MutationObserver(function(e) {
// Do something on change
});
divMO.observe(div, { childList: true, subtree: true, characterData: true });
const p = document.querySelector('p')
const result = document.querySelector('div')
const observer = new MutationObserver((mutationRecords) => {
result.textContent = mutationRecords[0].target.data
// result.textContent = p.textContent
})
observer.observe(p, {
characterData: true,
subtree: true,
})
<p contenteditable>abc</p>
<div />
Here's what worked for me:
var clicked = {}
$("[contenteditable='true']").each(function(){
var id = $(this).attr("id");
$(this).bind('focus', function() {
// store the original value of element first time it gets focus
if(!(id in clicked)){
clicked[id] = $(this).html()
}
});
});
// then once the user clicks on save
$("#save").click(function(){
for(var id in clicked){
var original = clicked[id];
var current = $("#"+id).html();
// check if value changed
if(original != current) save(id,current);
}
});
This thread was very helpful while I was investigating the subject.
I've modified some of the code available here into a jQuery plugin so it is in a re-usable form, primarily to satisfy my needs but others may appreciate a simpler interface to jumpstart using contenteditable tags.
https://gist.github.com/3410122
Update:
Due to its increasing popularity the plugin has been adopted by Makesites.org
Development will continue from here:
https://github.com/makesites/jquery-contenteditable
Non JQuery answer...
function makeEditable(elem){
elem.setAttribute('contenteditable', 'true');
elem.addEventListener('blur', function (evt) {
elem.removeAttribute('contenteditable');
elem.removeEventListener('blur', evt.target);
});
elem.focus();
}
To use it, call on (say) a header element with id="myHeader"
makeEditable(document.getElementById('myHeader'))
That element will now be editable by the user until it loses focus.
In Angular 2+
<div contentEditable (input)="type($event)">
Value
</div>
#Component({
...
})
export class ContentEditableComponent {
...
type(event) {
console.log(event.data) // <-- The pressed key
console.log(event.path[0].innerHTML) // <-- The content of the div
}
}
To avoid timers and "save" buttons, you may use blur event wich fires when the element loses focus. but to be sure that the element was actually changed (not just focused and defocused), its content should be compared against its last version. or use keydown event to set some "dirty" flag on this element.
Here is the solution I ended up using and works fabulously. I use $(this).text() instead because I am just using a one line div that is content editable. But you may also use .html() this way you dont have to worry about the scope of a global/non-global variable and the before is actually attached to the editor div.
$('body').delegate('#editor', 'focus', function(){
$(this).data('before', $(this).html());
});
$('#client_tasks').delegate('.task_text', 'blur', function(){
if($(this).data('before') != $(this).html()){
/* do your stuff here - like ajax save */
alert('I promise, I have changed!');
}
});
You need to use input event type
Demo
HTML
<div id="editor" contenteditable="true" >Some text here</div>
JS
const input = document.getElementById('editor');
input.addEventListener('input', updateValue);
function updateValue(e) {
console.log(e.target);
}
know more
The onchange event doesn't fires when an element with the contentEditable attribute is changed, a suggested approach could be to add a button, to "save" the edition.
Check this plugin which handles the issue in that way:
Creating a quick and dirty jQuery contentEditable Plugin
Using DOMCharacterDataModified under MutationEvents will lead to the same. The timeout is setup to prevent sending incorrect values (e.g. in Chrome I had some issues with space key)
var timeoutID;
$('[contenteditable]').bind('DOMCharacterDataModified', function() {
clearTimeout(timeoutID);
$that = $(this);
timeoutID = setTimeout(function() {
$that.trigger('change')
}, 50)
});
$('[contentEditable]').bind('change', function() {
console.log($(this).text());
})
JSFIDDLE example
I built a jQuery plugin to do this.
(function ($) {
$.fn.wysiwygEvt = function () {
return this.each(function () {
var $this = $(this);
var htmlold = $this.html();
$this.bind('blur keyup paste copy cut mouseup', function () {
var htmlnew = $this.html();
if (htmlold !== htmlnew) {
$this.trigger('change')
}
})
})
}
})(jQuery);
You can simply call $('.wysiwyg').wysiwygEvt();
You can also remove / add events if you wish
A simple answer in JQuery, I just created this code and thought it will be helpful for others too
var cont;
$("div [contenteditable=true]").focus(function() {
cont=$(this).html();
});
$("div [contenteditable=true]").blur(function() {
if ($(this).html()!=cont) {
//Here you can write the code to run when the content change
}
});
For me, I want to check the input is valid or not.
If valid, then update, Otherwise show an error message and keep the value as same as before.
Skill: When you edit done, usually, it will trigger the blur event.
Example
<span contenteditable="true">try input somethings.</span>
<script>
const elem = document.querySelector(`span`)
let oldValue = elem.innerText
elem.onkeydown = (keyboardEvent) => {
if (keyboardEvent.key === "Enter") {
elem.blur() // set focusout
}
}
elem.onblur = (e) => {
const curValue = elem.innerText
if (curValue === oldValue) {
return
}
if (curValue.length <= 50) { // 👈 Input your conditions.
// 👇 fail
elem.innerText = oldValue
// (Optional) Add error message
elem.insertAdjacentHTML("beforeend", `<span style="margin-left:5px;color:red">error length=${curValue.length}. Must greater than 50. undo to the previous value.</span>`)
const errMsg = elem.querySelector(`span`)
setTimeout(() => errMsg.remove(), 3500) // wait 3.5 second, and then remove it.
return
}
// 👇 OK, update
oldValue = curValue
}
</script>
Check this idea out.
http://pastie.org/1096892
I think it's close. HTML 5 really needs to add the change event to the spec. The only problem is that the callback function evaluates if (before == $(this).html()) before the content is actually updated in $(this).html(). setTimeout don't work, and it's sad. Let me know what you think.
Based on #balupton's answer:
$(document).on('focus', '[contenteditable]', e => {
const self = $(e.target)
self.data('before', self.html())
})
$(document).on('blur', '[contenteditable]', e => {
const self = $(e.target)
if (self.data('before') !== self.html()) {
self.trigger('change')
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

jQuery bind to Paste Event, how to get the content of the paste

I have a jquery token tagit plugin and I want to bind to the paste event to add items correctly.
I'm able to bind to the paste event like so:
.bind("paste", paste_input)
...
function paste_input(e) {
console.log(e)
return false;
}
How can I obtain the actual pasted content value?
There is an onpaste event that works in modern day browsers. You can access the pasted data using the getData function on the clipboardData object.
$("#textareaid").bind("paste", function(e){
// access the clipboard using the api
var pastedData = e.originalEvent.clipboardData.getData('text');
alert(pastedData);
} );
Note that bind and unbind are deprecated as of jQuery 3. The preferred call is to on.
All modern day browsers support the Clipboard API.
See also: In Jquery How to handle paste?
How about this: http://jsfiddle.net/5bNx4/
Please use .on if you are using jq1.7 et al.
Behaviour: When you type anything or paste anything on the 1st textarea the teaxtarea below captures the cahnge.
Rest I hope it helps the cause. :)
Helpful link =>
How do you handle oncut, oncopy, and onpaste in jQuery?
Catch paste input
EDIT:
Events list within .on() should be space-separated. Refer https://api.jquery.com/on/
code
$(document).ready(function() {
var $editor = $('#editor');
var $clipboard = $('<textarea />').insertAfter($editor);
if(!document.execCommand('StyleWithCSS', false, false)) {
document.execCommand('UseCSS', false, true);
}
$editor.on('paste keydown', function() {
var $self = $(this);
setTimeout(function(){
var $content = $self.html();
$clipboard.val($content);
},100);
});
});
I recently needed to accomplish something similar to this. I used the following design to access the paste element and value. jsFiddle demo
$('body').on('paste', 'input, textarea', function (e)
{
setTimeout(function ()
{
//currentTarget added in jQuery 1.3
alert($(e.currentTarget).val());
//do stuff
},0);
});
Another approach:
That input event will catch also the paste event.
$('textarea').bind('input', function () {
setTimeout(function () {
console.log('input event handled including paste event');
}, 0);
});
On modern browsers it's easy: just use the input event along with the inputType attribute:
$(document).on('input', 'input, textarea', function(e){
if (e.originalEvent.inputType == 'insertFromPaste') {
alert($(this).val());
}
});
https://codepen.io/anon/pen/jJOWxg
$(document).ready(function() {
$("#editor").bind('paste', function (e){
$(e.target).keyup(getInput);
});
function getInput(e){
var inputText = $(e.target).html(); /*$(e.target).val();*/
alert(inputText);
$(e.target).unbind('keyup');
}
});
This work on all browser to get pasted value. And also to creating common method for all text box.
$("#textareaid").bind("paste", function(e){
var pastedData = e.target.value;
alert(pastedData);
} )
You could compare the original value of the field and the changed value of the field and deduct the difference as the pasted value. This catches the pasted text correctly even if there is existing text in the field.
http://jsfiddle.net/6b7sK/
function text_diff(first, second) {
var start = 0;
while (start < first.length && first[start] == second[start]) {
++start;
}
var end = 0;
while (first.length - end > start && first[first.length - end - 1] == second[second.length - end - 1]) {
++end;
}
end = second.length - end;
return second.substr(start, end - start);
}
$('textarea').bind('paste', function () {
var self = $(this);
var orig = self.val();
setTimeout(function () {
var pasted = text_diff(orig, $(self).val());
console.log(pasted);
});
});
It would appear as though this event has some clipboardData property attached to it (it may be nested within the originalEvent property). The clipboardData contains an array of items and each one of those items has a getAsString() function that you can call. This returns the string representation of what is in the item.
Those items also have a getAsFile() function, as well as some others which are browser specific (e.g. in webkit browsers, there is a webkitGetAsEntry() function).
For my purposes, I needed the string value of what is being pasted. So, I did something similar to this:
$(element).bind("paste", function (e) {
e.originalEvent.clipboardData.items[0].getAsString(function (pStringRepresentation) {
debugger;
// pStringRepresentation now contains the string representation of what was pasted.
// This does not include HTML or any markup. Essentially jQuery's $(element).text()
// function result.
});
});
You'll want to perform an iteration through the items, keeping a string concatenation result.
The fact that there is an array of items makes me think more work will need to be done, analyzing each item. You'll also want to do some null/value checks.
I do it like so, this would work on most browsers used by humans
$("#couponCode").bind("change keyup input paste",function () {
const value= document.getElementById("couponCode").value;
});

How to apply multiple events to the same function

I'm not the best at this jquery stuff. But I'm trying to seperate the action from the function so I can apply multiple events that cause the same function. Unfortunately this isn't working. Anyone know why?
Updated Function, but still errors
$(document).ready(function() {
var $info_items = jQuery('.checkbox.has_info, .has_info');
$info_items.click(function(event) {
$(this).show_text(event);
});
// I suspect it has something to do with this initalizer of the function here
jQuery.fn.show_text = function(event){
var $info_item = jQuery(this);
$info_items.filter(function(index){
return $(".hidden_text").css("display","block");
}).not($info_item).parent().next().next().hide("slow");
$info_item.parent().next().next().show("fast");
});
});
What is e, the event? You need to name the event argument to the click() function to use it. Also, to invoke show_text such that it has a this, you need to invoke it on an element:
$info_items.click(function (event) {
// 'this' is the element in $info_items which was clicked
// invoke show_text on the element in question
$(this).show_text(event);
});
You also have an extra ) on your final }); line.
You can use jQuery bind to attach several events to a single function.
$('#whatever').bind('mouseover focus click', function() {
your_custom_function();
});
Are you looking for something like this?
var handle = function(event) {
$(event.currentTarget).show_text(event);
};
$info_items.bind('click blur', handle);

How to do early binding for event handler in JavaScript? (example with jQuery)

JavaScript's late binding is great. But how do I early bind when I want to?
I am using jQuery to add links with event handlers in a loop to a div. The variable 'aTag ' changes in the loop. When I click the links later, all links alert the same message, which is the last value of 'aTag'. How do I bind a different alert message to all links?
All links should alert with the value that 'aTag' had when the event handler was added, not when it was clicked.
for (aTag in tagList) {
if (tagList.hasOwnProperty(aTag)) {
nextTag = $('');
nextTag.text(aTag);
nextTag.click(function() { alert(aTag); });
$('#mydiv').append(nextTag);
$('#mydiv').append(' ');
}
}
You can pass data to the bind method:
nextTag.bind('click', {aTag: aTag}, function(event) {
alert(event.data.aTag);
});
This will make a copy of aTag, so each event handler will have different values for it. Your use case is precisely the reason this parameter to bind exists.
Full code:
for (aTag in tagList) {
if (tagList.hasOwnProperty(aTag)) {
nextTag = $('');
nextTag.text(aTag);
nextTag.bind('click', {aTag: aTag}, function(event) {
alert(event.data.aTag);
});
$('#mydiv').append(nextTag);
$('#mydiv').append(' ');
}
}
You can also make a wrapper function that takes the text to alert as a parameter, and returns the event handler
function makeAlertHandler(txt) {
return function() { alert(txt); }
}
and replace
nextTag.click(function() { alert(aTag); });
with
nextTag.click(makeAlertHandler(aTag));
You need to keep a copy of this variable, like this:
for (aTag in tagList) {
if (tagList.hasOwnProperty(aTag)) {
nextTag = $('');
nextTag.text(aTag);
var laTag = aTag;
nextTag.click(function() { alert(laTag); });
$('#mydiv').append(nextTag);
$('#mydiv').append(' ');
}
}
The aTag variable is changing each time you loop, at the end of the loop it's left as the last item in the loop. However, each of the functions you created point at this same variable. Instead, you want a variable per, so make a local copy like I have above.
You can also shorten this down a lot with chaining, but I feel it clouds the point in this case, since the issue is scoping and references.

Categories

Resources