├── .gitignore ├── .npmignore ├── .travis.yml ├── bower.json ├── form-to-obj.js ├── form-to-obj.min.js ├── form-to-obj.min.js.map ├── package.json ├── readme.md └── test └── form-to-obj.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | Web.config 2 | *.log 3 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | example 3 | .gitignore 4 | node_modules 5 | bower_components 6 | README.md 7 | bower.json 8 | .travis.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | before_script: 5 | - "npm i -g jasmine-node" -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "form-to-obj", 3 | "main": "form-to-obj.js", 4 | "version": "0.0.3", 5 | "homepage": "https://github.com/chrisdavies/form-to-obj", 6 | "authors": [ 7 | "Chris Davies " 8 | ], 9 | "description": "Zero dependency helper to serialize a form into a JavaScript object", 10 | "keywords": [ 11 | "forms" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | ".gitignore", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests", 20 | "README.md" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /form-to-obj.js: -------------------------------------------------------------------------------- 1 | function formToObj(form) { 2 | var fields = formToArr(form); 3 | 4 | fields.sort(function (a, b) { 5 | return a.name.localeCompare(b.name); 6 | }); 7 | 8 | return fields.reduce(function(obj, field) { 9 | addProp(obj, field.name, field.value); 10 | return obj; 11 | }, {}); 12 | 13 | function formToArr(form) { 14 | var inputs = form.querySelectorAll('input, textarea, select, [contenteditable=true]'); 15 | var arr = []; 16 | 17 | for (var i = 0; i < inputs.length; ++i) { 18 | var input = inputs[i], 19 | name = input.name || input.getAttribute('data-name'), 20 | val = input.value; 21 | 22 | if (!name || 23 | ((input.type === 'checkbox' || input.type === 'radio') && !input.checked)) { 24 | continue; 25 | } 26 | 27 | if (input.getAttribute('contenteditable') === 'true') { 28 | val = input.innerHTML; 29 | } 30 | 31 | arr.push({ 32 | name: name, 33 | value: val 34 | }); 35 | } 36 | 37 | return arr; 38 | } 39 | 40 | function addProp(o, prop, val) { 41 | var props = prop.split('.'); 42 | var lastProp = props.length - 1; 43 | 44 | props.reduce(function (obj, prop, i) { 45 | return setProp(obj, prop, i === lastProp 46 | ? val 47 | : {}); 48 | }, o); 49 | } 50 | 51 | function setProp(obj, name, val) { 52 | if (name.slice(-2) === '[]') { 53 | makeArr(obj, name).push(val); 54 | } else if (obj[name]) { 55 | return obj[name]; 56 | } else if (name[name.length - 1] === ']') { 57 | var arr = makeArr(obj, name); 58 | 59 | if (arr.prevName === name) { 60 | return arr[arr.length - 1]; 61 | } 62 | 63 | arr.push(val); 64 | arr.prevName = name; 65 | } else { 66 | obj[name] = val; 67 | } 68 | 69 | return val; 70 | } 71 | 72 | function makeArr(obj, name) { 73 | var arrName = name.replace(/\[\d*\]/, ''); 74 | return (obj[arrName] || (obj[arrName] = [])); 75 | } 76 | } 77 | 78 | if (typeof module !== 'undefined' && module.exports) { 79 | module.exports = formToObj; 80 | } 81 | -------------------------------------------------------------------------------- /form-to-obj.min.js: -------------------------------------------------------------------------------- 1 | function formToObj(e){function t(e){for(var t=e.querySelectorAll("input, textarea, select, [contenteditable=true]"),r=[],n=0;n", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/chrisdavies/form-to-obj/issues" 22 | }, 23 | "homepage": "https://github.com/chrisdavies/form-to-obj", 24 | "devDependencies": { 25 | "jasmine-node": "^1.14.5", 26 | "uglify-js": "^2.4.23" 27 | } 28 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # form-to-obj 2 | 3 | Tiny, zero-dependency, utility to turn a form into a JavaScript object. 4 | 5 | - Zero dependencies 6 | - Roughly 600 bytes minified and gzipped 7 | - Handles arrays and nested objects 8 | - Handles contenteditable 9 | 10 | [![Build Status](https://travis-ci.org/chrisdavies/form-to-obj.svg?branch=master)](https://travis-ci.org/chrisdavies/form-to-obj) 11 | 12 | ## Basic usage 13 | 14 | A form such as this: 15 | 16 | ```html 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | ``` 28 | 29 | When called like this: 30 | 31 | ```javascript 32 | var obj = formToObj(document.querySelector('form')); 33 | ``` 34 | 35 | Would produce an `obj` value of this: 36 | 37 | ```javascript 38 | { 39 | username: 'John', 40 | address: { 41 | street: '123 Somewhere', 42 | city: 'Durham,NC' 43 | }, 44 | gender: 'm', 45 | favorites: ['chocolate', 'vanilla'] 46 | } 47 | ``` 48 | 49 | ## Nested arrays and objects 50 | 51 | This form: 52 | 53 | ```html 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | ``` 63 | 64 | When serialized, would produce an object like this: 65 | 66 | ```javascript 67 | { 68 | user: [ 69 | { name: 'John', phone: '1231231234' }, 70 | { name: 'Jane', phone: '1001001000' }, 71 | { name: 'Sally', phone: '7778887777' } 72 | ] 73 | } 74 | ``` 75 | 76 | The sort order is not guaranteed, and the indices don't matter, except as a unique way of identifying a record. 77 | 78 | ## Contenteditable support 79 | 80 | If using a contenteditable item, give it a `data-name` attribute in order to take its `innerHTML` value. 81 | 82 | This markup: 83 | 84 | ```html 85 |
Hi
86 | ``` 87 | 88 | Would produce an object like this: 89 | 90 | ```javascript 91 | { 92 | whatevz: 'Hi' 93 | } 94 | ``` 95 | 96 | ### Browserify 97 | 98 | This library is [CommonJS](http://www.commonjs.org/) compatible, so you can use it in this way: 99 | 100 | ```javascript 101 | var formToObj = require('form-to-obj'), 102 | var obj = formToObj(document.querySelector('form')); 103 | ``` 104 | 105 | ## Installation 106 | 107 | Just download form-to-obj.min.js, or use bower: 108 | 109 | bower install form-to-obj 110 | 111 | Or use npm: 112 | https://www.npmjs.com/package/form-to-obj 113 | 114 | npm install --save form-to-obj 115 | 116 | ## Contributing 117 | 118 | Make your changes (and add tests), then run the tests: 119 | 120 | npm test 121 | 122 | If all is well, build your changes: 123 | 124 | npm run min 125 | 126 | ## License MIT 127 | 128 | Copyright (c) 2015 Chris Davies 129 | 130 | Permission is hereby granted, free of charge, to any person 131 | obtaining a copy of this software and associated documentation 132 | files (the "Software"), to deal in the Software without 133 | restriction, including without limitation the rights to use, 134 | copy, modify, merge, publish, distribute, sublicense, and/or sell 135 | copies of the Software, and to permit persons to whom the 136 | Software is furnished to do so, subject to the following 137 | conditions: 138 | 139 | The above copyright notice and this permission notice shall be 140 | included in all copies or substantial portions of the Software. 141 | 142 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 143 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 144 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 145 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 146 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 147 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 148 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 149 | OTHER DEALINGS IN THE SOFTWARE. 150 | -------------------------------------------------------------------------------- /test/form-to-obj.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | var formToObj = require('../form-to-obj'); 3 | 4 | describe('form-to-obj', function () { 5 | 6 | it('Serializes inputs', function () { 7 | var form = new MockForm({ 8 | fields: [new MockInput('username', 'Chris')] 9 | }); 10 | 11 | var obj = formToObj(form); 12 | 13 | expect(Object.keys(obj).length).toBe(1); 14 | expect(obj.username).toBe('Chris'); 15 | }); 16 | 17 | it('Handles contenteditable', function () { 18 | var form = new MockForm({ 19 | fields: [{ 20 | attrs: { 21 | contenteditable: 'true', 22 | 'data-name': 'yo' 23 | }, 24 | innerHTML: 'Hey!' 25 | }] 26 | }); 27 | 28 | var obj = formToObj(form); 29 | expect(Object.keys(obj).length).toBe(1); 30 | expect(obj.yo).toBe('Hey!'); 31 | }); 32 | 33 | it('Serializes arrays', function () { 34 | var form = new MockForm({ 35 | fields: [{ 36 | name: 'favs[]', 37 | value: 'choc', 38 | type: 'checkbox', 39 | checked: true 40 | }, { 41 | name: 'favs[]', 42 | value: 'vanil', 43 | type: 'checkbox', 44 | checked: true 45 | }] 46 | }); 47 | 48 | var obj = formToObj(form); 49 | 50 | expect(Object.keys(obj).length).toBe(1); 51 | expect(obj.favs).toEqual(['choc', 'vanil']); 52 | }); 53 | 54 | it('Does not serialize unchecked inputs', function () { 55 | var form = new MockForm({ 56 | fields: [{ 57 | name: 'favs[]', 58 | value: 'choc', 59 | type: 'checkbox', 60 | checked: false 61 | }, { 62 | name: 'favs[]', 63 | value: 'vanil', 64 | type: 'checkbox', 65 | checked: true 66 | }] 67 | }); 68 | 69 | var obj = formToObj(form); 70 | 71 | expect(Object.keys(obj).length).toBe(1); 72 | expect(obj.favs).toEqual(['vanil']); 73 | }); 74 | 75 | it('Serializes only checked radios', function () { 76 | var form = new MockForm({ 77 | fields: [{ 78 | name: 'gender', 79 | value: 'm', 80 | type: 'radio', 81 | checked: false 82 | }, { 83 | name: 'gender', 84 | value: 'f', 85 | type: 'radio', 86 | checked: true 87 | }] 88 | }); 89 | 90 | var obj = formToObj(form); 91 | 92 | expect(Object.keys(obj).length).toBe(1); 93 | expect(obj.gender).toEqual('f'); 94 | }); 95 | 96 | it('Serializes nested objects', function () { 97 | var form = new MockForm({ 98 | fields: [{ 99 | name: 'address.street', 100 | value: '123 somewhere', 101 | type: 'text' 102 | }, { 103 | name: 'address.city', 104 | value: 'durham', 105 | type: 'text' 106 | }, { 107 | name: 'user.auth.username', 108 | value: 'cd', 109 | type: 'text' 110 | }] 111 | }); 112 | 113 | var obj = formToObj(form); 114 | 115 | expect(Object.keys(obj).length).toBe(2); 116 | expect(obj.address.street).toEqual('123 somewhere'); 117 | expect(obj.address.city).toEqual('durham'); 118 | expect(obj.user.auth.username).toEqual('cd'); 119 | }); 120 | 121 | it('Ignores fields with no name', function () { 122 | var form = new MockForm({ 123 | fields: [{ 124 | attrs: { 125 | contenteditable: 'true' 126 | }, 127 | innerHTML: 'Hey!' 128 | }, { 129 | name: 'joe', 130 | value: 'shmo', 131 | type: 'text' 132 | }, { 133 | value: 'baz', 134 | type: 'text' 135 | }] 136 | }); 137 | 138 | var obj = formToObj(form); 139 | expect(Object.keys(obj).length).toBe(1); 140 | expect(obj.joe).toBe('shmo'); 141 | }); 142 | 143 | it('Serializes arrays', function () { 144 | var form = new MockForm({ 145 | fields: [{ 146 | name: 'user[45].name', 147 | value: 'a', 148 | type: 'text' 149 | }, { 150 | name: 'user[20].name', 151 | value: 'b', 152 | type: 'text' 153 | }, { 154 | name: 'user[50].name', 155 | value: 'c', 156 | type: 'text' 157 | }] 158 | }); 159 | 160 | var obj = formToObj(form); 161 | 162 | expect(Object.keys(obj).length).toBe(1); 163 | expect(obj.user.length).toBe(3); 164 | expect(obj.user.map(function (u) { 165 | return u.name; 166 | })).toEqual(['b', 'a', 'c']); 167 | }); 168 | 169 | }); 170 | 171 | function MockInput(name, value, inputType) { 172 | this.name = name; 173 | this.value = value; 174 | this.type = inputType || 'input'; 175 | } 176 | 177 | function MockForm(args) { 178 | this.fields = args.fields; 179 | 180 | args.fields.forEach(function (f) { 181 | var attrs = f.attrs || {}; 182 | delete f.attrs; 183 | f.getAttribute || (f.getAttribute = function (name) { 184 | return attrs[name]; 185 | }); 186 | }); 187 | } 188 | 189 | MockForm.prototype = { 190 | querySelectorAll: function (fields) { 191 | var fieldsArr = fields.split(/,/).map(function (s) { 192 | return s.trim(); 193 | }); 194 | 195 | if (fieldsArr.indexOf('input') < 0 || 196 | fieldsArr.indexOf('select') < 0 || 197 | fieldsArr.indexOf('textarea') < 0 || 198 | fieldsArr.indexOf('[contenteditable=true]') < 0) { 199 | throw new Error('Expected to select inputs, selects, contenteditable, and textareas'); 200 | } 201 | 202 | return this.fields; 203 | } 204 | }; --------------------------------------------------------------------------------