views:

35

answers:

1

Hi all, I have a UL on a page and on the click of a button I am appending an li and fading it in. This works fine for single clicks. However, when the button is clicked twice, the first animation immediately stops and the second completes.

I have setup a test page to demonstrate this: http://anttears.co.uk/test (the test page has been tested in FF only)

Putting a console.log in the setOpacity function for the value of elem seems to show the javascript working as expected - both li's reaching opacity=1. However, the first li seems to be a fragment dissasoiated with the actual DOM.

I am deliberately trying to get this working without the usual libraries, as a learning experience.

Any help greatfully appreciated... Ant

A: 
this.prepend = function(string, elem) {
    var content = elem.innerHTML;
    content = string + content;
    elem.innerHTML = content;
}

Never do this.

You're taking the DOM content of the elem (the growlList), serialising it to a new HTML string, prepending some HTML content, and parsing the joined HTML back into completely new DOM nodes. This loses all non-HTML-serialisable content, such as form field values, event handlers and JavaScript references. The animation still has a reference to the old HTMLElement node, which is no longer in the DOM, having been replaced by newly-parsed-from-content elements.

Indeed, you usually want to avoid generating HTML strings at all:

growl = '<li id="' + gid + '" class="' + className + ' growl" style="display: none"><span class="close" title="close">x</span><h2>' + heading + '</h2><div class="growlContent">' + content + '</div></li>',

Any unescaped HTML-special characters in that content and you've broken your markup at best. At worst, the data comes from a malicious user and you've given yourself an XSS security hole.

Instead, use DOM-style methods, eg:

var span= document.createElement('span');
span.className=span.title= 'close';
span.appendChild(document.createTextNode('x'));
var h2= document.createElement('h2');
h2.appendChild(document.createTextNode(heading));
var div= document.createElement('div');
div.className= 'growlContent';
div.appendChild(document.createTextNode(content))

var li= document.createElement('li');
li.id= gid;
li.className= className;
li.appendChild(span);
li.appendChild(h2);
li.appendChild(div);

gl.insertBefore(li, gl.firstChild);

This is quite wordy, but it's easy to write a helper function to cut down on typing, eg:

gl.insertBefore(el('li', {id: gid, className: className), [
    el('span', {className: 'close', title: 'close'}, 'x'),
    el('h2', {}, heading),
    el('div', {className: 'growlContent'}, content)
]), gl.firstChild);


// Element creation convenience function
//
function el(tag, attrs, content) {
    var el= document.createElement(tag);
    if (attrs!==undefined)
        for (var k in attrs)
            if (attrs.hasOwnProperty(k))
                el[k]= attrs[k];
    if (content!==undefined) {
        if (typeof(content)==='string')
            el.appendChild(document.createTextNode(content));
        else
            for (var i= 0; i<content.length; i++)
                el.appendChild(content[i]);
    }
    return el;
};
bobince
Hi Bobince, thanks for the answer, but I think you've missed the point a bit - I'm not using jQuery or any other library deliberately. As for global variables, I'm using comma's to separate the declarations, so they will be in scope. And the page is just a test page to demonstrate the issue, not the final methodology, also, the values are never stored in a db or actually posted, so even if it was final code the only person vulnerable would be the current user - kind of a suicide xss attack :). Any ideas on the actual problem though and how to fix it in a non jquery fashion?
Ant
Oops! Sorry, I saw “animate” and “prepend” and my mind instantly switches to jQuery mode. And I don't even *like* jQuery! That's the curse of this obsessed place! Updated to include plain-JS versions.
bobince
I see what you're doing now with the commas, but I wouldn't recommend it: it's so easy to quickly edit/rearrange lines and lose the `var`. For example in the page itself `header` and `content` are accidental globals, and since there are also elements with the same names as `id`​s, trying to assign to them will cause an exception in IE (which makes `id`​d elements visible as globals). If you use var-comma-over-more-than-one-line, the normal practice is to indent subsequent lines to show they're part of the same statement; personally I prefer separate `var`​s FWIW.
bobince
Thanks for getting back to me on this. Tried your way of appending and voila :) Many thanks, now to get the rest of it sorted.
Ant