1 /** 2 * @fileOverview Library for easy forms. This library is 3 * still under development.<br/> 4 * 5 * <p>To display a QuickForm, first create one, then add the fields in order, 6 * then print the QuickForm. QuickForms take care of basic formatting like 7 * labeling input fields.</p> 8 * 9 * @example 10 import("quickforms"); 11 var form = new QuickForm({}); 12 form.addHeading("h", raw("What do <em>you</em> like?")); 13 form.addInputText("who", {label: "you are..."}); 14 form.addInputText("what", {label: "you like..."}); 15 form.addInputTextArea("why", {label: "because..."}); 16 form.addSubmit("That's why!", "That's why!"); 17 print(form); 18 */ 19 20 // TODO: should you be able to create QuickForm and QuickButton without 21 // the "this" keyword? 22 23 //---------------------------------------------------------------- 24 // globals 25 //---------------------------------------------------------------- 26 27 var _CSS = """ 28 <style> 29 30 form.quickform { 31 background-color: #fff; 32 border: 1px solid #ccc; 33 padding: 0.6em; 34 } 35 36 /*---- header ----*/ 37 38 form.quickform div.qfheader { 39 font-size: 1.0em; 40 font-weight: bold; 41 border-bottom: 1px solid #ccc; 42 margin-bottom: 1.0em; 43 padding: 0.1em 0.2em 0.2em 0; 44 } 45 46 /*---- label ----*/ 47 form.quickform div.qflabeldiv { 48 margin-top: 1.0em; 49 } 50 form.quickform label.qflabel { 51 color: #222; 52 font-weight: bold; 53 font-size: 76%; 54 } 55 form.quickform .qffield { 56 margin-bottom: 1.0em; 57 } 58 /*---- inputText ----*/ 59 form.quickform div.qfinputtextdiv { 60 } 61 form.quickform input.qfinputtext, form.quickform textarea.qfinputtextarea { 62 border: 1px solid #8faec6; 63 } 64 65 form.quickform input.qfsubmitdiv { 66 } 67 68 form.quickform input.qfsubmit { 69 margin-top: 1.0em; 70 padding: 2px 6px; 71 } 72 73 form.quickform span.surroundtext { 74 color: #555; 75 font-size: 80%; 76 } 77 78 div.qfclearfloats { clear: both; } 79 80 </style> 81 """; 82 83 page.head.write(_CSS); 84 85 //---------------------------------------------------------------- 86 // helper functions 87 //---------------------------------------------------------------- 88 function _clearfloats() { 89 return DIV({className: 'qfclearfloats'}, raw("<!-- -->")); 90 } 91 92 //---------------------------------------------------------------- 93 // QuickForm object 94 //---------------------------------------------------------------- 95 96 /** 97 * <p>Creates a new, empty QuickForm.</p> 98 * 99 * <p>Supported options (all optional):</p> 100 * <ul> 101 * <li><strong>method:</strong> "get" or "post" (defaults to "post")</li> 102 * <li><strong>action:</strong> path to submit to, defaults to current path</li> 103 * <li><strong>enctype:</strong> used to change the method of data encoding</li> 104 * </ul> 105 * 106 * @constructor 107 * @param {object} [opts] an optional dictionary of options 108 */ 109 QuickForm = function(opts) { 110 this.method = (opts && opts.method) ? opts.method.toUpperCase() : 'POST'; 111 this.action = (opts && opts.action) ? opts.action : request.path; 112 this.enctype = (opts && opts.enctype) ? opts.enctype : 113 "application/x-www-form-urlencoded"; 114 115 this.form = FORM({method: this.method, enctype: this.enctype, 116 action: this.action, className: 'quickform'}); 117 this.inputNames = []; 118 }; 119 120 /** 121 * Adds a heading in bold type that introduces a section of the form. 122 * 123 * @param {string} [id] (optional) unique id string, used for CSS rules 124 * @param {string} text the text of the heading 125 * @function 126 */ 127 QuickForm.prototype.addHeading = function(id, text) { 128 if (! text) { 129 text = id; 130 id = undefined; 131 } 132 var attribs = {className: 'qfheader'}; 133 if (id) attribs.id = id; 134 this.form.push(DIV(attribs, text)); 135 return this; 136 }; 137 138 /** 139 * <p>Creates a one-line text box for entering a small amount of text.</p> 140 * 141 * <p>Supported options (all optional):</p> 142 * <ul> 143 * <li><strong>label:</strong> text to label the field with, defaults to id</li> 144 * <li><strong>beforeText:</strong> text to put immediately before the input field</li> 145 * <li><strong>afterText:</strong> text to put immediately after the input field</li> 146 * <li><strong>value:</strong> initial text for the field</li> 147 * <li><strong>size:</strong> width of the field in characters</li> 148 * <li><strong>disabled:</strong> set to "disabled" to disable the field</li> 149 * </ul> 150 * 151 * @param {string} id a unique id string, used as a parameter name for this field 152 * @param {object} [opts] an optional dictionary of options 153 * @function 154 */ 155 QuickForm.prototype.addInputText = function(id, opts) { 156 // remember this name 157 this.inputNames.push(id); 158 159 // label 160 var labelText = (opts && opts.label) ? opts.label : id; 161 this.form.push(DIV({className: 'qflabeldiv'}, 162 LABEL({htmlFor: id, 163 className: 'qflabel'}, 164 labelText))); 165 166 // container 167 var container = DIV({className: 'inputtextdiv qffield'}); 168 169 // beforeText 170 if (opts && opts.beforeText) { 171 container.push(SPAN({className: 'surroundtext'}, opts.beforeText)); 172 } 173 174 // input 175 var attrs = {className: 'qfinputtext', 176 type: 'text', 177 name: id, 178 id: id}; 179 var quotestripper = new RegExp("\"", "g"); 180 eachProperty(opts, function(k, v) { 181 attrs[k] = String(v).replace(quotestripper, """); 182 }); 183 container.push(INPUT(attrs)); 184 185 // afterText 186 if (opts && opts.afterText) { 187 container.push(SPAN({className: 'surroundtext'}, opts.afterText)); 188 } 189 190 this.form.push(container); 191 return this; 192 }; 193 194 /** 195 * <p>Creates a rectangular, multi-line box for entering text.</p> 196 * 197 * <p>Supported options (all optional):</p> 198 * <ul> 199 * <li><strong>label:</strong> text to label the box with, defaults to id</li> 200 * <li><strong>rows:</strong> how many lines high to make the box</li> 201 * <li><strong>cols:</strong> how many characters wide to make the box</li> 202 * <li><strong>value:</strong> initial text for the text area</li> 203 * </ul> 204 * 205 * @param {string} id a unique id string, used as a parameter name for this field 206 * @param {object} [opts] an optional dictionary of options 207 * @function 208 */ 209 QuickForm.prototype.addInputTextArea = function(id, opts) { 210 // remember this name 211 this.inputNames.push(id); 212 213 // label 214 var labelText = (opts && opts.label) ? opts.label : id; 215 this.form.push(DIV({className: 'qflabeldiv'}, 216 LABEL({htmlFor: id, 217 className: 'qflabel'}, 218 labelText))); 219 220 // input 221 var rows = (opts && opts.rows) ? opts.rows : 10; 222 var cols = (opts && opts.cols) ? opts.cols : 40; 223 var value = (opts && opts.value) ? opts.value : ''; 224 this.form.push(DIV({className: 'inputtextareadiv qffield'}, 225 TEXTAREA({className: 'qfinputtextarea', 226 id: id, 227 name: id, 228 rows: rows, 229 cols: cols}, raw(value)))); 230 return this; 231 } 232 233 /** 234 * <p>Creates a control that allows the user to select a file for upload. 235 * This feature is particularly experimental.</p> 236 * 237 * <p>Supported options (all optional):</p> 238 * <ul> 239 * <li><strong>label:</strong> text to label the control with, defaults to id</li> 240 * </ul> 241 * 242 * @param {string} id a unique id string, used as a parameter name for this control 243 * @param {object} [opts] an optional dictionary of options 244 * @function 245 */ 246 QuickForm.prototype.addInputFile = function(id, opts) { 247 this.inputNames.push(id) 248 249 var labelText = (opts && opts.label) ? opts.label : id 250 this.form.push(DIV({className: 'qflabeldiv'}, 251 LABEL({htmlFor: id, 252 className: 'qflabel'}, 253 labelText))); 254 255 this.form.push(DIV({className: 'inputtextdiv qffield'}, 256 INPUT({className: 'qfinputtext', 257 type: 'file', 258 name: id, 259 id: id}))); 260 return this; 261 }; 262 263 /** 264 * Adds a hidden field to the form which does not affect display, but 265 * causes an additional parameter to be submitted with the form. 266 * @param {string} id a unique id string, used as a parameter name for 267 * this field 268 * @param {string} val the value of this parameter 269 * @function 270 */ 271 QuickForm.prototype.addInputHidden = function(id, val) { 272 this.inputNames.push(id) 273 274 this.form.push(INPUT({type: 'hidden', name: id, value: val})); 275 return this; 276 }; 277 278 // /** 279 // * Creates a drop-down menu to the form that allows a user to select 280 // * from several options. 281 // * @param {string} id a unique id string, used as a parameter name for 282 // * this field 283 // * @param {iterable} base an object that supports the 284 // * <code>forEach</code> method, and whose elements contain the 285 // * properties for the option elements. 286 // * @param {object} keys a dictionary with properties "value", 287 // * "content", and "selected", that specify the property of 288 // * <code>base</code>'s objects that should be used as the "value", 289 // * menu text, and whether that menu item is selected, respecitvely. 290 // * @param {object} [opts] an optional dictionary of options. 291 // * @function 292 // */ 293 // QuickForm.prototype.addSelect = function(id, base, keys, opts) { 294 // // label 295 // var labelText = (opts && opts.label) ? opts.label : id; 296 // this.form.push(DIV({className: 'qflabeldiv'}, 297 // LABEL({htmlFor: id, 298 // className: 'qflabel'}, 299 // labelText))); 300 301 // // container 302 // var container = DIV({className: 'selectdiv qffield'}); 303 304 // // beforeText 305 // if (opts && opts.beforeText) { 306 // container.push(SPAN({className: 'surroundtext'}, opts.beforeText)); 307 // } 308 309 // // input 310 // var attrs = {className: 'qfinputtext', 311 // type: 'text', 312 // name: id, 313 // id: id}; 314 // var quotestripper = new RegExp("\"", "g"); 315 // eachProperty(opts, function(k, v) { 316 // attrs[k] = String(v).replace(quotestripper, """); 317 // }); 318 // var select = SELECT(attrs); 319 // container.push(select); 320 321 // base.forEach(function(obj) { 322 // var attrs = {name: id, 323 // value: String(obj[keys.value]).replace(quotestripper, """)}; 324 // if (obj[keys.selected]) 325 // attrs.selected = "selected"; 326 // select.push(new OPTION(attrs, obj[keys.content])); 327 // }); 328 329 // // afterText 330 // if (opts && opts.afterText) { 331 // container.push(SPAN({className: 'surroundtext'}, opts.afterText)); 332 // } 333 334 // this.form.push(container); 335 // return this; 336 // } 337 338 339 /** 340 * Adds a submit button to this form. 341 * @param {string} id a unique id string, used as a parameter name for this field 342 * @param {string} text the text on the button, and also the value of the parameter 343 * @function 344 */ 345 QuickForm.prototype.addSubmit = function(id, text) { 346 this.form.push(DIV({className: 'qfsubmitdiv qffield'}, 347 INPUT({type: 'submit', 348 name: id, 349 value: text, 350 className: 'qfsubmit'}))); 351 return this; 352 }; 353 354 /** 355 * Does basic checking to determine whether the current request is a valid 356 * submission of this form. 357 * 358 * @function 359 * @return {boolean} 360 */ 361 QuickForm.prototype.validate = function() { 362 // what to do here? 363 // eventually: 364 // run validation functions on form input 365 // X make sure request method matches form's method 366 // if returns false, pre-fill existing values with stuff 367 // make sure request path matches the form's action 368 // X make sure all the input parameters are present 369 // 370 371 if (request.method != this.method) { 372 return false; 373 } 374 375 var hasparams = true; 376 this.inputNames.forEach(function(name) { 377 if (!request.param(name)) { 378 hasparams = false; 379 } 380 }); 381 if (!hasparams) { 382 return false; 383 } 384 385 return true; 386 }; 387 388 /** 389 * Assuming that the current request is a form submission of this QuickForm, 390 * assembles an object with a property for each form field. 391 * @function 392 * @return {object} 393 */ 394 QuickForm.prototype.getInput = function() { 395 var input = {}; 396 this.inputNames.forEach(function(n) { 397 input[n] = request.param(n); 398 }); 399 return input; 400 }; 401 402 /** 403 * Converts this QuickForm to HTML mark-up. This is called for you 404 * when you print a QuickForm. 405 * @function 406 * @return {string} html-formatted string. 407 */ 408 QuickForm.prototype.toHTML = function() { 409 return this.form.toHTML(); 410 }; 411 412 413 414 //---------------------------------------------------------------- 415 // QuickButton 416 //---------------------------------------------------------------- 417 418 /** 419 * Creates a QuickButton, a self-contained button that when pressed 420 * submits a form that contains a bunch of parameter/value pairs. 421 * 422 * <p>Supported options (all optional):</p> 423 * <ul> 424 * <li><strong>method:</strong> "get" or "post" (defaults to "get")</li> 425 * <li><strong>action:</strong> path to submit to, defaults to current path</li> 426 * </ul> 427 * 428 * @param {string} label a label for the button 429 * @param {object} opts a dictionary of options 430 * @param {object} inputs a dictionary of parameter/value mappings to submit 431 * @constructor 432 */ 433 function QuickButton(label, opts, inputs) { 434 var method = (opts && opts.method) ? opts.method : 'GET'; 435 var action = (opts && opts.action) ? opts.action : request.path; 436 var form = FORM({method: method, action: action, style: 'display: inline;', className: 'quickbutton'}); 437 438 eachProperty(inputs, function(k, v) { 439 form.push(INPUT({type: 'hidden', name: k, value: v})); 440 }); 441 form.push(INPUT({type: 'submit', value: label, style: 'display: inline;'})); 442 this.form = form; 443 } 444 445 /** 446 * Converts this QuickButton to HTML mark-up. This is called for you 447 * when you print a QuickButton. 448 * @function 449 * @return {string} html-formatted string. 450 */ 451 QuickButton.prototype.toHTML = function() { 452 return this.form.toHTML(); 453 }; 454 455 456 457