Is it so bad to have heaps of elements in your DOM? - javascript

I am making a real estate non interactive display for their shop window.
I have kicked jCarousel into doing what I want:
Add panels per AJAX
Towards the end of the current set, go and AJAX some new panels and insert them
This works fine, but it appears calling jQuery's remove() on the prior elements cause an ugly bump. I'm not sure if calling hide() will free up any resources, as the element will still exist (and the element will be off screen anyway).
I've seen this, and tried carousel.reset() from within a callback. It just clears out all the elements.
This will be running on Google Chrome on Windows XP, and will solely be displaying on LCD televisions.
I am wondering, if I can't find a reasonable solution to remove the extra DOM elements, will it bring my application to a crawl, or will Chrome do some clever garbage collecting?
Or, how would you solve this problem?
Thanks

Could you reuse old elements instead of removing them and adding new ones ?

I worked out a fix that ended up being very simple!
Simply pass this to the config for jCarousel
itemFirstOutCallback: {
onAfterAnimation: function(carousel, li, index, state) {
if (state === 'init') return;
carousel.remove(index);
}
}
Basically, this just removes the list element as soon as it becomes invisible (scrolled into negative overflow: hidden territory, if you will :) )

Related

Using addEventListener on multiple elements, avoid TypeError when particular element not found

I'm using two simple addEventListener mouseenter and mouseleave functions respectively to play and stop animations (Bodymovin/SVG animations, though I suspect that fact is irrelevant).
So, the following works fine:
document.getElementById('animationDiv').addEventListener('mouseenter', function(){
animation.play();
})
(The HTML couldn't be simpler: The relevant part is just an empty div placeholder filled by script - i.e., <div id="animationDiv"></div>.
I can place that in the same file as the one that operationalizes the animation code, or I can place it in a separate "trigger" file, with both files (and other others necessary to processing) loaded in the site footer.
The problem arises when I need to be able to set triggers for any of multiple similar animations that may or may not appear on a given page.
If only one of two animatable elements are present on a page, then one of two sets of triggers will throw an error. If the first of two such triggers is not present, then the second one will not be processed, meaning that the animation will fail. Or at least that's what it looks like to me is happening.
So, just to be clear, if I add the following two triggers for the same page, and the first of the following two elements is present, then the animation will play on mouseenter. If only the second is present, its animation won't be triggered, apparently because of the error thrown on the first.
document.getElementById('firstAnimationDiv').addEventListener('mouseenter', function(){
firstAnimation.play();
})
document.getElementById('secondAnimationDiv').addEventListener('mouseenter', function(){
secondAnimation.play();
})
At present I can work around the problem by creating multiple trigger files, one for each animation, and setting them to load only when I know that the animatable element will be present, but this approach would get increasingly inefficient when I am using multiple animations per page, on pages whose content may be altered.
I've looked at try/catch approaches and also at event delegation approaches, but so far they seem a bit complicated for handling this simple problem, if appropriate at all.
Is there an efficient and flexible standard method for preventing or properly handling an error for an element not found, in such a way that subsequent functions can still be processed? Or am I missing something else or somehow misreading the error and the function failure I've been encountering?
WHY I PICKED THE ANSWER THAT I DID (PLUS WORKING CODE)
I was easily able to make the simple, directly responsive answer by Baoo work.
I was unable to make the answers below by Patrick Roberts and Crazy Train work, though no doubt my undeveloped js skills are entirely at fault. When I have the time, or when the issue next comes up for me in a more complex implementation (possibly soon!), I'll take another look at their solutions, and see if I can either make them work or if I can formulate a better question with fully fledged coding examples to be worked through.
Finally, just to make things clear for people who might be looking for an answer on Bodymovin animations, and whose js is even weaker than mine, the following is working code, all added to the same single file in which a larger set of Bodymovin animations are constructed, relieving me of any need to create separate trigger files, and preventing TypeErrors and impaired functionality.
//There are three "lets_talk" animations that can play - "home," "snug," and "fixed"
//and three types of buttons needing enter and leave play and stop triggers
let home = document.getElementById('myBtn_bm_home');
if (home) home.addEventListener('mouseenter', function() {
lets_talk_home.play();
});
if (home) home.addEventListener('mouseleave', function() {
lets_talk_home.stop();
});
let snug = document.getElementById('myBtn_bm_snug');
if (snug) snug.addEventListener('mouseenter', function() {
lets_talk_snug.play();
});
if (snug) snug.addEventListener('mouseleave', function() {
lets_talk_snug.stop();
});
let fixed = document.getElementById('myBtn_bm_fixed');
if (fixed) fixed.addEventListener('mouseenter', function() {
lets_talk_fixed.play();
});
if (fixed) fixed.addEventListener('mouseleave', function() {
lets_talk_fixed.stop();
});
At typical piece of underlying HTML (it's generated by a PHP function taking into account other conditions, so not identical for each button), looks like this at the moment - although I'll be paring away the data-attribute and class, since I'm not currently using either. I provide it on the off-chance that someone sees something significant or useful there.
<div id="letsTalk" class="lets-talk">
<a id="myBtn" href="#"><!-- a default-prevented link to a pop-up modal -->
<div class="bm-button" id="myBtn_bm_snug" data-animation="snug"></div><!-- "snug" (vs "fixed" or "home" is in both instances added by PHP -->
</a>
</div>
Obviously, a more parsimonious and flexible answer could be - and probably should be - written. On that note, correctly combining both the play and stop listeners within a single conditional would be an obvious first step, but I'm too much of a js plodder even to get that right on a first or second try. Maybe later/next time!
Thanks again to everyone who provided an answer. I won't ask you to try to squeeze the working solution into your suggested framework - but I won't ask you not to either...
Just write your code so that it won't throw an error if the element isn't present, by simply checking if the element exists.
let first = document.getElementById('firstAnimationDiv');
if (first) first.addEventListener('mouseenter', function() {firstAnimation.play();});
You could approach this slightly differently using delegated event handling. mouseover, unlike mouseenter, bubbles to its ancestor elements, so you could add a single event listener to an ancestor element where every #animationDiv is contained, and switch on event.target.id to call the correct play() method:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
switch (event.target.id) {
case 'firstAnimationDiv':
return firstAnimation.play();
case 'secondAnimationDiv':
return secondAnimation.play();
// and so on
}
});
You could also avoid using id and use a more semantically correct attribute like data-animation as a compromise between this approach and #CrazyTrain's:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
// assuming <div data-animation="...">
// instead of <div id="...">
switch (event.target.dataset.animation) {
case 'first':
return firstAnimation.play();
case 'second':
return secondAnimation.play();
// and so on
}
});
First, refactor your HTML to add a common class to all of the placeholder divs instead of using unique IDs. Also add a data-animation attribute to reference the desired animation.
<div class="animation" data-animation="first"></div>
<div class="animation" data-animation="second"></div>
The data- attribute should have a value that targets the appropriate animation.
(As #PatrickRobers noted, the DOM selection can be based on the data-animation attribute, so the class isn't really needed.)
Since your animations are held as global variables, you can use the value of data-animation to look up that variable. However, it would be better if they weren't global, but were rather in a common object.
const animations = {
first: null, // your first animation
second: null, // your second animation
};
Then select the placeholder elements by class, and use the data attribute to see if the animation exists, and if so, play it.
const divs = document.querySelectorAll("div.animation");
divs.forEach(div => {
const anim = animations[div.dataset.animation];
if (anim) {
anim.play(); // Found the animation for this div, so play it
}
});
This way you're guaranteed only to work with placeholder divs that exist and animations that exist.
(And as noted above, selection using the data attribute can be done const divs = document.querySelectorAll("div[data-animation]"); so the class becomes unnecessary.)

Span element not updating correctly

Update 3 - 10/09/2013: Just tested this with Version 29.0.1547.66 m and the problem still persists. If anyone else can test this out and let me know the results that would be great. You need an inline element such as a span with some text in, have it relatively positioned and moved by however many pixels you want from left and top. Then set up some jScript to change the current inner html of the element to something else and you should see it remain the same in the viewport but change correctly in the DOM.
Update 2: After a bit more testing the problem seems to occur on elements that are inline such as span, or have CSS that makes them inline, but that are also relatively positioned, it seems to be this combination that is causing the issue. After posting the bug on Chromium it has been flagged as a cr-Blink-Rendering issue which looks to be the engine that renders the DOM in the broswer viewport. I am using Version 29.0.1547.57 (the current version ends .66 but mine has not updated due to an error). So if you're on the latest version this issue may no longer be there.
Update: On further investigation I think the problem is with the latest Chrome build Version 29.0.1547.57 m As I tested the element in an inline fashion in IE9 and Firefox 21 and it worked fine. I have filed a bug report for this on chromium
I'm having a problem (that I have not been able to recreate with jsfiddle) where I perform an ajax request, obtain some values and place them within span elements that exist within my page.
I have a very odd problem where the ajax request is working and bringing the values back. The values are being inserted into the span elements via jQuerys .html' method and when I check the DOM using Chrome developer tools I can see the new value in the span.
However, what I see on the page doesn't reflect this, it still shows the old value. Yet if I attempt to highlight the value, it instantly changes to the correct value (the value that is showing in the DOM).
I have even tried to update the spans value before the ajax call (as the value I am using is being obtained from jQuery UI's slider widget) but this still yields the same results.
Has anyone else come across this?
EDIT: Here is some of the code
HTML
<div id="NewLoanSliderAmount" class="NewLoanSliderRules"></div>
<span id="NewLoanSliderAmountDisplay" class="NewLoanDisplay">£600</span>
The slider code. This is the version where I attempt to update it directly from the slider value
$("#NewLoanSliderAmount").slider({
value: amount,
min: 300,
max: amount,
step: 100,
change: function (event, ui) {
$("#NewLoanSliderAmountDisplay").html("£" + ui.value);
window.CkSpace.GetLoanValues();
}
});
Here is the ajax code:
(function (CkSpace, $, undefined) {
CkSpace.GetLoanValues = function () {
var url = "/Home/UpdateAPR";
$.get(url, { Amount: $("#NewLoanSliderAmount").slider("value"), Length: $("#NewLoanSliderLength").slider("value") }, function (data) {
$("#NewLoanAmount").html("£"+data.LoanAdvance);
$("#NewLoanLength").html(data.LoanTerm);
$("#NewLoanMonthlyCost").html("£"+data.LoanInstalment);
$("#NewLoanTotal").html(data.LoanGrossRepyable);
$("#NewLoanAPR").html(data.LoanAPR+"%");
$("#NewLoanSliderAmountDisplay").html("£" + data.LoanAdvance);
});
}
} (window.CkSpace = window.CkSpace || {}, jQuery));
EDIT 2:
Another thing to note is that if i set a break point on the span being populated and step through it, it updates perfectly every time in Chrome developer tools
I've figured out what was causing the problem although I don't know WHY it is causing the problem.
First off I tried changing the span to a div and adding display:inline to the css. It still didn't work.
I then removed the inline display and all of a sudden, as a block level element it works.
If anyone knows more about why this is and can elaborate then please do!
EDIT: On further investigation I think the problem is with the latest Chrome build Version 29.0.1547.57 m As I tested the element in an inline fashion in IE9 and Firefox 21 and it worked fine. I think it's time to file a bug report!

Is it possible to make a change with jQuery and then immediately reverse that change?

I have a pretty specific scenario where I would like to select all elements with jQuery, make a CSS change, save the elements, then reverse the change I made.
The Goal
I created a jQuery plugin called jQuery.sendFeedback. This plugin allows the user to highlight areas of the screen, as shown in this demo. When they submit their feedback the plugin grabs all the HTML on the page and dumps it into a callback function. Like so:
$('*').each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
var feedbackInformation = {
subject: $feedbackSubject.val(),
details: $feedbackDetails.val(),
html: '<html>' + $('html').html() + '</html>'
};
if (settings.feedbackSent)
settings.feedbackSent(feedbackInformation);
The callback function accepts this feedback information and makes an AJAX call to store the page HTML on the server (this HTML includes the red box highlights the user drew on the screen). When someone from tech support needs to view the user's "screen shot" they navigate to a page that serves up the stored HTML so the developer can see where the user drew their highlights on the screen.
My original problem was that different screen resolutions made the elements different sizes and the red highlights would highlight the wrong areas as the screen changed. This was fixed pretty easily by selecting all elements on the page and manually setting their height and width to their current height and width when the user takes the snap shot. This makes all the element sizes static, which is perfect.
$('*').each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
The Problem
The issue with this is that when the plugin is done transmitting this HTML the page currently being viewed now has static heights and widths on every element. This prevents dropdown menus and some other things from operating as they should. I cannot think of an easy way to reverse the change I made to the DOM without refreshing the page (which may very well end up being my only option). I'd prefer not to refresh the page.
Attempted Solution
What I need is a way to manipulate the HTML that I'm sending to the server, but not the DOM. I tried to change the above code to pull out the HTML first, then do the operation on the string containing the HTML (thus not affecting the DOM), but I'm not quite sure what I'm doing here.
var html = '<html>' + $('html').html() + '</html>';
$('*', html).each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
This did not work. So either I need to be able to manipulate the string of HTML or I need to be able to manipulate the DOM and undo the manipulation afterward. I'm not quite sure what to do here.
Update
I employed the solution that I posted below it is working beautifully now. Now I am wondering if there is a way to statically write all the css for each element to the element, eliminating the need for style sheets to be referenced.
I think you are mostly on the right track by trying to make the modifications to the HTML as a string rather than on the current page for the user.
If you check this post, you might also want to follow the recommendation of creating a temporary <div> on the page, cloning your intended content to the new <div> ensuring it is invisible using "display:none." By also putting a custom Id on the new <div> you can safely apply your static sizing CSS to those elements using more careful selectors. Once you have sent the content to the server, you can blow away the new <div> completely.
Maybe?
After much pain and suffering I figured a crude but effective method for reverting my modifications to the DOM. Though I hadn't gotten around to trying #fdfrye's suggestion of cloning, I will be trying that next to see if there is a mroe elegant solution. In the meantime, here is the new code in case anyone else can benefit from it:
$('*').each(function () {
if ($(this).attr('style'))
$(this).data('oldStyle', $(this).attr('style'));
else
$(this).data('oldStyle', 'none');
$(this).width($(this).width());
$(this).height($(this).height());
});
var html = '<html>' + $('html').html() + '</html>';
$('*').each(function () {
if ($(this).data('oldStyle') != 'none')
$(this).attr('style', $(this).data('oldStyle'));
else
$(this).removeAttr('style');
});
When I'm looping through every element and modifying the css, I log the original value onto the element as data. After I assign the DOM HTML to a variable I then loop through all elements again and restore the style attribute to its original value. If there was no style attribute then I log 'none' to the element data and then remove the style attribute entirely when looping through again.
This is more performance heavy than I wish it was since it loops through all elements twice; it takes a few seconds to finish. Not horrible but it seems like a little much for such a small task. Anyway, it works. I get a string with fixed-sized HTML elements and the DOM goes back to normal as if the plugin never touched it.

JQuery: Why is hoverIntent not a function here?

I'm modifying some code from a question asked a few months ago, and I keep getting stymied. The bottom line is, I hover over Anchors, which is meant to fade in corresponding divs and also apply a "highlight" class to the Anchor. I can use base JQuery and get "OK" results, but mouse events are making the user experience less than smooth.
I load JQuery 1.3 via Google APIs.
And it seems to work. I can use the built in hover() or mouseover(), and fadeIn() is intact... no JavaScript errors, etc. So, JQuery itself is clearly "loaded". But I was facing a problem that it seemed everyone was recommending hoverIntent to solve.
After loading JQuery, I load the hoverIntent JavaScript. I've triple-checked the path, and even dummy-proofed the path. I just don't see any reasonable way it can be a question of path.
Once the external javascripts are (allegedly) loaded in, I continue with my page's script:
var $old=null;
$(function () {
$("#rollover a").hoverIntent(doSwitch,doNothing)
});
function doNothing() {};
function doSwitch() {
var $this = $(this);
var $index = $this.attr("id").replace(/switch/, ""); //extract the index number of the ID by subtracting the text "switch" from its name
if($old!=null) $old.removeClass("highlight"); //remove the highlight class from the old (previous) switch before adding that class to the next
$this.addClass("highlight"); //adds the class "highlight" to the current switch div
$("#panels div").hide(); //hide the divs inside panels
$("#panel" + $index).fadeIn(300); //show the panel div "panel + number" -- so if switch2 is used, panel2 will be shown
$old = $this; //declare that the current switch div is now "old". When the function is called again, the old highlight can be removed.
};
I get the error:
Error: $("#rollover a").hoverIntent is not a function
If I change to a known-working function like hover (just change ".hoverIntent" to ".hover") it "works" again. I'm sure this is a basic question but I'm a total hack when it comes to this (as you can see by my code).
Now, for all appearances, it SEEMS like either the path is wrong (I've zillion-checked and even put it on an external site with an HTTP link that I double-checked; it's not wrong), or the .js doesn't declare the function. If it's the latter, I must be missing a few lines of code to make the function available, but I couldn't find anything on the author's site. In his source code he uses a $(document).ready, which I also tried to emulate, but maybe I did that wrong, too.
Again, the weird bit is that .hover works fine, .hoverIntent doesn't. I can't figure out why it's not considered a function.
Trying to avoid missing anything... let's see... there are no other JavaScripts being called. This post contains all the Javascript the page uses... I tried doing it as per the author's var config example (hoverIntent is still not a function).
I get the itching feeling I'm just missing one line to declare the function, but I can't for the life of me figure out what it is, or why it's not already declared in the external .js file. Thanks for any insight!
Greg
Update:
The weirdest thing, since I'm on it... and actually, if this gets solved, I might not need hoverIntent solved:
I add an alert to the "doNothing" function and revert back to plain old .hover, just to see what's going on. For 2 of my 5 Anchors, as soon as I hover, doNothing() gets called and I see the alert. For the other 3, doNothing() correctly does NOT get called until mouseout. As you can see, the same function should apply for any Anchor inside of "rollover" div. I don't know why it's being particular.
But:
If I change fadeIn to another effect like slideDown, doNothing() correctly does NOT get called until mouseout.
when using fadeIn, doNothing() doesn't get called in Opera, but seems to get called in pretty much all other browsers.
Is it possible that fadeIn itself is buggy, or is it just that I need to pass it an appropriate callback? I don't know what that callback would be, if so.
Cheers for your long attention spans...
Greg
Hope I didn't waste too many people's time...
As it turns out, the second problem was 2 feet from the screen, too. I suspected it would have to do with the HTML/CSS because it was odd that only 2 out of 5 elements exhibited strange behaviour.
So, checked my code, dug out our friend FireBug, and discovered that I was hovering over another div that overlapped my rollover div. Reason being? In the CSS I had called it .panels instead of .panel, and the classname is .panel. So, it used defaults for the div... ie. 100% width...
Question is answered... "Be more careful"
Matt and Mak forced me to umpteen-check my code and sure enough I reloaded JQuery after loading another plugin and inserting my own code. Since hoverIntent modifies JQuery's hover() in order to work, re-loading JQuery mucked it up.
That solved, logic dictated I re-examine my HTML/CSS in order to investigate my fadeIn() weirdness... and sure enough, I had a typo in a class which caused some havoc.
Dumb dumb dumb... But now I can sleep.

jquery ui.slider: add click event to display/hide handle

I am completely new to javascript and jquery. My programming knowledge is... nonexistent, I just started some days ago with some simple tasks like replacing a CSS class or toggling a div. So I want to apologize if I'm treading on someones toes by asking newbie-questions in here. But I hope that someone can help me and solve my problem.
I need to implement some sort of visual analog scale for a survey; ui.slider is perfect for that one. But I need the handle to be hidden by default. When the user clicks on the slider, the handle shall appear in the proper position. That should be fairly simple - at least I hope so - by just hiding the handle with CSS and changing it by a click event on the slider.
I use the following piece of code to wrap a normal div (a div is needed in my understanding to apply the jquery slider.js) to my input elements (they should be - at least visually - replaced by the slider) and pass the value of the slider to the input elements (needed for passing the values to a form). that works properly. (I do that instead of just putting a div in my DOM by default because I cannot influence some PHP scripts that will generate form elements of the survey and so on)
$(function () {
$.each($('.slider'),
function () {
obj = $(this);
obj.wrap('<div></div>');
obj.parent().slider({
change: function (event, ui) {
$('input', this).val(ui.value);
}
});
});
});
Hiding the slider-handle can be done by CSS as described above by changing style properties of a.ui-slider-handle. but when I add a normal click event to the slider (.ui-slider) that changes CSS properties of the handle, nothing happens. As far as my basic knowledge goes it should have something to do with the click event not working on generated DOM elements. Am I right with that one? And if yes: how can I solve this problem? Could someone provide me a piece of code for my function and explain it so I might comprehend what's exactly going on?
I read a tutorial about events on learningjquery.com but I have not made enough progresses the last few days since I started working with JS/jquery to comprehend the steps and translate it into my example/problem. And I am running out of time (I need this for a survey I have to make asap, that's why I hope someone could give me a hint so I can solve this little issue somehow).
Any reason you can't just include the show on the change event rather than a click? It's a bit cleaner code-wise rather than including a whole new event.
$(function() {
$('.slider').wrap('<div></div>').parent().slider({
change: function(event, ui) {
$('input', this).val(ui.value);
$('.ui-slider-handle').show();
}
});
});
Also, there was a bit of redundancy in the code - most jQuery functions return the object itself, so you can chain them. And you don't need that each function, since most jQuery functions also, when applied to a collection, run on all of them :)

Categories