views:

340

answers:

2

I want to use document.createDocumentFragment() to create an optimized collection of HTML elements that contain ".data" coming from jQuery (v 1.4.2), but I'm kind of stuck on how to get the data to surface from the HTML elements.

Here's my code:


var genres_html = document.createDocumentFragment();
$(xmlData).find('genres').each(function(i, node) {
    var genre = document.createElement('a');
    $(genre).addClass('button')
        .attr('href', 'javascript:void(0)')
        .html( $(node).find('genreName:first').text() )
        .data('genreData', { id: $(node).find('genreID:first').text() });
    genres_html.appendChild( genre.cloneNode(true) );
});

$('#list').html(genres_html);

// error: $('#list a:first').data('genreData') is null
alert($('#list a:first').data('genreData').id);

What am I doing wrong here? I suspect it's probably something with .cloneNode() not carrying over the data when the element is appended to the documentFragment. Sometimes there are tons of rows so I want to keep things pretty optimized, speed-wise.

Thanks!

A: 

You're running cloneNode on a jQuery object. You start off with native API, then convert it to a jQuery object, then switch back.

I suppose you could do:

genres_html.appendChild( genre.get(0).cloneNode(true) );

But then I suspect you would lose your data.


EDIT:

If you want jQuery, instead of creating a fragment, try creating an empty jQuery object, then pushing each genre into it:

var genres_html = $();
...
genres_html.push( genre );

EDIT:

Give this a try. I'm no DOM expert, but it may work for you.

var genres_html = document.createDocumentFragment();
$(xmlData).find('genres').each(function(i, node) {
    var genre = document.createElement('a');
    genre.setAttribute('class','button');
    genre.setAttribute('href', 'javascript:void(0)');
    var $node = $(node);
    genre.setAttribute('genreData', $node.find('genreID:first').text() );
    genre.innerHTML = $node.find('genreName:first').text();
    genres_html.appendChild( genre.cloneNode(true) );   // Not sure why you would need to make a clone??
});

var list = document.getElementById('list');
list.appendChild(genres_html);

// error: $('#list a:first').data('genreData') is null
alert($('#list a:first').attr('genreData'));

Let me know if it works.

EDIT: Changed my error with innerHTML

EDIT2: Using native innerHTML to append to #list

patrick dw
Yeah I think that's the main part I'm confused with, I don't know if I want to start with a jQuery object or not. I was looking at this slide, but it's in plain JS: http://ejohn.org/apps/workshop/adv-talk/#6 - what I need is to like basically convert that to a jQuery object that houses the element data.
taber
You could use the native `setAttribute` and `innerHTML` functions, but if you need to use jQuery's `data()`, then obviously you'll need a jQuery object. So the question is, *do you really need to use 'data'*? Maybe you could get by with setting a custom attribute. I'll update my answer with a possibility.
patrick dw
Thanks, yeah the problem is I can't append a "jQuerified" documentElement (that contains the data) to my documentFragment. Yeah I would like to use 'data'. :(
taber
Yeah, that's why you would either need to push the jQuerified elements to the (originally) empty jQuery object, or just not use jQuery's `data`. Since you're just storing text, you should be able to do so in an attribute. See my updated answer.
patrick dw
I'm pretty sure that would do the trick, but I guess I'm looking for a more "jQueryish" solution. :/
taber
You mean more jQueryish with `data`? Or with setting attributes? If you're looking to optimize performance, then the more native you go, the better. If your only optimization you wanted was the `documentFragment`, then go back to starting with an empty jQuery object, and pushing each `genre` into it. I'd be happy to help with a solution, but just need to know which direction you want.
patrick dw
I mean more jQuerish with creating the DOM elements inside the documentFragment, etc. sorry for not being very clear. :) I added a new answer with what I'm planning on using. Thanks again!
taber
A: 

Sorry I wasn't very clear - I want the performance gain of using documentFragments but the "cleanliness" of jQuery. :) Awesome though, I think I figured it out!


var genres_list = document.createDocumentFragment();
$(xmlData).find("genres").each(function(i, node) {
    genres_list.appendChild(
        $('<a></a>').addClass('button')
            .attr('href', 'javascript:void(0)')
            .html('Anchor Text Here')
            .data('genreData', {id: 2000})
            .get(0) // ah-ha!
        )
    );
});

$('#list').append(genres_list);

// alerts 2000
alert($('#list').find('a:first').data('genreData').id);

Thanks a ton for helping out! I think the problem was the missing .get(0) when appending it to the documentFragment. It loks like .get(0) also returns the data intact!

Performance gains are still TBD. I know jQuery 1.4 uses documentFragments but not exactly sure where/which methods. At least this works now though! :)

taber
If you're looking for performance gains, you would need to bypass the creation of a jQuery object in the first place, as well as using jQuery's API. All of that is just an abstraction of the native API, and will only slow down your code. If the performance is acceptable, then might as well use jQuery. If you need improvements, then go native. Using a `documentFragment` probably won't help any more than populating an empty jQuery object since jQuery likely just creates its own fragment.
patrick dw
If you want some really nice jQuery, do this `$('<a/>', {'class':'button','href':'javascript:void(0)','text':'Anchor text here'}).data('genreData', {id: 2000}).get(0)`
patrick dw
Good point! Ditching jQuery at this point is out of the question though. :D I put together a quick benchmark test concating numbers 0-1000 to the letter 'h' and appending them to a div ... 1) using my method above (jQuery with document.createDocumentFragment), 2) using jQuery.append in every iteration of the for loop, and 3) using document.createDocumentFragment without jQuery. Here's a sample of the results: 1) 922ms, 2) 1547ms, 3) 828ms. So for ~600 ms in savings I'm goin with the first option. :D Thanks for the nice new documentElement creation syntax too! 1.4 is good stuff.
taber