EDIT**
In my game I want my button to disable after one click but for some reason all the ways I have looked at do not work.
Here is what I have at the moment..
$('.minibutton').click(function() {
$(this).attr('disabled','disabled');
$('.minibutton').prop('disabled', false);
$('.picstyle').show();
$('td').removeClass('spellword');
var r = rndWord;
while (r == rndWord) {
rndWord = Math.floor(Math.random() * (listOfWords.length));
}
$('td[data-word="' + listOfWords[rndWord].name + '"]').addClass('spellword');
$('td[data-word=' + word + ']').removeClass('wordglow').removeClass('wordglow4').removeClass('wordglow3').css('color', 'transparent');
var noExist = $('td[data-word=' + listOfWords[rndWord].name + ']').hasClass('wordglow2');
if (noExist) {
$('.minibutton').click();
} else {
$("#mysoundclip").attr('src', listOfWords[rndWord].audio);
audio.play();
$("#mypic").attr('src', listOfWords[rndWord].pic);
jQuery(pic).show();
}
}).trigger("click");
I have tried changing the click function to..
$('.minibutton').one("click", function() {
I have also tried to bind and unbind it but it will not work
fiddle http://jsfiddle.net/7Y7A5/9/
You spell the highlighted word, using the picture as a clue. If you get the word wrong 3 times you are given the opportunity to move on because "minibutton" appears. At this point I only want the user to be able to click it once before it disappears again.
Take a look at the jQuery .on() and .off() methods. In the documentation on jQuery's site, there should be a section about namespacing - this section covers what you're looking for.
Below, I have given you the solution that you are looking for, but review the documentation anyway so that you can better understand it.
First, add this variable declaration above your click-handler:
var timesClicked = 0;
Then, change your handler declaration to this:
$('.minibutton').on("click.oneclick", function () {...});
And add these lines in your handler:
if (timesClicked > 0) {
$(this).off("click.oneclick");
} else {
timesClicked++;
}
Basically, what's happening here is that you are giving your click-event handler a distinct name that differentiates it from all other click-event handlers on the page (that is, all other click-handlers that do not also have that namespace). You could have applied the namespace "foo" - I chose to apply the namespace "oneclick". The result is that you have the ability to remove that specific click-event handler without removing any of the other click-event handlers (that is, any others that do not have the given namespace).
Try this updated fiddle of your solution.
UPDATE:
To help you with your question of re-enabling the click event, first do the following (note: there are a lot of things in your code that need cleaning up, but I did point out one thing, which was an empty click handler being set to minibutton):
var clickOnceTest = 0;
function enableMinibutton() {
$('.minibutton').on("click.clickOnceNS", function() {
if (clickOnceTest > 0) {
$(this).off("click.clickOnceNS");
} else {
clickOnceTest++;
}
$('.minibutton').prop('disabled', false);
$('.picstyle').show();
$('td').removeClass('spellword');
var r = rndWord;
while (r == rndWord) {
rndWord = Math.floor(Math.random() * (listOfWords.length));
}
$('td[data-word="' + listOfWords[rndWord].name + '"]').addClass('spellword');
$('td[data-word=' + word + ']').removeClass('wordglow').removeClass('wordglow4').removeClass('wordglow3').css('color', 'transparent');
var noExist = $('td[data-word=' + listOfWords[rndWord].name + ']').hasClass('wordglow2');
if (noExist) {
/////
/////
/////
/////
/////
////// Is this supposed to trigger a click? because all this does is set another click handler (which does nothing) to minibutton..
/////
/////
/////
/////
/////
$('.minibutton').click();
} else {
$("#mysoundclip").attr('src', listOfWords[rndWord].audio);
audio.play();
$("#mypic").attr('src', listOfWords[rndWord].pic);
pic.show();
}
});
}
enableMinibutton();
$(".minibutton").trigger("click");
Once you've done that, when you're ready to re-enable minibutton all you have to do is call the method enableMinibutton(). If you need to call the trigger("click") method again, do so (I'm not exactly sure why it was there for in the first place). You can call this method either immediately before/after showing the button the second time (my preference - I don't like unnecessary handlers slowing my script down), or you can call it immediately after you hide the button.
As for the other things that need cleaning: throughout your code you have things, like the empty click handler that I pointed out, that are unnecessary, slow down your program, throw errors, or just plain don't work. Remove or comment these things out ASAP. Once you do, your code will run much smoother, and your tests/experiments will be more likely to succeed/fail properly, without influence of bad code.
Additionally, and I cannot stress this one enough, you need to set your element selectors to variables. You select minibutton and minibutton2 and a bunch of other items dozens of times throughout your code. Just set their selectors to variables at the top and use said variables to refer to your elements, instead of selecting them over and over again.
Every time you call a jQuery selector, you are essentially searching the DOM for the elements that satisfy your selector. If your collection is always going to have the same elements in it, set the selector to a variable and call on the variable instead. This way, you are only searching the DOM once for these elements. The performance difference is very noticeable in heavy-weight programs, especially in ones like this. In case you are unfamiliar with this syntax, here are two examples, using minibutton and minibutton2:
var $minibutton = $(".minibutton");
var $minibutton2 = $(".minibutton2");
....
$minibutton.on("click.click.......
...
$minibutton.trigger(....
...
$minibutton2.prop(...
...
As you can see, I like to prefix my selector variables with the $ so that it is easier to see and remember that the variable they hold is a selected element. This is not required, and you can set the variable name to whatever you want.
The only catch to doing this is that you must "update" the variable if you plan to add more items to the DOM that would be returned by the selector; the variable will reference all elements returned by the selector AT THE TIME THE SELECTOR WAS INITIALLY CALLED. This means that if you have a collection of elements of a particular class and set a variable to refer to that collection, before adding additional elements with said class, in order to reference additional elements that would later be returned by that selector you will have to set it again. Yes, I know that was long and confusing, but it's actually pretty simple and there are dozens of examples online about it.
<input type="submit" onclick="this.disabled = true" value="Save"/>
Use modern Js, with "disabled" or "once"!
button.disabled = true;
or
button.addEventListener("click", function() {
// Fire event
}, {once : true});
Documentation, CanIUse
Related
This question already has answers here:
How do I attach events to dynamic HTML elements with jQuery? [duplicate]
(8 answers)
Closed 3 years ago.
I'm trying to figure how I can use the each() method to loop through dynamic elements. The method does not seem to be working if the element is not present. I do not wish to use setTimeout either and would prefer something that would not delay the method or event.
So far all of the research and searching I've seen is using an event handler to trigger on dynamic objects.
$('.task').each(function() {
let that = this;
// let startTask = $('.start-task', that);
let mc = new Hammer(this);
mc.on("panright", function(e){
if(e.deltaX < 100 ) {
$(that).css('transform', 'translateX(' + e.deltaX + 'px' + ')');
$(that).addClass('pan-task');
} else {
$(that).css('transform', 'translateX(100px)');
$('.start-task', that).trigger('click');
}
});
mc.on('panend', function(e){
$(that).css('transform', 'translateX(' + '0' + 'px' + ')');
$(that).removeClass('pan-task');
});
});
This will work with dynamic elements. It just depends when you run it. First you do what you have copied here but you isolate what is inside the “each” in a function. Then when you create a new task, you run the same function as a callback.
Really do a function where the task is the argument. Otherwise you risk attaching the same event multiple times.
function initTaskEvents(task) {
let mc = new Hammer(task.get(0));
mc.on("panright", function(e){
if (e.deltaX < 100 ) {
task.css('transform', 'translateX(' + e.deltaX + 'px' + ')');
task.addClass('pan-task');
} else {
task.css('transform', 'translateX(100px)');
$('.start-task', task).trigger('click');
}
});
mc.on('panend', function(e){
task.css('transform', 'translateX(' + '0' + 'px' + ')');
task.removeClass('pan-task');
});
}
As you see the argument is assumed to be already a jQuery object. This is why I used .get(0) for your constructor. Remove it if it accepts jQuery objects. By the way, it is not needed anymore, but avoid creating the same jQuery object $(that) many times because $ is actually an expensive function. You want to save it in a variable if possible and reuse it.
Then you use it on existing tasks if you have tasks when loading the page already.
$(function() { // When ready
$('.task').each(function() {
initTaskEvents($(this));
});
});
So far it does the same as what you already had. But then when you insert a new task in the DOM, you run it through the function as well.
// ...
tasklist.append(myNewTask);
initTaskEvents(myNewTask);
// ...
Does that make sense? It is the common way to do it.
Now if you only attach events (not your case since you are creating Hammer in the middle), you can use this in jQuery.
$('body').on('.task', 'click', function(event) {
// ...
});
It works on things which don't exist yet because the event is attached to body and delegates. I have seen this mentioned in the comments, but again this does not work in your case because you are creating a Hammer object from your task.
You can achieve this behavior with MutationObserver
as the name implies, it is used to observe mutations on DOM elements. It's the most efficient way to watch DOM changes and the callback is invoked after the DOM updated.
const taskList = $('#task-list');
const observer = new MutationObserver((mutation, observer) => {
// Looping only on added nodes
mutation[0].addedNodes.forEach(node => {
// Do something with added node
})
})
const observerOts: MutationObserverInit = { attributes: true, childList: true, subtree: true };
observer.observe(taskList[0], observerOts);
here's a working example
Not sure if I am understanding the problem exactly, but everytime I have to loop a list of dynamic elements I use an event delegation approach, and it looks like this is what you need here.
Basically, it consists in attaching the event listener to the parent and then use event.target to get the element you need to work with.
Here is a great explanation by David Walsh: https://davidwalsh.name/event-delegate
from what I understand from your question is that you want to addEventsListener to elements which returned from Hammer class right? so can you post your Hammer class too ?
you can try:
$(mc).on('panright', function() { ... })
Using jQuery I need to:
persists list of all event handlers that are added to element,
remove them all for few seconds and
return things to initial state (reassign the same event handlers)
I found that get list of current listeners with (some jQuery inner mechanisms):
var eventsSubmitBtn = $._data(submitButton[0], "events");
Then I can remove all event listeners with
submitButton.off();
But last stem seems not to be working
setTimeout(function () {
$._data(submitButton[0], "events", eventsSubmitBtn);
}, 5000);
eventsSubmitBtn is an empty array.
Is this the way this should be done with initial setting and I'm need something like deep cloning for those objects or this can't be done with $._data?
N.B. I have possibility to add my cistom code after all other system js code, thus I can't place the code assigning to $.fn.on before anything. Code that I write will run the last on startup and other event listeners are attached before my scripts will run.
As you get a reference to the object returned by $._data(), any change to that object will not go unnoticed, i.e. after you invoke .off(), that object will have changed to reflect that there are no handlers attached any more.
You could solve this by taking a shallow copy of the object, (e.g. with Object.assign).
But this is not really a recommended way to proceed. According to a jQuery blog, "jQuery._data(element, "events") ... is an internal data structure that is undocumented and should not be modified.". As you are modifying it when restoring the handlers, this cannot be regarded best practice. But even only reading it should only be used for debugging, not production code.
It would be more prudent to put a condition in your event handling code:
var ignoreEventsFor = $(); // empty list
$("#button").on('click', function () {
if (ignoreEventsFor.is(this)) return;
// ...
});
Then, at the time it is needed, set ignoreEventsFor to the element(s) you want to ignore events for. And when you want to revert back to normal, set it to $() again.
Now adding this to all your event handlers may become a burden. If you stick to using on() for attaching event handlers, then you could instead extend $.fn.on so it will add this logic to the handlers you pass to it.
The following demo has a button which will respond to a click by changing the background color. With a checkbox you can disable this from happening:
/* Place this part immediately after jQuery is loaded, but before any
other library is included
*/
var ignoreEventsFor = $(), // empty list
originalOn = $.fn.on;
$.fn.on = function (...args) {
var f = args[args.length-1];
if (typeof f === 'function') {
args[args.length-1] = function (...args2) {
if (ignoreEventsFor.is(this)) return;
f.call(this, ...args2);
};
}
originalOn.call(this, ...args);
}
/* This next part belongs to the demo, and can be placed anywhere */
$(function () {
$("#colorButton").on('click', function () {
// Just some handler that changes the background
var random = ('00' + (Math.random() * 16*16*16).toString(16)).substr(-3);
$('body').css({ backgroundColor: "#" + random });
});
$("#toggler").on('change', function () {
// Toggle the further handling of events for the color button:
ignoreEventsFor = $(this).is(':checked') ? $("#colorButton") : $();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="colorButton">Change color</button><br>
<input type="checkbox" id="toggler">Disable events
Notice: the above code uses ES6 spread/rest syntax: if you need support for IE then that would have to be written using the arguments variable, apply, ...etc.
I'm developing a Chrome extension, and I'm adding an onmouseover handler to each of the images on a page. When the user mouses over an image, it's URL should be stored in a variable. I know I can easily get the value of the src attribute of the image, but I want the full URL. The src attribute stores the path of the image on the server. For example, when you right click an image in Google Chrome, you get the "Copy Image URL" option, which copies the image's URL to the clipboard.
Is there any way to achieve this? Thanks.
Instead of imageElement.getAttribute("src") or $("img.something").attr("src"), which reads the original markup, use imageElement.src property which will always give you the full URL.
var imgFullURL = document.querySelector('img.something').src;
or:
var imgFullURL = $('img.something')[0].src;
To extract host name, path name etc. - parse the url with URL() constructor, which works in modern browsers or use the legacy method via creating a temporary a node.
You can use window.location to get the page you are currently on and the following will give you the URL parts you need:
window.location.protocol = "http:"
window.location.host = "stackoverflow.com"
window.location.pathname = "/questions/32828681/how-to-get-url-of-an-image-in-javascript"
So, likely, you will need protocol, then "//", then host and finally the image src.
So the TL;DR is this:
(function() {
const imageInfo = new Object();
imageInfo.source = '';
window.addEventListener('mouseover', function (event) {
var currentElement = event.target;
// console.log(event.target);
if (currentElement.tagName === 'IMG') {
// console.log(currentElement.outerHTML + "is a photo");
imageInfo.source = currentElement.src;
// console.log("src is :" + imageInfo.source)
return imageInfo.source;
}
})
})();
See CodePen:
How to find the src URL for a photo by Trevor Rapp on
CodePen
This is how I thought about solving the problem in the most basic steps:
get the function to fire.
get the function to add an event listener that will perform an action on a mouseover event.
make that action know what the mouse is currently over.
figure out if what the mouse is currently over is an image or not.
create logic that will respond if it is.
that action that logic should do is return the source URL.
I will need to store that source URL if I am going to have to return it.
Here are how each of those solutions looked:
get the function to fire.
An IFFE is a great way to get a function to fire without having to worry about polluting the name space.
//skeleton for an IFFE statement
(function() {
})();
get the function to add an event listener that will perform an action on a mouseover event.
An event listener that could fire anywhere would have to be attached to the window or the document.
make that action know what the mouse is currently over.
This part will be combined with part 2. Event listener's first parameter is what type of event you want to listen for -- in this case 'mouseover. So now our code looks like this
(function () {
window.addEventListener('mouseover', function (event) {
//do stuff here
}
})()
figure out if what the mouse is currently over is an image or not.
*To figure out which element the mouse if currently over you would use Event.target.
The MDN definition for that is: *
The target property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event. --Event.Target
*So the code would then look like this: *
(function () {
window.addEventListener('mouseover', function (event) {
//get the current element the mouse is over
var currentElement = event.target;
}
})()
create logic that will respond if it is.
This was a little trickier since a photo or IMG can be presented in various ways.
I chose to create a solution for the simplest way, which is assuming that the web developer used the more syntactically correct version of an tag. However, there are many times when they may choose to apply a 'background-image' CSS property to a normal . Other things to consider could be the use of iframes, which can make detecting the attributes of child elements very frustrating since they don't allow bubbling to occur. To tell if an element is an , you can simply use elem.tagName === "IMG" for your logic check. While not included in the above code, if you wanted to check if a div is using the 'background-image', you could use something like element.getAttribute('style').includes('term') and switch out 'term' for something like 'url' or 'jpg' or 'png.' Kind of clunky and hacky, but just a thought. Anyway, the code would then become
(function () {
window.addEventListener('mouseover', function (event) {
//get the current element the mouse is over
var currentElement = event.target;
if (currentElement.tagName === 'IMG') {
//do stuff
}
}
})()
that action that logic should do is return the source URL.
Once you get the logic done and you have properly selected the element, then you can use element.src to get the source URL.
I will need to store that source URL if I am going to have to return it.
You can do this anyway you want, but I played around with instantiating an object since it sounded like the value would need to change often, but you didn't necessarily need to store previous values.
And so the final product could be something like this
(function() {
const imageInfo = new Object();
imageInfo.source = '';
window.addEventListener('mouseover', function (event) {
var currentElement = event.target;
// console.log(event.target);
if (currentElement.tagName === 'IMG') {
// console.log(currentElement.outerHTML + "is a photo");
imageInfo.source = currentElement.src;
// console.log("src is :" + imageInfo.source)
return imageInfo.source;
}
})
})();
I'm using late-binding to assign onClick events to checkboxes (styled as dots). My first assignment [located at https://github.com/farfromunique/vampirrePoints/blob/master/events.js#L264 ] goes perfectly, and reacts as expected - all dots have this as their onClick:
/* https://github.com/farfromunique/vampirrePoints/blob/master/main.js#L440 */
allDots[i].onclick = function() {
if (this.checked) {
decrementCounter(1);
} else {
incrementCounter(1);
};
}
However, when Step11() is triggered, and freebieDotSetup() is called, only some of the checkboxes get their onClicks updated. Specifically, sta1, sta2, sta3, sta4, sta5 (possibly others, too) keep their initial value.
I have tried putting console.log() statements in during the assignment process, and it looks like the assignment happens, but it doesn't "stick". Why doesn't this work?
Code reference (whole site): https://github.com/farfromunique/vampirrePoints
By request, a non-working JSFiddle: http://jsfiddle.net/farfromunique/mS4Lp/
Note: the menu is on the wrong side, doesn't advance properly, and none of the "dots" (checkboxes) are clickable, so functionality is not testable. I strongly suspect that my code is not cross-browser compatible, but that isn't a priority for me (yet).
The solution: In the function freebieDotSetup(), I was doing this:
for (i=0;i<attributeDots.length;i++) {
attributeDots[i] = allDots[i];
}
...
for (i=0;i<abilityDots.length;i++) {
abilityDots[i] = allDots[i+attributeDots.length];
}
...
for (i=0;i<disciplineDots.length;i++) {
disciplineDots[i] = allDots[i+abilityDots.length];
}
...
Since I was using the previous group's length, it would occasionally overwrite the previous values (due to groups being shorter). I corrected this with this:
startPos = startPos + attributeDots.length;
for (i=0;i<abilityDots.length;i++) {
abilityDots[i] = allDots[i+startPos];
}
Adding the previous group's length to startPos each time. Making this change resolved my issue.
... But the JSFiddle is totally busted, still.
Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.