UNPKG

24.4 kBJavaScriptView Raw
1(function(e){if("function"==typeof bootstrap)bootstrap("reformer",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeReformer=e}else"undefined"!=typeof window?window.Reformer=e():global.Reformer=e()})(function(){var define,ses,bootstrap,module,exports;
2return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
3// nothing to see here... no file methods for the browser
4
5},{}],2:[function(require,module,exports){
6
7/**
8 * Expose `parse`.
9 */
10
11module.exports = parse;
12
13/**
14 * Wrap map from jquery.
15 */
16
17var map = {
18 option: [1, '<select multiple="multiple">', '</select>'],
19 optgroup: [1, '<select multiple="multiple">', '</select>'],
20 legend: [1, '<fieldset>', '</fieldset>'],
21 thead: [1, '<table>', '</table>'],
22 tbody: [1, '<table>', '</table>'],
23 tfoot: [1, '<table>', '</table>'],
24 colgroup: [1, '<table>', '</table>'],
25 caption: [1, '<table>', '</table>'],
26 tr: [2, '<table><tbody>', '</tbody></table>'],
27 td: [3, '<table><tbody><tr>', '</tr></tbody></table>'],
28 th: [3, '<table><tbody><tr>', '</tr></tbody></table>'],
29 col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
30 _default: [0, '', '']
31};
32
33/**
34 * Parse `html` and return the children.
35 *
36 * @param {String} html
37 * @return {Array}
38 * @api private
39 */
40
41function parse(html) {
42 if ('string' != typeof html) throw new TypeError('String expected');
43
44 // tag name
45 var m = /<([\w:]+)/.exec(html);
46 if (!m) throw new Error('No elements were generated.');
47 var tag = m[1];
48
49 // body support
50 if (tag == 'body') {
51 var el = document.createElement('html');
52 el.innerHTML = html;
53 return el.removeChild(el.lastChild);
54 }
55
56 // wrap map
57 var wrap = map[tag] || map._default;
58 var depth = wrap[0];
59 var prefix = wrap[1];
60 var suffix = wrap[2];
61 var el = document.createElement('div');
62 el.innerHTML = prefix + html + suffix;
63 while (depth--) el = el.lastChild;
64
65 var els = el.children;
66 if (1 == els.length) {
67 return el.removeChild(els[0]);
68 }
69
70 var fragment = document.createDocumentFragment();
71 while (els.length) {
72 fragment.appendChild(el.removeChild(els[0]));
73 }
74
75 return fragment;
76}
77
78},{}],3:[function(require,module,exports){
79var templates = require('./templates');
80var domify = require('domify');
81
82
83// from caolan's async.js lib
84// async.forEach method
85function asyncForEach(arr, iterator, cb) {
86 if (!arr.length) return cb();
87 var completed = 0;
88 arr.forEach(function (x) {
89 iterator(x, function (err) {
90 if (err) {
91 cb(err);
92 cb = function () {};
93 } else {
94 completed += 1;
95 if (completed === arr.length) {
96 cb();
97 }
98 }
99 });
100 });
101}
102
103// shim/helper for trimming values
104function trim(val) {
105 if (!val || !val.trim) return '';
106 return val.trim();
107}
108
109
110function Reformer(spec) {
111 var self = this;
112 var f = function () {}; // empty func
113 var item;
114
115 this.settings = {
116 error: f,
117 submit: f,
118 reqMessage: 'This field is required',
119 html5Validation: true,
120 errorPlacement: 'before'
121 };
122
123 this.fieldDefinition = spec.fields;
124 this.fields = [];
125 this.id = spec.id;
126 this.formEl = spec.formEl;
127 this.fieldContainer = spec.fieldContainer;
128 this.initialData = spec.data || {};
129 delete spec.fields;
130
131 // apply options
132 for (item in spec) {
133 this.settings[item] = spec[item];
134 }
135
136 // apply field options
137 this.fieldDefinition.forEach(function (field) {
138 field.parent = self;
139 self.fields.push(new Field(field));
140 });
141}
142
143Reformer.prototype.render = function (opts, updateData) {
144 var self = this;
145 var options = opts || {};
146
147 if (options.formEl) this.formEl = options.formEl;
148 if (options.fieldContainer) this.fieldContainer = options.fieldContainer;
149
150 if (!this.rendered) {
151 this.formEl.addEventListener('submit', function (e) {
152 e.preventDefault(); // stop submit, always
153 }, true);
154
155 this.addButtonHandlers();
156 }
157
158 // track that we've rendered
159 this.rendered = true;
160
161 this.fields.forEach(function (field) {
162 field.render(updateData);
163 });
164
165 return this.formEl;
166};
167
168Reformer.prototype.addButtonHandlers = function () {
169 var self = this;
170 var buttons = this.formEl.getElementsByTagName('button', this.dom);
171 var i = 0;
172 var l = buttons.length;
173
174 for (; i < l; i++) {
175 buttons[i].addEventListener('click', function (e) {
176 var cls = e.target.className;
177 var handler;
178
179 if (self.submitRe.test(cls) || e.target.type === 'submit') {
180 self.handleSubmit();
181 e.stopPropagation();
182 return false;
183 } else {
184 handler = self.settings[cls];
185 if (handler) {
186 e.preventDefault();
187 e.stopPropagation();
188 handler();
189 return false;
190 }
191 }
192 // fall through
193 }, true);
194 }
195};
196
197// reset the form, apply new initial data if passed
198Reformer.prototype.reset = function (data) {
199 if (data) this.initialData = data;
200 this.render({}, true);
201};
202
203Reformer.prototype.handleSubmit = function (e) {
204 var self = this;
205 if (self.settings.preSubmit) self.settings.preSubmit.call(self);
206 this.validate(function (valid) {
207 self.render();
208 if (valid) {
209 var data = self.data();
210 self.settings.submit.call(self, data, self.diffData(data));
211 } else {
212 self.settings.error.call(self);
213 }
214 });
215};
216
217Reformer.prototype.data = function () {
218 var results = {};
219 this.fields.forEach(function (field) {
220 // trim if setting and available for brower and value type
221 results[field.name] = field.data();
222 });
223 if (this.settings.clean) {
224 results = this.settings.clean(results);
225 }
226 return results;
227};
228
229Reformer.prototype.errors = function () {
230 var results = {};
231 this.fields.forEach(function (field) {
232 if (field.errors.length) results[field.name] = field.errors;
233 });
234 return results;
235};
236
237Reformer.prototype.diffData = function (newData) {
238 var orig = this.initialData,
239 diff = {},
240 changed;
241 for (var key in newData) {
242 if (newData[key] !== orig[key]) {
243 changed = true;
244 diff[key] = newData[key];
245 }
246 }
247 return changed ? diff : undefined;
248};
249
250// quick way to populate form for testing
251Reformer.prototype.loadDummyData = function () {
252 this.fields.forEach(function (field) {
253 if (field.dummy && !field.value) field.setValue(field.dummy);
254 });
255};
256
257Reformer.prototype.clearAll = function () {
258 this.fields.forEach(function (field) {
259 field.inputEl.value = '';
260 field.errors = [];
261 });
262 return true;
263};
264
265Reformer.prototype.validate = function (cb) {
266 var self = this,
267 isValid = true;
268
269 // async loop for each field
270 asyncForEach(this.fields, function (field, fieldLoopCb) {
271 field.validate(function (valid) {
272 if (!valid) {
273 isValid = false;
274 }
275 fieldLoopCb();
276 });
277 }, function () {
278 cb(isValid);
279 });
280};
281
282Reformer.prototype.getField = function (name) {
283 var found;
284 this.fields.some(function (field) {
285 if (field.name === name || field.id === name) {
286 found = field;
287 return true;
288 }
289 });
290 return found;
291};
292
293Reformer.prototype.submitRe = /(^|\s)submit(\s|$)/;
294
295
296function Field(opts) {
297 var item;
298 var parentData = opts.parent.initialData;
299
300 this.errors = [];
301 this.type = 'text';
302 this.tests = [];
303 this.textarea = opts.widget === 'textarea';
304 this.select = opts.widget === 'select' && opts.hasOwnProperty('options');
305 this.input = opts.hasOwnProperty('widget') ? opts.widget === 'input' : true;
306 this.errorPlacement = opts.errorPlacement || opts.parent.settings.errorPlacement;
307 this.trim = true;
308
309 for (item in opts) {
310 this[item] = opts[item];
311 }
312
313 // make sure we've always got an id
314 this.id = opts.id || opts.name;
315 if (!this.id) throw new Error('All fields need either a name or id attribute');
316
317 // make sure container is an element if passed
318 this.containerEl = function () {
319 var type = typeof opts.containerEl;
320 if (type === 'string') {
321 return document.getElementById(opts.containerEl);
322 } else if (type === 'object') {
323 return opts.containerEl;
324 }
325 }();
326
327 this.containerId = this.containerEl && this.containerEl.id || (this.id + '_parent');
328
329 // set our value if we've got one
330 if (parentData && parentData.hasOwnProperty(this.name)) {
331 this.setValue(parentData[this.name]);
332 } else {
333 this.setValue('');
334 }
335
336 this.initial = this.value;
337}
338
339Field.prototype.render = function (resetData) {
340 var newEl = domify(templates.field({field: this}));
341 var parentNode;
342
343 // if we got a string go find that id
344 if (typeof this.fieldContainer === 'string') {
345 this.fieldContainer = this.parent.formEl.querySelector('#' + this.fieldContainer);
346 }
347
348 // handles first render if not passed a container
349 if (!this.fieldContainer) {
350 this.fieldContainer = newEl;
351 this.parent.fieldContainer.appendChild(newEl);
352 } else {
353 // replaces current with whatever
354 parentNode = this.fieldContainer.parentNode;
355 parentNode.replaceChild(newEl, this.fieldContainer);
356 this.fieldContainer = newEl;
357 }
358
359 // store some references
360 this.inputEl = this.fieldContainer.querySelector('[name="'+ this.name +'"]');
361 this.labelEl = this.fieldContainer.querySelector('label[for="'+ this.id +'"]');
362
363 if (resetData) {
364 this.resetValue();
365 } else {
366 this.setValue(this.value);
367 }
368
369 this.registerHandlers();
370
371 // call our setup function on first render
372 if (!this.rendered && this.setup) {
373 this.setup();
374 }
375
376 this.rendered = true;
377};
378
379Field.prototype.registerHandlers = function () {
380 var self = this;
381 this.inputEl.addEventListener('input', function (e) {
382 self.handleInputChange.apply(self, arguments);
383 }, true);
384 this.inputEl.addEventListener('blur', function (e) {
385 self.handleInputChange.apply(self, arguments);
386 }, true);
387 this.inputEl.addEventListener('change', function (e) {
388 self.handleInputChange.apply(self, arguments);
389 }, true);
390 this.inputEl.addEventListener('invalid', function (e) {
391 e.preventDefault();
392 }, true);
393};
394
395Field.prototype.handleInputChange = function (e) {
396 var inputEl = this.inputEl;
397 var type = this.type;
398 this.value = function () {
399 if (['range', 'number'].indexOf(type) !== -1) {
400 return inputEl.valueAsNumber;
401 } else if (type === 'date') {
402 return inputEl.valueAsDate;
403 } else {
404 return inputEl.value;
405 }
406 }();
407};
408
409Field.prototype.resetValue = function () {
410 var newVal = this.parent.initialData[this.name] || '';
411 this.setValue(newVal);
412 this.initial = newVal;
413};
414
415Field.prototype.setValue = function (val) {
416 if (this.inputEl) this.inputEl.value = val;
417 this.value = val;
418};
419
420Field.prototype.data = function () {
421 if (this.clean) {
422 return this.clean(this.value);
423 } else if (this.trim) {
424 return trim(this.value);
425 } else {
426 return this.value;
427 }
428};
429
430Field.prototype.validate = function (cb) {
431 var self = this;
432 var tests = this.tests instanceof Array ? this.tests : [this.tests];
433 var isValid = true;
434
435 this.errors = []; // clear errors
436 if (this.required && (!this.value || this.value.length === 0)) {
437 isValid = false;
438 this.errors.push(this.reqMessage || this.parent.settings.reqMessage);
439 }
440
441 // html5 error message handling
442 if (this.parent.settings.html5Validation && this.inputEl.validationMessage) {
443 isValid = false;
444 this.errors.push(this.inputEl.validationMessage);
445 }
446
447 // async loop for each test
448 asyncForEach(tests, function (test, loopCb) {
449 var passed = false;
450 // if we're ignoring same values.. carry on
451 if (test.async) {
452 test.test.call(self, self.value, self, function (passed) {
453 if (!passed) {
454 isValid = false;
455 self.errors.push(test.message);
456 }
457 loopCb(null, passed);
458 });
459 } else {
460 passed = test.test.call(self, self.value, self);
461 if (!passed) {
462 isValid = false;
463 self.errors.push(test.message);
464 }
465 loopCb(null, passed);
466 }
467 }, function () {
468 cb(isValid);
469 });
470};
471
472// simplifies turning potential values into stuff we want
473// in the template
474Field.prototype.get = function (name, altReturn) {
475 var alt = altReturn || null;
476 if (typeof this[name] === 'undefined') {
477 return alt;
478 } else {
479 return this[name];
480 }
481};
482
483module.exports = Reformer;
484
485},{"./templates":4,"domify":2}],4:[function(require,module,exports){
486(function () {
487var root = this, exports = {};
488
489// The jade runtime:
490var jade=function(exports){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==Object.prototype.toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(key);return arr}),exports.merge=function merge(a,b){var ac=a["class"],bc=b["class"];if(ac||bc)ac=ac||[],bc=bc||[],Array.isArray(ac)||(ac=[ac]),Array.isArray(bc)||(bc=[bc]),ac=ac.filter(nulls),bc=bc.filter(nulls),a["class"]=ac.concat(bc).join(" ");for(var key in b)key!="class"&&(a[key]=b[key]);return a};function nulls(val){return val!=null}return exports.attrs=function attrs(obj,escaped){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];"boolean"==typeof val||null==val?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):0==key.indexOf("data")&&"string"!=typeof val?buf.push(key+"='"+JSON.stringify(val)+"'"):"class"==key&&Array.isArray(val)?buf.push(key+'="'+exports.escape(val.join(" "))+'"'):escaped&&escaped[key]?buf.push(key+'="'+exports.escape(val)+'"'):buf.push(key+'="'+val+'"')}}return buf.join(" ")},exports.escape=function escape(html){return String(html).replace(/&(?!(\w+|\#\d+);)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err},exports}({});
491
492// create our folder objects
493
494// errors.jade compiled template
495exports.errors = function anonymous(locals) {
496 var buf = [];
497 with (locals || {}) {
498 var hasErrors = !!field.errors.length;
499 if (hasErrors) {
500 buf.push('<div class="errorContainer">');
501 (function() {
502 var $$obj = field.errors;
503 if ("number" == typeof $$obj.length) {
504 for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
505 var error = $$obj[$index];
506 if (error) {
507 buf.push('<span class="error">' + (null == (jade.interp = error) ? "" : jade.interp) + "</span>");
508 }
509 }
510 } else {
511 var $$l = 0;
512 for (var $index in $$obj) {
513 $$l++;
514 if ($$obj.hasOwnProperty($index)) {
515 var error = $$obj[$index];
516 if (error) {
517 buf.push('<span class="error">' + (null == (jade.interp = error) ? "" : jade.interp) + "</span>");
518 }
519 }
520 }
521 }
522 }).call(this);
523 buf.push("</div>");
524 }
525 }
526 return buf.join("");
527};
528
529// field.jade compiled template
530exports.field = function anonymous(locals) {
531 var buf = [];
532 with (locals || {}) {
533 var hidden = field.type === "hidden";
534 buf.push("<div" + jade.attrs({
535 id: field.containerId,
536 "class": "fieldContainer clearfix" + (field.errors.length ? " error" : "")
537 }, {
538 id: true,
539 "class": true
540 }) + ">");
541 if (field.type !== "hidden") {
542 if (field.errorPlacement === "before") {
543 var hasErrors = !!field.errors.length;
544 if (hasErrors) {
545 buf.push('<div class="errorContainer">');
546 (function() {
547 var $$obj = field.errors;
548 if ("number" == typeof $$obj.length) {
549 for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
550 var error = $$obj[$index];
551 if (error) {
552 buf.push('<span class="error">' + (null == (jade.interp = error) ? "" : jade.interp) + "</span>");
553 }
554 }
555 } else {
556 var $$l = 0;
557 for (var $index in $$obj) {
558 $$l++;
559 if ($$obj.hasOwnProperty($index)) {
560 var error = $$obj[$index];
561 if (error) {
562 buf.push('<span class="error">' + (null == (jade.interp = error) ? "" : jade.interp) + "</span>");
563 }
564 }
565 }
566 }
567 }).call(this);
568 buf.push("</div>");
569 }
570 }
571 if (field.label) {
572 buf.push("<label" + jade.attrs({
573 "for": field.id
574 }, {
575 "for": true
576 }) + ">" + jade.escape(null == (jade.interp = field.label) ? "" : jade.interp) + "</label>");
577 }
578 if (field.textarea) {
579 buf.push("<textarea" + jade.attrs({
580 id: field.id,
581 name: field.name,
582 placeholder: field.placeholder
583 }, {
584 id: true,
585 name: true,
586 placeholder: true
587 }) + "></textarea>");
588 }
589 if (field.select) {
590 buf.push("<select" + jade.attrs({
591 id: field.id,
592 name: field.name,
593 "class": field.class
594 }, {
595 id: true,
596 name: true,
597 "class": true
598 }) + ">");
599 (function() {
600 var $$obj = field.options;
601 if ("number" == typeof $$obj.length) {
602 for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
603 var option = $$obj[$index];
604 buf.push("<option" + jade.attrs({
605 value: option.val
606 }, {
607 value: true
608 }) + ">" + jade.escape(null == (jade.interp = option.text) ? "" : jade.interp) + "</option>");
609 }
610 } else {
611 var $$l = 0;
612 for (var $index in $$obj) {
613 $$l++;
614 if ($$obj.hasOwnProperty($index)) {
615 var option = $$obj[$index];
616 buf.push("<option" + jade.attrs({
617 value: option.val
618 }, {
619 value: true
620 }) + ">" + jade.escape(null == (jade.interp = option.text) ? "" : jade.interp) + "</option>");
621 }
622 }
623 }
624 }).call(this);
625 buf.push("</select>");
626 }
627 }
628 if (field.input) {
629 buf.push("<input" + jade.attrs({
630 id: field.id,
631 min: field.get("min"),
632 max: field.get("max"),
633 step: field.get("step"),
634 placeholder: field.get("placeholder"),
635 type: field.type,
636 name: field.name,
637 autocomplete: field.autocomplete || null
638 }, {
639 id: true,
640 min: true,
641 max: true,
642 step: true,
643 placeholder: true,
644 type: true,
645 name: true,
646 autocomplete: true
647 }) + "/>");
648 }
649 if (field.errorPlacement === "after" && field.type !== "hidden") {
650 var hasErrors = !!field.errors.length;
651 if (hasErrors) {
652 buf.push('<div class="errorContainer">');
653 (function() {
654 var $$obj = field.errors;
655 if ("number" == typeof $$obj.length) {
656 for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
657 var error = $$obj[$index];
658 if (error) {
659 buf.push('<span class="error">' + (null == (jade.interp = error) ? "" : jade.interp) + "</span>");
660 }
661 }
662 } else {
663 var $$l = 0;
664 for (var $index in $$obj) {
665 $$l++;
666 if ($$obj.hasOwnProperty($index)) {
667 var error = $$obj[$index];
668 if (error) {
669 buf.push('<span class="error">' + (null == (jade.interp = error) ? "" : jade.interp) + "</span>");
670 }
671 }
672 }
673 }
674 }).call(this);
675 buf.push("</div>");
676 }
677 }
678 if (field.type !== "hidden" && field.helpText) {
679 buf.push("<p" + jade.attrs({
680 id: field.id + "_helpText",
681 "class": "helpText"
682 }, {
683 id: true
684 }) + ">" + jade.escape(null == (jade.interp = field.helpText) ? "" : jade.interp) + "</p>");
685 }
686 buf.push("</div>");
687 }
688 return buf.join("");
689};
690
691// form.jade compiled template
692exports.form = function anonymous(locals) {
693 var buf = [];
694 with (locals || {}) {
695 buf.push('<form><div class="fields"></div><button type="submit">Submit</button><button type="reset">Cancel</button></form>');
696 }
697 return buf.join("");
698};
699
700
701// attach to window or export with commonJS
702if (typeof module !== "undefined") {
703 module.exports = exports;
704} else if (typeof define === "function" && define.amd) {
705 define(exports);
706} else {
707 root.templatizer = exports;
708}
709
710})();
711},{"fs":1}]},{},[3])(3)
712});
713;
\No newline at end of file