Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Ng model options extend #15389

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ var angularFiles = {
'src/ng/directive/ngInit.js',
'src/ng/directive/ngList.js',
'src/ng/directive/ngModel.js',
'src/ng/directive/ngModelOptions.js',
'src/ng/directive/ngNonBindable.js',
'src/ng/directive/ngOptions.js',
'src/ng/directive/ngPluralize.js',
Expand Down
2 changes: 0 additions & 2 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@
$jsonpCallbacksProvider,
$LocationProvider,
$LogProvider,
$ModelOptionsProvider,
$ParseProvider,
$RootScopeProvider,
$QProvider,
Expand Down Expand Up @@ -247,7 +246,6 @@ function publishExternalAPI(angular) {
$jsonpCallbacks: $jsonpCallbacksProvider,
$location: $LocationProvider,
$log: $LogProvider,
$modelOptions: $ModelOptionsProvider,
$parse: $ParseProvider,
$rootScope: $RootScopeProvider,
$q: $QProvider,
Expand Down
338 changes: 6 additions & 332 deletions src/ng/directive/ngModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
UNTOUCHED_CLASS: true,
TOUCHED_CLASS: true,
PENDING_CLASS: true,
$ModelOptionsProvider: true,
addSetValidityMethod: true,
setupValidity: true
setupValidity: true,
$defaultModelOptions: false
*/


var VALID_CLASS = 'ng-valid',
INVALID_CLASS = 'ng-invalid',
PRISTINE_CLASS = 'ng-pristine',
Expand Down Expand Up @@ -223,8 +224,8 @@ is set to `true`. The parse error is stored in `ngModel.$error.parse`.
*
*
*/
NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate', '$modelOptions'];
function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate, $modelOptions) {
NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate'];
function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) {
this.$viewValue = Number.NaN;
this.$modelValue = Number.NaN;
this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
Expand All @@ -244,7 +245,7 @@ function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $
this.$pending = undefined; // keep pending keys here
this.$name = $interpolate($attr.name || '', false)($scope);
this.$$parentForm = nullFormCtrl;
this.$options = $modelOptions;
this.$options = $defaultModelOptions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Typically, internal/private variables are not prefixed. The $ prefix is supposed to indicate public stuff.


this.$$parsedNgModel = $parse($attr.ngModel);
this.$$parsedNgModelAssign = this.$$parsedNgModel.assign;
Expand Down Expand Up @@ -1158,330 +1159,3 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
}
};
}];


/**
* @ngdoc directive
* @name ngModelOptions
*
* @description
* This directive allows you to modify the behaviour of ngModel and input directives within your
* application. You can specify an ngModelOptions directive on any element and the settings affect
* the ngModel and input directives on all descendent elements.
*
* The ngModelOptions settings are found by evaluating the value of the ngModelOptions attribute as
* an Angular expression. This expression should evaluate to an object, whose properties contain
* the settings.
*
* If a setting is not specified as a property on the object for a particular ngModelOptions directive
* then it will inherit that setting from the first ngModelOptions directive found by traversing up the
* DOM tree. If there is no ancestor element containing an ngModelOptions directive then the settings in
* {@link $modelOptions} will be used.
*
* For example given the following fragment of HTML
*
*
* ```html
* <div ng-model-options="{ allowInvalid: true }">
* <form ng-model-options="{ updateOn: 'blur' }">
* <input ng-model-options="{ updateOn: 'default' }" />
* </form>
* </div>
* ```
*
* the `input` element will have the following settings
*
* ```js
* { allowInvalid: true, updateOn: 'default' }
* ```
*
*
* ## Triggering and debouncing model updates
*
* The `updateOn` and `debounce` properties allow you to specify a custom list of events that will
* trigger a model update and/or a debouncing delay so that the actual update only takes place when
* a timer expires; this timer will be reset after another change takes place.
*
* Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
* be different from the value in the actual model. This means that if you update the model you
* should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
* order to make sure it is synchronized with the model and that any debounced action is canceled.
*
* The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
* method is by making sure the input is placed inside a form that has a `name` attribute. This is
* important because `form` controllers are published to the related scope under the name in their
* `name` attribute.
*
* Any pending changes will take place immediately when an enclosing form is submitted via the
* `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
* to have access to the updated model.
*
* The following example shows how to override immediate updates. Changes on the inputs within the
* form will update the model only when the control loses focus (blur event). If `escape` key is
* pressed while the input field is focused, the value is reset to the value in the current model.
*
* <example name="ngModelOptions-directive-blur" module="optionsExample">
* <file name="index.html">
* <div ng-controller="ExampleController">
* <form name="userForm">
* <label>
* Name:
* <input type="text" name="userName"
* ng-model="user.name"
* ng-model-options="{ updateOn: 'blur' }"
* ng-keyup="cancel($event)" />
* </label><br />
* <label>
* Other data:
* <input type="text" ng-model="user.data" />
* </label><br />
* </form>
* <pre>user.name = <span ng-bind="user.name"></span></pre>
* </div>
* </file>
* <file name="app.js">
* angular.module('optionsExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.user = { name: 'say', data: '' };
*
* $scope.cancel = function(e) {
* if (e.keyCode === 27) {
* $scope.userForm.userName.$rollbackViewValue();
* }
* };
* }]);
* </file>
* <file name="protractor.js" type="protractor">
* var model = element(by.binding('user.name'));
* var input = element(by.model('user.name'));
* var other = element(by.model('user.data'));
*
* it('should allow custom events', function() {
* input.sendKeys(' hello');
* input.click();
* expect(model.getText()).toEqual('say');
* other.click();
* expect(model.getText()).toEqual('say hello');
* });
*
* it('should $rollbackViewValue when model changes', function() {
* input.sendKeys(' hello');
* expect(input.getAttribute('value')).toEqual('say hello');
* input.sendKeys(protractor.Key.ESCAPE);
* expect(input.getAttribute('value')).toEqual('say');
* other.click();
* expect(model.getText()).toEqual('say');
* });
* </file>
* </example>
*
* The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
* If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
*
* <example name="ngModelOptions-directive-debounce" module="optionsExample">
* <file name="index.html">
* <div ng-controller="ExampleController">
* <form name="userForm">
* Name:
* <input type="text" name="userName"
* ng-model="user.name"
* ng-model-options="{ debounce: 1000 }" />
* <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
* </form>
* <pre>user.name = <span ng-bind="user.name"></span></pre>
* </div>
* </file>
* <file name="app.js">
* angular.module('optionsExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.user = { name: 'say' };
* }]);
* </file>
* </example>
*
* ## Model updates and validation
*
* The default behaviour in `ngModel` is that the model value is set to `undefined` when the
* validation determines that the value is invalid. By setting the `allowInvalid` property to true,
* the model will still be updated even if the value is invalid.
*
*
* ## Connecting to the scope
*
* By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression
* on the scope refers to a "getter/setter" function rather than the value itself.
*
* The following example shows how to bind to getter/setters:
*
* <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
* <file name="index.html">
* <div ng-controller="ExampleController">
* <form name="userForm">
* <label>
* Name:
* <input type="text" name="userName"
* ng-model="user.name"
* ng-model-options="{ getterSetter: true }" />
* </label>
* </form>
* <pre>user.name = <span ng-bind="user.name()"></span></pre>
* </div>
* </file>
* <file name="app.js">
* angular.module('getterSetterExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* var _name = 'Brian';
* $scope.user = {
* name: function(newName) {
* return angular.isDefined(newName) ? (_name = newName) : _name;
* }
* };
* }]);
* </file>
* </example>
*
*
* ## Specifying timezones
*
* You can specify the timezone that date/time input directives expect by providing its name in the
* `timezone` property.
*
* @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
* - `updateOn`: string specifying which event should the input be bound to. You can set several
* events using an space delimited list. There is a special event called `default` that
* matches the default events belonging to the control.
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
* value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
* custom value for each event. For example:
* `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
* - `allowInvalid`: boolean value which indicates that the model can be set with values that did
* not validate correctly instead of the default behavior of setting the model to undefined.
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
* `ngModel` as getters/setters.
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
* `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the
* continental US time zone abbreviations, but for general use, use a time zone offset, for
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
* If not specified, the timezone of the browser will be used.
*
*/
var ngModelOptionsDirective = ['$modelOptions', function($modelOptions) {
return {
restrict: 'A',
// ngModelOptions needs to run before ngModel and input directives
priority: 10,
require: ['ngModelOptions', '?^^ngModelOptions'],
controller: function NgModelOptionsController() {},
link: {
pre: function ngModelOptionsPreLinkFn(scope, element, attrs, ctrls) {
var optionsCtrl = ctrls[0];
var parentOptions = ctrls[1] ? ctrls[1].$options : $modelOptions;
optionsCtrl.$options = parentOptions.createChild(scope.$eval(attrs.ngModelOptions));
}
}
};
}];


/**
* @ngdoc provider
* @name $modelOptionsProvider
* @description
*
* Here, you can change the default settings from which {@link ngModelOptions}
* directives inherit.
*
* See the {@link ngModelOptions} directive for a list of the available options.
*/
function $ModelOptionsProvider() {
return {
/**
* @ngdoc property
* @name $modelOptionsProvider#defaultOptions
* @type {Object}
* @description
* The default options to fall back on when there are no more ngModelOption
* directives as ancestors.
* Use this property to specify the defaultOptions for the application as a whole.
*
* The initial default options are:
*
* * `updateOn`: `default`
* * `debounce`: `0`
* * `allowInvalid`: `undefined`
* * `getterSetter`: `undefined`
* * `timezone`: 'undefined'
*/
defaultOptions: {
updateOn: 'default',
debounce: 0
},

/**
* @ngdoc service
* @name $modelOptions
* @type ModelOptions
* @description
*
* This service provides the application wide default {@link ModelOptions} options that
* will be used by {@link ngModel} directives if no {@link ngModelOptions} directive is
* specified.
*/
$get: function() {
return new ModelOptions(this.defaultOptions);
}
};
}


/**
* @ngdoc type
* @name ModelOptions
* @description
* A container for the options set by the {@link ngModelOptions} directive
* and the {@link $modelOptions} service.
*/
var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
function ModelOptions(options, parentOptions) {

// Extend the parent's options with these new ones
var _options = extend({}, parentOptions, options);

// do extra processing on the options

// updateOn and updateOnDefault
if (isDefined(_options.updateOn) && _options.updateOn.trim()) {
_options.updateOnDefault = false;
// extract "default" pseudo-event from list of events that can trigger a model update
_options.updateOn = trim(_options.updateOn.replace(DEFAULT_REGEXP, function() {
_options.updateOnDefault = true;
return ' ';
}));
} else if (parentOptions) {
_options.updateOn = parentOptions.updateOn;
_options.updateOnDefault = parentOptions.updateOnDefault;
} else {
_options.updateOnDefault = true;
}


/**
* @ngdoc method
* @name ModelOptions#getOption
* @param {string} name the name of the option to retrieve
* @returns {*} the value of the option
* @description
* Returns the value of the given option
*/
this.getOption = function(name) { return _options[name]; };

/**
* @ngdoc method
* @name ModelOptions#createChild
* @param {Object} options a hash of options for the new child that will override the parent's options
* @return {ModelOptions} a new `ModelOptions` object initialized with the given options.
*/
this.createChild = function(options) {
return new ModelOptions(options, _options);
};
}
Loading