jpeltoniemi
jpeltoniemi

Reputation: 5620

How to extract HTML form data with javascript?

I need to extract data from an HTML form with javascript(+Mootools) in the same kind of nested object format that PHP would see it when the form is posted.

Am I just bad at googling or is there really no native nor a well-known way to achieve this? I see many others have asked the same question in different forums but so far all have accepted solutions like jQuery serializeArray and such.

I tried out serializeArray with jsFiddle http://jsfiddle.net/7quxe/ and the results were disappointing.

I've previously written a script for this myself and it worked quite well except it had some problems when the form had overlapping mixed key type fields (<input name="foo[bar]" value="1"> and <input name="foo[]" value="2". I've already started working on a better version but as I found myself starting over again and again, I thought to myself: There are lots of great js libraries out there that aim to solve many basic everyday problems. Can it really be that wanting to extract data from a form in a properly formatted object is not that common?

Here is an example of what I'm attempting:

<form method="post" action="/">
    <input name="test" value="test">
    <input name="nested[a]" value="a">
    <input name="nested[b]" value="b">
    <input name="nested[c]" value="c">
    <input name="arraytest[]" value="foo">
    <input name="arraytest[]" value="foo">
    <input name="arraytest[]" value="foo">
</form>

Here's how PHP would see this:

$_POST = array(
    'test' => 'test',
    'nested' => array(
        'a' => 'a',
        'b' => 'b',
        'c' => 'c',
    ),
    'arraytest' => array(
        0 => 'foo1',
        1 => 'foo2',
        2 => 'foo3'
    )
)

and this is what I'd like to get in js:

{
    test : 'test',
    nested : {
        a : 'a',
        b : 'b',
        c : 'c'
    },
    arraytest : {       // This could also be an array ['foo1','foo2','foo3']
        0 : 'foo1',
        1 : 'foo2',
        2 : 'foo3'
    }
}

Upvotes: 3

Views: 21467

Answers (6)

Manjunath H
Manjunath H

Reputation: 41

FormData data= new FormData();

Data consists of your all form data If you want specifically then you need to use

data.get("fieldName");

Upvotes: 4

Manjunath H
Manjunath H

Reputation: 41

export default class Article extends Component{
    handlesubmit(e)
    {
        e.preventDefault();
        const data = new FormData(e.target);
        let userid=qs.parseUrl(window.location.search).query.id;
        data.append("userid",userid);
        if(document.cookie.split(';')[0]=='login=true')
        fetch('/api/medium/article',{
            method: 'POST',
            body:data
        })
        .then(Response=>{
            console.log(Response);
            return Response.json();
        })
        .then(Response=>{
            console.log(Response);
            window.location.pathname='/medium';
            return Response;
        })
        .catch(err=>{
            return err;
        });
        else{
            alert("please login again");
            window.location.pathname='/medium';
        }
    }
    render()
    {
        return (
            <div className="create-article">
                <form encType="multipart/form-data" className="articleCreating" onSubmit={this.handlesubmit} >

                    <div className="form-group">
                        <select className="custom-select" autoFocus required name='category'>
                            <option value="">Select Category</option>
                            <option value="TECHNOLOGY">Technology</option>
                            <option value="NATURE">Nature</option>
                            <option value="FOOD">Food</option>
                        </select>
                    </div>

                    <div className="input-group">
                        <input type="text" className="form-control" aria-label="Title" placeholder="Title"
                        maxLength="70" spellCheck="true" name="title" required />
                    </div>

                    <div className="input-group">
                        <input type="text" className="form-control" aria-label="Subtitle" placeholder="Subtitle" 
                        spellCheck="true" name="description" required />
                    </div>

                    <div className="input-group has-error">
                        <textarea className="form-control" aria-label="With textarea" placeholder="Description" cols="10"
                        rows="10" spellCheck="true" name="article" required>
                        </textarea>
                    </div>

                    <div className="fileinput">
                        <span><input type="file" name="image"/></span>
                    </div>
                    <div className="article-submit">
                       <button className="btn btn-raised btn-success" type='submit'>Create Article</button>
                    </div>
                </form>
            </div>

        )
    }
}

this is in react I think u can achieve the same thing using js also

Upvotes: 0

DanMan
DanMan

Reputation: 11561

dojo has a neat method for that called dojo.formToObject(). As the name suggests, it converts all form element (inputs, selects, ...) into an object. You could then easily convert it into JSON and send it over the wire via AJAX, for example.

Upvotes: 0

jpeltoniemi
jpeltoniemi

Reputation: 5620

I got obsessed about this and then some more :D

Here's my solution: http://jsfiddle.net/sLZZr/3/

Feel free to borrow it. If you find it helpful, please let me know (not a requirement, just curious. My email's in the source :)


Answering requires formatted code, so here goes:

/**
 * FormAccess
 * 
 * @description Manipulate data in forms and element collections. Currently only reading supported.
 *
 * @version     0.8
 *
 * @license     MIT License
 * 
 * @author      Jani Peltoniemi <[email protected]>
 * 
 * @copyright   2012 Jani Peltoniemi
 */


/**
* FormAccess main class
* 
*/
FormAccess = new Class({
    formElm : null,                 // The form element this instance will be linked to
    /**
    * Constructs the class and adds the quick access method "extractData" to formElm
    * 
    * @param Form element
    */
    initialize:function(formElm) {
        this.formElm = document.id(formElm);
        this.formElm.extractData = this.extractData.bind(this);
    },
    /**
    * Calls the static extractData function with the linked form's inputs
    * 
    * @returns Extracted form data in nested object
    */
    extractData : function() {
        var inputElms = this.formElm.getElements('input,textarea,select');
        return this.$constructor.extractData(inputElms);    
    }

});

/**
* FormAccess static functions
* 
* Currently only reading available. Filling forms according to data in an object will come later.
* 
*/
FormAccess.extend({
    /**
    * Extracts the data from given elements
    * 
    * Notes :
    *   - Converts empty keys to numeric. If (non-converted)numeric keys are already present, empty key becomes <largest key>+1.
    *       - Elements are handled from top to bottom. When converting empty keys between other numeric keys the largest key means largest key _up to that point_.
    *   - Inputs with empty names are not included in results.
    *   - Checkboxes' value attribute is ignored and only their checked state is included in results.
    *   - Multi-selectboxes return the selected values in an array
    * 
    * @param Selector, element or element collection - everything that $$() takes in.
    * 
    * @returns Extracted form data in nested object
    */
    extractData : function(inputElms) {
        // Normalize the input / query DOM
        inputElms = $$(inputElms);

        var that = this;
        var result = {};

        // Data from input elements is collected here for easier handling
        var inputData = [];

        // Collect inputData
        inputElms.each(function(inputElm) {
            if (!inputElm.name)
                return;
            // Explode the input name into an array path
            var path = that.parseName(inputElm.name);

            inputData.push({
                path:path,
                value:inputElm.value ? inputElm.value : '',
                elm:inputElm
            });
        });
        // Index tracking variable. Used to find next free numeric keys
        var maxIndex;
        inputData.each(function(data,i) {
            var path = data.path;

            // Last element of the path needs special treatment. Pop it out.
            var last = path.pop();

            // Working var
            var current = result;

            path.each(function(part) {

                // Assign a numeric key if the key is empty
                if (part == '') {
                    // Reset the index tracker
                    maxIndex = -1;

                    // Loop through the current position in result set
                    Object.each(current,function(val,key) {
                        // Convert key to int and make sure it is a proper number
                        var intKey = key.toInt();
                        if (intKey == key && intKey > maxIndex) {
                            // Key is greater than known largest key.
                            maxIndex = intKey;
                        }
                    });
                    // Set the key to largest found key + 1
                    part = maxIndex + 1;
                }

                // If the next position is not defined or is not an object, overwrite it.
                if (typeOf(current[part]) != 'object')
                    current[part] = {};

                // Update the position
                current = current[part];
            });

            var lastWasEmpty = false;
            // Evaluate the last part separately
            if (last == '') {
                lastWasEmpty = true;
                // Reset the index tracker
                maxIndex = -1;

                // Loop through the current position in result set
                Object.each(current,function(val,key) {
                    // Convert key to int and make sure it is a proper number
                    var intKey = key.toInt();
                    if (intKey == key && intKey > maxIndex) {
                        // Key is greater than known largest key.
                        maxIndex = intKey;
                    }
                });
                // Set the key to largest found key + 1
                last = maxIndex + 1;
            }

            // Element-specific value handling
            switch (data.elm.tagName.toLowerCase()) {
                // Case for Select, since they allow multiple selections
                case 'select':
                    if (data.elm.multiple) {
                        // A <value> = <selected> format was considered here but rejected due to long values being bad keys
                        current[last] = data.elm.getSelected().get('value');
                    } else
                        current[last] = data.value; 
                break;

                // Inputs have a couple of special cases that need to be handled differently
                case 'input':
                    switch (data.elm.type) {
                        // Only the value of the checked radiobutton is included in results.
                        // If none is checked, the result will display null
                        case 'radio':
                            // Only set the value if radiobutton is checked
                            // Otherwise check if this key is not already in results and then set it to null
                            if (data.elm.checked)
                                current[last] = data.value; 
                            else if (current[last] == undefined)
                                current[last] = null;
                        break;

                        // Checkboxes' value attribute is ignored and the checked state is included in results
                        case 'checkbox':
                            current[last] = data.elm.checked;
                        break;

                        // All others
                        default:
                                current[last] = data.value; 
                        break;
                    }
                break;

                // All other elements are handled here. 
                default:
                    current[last] = data.value; 
                break;
            }
        });
        return result;
    },
    /**
    * Explodes the name attribute.
    * 
    * Example:
    *   name="testinput[foo][bar][]"  -> ['testinput','foo','bar','']
    * 
    * @param Input name
    * 
    * @returns Exploded input name
    */
    parseName : function(name) {
        var path = [name.match(/^[^\[]*/)[0]];
        var pathRegExp = /\[(.*?)\]/g;
        var part = '';
        while (true) {
            part = pathRegExp.exec(name);
            if (part !== null)
                path.push(part[1]);
            else
                break;
        }

        return path;
    },
    /**
    * Applies the FormData object to chosen form elements.
    * 
    * If falsy argument is given, FormData is applied to all forms
    * 
    * @param Selector, element or element collection - everything that $$() takes in.
    * 
    * @returns Element collection
    */
    apply : function(elements) {
        if (!elements)
            elements = 'form';
        elements = $$(elements);

        elements.each(function(element) {
            element.formAccess = new FormAccess(element);
        });

        return elements;
    }
});

Upvotes: 0

Kotzilla
Kotzilla

Reputation: 1413

from your answer. i've made jquery script to extract input element and it will work only with input one dimension array like "input[string]" and "input[]" only, cheers
jsfiddle

HTML

<h2>Form</h2>
<form action="" name="form" method="post">
  <input name="test" value="test">
  <input name="nested[a]" value="a">
  <input name="nested[b]" value="b">
  <input name="nested[c]" value="c">
  <input name="arraytest[]" value="foo">
  <input name="arraytest[]" value="foo">
  <input name="arraytest[]" value="foo">
  <p><input type="submit" /></p>
</form>

<h2>JSON output</h2>
<pre id="result"></pre>​​​​​​

jQuery

  $.fn.serializeObject = function()
  {
    var o = {}, a = this.serializeArray();
    $.each(a, function() {

      if (/(\[\]$|\[.+\]$)/.test(this.name)) {

        var match = /(.+)(\[\]|\[(.+)\])/.exec(this.name);
        if (match[3] !== undefined)
        {
          var index = match[3];
          if (o[match[1]] === undefined)
            o[match[1]] = {};

          if (o[match[1]][index] === undefined)
            o[match[1]][index] = [o[match[1]][index]];

          o[match[1]][index] = this.value || '';

        } else {
          if (o[match[1]] === undefined)
            o[match[1]] = new Array;

          o[match[1]].push(this.value || '');
        }


      } else {
        if (o[this.name] !== undefined) {
          if (!o[this.name].push) {
            o[this.name] = [o[this.name]];
          }
          o[this.name].push(this.value || '');
        } else {
          o[this.name] = this.value || '';
        }
      }

    });

    return o;
  };

  $(function() {
    $('form').submit(function() {
      $('#result').text(JSON.stringify($('form').serializeObject()));
      return false;
    });
  });​

Upvotes: 1

Matt Brock
Matt Brock

Reputation: 5391

You can do this with straight JavaScript using the form.elements object. Here's an example that will convert any form into string of URL parameters:

function getParameters(form) {
  var parameters = "";
  for (var x=0, y=form.elements.length; x < y; x++) {
  var field = form.elements[x];
  if (field.name && field.type !== "submit") {
    parameters += "&" + encodeURIComponent(field.name) + "=" + (field.type == "radio" || field.type == "checkbox" ? (field.checked == "checked") : encodeURIComponent(field.value));
  }
  return parameters;
}

Upvotes: 1

Related Questions