'use strict'; var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var inputType = { /** * @ngdoc inputType * @name angular.module.ng.$compileProvider.directive.input.text * * @description * Standard HTML text input with angular data binding. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. * @param {number=} ng-minlength Sets `MINLENGTH` validation error key if the value is shorter than * minlength. * @param {number=} ng-maxlength Sets `MAXLENGTH` validation error key if the value is longer than * maxlength. * @param {string=} ng-pattern Sets `PATTERN` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ng-change Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
Single word: Required! Single word only! text = {{text}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
it('should initialize to model', function() { expect(binding('text')).toEqual('guest'); expect(binding('myForm.input.$valid')).toEqual('true'); }); it('should be invalid if empty', function() { input('text').enter(''); expect(binding('text')).toEqual(''); expect(binding('myForm.input.$valid')).toEqual('false'); }); it('should be invalid if multi word', function() { input('text').enter('hello world'); expect(binding('myForm.input.$valid')).toEqual('false'); });
*/ 'text': textInputType, /** * @ngdoc inputType * @name angular.module.ng.$compileProvider.directive.input.number * * @description * Text input with number validation and transformation. Sets the `NUMBER` validation * error if not a valid number. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `MIN` validation error key if the value entered is less then `min`. * @param {string=} max Sets the `MAX` validation error key if the value entered is greater then `min`. * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. * @param {number=} ng-minlength Sets `MINLENGTH` validation error key if the value is shorter than * minlength. * @param {number=} ng-maxlength Sets `MAXLENGTH` validation error key if the value is longer than * maxlength. * @param {string=} ng-pattern Sets `PATTERN` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ng-change Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
Number: Required! Not valid number! value = {{value}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
it('should initialize to model', function() { expect(binding('value')).toEqual('12'); expect(binding('myForm.input.$valid')).toEqual('true'); }); it('should be invalid if empty', function() { input('value').enter(''); expect(binding('value')).toEqual(''); expect(binding('myForm.input.$valid')).toEqual('false'); }); it('should be invalid if over max', function() { input('value').enter('123'); expect(binding('value')).toEqual(''); expect(binding('myForm.input.$valid')).toEqual('false'); });
*/ 'number': numberInputType, /** * @ngdoc inputType * @name angular.module.ng.$compileProvider.directive.input.url * * @description * Text input with URL validation. Sets the `URL` validation error key if the content is not a * valid URL. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. * @param {number=} ng-minlength Sets `MINLENGTH` validation error key if the value is shorter than * minlength. * @param {number=} ng-maxlength Sets `MAXLENGTH` validation error key if the value is longer than * maxlength. * @param {string=} ng-pattern Sets `PATTERN` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ng-change Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
URL: Required! Not valid url! text = {{text}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
myForm.$error.url = {{!!myForm.$error.url}}
it('should initialize to model', function() { expect(binding('text')).toEqual('http://google.com'); expect(binding('myForm.input.$valid')).toEqual('true'); }); it('should be invalid if empty', function() { input('text').enter(''); expect(binding('text')).toEqual(''); expect(binding('myForm.input.$valid')).toEqual('false'); }); it('should be invalid if not url', function() { input('text').enter('xxx'); expect(binding('myForm.input.$valid')).toEqual('false'); });
*/ 'url': urlInputType, /** * @ngdoc inputType * @name angular.module.ng.$compileProvider.directive.input.email * * @description * Text input with email validation. Sets the `EMAIL` validation error key if not a valid email * address. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. * @param {number=} ng-minlength Sets `MINLENGTH` validation error key if the value is shorter than * minlength. * @param {number=} ng-maxlength Sets `MAXLENGTH` validation error key if the value is longer than * maxlength. * @param {string=} ng-pattern Sets `PATTERN` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * * @example
Email: Required! Not valid email! text = {{text}}
myForm.input.$valid = {{myForm.input.$valid}}
myForm.input.$error = {{myForm.input.$error}}
myForm.$valid = {{myForm.$valid}}
myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
myForm.$error.EMAIL = {{!!myForm.$error.EMAIL}}
it('should initialize to model', function() { expect(binding('text')).toEqual('me@example.com'); expect(binding('myForm.input.$valid')).toEqual('true'); }); it('should be invalid if empty', function() { input('text').enter(''); expect(binding('text')).toEqual(''); expect(binding('myForm.input.$valid')).toEqual('false'); }); it('should be invalid if not email', function() { input('text').enter('xxx'); expect(binding('myForm.input.$valid')).toEqual('false'); });
*/ 'email': emailInputType, /** * @ngdoc inputType * @name angular.module.ng.$compileProvider.directive.input.radio * * @description * HTML radio button. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string} value The value to which the expression should be set when selected. * @param {string=} name Property name of the form under which the control is published. * @param {string=} ng-change Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
Red
Green
Blue
color = {{color}}
it('should change state', function() { expect(binding('color')).toEqual('blue'); input('color').select('red'); expect(binding('color')).toEqual('red'); });
*/ 'radio': radioInputType, /** * @ngdoc inputType * @name angular.module.ng.$compileProvider.directive.input.checkbox * * @description * HTML checkbox. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} ng-true-value The value to which the expression should be set when selected. * @param {string=} ng-false-value The value to which the expression should be set when not selected. * @param {string=} ng-change Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
Value1:
Value2:
value1 = {{value1}}
value2 = {{value2}}
it('should change state', function() { expect(binding('value1')).toEqual('true'); expect(binding('value2')).toEqual('YES'); input('value1').check(); input('value2').check(); expect(binding('value1')).toEqual('false'); expect(binding('value2')).toEqual('NO'); });
*/ 'checkbox': checkboxInputType, 'hidden': noop, 'button': noop, 'submit': noop, 'reset': noop }; function isEmpty(value) { return isUndefined(value) || value === '' || value === null || value !== value; } function textInputType(scope, element, attr, ctrl) { element.bind('blur', function() { scope.$apply(function() { ctrl.$setViewValue(trim(element.val())); }); }); ctrl.$render = function() { element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); }; // pattern validator var pattern = attr.ngPattern, patternValidator; var validate = function(regexp, value) { if (isEmpty(value) || regexp.test(value)) { ctrl.$setValidity('PATTERN', true); return value; } else { ctrl.$setValidity('PATTERN', false); return undefined; } }; if (pattern) { if (pattern.match(/^\/(.*)\/$/)) { pattern = new RegExp(pattern.substr(1, pattern.length - 2)); patternValidator = function(value) { return validate(pattern, value) }; } else { patternValidator = function(value) { var patternObj = scope.$eval(pattern); if (!patternObj || !patternObj.test) { throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj); } return validate(patternObj, value); }; } ctrl.$formatters.push(patternValidator); ctrl.$parsers.push(patternValidator); } // min length validator if (attr.ngMinlength) { var minlength = parseInt(attr.ngMinlength, 10); var minLengthValidator = function(value) { if (!isEmpty(value) && value.length < minlength) { ctrl.$setValidity('MINLENGTH', false); return undefined; } else { ctrl.$setValidity('MINLENGTH', true); return value; } }; ctrl.$parsers.push(minLengthValidator); ctrl.$formatters.push(minLengthValidator); } // max length validator if (attr.ngMaxlength) { var maxlength = parseInt(attr.ngMaxlength, 10); var maxLengthValidator = function(value) { if (!isEmpty(value) && value.length > maxlength) { ctrl.$setValidity('MAXLENGTH', false); return undefined; } else { ctrl.$setValidity('MAXLENGTH', true); return value; } }; ctrl.$parsers.push(maxLengthValidator); ctrl.$formatters.push(maxLengthValidator); } }; function numberInputType(scope, element, attr, ctrl) { textInputType(scope, element, attr, ctrl); ctrl.$parsers.push(function(value) { var empty = isEmpty(value); if (empty || NUMBER_REGEXP.test(value)) { ctrl.$setValidity('NUMBER', true); return value === '' ? null : (empty ? value : parseFloat(value)); } else { ctrl.$setValidity('NUMBER', false); return undefined; } }); ctrl.$formatters.push(function(value) { return isEmpty(value) ? '' : '' + value; }); if (attr.min) { var min = parseFloat(attr.min); var minValidator = function(value) { if (!isEmpty(value) && value < min) { ctrl.$setValidity('MIN', false); return undefined; } else { ctrl.$setValidity('MIN', true); return value; } }; ctrl.$parsers.push(minValidator); ctrl.$formatters.push(minValidator); } if (attr.max) { var max = parseFloat(attr.max); var maxValidator = function(value) { if (!isEmpty(value) && value > max) { ctrl.$setValidity('MAX', false); return undefined; } else { ctrl.$setValidity('MAX', true); return value; } }; ctrl.$parsers.push(maxValidator); ctrl.$formatters.push(maxValidator); } ctrl.$formatters.push(function(value) { if (isEmpty(value) || isNumber(value)) { ctrl.$setValidity('NUMBER', true); return value; } else { ctrl.$setValidity('NUMBER', false); return undefined; } }); } function urlInputType(scope, element, attr, ctrl) { textInputType(scope, element, attr, ctrl); var urlValidator = function(value) { if (isEmpty(value) || URL_REGEXP.test(value)) { ctrl.$setValidity('URL', true); return value; } else { ctrl.$setValidity('URL', false); return undefined; } }; ctrl.$formatters.push(urlValidator); ctrl.$parsers.push(urlValidator); } function emailInputType(scope, element, attr, ctrl) { textInputType(scope, element, attr, ctrl); var emailValidator = function(value) { if (isEmpty(value) || EMAIL_REGEXP.test(value)) { ctrl.$setValidity('EMAIL', true); return value; } else { ctrl.$setValidity('EMAIL', false); return undefined; } }; ctrl.$formatters.push(emailValidator); ctrl.$parsers.push(emailValidator); } function radioInputType(scope, element, attr, ctrl) { // correct the name element.attr('name', attr.id + '@' + attr.name); element.bind('click', function() { if (element[0].checked) { scope.$apply(function() { ctrl.$setViewValue(attr.value); }); }; }); ctrl.$render = function() { var value = attr.value; element[0].checked = isDefined(value) && (value == ctrl.$viewValue); }; } function checkboxInputType(scope, element, attr, ctrl) { var trueValue = attr.ngTrueValue, falseValue = attr.ngFalseValue; if (!isString(trueValue)) trueValue = true; if (!isString(falseValue)) falseValue = false; element.bind('click', function() { scope.$apply(function() { ctrl.$setViewValue(element[0].checked); }); }); ctrl.$render = function() { element[0].checked = ctrl.$viewValue; }; ctrl.$formatters.push(function(value) { return value === trueValue; }); ctrl.$parsers.push(function(value) { return value ? trueValue : falseValue; }); } /** * @ngdoc directive * @name angular.module.ng.$compileProvider.directive.textarea * * @description * HTML textarea element control with angular data-binding. The data-binding and validation * properties of this element are exactly the same as those of the * {@link angular.module.ng.$compileProvider.directive.input input element}. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. * @param {number=} ng-minlength Sets `MINLENGTH` validation error key if the value is shorter than * minlength. * @param {number=} ng-maxlength Sets `MAXLENGTH` validation error key if the value is longer than * maxlength. * @param {string=} ng-pattern Sets `PATTERN` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ng-change Angular expression to be executed when input changes due to user * interaction with the input element. */ /** * @ngdoc directive * @name angular.module.ng.$compileProvider.directive.input * @restrict E * * @description * HTML input element control with angular data-binding. Input control follows HTML5 input types * and polyfills the HTML5 validation behavior for older browsers. * * @param {string} ng-model Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `REQUIRED` validation error key if the value is not entered. * @param {number=} ng-minlength Sets `MINLENGTH` validation error key if the value is shorter than * minlength. * @param {number=} ng-maxlength Sets `MAXLENGTH` validation error key if the value is longer than * maxlength. * @param {string=} ng-pattern Sets `PATTERN` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. * @param {string=} ng-change Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
User name: Required!
Last name: Too short! Too long!

user = {{user}}
myForm.userName.$valid = {{myForm.userName.$valid}}
myForm.userName.$error = {{myForm.userName.$error}}
myForm.lastName.$valid = {{myForm.lastName.$valid}}
myForm.userName.$error = {{myForm.lastName.$error}}
myForm.$valid = {{myForm.$valid}}
myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
myForm.$error.MINLENGTH = {{!!myForm.$error.MINLENGTH}}
myForm.$error.MAXLENGTH = {{!!myForm.$error.MAXLENGTH}}
it('should initialize to model', function() { expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}'); expect(binding('myForm.userName.$valid')).toEqual('true'); expect(binding('myForm.$valid')).toEqual('true'); }); it('should be invalid if empty when required', function() { input('user.name').enter(''); expect(binding('user')).toEqual('{"last":"visitor"}'); expect(binding('myForm.userName.$valid')).toEqual('false'); expect(binding('myForm.$valid')).toEqual('false'); }); it('should be valid if empty when min length is set', function() { input('user.last').enter(''); expect(binding('user')).toEqual('{"last":"","name":"guest"}'); expect(binding('myForm.lastName.$valid')).toEqual('true'); expect(binding('myForm.$valid')).toEqual('true'); }); it('should be invalid if less than required min length', function() { input('user.last').enter('xx'); expect(binding('user')).toEqual('{"name":"guest"}'); expect(binding('myForm.lastName.$valid')).toEqual('false'); expect(binding('myForm.lastName.$error')).toMatch(/MINLENGTH/); expect(binding('myForm.$valid')).toEqual('false'); }); it('should be invalid if longer than max length', function() { input('user.last').enter('some ridiculously long name'); expect(binding('user')) .toEqual('{"name":"guest"}'); expect(binding('myForm.lastName.$valid')).toEqual('false'); expect(binding('myForm.lastName.$error')).toMatch(/MAXLENGTH/); expect(binding('myForm.$valid')).toEqual('false'); });
*/ var inputDirective = [function() { return { restrict: 'E', require: '?ngModel', link: function(scope, element, attr, ctrl) { if (ctrl) { (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl); } } }; }]; /** * @ngdoc object * @name angular.module.ng.$compileProvider.directive.ng-model.NgModelController * * @property {string} $viewValue Actual string value in the view. * @property {*} $modelValue The value in the model, that the control is bound to. * @property {Array.} $parsers Whenever the control reads value from the DOM, it executes * all of these functions to sanitize / convert the value as well as validate. * * @property {Array.} $formatters Whenever the model value changes, it executes all of * these functions to convert the value as well as validate. * * @property {Object} $error An bject hash with all errors as keys. * * @property {boolean} $pristine True if user has not interacted with the control yet. * @property {boolean} $dirty True if user has already interacted with the control. * @property {boolean} $valid True if there is no error. * @property {boolean} $invalid True if at least one error on the control. * * @description * */ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', function($scope, $exceptionHandler, $attr, ngModel) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; this.$parsers = []; this.$formatters = []; this.$viewChangeListeners = []; this.$error = {}; this.$pristine = true; this.$dirty = false; this.$valid = true; this.$invalid = false; this.$render = noop; this.$name = $attr.name; /** * @ngdoc function * @name angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$setValidity * @methodOf angular.module.ng.$compileProvider.directive.ng-model.NgModelController * * @description * Change the validity state, and notifies the form when the control changes validity. (i.e. it * does not notify form if given validator is already marked as invalid). * * This method should be called by validators - i.e. the parser or formatter functions. * * @param {string} validationToken Name of the validator. * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). */ this.$setValidity = function(validationToken, isValid) { if (!isValid && this.$error[validationToken]) return; if (isValid && !this.$error[validationToken]) return; if (isValid) { delete this.$error[validationToken]; if (equals(this.$error, {})) { this.$valid = true; this.$invalid = false; } } else { this.$error[validationToken] = true; this.$invalid = true; this.$valid = false; } if (this.$form) { this.$form.$setValidity(validationToken, isValid, this); } }; /** * @ngdoc function * @name angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$setViewValue * @methodOf angular.module.ng.$compileProvider.directive.ng-model.NgModelController * * @description * Read a value from view. * * This method should be called from within a DOM event handler. * For example {@link angular.module.ng.$compileProvider.directive.input input} or * {@link angular.module.ng.$compileProvider.directive.select select} directives call it. * * It internally calls all `formatters` and if resulted value is valid, updates the model and * calls all registered change listeners. * * @param {string} value Value from the view. */ this.$setViewValue = function(value) { this.$viewValue = value; // change to dirty if (this.$pristine) { this.$dirty = true; this.$pristine = false; if (this.$form) this.$form.$setDirty(); } forEach(this.$parsers, function(fn) { value = fn(value); }); if (this.$modelValue !== value) { this.$modelValue = value; ngModel(value); forEach(this.$viewChangeListeners, function(listener) { try { listener(); } catch(e) { $exceptionHandler(e); } }) } }; // model -> value var ctrl = this; $scope.$watch(function() { return ngModel(); }, function(value) { // ignore change from view if (ctrl.$modelValue === value) return; var formatters = ctrl.$formatters, idx = formatters.length; ctrl.$modelValue = value; while(idx--) { value = formatters[idx](value); } if (ctrl.$viewValue !== value) { ctrl.$viewValue = value; ctrl.$render(); } }); }]; /** * @ngdoc directive * @name angular.module.ng.$compileProvider.directive.ng-model * * @element input * * @description * Is directive that tells Angular to do two-way data binding. It works together with `input`, * `select`, `textarea`. You can easily write your own directives to use `ng-model` as well. * * `ng-model` is responsible for: * * - binding the view into the model, which other directives such as `input`, `textarea` or `select` * require, * - providing validation behavior (i.e. required, number, email, url), * - keeping state of the control (valid/invalid, dirty/pristine, validation errors), * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`), * - register the control with parent {@link angular.module.ng.$compileProvider.directive.form form}. * * For basic examples, how to use `ng-model`, see: * * - {@link angular.module.ng.$compileProvider.directive.input input} * - {@link angular.module.ng.$compileProvider.directive.input.text text} * - {@link angular.module.ng.$compileProvider.directive.input.checkbox checkbox} * - {@link angular.module.ng.$compileProvider.directive.input.radio radio} * - {@link angular.module.ng.$compileProvider.directive.input.number number} * - {@link angular.module.ng.$compileProvider.directive.input.email email} * - {@link angular.module.ng.$compileProvider.directive.input.url url} * - {@link angular.module.ng.$compileProvider.directive.select select} * - {@link angular.module.ng.$compileProvider.directive.textarea textarea} * */ var ngModelDirective = [function() { return { inject: { ngModel: 'accessor' }, require: ['ngModel', '^?form'], controller: NgModelController, link: function(scope, element, attr, ctrls) { // notify others, especially parent forms var modelCtrl = ctrls[0], formCtrl = ctrls[1]; modelCtrl.$form = formCtrl; if (formCtrl) formCtrl.$addControl(modelCtrl); forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) { scope.$watch(function() { return modelCtrl['$' + name]; }, function(value) { element[value ? 'addClass' : 'removeClass']('ng-' + name); }); }); element.bind('$destroy', function() { if (formCtrl) formCtrl.$removeControl(modelCtrl); }); } }; }]; /** * @ngdoc directive * @name angular.module.ng.$compileProvider.directive.ng-change * @restrict E * * @description * Evaluate given expression when user changes the input. * The expression is not evaluated when the value change is coming from the model. * * Note, this directive requires `ng-model` to be present. * * @element input * * @example * * * *
* * *
* debug = {{confirmed}}
* counter = {{counter}} *
*
* * it('should evaluate the expression if changing from view', function() { * expect(binding('counter')).toEqual('0'); * element('#ng-change-example1').click(); * expect(binding('counter')).toEqual('1'); * expect(binding('confirmed')).toEqual('true'); * }); * * it('should not evaluate the expression if changing from model', function() { * element('#ng-change-example2').click(); * expect(binding('counter')).toEqual('0'); * expect(binding('confirmed')).toEqual('true'); * }); * *
*/ var ngChangeDirective = valueFn({ require: 'ngModel', link: function(scope, element, attr, ctrl) { ctrl.$viewChangeListeners.push(function() { scope.$eval(attr.ngChange); }); } }); /** * @ngdoc directive * @name angular.module.ng.$compileProvider.directive.ng-model-instant * * @element input * * @description * By default, Angular udpates the model only on `blur` event - when the input looses focus. * If you want to update after every key stroke, use `ng-model-instant`. * * @example * * * First name:
* Last name:
* * First name ({{firstName}}) is only updated on `blur` event, but the last name ({{lastName}}) * is updated immediately, because of using `ng-model-instant`. *
* * it('should update first name on blur', function() { * input('firstName').enter('santa', 'blur'); * expect(binding('firstName')).toEqual('santa'); * }); * * it('should update last name immediately', function() { * input('lastName').enter('santa', 'keydown'); * expect(binding('lastName')).toEqual('santa'); * }); * *
*/ var ngModelInstantDirective = ['$browser', function($browser) { return { require: 'ngModel', link: function(scope, element, attr, ctrl) { var handler = function() { scope.$apply(function() { ctrl.$setViewValue(trim(element.val())); }); }; var timeout; element.bind('keydown', function(event) { var key = event.keyCode; // command modifiers arrows if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; if (!timeout) { timeout = $browser.defer(function() { handler(); timeout = null; }); } }); element.bind('change input', handler); } }; }]; var requiredDirective = [function() { return { require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; var validator = function(value) { if (attr.required && (isEmpty(value) || value === false)) { ctrl.$setValidity('REQUIRED', false); return; } else { ctrl.$setValidity('REQUIRED', true); return value; } }; ctrl.$formatters.push(validator); ctrl.$parsers.unshift(validator); attr.$observe('required', function() { validator(ctrl.$viewValue); }); } }; }]; /** * @ngdoc directive * @name angular.module.ng.$compileProvider.directive.ng-list * * @description * Text input that converts between comma-seperated string into an array of strings. * * @element input * * @example
List: Required! names = {{names}}
myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
myForm.namesInput.$error = {{myForm.namesInput.$error}}
myForm.$valid = {{myForm.$valid}}
myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
it('should initialize to model', function() { expect(binding('names')).toEqual('["igor","misko","vojta"]'); expect(binding('myForm.namesInput.$valid')).toEqual('true'); }); it('should be invalid if empty', function() { input('names').enter(''); expect(binding('names')).toEqual('[]'); expect(binding('myForm.namesInput.$valid')).toEqual('false'); });
*/ var ngListDirective = function() { return { require: 'ngModel', link: function(scope, element, attr, ctrl) { var parse = function(viewValue) { var list = []; if (viewValue) { forEach(viewValue.split(/\s*,\s*/), function(value) { if (value) list.push(value); }); } return list; }; ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value) && !equals(parse(ctrl.$viewValue), value)) { return value.join(', '); } return undefined; }); } }; };