Search code examples
javascriptjqueryjquery-uiclone

jQuery clone() copies data...sometimes...?


Using the sample below, I have a tr that I am duplicating. It contains a jQuery autocomplete. The first time it is cloned, the auto complete functionality does not work because the attached data("items") is null. The second time the Add button is clicked, the autocomplete works. Thereafter, clicking Add once again produces a non-functioning autocomplete.

Adding a breakpoint inside of the makeAutoComplete function shows that items is always null except for when clicking Add the second time!

Can anyone explain this strange behavior?

HTML/JS (Fiddle here: http://jsfiddle.net/SDvF4/12/)

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Test!</title>
        <style type="text/css">
            tr.Template
            {
                display: none;
            }
        </style>
        <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
        <script type="text/javascript" src="http://code.jquery.com/ui/1.8.18/jquery-ui.js"></script>
        <script type="text/javascript">
            $(document).ready(function ()
            {
                var textbox = $(".AutoComplete");

                makeAutoComplete(textbox);

                $("#addButton").click(function ()
                {
                    var attrRegex = /\d+/;
                    var template = $("tr.Template");
                    var newRow = template.clone(false);
                    var newRowIndex = (template.siblings().length + 1);

                    newRow.removeClass("Template");
                    newRow.find("*[id]").each(function ()
                    {
                        var element = $(this);

                        element.attr("id", element.attr("id").replace(attrRegex, newRowIndex));
                    });
                    newRow.find("*[name]").each(function ()
                    {
                        var element = $(this);

                        element.attr("name", element.attr("name").replace(attrRegex, newRowIndex));
                    });
                    newRow.insertBefore(template);
                    newRow.find(".AutoComplete").each(function ()
                    {
                        makeAutoComplete($(this));
                    });
                });
            });

            function makeAutoComplete(textbox)
            {
                var items = textbox.data("items");
                var test = textbox.data("test");

                if (items == null)
                {
                    if (test == "JSM")
                        alert("ERROR: data.items not copied but data.test was!");
                    else
                        alert("ERROR: data.items not copied nor was data.test!");
                }

                textbox.autocomplete(
                {
                    minLength: 0,
                    source: items
                });
            }
        </script>
    </head>
    <body>
        <table>
            <tbody>
                <tr class="Template">
                    <td>
                        <input id="test_0" name="test_0" class="AutoComplete" type="text"/>
                        <script type="text/javascript">
                            var testData = [{ label: "One", value: 1 }, { label: "Two", value: 2 }];

                            $("#test_0").data("items", testData);
                            $("#test_0").data("test", "JSM");
                        </script>
                    </td>
                </tr>
            </tbody>
        </table>
        <br/><br/>

        <button id="addButton">Add</button>​
    </body>
</html>

Solution

  • There were multiple issues I had to fix to get this to work.

    First was pointed out by @pimvdb - I wasn't IDing the elements correctly so the second new row had the same ID as the template row.

    Second, you can't simply call autocomplete on a widget that is already an autocomplete - first you have to destroy it:

    textbox.autocomplete("destroy");
    textbox.removeData("autocomplete");
    

    The 12th revision works correctly: http://jsfiddle.net/SDvF4/12/