Modifying inserted element inside DOMNodeInserted event produces "too much recursion" - javascript

I'm writing code for a message board and when the user is writing a post and clicks "preview" it creates a new DIV element that contains the parsed post. I need to detect when this preview element is created and modify its contents. The following code creates infinite recursion in Chrome, Firefox halts after 5 recursions.
$('#c_post').on('DOMNodeInserted', function(){
var $preview = $('#c_post-preview');
if($preview.length) {
$preview.html(applyForEach(funcs, $preview.html()));
}
});
It's not related to applyForEach because I just added that code and I was getting the recursion error before that but here's the code for that anyway:
function applyForEach(arr, s) {
for(var i = 0; i < arr.length; ++i) {
s = arr[i](s);
}
return s;
}
var funcs = [createGifvVideo, createGfycatVideo, createHtml5Video];
The functions simply take a string, call replace on it, and returns the string.

You may break the infinite recursion by unbinding and binding event . so it would not go into infinite call.Try following-
$('#c_post').on('DOMNodeInserted',DomInsCallback);
function DomInsCallback(){
var $preview = $('#c_post-preview');
if($preview.length) {
$('#c_post').off('DOMNodeInserted');//here unbind first
$preview.html(applyForEach(funcs, $preview.html()));
$('#c_post').on('DOMNodeInserted',DomInsCallback);//bind again
}
}

I suppose #c_post-preview is inside #c_post. So when you modify #c_post-preview, the event DOMNodeInserted is triggered again. And you catch it again, and you modify #c_post-preview, and so on ...

Most probably you have nested #c_post-preview inside of #c_post, but i can't tell for sure, since you didn't post the HTML source. Of course this would lead to an infinite loop of triggering and catching events. But besides that, i don't think you want to applyForEach the content of the post preview, but the one of the post itself.
Consider the following: http://jsfiddle.net/wpb18pyu/
compared to: http://jsfiddle.net/wpb18pyu/1/

Related

Events are being executed automatically

So i decided to make a wordle clone today and wrote some basic javascript after doing some html and css. I wanted to add an event to all the elements of the array at once using forEach using the code below.
const letterCounter = 0;
Array.from(document.querySelectorAll("button")).forEach(elem => {
elem.addEventListener('click', inputLetter(elem.innerText))
})
function inputLetter(letter){
Array.from(document.querySelectorAll("letter-box"))[letterCounter].innerText = letter
letterCounter++
}
It immediately throws me an error in the console. The problem is that it is supposed to do that when I click on an element of the array, not automatically. I tried it with other elements, like displaying hello world in the console after clicking but it also executed automatically.(btw i do not need help with the code itself, I just want know why the added event is being executed automatically and also i'm a beginner so please don't judge my code :)).
eventHandler should be a function, you passed a function call by mistake. Function calls are run immediately.
const letterCounter = 0;
Array.from(document.querySelectorAll("button")).forEach(elem => {
elem.addEventListener('click', () => inputLetter(elem.innerText))
})
function inputLetter(letter){
Array.from(document.querySelectorAll("letter-box"))[letterCounter].innerText = letter
letterCounter++
}

How do I assign a pre and post function on button click without changing button definition or previously written functions

I have the following buttons:
<button id="abcd" onclick="something()">click</button>
and the following functions are attached to this button apart from the one in its html definition.
$('#abcd').on('click',function(){alert("abcd");});
$('#abcd').on('click',function(){
someAjaxCallWithCallback;
});
Now I want a new function with another ajax call to execute on this button's click, before the above mentioned functions. This new function determines whether the remaining functions would be called or not based on what data is recieved by the ajax call. That is, this pre function should complete its execution before giving control over to the rest of the functions and also determine whether they would run or not.
As an example, without changing the existing validation logics and button code, I have to add a new pre-validation function and similarly and post validation function.
I have a bindFirst method using which I can at least bring my new function to the beginning of the call stack but I have not been able to contain its execution and control further delegation because of callbacks.
If I understand correctly, you are looking for the way to do this, without modifying html and already existing js, only by adding new js-code.
First of all, if onclick handler is set and you want to control it, you should disable it on page load (maybe, saving it to some variable):
$(document).ready(function() {
var onclick = $("#abcd").attr("onclick").split("(")[0];
//to run it in future: window[onclick]();
$("#abcd").attr("onclick", "");
});
Edit: I changed my answer a little, previous approach didn't work.
Now you need to remove all already existing handlers. If number of handlers you want to control is limited, constant and known to you, you can simply call them in if-else after pre-validation inside your pre-function. If you want something more flexible, you are able to get all the handlers before removing, save them and then call them in a loop.
For that "flexible" solution in the end of $(document).ready(); you save all already existing handlers to an array and disable them. Then you write your pre-function and leave it as the only handler.
var handlers = ($._data($("#abcd")[0], "events")["click"]).slice();
$("#abcd").off("click");
$("#abcd").click(function() {
//this is your pre-func
//some code
handlers[1].handler.call();
});
Try console.log($._data($("#abcd")[0], "events")) to see, what it is.
Finally just run your post-function and do whatever you need, using conditions.
So, the general algorithm is as follows:
Disable onclick
Save all handlers
Disable all handlers
Run pre-func first
Run handlers you want to be executed
Run post-func
In fact, you just make your pre-func the only handler, which can run all other handlers you may need.
Although Alex was spot on, I just wanted to add more details to cover certain cases that were left open.
class preClass{
constructor(name,id){
if($(id) && $(id)[0] && $(id)[0]['on'+name])
{
var existing = $(id)[0]['on'+name]
$(id).bindFirst(name,existing);
$(id).removeAttr('on'+name)
alert("here");
}
if($._data($(id)[0],"events")){
this.handlers = $._data($(id)[0],"events")[name].slice();
}
else
{
this.handlers = null;
}
this.id = id;
this.name = name;
}
generatePreMethod(fn,data)
{
$(this.id).off(this.name);
$(this.id).bindFirst(this.name,function(){
$.when(fn()).then(execAll(data));
});
}
}
function exec(item,index){
item.handler.call()
}
function execAll(handlers){
return function(){ handlers.forEach(exec);}
}
This more or less takes care of all the cases.
Please let me know if there is something I missed!

Replacing all element's click bindings with a single document.on('click')?

I've always added click listeners to every separate element that needs to be listened, which can create a big messy Javascript with a lot of event bindings.
I was now thinking of doing it another way; by binding the click event to the entire document and upon click, see if the targeted element has a 'data-action' attribute and if present, execute the function in it. So that clicking:
Will execute function ajax_load_stuff()
It would make my code much cleaner, especially in ajax environments, but I want to know about performance and efficiency of this method. Are there any disadvantages to this approach?
UPDATE code example:
document.body.addEventListener("click", function (e) {
if (e.target) {
var action = e.target.getAttribute("data-action");
if (action) {
e.stopPropagation();
var params = e.target.getAttribute("data-params");
var data = [];
if (params) {
data = params.split(',');
}
window[action].apply(e.target, data);
}
}
}, false);
Ofcourse this approch has several advantages and disadvantages.
First discussing the disadvantages.
Need to handle event propagation perfectly otherwise it could make your system slow.
Passing parameter to click event will be difficult. Maybe need to introduce another attribute like : data-action-param
Advantages:
Less event handling code.

Automation script is not working?

This is the first time I get my hands on with automation instruments in xcode The script works well for all button taps but the one making server connection. I don't know the reason
Here is the script I tried so far
var target = UIATarget.localTarget();
target.pushTimeout(4);
target.popTimeout();
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
appScroll.logElementTree();
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
The above script works up to showing the UIActivityIndicator instead of moving to next controller after success
I know There must be a very simple point I am missing. So help me out
UIAutomation attempts to make things "easy" for the developer, but in doing so it can make things very confusing. It sounds like you're getting a reference to window, waiting for a button to appear, then executing .tap() on that button.
I see that you've already considered messing with target.pushTimeout(), which is related to your issue. The timeout system lets you do something that would be impossible in any sane system: get a reference to an element before it exists. I suspect that behind-the-scenes, UIAutomation repeatedly attempts to get the reference you want -- as long as the timeout will allow.
So, in the example you've posted, it's possible for this "feature" to actually hurt you.
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
What if the view changes during the 2-second delay? Your reference to target.frontMostApp().mainWindow.scrollViews()[0] may be invalid, or it may not point to the object you think you're pointing at.
We got around this in our Illuminator framework by forgetting about the timeout system altogether, and just manually re-evaluating a given reference until it actually returns something. We called it waitForChildExistence, but the functionality is basically as follows:
var myTimeout = 3; // how long we want to wait
// this function selects an element
// relative to a parent element (target) that we will pass in
var selectorFn = function (myTarget) {
var ret = myTarget.frontMostApp().mainWindow.scrollViews()[0];
// assert that ret exists, is visible, etc
return ret;
}
// re-evaluate our selector until we get something
var element = null;
var later = get_current_time() + myTimeout;
while (element === null && get_current_time() < later) {
try {
element = selectorFn(target);
} catch (e) {
// must not have worked
}
}
// check whether element is still null
// do something with element
For cases where there is a temporary progress dialog, this code will simply wait for it to disappear before successfully returning the element you want.

jQuery Event Handler created in loop

So I have a group of events like this:
$('#slider-1').click(function(event){
switchBanners(1, true);
});
$('#slider-2').click(function(event){
switchBanners(2, true);
});
$('#slider-3').click(function(event){
switchBanners(3, true);
});
$('#slider-4').click(function(event){
switchBanners(4, true);
});
$('#slider-5').click(function(event){
switchBanners(5, true);
});
And I wanted to run them through a loop I am already running something like this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners(i, true);
});
}
In theory that should work, but it doesnt seem to once I load the document... It doesnt respond to any specific div id like it should when clicked... it progresses through each div regardless of which one I click. There are more event listeners I want to dynamically create on the fly but I need these first...
What am I missing?
This is a very common issue people encounter.
JavaScript doesn't have block scope, just function scope. So each function you create in the loop is being created in the same variable environment, and as such they're all referencing the same i variable.
To scope a variable in a new variable environment, you need to invoke a function that has a variable (or function parameter) that references the value you want to retain.
In the code below, we reference it with the function parameter j.
// Invoke generate_handler() during the loop. It will return a function that
// has access to its vars/params.
function generate_handler( j ) {
return function(event) {
switchBanners(j, true);
};
}
for(var i = 1; i <= totalBanners; i++){
$('#slider-' + i).click( generate_handler( i ) );
}
Here we invoked the generate_handler() function, passed in i, and had generate_handler() return a function that references the local variable (named j in the function, though you could name it i as well).
The variable environment of the returned function will exist as long as the function exists, so it will continue to have reference to any variables that existed in the environment when/where it was created.
UPDATE: Added var before i to be sure it is declared properly.
Instead of doing something this .. emm .. reckless, you should attach a single event listener and catch events us they bubble up. Its called "event delegation".
Some links:
http://davidwalsh.name/event-delegate
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-event-delegation-in-4-minutes/
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
http://lab.distilldesign.com/event-delegation/
Study this. It is a quite important thing to learn about event management in javascript.
[edit: saw this answer get an upvote and recognized it's using old syntax. Here's some updated syntax, using jQuery's "on" event binding method. The same principle applies. You bind to the closest non-destroyed parent, listening for clicks ON the specified selector.]
$(function() {
$('.someAncestor').on('click', '.slider', function(e) {
// code to do stuff on clicking the slider. 'e' passed in is the event
});
});
Note: if your chain of initialization already has an appropriate spot to insert the listener (ie. you already have a document ready or onload function) you don't need to wrap it in this sample's $(function(){}) method. You would just put the $('.someAncestor')... part at that appropriate spot.
Original answer maintained for more thorough explanation and legacy sample code:
I'm with tereško : delegating events is more powerful than doing each click "on demand" as it were. Easiest way to access the whole group of slider elements is to give each a shared class. Let's say, "slider" Then you can delegate a universal event to all ".slider" elements:
$(function() {
$('body').delegate('.slider', 'click', function() {
var sliderSplit = this.id.split('-'); // split the string at the hyphen
switchBanners(parseInt(sliderSplit[1]), true); // after the split, the number is found in index 1
});
});
Liddle Fiddle: http://jsfiddle.net/2KrEk/
I'm delegating to "body" only because I don't know your HTML structure. Ideally you will delegate to the closest parent of all sliders that you know is not going to be destroyed by other DOM manipulations. Often ome sort of wrapper or container div.
It's because i isn't evaluated until the click function is called, by which time the loop has finished running and i is at it's max (or worse overwritten somewhere else in code).
Try this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners($(this).attr('id').replace('slider-', ''), true);
});
}
That way you're getting the number from the id of the element that's actually been clicked.
Use jQuery $.each
$.each(bannersArray, function(index, element) {
index += 1; // start from 0
$('#slider-' + index).click(function(event){
switchBanners(index, true);
});
});
You can study JavaScript Clousure, hope it helps

Categories