views:

362

answers:

5

I start with this markup:

<div>
  <span>
    <label for="Item[0]">Item #1</label>
    <input type="text" value="" name="Item" id="Item[0]"/>
  </span>
</div>

On each button click I want to add another section exactly as the one above but with an incremented index.

<div>
  <span>
    <label for="Item[0]">Item #1</label>
    <input type="text" value="" name="Item" id="Item[0]"/>
  </span>
</div>
<div>
  <span>
    <label for="Item[1]">Item #2</label>
    <input type="text" value="" name="Item" id="Item[1]"/>
  </span>
</div>

I'm trying to use this javascript:

 $(document).ready(function(){

    var count = <%= Items.Count - 1 %>;

    $('#AddItem').click(function(e) {
        e.preventDefault();
        count++;

        var tb = $('#Item[0]').clone().attr('id', 'Item[' + count + ']');

        var label = document.createElement('label')
        label.setAttribute('For', 'Item[' + count + ']')

        $('#ItemFields').append(label);
        $('#ItemFields').append(tb);
    });
});

So a few issues:

Appending the label works, but my cloned textbox does not.

The label has no text. I can't seem to find the property for that. Can anyone tell me what it is?

I can't figure out how to wrap the label and textbox together in a div and span. If I try

$('#ItemFields').append('<div><span>' + label + tb + '</span></div>');

it outputs [object HTMLLabelElement] instead of the actual label. If I try to split them up into multiple append statements, it self terminates the div and span. I'm new to jQuery/Javascript, so I'm not quite sure what I'm doing wrong.

+2  A: 

I think I'd find a way to extract the next number from the existing ones in the mark up rather than relying on keeping variable in sync. Also, I think you simply need to nest your appends properly to get the containment that you want.

$(document).ready(function(){

    $('#AddItem').click(function(e) {
        var count = $('[name=Item]:last').attr('id').replace(/Item\[/,'').replace(/\]/,'');
        count = Number(count) + 1;

        var newItemID = 'Item[' + count + ']';

        var tb = $('#Item[0]').clone();
        tb.attr('id', newItemID );

        var label = $('<label for="' + newItemID + '">Item #' + count + '</label>');

        var container = $('<div></div>');

        $('<span></span>').append(label)
                          .append(tb)
                          .appendTo(container);

        $('#ItemFields').append(container);
        return false;
    });
});
tvanfosson
Thanks for the reply. I tried out your code but it generates the markup: <div><span/><label for="Name[1]">Name #1</label></div>It has a self terminating span and no textbox.
Brandon
Also, the counter is not incrementing. Should the first arguments in the .replace have quotes around it as well?
Brandon
I didn't think about it appending the label and input after the span. I've updated to separate the creation of the div and span and combine them in the proper order.
tvanfosson
No the first argument is a regular expression. Perhaps, it's not matching properly. Try removing the backslashes in front of the brackets. Bracket may only be special if there are two of them.
tvanfosson
Removing the backslashes make it NaN, and your solution works better, except there is still no textbox.
Brandon
Instead of doing a regex "find-and-replace", just do a split on square braces: `var split = $('[name=Item]:last').attr('id').split('['); split = split[0].split(']'); var count = parseInt(split[0], 10);` (warning: untested)
Matt Ball
BTW, always specify a radix when using parseInt, since Javascript interprets numbers that start with `0` as octal.
Matt Ball
I think the reason the count is wrong is because the textbox isn't being appened, so the count should never change. So really my only problem is that darn textbox now.
Brandon
I see -- you're using attr() on the Textbox -- that needs to be on a separate line so that you get the textbox back, then you can add the attribute and append it. As it is, it contains nothing since attr() doesn't return anything when setting attributes.
tvanfosson
+2  A: 

To start with "what's wrong," you're mixing native DOM calls, jQuery, and something else (I have no clue what <%= Items.Count - 1 %> is - PHP, maybe?). I'll leave that last one alone, as it sounds like it's working for you. Otherwise, if you're going to use jQuery, try and stick to using its methods as much as possible.

The "property" for the label's text is just the value, which is get/set by the jQuery value() method.

To append the label, I'd recommend you do it like:

$('#ItemFields').append('<label for=Item["' + count + ']">'+ '</label>')

I know append can take text, a jQuery object, or a DOM element, but I personally prefer to do it all with strings. If you'd rather not, you can do this:

$('#ItemFields').append($('<label></label>')
    .attr('for', 'Item["' + count + ']')
    .value('your label text here'));

and so on.

The reason that

$('#ItemFields').append('<div><span>' + label + tb + '</span></div>');

didn't give you what you wanted is that you are concatenating Javascript objects with strings, which (like Java) will automatically convert the objects to strings before concatenating.

I'm leaving the rest of this answer incomplete since tvanfosson's answer should cover it.

Matt Ball
Thanks for the answer. I didn't know I wasn't suppose to mix native DOM with Jquery, I was actually following this other SO question http://stackoverflow.com/questions/953415/how-to-dynamically-add-a-div-using-jquery as a guide, it used Jquery but created the elements in regular javascript.
Brandon
Oh, and the something else was actually C#.
Brandon
It's not really a "not supposed to" thing. Nothing will really break. However, it's just better practice to avoid mixing calls from 2 different APIs that provide overlapping functionality. And if you're already using jQuery, why not take it as far as you can? Re: the "something else" - take tvanfosson's advice and calculate the value of `count` using Javascript.
Matt Ball
+1  A: 

Use a function to build your html; if the structure is simple, assembling it as a string is easier:

$(document).ready(function(){

    var count = <%= Items.Count - 1 %>;

    $('#AddItem').click(function(e) {
        e.preventDefault();
        count++;

        $('#ItemFields').append(create_item(count));
    });


    function create_item(i) {
        return $(['<div><span>'
                ,'<label for="Item[', i, ']">Item #', i, '</label>'
                ,'<input type="text" value="" name="Item" id="Item[', i ,']"/>'
            ,'</span></div>'].join(''), document);
    };
});
Brandon
+1  A: 
var oldDiv = $('whatever');//Get div here somehow
var newHTML = $( oldDiv ).html();
newHTML.replace(/\[[0-9]+\]/g, '['+count+']');
newHTML.replace(/#[0-9]+/g, '#'+(count+1));    
var newDiv = $('<div></div>').html( newHTML );

This will create a new div, grab the HTML from oldDiv, and perform two regex replaces for the [index] and #number areas. I've not tested it, but it should work with few modifications.

Adam Bard
+1  A: 

Example cloning the whole block (but adding an item class) because I think it's more robust for the future (if you change the html for an item e.g.) and handling the label's text:

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>Demo</title>
  <script type="text/javascript" src="jquery-1.3.2.min.js"></script>
  <script type="text/javascript">
  $(document).ready(function(){
    $('#AddItem').click(function(e) {
      e.preventDefault();

      var count = $('.item').length;
      var id = 'Item[' + count + ']';
      var item = $('.item:first').clone();
      item.find('input:first').attr('id', id);
      item.find('label:first').attr('for', id)
          .html('Item #' + (1+count));
      item.appendTo('#ItemFields');
    });
  });
  </script>
</head>
<body>
  <input type="button" id="AddItem" value="Add" />
  <div id="ItemFields">
    <div class="item">
      <span>
        <label for="Item[0]">Item #1</label>
        <input type="text" value="" name="Item" id="Item[0]"/>
      </span>
    </div>
  </div>
</body>
</html>
RC
This works great, except the count isn't getting incremented.
Brandon
Oops, nevermind. Used an id instead of a class on the div. Works great now. Thanks.
Brandon
Yes, the count is the number of element with 'item' class.
RC
If you're going to use length, make sure you don't delete any of these (or only delete the last one). If any but the last get deleted and then you add more, you'll repeat numbers.
tvanfosson