tags:

views:

66

answers:

6

Hi, have a function in jquery to automatically create the tab elements for a product in ecommerce system.

function structureTabs()
{
    $('#tabs').prepend('<ul id="tabbtns"></ul>');

    $('#tabs h2').each(function(index){

        if(index > 0)
        {
            $(this).before('</div><div id="tabs-'+(index+1)+'">');
        }
        else
        {
            $(this).before('<div id="tabs-'+(index+1)+'" class="jackie">');
        }

        var title = $(this).text();

        $('#tabbtns').append('<li><a href="#tabs-' + (index+1) + '">' + title + '</a></li>');

    });

    $('#tabs').append('</div>');
}

You cannot put unclosed tabs into .before()??

Basically the content this is trying to wrap around is:

<div id="tabs">
<h2>Details</h2>
details text...
<h2>tech specs</h2>
tech spec text...
<h2>Video Sample</h2>
video embed url
</div>

The client of the eccomerce store can't edit html, so need to build the tabs automatically in .js....

+4  A: 

jQuery works with elements, not markup. Before, after and the rest of the dom manipulation functions as well as the $/jQuery function all work with elements. The only function which works with markup is html.

To do what you want you either have to rethink your logic, or modify the markup via the html function.

balupton
I'd +1 this because its accurate, however I decided not to vote because it doesn't provide any solution, just better describes his problem. Perhaps it should of been a comment? It's a shame that you don't offer more details on the "rethink" portion, or an example of how to use the `html()` function to do this correctly. This answer is in effect, not a solution that can help him.
gnarf
Thanks, Generally I feel a push in the right direction is better than being handed the solution - which unfortunately seems to be the way SO is... neutering self discovery, and promoting zombies.
balupton
+4  A: 

You might want to take a look at the .wrap() and .wrapAll() functions. They allow you to wrap your element(s) in another. For instance:

 $(this).wrap('<div id="tabs-'+(index+1)+'" class="jackie">');

You can take a slightly different approach: Create your <div> element, insert it before the element, and then add the elements you want to it as children. This is a little complicated when dealing with text nodes (i.e. not wrapped in a tag) because jQuery will just ignore them for its traversal methods. The following code should work given your example code:

$("#tabs h2").each(function(index) {

    var $div = $('<div id="tabs-' + (index + 1) + '" class="jackie">');
    $div.insertBefore(this);
    // jQuery skips text nodes, so lets grab all the nodes until we find another h2
    // using DOM
    var nodes = [this];
    var node = this.nextSibling;
    while (node && !(node.tagName && node.tagName.toUpperCase() == 'H2')) {
        nodes.push(node);
        node = node.nextSibling;
    }
    $div.append(nodes);

});

Demoed on jsFiddle

If you don't need to worry about text nodes, it could be shortened quite a bit using the .nextUntil() function for traversing.

$("#tabs h2").each(function(index) {

    var $div = $('<div id="tabs-' + (index + 1) + '" class="jackie">');
    // put the div before the <h2>
    $div.insertBefore(this);
    // append the <h2> and all the siblings forward until you find another <h2>
    $div.append(this, $(this).nextUntil('h2'));

});
gnarf
I think this fits what Pete is looking to do.
yuval
+1 You beat me to it.
JungleFreak
Yes I need to wrap the <h2>... but i also need to wrap all the content below the <h2> before I get to the next <h2> in the list... any ideas?
Pete Hawkins
@Pete Hawkins - I was working on that... This is WAY complicated considering jQuery basically ignores "text nodes"... if that content between the `<h2>`'s was wrapped in another tag it would be WAY easier...
gnarf
Yeah I thought it would be complicated. Well it is wrapped in other tags, but it could be any ammount of <p>'s, <ul>'s or embed tags..
Pete Hawkins
So long as there are no orphaned text nodes, you could use `$div.append(this, $(this).nextUntil('h2'));`
gnarf
I used$(this).nextUntil('h2').wrap('<div id="tabs-'+(index+1)+'"></div>');however that wrapped every <p> in a separate div, how do i wrap it all in one... also it didn't include the <h2> in the wrap...
Pete Hawkins
@Pete Hawkins - The answer was already in my answer here, -- I have updated it to make it more obvious, but you could use `$('<div id="tabs-'+(index+1)+'"></div>').insertBefore(this).append(this, $(this).nextUntil('h2'));`
gnarf
A: 

Why not use the .wrap() function after you build the tabs?

$('#tabs').wrap('<ul id="tabbtns"></ul>');

That will wrap the <ul> tag around the html you built.

JungleFreak
Sorry, this is for shopify ecommerce system... its not the UL i need wrapped, the ul populates with the names and links of the tabs... that part works..
Pete Hawkins
It's that the client just ednters a product description, it has to be split into 4.. where each <h2> starts, to the start of the next <h2>
Pete Hawkins
A: 

I'd build up a jQuery object and append it to the element you need:

(function() {

    var h2 = ["Details", "tech specs", "Video sample"];
    var text = ["details text...", "tech spec text...", "video embed url"]

    $(function() {

          // create tabs jQuery object
        var $tabs = $("<div/>").attr("id","tabs");

          // loop through all h2 contents
        $.each(h2, function(index, value) {

              // Add h2 tags
            $tabs.append($("<h2/>").html(value));

              // Add text after h2 tag
            $tabs.append(text[index]);        
        });

          // Add tabs to the DOM
        $("body").append($tabs);
    });
})();
Peter Ajtai
Might want to put a `var` ahead of `$h2` and `$text` in that global scope... Also, why not define them in the doc ready so it doesn't pollute global scope... And also, a "naming convention" generally adhered to in jQuery code, don't use the `$` prefix on a variable unless its a jQuery object.
gnarf
@gnarf - Thanks for catching the var slip up. I didn't realize that was the convention, I just use the `$` when I have a variable that I use with jQuery... I'll edit.
Peter Ajtai
@Peter - I'm not sure its a convention, its just a pattern I see a lot, and try to stick to... For instance, in your code, the `myH2` and `tabs` could be `$tabs` and `$h2` because they are jQuery sets... Makes naming make a little more sense IMO
gnarf
@gnarf - Definitely seems like a good idea. I haven't been paying much attention to when I use `$` and when I don't. Only using it if the var is a jQuery object makes sense. ---- I just got rid of the `$h2` jquery set...
Peter Ajtai
@gnarf - Added the arrays to an anonymous function outside doc ready... Probably a smidgen faster.
Peter Ajtai
+1  A: 

So to sum up all the other answers, I think the final script you are looking for would be something like this:

$("#tabs h2").each(function(i) {
    $(this).nextUntil("h2").andSelf()
        .wrapAll('<div id="tab-'+ i +'"></div>');
});

$("#tabs").before('<ul id="menu"></ul>')
$("#tabs > div > h2").each(function(i) {
    $("#menu")
        .append('<li><a href="#tab-'+ i +'">'+$(this).text()+'</a></li>');
});

There is just one little problem, though. It won't work if you details text isn't inside some html element. jQuery's next or nextUntil function doesn't recognise plain text as an element.

skajfes
As far as im aware, shopify wraps all text inside paragraphs.
Pete Hawkins
Thanks a million, have used the following code..
Pete Hawkins
function structureTabs() { $('#tabs').prepend('<ul id="tabbtns"></ul>'); $('#tabs h2').each(function(index){ $(this).nextUntil('h2').andSelf().wrapAll('<div id="tabs-'+(index+1)+'"></div>'); var title = $(this).text(); $('#tabbtns').append('<li><a href="#tabs-' + (index+1) + '">' + title + '</a></li>'); }); $('#tabs div').remove('h2'); }
Pete Hawkins
If there is no orphaned text that the script provided in the answer should do the trick.
skajfes
+1 - Nice use of `.andSelf().wrapAll()`
gnarf
Thanks :) I like jQuery as it alows such simple (dare I say elegant?) solutions
skajfes
@Pete Hawkins - Just as a note - don't forget to mark one of these answers as the 'accepted answer' by clicking on the checkmark near the voting area! Welcome to stack overflow!
gnarf
A: 

function structureTabs() { $('#tabs').prepend('');

$('#tabs h2').each(function(index){ $(this).nextUntil('h2').andSelf().wrapAll(''); var title = $(this).text(); $('#tabbtns').append('

  • ' + title + '
  • '); });

    $('#tabs div').remove('h2'); }

    structureTabs();

    Pete Hawkins