1

I've asked this a couple times already, but I haven't been able to convey my question properly, so I figured I would try again:

I want to make a form. The structure will be:

<html>
<head>
<script type="text/javascript">

</script>
<style type="text/css">
ul {
list-style-type:none;
}
</style>
</head>

<body>

<input type="text" value="Menu 1 Name" />
<input type="button" value="Remove Menu" />
<ul>
<li><input type="text" value="Option 1" /><input type="button" value="Remove Option" /></li>
<li><input type="text" value="Option 2" /><input type="button" value="Remove Option" /></li>
<li><input type="button" value="Add Option" /></li>
</ul>
<input type="text" value="Menu 2 Name" />
<input type="button" value="Remove Menu" />
<ul>
<li><input type="text" value="Option 1" /><input type="button" value="Remove Option" /></li>
<li><input type="text" value="Option 2" /><input type="button" value="Remove Option" /></li>
<li><input type="button" value="Add Option" /></li>
</ul>
<input type="text" value="Menu 3 Name" />
<input type="button" value="Remove Menu" />
<ul>
<li><input type="text" value="Option 1" /><input type="button" value="Remove Option" /></li>
<li><input type="text" value="Option 2" /><input type="button" value="Remove Option" /></li>
<li><input type="button" value="Add Option" /></li>
</ul>
<input type="button" value="Add Menu" />
</body>
</html>

I need to be able to get the form data for the menu names and the menu options for however many menus and options there are. I was thinking about doing something along the lines of making a variable that is put into the id of the menu and another variable for the menu options and then doing a for loop that continues until it gets the information from all the forms.

The problem is that I want the menu ids and option ids to always be in order, no matter how many times they add or remove a menu. For example, let's say I have three options with ids of Menu1Option1, Menu1Option2, and Menu1Option3. If I remove the second option, it will show the first and third options. Since there are only two, I would want the third option's ID to become Menu1Option2. The only way I can think of to do this is to create a new variable for the amount of options every time they add a menu and then use the variable to determine the ids of the options and, when one is removed, re-id all the options after it based on the id of the option removed...

If you understand what I'm trying to do, can you tell me how to do it the right way please? I'm sure I'm quite off course here. I'm guessing that the better way would be to NOT id each menu and option but rather to make an array of them some how? It seems more suited to what I want, but I have no idea how to do it.

1
  • Also, you can edit your question. Don't post duplicate questions, just keep refining a single question until it is as clear as you can make it. Commented Jul 5, 2011 at 4:39

3 Answers 3

1

I would suggest the following:

  1. Create hidden elements on the page that hold template markup for the content you want to add dynamically:

    <div id="menuSectionTemplate" style="display:none;">
        <input type="text" value="MENU_NAME" />
        <input type="button" value="Remove Menu" onclick="removeMenu('MENU_ID');"/>
        <ul>
            OPTION_LIST
            <li><input type="button" value="Add Option" onclick="addOption('MENU_ID')" /></li>
        </ul>
    </div>
    <div id="menuListItemTemplate" style="display:none;">
        <li><input type="text" id="MenuMENU_IDOptionOPTION_ID" value="OPTION_VALUE" />
            <input type="button" value="Remove Option" onclick="removeOption('MENU_ID', 'OPTION_ID');" />
        </li>
    </div>
    
  2. Work out a data structure to represent your current interface state. Something like:

    window.menus = [{name: "Menu 1", options: 
                       [{value: "Option 1" }, 
                        {value: "Option 2" }
                       ]
                    },
                    {name: "Menu 2", options: 
                       [{value: "Option 2.1" }, 
                        {value: "Option 2.2" }
                       ]
                    }];
    
  3. Work out the logic to generate your interface based upon your backing data. Something roughly like:

    //draws the current state
    window.render = function() {
       var allMenus = "";
       var menuTemplate = document.getElementById("menuSectionTemplate").innerHTML;
       var listTemplate = document.getElementById("menuListItemTemplate").innerHTML;
       for (var index = 0; index < menus.length; index++) {
           var menuOptions = "";
           var menu = menus[index];
           for (var optionIndex = 0; optionIndex < menu.options.length; optionIndex++) {
               var option = menu.options[optionIndex];
               var optionMarkup = processTemplate(listTemplate, 
                                                  ["MENU_ID", "OPTION_ID", "OPTION_VALUE"],
                                                  [index, optionIndex, option.value]);
               menuOptions += optionMarkup;
           }
           var menuMarkup = processTemplate(menuTemplate, 
                                            ["MENU_ID", "MENU_NAME", "OPTION_LIST"], 
                                            [index, menu.name, menuOptions]);
           allMenus += menuMarkup;
       }
       var existingContainer = document.getElementById("menus");
       if (existingContainer) {
          document.body.removeChild(existingContainer);
       }
       existingContainer = document.createElement("div");
       existingContainer.id = "menus";
       existingContainer.innerHTML = allMenus;
       document.body.appendChild(existingContainer);
    };
    
    
    //utility methods
    window.processTemplate = function(template, targets, replacements) {
        var result = template;
        for (var index = 0; index < targets.length; index++) {
            result = replaceAllSubstrings(result, targets[index], replacements[index]);
        }
        return result;
    };
    
    window.replaceAllSubstrings = function(haystack, find, sub) {
        return haystack.split(find).join(sub);
    };
    
  4. Implement your add/remove handlers so that they modify the backing data appropriately and then redraw the interface, like so:

    window.removeOption = function(menuIndex, optionIndex) {
        menus[menuIndex].options.splice(optionIndex, 1);
        render();
    };
    
    window.removeMenu = function(menuIndex) {
        menus.splice(menuIndex, 1);
        render();
    };
    
    window.addMenu = function() {
        var menuName = prompt("Enter a menu name:", "Menu " + menus.length);
        menus.push({name: menuName, options:[]});
        render();
    };
    
    window.addOption = function(menuIndex) {
        var optionValue = prompt("Enter a default value:", "Option " + menus[menuIndex].options.length);
        menus[menuIndex].options.push({value: optionValue}); 
        render();
    };
    
  5. Profit!

Here is a working example: http://jsfiddle.net/cYtdN/2/

Sign up to request clarification or add additional context in comments.

1 Comment

God damn. You make it look so easy. I'm going over this now. Thanks so much man!!
0

If you don't care about the specific IDs, don't give them specific IDs. The DOM already keeps a tree of all the elements for you. You don't need to keep a separate array of menu items. Just let the DOM be your data structure and use the DOM API and/or jQuery to access what you want.

$('.menuItem').each(function (index, item) { //Do what you need here. });

See jQuery.each

2 Comments

Where did he say he is using jQuery? Or prototype? Or any other framework that thinks that naming a function $ is cute?
He didn't that's why I said "DOM API". Feel free to re-implement jquery to make your DOM operations portable.
0

Instead of dynamically changing ids. I think you can shift to another idea:

1) Assume you want to submit the result according to the menus/options, can you use wrapData() to load the information:

function wrapData(){
    var data = [];
    $('.menu').each(function(){
        var $t = $(this);
        var m = {'name': $t.val(), 'options': []};
        $t.nextAll('ul:eq(0)').find('[type=text]').each(function(){
            m.options.push({'name': $(this).val()});
        });
        data.push(m);
    });
    return data;
}

2) If you want to render the user interface from data, you can probably call loadData(), something like:

function loadData(data){
    for(var i = 0; i < data.length; i++){
        // generating menus
        for(var j = 0; j < data[i].options.length; j++){
            // generating options
        }
    }
    // finally update your user interface
}

Please check it out from http://jsfiddle.net/rd8Wp/3/

UPDATE

I have written another complete solution without jquery, but I accidentally close the broswer without saving. So I wrote this simpler solution using jquery. Indeed, using a framework, whatever it is, would help. You can see the code below is much clearer and also easier to maintain later.

html:

<input class='menu' type="text" value="Menu 1 Name" />
<input type="button" value="Remove Menu"/>
<ul>
    <li><input type="text" value="Option 1 1" /><input type="button" value="Remove Option"/></li>
    <li><input type="text" value="Option 2 1" /><input type="button" value="Remove Option"/></li>
    <li><input type="button" value="Add Option" /></li>
</ul>
<input type="button" value="Add Menu" /><input type="button" value="Wrap Data" />

js:

function wrapData(){
        var data = [];
        $('.menu').each(function(){
            var $t = $(this);
            var m = {'name': $t.val(), 'options': []};
            $t.nextAll('ul:eq(0)').find('[type=text]').each(function(){
                m.options.push({'name': $(this).val()});
            });
            data.push(m);
        });
        return data;
    }

$(document).ready(function(){
    $('[value="Remove Option"]').live('click', function(){
        $(this).closest('li').remove();
    });
    $('[value="Add Option"]').live('click', function(){
        $(this).closest('li')
            .before('<li><input type="text" value="Unnamed Option" /><input type="button" value="Remove Option"/></li>');
    });
    $('[value="Add Menu"]').live('click', function(){
        $(this).before('<input class="menu" type="text" value="Unnamed Menu" /> <input type="button" value="Remove Menu"/> <ul> <li><input type="button" value="Add Option" /></li> </ul>');
    });
    $('[value="Remove Menu"]').live('click', function(){
        var $t = $(this);
        $t.next().remove();
        $t.prev().remove();
        $t.remove();
    });
    $('[value="Wrap Data"]').live('click', function(){
        console.log(wrapData());
    });
})

Try it at http://jsfiddle.net/rd8Wp/7/

7 Comments

I think this solution is not answering your specific question directly, but it is an alternative way that you can consider about how your app works.
What is $? He didn't way he was using any sort of framework.
You're right. I said that's just an alternative solution for him to consider. Except for the framework, your idea is similar to mine, the only thing I don't quite agree with you is that every operation would trigger changing the data and rendering the UI. I think it is more efficient that the data will only be wrapped into an array when the user finally click submit.
This looks great, but I really don't understand. What does var $t do? Why the dollar sign? var m = {'name': $t.val(), 'options': []}; What type of value are you assigning to variable m? I've never seen a variable's value inside of curly brackets. This looks great, but I can't understand any of it.
1. I'm wondering if you have experience with jquery? 2.$ sign is merely my convention of naming a variable that is a jquery object. 3. The assignment means that m is an object, with two property called name and options. The first one equals to the value of the target input, the second one is an empty array. Please have a check @ json.org
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.