SitePoint Sponsor

User Tag List

Results 1 to 19 of 19
  1. #1
    SitePoint Addict whydna's Avatar
    Join Date
    Jun 2006
    Posts
    258
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Complex dynamic form, add/remove fields dynamically

    Hey guys,

    I am working on a dynamic form using JavaScript where the user can add/remove fields as required. Here is a small mock up of what I am trying to achieve: http://img176.imageshack.us/my.php?image=ssuc3.gif

    For the purpose of this question, you can ignore the cardio and only look at the weight/weight training aspect. As you can see, the user is:

    - Presented with a form where he can choose to add a weight (weight training exercise).
    - If he clicks add "weight" exercise, the form will be populated with a field allowing him to enter the name of the exercise (ie "benchpress"). He can also choose to click remove the exercise, which will remove it.
    - He can then click add set, which will populate a child field under the exercise allowing him to enter the details of 1 set of that exercise.

    The main problem I am running to is how to keep track of all these fields in a way that makes sense when I am processing the form. I am pulling my hair out trying to figure this out. I was going to use a counter (ie var numWeightEntries) to name the divs (div id="weight_1_", "weight_2", etc) but I found this to be a problem when there are childs to each div, and also due to the fact that they can remove fields (you run into the problem with it not being contiguous). I have also tried to structure it like below but still running into problems:

    Code HTML4Strict:
    <form>
     
    	Workout Name: <input type="text" name="workout_name" />
     
    	<div id="cardios_in_workout">
    		<div id="cardio_1">
     
    		</div>
    		<div id="cardio_2">
     
    		</div>
    	</div>
     
    	<div id="weight_trainings_in_workout">
     
    		<div class="weight_training">
     
    			Weight Training Name: <input type="text" name="weight_training_names[]"><a href="#" onClick="addSetToWeight(weight_1);">Add Set</a>
     
    			<div class="sets">
    				<div class="set">
    					Reps: <input type="text" name="weight_training_names[0][reps][]">
    					Weight: <input type="text" name="weight_training_names[0][weights][]">
                        <a href="#" onClick="removeSetFrom(??);">Remove Set</a>
    				</div>
    			</div>
     
    		</div>
     
    		<div class="weight_training">
     
    			Weight Training Name: <input type="text" name="weight_training_names[]"><a href="#" onClick="addSetToWeight(weight_1);">Add Set</a>
     
    			<div class="sets">
    				<div class="set">
    					Reps: <input type="text" name="weight_training_names[1][reps][]">
    					Weight: <input type="text" name="weight_training_names[1][weights][]">
                       <a href="#" onClick="removeSetFrom(??);">Remove Set</a>
    				</div>
    			</div>
     
    		</div>
     
    	</div>
     
    	<input type="submit" name="submit" value="Save workout">
    	<input type="button" name="cancel" value="Cancel">
     
    </form>

    Any advice on how to approach this would be much appreciated. Thanks!

  2. #2
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    You don't need a fancy schema for this. Each set can have a separate number for them. When the set has multiple names with the same name, you can access them from the server as if they were an array.

    Here is a working sample of the form sets, without identifiers or class names. The purpose being to develop a solution that doesn't rely on them.

    Note that all of the reps and weights in the first set will have an index number of 1, so if you add 4 sets you will have five inputs all called reps[1][] and"weights[1][]. This helps to group them together while allowing you to keep the individual sets separate.

    Code html4strict:
    <div>
    	Weight Training Name: <input type="text" name="weight_training_name"><a href="#" onClick="addSet(this);">Add Set</a>
    	<div>
    		<div>
    			Reps: <input type="text" name="reps[1][]">
    			Weight: <input type="text" name="weights[1][]">
    			<a href="#" onClick="removeSet(this);">Remove Set</a>
    		</div>
    	</div>
    </div>
    <div>
    	Weight Training Name: <input type="text" name="weight_training_name"><a href="#" onClick="addSet(this);">Add Set</a>
    	<div class="sets">
    		<div class="set">
    			Reps: <input type="text" name="reps[2][]">
    			Weight: <input type="text" name="weights[2][]">
    		   <a href="#" onClick="removeSet(this);">Remove Set</a>
    		</div>
    	</div>
    </div>

    The script to add and remove sets is fairly simple

    Code javascript:
    function addSet(el) {
    	var set = el.parentNode.getElementsByTagName('div')[0];
    	var num = getNumberOfExercises(set);
    	set.appendChild(createSet(num + 1));
    }
    function removeSet(el) {
    	el.parentNode.parentNode.removeChild(el.parentNode);
    }

    The addSet() function gets the number of existing sets with the following:

    Code javascript:
    function getNumberOfExercises(el) {
    	el = el.firstChild;
    	var num = 0;
    	while (el) {
    		if (el.nodeName === 'DIV') {
    			num += 1;
    		}
    		el = el.nextSibling;
    	}
    	return num;
    }

    And the sets themself are created with this:

    Code javascript:
    function createSet(num) {
    	var div = document.createElement('div');
    	var reps = document.createElement('input');
    	var weight = document.createElement('input');
    	var a = document.createElement('a');
    	reps.type = 'text';
    	reps.name = 'reps[' + num + '][]';
    	weight.type = 'text';
    	weight.name = 'weights[' + num + '][]';
    	a.href = '#';
    	a.onclick = function () {
    		removeSet(a);
    	}
    	div.appendChild(document.createTextNode('Reps: '));
    	div.appendChild(reps);
    	div.appendChild(document.createTextNode(' '));
    	div.appendChild(document.createTextNode('Weight: '));
    	div.appendChild(weight);
    	div.appendChild(document.createTextNode(' '));
    	a.appendChild(document.createTextNode('Remove Set'));
    	div.appendChild(a);
    	return div;
    }

    edit: field names updated to be accessible as arrays from php
    Last edited by paul_wilkins; Aug 22, 2008 at 15:32.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  3. #3
    SitePoint Addict whydna's Avatar
    Join Date
    Jun 2006
    Posts
    258
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    pmw, thanks for the help so far - very useful.

    However, I think your solution will have problems when sets are removed. The numbers will not be contiguous?

  4. #4
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    Here is how the elements will be named when the first lot contains 3 sets, and the second lot contains two sets

    weight_training_name = First lot
    - reps[1][] = 20
    - weight[1][] = 40
    - reps[1][] = 15
    - weight[1][] = 40
    - reps[1][] = 10
    - weight[1][] = 30
    weight_training_name = Second lot
    - reps[2][] = 20
    - weight[2][] = 40
    - reps[2][] = 15
    - weight[2][] = 40

    So when from the first lot you remove the second set, there are no contiguous issues to worry about.

    edit: field names updated to be accessible as arrays from php
    Last edited by paul_wilkins; Aug 22, 2008 at 14:54.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  5. #5
    SitePoint Addict whydna's Avatar
    Join Date
    Jun 2006
    Posts
    258
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I am actually talking about when you remove an entire exercise. You lose the contiguity of the reps/weight naming scheme.

    For example:

    function addSet(el) {
    var set = el.parentNode.getElementsByTagName('div')[0];
    var num = getNumberOfSets(set);
    set.appendChild(createSet(num + 1));
    }

    First off, I think for "var num=", you actually meant to get the number of exercises? Cause that is what the numbering is based off of (which weight training exercise group it belongs to?).

    So if you create WeightTraining_1, WeightTraning_2 and WeightTraining_3, and you delete the second one. If you create a fourth one, its numbering will be reps_4, weight_4 and you have contiguity issues with the #2.

    Let me know if I have overlooked something.

    thanks!

  6. #6
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    That's correct. The function should be called getNumberOfExercises()

    The situation with exercises being added and removed hasn't been covered yet, not in the code I posted nor anywhere else.

    I've dealt with the contiguous issue before in other projects, and there's a very simple solution that's very hard to break. Rename the fields when removal occurs.

    When whole exercises are removed, the script for doing that would be easily able to walk through the form and ensure that the index number for each exercise is as it should be.

    With four exercises, when the second one is removed the script would walk through the fields of each exercise renaming the names of the fields.

    The first exercise would have the sets renamed to reps[1][] and weight[1][], which wouldn't have much effect on that exercise as it's renaming them to what they already are.

    The second exercise though demonstrates the payoff. That would be renamed from reps[3][] and weight[3][] to reps[2][] and weight[2][]. And as the script walks through each field , the act of renaming the appropriate fields results in them all being automatically contiguous.

    The following is an outline of the solution.

    Code javascript:
    function removeExercise(el) {
        el.parentNode.removeChild(el);
        renumberSets();
    }
    function renumberSets() {
        var els = getEachExercise();
        var i;
        for (i = 0; i < els.length; i++) {
            setFieldNumbers(els[i], i + 1);
        }
    }

    edit: field names updated to be accessible as arrays from php
    Last edited by paul_wilkins; Aug 22, 2008 at 14:55.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  7. #7
    SitePoint Addict whydna's Avatar
    Join Date
    Jun 2006
    Posts
    258
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks, Ill give it a try and report back.

    BTW, the reps1, weights1 need to be reps1[] right? To be posted as an array?

  8. #8
    SitePoint Guru SSJ's Avatar
    Join Date
    Jan 2007
    Posts
    830
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yups reps1, weights1 need to be reps1[] and weights1[]

  9. #9
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2008
    Posts
    5,757
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    You would probably want them reps[1][] for easy collection in php.

  10. #10
    SitePoint Addict whydna's Avatar
    Join Date
    Jun 2006
    Posts
    258
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just an update guys - it works wonderfully, thanks for the help.

    Btw: I found it easier to use set[1][] rather then set1[] since it helps PHP group them after the form is posted, and you can just do a foreach statement rather then trying to generate the name of the variables (as crmalibu pointed out).

  11. #11
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    That's excellent. I'll go back and update the named elements in my code so that others who may copy/paste will have less issues to work through.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  12. #12
    SitePoint Addict whydna's Avatar
    Join Date
    Jun 2006
    Posts
    258
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Btw guys, can you compare div elements like this?
    Code JavaScript:
     
        if (parentDiv.getLastChild() == someDiv)

    And also btw, you cannot just use "getNumExercises()" to group the set with a workout exercise. Because you could add all the exercises first, and then go back and add sets, in which case all the sets will be associated with the last exercise added. So instead, you need to identify exactly which exercise's add set button was pressed.

  13. #13
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by whydna View Post
    Btw guys, can you compare div elements like this?
    Code JavaScript:
     
        if (parentDiv.getLastChild() == someDiv)
    Yes, that's a perfectly good way to compare elements.

    Quote Originally Posted by whydna View Post
    And also btw, you cannot just use "getNumExercises()" to group the set with a workout exercise. Because you could add all the exercises first, and then go back and add sets, in which case all the sets will be associated with the last exercise added. So instead, you need to identify exactly which exercise's add set button was pressed.
    So what should happen is the script should count through each of the <div class="sets"> elements until it reaches the one from which the addset event occured.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  14. #14
    SitePoint Member
    Join Date
    Oct 2009
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Post

    very good helping material. i try to add/Remove Exercises but this code not support, please help.

    i am new in JavaScript.

  15. #15
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    code not support? That's very close to "it doesn't work" which is something that tends to require more information so that further assistance can be given.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  16. #16
    SitePoint Member
    Join Date
    Oct 2009
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Post

    Sorry Dear, i do not have any experience in javascript that's why i was failed to apply this code in my project. kindly me in this regard.

    basically i want to add this div onclick

    <div>
    Weight Training Name: <input type="text" name="weight_training_name"><a href="#" onClick="addSet(this);">Add Set</a>
    <div>
    <div>
    Reps: <input type="text" name="reps[1][]">
    Weight: <input type="text" name="weights[1][]">
    <a href="#" onClick="removeSet(this);">Remove Set</a>
    </div>
    </div>
    </div>
    other code are fit..

  17. #17
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    If you put up a demo page, we can examine how the script interacts with your HTML code and most effectively provide you with the best action to take.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  18. #18
    SitePoint Member
    Join Date
    Oct 2009
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Post

    Thanks for your reply,

    i want to make page like add/remove attributes and there properties in joomla virtuemart see in attachment.

    Thanks Again,
    Attached Images Attached Images

  19. #19
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    Well we're very happy to help you resolve any difficulties that you have with your own coding efforts.

    If however you require someone to code it up for you, there is the Marketplace where you may engage the services of someone to do the work for you.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •