1 /*jslint
  2     white: true,
  3     vars: true,
  4     plusplus: true
  5 */
  6 /*globals
  7     HTMLDocument,
  8     HTMLFormElement,
  9     document,
 10     module
 11 */
 12 
 13 /**
 14  * @fileOverview atropa-formdata-generator: generates a function to produce
 15  *  FormData objects based on a given HTML form.
 16  * @author <a href="mailto:matthewkastor@gmail.com">
 17  *  Matthew Christopher Kastor-Inare III </a><br />
 18  *  ☭ Hial Atropa!! ☭
 19  */
 20 
 21 /**
 22  * A function generator that takes an HTML form and writes an equivalent
 23  *  function which will produce a formData object. The function can be generated
 24  *  to accept multiple parameters or a single options object.
 25  * @author <a href="mailto:matthewkastor@gmail.com">
 26  *  Matthew Christopher Kastor-Inare III </a><br />
 27  *  ☭ Hial Atropa!! ☭
 28  * @param {HTMLFormElement} form A DOM reference to a form.
 29  * @param {String} funcName Optional. The name you want to give your generated
 30  *  function. Defaults to <code>prettyPinkBike</code>
 31  * @param {Boolean} useOptionsObj Optional. Set to true if you want the
 32  *  generated function to accept a single options object instead of multiple
 33  *  parameters.
 34  * @param {String} tab Optional. The string specified here will be used as tabs.
 35  *  Defaults to four spaces.
 36  * @param {String} eol Optional. The string specified here will be used as the
 37  *  end of line character. Defaults to <code>\r\n</code>.
 38  * @returns {String} Returns a JavaScript function as a string. This function
 39  *  will accept parameters representing the data which would be entered into the
 40  *  form elements, and will return a FormData object.
 41  */
 42 function formdataGenerator (
 43     form, funcName, useOptionsObj, tab, eol
 44 ) {
 45     "use strict";
 46     if(!(form instanceof HTMLFormElement)) {
 47         throw new TypeError('form must be an HTMLFormElement');
 48     }
 49     var defaultsQueue = [];
 50     // easier to look at vars than quotes and punctuation.
 51     var c = ',';
 52     var q = "'";
 53     var sp = ' ';
 54     var sc = ';';
 55     // configurable for the whiny sorts.
 56     tab = tab || '    ';
 57     eol = eol || '\r\n';
 58     // why do they insist on giving us useless data structures from the dom?
 59     var elementsArray = Array.prototype.slice.call(form.elements, 0);
 60     
 61     funcName = funcName || 'prettyPinkBike';
 62     // avoid stupid collisions with inherited properties.
 63     var funcArgsObj = Object.create(null);
 64     if (useOptionsObj) {
 65         funcArgsObj.options = 1;
 66     }
 67     
 68     var funcBody = tab+'var formData = new FormData()'+sc+eol+eol;
 69     
 70     /**
 71      * escapes strings so they can be boxed.
 72      * @param {String} str the string to escape.
 73      * @returns {String} Returns the escaped string.
 74      */
 75     function esc (str) {
 76         str = str || '';
 77         return str.replace('\\', '\\\\').replace('\'', '\\\'');
 78     }
 79     /**
 80      * adds an argument to the funcArgsObj for use when the generated function
 81      *  is not using an options argument.
 82      * @param {String} arg The argument to add.
 83      */
 84     function addArg (arg) {
 85         // Adds property to null object so args are guaranteed to be unique.
 86         // Takes everything that comes before the first open brace to be the
 87         // name of the argument. Boxed values attached to this name would be
 88         // properties of the argument and I have no intention of flattening
 89         // them.
 90         funcArgsObj[arg.split('[')[0]] = 1;
 91     }
 92     /**
 93      * Gets the current value of the form element and generates a default value
 94      *  from it.
 95      * @param {HTMLElement} formElement The form element to process.
 96      * @returns {String} Returns a string representing a method of assigning
 97      *  the "default value" of the element.
 98      */
 99     function getDefaultValue (val, formElement) {
100         var out = val+sp+'='+sp+q+esc(formElement.value)+q+sc;
101         var fakeFile = "new Blob(['here is a simple text file']," +
102                         "{ 'type' : 'text/plain' })"
103         ;
104         switch (formElement.type) {
105             case 'file':
106                 out = '//'+sp+val+sp+'='+sp+fakeFile+sc;
107                 break;
108             case 'checkbox':
109                 out = formElement.checked ? out : '//'+sp+out;
110                 break;
111             default:
112                 break;
113         }
114         return out;
115     }
116     /**
117      * Generates a javascript comment containing information about the
118      *  attributes of the given element.
119      * @param {HTMLElement} element The HTML element to document.
120      * @param {String} tabs The string to use as tabs, this allows for padding
121      *  the left side of the comment arbitrarily.
122      * @returns {String} Returns the comment as a string.
123      */
124     function commentGenerator (element, tabs) {
125         tabs = tabs || '';
126         var elAttrs = (
127                 Array.prototype.slice.call(element.attributes, 0).
128                 map(function (item) {
129                     return item.name + ' : ' + item.value;
130                 }).
131                 join(eol+tabs+sp+'*'+sp)
132             );
133         var out = 
134             '/**'+eol+tabs+
135             sp+'*'+sp+element.tagName+' attributes: '+eol+tabs+
136             sp+'*'+sp+elAttrs+eol+tabs+
137             sp+'*/'+eol;
138         return out;
139     }
140     /**
141      * Generates a line for the function body based on properties of the form
142      *  element given.
143      * @param {HTMLElement} The form element to process.
144      * @returns {String} Returns the line as a string.
145      */
146     function generateLine (formElement) {
147         var name = formElement.name;
148         var key = name;
149         var val = name;
150         var line;
151         // this sets up the initial value for the map reduce function
152         // if the user wants a single options arg then everything must be boxed
153         // otherwise, this will be an opitons or array argument for the final
154         // function, if it contain boxes.
155         var p = (funcArgsObj.options === 1) ? 'options' : null;
156         // if there are boxes in the name we need to quote the name and box it
157         // for proper access as a javascript identifier.
158         if(name.indexOf('[') !== -1) {
159             // split on left square bracket
160             val = name.split('[').map(function (item) {
161                 // remove the right square bracket from every element in the
162                 // array.
163                 return item.replace(']', '');
164                 // reduce the array to a single string value.
165             }).reduce(function (prev, curr) {
166                 var out;
167                 // in the initial case, if p is 'options' then concat 'options'
168                 // with the boxed representation of the first array element.
169                 // subsequent elements will be boxed and quoted.
170                 if (prev) {
171                     out = prev + '[\'' + esc(curr) + '\']';
172                     // otherwise, the first element does not need boxing.
173                     // subsequent elements will be boxed and quoted.
174                 } else {
175                     out = curr;
176                 }
177                 return out;
178             }, p);
179             // if there are no boxes in the name but the user has specified
180             // that the function should accept a single options object, then
181             // everything needs to be boxed and quoted anyway.
182         } else if (funcArgsObj.options === 1) {
183             val = 'options[\'' + esc(name) + '\']';
184         }
185         // the key is the unaltered name of the form element, the val is an
186         // identifier corresponding to one of the generated functions arguments.
187         line =
188             tab+commentGenerator(formElement, tab)+
189             tab+'formData.append('+q+key+q+c+sp+val+')'+sc+eol;
190         defaultsQueue.push(getDefaultValue(val, formElement));
191         return line;
192     }
193     /**
194      * Reducer function used to process the array of form elements into a string
195      *  representing the generated function's body.
196      * @param {String} prev The accumulated value of reduction so far.
197      * @param {String} curr The current information to process and add to
198      *  `prev`.
199      * @returns {String} Returns the value of reduction so far, to be fed into
200      *  the next invocation of this function.
201      */
202     function reducer (prev, curr) {
203         // carrying over the previous value of the function body.
204         var out = prev;
205         // if the current element doesn't have a name then it wouldn't submit
206         // usable data from the form, so skip it.
207         if(curr.name) {
208             if(!useOptionsObj) {
209                 // if not generating a function using an options object,
210                 // concatenate the form element name into the list of function
211                 // args being genrated in the parent scope.
212                 addArg(curr.name);
213             }
214             // append a new line to the function body.
215             out += generateLine(curr);
216         }
217         // return the function body in it's current state.
218         return out;
219     }
220     /**
221      * Generates the function and defaults for output.
222      * @returns {String} Returns the string value of a javascript function
223      *  which will accept arguments and generate a FormData representation of
224      *  the form processed.
225      */
226     function generateFunction () {
227         var lints = '/*jslint white: true, sub: true*/'+eol+eol+
228             '/*globals FormData */'+eol+eol;
229         var strict = tab+'"use strict"'+sc+eol;
230         // beginning with the function body, reduce the elements array to a
231         // single value using the reducer function. Replace the current function
232         // body with these results.
233         funcBody = elementsArray.reduce(reducer, funcBody);
234         // append a return statement to the function body.
235         funcBody += tab+eol+tab+'return formData'+sc+eol;
236         var formInfo = commentGenerator(form);
237         var n = funcName;
238         var b = strict+funcBody;
239         var a = Object.keys(funcArgsObj).reduce(function (prev, curr) {
240             var arg;
241             if(prev !== '') {
242                 arg = prev+c+sp+curr;
243             } else {
244                 arg = curr;
245             }
246             return arg;
247         }, '');
248         var defaults = defaultsQueue.sort().join(eol);
249         var generated = lints+formInfo+'function'+sp+n+sp+'('+a+')'+sp+
250             '{'+eol+b+'}'+eol+eol+defaults;
251         return generated;
252     }
253     
254     return generateFunction();
255 }
256 
257 // atropaFormdataGenerator (form, funcName, true);
258 
259 try {
260     module.exports = formdataGenerator;
261     //module.exports.forEveryForm = forEveryForm;
262 } catch (ignore) {
263     // module.exports does not exist.
264 }
265 
266 
267 
268