'use strict'; /* global -VALID_CLASS, -INVALID_CLASS, -PRISTINE_CLASS, -DIRTY_CLASS */ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var inputType = { /** * @ngdoc inputType * @name ng.directive:input.text * * @description * Standard HTML text input with angular data binding. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Adds `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern 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=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. * * @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}}
var text = element(by.binding('text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('text')); it('should initialize to model', function() { expect(text.getText()).toContain('guest'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if multi word', function() { input.clear(); input.sendKeys('hello world'); expect(valid.getText()).toContain('false'); });
*/ 'text': textInputType, /** * @ngdoc inputType * @name ng.directive:input.number * * @description * Text input with number validation and transformation. Sets the `number` validation * error if not a valid number. * * @param {string} ngModel 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 than `min`. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern 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=} ngChange 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}}
var value = element(by.binding('value')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('value')); it('should initialize to model', function() { expect(value.getText()).toContain('12'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if over max', function() { input.clear(); input.sendKeys('123'); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); });
*/ 'number': numberInputType, /** * @ngdoc inputType * @name ng.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} ngModel 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 {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern 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=} ngChange 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}}
var text = element(by.binding('text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('text')); it('should initialize to model', function() { expect(text.getText()).toContain('http://google.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not url', function() { input.clear(); input.sendKeys('box'); expect(valid.getText()).toContain('false'); });
*/ 'url': urlInputType, /** * @ngdoc inputType * @name ng.directive:input.email * * @description * Text input with email validation. Sets the `email` validation error key if not a valid email * address. * * @param {string} ngModel 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 {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern 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=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @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}}
var text = element(by.binding('text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('text')); it('should initialize to model', function() { expect(text.getText()).toContain('me@example.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not email', function() { input.clear(); input.sendKeys('xxx'); expect(valid.getText()).toContain('false'); });
*/ 'email': emailInputType, /** * @ngdoc inputType * @name ng.directive:input.radio * * @description * HTML radio button. * * @param {string} ngModel 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=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {string} ngValue Angular expression which sets the value to which the expression should * be set when selected. * * @example
Red
Green
Blue
color = {{color | json}}
Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
it('should change state', function() { var color = element(by.binding('color')); expect(color.getText()).toContain('blue'); element.all(by.model('color')).get(0).click(); expect(color.getText()).toContain('red'); });
*/ 'radio': radioInputType, /** * @ngdoc inputType * @name ng.directive:input.checkbox * * @description * HTML checkbox. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} ngTrueValue The value to which the expression should be set when selected. * @param {string=} ngFalseValue The value to which the expression should be set when not selected. * @param {string=} ngChange 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() { var value1 = element(by.binding('value1')); var value2 = element(by.binding('value2')); expect(value1.getText()).toContain('true'); expect(value2.getText()).toContain('YES'); element(by.model('value1')).click(); element(by.model('value2')).click(); expect(value1.getText()).toContain('false'); expect(value2.getText()).toContain('NO'); });
*/ 'checkbox': checkboxInputType, 'hidden': noop, 'button': noop, 'submit': noop, 'reset': noop }; // A helper function to call $setValidity and return the value / undefined, // a pattern that is repeated a lot in the input validation logic. function validate(ctrl, validatorName, validity, value){ ctrl.$setValidity(validatorName, validity); return validity ? value : undefined; } function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { // In composition mode, users are still inputing intermediate text buffer, // hold the listener until composition is done. // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent if (!$sniffer.android) { var composing = false; element.on('compositionstart', function(data) { composing = true; }); element.on('compositionend', function() { composing = false; }); } var listener = function() { if (composing) return; var value = element.val(); // By default we will trim the value // If the attribute ng-trim exists we will avoid trimming // e.g. if (toBoolean(attr.ngTrim || 'T')) { value = trim(value); } if (ctrl.$viewValue !== value) { if (scope.$$phase) { ctrl.$setViewValue(value); } else { scope.$apply(function() { ctrl.$setViewValue(value); }); } } }; // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the // input event on backspace, delete or cut if ($sniffer.hasEvent('input')) { element.on('input', listener); } else { var timeout; var deferListener = function() { if (!timeout) { timeout = $browser.defer(function() { listener(); timeout = null; }); } }; element.on('keydown', function(event) { var key = event.keyCode; // ignore // command modifiers arrows if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; deferListener(); }); // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it if ($sniffer.hasEvent('paste')) { element.on('paste cut', deferListener); } } // if user paste into input using mouse on older browser // or form autocomplete on newer browser, we need "change" event to catch it element.on('change', listener); ctrl.$render = function() { element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); }; // pattern validator var pattern = attr.ngPattern, patternValidator, match; if (pattern) { var validateRegex = function(regexp, value) { return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); }; match = pattern.match(/^\/(.*)\/([gim]*)$/); if (match) { pattern = new RegExp(match[1], match[2]); patternValidator = function(value) { return validateRegex(pattern, value); }; } else { patternValidator = function(value) { var patternObj = scope.$eval(pattern); if (!patternObj || !patternObj.test) { throw minErr('ngPattern')('noregexp', 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, patternObj, startingTag(element)); } return validateRegex(patternObj, value); }; } ctrl.$formatters.push(patternValidator); ctrl.$parsers.push(patternValidator); } // min length validator if (attr.ngMinlength) { var minlength = int(attr.ngMinlength); var minLengthValidator = function(value) { return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); }; ctrl.$parsers.push(minLengthValidator); ctrl.$formatters.push(minLengthValidator); } // max length validator if (attr.ngMaxlength) { var maxlength = int(attr.ngMaxlength); var maxLengthValidator = function(value) { return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); }; ctrl.$parsers.push(maxLengthValidator); ctrl.$formatters.push(maxLengthValidator); } } function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); ctrl.$parsers.push(function(value) { var empty = ctrl.$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 ctrl.$isEmpty(value) ? '' : '' + value; }); if (attr.min) { var minValidator = function(value) { var min = parseFloat(attr.min); return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); }; ctrl.$parsers.push(minValidator); ctrl.$formatters.push(minValidator); } if (attr.max) { var maxValidator = function(value) { var max = parseFloat(attr.max); return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); }; ctrl.$parsers.push(maxValidator); ctrl.$formatters.push(maxValidator); } ctrl.$formatters.push(function(value) { return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); }); } function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); var urlValidator = function(value) { return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); }; ctrl.$formatters.push(urlValidator); ctrl.$parsers.push(urlValidator); } function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { textInputType(scope, element, attr, ctrl, $sniffer, $browser); var emailValidator = function(value) { return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); }; ctrl.$formatters.push(emailValidator); ctrl.$parsers.push(emailValidator); } function radioInputType(scope, element, attr, ctrl) { // make the name unique, if not defined if (isUndefined(attr.name)) { element.attr('name', nextUid()); } element.on('click', function() { if (element[0].checked) { scope.$apply(function() { ctrl.$setViewValue(attr.value); }); } }); ctrl.$render = function() { var value = attr.value; element[0].checked = (value == ctrl.$viewValue); }; attr.$observe('value', ctrl.$render); } function checkboxInputType(scope, element, attr, ctrl) { var trueValue = attr.ngTrueValue, falseValue = attr.ngFalseValue; if (!isString(trueValue)) trueValue = true; if (!isString(falseValue)) falseValue = false; element.on('click', function() { scope.$apply(function() { ctrl.$setViewValue(element[0].checked); }); }); ctrl.$render = function() { element[0].checked = ctrl.$viewValue; }; // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. ctrl.$isEmpty = function(value) { return value !== trueValue; }; ctrl.$formatters.push(function(value) { return value === trueValue; }); ctrl.$parsers.push(function(value) { return value ? trueValue : falseValue; }); } /** * @ngdoc directive * @name ng.directive:textarea * @restrict E * * @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 ng.directive:input input element}. * * @param {string} ngModel 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 {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern 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=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. */ /** * @ngdoc directive * @name ng.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} ngModel 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 {boolean=} ngRequired Sets `required` attribute if set to true * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. * @param {string=} ngPattern 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=} ngChange 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.lastName.$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}}
var user = element(by.binding('{{user}}')); var userNameValid = element(by.binding('myForm.userName.$valid')); var lastNameValid = element(by.binding('myForm.lastName.$valid')); var lastNameError = element(by.binding('myForm.lastName.$error')); var formValid = element(by.binding('myForm.$valid')); var userNameInput = element(by.model('user.name')); var userLastInput = element(by.model('user.last')); it('should initialize to model', function() { expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); expect(userNameValid.getText()).toContain('true'); expect(formValid.getText()).toContain('true'); }); it('should be invalid if empty when required', function() { userNameInput.clear(); userNameInput.sendKeys(''); expect(user.getText()).toContain('{"last":"visitor"}'); expect(userNameValid.getText()).toContain('false'); expect(formValid.getText()).toContain('false'); }); it('should be valid if empty when min length is set', function() { userLastInput.clear(); userLastInput.sendKeys(''); expect(user.getText()).toContain('{"name":"guest","last":""}'); expect(lastNameValid.getText()).toContain('true'); expect(formValid.getText()).toContain('true'); }); it('should be invalid if less than required min length', function() { userLastInput.clear(); userLastInput.sendKeys('xx'); expect(user.getText()).toContain('{"name":"guest"}'); expect(lastNameValid.getText()).toContain('false'); expect(lastNameError.getText()).toContain('minlength'); expect(formValid.getText()).toContain('false'); }); it('should be invalid if longer than max length', function() { userLastInput.clear(); userLastInput.sendKeys('some ridiculously long name'); expect(user.getText()).toContain('{"name":"guest"}'); expect(lastNameValid.getText()).toContain('false'); expect(lastNameError.getText()).toContain('maxlength'); expect(formValid.getText()).toContain('false'); });
*/ var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { return { restrict: 'E', require: '?ngModel', link: function(scope, element, attr, ctrl) { if (ctrl) { (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, $browser); } } }; }]; var VALID_CLASS = 'ng-valid', INVALID_CLASS = 'ng-invalid', PRISTINE_CLASS = 'ng-pristine', DIRTY_CLASS = 'ng-dirty'; /** * @ngdoc object * @name ng.directive:ngModel.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 Array of functions to execute, as a pipeline, whenever the control reads value from the DOM. Each function is called, in turn, passing the value through to the next. Used to sanitize / convert the value as well as validation. For validation, the parsers should update the validity state using {@link ng.directive:ngModel.NgModelController#methods_$setValidity $setValidity()}, and return `undefined` for invalid values. * * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever the model value changes. Each function is called, in turn, passing the value through to the next. Used to format / convert values for display in the control and validation. *
 *      function formatter(value) {
 *        if (value) {
 *          return value.toUpperCase();
 *        }
 *      }
 *      ngModel.$formatters.push(formatter);
 *      
* * @property {Array.} $viewChangeListeners Array of functions to execute whenever the * view value has changed. It is called with no arguments, and its return value is ignored. * This can be used in place of additional $watches against the model value. * * @property {Object} $error An object 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 * * `NgModelController` provides API for the `ng-model` directive. The controller contains * services for data-binding, validation, CSS updates, and value formatting and parsing. It * purposefully does not contain any logic which deals with DOM rendering or listening to * DOM events. Such DOM related logic should be provided by other directives which make use of * `NgModelController` for data-binding. * * ## Custom Control Example * This example shows how to use `NgModelController` with a custom control to achieve * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) * collaborate together to achieve the desired result. * * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element * contents be edited in place by the user. This will not work on older browsers. * * [contenteditable] { border: 1px solid black; background-color: white; min-height: 20px; } .ng-invalid { border: 1px solid red; } angular.module('customControl', []). directive('contenteditable', function() { return { restrict: 'A', // only activate on element attribute require: '?ngModel', // get a hold of NgModelController link: function(scope, element, attrs, ngModel) { if(!ngModel) return; // do nothing if no ng-model // Specify how UI should be updated ngModel.$render = function() { element.html(ngModel.$viewValue || ''); }; // Listen for change events to enable binding element.on('blur keyup change', function() { scope.$apply(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a
behind // If strip-br attribute is provided then we strip this out if( attrs.stripBr && html == '
' ) { html = ''; } ngModel.$setViewValue(html); } } }; });
Change me!
Required!
it('should data-bind and become invalid', function() { var contentEditable = element(by.css('.doc-example-live [contenteditable]')); expect(contentEditable.getText()).toEqual('Change me!'); contentEditable.clear(); contentEditable.sendKeys(protractor.Key.BACK_SPACE); expect(contentEditable.getText()).toEqual(''); expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); }); *
* * */ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', function($scope, $exceptionHandler, $attr, $element, $parse) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; this.$parsers = []; this.$formatters = []; this.$viewChangeListeners = []; this.$pristine = true; this.$dirty = false; this.$valid = true; this.$invalid = false; this.$name = $attr.name; var ngModelGet = $parse($attr.ngModel), ngModelSet = ngModelGet.assign; if (!ngModelSet) { throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", $attr.ngModel, startingTag($element)); } /** * @ngdoc function * @name ng.directive:ngModel.NgModelController#$render * @methodOf ng.directive:ngModel.NgModelController * * @description * Called when the view needs to be updated. It is expected that the user of the ng-model * directive will implement this method. */ this.$render = noop; /** * @ngdoc function * @name { ng.directive:ngModel.NgModelController#$isEmpty * @methodOf ng.directive:ngModel.NgModelController * * @description * This is called when we need to determine if the value of the input is empty. * * For instance, the required directive does this to work out if the input has data or not. * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. * * You can override this for input directives whose concept of being empty is different to the * default. The `checkboxInputType` directive does this because in its case a value of `false` * implies empty. */ this.$isEmpty = function(value) { return isUndefined(value) || value === '' || value === null || value !== value; }; var parentForm = $element.inheritedData('$formController') || nullFormCtrl, invalidCount = 0, // used to easily determine if we are valid $error = this.$error = {}; // keep invalid keys here // Setup initial state of the control $element.addClass(PRISTINE_CLASS); toggleValidCss(true); // convenience method for easy toggling of classes function toggleValidCss(isValid, validationErrorKey) { validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; $element. removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); } /** * @ngdoc function * @name ng.directive:ngModel.NgModelController#$setValidity * @methodOf ng.directive:ngModel.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} validationErrorKey Name of the validator. the `validationErrorKey` will assign * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. * The `validationErrorKey` should be in camelCase and will get converted into dash-case * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` * class and can be bound to as `{{someForm.someControl.$error.myError}}` . * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). */ this.$setValidity = function(validationErrorKey, isValid) { // Purposeful use of ! here to cast isValid to boolean in case it is undefined // jshint -W018 if ($error[validationErrorKey] === !isValid) return; // jshint +W018 if (isValid) { if ($error[validationErrorKey]) invalidCount--; if (!invalidCount) { toggleValidCss(true); this.$valid = true; this.$invalid = false; } } else { toggleValidCss(false); this.$invalid = true; this.$valid = false; invalidCount++; } $error[validationErrorKey] = !isValid; toggleValidCss(isValid, validationErrorKey); parentForm.$setValidity(validationErrorKey, isValid, this); }; /** * @ngdoc function * @name ng.directive:ngModel.NgModelController#$setPristine * @methodOf ng.directive:ngModel.NgModelController * * @description * Sets the control to its pristine state. * * This method can be called to remove the 'ng-dirty' class and set the control to its pristine * state (ng-pristine class). */ this.$setPristine = function () { this.$dirty = false; this.$pristine = true; $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); }; /** * @ngdoc function * @name ng.directive:ngModel.NgModelController#$setViewValue * @methodOf ng.directive:ngModel.NgModelController * * @description * Update the view value. * * This method should be called when the view value changes, typically from within a DOM event handler. * For example {@link ng.directive:input input} and * {@link ng.directive:select select} directives call it. * * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to * `$modelValue` and the **expression** specified in the `ng-model` attribute. * * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. * * Note that calling this function does not trigger a `$digest`. * * @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; $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); parentForm.$setDirty(); } forEach(this.$parsers, function(fn) { value = fn(value); }); if (this.$modelValue !== value) { this.$modelValue = value; ngModelSet($scope, value); forEach(this.$viewChangeListeners, function(listener) { try { listener(); } catch(e) { $exceptionHandler(e); } }); } }; // model -> value var ctrl = this; $scope.$watch(function ngModelWatch() { var value = ngModelGet($scope); // if scope model value and ngModel value are out of sync if (ctrl.$modelValue !== value) { 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(); } } return value; }); }]; /** * @ngdoc directive * @name ng.directive:ngModel * * @element input * * @description * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a * property on the scope using {@link ng.directive:ngModel.NgModelController NgModelController}, * which is created and exposed by this directive. * * `ngModel` 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 the state of the control (valid/invalid, dirty/pristine, validation errors). * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`). * - Registering the control with its parent {@link ng.directive:form form}. * * Note: `ngModel` will try to bind to the property given by evaluating the expression on the * current scope. If the property doesn't already exist on this scope, it will be created * implicitly and added to the scope. * * For best practices on using `ngModel`, see: * * - {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes} * * For basic examples, how to use `ngModel`, see: * * - {@link ng.directive:input input} * - {@link ng.directive:input.text text} * - {@link ng.directive:input.checkbox checkbox} * - {@link ng.directive:input.radio radio} * - {@link ng.directive:input.number number} * - {@link ng.directive:input.email email} * - {@link ng.directive:input.url url} * - {@link ng.directive:select select} * - {@link ng.directive:textarea textarea} * */ var ngModelDirective = function() { return { require: ['ngModel', '^?form'], controller: NgModelController, link: function(scope, element, attr, ctrls) { // notify others, especially parent forms var modelCtrl = ctrls[0], formCtrl = ctrls[1] || nullFormCtrl; formCtrl.$addControl(modelCtrl); scope.$on('$destroy', function() { formCtrl.$removeControl(modelCtrl); }); } }; }; /** * @ngdoc directive * @name ng.directive:ngChange * * @description * Evaluate the given expression when the user changes the input. * The expression is evaluated immediately, unlike the JavaScript onchange event * which only triggers at the end of a change (usually, when the user leaves the * form element or presses the return key). * The expression is not evaluated when the value change is coming from the model. * * Note, this directive requires `ngModel` to be present. * * @element input * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change * in input value. * * @example * * * *
* * *
* debug = {{confirmed}}
* counter = {{counter}}
*
*
* * var counter = element(by.binding('counter')); * var debug = element(by.binding('confirmed')); * * it('should evaluate the expression if changing from view', function() { * expect(counter.getText()).toContain('0'); * * element(by.id('ng-change-example1')).click(); * * expect(counter.getText()).toContain('1'); * expect(debug.getText()).toContain('true'); * }); * * it('should not evaluate the expression if changing from model', function() { * element(by.id('ng-change-example2')).click(); * expect(counter.getText()).toContain('0'); * expect(debug.getText()).toContain('true'); * }); * *
*/ var ngChangeDirective = valueFn({ require: 'ngModel', link: function(scope, element, attr, ctrl) { ctrl.$viewChangeListeners.push(function() { scope.$eval(attr.ngChange); }); } }); var requiredDirective = function() { return { require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; attr.required = true; // force truthy in case we are on non input element var validator = function(value) { if (attr.required && ctrl.$isEmpty(value)) { 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 ng.directive:ngList * * @description * Text input that converts between a delimited string and an array of strings. The delimiter * can be a fixed string (by default a comma) or a regular expression. * * @element input * @param {string=} ngList optional delimiter that should be used to split the value. If * specified in form `/something/` then the value will be converted into a regular expression. * * @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}}
var listInput = element(by.model('names')); var names = element(by.binding('{{names}}')); var valid = element(by.binding('myForm.namesInput.$valid')); var error = element(by.css('span.error')); it('should initialize to model', function() { expect(names.getText()).toContain('["igor","misko","vojta"]'); expect(valid.getText()).toContain('true'); expect(error.getCssValue('display')).toBe('none'); }); it('should be invalid if empty', function() { listInput.clear(); listInput.sendKeys(''); expect(names.getText()).toContain(''); expect(valid.getText()).toContain('false'); expect(error.getCssValue('display')).not.toBe('none'); });
*/ var ngListDirective = function() { return { require: 'ngModel', link: function(scope, element, attr, ctrl) { var match = /\/(.*)\//.exec(attr.ngList), separator = match && new RegExp(match[1]) || attr.ngList || ','; var parse = function(viewValue) { // If the viewValue is invalid (say required but empty) it will be `undefined` if (isUndefined(viewValue)) return; var list = []; if (viewValue) { forEach(viewValue.split(separator), function(value) { if (value) list.push(trim(value)); }); } return list; }; ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value)) { return value.join(', '); } return undefined; }); // Override the standard $isEmpty because an empty array means the input is empty. ctrl.$isEmpty = function(value) { return !value || !value.length; }; } }; }; var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; /** * @ngdoc directive * @name ng.directive:ngValue * * @description * Binds the given expression to the value of `input[select]` or `input[radio]`, so * that when the element is selected, the `ngModel` of that element is set to the * bound value. * * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as * shown below. * * @element input * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute * of the `input` element * * @example

Which is your favorite?

You chose {{my.favorite}}
var favorite = element(by.binding('my.favorite')); it('should initialize to model', function() { expect(favorite.getText()).toContain('unicorns'); }); it('should bind the values to the inputs', function() { element.all(by.model('my.favorite')).get(0).click(); expect(favorite.getText()).toContain('pizza'); });
*/ var ngValueDirective = function() { return { priority: 100, compile: function(tpl, tplAttr) { if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { return function ngValueConstantLink(scope, elm, attr) { attr.$set('value', scope.$eval(attr.ngValue)); }; } else { return function ngValueLink(scope, elm, attr) { scope.$watch(attr.ngValue, function valueWatchAction(value) { attr.$set('value', value); }); }; } } }; };