views:

47

answers:

2

I have a pretty simple HTML form where users can enter in information about a person. Below that form is a button which allows them to 'add more'. When clicked, the 'person' form is copied and appended to the page.

The way I used to do this was to take my HTML file, copy out the relevant section (the part that gets 'added more') and then save it into a variable in the Javascript. This became rather annoying when I had to make changes to the form as I would then have to make the same changes to the Javascript variable.

My new method is to create the variable dynamically in Javascript. When the page loads, I use jQuery to grab out the 'add more' part of the code and cache the HTML into a variable. Then when the 'add more' button is clicked, I append that cached HTML to the page.

The problem is with form inputs. The server-side code autofills the form with the user's data from the database. I want to cache that HTML data with no form inputs...

My current function looks like this:

function getHTML($obj, clean)
{
    if (clean)
    {
        var $html = $obj.clone();

        $html.find('input').each(function() { $(this)[0].value = ''; });
    }
    else
    {
        var $html = $obj;
    }

    var html = $html.wrap('<div></div>').parent()[0].innerHTML;
    $html.unwrap();

    return html;
}

It doesn't work. I'm also unsure if this is the best approach to solving the problem.

Any ideas?

+1  A: 

I don't know why this wouldn't work. I can't see how the function is being called, or what is being passed to it.

I guess one thing I'd do differently would be to create a .clone() whether or not you're "cleaning" the inputs. Then you're not wrapping and unwrapping an element that is in the DOM. Just use the if() statement to decide whether or not to clean it.

Something like this:

function getHTML($obj, clean) {
    var $clone = $obj.clone();
    if (clean) {
        $clone.find('input').each(function() { this.value = ''; });
    }
    return $clone.wrap('<div></div>').parent()[0].innerHTML;
}

Or a little more jQuery and less code:

function getHTML($obj) {
    return $obj.clone().find('input').val('').end().wrap('<div/>').parent().html();
}

A little less efficient, but if it only runs once at the page load, then perhaps not a concern.

Or if it is going to be made into a jQuery object eventually anyway, why not just return that?

function getHTML($obj) {
    return $obj.clone().find('input').val('').end();
}

Now you've returned a cleaned clone of the original that is ready to be inserted whenever you want.


EDIT:

Can't figure out right now why we can't get a new string.

Here's a function that will return the DOM elements. Beyond that, I'm stumped!

function getHTML($obj, clean) {
    var $clone = $obj.clone();
    if (clean) {
        $clone.find('input').each(function() {
            this.value = '';
        });
    }
    return $clone.get();  // Return Array of DOM Elements
}

EDIT: Works now.

I ditched most of the jQuery, and used .setAttribute("value","") instead of this.value.

Give it a try:

function getHTML($obj, clean) {
    var clone = $obj[0].cloneNode(true);
    var inputs = clone.getElementsByTagName('input');
    console.log(inputs);
    for(var i = 0, len = inputs.length; i < len; i++) {
        inputs[i].setAttribute('value','');
    }
    return $('<div></div>').append(clone)[0].innerHTML;
}
patrick dw
I tried your first function and it's not clearing the inputs - I can't see why... I'm calling the function with `document.write(getHTML($('fieldset'), true));` as a test and it's writing it the fieldset correctly. But it's doing so with the input data still in it :/
stan
If I replace `this.value` with `$(this).css('border', '1px solid red')` it changes it - So I know the inputs are being affected. And if I append an `alert(this.value)` after the change it comes up blank. It seems the innerHTML/.html() methods are returning the _actual_ HTML instead of the current DOM HTML
stan
@stan - That is strange. Seems to have something to do with the `.wrap()`, but not sure what yet. My last example (without `.wrap()`) does work. I'll keep tinkering to see what's going on. I have a feeling that `.parent()` is somehow bringing us back into the DOM instead of the clone. Perhaps the `<div>` we wrapped is not traversable until it is part of the DOM.
patrick dw
From my testing, it seems the problem is with writing back the innerHTML. See: http://pastebin.com/6tnHkMBa - The changes get made to the DOM but that data can't then be returned as a string. Which is rather annoying...
stan
@stan - Yeah, this is driving me a little nuts. I just don't understand why/how it is giving us the old elements again. Anyway, I'm going to update my answer that will return the Array of DOM elements instead of a String. Sorry I couldn't be more help.
patrick dw
@stan - I think I may know what it is. I assume that these elements are probably ending up in a documentFragment, which doesn't have `.innerHTML` like type 1 nodes do.
patrick dw
It is a bit of a head scratcher. How would I use that returned array of DOM elements to construct a string? Is that the purpose of it?
stan
@stan - No, it's more of a compromise. Do you need a string? The array of DOM elements can be passed into a new jQuery object. If you don't really need a string, then better may be to just return the jQuery object, and `.clone()` it again every time your user clicks the "Add More" button. You'll be cloning the empty set, so no need to "clean" it again.
patrick dw
Ah I see. I suppose I don't need a string. I can just use jQuery's append() to write the returned object to the page. Cheers for the help.
stan
@stan - You're welcome. And if you return the jQuery object, you can use `.appendTo()` to avoid creating another object. `$obj.appendTo('body')` or whatever. Best of luck!
patrick dw
@stan - OK, I've got something figured out. I removed most of the jQuery, and ultimately got it working when I used the native `.setAttribute("value","")` to update the value. I'll post this solution at the bottom.
patrick dw
A: 

I would wrap the part of the form that needs to be cloned in a <fieldset>:

<form id="my_form">
    <fieldset id="clone_1">
        <input name="field_1_1">
        <input name="field_2_1">
        <input name="field_3_1">
    </fieldset>
</form>
<a href="#" id="fieldset_clone">Add one more</a>

Then for the jQuery script:

$("#fieldset_clone").click(function(event) {
    // Get the number of current clones and set the new count ...
    var cloneCount = parseInt($("fieldset[id^=clone_]").size());
    var newCloneCount = cloneCount++;

    // ... then create new clone based on the first fieldset ...
    var newClone = $("#clone_1").clone();

    // .. and do the cleanup, make sure it has
    // unique IDs and name for server-side parsing
    newClone.attr('id', 'clone_' + newCloneCount);
    newClone.find("input[id^=clone_]").each(function() {
        $(this).val('').attr('name', ($(this).attr('name').substr(0,7)) + newCloneCount);
    });

    // .. and finally insert it after the last fieldset
    newClone.insertAfter("#clone_" + cloneCount);
    event.preventDefault();
});

This would not only clone and clean the set of input fields, but it would also set new ID's and names so once the form is posted, their values would not be overwritten by the last set.

Also, in case you want to add the option of removing sets as well (one might add too many by mistake, or whatever other reason), having them wrapped in a <fieldset> that has an unique ID will help in accessing it and doing a .remove() on it.

Hope this helps.

FreekOne
I was trying to come up with a nice way of incrementing the input names so thanks for that.
stan
Glad to be of help ! :)
FreekOne