├── .editorconfig ├── .gitignore ├── .travis.yml ├── .zuul.yml ├── History.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.js] 2 | indent_style = space 3 | indent_size = 4 4 | insert_final_newline = true 5 | max_line_length = 80 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '5' 4 | env: 5 | global: 6 | - secure: QHQz/F+IvYyRkgrVEEc3NTsfkJwXlzsU8jACVIPbhHVOaaDe97b/tk0A4vv+eo+BXVNhyio7ojLlOnlcY0eiHKc31frf/Rv4nARzdG+EmzRuBtpxsnchQ92RzKhL+hL57p0LLvTW3yPAJX44lTnI8WrbvuUA22rphm0YsD1yaLM= 7 | - secure: exf51mpyUBQeesvMOpIrKWI/SoiSBmc8TK5Jw+npDmjLUWZl0ou4Ptg0vUqhIjoKM/mvrAnOcTIeSqrK5+Kngy9gRrYJxJjjNARphIZA3LQtyvbDGvMoJXooare6DmYCLf3r/YVN3SAE2yfkpE4fvJHrugsD2Ch4RX618ZdqzbI= 8 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-qunit 2 | browsers: 3 | - name: chrome 4 | version: latest 5 | - name: firefox 6 | version: latest 7 | - name: safari 8 | version: 5..latest 9 | - name: ie 10 | version: 9..latest 11 | - name: iphone 12 | version: latest 13 | - name: android 14 | version: latest 15 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # 0.7.2 (2017-06-11) 2 | 3 | * Serialize properly radio input with empty value (1b7a042) 4 | 5 | # 0.7.1 (2016-03-11) 6 | 7 | * fix bracket notation number parsing (de5d19) 8 | 9 | # 0.7.0 (2015-10-17) 10 | 11 | * add bracket notation support for hash serialization 12 | 13 | # 0.6.0 (2015-02-23) 14 | 15 | * add options.empty to force serialize empty inputs 16 | 17 | # 0.5.0 (2015-02-08) 18 | 19 | * fix specifying custom serializer 20 | 21 | # 0.4.1 (2015-01-15) 22 | 23 | * fix nested [][] serialization 24 | 25 | # 0.4.0 (2014-12-16) 26 | 27 | * consistently serialize [] params into arrays 28 | * fix multi-select field support 29 | 30 | # 0.3.0 / 2014-05-01 31 | 32 | * support bracket notation for hash serialization 33 | 34 | # 0.2.0 / 2013-12-05 35 | 36 | * add `disabled` option to include disabled fields 37 | 38 | # 0.1.1 / 2013-08-26 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Roman Shtylman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # form-serialize [![Build Status](https://travis-ci.org/defunctzombie/form-serialize.png?branch=master)](https://travis-ci.org/defunctzombie/form-serialize) 2 | 3 | serialize form fields to submit a form over ajax 4 | 5 | ## install 6 | 7 | ```shell 8 | npm install form-serialize 9 | ``` 10 | 11 | ## use 12 | 13 | form-serialize supports two output formats, url encoded (default) or hash (js objects). 14 | 15 | Lets serialize the following html form: 16 | ```html 17 |
18 | 19 | 20 |
21 | ``` 22 | 23 | ```js 24 | var serialize = require('form-serialize'); 25 | var form = document.querySelector('#example-form'); 26 | 27 | var str = serialize(form); 28 | // str -> "foo=bar" 29 | 30 | var obj = serialize(form, { hash: true }); 31 | // obj -> { foo: 'bar' } 32 | ``` 33 | 34 | ## api 35 | 36 | ### serialize(form [, options]) 37 | 38 | Returns a serialized form of a HTMLForm element. Output is determined by the serializer used. Default serializer is url-encoded. 39 | 40 | arg | type | desc 41 | :--- | :--- | :--- 42 | form | HTMLForm | must be an HTMLForm element 43 | options | Object | optional options object 44 | 45 | #### options 46 | 47 | option | type | default | desc 48 | :--- | :--- | :---: | :--- 49 | hash | boolean | false | if `true`, the hash serializer will be used for `serializer` option 50 | serializer | function | url-encoding | override the default serializer (hash or url-encoding) 51 | disabled | boolean | false | if `true`, disabled fields will also be serialized 52 | empty | boolean | false | if `true`, empty fields will also be serialized 53 | 54 | ### custom serializer 55 | 56 | Serializers take 3 arguments: `result`, `key`, `value` and should return a newly updated result. 57 | 58 | See the example serializers in the index.js source file. 59 | 60 | ## notes 61 | 62 | only [successful control](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2) form fields are serialized (with the exception of disabled fields if disabled option is set) 63 | 64 | multiselect fields with more than one value will result in an array of values in the `hash` output mode using the default hash serializer 65 | 66 | ### explicit array fields 67 | 68 | Fields who's name ends with `[]` are **always** serialized as an array field in `hash` output mode using the default hash serializer. 69 | The field name also gets the brackets removed from its name. 70 | 71 | This does not affect `url-encoding` mode output in any way. 72 | 73 | ```html 74 |
75 | 76 | 77 | 78 |
79 | ``` 80 | 81 | ```js 82 | var serialize = require('form-serialize'); 83 | var form = document.querySelector('#example-form'); 84 | 85 | var obj = serialize(form, { hash: true }); 86 | // obj -> { foo: ['bar'] } 87 | 88 | var str = serialize(form); 89 | // str -> "foo[]=bar" 90 | 91 | ``` 92 | 93 | ### indexed arrays 94 | 95 | Adding numbers between brackets for the array notation above will result in a hash serialization with explicit ordering based on the index number regardless of element ordering. 96 | 97 | Like the "[explicit array fields](explicit-array-fields)" this does not affect url-encoding mode output in any way. 98 | 99 | ```html 100 |
101 | 102 | 103 | 104 |
105 | ``` 106 | 107 | ```js 108 | var serialize = require('form-serialize'); 109 | var form = document.querySelector('#todos-form'); 110 | 111 | var obj = serialize(form, { hash: true }); 112 | // obj -> { todos: ['eggs', 'milk', 'flour'] } 113 | 114 | var str = serialize(form); 115 | // str -> "todos[1]=milk&todos[0]=eggs&todos[2]=flour" 116 | 117 | ``` 118 | 119 | ### nested objects 120 | 121 | Similar to the indexed array notation, attribute names can be added by inserting a string value between brackets. The notation can be used to create deep objects and mixed with the array notation. 122 | 123 | Like the "[explicit array fields](explicit-array-fields)" this does not affect url-encoding mode output. 124 | 125 | ```html 126 |
127 | 128 | 129 |
130 | ``` 131 | 132 | ```js 133 | var serialize = require('form-serialize'); 134 | var form = document.querySelector('#todos-form'); 135 | 136 | var obj = serialize(form, { hash: true }); 137 | // obj -> { foo: { bar: { baz: 'qux' } }, norf: [ 'item 1' ] } 138 | 139 | ``` 140 | 141 | ## references 142 | 143 | This module is based on ideas from jQuery serialize and the Form.serialize method from the prototype library 144 | 145 | ## license 146 | 147 | MIT 148 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // get successful control from form and assemble into object 2 | // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2 3 | 4 | // types which indicate a submit action and are not successful controls 5 | // these will be ignored 6 | var k_r_submitter = /^(?:submit|button|image|reset|file)$/i; 7 | 8 | // node names which could be successful controls 9 | var k_r_success_contrls = /^(?:input|select|textarea|keygen)/i; 10 | 11 | // Matches bracket notation. 12 | var brackets = /(\[[^\[\]]*\])/g; 13 | 14 | // serializes form fields 15 | // @param form MUST be an HTMLForm element 16 | // @param options is an optional argument to configure the serialization. Default output 17 | // with no options specified is a url encoded string 18 | // - hash: [true | false] Configure the output type. If true, the output will 19 | // be a js object. 20 | // - serializer: [function] Optional serializer function to override the default one. 21 | // The function takes 3 arguments (result, key, value) and should return new result 22 | // hash and url encoded str serializers are provided with this module 23 | // - disabled: [true | false]. If true serialize disabled fields. 24 | // - empty: [true | false]. If true serialize empty fields 25 | function serialize(form, options) { 26 | if (typeof options != 'object') { 27 | options = { hash: !!options }; 28 | } 29 | else if (options.hash === undefined) { 30 | options.hash = true; 31 | } 32 | 33 | var result = (options.hash) ? {} : ''; 34 | var serializer = options.serializer || ((options.hash) ? hash_serializer : str_serialize); 35 | 36 | var elements = form && form.elements ? form.elements : []; 37 | 38 | //Object store each radio and set if it's empty or not 39 | var radio_store = Object.create(null); 40 | 41 | for (var i=0 ; i'); 43 | hash_check(form, {}); 44 | str_check(form, ''); 45 | empty_check(form, ''); 46 | empty_check_hash(form, {}); 47 | }); 48 | 49 | // basic form with single input 50 | test('single element', function() { 51 | var form = domify('
'); 52 | hash_check(form, { 53 | 'foo': 'bar' 54 | }); 55 | str_check(form, 'foo=bar'); 56 | empty_check(form, 'foo=bar'); 57 | empty_check_hash(form, { 58 | 'foo': 'bar' 59 | }); 60 | }); 61 | 62 | test('ignore no value', function() { 63 | var form = domify('
'); 64 | hash_check(form, {}); 65 | str_check(form, ''); 66 | }); 67 | 68 | test('do not ignore no value when empty option', function() { 69 | var form = domify('
'); 70 | empty_check(form, 'foo='); 71 | empty_check_hash(form, { 72 | 'foo': '' 73 | }); 74 | }); 75 | 76 | test('multi inputs', function() { 77 | var form = domify('
' + 78 | '' + 79 | '' + 80 | '' + 81 | '
'); 82 | hash_check(form, { 83 | 'foo': 'bar 1', 84 | 'foo.bar': 'bar 2', 85 | 'baz.foo': 'bar 3' 86 | }); 87 | str_check(form, 'foo=bar+1&foo.bar=bar+2&baz.foo=bar+3'); 88 | }); 89 | 90 | test('handle disabled', function() { 91 | var form = domify('
' + 92 | '' + 93 | '' + 94 | '
'); 95 | hash_check(form, { 96 | 'foo': 'bar 1' 97 | }); 98 | str_check(form, 'foo=bar+1'); 99 | disabled_check(form, 'foo=bar+1&foo.bar=bar+2'); 100 | }); 101 | 102 | test('handle disabled and empty', function() { 103 | var form = domify('
' + 104 | '' + 105 | '' + 106 | '
'); 107 | hash_check(form, {}); 108 | str_check(form, ''); 109 | disabled_check(form, ''); 110 | empty_check(form, 'foo=&foo.bar='); 111 | empty_check_hash(form, { 112 | 'foo': '', 113 | 'foo.bar': '' 114 | }); 115 | }); 116 | 117 | test('ignore buttons', function() { 118 | var form = domify('
' + 119 | '' + 120 | '' + 121 | '
'); 122 | hash_check(form, {}); 123 | str_check(form, ''); 124 | }); 125 | 126 | test('checkboxes', function() { 127 | var form = domify('
' + 128 | '' + 129 | '' + 130 | '' + 131 | '
'); 132 | hash_check(form, { 133 | 'foo': "on", 134 | 'baz': "on" 135 | }); 136 | str_check(form, 'foo=on&baz=on'); 137 | empty_check(form, 'foo=on&bar=&baz=on'); 138 | empty_check_hash(form, { 139 | 'foo': 'on', 140 | 'bar': '', 141 | 'baz': 'on' 142 | }); 143 | }); 144 | 145 | test('checkboxes - array', function() { 146 | var form = domify('
' + 147 | '' + 148 | '' + 149 | '' + 150 | '
'); 151 | hash_check(form, { 152 | 'foo': ['bar', 'baz'] 153 | }); 154 | str_check(form, 'foo%5B%5D=bar&foo%5B%5D=baz'); 155 | empty_check(form, 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D='); 156 | empty_check_hash(form, { 157 | 'foo': ['bar', 'baz', ''] 158 | }); 159 | }); 160 | 161 | test('checkboxes - array with single item', function() { 162 | var form = domify('
' + 163 | '' + 164 | '
'); 165 | hash_check(form, { 166 | 'foo': ['bar'] 167 | }); 168 | str_check(form, 'foo%5B%5D=bar'); 169 | }); 170 | 171 | test('select - single', function() { 172 | var form = domify('
' + 173 | '' + 177 | '
'); 178 | hash_check(form, { 179 | 'foo': 'baz' 180 | }); 181 | str_check(form, 'foo=baz'); 182 | }); 183 | 184 | test('select - single - empty', function () { 185 | var form = domify('
' + 186 | '' + 191 | '
'); 192 | hash_check(form, {}); 193 | str_check(form, ''); 194 | empty_check(form, 'foo='); 195 | empty_check_hash(form, { 196 | 'foo': '' 197 | }); 198 | }); 199 | 200 | test('select - multiple', function() { 201 | var form = domify('
' + 202 | '' + 207 | '
'); 208 | hash_check(form, { 209 | 'foo': ['bar', 'cat'] 210 | }); 211 | str_check(form, 'foo=bar&foo=cat'); 212 | }); 213 | 214 | test('select - multiple - empty', function() { 215 | var form = domify('
' + 216 | '' + 222 | '
'); 223 | hash_check(form, {}); 224 | str_check(form, ''); 225 | empty_check(form, 'foo='); 226 | empty_check_hash(form, { 227 | 'foo': '' 228 | }); 229 | }); 230 | 231 | test('radio - no default', function() { 232 | var form = domify('
' + 233 | '' + 234 | '' + 235 | '
'); 236 | hash_check(form, {}); 237 | str_check(form, ''); 238 | empty_check(form, 'foo='); 239 | empty_check_hash(form, { 240 | 'foo':'' 241 | }); 242 | }); 243 | 244 | test('radio - single default', function() { 245 | var form = domify('
' + 246 | '' + 247 | '' + 248 | '
'); 249 | hash_check(form, { 250 | foo: 'bar1' 251 | }); 252 | str_check(form, 'foo=bar1'); 253 | empty_check(form, 'foo=bar1'); 254 | empty_check_hash(form, { 255 | foo: 'bar1' 256 | }); 257 | }); 258 | 259 | test('radio - empty value', function() { 260 | var form = domify('
' + 261 | '' + 262 | '' + 263 | '
'); 264 | hash_check(form, {}); 265 | str_check(form, ''); 266 | empty_check(form, 'foo='); 267 | empty_check_hash(form, { 268 | 'foo':'' 269 | }); 270 | }); 271 | 272 | // in this case the radio buttons and checkboxes share a name key 273 | // the checkbox value should still be honored 274 | test('radio w/checkbox', function() { 275 | var form = domify('
' + 276 | '' + 277 | '' + 278 | '' + 279 | '' + 280 | '
'); 281 | hash_check(form, { 282 | foo: ['bar1', 'bar3'] 283 | }); 284 | str_check(form, 'foo=bar1&foo=bar3'); 285 | 286 | // leading checkbox 287 | form = domify('
' + 288 | '' + 289 | '' + 290 | '' + 291 | '' + 292 | '' + 293 | '
'); 294 | hash_check(form, { 295 | foo: ['bar3', 'bar1', 'bar5'] 296 | }); 297 | str_check(form, 'foo=bar3&foo=bar1&foo=bar5'); 298 | }); 299 | 300 | test('bracket notation - hashes', function() { 301 | var form = domify('
' + 302 | '' + 303 | '' + 304 | '' + 305 | '' + 306 | '' + 307 | '
'); 308 | 309 | hash_check(form, { 310 | account: { 311 | name: 'Foo Dude', 312 | email: 'foobar@example.org', 313 | address: { 314 | city: 'Qux', 315 | state: 'CA' 316 | } 317 | } 318 | }); 319 | 320 | empty_check_hash(form, { 321 | account: { 322 | name: 'Foo Dude', 323 | email: 'foobar@example.org', 324 | address: { 325 | city: 'Qux', 326 | state: 'CA', 327 | empty: '' 328 | } 329 | } 330 | }); 331 | }); 332 | 333 | test('bracket notation - hashes with a digit as the first symbol in a key', function() { 334 | var form = domify('
' + 335 | '' + 336 | '' + 337 | '
'); 338 | 339 | hash_check(form, { 340 | 'somekey': { 341 | '123abc': { 342 | 'first': 'first_value', 343 | 'second': 'second_value' 344 | } 345 | } 346 | }); 347 | 348 | empty_check_hash(form, { 349 | 'somekey': { 350 | '123abc': { 351 | 'first': 'first_value', 352 | 'second': 'second_value' 353 | } 354 | } 355 | }); 356 | }); 357 | 358 | test('bracket notation - select multiple', function() { 359 | var form = domify('
' + 360 | '' + 365 | '
'); 366 | 367 | hash_check(form, { 368 | foo: [ 'bar', 'qux' ] 369 | }); 370 | 371 | // Trailing notation on select.name. 372 | form = domify('
' + 373 | '' + 378 | '
'); 379 | 380 | hash_check(form, { 381 | foo: [ 'bar', 'qux' ] 382 | }); 383 | }); 384 | 385 | 386 | test('bracket notation - select multiple, nested', function() { 387 | var form = domify('
' + 388 | '' + 393 | '
'); 394 | 395 | hash_check(form, { 396 | foo: { 397 | bar: [ 'baz', 'norf' ] 398 | } 399 | }); 400 | }); 401 | 402 | test('bracket notation - select multiple, empty values', function() { 403 | var form = domify('
' + 404 | '' + 411 | '
'); 412 | 413 | hash_check(form, { 414 | foo: { 415 | bar: [ 'Default value', 'baz', 'norf' ] 416 | } 417 | }); 418 | 419 | empty_check_hash(form, { 420 | foo: { 421 | bar: [ 'Default value', '', 'baz', 'norf' ] 422 | } 423 | }); 424 | }); 425 | 426 | test('bracket notation - non-indexed arrays', function() { 427 | var form = domify('
' + 428 | '' + 429 | '' + 430 | '' + 431 | '
'); 432 | 433 | hash_check(form, { 434 | people: [ 435 | { name: "fred" }, 436 | { name: "bob" }, 437 | { name: "bubba" }, 438 | ] 439 | }); 440 | }); 441 | 442 | test('bracket notation - nested, non-indexed arrays', function() { 443 | var form = domify('
' + 444 | '' + 445 | '' + 446 | '
'); 447 | 448 | hash_check(form, { 449 | user: { 450 | tags: [ "cow", "milk" ], 451 | } 452 | }); 453 | }); 454 | 455 | test('bracket notation - indexed arrays', function() { 456 | var form = domify('
' + 457 | '' + 458 | '' + 459 | '' + 460 | '' + 461 | '' + 462 | '' + 463 | '' + 464 | '' + 465 | '
'); 466 | 467 | hash_check(form, { 468 | people: [ 469 | { 470 | name: "fred", 471 | age: "12" 472 | }, 473 | { 474 | name: "bob", 475 | age: "14" 476 | }, 477 | { 478 | name: "bubba", 479 | age: "15" 480 | }, 481 | { 482 | name: "frank", 483 | age: "2" 484 | } 485 | ] 486 | }); 487 | }); 488 | 489 | test('bracket notation - bad notation', function() { 490 | var form = domify('
' + 491 | '' + 492 | '' + 493 | '
'); 494 | 495 | hash_check(form, { 496 | _values: [ 497 | { foo: 'bar' } 498 | ], 499 | baz: { qux: 'norf' } 500 | }); 501 | }); 502 | 503 | test('custom serializer', function() { 504 | var form = domify('
/
'); 505 | 506 | assert.deepEqual(serialize(form, { 507 | serializer: function(curry, k, v) { 508 | curry[k] = 'ZUUL'; 509 | return curry; 510 | } 511 | }), { 512 | "node": "ZUUL" 513 | }); 514 | }); 515 | --------------------------------------------------------------------------------