Ok, so I am writing a small plugin...nothing big, but I ran across this issue (stripped down to show isolated problem)
My Plugin uses jQuery .wrap() and in this example I'll be using .after() as well.
(function( $ ) {
var newWrap = $('<div/>');
$.fn.smartform = function() {
this.wrap(newWrap);
return this;
};
}( jQuery ));
Implementation mixed with .after()
$(document).on('click', '#addInput', function(){
var newInput = $('<input/>').smartform().val('Just Added');
$(this).after(newInput);
});
My function returns the correct targeted object properly since I see the value of the new input is 'Just Added' but the input does not get wrapped and I don't understand why.
I can get it to work this way (below) but it is not ideal as depending on how the new element is inserted ( .append .before .after ) will force traversing differently to target the newly added element.
$(document).on('click', '#addInput', function(){
var newInput = $('<input/>').val('Just Added');
$(this).after(newInput).next('input').smartform();
});
Any thoughts would be great, If you have any questions fell free to ask...
Thank you in advance
The problem is newInput is referring to the input element you have created, which is still not part of the dom. When you wrap the input the wrapper is created only in memory not in the dom.
So when you use newInput in after(), the input is removed form the in memory div and is inserted after the button so the wrapper element is lost.
$(document).on('click', '#addInput', function () {
var newInput = $('<input/>').smartform().val('Just Added');
$(this).after(newInput.parent());
});
Demo: Fiddle
Related
So I'm making a small quiz app with object oriented JS using Object.create cloning method. I have an ol, and a function called showVals() that populates it with lis. That seems to be working fine. What I'm having trouble with is: my li click function to give the attr of ".selected' class seems to work intitially, but after I click to proceed and qn2.showVals() is called it is no longer giving the lis a class of selected when clicked.
The data for qn2 is there. Everything looks normal, except for the click function no longer working (giving the lis the class).
$(document).ready(function(){
qn1.showVals();
qn1.setAns(1); // calling question1 answer for now
$('li').click(function(){
$('li').removeAttr("class");
$(this).attr({"class": "selected"});
});
$('.proceed').click(function(e){
e.preventDefault();
if ($('.selected').html() == qn1.ctAns) {
if (confirm("You are correct")){
qn2.showVals();
qn2.setAns(3);
};
};
});
});
var qn1 = {
title:"The Mouth of Sauron",
qn: "How did 'The mouth of Sauron' meet his demise?",
img: "images/mouth-sauron.gif",
ans: ["Shot in the back", "Beheaded", "Drowned in a swamp", "Sacrificed"],
setAns: function(x) {
this.ctAns = this.ans[x]; //setting correct answer
},
showVals: function(){
$('#slide-title').text(this.title);
$('.question-box > p').text(this.qn);
$('#obj-img').attr("src", this.img);
$('ol').html('<li>'+this.ans[0]+'</li>'+'<li>'+this.ans[1]+'</li>'+
'<li>'+this.ans[2]+'</li>'+'<li>'+this.ans[3]+'</li>')
}
}
var qn2 = Object.create(qn1);
qn2.title = "Golemn";
qn2.qn = "What this dude's name?";
qn2.ans= ["Golemn", "Gimli", "Goober", "Poop"]
qn2.img = "images/golemn.gif";
This is likely because your li elements are dynamically added.
You should try using jQuery on(), which allows you to bind an event handler to the parent element which must already exists in your DOM, and then you can specify the child/descendant selector that will call the event handler. This child element may still be non-existent at the time you do the event binding. In such a case, you call on() like:
$('ol').on('click', 'li', function () {...});
where ol already exists.
Alternatively, you could always bind your click handler to your dynamically generated li elements after you have added them to your DOM. Although I think that is more processor-time consuming as I assume you have to do this for all quiz questions you ask your user.
I am attempting to clone an input element of type="file". In this input element there is an onchange event. The clone method is called in the function that is called from the onchange event.
The problem I am having is that when I clone the file upload input, the onchange event is fired again. This only happens in Chrome, but works as intended in IE.
<input type="file" id="fileUpLoad" onchange="doFunction();" />
doFunction = function() {
var oldEl = $('#fileUpLoad');
var newEl = $(oldEl).clone();
$(oldEl).attr('id','fileUpLoadOLD');
$(oldEl).before(newEl);
}
I have created a fiddle duplicating this issue. Here is the fiddle.
How can I clone this file upload input without firing the onchange event again and again? I am using jQuery 1.7.1. This is actually a simplified a snippet from ajaxfileuploader.js:
var oldElement = jQuery('#' + fileElementId);
var newElement = jQuery(oldElement).clone();
jQuery(oldElement).attr('id', fileId);
jQuery(oldElement).before(newElement);
jQuery(oldElement).appendTo(form);
UPDATE: fiddle I think this is accomplishing what I need based on #MichaelAngstadt answer. Still testing though.
This is what seems to be working for me in the ajaxfileuploader.js extension:
var oldElement = jQuery('#' + fileElementId);
var oldElEvents = oldElement.data("events");
var newElement = jQuery(oldElement).attr('id', fileId).data("events", null).clone();
jQuery(oldElement).attr('id', fileElementId).before(newElement);
jQuery(oldElement).data("events", oldElEvents);
jQuery(newElement).appendTo(form);
You could always hold the events in a temporary object and re-attach them after cloning like:
doFunction = function(){
var oldEl = $('#fileUpLoad');
var oldElEvents = oldEl.data("events");
var newEl = $(oldEl).data("events", null).clone();
$(oldEl).attr('id','fileUpLoadOLD');
$(oldEl).before(newEl.data("events",oldElEvents));
}
This way oldEl doesn't actually have events defined to be cloned. Otherwise you'll be battling some fundamental functionality with regards to how cloning & the DOM work.
Have you tried cloning the object using $.extend instead.
var newEl = $.extend({},oldEl);
It seems the problem is related to onchange attribute. You can remove/reset the attribute before cloning. After cloning, the change event is triggered again and it results in an infinite loop.
var newEl = oldEl.removeAttr('onchange').clone();
You are loading jQuery, why not using it's methods? If you are using onchange attribute for keeping the event handler, you can use event delegation technique instead.
$(document).on('change', '.fileUpLoads', function() {
$(this).clone().insertBefore(this);
});
You don't need anything fancy here. If you attach the onchange event with jQuery/javascript instead of in the HTML, then clone( ) will only create one copy, as desired.
HTML:
<input type="file" id="fileUpLoad" />
JS:
$(document).ready( function() {
$('#fileUpLoad').change( function() {
doFunction();
});
});
doFunction = function() {
var oldEl = $('#fileUpLoad');
var newEl = $(oldEl).clone( );
$(oldEl).attr('id','fileUpLoadOLD');
$(oldEl).before(newEl);
}
P.S fileUpLoad, really? That sneaky capital L in there caused me some problems.
Building on the answer from #undefined (which works) I'd suggest actually making the change event a delegated event on a container object. Otherwise you end up with one input (the old one) which clones itself on change and another (the new one) which doesn't.
HTML:
<div class="container">
<input type="file" class="foo" id="fileUpLoad" />
</div>
Javascript:
$(function () {
$('.container').on('change', '.foo', function () {
var oldEl = $('#fileUpLoad');
var newEl = $(oldEl).clone().val('');
$(oldEl).attr('id', 'fileUpLoadOLD');
$(oldEl).before(newEl);
});
});
Working fiddle (I have also reset the value of the new input to null)
Note: This is still crashing Firefox for me. Not sutre why but might be my FF - the change event hasn't fired (in the other browsers) at the point FF crashes.
Explanation
I think what is happening here is that jQuery does not (by default) clone events when you clone an element. It does, however, clone attributes. So the onchange attribute on the element gets cloned and creates a change event on the cloned element, which for some reason is then called triggering an infinte loop. Not sure why .clone() triggers change events
Events attached via .on() on the object are not cloned - so no extra elements but no event on them either. By making the event a delegated event on a container we avoid the problem (since when the change event is triggered during clone the new element is not part of the page yet and so does not trigger the delegated event) but still have the functionality on the new object (once we insert it into the page).
You need better handling of the IDs if you want to be able to get more than two such inputs.
I am making a Sentence Generator. So far, it can take a list of words. Then it gets data from sentence.yourdictionary.com to take a sentence. I display the first sentence from sentence.yourdictionary.com using $("ul.example>li").first().Then it is put into a paragraph <p id="sents">.
So if you entered in the words yo and nose your output would be
<p id="sents"><li id="yo"><strong>Yo</strong> ' money
back a hund'd times, de preacher says!</li><li id="nose">I will no longer be caught with a
bleeding <strong>nose</strong>, heart palpatations, week long benders or hurting myself in any major way.</li></p>
I want a function to be called when you hover over the new list items.
$(document).ready( function() {
$("li").hover( function () {
$(this).append("<span>Testing</span>");
var id = $(this).attr("id");
console.log(id);
}, function () {
$(this).find("span:last").remove();
});
});
This doesnt work after the new list items are injected into the DOM. I tried adding an event listener for mousemove, but then when you hover over it the word "test" shows up a bunch of times! How can I make it happen after the new list items are injected?
Here is a jfiddle if you want some clarification: http://jsfiddle.net/varhawk5/cNKyx/1/
Thank you so much. Sorry I'm just learning javascript!
EDIT
To fix this issue, I used the .on() function as the comments suggested. There is no "hover" event though, so I think this is the only way.
$("body").on("mouseenter", "li#topWord", function() {
var word = $(this).data("word");
var sents = sentences[word]
$(this).html("<div class='restOfSents' data-word='" + word +"'></div>");
for(var i=1; i<sentences[word].length; i++) {
$(".restOfSents").append($(sentences[word][i]));
}
console.log(sents);
});
$("body").on("mouseleave", "li", function() {
// Remove the new div
});
$(document).ready( function() {
$(document).on('hover','li', function () {
$(this).append("<span>Testing</span>");
var id = $(this).attr("id");
console.log(id);
}, function () {
$(this).find("span:last").remove();
});
});
You're right! The reason for this, is that $(document).ready() only gets called on page load. You can either manually add a new event hook to each new element as you add it, or take advantage of jQuery's "on" functionality which will automatically detect new dom elements which match your criteria.
You should make use of .on() rather than .hover():
$('li').on('mouseenter', function() { ... })
Also you shouldn't use IDs for this. Make use of data-* attributes instead. Otherwise your code will break when a user enters the same word twice (as IDs are unique).
var id = $(this).attr('data-example');
I have a javascript function that runs quite nicely from html
'onClick="my_function(this.form)"
but I also want to call this function if a specific element within the form has data keyed in I have tried
jQuery(document).ready(function($){
$('#option_field_bizpostcode').keyup(function() {
var myform = $(this).closest('form').options-i-have-tried();
my_function(myform);
});
});
options-i-have-tried() include html() (and that shows that I have html inside of the correct form ok),
get() a bit of a stab in the dark,
serializeArray() from some answers to similar questions,
and nothing at all.
In each case my function complains that its argument form, or more specifically form.myelement is undefined
Many thanks in anticipation
Well your passing the FORM Element into the function in the inline handler (onclick attribute) so you need to do the same with the jQuery handler.
jQuery(document).ready(function($){
$('#option_field_bizpostcode').keyup(function() {
var myform = $(this).closest('form')[0]; //because the form element is at the first index in the jquery object
my_function(myform);
});
});
OR even better, why don't you just stick to doing this:
jQuery(document).ready(function($){
$('#option_field_bizpostcode').keyup(function() {
my_function(this.form);
});
});
I think you should be passing myform not form
this in a jQuery callback will be the element where the handler is attached. It is the native DOM element, without any jQuery wrapper.
So presuming that #option_field_bizpostcode is a form element, you should be able to do this.form just as you would in the onclick method.
my_function(this.form);
I think if you use the first element from the closest call you will be successful:
$('#option_field_bizpostcode').keyup(function() {
var myform = $(this).closest('form')[0];
my_function(myform);
});
The function associated with the selector stops working when I replace it's contents using .html(). Since I cannot post my original code I've created an example to show what I mean...
Jquery
$(document).ready(function () {
$("#pg_display span").click(function () {
var pageno = $(this).attr("id");
alert(pageno);
var data = "<span id='page1'>1</span><span id='page2'> 2</span><span id='page3'> 3</span>";
$("#pg_display").html(data);
});
});
HTML
<div id="pg_display">
<span id="page1">1</span>
<span id="page2">2</span>
<span id="page3">3</span>
</div>
Is there any way to fix this??...Thanks
Not sure I understand you completely, but if you're asking why .click() functions aren't working on spans that are added later, you'll need to use .live(),
$("#someSelector span").live("click", function(){
# do stuff to spans currently existing
# and those that will exist in the future
});
This will add functionality to any element currently on the page, and any element that is later created. It keeps you have having to re-attach handlers when new elements are created.
You have to re-bind the event after you replace the HTML, because the original DOM element will have disappeared. To allow this, you have to create a named function instead of an anonymous function:
function pgClick() {
var pageno = $(this).attr("id");
alert(pageno);
var data="<span id='page1'>1</span><span id='page2'> 2</span><span id='page3'> 3</span>";
$("#pg_display").html(data);
$("#pg_display span").click(pgClick);
}
$(document).ready(function(){
$("#pg_display span").click(pgClick);
});
That's to be expected, since the DOM elements that had your click handler attached have been replaced with new ones.
The easiest remedy is to use 1.3's new "live" events.
In your situation, you can use 'Event delegation' concept and get it to work.
Event delegation uses the fact that an event generated on a element will keep bubbling up to its parent unless there are no more parents. So instead of binding click event to span, you will find the click event on your #pg_display div.
$(document).ready(
function()
{
$("#pg_display").click(
function(ev)
{
//As we are binding click event to the DIV, we need to find out the
//'target' which was clicked.
var target = $(ev.target);
//If it's not span, don't do anything.
if(!target.is('span'))
return;
alert('page #' + ev.target.id);
var data="<span id='page1'>1</span><span id='page2'>2</span><span id='page3'>3</span>";
$("#pg_display").html(data);
}
);
}
);
Working demo: http://jsbin.com/imuye
Code: http://jsbin.com/imuye/edit
The above code has additional advantage that instead of binding 3 event handlers, it only binds one.
Use the $("#pg_display span").live('click', function....) method instead of .click. Live (available in JQuery 1.3.2) will bind to existing and FUTURE matches whereas the click (as well as .bind) function is only being bound to existing objects and not any new ones. You'll also need (maybe?) to separate the data from the function or you will always add new span tags on each click.
http://docs.jquery.com/Events/live#typefn