Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
Cannot retrieve contributors at this time.
Cannot retrieve contributors at this time
| /*$AMPERSAND_VERSION*/ | |
| var View = require('ampersand-view'); | |
| var dom = require('ampersand-dom'); | |
| var matchesSelector = require('matches-selector'); | |
| var slice = Array.prototype.slice; | |
| function getMatches(el, selector) { | |
| if (selector === '') return [el]; | |
| var matches = []; | |
| if (matchesSelector(el, selector)) matches.push(el); | |
| return matches.concat(slice.call(el.querySelectorAll(selector))); | |
| } | |
| module.exports = View.extend({ | |
| template: [ | |
| '<label>', | |
| '<span data-hook="label"></span>', | |
| '<input class="form-input">', | |
| '<div data-hook="message-container" class="message message-below message-error">', | |
| '<p data-hook="message-text"></p>', | |
| '</div>', | |
| '</label>' | |
| ].join(''), | |
| bindings: { | |
| 'name': { | |
| type: 'attribute', | |
| selector: 'input, textarea', | |
| name: 'name' | |
| }, | |
| 'tabindex': { | |
| type: 'attribute', | |
| selector: 'input, textarea', | |
| name: 'tabindex' | |
| }, | |
| 'label': [ | |
| { | |
| hook: 'label' | |
| }, | |
| { | |
| type: 'toggle', | |
| hook: 'label' | |
| } | |
| ], | |
| 'message': { | |
| type: 'text', | |
| hook: 'message-text' | |
| }, | |
| 'showMessage': { | |
| type: 'toggle', | |
| hook: 'message-container' | |
| }, | |
| 'placeholder': { | |
| type: 'attribute', | |
| selector: 'input, textarea', | |
| name: 'placeholder' | |
| }, | |
| 'readonly': { | |
| type: 'booleanAttribute', | |
| name: 'readonly', | |
| selector: 'input, textarea' | |
| }, | |
| 'autofocus': { | |
| type: 'booleanAttribute', | |
| name: 'autofocus', | |
| selector: 'input, textarea' | |
| } | |
| }, | |
| initialize: function (spec) { | |
| spec || (spec = {}); | |
| this.tests = this.tests || spec.tests || []; | |
| this.on('change:type', this.handleTypeChange, this); | |
| this.handleChange = this.handleChange.bind(this); | |
| this.handleInputChanged = this.handleInputChanged.bind(this); | |
| var value = !spec.value && spec.value !== 0 ? '' : spec.value; | |
| this.startingValue = value; | |
| this.inputValue = value; | |
| this.on('change:valid change:value', this.reportToParent, this); | |
| this.on('change:validityClass', this.validityClassChanged, this); | |
| if (spec.autoRender) this.autoRender = spec.autoRender; | |
| if (spec.template) this.template = spec.template; | |
| if (spec.beforeSubmit) this.beforeSubmit = spec.beforeSubmit; | |
| }, | |
| render: function () { | |
| this.renderWithTemplate(); | |
| this.input = this.query('input') || this.query('textarea'); | |
| // switches out input for textarea if that's what we want | |
| this.handleTypeChange(); | |
| this.initInputBindings(); | |
| // Skip validation on initial setValue | |
| // if the field is not required | |
| this.setValue(this.inputValue, !this.required); | |
| return this; | |
| }, | |
| props: { | |
| inputValue: 'any', | |
| startingValue: 'any', | |
| name: 'string', | |
| type: ['string', true, 'text'], | |
| placeholder: ['string', true, ''], | |
| label: ['string', true, ''], | |
| required: ['boolean', true, true], | |
| directlyEdited: ['boolean', true, false], | |
| readonly: ['boolean', true, false], | |
| autofocus: ['boolean', true, false], | |
| shouldValidate: ['boolean', true, false], | |
| message: ['string', true, ''], | |
| requiredMessage: ['string', true, 'This field is required.'], | |
| validClass: ['string', true, 'input-valid'], | |
| invalidClass: ['string', true, 'input-invalid'], | |
| validityClassSelector: ['string', true, 'input, textarea'], | |
| tabindex: ['number', true, 0] | |
| }, | |
| derived: { | |
| value: { | |
| deps: ['inputValue'], | |
| fn: function () { | |
| return this.inputValue; | |
| } | |
| }, | |
| valid: { | |
| cache: false, | |
| deps: ['inputValue'], | |
| fn: function () { | |
| return !this.runTests(); | |
| } | |
| }, | |
| showMessage: { | |
| deps: ['message', 'shouldValidate'], | |
| fn: function () { | |
| return this.shouldValidate && this.message; | |
| } | |
| }, | |
| changed: { | |
| deps: ['inputValue', 'startingValue'], | |
| fn: function () { | |
| return this.inputValue !== this.startingValue; | |
| } | |
| }, | |
| validityClass: { | |
| deps: ['valid', 'validClass', 'invalidClass', 'shouldValidate'], | |
| fn: function () { | |
| if (!this.shouldValidate) { | |
| return ''; | |
| } else { | |
| return this.valid ? this.validClass : this.invalidClass; | |
| } | |
| } | |
| } | |
| }, | |
| setValue: function (value, skipValidation) { | |
| if (!this.input) { | |
| this.inputValue = value; | |
| return; | |
| } | |
| if (!value && value !== 0) { | |
| this.input.value = ''; | |
| } else { | |
| this.input.value = value.toString(); | |
| } | |
| this.inputValue = this.clean(this.input.value); | |
| if (!skipValidation && !this.getErrorMessage()) { | |
| this.shouldValidate = true; | |
| } else if (skipValidation) { | |
| this.shouldValidate = false; | |
| } | |
| }, | |
| getErrorMessage: function () { | |
| var message = ''; | |
| if (this.required && this.value === '') { | |
| return this.requiredMessage; | |
| } else { | |
| (this.tests || []).some(function (test) { | |
| message = test.call(this, this.value) || ''; | |
| return message; | |
| }, this); | |
| return message; | |
| } | |
| }, | |
| handleTypeChange: function () { | |
| if (this.type === 'textarea' && this.input.tagName.toLowerCase() !== 'textarea') { | |
| var parent = this.input.parentNode; | |
| var textarea = document.createElement('textarea'); | |
| parent.replaceChild(textarea, this.input); | |
| this.input = textarea; | |
| this._applyBindingsForKey(''); | |
| } else { | |
| this.input.type = this.type; | |
| } | |
| }, | |
| clean: function (val) { | |
| return (this.type === 'number') ? Number(val) : val.trim(); | |
| }, | |
| //`input` event handler | |
| handleInputChanged: function () { | |
| if (document.activeElement === this.input) { | |
| this.directlyEdited = true; | |
| } | |
| this.inputValue = this.clean(this.input.value); | |
| }, | |
| //`change` event handler | |
| handleChange: function () { | |
| if (this.inputValue && this.changed) { | |
| this.shouldValidate = true; | |
| } | |
| this.runTests(); | |
| }, | |
| beforeSubmit: function () { | |
| // catch undetected input changes that were not caught due to lack of | |
| // browser event firing see: | |
| // https://github.com/AmpersandJS/ampersand-input-view/issues/2 | |
| this.inputValue = this.clean(this.input.value); | |
| // at the point where we've tried | |
| // to submit, we want to validate | |
| // everything from now on. | |
| this.shouldValidate = true; | |
| this.runTests(); | |
| }, | |
| runTests: function () { | |
| var message = this.getErrorMessage(); | |
| if (!message && this.inputValue && this.changed) { | |
| // if it's ever been valid, | |
| // we want to validate from now | |
| // on. | |
| this.shouldValidate = true; | |
| } | |
| this.message = message; | |
| return message; | |
| }, | |
| initInputBindings: function () { | |
| this.input.addEventListener('input', this.handleInputChanged, false); | |
| this.input.addEventListener('change', this.handleChange,false); | |
| }, | |
| remove: function () { | |
| this.input.removeEventListener('input', this.handleInputChanged, false); | |
| this.input.removeEventListener('change', this.handleChange, false); | |
| View.prototype.remove.apply(this, arguments); | |
| }, | |
| reset: function () { | |
| this.setValue(this.startingValue, true); //Skip validation just like on initial render | |
| }, | |
| clear: function () { | |
| this.setValue('', true); | |
| }, | |
| validityClassChanged: function (view, newClass) { | |
| var oldClass = view.previousAttributes().validityClass; | |
| getMatches(this.el, this.validityClassSelector).forEach(function (match) { | |
| dom.switchClass(match, oldClass, newClass); | |
| }); | |
| }, | |
| reportToParent: function () { | |
| if (this.parent) this.parent.update(this); | |
| } | |
| }); |