When i use if statements to determine whether a user has viewed the form before, it breaks my whole code.Originally, my code just removed a child, and set the display of the next DIV to "block".This worked perfectly fine. all my variables saved properly in the end, etc.
However, since I added these checks to the buttons, everything has gone haywire.
I've tried using different mixes of appendChild, removeChild, and style.display methods. I even booted it up in notepad++ to help me visualize.
document.getElementById("newUser").addEventListener("click", function(parent, start, personal){
parent.removeChild(start);
document.getElementById.style.display="block";
});
document.getElementById("toGeneral").addEventListener("click", function(){
if(reUser === 0){
oPersonal;
document.getElementById("general").style.display="block";
} else if(reUser === 1){
oPersonal;
parent.appendChild(oShowInfo);
} else {
window.alert("Whoops this function is in progress");
}
return oPersonal;
});
I want my forms to be editable at the end of the form.
In one test (not this one). I was able to move back and forth between pages, but it would stop the loops that kept my variables up.
This one is using the oldChild = parent.removeChild() method to fix that, which may also be part of the issue.
**Note: variables such as
oPersonal
reUser
parent
are stored just above this code at the beginning of the page loop.
New code:
document.getElementById("newUser").addEventListener("click", function(){
parent.removeChild(start);
document.getElementById.style.display="block";
});
document.getElementById("toGeneral").addEventListener("click", function(){
if(reUser === 0){
oPersonal;
document.getElementById("general").style.display="block";
} else if(reUser === 1){
oPersonal;
parent.appendChild(oShowInfo);
} else {
window.alert("Whoops this function is in progress");
}
return oPersonal;
});
You have many problems in this code. First, your click event's handler can not take 3 args like this. When an event is triggered, only this event is passed to the function. So your code should looks like :
document.getElementById("newUser").addEventListener("click", function(event) {
let element = event.target;
let parent = element.parentNode;
parent.removeChild(element);
//Next line is wrong since getElementById is a function and should take args
//document.getElementById.style.display="block";
});
Then on your second element's click event handler, it seems like you are trying to use parent which is not in the same scope.
Also the return statement is strange, what are you trying to do ?
Related
https://jsfiddle.net/50Lw423g/2/
gameLogic: function() {
console.log("GAME LOGIC");
alert("Your Turn!");
var that = this;
$(".game-screen").on("click", ".tic", function() {
console.log("EVENT ATTACHED");
var movesDisplay = $(this).text(),
playerOne = that.playerOneMoves,
moveID = +(this.id);
if (movesDisplay !== "O" && movesDisplay !== "X") {
that.playerOneMoves.push(moveID);
if (playerOne.length > 2) {
if (that.checkIfWinCondition(playerOne, that.winConditions)) {
alert("GAME OVER!");
that.inGame = false;
ticTacToe.restartGame();
}
}
$(this).text(that.playerFaction === "X" ? "X" : "O");
}
});
},
I'm writing a Tic-Tac-Toe game and ran into this problem - when multiple sessions are "played" click events keep compounding. Which I solved by clearing the previous click events every time before a new one is attached.
$(".game-screen").off().on("click", ".tic", function () { //do stuff }
By the way event.stopPropagation() and event.stopImmediatePropagation() did NOT work.
Anyway, while I managed to fix the problem and understand why the click events where compounding, what I can't seem to wrap my head around is why those compounded click events kept calling the parent function gameLogic: function(). Try "playing" several sessions in a row -
console.log("GAME LOGIC");
alert("Your Turn!");
get called exponentially more with every session. Does jQuery use the parent function to track the click events or something? Can anyone explain what's happening under the hood? Why do the event handlers keep referring to the parent function?
Move your function implementation out from the gameLogic function like bellow;
function gameScreenClick(){
// your click event handler code.....
}
then
$(".game-screen").on("click", ".tic", gameScreenClick);
I traced the call stack, and looked at the jQuery dispatch: function(). Actually, as it turns out, the problem wasn't with the way jQuery internally refers to it's click functions and handlers.
The culprit was on line 46. The getUserInput() attaches an event handler which in turn attaches the chooseFaction() event handler. On the next session, getUserInput() gets attached one more time and chooseFaction() is attached 2 times (+1 time from before) - so chooseFaction() fires 3 times calling generateBoard(), and in turn, calling gameLogic()etc. Thus calls to gameLogic() exponentially increasing with every iteration.
Moved $(.game-screen).off() to the restartGame() function, to remove all click events before starting a new session, as a cleaner solution.
Solution: https://jsfiddle.net/50Lw423g/4/
I have a wrapper for the HighCharts lib which autogenerates some code based on their API. In order to autogenerate this code I must export the HTML of the API website with ALL (recursive) the links (from the left side menu) expanded. This must be done recursive as new expanded links may have more not-yet-expanded links.
Right now I must manually proceed this loop from the Browser's Javascript console:
$('div.collapsed').find($('a.plus')).click();
$('div.collapsed').find($('a.plus')).length. If zero, I am done. If none zero, then proceed again with 1).
I tried to automatize this like follows:
while ( $('div.collapsed').find($('a.plus')).length !== 0 ) {
console.log('Doing a pass');
$('div.collapsed').find($('a.plus')).click();
console.log('Pass finished');
}
But it doesn't work as it goes to an endless loop. I guess this is because of onClick fires some async code (maybe an Ajax call?). Any idea how can I make it work?
Thanks in advance,
$('div.collapsed').find($('a.plus')).length is not going to change the value so please use
$('div.collapsed').find('a.plus').each(function(){
//some code
})
for more information regarding each. please check here
I finally fixed it this way:
/* Technically we could simulate the click on the links, like this:
$('div.collapsed').find($('a.plus')).click();
But that won't work as the clicks fire an async AJAX call so we don't know
when to expand their children. To make the recursion to work, I found there are many useful functions
in http://api.highcharts.com/resources/js/api.js
The function toogleExpand() allow us to pass a callback. That way, on callback, we expand again with children
making the recursion to work. */
function expandLinks( items ) {
if (items.find('div[id$="-menu"]').length !== 0) {
items.find('div[id$="-menu"]').each(function(){
var link = $(this);
toggleExpand(link.closest('.collapsed'), function() {
/* note that in this case we are making the recursion but only with the children of items */
expandLinks( items.find('div.menuitem.collapsed') )
});
});
} else {
if( $('div.collapsed').find($('a.plus')).length == 0 ) {
/* If there are no more links to open it means everything is ready so lets download the file */
downloadDetailsDivContent();
}
}
}
Specifically, I want to iterate over every element on the page, each time the value of one changes.
So to paraphrase my code I have:
$('select.filterbox').change(function() {
// stuff
$('select.filterbox').each(function() {
// other stuff
});
});
'stuff' all executes just fine, but 'other stuff' doesn't happen
Here's the full code
// On Filterbox Change
$j('select.filterbox').change(function() {
// Show All Rows
$j('#table1 tr').show();
// For Each Filterbox
$j('select.filterbox').each(function() {
var selVal = $j(this).attr('value');
var col = $j(this).closest('th').parent().children().index($j(this).closest('th'));
alert('Column '+val+' : '+selVal);
// If Selected Value Not Empty
if(selVal != "") {
// For Each Row
$j('#table1 tr').each(function() {
var $tds = $j(this).find('td');
var cellVal = $tds.eq(col).text();
cellVal = $j.trim(cellVal);
// If td text != selected
if( cellVal != selVal ) {
// Hide this row
$j(this).hide();
}
});
}
});
});
Answer: yes you can. I've done a lot of stuff like that recently. Indeed, your code should do it. It is likely that you've made some small but significant error in your code that will need to be hunted down and fixed. I've done a lot of stuff stuff like that recently, too.
More helpful answer: do things to figure out where the breakdown is. replace the "$('select.filterbox').each()" call with something obvious - say, calling .hide() on large chunks of your page. Make sure that line of code is even being called. If it is being called, put the "$('select.filterbox').each()" call back, then move the hide() call inside of it. If that works, then you know that it's running at least once. Change the hide() call to a (this).hide(), and see if you can see which one it's identified. Once you've narrowed down where it's failing you, it'll be a lot easier to figure out what you've done wrong.
You can do this, but it's not optimal to attach separate event handlers to a lot of elements. You can take advantage of event bubbling and just attach a single handler to their parent container. For example:
$("#filters").change(function (evt) {
// evt.target will give you actual element that changed
$("#filters select.filterbox").each(function () {
// other stuff
});
});
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.
I have a series of buttons that fire the list function when they are clicked. The list function itself contains an AJAX request and a bunch of other stuff before and after which loads in a separate section of the page.
var list = function() { }
$$('.buttons').addEvent('click', list);
Everything works fine if I wait for list to complete before clicking on another button. However, if I click on two buttons quickly, the page will start to return incorrect responses. In fact, it appears as though the responses get out of sync by 1. So if I quickly click on button A then button B, it will load response A. Then if I click (much later) on button C, it will load response B.
There are two ways I can see to solve this:
1) Remove the click event from other buttons when any button is clicked and then restore it when list is complete. Unfortunately, I have tried placing $$('.buttons').removeEvents() at the top of the list function and then $$('.buttons').addEvent('click', list); at the bottom but this has no effect on the problem.
2) Chain the click events so that list is only ever executed when the preceding list has finished.
So can anybody tell me how to get the second solution working? Additionally, if anybody knows why the first solution doesn't work and why I get the weird delayed AJAX response behaviour, that would be great!
The first solution doesn't work because events on an element are fired in order, but are executed asynchronously. You'll need to setup a queue of callbacks that you can process when the event is triggered.
Here's the basic idea:
addQueuedEvent = function(node, event, callback) {
if ( typeof callback != "function" ) {
throw "Callback must be a function";
}
event = event.toLowerCase();
var eventQueue = "_" + event;
if ( !node.hasOwnProperty(eventQueue) ) {
node[eventQueue] = [];
}
node[eventQueue].push(callback)
};
processEventQueue = function(node, event) {
var eventQueue = "_" + event;
if ( node.hasOwnProperty(eventQueue) ) {
for ( var i=0, l=node[eventQueue].length; i<l; ++i ) {
node[eventQueue][i]();
}
}
};
And the usage:
var someElement = $("#some-element");
addQueuedEvent(someElement, "click", callback1);
addQueuedEvent(someElement, "click", callback2);
addQueuedEvent(someElement, "click", callback3);
someElement.addEvent("click", function() {
processEventQueue(this, "click");
});
The syntax checks out, but this is not tested. Hope that helps.
i would personally just set a global / scoped variable in your class or whatever - something like 'isClicked = false'.
then simply check at the the the click event function, something like:
var isClicked = false, click = function() {
if (isClicked)
return false;
isClicked = true;
// ... do stuff, chained or otherwise...
// when done, make click function work again:
isClicked = false; // you can do this onComplete on the fx class also if you use it
};
i would go against chaining events with effects - if you have an animation going on, simply wait for it to end--otherwise it can get messy for any trigger happy user that thinks double clicking is the way to go. an alternative is to stop / cancel any effects that are taking place on a new click. for instance, you can stop any tweens etc through FX by something like:
if (isClicked === true) fxinstance.cancel();
http://mootools.net/docs/core/Fx/Fx
the other thing you can do is look at the mootools .chain class
http://mootools.net/docs/core/Class/Class.Extras#Chain
and also, on any fx instances, you can pass on link: "chain" and simply queue them up.
good luck