Allow user to create another iteration of form fields

I’ll try to explain what I’m looking for. A user can fill out a collection of form fields and save the information (in localStorage). This is easy to code. But I want to provide a button so the user can duplicate the collection of form fields and add different information, and those fields would be saved as well as the former. User should be able to delete collections at will.

I could hard-code the form for a number of times, but that would put a limit on how many they can save. I don’t want to enforce this limit.

How do we do the effect that duplicates fields for saving in localStorage?

You’ll need a template that can be duplicated and rendered with the data from localStorage. Loop through the array and render a set of fields for each. Have a go and post your code.

Thanks, Mark. I don’t have the code at hand; coming up in the project later. Didn’t think to put it all in an array. Will have to think about that approach. Thanks!

1 Like

I might end up using webSql instead. I wrote about it in one of my step-by-step articles a while ago and it worked. The problem is, will webSql still be around later? Seems to be deprecated. (I would post the link to the article, but it could be construed as spam.)

From what I can see, it will never be around for IE, Edge or Firefox. It is also not supported from web workers. Future support in Chrome, Safari etc. is unknown.

indexedDB was announced as its replacement in November 2010.

Your main issue isn’t how to store the data, it’s rendering dynamic templates and loading data into them. After that’s working you can easily persist on a server or in localStorage.

But would that have to be dynamic templates? If the form itself is fixed, you could just store such form configurations as an array of objects of the form {inputId1: inputValue1, ...}… like

var configurations = localStorage.configurations ? 
  JSON.parse(localStorage.configurations) : [];
var form = document.getElementById('my-form');
var inputs = form.querySelectorAll('input');

var saveConfiguration = function() {
  var configuration = {};
  var input;
  
  // Get the values of the input elements and store
  // them in a configuration object
  for (var i = 0; i < inputs.length; i++) {
    input = inputs[i];
    configuration[input.id] = input.value;
  }
  
  // Push it to the local storage configurations
  configurations.push(configuration);
  localStorage.configurations = JSON.stringify(configurations);
};

var restoreConfiguration = function(which) {
  var configuration;
  var element;
  
  // Access the configuration by a key, e.g. from
  // a select element
  configuration = configurations[which];

  // Iterate over the properties of the configuration
  for (var i in configuration) {

    // Fill the input fields with saved configuration
    element = document.getElementById(i);

    if (element) {
      element.value = configuration[i];
    }
  }
};

var clearConfiguration = function() {

  configurations = [];
  localStorage.configurations = JSON.stringify(configurations);
};

This way it wouldn’t even matter if the form changes at some point, as only the matching fields will be filled. Here’s a small demo (doesn’t seem to work as a fiddle). Alternatively, you could set the input values as HTML attributes when saving the configuration, and store the form’s entire innerHTML… would be a bit dirty, but allow storing entirely arbitrary forms. Like (mutatis mutandis)

var saveConfiguration = function() {
  var inputs = form.querySelectorAll('input');
  
  // Set the value attributes of the input elements
  // to store them with the form's innerHTML
  for (var i = 0; i < inputs.length; i++) {
    inputs[i].setAttribute('value', inputs[i].value)
  }
  
  // Push the form the local storage configurations
  configurations.push(form.innerHTML);
  localStorage.configurations = JSON.stringify(configurations);
};

var restoreConfiguration = function(which) {
  
  // Set the form to the configuration
  form.innerHTML = configurations[which];
};

No, like you demonstrated is one way to achieve it.

But I want to provide a button so the user can duplicate the collection of form fields and add different information

My understanding was that the aim was to show repeated sets of fields, templates simplify this greatly.

Wow, this works well. Good demo. However, restoring a set, modifying one of its values, and saving should overwrite the set, not create a new set with the same name. And instead of clearing all fields for all sets, only the set selected in the dropdown.

Thanks for taking the time and effort!

“My understanding was that the aim was to show repeated sets of fields, templates simplify this greatly.”

The demo given was sufficient. It will show repeated sets of fields after selecting any of them from a drop-down box. That’s a good way to keep them in order, and allows them to make as many sets as they wish.

I’m unable to add radio buttons to the form and capture their input:

< div>
< input type=“radio” id=“bar3” name=“bar3” value=“1” tabindex=“1”>< label for=“bar3”>1
< input type=“radio” id=“bar4” name=“bar4” value=“1” tabindex=“2”>< label for=“bar4”>2
< input type=“radio” id=“bar5” name=“bar5” value=“1” tabindex=“3”>< label for=“bar5”>3
< /div>
< /form>

If I make all name= the same as name=“bar3” still doesn’t store the values.

When restoring a set, you’d remember the index (which could be undefined otherwise), and later save it to configurations[index] instead of .push()ing it.

Instead of storing (only) the values, you’d store the checked property here. Like

var radios = form.querySelectorAll('input[type="radio"]');

for (var i = 0; i < radios.length; i++) {
  radio = radios[i];
  configuration[radio.id] = radio.checked;
}

When restoring such a configuration, you’d have to check whether the input is a radio then, like (in the for loop as above)

if (element) {
  if (element.type === 'radio') {
    element.checked = configuration[i];
  } else {
    element.value = configuration[i];
  }
}

Do we modify your code with these additions, or add to existing code? I added the code to two different places and don’t see it working.

Obviously, I’ll need to duplicate the above code for check boxes as well.

Thanks for helping!

OK, I got the checkboxes working!

Got the radios and checkboxes working.

Now, how to select the chosen id to delete only that one set.

Here’s what I have so far in editing your work:

Gist (download and add .html to end of filename to open):

Right now I’m trying to get all the localstorage info into a single textarea field for export. This is successful. However, I only want the checked inputs, not all the inputs. What changes do I need to narrow that down?

Well, the data model above was very simple to just get you started. :-) If you want to perform further operations on the input elements (depending on their type, checked-state etc.) and sets themselves you might need a more appropriate format to to store them. Maybe something like

var someSet = {
  setId: 1470765975628, // As obtained from Date.now() to ensure uniqueness
  inputs: [
    {id: 'SettingName1', type: 'text', value: 'foo'},
    {id: 'SettingName3', type: 'radio', value: 'bar', checked: false},
    {id: 'SettingName4', type: 'radio', value: 'baz', checked: true},
    /* etc... */
  ]
};

sets.push(someSet);

This way you could filter out unchecked elements of a set like

var inputs = someSet.inputs.filter(function(input) {
  return input.checked !== false;
});

and you could remove a set with a specific ID like

sets = sets.filter(function(set) {
  return set.setId !== idToRemove;
});

Thanks! I’ll puzzle over this and see what I come up with. Much appreciated!

The hard part coming up: saving everything to localStorage! I think that will require new functions.

This is way over my head. I’ll revert to hard-wiring localStorage fields with the form fields.

Actually, you’d save it just the same way – by stringify()ing the entire set array. What does require a bit more logic is storing the set itself. Here’s another take:

// Restore the sets from the local storage, if available
var sets = localStorage.sets ? 
  JSON.parse(localStorage.sets) : [];

// The form to store
var form = document.getElementById('my-form');

// The input elements
var inputs = form.querySelectorAll('input');

// The select to restore and delete sets
var select = document.getElementById('select');

var addOption = function(set) {  
  
  // Create a new option for the set
  var option = document.createElement('option');
  
  // The value of the option references the set
  // by its index
  option.value = set.setId;
  
  // As label we'll arbitrarily choose the `foo` value
  option.textContent = 'Set ' + set.setId;
  
  // Append it to the select
  select.appendChild(option);

  // Enable the restore button
  restore.removeAttribute('disabled');
};

var saveSet = function() {
  var set = {setId: Date.now(), inputs: []};
  var input;
  
  // Get the relevant properties of the input 
  // elements and store them in the set object
  for (var i = 0; i < inputs.length; i++) {
    input = inputs[i];

    set.inputs.push({
      id: input.id,
      value: input.value,
      checked: input.checked
    });
  }
  
  // Push it to the local storage sets
  sets.push(set);
  localStorage.sets = JSON.stringify(sets);
  
  // Append an option to the select
  addOption(set);
};

var restoreSet = function() {
  var set;
  var input;
  var item;
  
  // Access the set by the select value
  set = sets.find(function(el) {
    return el.setId === parseInt(select.value);
  });

  if (!set) return;

  // Iterate over the input elements of the form
  for (var i = 0; i < inputs.length; i++) {
    input = inputs[i];

    // Get the corresponding item in the set
    item = set.inputs.find(function(el) {
      return el.id === input.id;
    });

    if (item) {

      // Set the input value to the value in the stored set
      input.value = item.value;

      // If it's a radio or checkbox, also apply the checked state
      if (input.type === 'radio' || input.type === 'checkbox') {
        input.checked = item.checked;
      }
    }
  }
};

var removeSet = function() {

  // The selected option
  var option = select.querySelector('option[value="' + select.value + '"]');

  // Remove the selected set
  sets = sets.filter(function(el) {
    return el.setId !== select.value;
  });

  localStorage.sets = JSON.stringify(sets);

  // Also remove the corresponding select option
  select.removeChild(option);
};

I’ve also updated the above demo link. If you have difficulties understanding one line or the other, don’t hesitate to ask! :-)

This is an amazing piece of work!

I tried to modify so the user can create his own form set name:

HTML (ignore the spaces after the brackets to make the HTML show up):
< div>
< label for=“setname”>Setname< /label>
< input type=“text” id=“setname” name=“setname”>
< /div>

JS:
var saveSet = function() {
var setname = document.getElementById(‘setname’);
var set = {setId: setname, inputs: };
var input;

But this returned a very unhelpful set name in the dropdown. After restoring the original code, this edit did not work either:

// As label we’ll arbitrarily choose the foo value
var setname = document.getElementById(‘setname’);
option.textContent = setname + set.setId;