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:
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.
The script to add and remove sets is fairly simple
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:
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:
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
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.
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.
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
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).
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.
Yes, that’s a perfectly good way to compare elements.
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.
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.