aboutsummaryrefslogtreecommitdiffstats
path: root/src/ng/directive
diff options
context:
space:
mode:
authorMisko Hevery2012-03-23 14:03:24 -0700
committerMisko Hevery2012-03-28 11:16:35 -0700
commit2430f52bb97fa9d682e5f028c977c5bf94c5ec38 (patch)
treee7529b741d70199f36d52090b430510bad07f233 /src/ng/directive
parent944098a4e0f753f06b40c73ca3e79991cec6c2e2 (diff)
downloadangular.js-2430f52bb97fa9d682e5f028c977c5bf94c5ec38.tar.bz2
chore(module): move files around in preparation for more modules
Diffstat (limited to 'src/ng/directive')
-rw-r--r--src/ng/directive/a.js29
-rw-r--r--src/ng/directive/booleanAttrDirs.js314
-rw-r--r--src/ng/directive/directives.js11
-rw-r--r--src/ng/directive/form.js267
-rw-r--r--src/ng/directive/input.js1194
-rw-r--r--src/ng/directive/ngBind.js155
-rw-r--r--src/ng/directive/ngClass.js143
-rw-r--r--src/ng/directive/ngCloak.js61
-rw-r--r--src/ng/directive/ngController.js103
-rw-r--r--src/ng/directive/ngEventDirs.js222
-rw-r--r--src/ng/directive/ngInclude.js131
-rw-r--r--src/ng/directive/ngInit.js37
-rw-r--r--src/ng/directive/ngNonBindable.js33
-rw-r--r--src/ng/directive/ngPluralize.js204
-rw-r--r--src/ng/directive/ngRepeat.js181
-rw-r--r--src/ng/directive/ngShowHide.js80
-rw-r--r--src/ng/directive/ngStyle.js42
-rw-r--r--src/ng/directive/ngSwitch.js112
-rw-r--r--src/ng/directive/ngTransclude.js58
-rw-r--r--src/ng/directive/ngView.js170
-rw-r--r--src/ng/directive/script.js43
-rw-r--r--src/ng/directive/select.js449
-rw-r--r--src/ng/directive/style.js6
23 files changed, 4045 insertions, 0 deletions
diff --git a/src/ng/directive/a.js b/src/ng/directive/a.js
new file mode 100644
index 00000000..d96af784
--- /dev/null
+++ b/src/ng/directive/a.js
@@ -0,0 +1,29 @@
+'use strict';
+
+/*
+ * Modifies the default behavior of html A tag, so that the default action is prevented when href
+ * attribute is empty.
+ *
+ * The reasoning for this change is to allow easy creation of action links with ng-click without
+ * changing the location or causing page reloads, e.g.:
+ * <a href="" ng-click="model.$save()">Save</a>
+ */
+var htmlAnchorDirective = valueFn({
+ restrict: 'E',
+ compile: function(element, attr) {
+ // turn <a href ng-click="..">link</a> into a link in IE
+ // but only if it doesn't have name attribute, in which case it's an anchor
+ if (!attr.href) {
+ attr.$set('href', '');
+ }
+
+ return function(scope, element) {
+ element.bind('click', function(event){
+ // if we have no href url, then don't navigate anywhere.
+ if (!element.attr('href')) {
+ event.preventDefault();
+ }
+ });
+ }
+ }
+});
diff --git a/src/ng/directive/booleanAttrDirs.js b/src/ng/directive/booleanAttrDirs.js
new file mode 100644
index 00000000..7da52db0
--- /dev/null
+++ b/src/ng/directive/booleanAttrDirs.js
@@ -0,0 +1,314 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-href
+ * @restrict A
+ *
+ * @description
+ * Using <angular/> markup like {{hash}} in an href attribute makes
+ * the page open to a wrong URL, if the user clicks that link before
+ * angular has a chance to replace the {{hash}} with actual URL, the
+ * link will be broken and will most likely return a 404 error.
+ * The `ng-href` solves this problem by placing the `href` in the
+ * `ng-` namespace.
+ *
+ * The buggy way to write it:
+ * <pre>
+ * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * The correct way to write it:
+ * <pre>
+ * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * @element A
+ * @param {template} ng-href any string which can contain `{{}}` markup.
+ *
+ * @example
+ * This example uses `link` variable inside `href` attribute:
+ <doc:example>
+ <doc:source>
+ <input ng-model="value" /><br />
+ <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
+ <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
+ <a id="link-3" ng-href="/{{'123'}}" ng-ext-link>link 3</a> (link, reload!)<br />
+ <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
+ <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
+ <a id="link-6" ng-href="/{{value}}" ng-ext-link>link</a> (link, change hash)
+ </doc:source>
+ <doc:scenario>
+ it('should execute ng-click but not reload when href without value', function() {
+ element('#link-1').click();
+ expect(input('value').val()).toEqual('1');
+ expect(element('#link-1').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click but not reload when href empty string', function() {
+ element('#link-2').click();
+ expect(input('value').val()).toEqual('2');
+ expect(element('#link-2').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click and change url when ng-href specified', function() {
+ expect(element('#link-3').attr('href')).toBe("/123");
+
+ element('#link-3').click();
+ expect(browser().window().path()).toEqual('/123');
+ });
+
+ it('should execute ng-click but not reload when href empty string and name specified', function() {
+ element('#link-4').click();
+ expect(input('value').val()).toEqual('4');
+ expect(element('#link-4').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click but not reload when no href but name specified', function() {
+ element('#link-5').click();
+ expect(input('value').val()).toEqual('5');
+ expect(element('#link-5').attr('href')).toBe("");
+ });
+
+ it('should only change url when only ng-href', function() {
+ input('value').enter('6');
+ expect(element('#link-6').attr('href')).toBe("/6");
+
+ element('#link-6').click();
+ expect(browser().window().path()).toEqual('/6');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-src
+ * @restrict A
+ *
+ * @description
+ * Using <angular/> markup like `{{hash}}` in a `src` attribute doesn't
+ * work right: The browser will fetch from the URL with the literal
+ * text `{{hash}}` until <angular/> replaces the expression inside
+ * `{{hash}}`. The `ng-src` attribute solves this problem by placing
+ * the `src` attribute in the `ng-` namespace.
+ *
+ * The buggy way to write it:
+ * <pre>
+ * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * The correct way to write it:
+ * <pre>
+ * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * @element IMG
+ * @param {template} ng-src any string which can contain `{{}}` markup.
+ */
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-disabled
+ * @restrict A
+ *
+ * @description
+ *
+ * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
+ * <pre>
+ * <div ng-init="scope = { isDisabled: false }">
+ * <button disabled="{{scope.isDisabled}}">Disabled</button>
+ * </div>
+ * </pre>
+ *
+ * The HTML specs do not require browsers to preserve the special attributes such as disabled.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce ng-disabled.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
+ <button ng-model="button" ng-disabled="checked">Button</button>
+ </doc:source>
+ <doc:scenario>
+ it('should toggle button', function() {
+ expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element INPUT
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-checked
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as checked.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce ng-checked.
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me to check both: <input type="checkbox" ng-model="master"><br/>
+ <input id="checkSlave" type="checkbox" ng-checked="master">
+ </doc:source>
+ <doc:scenario>
+ it('should check both checkBoxes', function() {
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
+ input('master').check();
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element INPUT
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-multiple
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as multiple.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce ng-multiple.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me check multiple: <input type="checkbox" ng-model="checked"><br/>
+ <select id="select" ng-multiple="checked">
+ <option>Misko</option>
+ <option>Igor</option>
+ <option>Vojta</option>
+ <option>Di</option>
+ </select>
+ </doc:source>
+ <doc:scenario>
+ it('should toggle multiple', function() {
+ expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element SELECT
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-readonly
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as readonly.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce ng-readonly.
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
+ <input type="text" ng-readonly="checked" value="I'm Angular"/>
+ </doc:source>
+ <doc:scenario>
+ it('should toggle readonly attr', function() {
+ expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element INPUT
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-selected
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as selected.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce ng-selected.
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me to select: <input type="checkbox" ng-model="selected"><br/>
+ <select>
+ <option>Hello!</option>
+ <option id="greet" ng-selected="selected">Greetings!</option>
+ </select>
+ </doc:source>
+ <doc:scenario>
+ it('should select Greetings!', function() {
+ expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
+ input('selected').check();
+ expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element OPTION
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+var ngAttributeAliasDirectives = {};
+
+
+// boolean attrs are evaluated
+forEach(BOOLEAN_ATTR, function(propName, attrName) {
+ var normalized = directiveNormalize('ng-' + attrName);
+ ngAttributeAliasDirectives[normalized] = function() {
+ return {
+ compile: function(tpl, attr) {
+ attr.$observers[attrName] = [];
+ return function(scope, element, attr) {
+ scope.$watch(attr[normalized], function(value) {
+ attr.$set(attrName, value);
+ });
+ };
+ }
+ };
+ };
+});
+
+
+// ng-src, ng-href are interpolated
+forEach(['src', 'href'], function(attrName) {
+ var normalized = directiveNormalize('ng-' + attrName);
+ ngAttributeAliasDirectives[normalized] = function() {
+ return {
+ compile: function(tpl, attr) {
+ attr.$observers[attrName] = [];
+ return function(scope, element, attr) {
+ attr.$observe(normalized, function(value) {
+ attr.$set(attrName, value);
+ });
+ };
+ }
+ };
+ };
+});
diff --git a/src/ng/directive/directives.js b/src/ng/directive/directives.js
new file mode 100644
index 00000000..123645f9
--- /dev/null
+++ b/src/ng/directive/directives.js
@@ -0,0 +1,11 @@
+'use strict';
+
+function ngDirective(directive) {
+ if (isFunction(directive)) {
+ directive = {
+ link: directive
+ }
+ }
+ directive.restrict = directive.restrict || 'AC';
+ return valueFn(directive);
+};
diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js
new file mode 100644
index 00000000..b6d3f4be
--- /dev/null
+++ b/src/ng/directive/form.js
@@ -0,0 +1,267 @@
+'use strict';
+
+
+var nullFormCtrl = {
+ $addControl: noop,
+ $removeControl: noop,
+ $setValidity: noop,
+ $setDirty: noop
+}
+
+/**
+ * @ngdoc object
+ * @name angular.module.ng.$compileProvider.directive.form.FormController
+ *
+ * @property {boolean} $pristine True if user has not interacted with the form yet.
+ * @property {boolean} $dirty True if user has already interacted with the form.
+ * @property {boolean} $valid True if all of the containg forms and controls are valid.
+ * @property {boolean} $invalid True if at least one containing control or form is invalid.
+ *
+ * @property {Object} $error Is an object hash, containing references to all invalid controls or
+ * forms, where:
+ *
+ * - keys are validation tokens (error names) — such as `REQUIRED`, `URL` or `EMAIL`),
+ * - values are arrays of controls or forms that are invalid with given error.
+ *
+ * @description
+ * `FormController` keeps track of all its controls and nested forms as well as state of them,
+ * such as being valid/invalid or dirty/pristine.
+ *
+ * Each {@link angular.module.ng.$compileProvider.directive.form form} directive creates an instance
+ * of `FormController`.
+ *
+ */
+FormController.$inject = ['$element', '$attrs'];
+function FormController(element, attrs) {
+ var form = this,
+ parentForm = element.parent().controller('form') || nullFormCtrl,
+ invalidCount = 0, // used to easily determine if we are valid
+ errors = form.$error = {};
+
+ // init state
+ form.$name = attrs.name;
+ form.$dirty = false;
+ form.$pristine = true;
+ form.$valid = true;
+ form.$invalid = false;
+
+ parentForm.$addControl(form);
+
+ // 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);
+ }
+
+ form.$addControl = function(control) {
+ if (control.$name && !form.hasOwnProperty(control.$name)) {
+ form[control.$name] = control;
+ }
+ };
+
+ form.$removeControl = function(control) {
+ if (control.$name && form[control.$name] === control) {
+ delete form[control.$name];
+ }
+ forEach(errors, cleanupControlErrors, control);
+ };
+
+ form.$setValidity = function(validationToken, isValid, control) {
+ if (isValid) {
+ cleanupControlErrors(errors[validationToken], validationToken, control);
+
+ if (!invalidCount) {
+ toggleValidCss(isValid);
+ form.$valid = true;
+ form.$invalid = false;
+ }
+ } else {
+ if (!invalidCount) {
+ toggleValidCss(isValid);
+ }
+ addControlError(validationToken, control);
+
+ form.$valid = false;
+ form.$invalid = true;
+ }
+ };
+
+ form.$setDirty = function() {
+ element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ form.$dirty = true;
+ form.$pristine = false;
+ };
+
+ function cleanupControlErrors(queue, validationToken, control) {
+ if (queue) {
+ control = control || this; // so that we can be used in forEach;
+ arrayRemove(queue, control);
+ if (!queue.length) {
+ invalidCount--;
+ errors[validationToken] = false;
+ toggleValidCss(true, validationToken);
+ parentForm.$setValidity(validationToken, true, form);
+ }
+ }
+ }
+
+ function addControlError(validationToken, control) {
+ var queue = errors[validationToken];
+ if (queue) {
+ if (includes(queue, control)) return;
+ } else {
+ errors[validationToken] = queue = [];
+ invalidCount++;
+ toggleValidCss(false, validationToken);
+ parentForm.$setValidity(validationToken, false, form);
+ }
+ queue.push(control);
+ }
+}
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-form
+ * @restrict EAC
+ *
+ * @description
+ * Nestable alias of {@link angular.module.ng.$compileProvider.directive.form `form`} directive. HTML
+ * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
+ * sub-group of controls needs to be determined.
+ *
+ * @param {string=} ng-form|name Name of the form. If specified, the form controller will be published into
+ * related scope, under this name.
+ *
+ */
+
+ /**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.form
+ * @restrict E
+ *
+ * @description
+ * Directive that instantiates
+ * {@link angular.module.ng.$compileProvider.directive.form.FormController FormController}.
+ *
+ * If `name` attribute is specified, the form controller is published onto the current scope under
+ * this name.
+ *
+ * # Alias: {@link angular.module.ng.$compileProvider.directive.ng-form `ng-form`}
+ *
+ * In angular forms can be nested. This means that the outer form is valid when all of the child
+ * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
+ * reason angular provides {@link angular.module.ng.$compileProvider.directive.ng-form `ng-form`} alias
+ * which behaves identical to `<form>` but allows form nesting.
+ *
+ *
+ * # CSS classes
+ * - `ng-valid` Is set if the form is valid.
+ * - `ng-invalid` Is set if the form is invalid.
+ * - `ng-pristine` Is set if the form is pristine.
+ * - `ng-dirty` Is set if the form is dirty.
+ *
+ *
+ * # Submitting a form and preventing default action
+ *
+ * Since the role of forms in client-side Angular applications is different than in classical
+ * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
+ * page reload that sends the data to the server. Instead some javascript logic should be triggered
+ * to handle the form submission in application specific way.
+ *
+ * For this reason, Angular prevents the default action (form submission to the server) unless the
+ * `<form>` element has an `action` attribute specified.
+ *
+ * You can use one of the following two ways to specify what javascript method should be called when
+ * a form is submitted:
+ *
+ * - ng-submit on the form element (add link to ng-submit)
+ * - ng-click on the first button or input field of type submit (input[type=submit])
+ *
+ * To prevent double execution of the handler, use only one of ng-submit or ng-click. This is
+ * because of the following form submission rules coming from the html spec:
+ *
+ * - If a form has only one input field then hitting enter in this field triggers form submit
+ * (`ng-submit`)
+ * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
+ * doesn't trigger submit
+ * - if a form has one or more input fields and one or more buttons or input[type=submit] then
+ * hitting enter in any of the input fields will trigger the click handler on the *first* button or
+ * input[type=submit] (`ng-click`) *and* a submit handler on the enclosing form (`ng-submit`)
+ *
+ * @param {string=} name Name of the form. If specified, the form controller will be published into
+ * related scope, under this name.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.userType = 'guest';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ userType: <input name="input" ng-model="userType" required>
+ <span class="error" ng-show="myForm.input.$error.REQUIRED">Required!</span><br>
+ <tt>userType = {{userType}}</tt><br>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
+ <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('userType')).toEqual('guest');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('userType').enter('');
+ expect(binding('userType')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var formDirectiveDir = {
+ name: 'form',
+ restrict: 'E',
+ controller: FormController,
+ compile: function() {
+ return {
+ pre: function(scope, formElement, attr, controller) {
+ if (!attr.action) {
+ formElement.bind('submit', function(event) {
+ event.preventDefault();
+ });
+ }
+
+ var parentFormCtrl = formElement.parent().controller('form'),
+ alias = attr.name || attr.ngForm;
+
+ if (alias) {
+ scope[alias] = controller;
+ }
+ if (parentFormCtrl) {
+ formElement.bind('$destroy', function() {
+ parentFormCtrl.$removeControl(controller);
+ if (alias) {
+ scope[alias] = undefined;
+ }
+ extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
+ });
+ }
+ }
+ };
+ }
+};
+
+var formDirective = valueFn(formDirectiveDir);
+var ngFormDirective = valueFn(extend(copy(formDirectiveDir), {restrict: 'EAC'}));
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
new file mode 100644
index 00000000..348c9f25
--- /dev/null
+++ b/src/ng/directive/input.js
@@ -0,0 +1,1194 @@
+'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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.text = 'guest';
+ $scope.word = /^\w*$/;
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Single word: <input type="text" name="input" ng-model="text"
+ ng-pattern="word" required>
+ <span class="error" ng-show="myForm.input.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.input.$error.pattern">
+ Single word only!</span>
+
+ <tt>text = {{text}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ 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');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ '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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.value = 12;
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Number: <input type="number" name="input" ng-model="value"
+ min="0" max="99" required>
+ <span class="error" ng-show="myForm.list.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.list.$error.number">
+ Not valid number!</span>
+ <tt>value = {{value}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ 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');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ '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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.text = 'http://google.com';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ URL: <input type="url" name="input" ng-model="text" required>
+ <span class="error" ng-show="myForm.input.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.input.$error.url">
+ Not valid url!</span>
+ <tt>text = {{text}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ 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');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ '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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.text = 'me@example.com';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Email: <input type="email" name="input" ng-model="text" required>
+ <span class="error" ng-show="myForm.input.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.input.$error.email">
+ Not valid email!</span>
+ <tt>text = {{text}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ 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');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ '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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.color = 'blue';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ <input type="radio" ng-model="color" value="red"> Red <br/>
+ <input type="radio" ng-model="color" value="green"> Green <br/>
+ <input type="radio" ng-model="color" value="blue"> Blue <br/>
+ <tt>color = {{color}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should change state', function() {
+ expect(binding('color')).toEqual('blue');
+
+ input('color').select('red');
+ expect(binding('color')).toEqual('red');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ '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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.value1 = true;
+ $scope.value2 = 'YES'
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Value1: <input type="checkbox" ng-model="value1"> <br/>
+ Value2: <input type="checkbox" ng-model="value2"
+ ng-true-value="YES" ng-false-value="NO"> <br/>
+ <tt>value1 = {{value1}}</tt><br/>
+ <tt>value2 = {{value2}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ 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');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ '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 = int(attr.ngMinlength);
+ 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 = int(attr.ngMaxlength);
+ 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 = (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.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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.user = {name: 'guest', last: 'visitor'};
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <form name="myForm">
+ User name: <input type="text" name="userName" ng-model="user.name" required>
+ <span class="error" ng-show="myForm.userName.$error.required">
+ Required!</span><br>
+ Last name: <input type="text" name="lastName" ng-model="user.last"
+ ng-minlength="3" ng-maxlength="10">
+ <span class="error" ng-show="myForm.lastName.$error.minlength">
+ Too short!</span>
+ <span class="error" ng-show="myForm.lastName.$error.maxlength">
+ Too long!</span><br>
+ </form>
+ <hr>
+ <tt>user = {{user}}</tt><br/>
+ <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br>
+ <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br>
+ <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br>
+ <tt>myForm.userName.$error = {{myForm.lastName.$error}}</tt><br>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
+ <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br>
+ <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ 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');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+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);
+ }
+ }
+ };
+}];
+
+var VALID_CLASS = 'ng-valid',
+ INVALID_CLASS = 'ng-invalid',
+ PRISTINE_CLASS = 'ng-pristine',
+ DIRTY_CLASS = 'ng-dirty';
+
+/**
+ * @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.<Function>} $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.<Function>} $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', '$element',
+ function($scope, $exceptionHandler, $attr, ngModel, $element) {
+ 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.$render = noop;
+ this.$name = $attr.name;
+
+ 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 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} 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) {
+ if ($error[validationErrorKey] === !isValid) return;
+
+ 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 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;
+ $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ parentForm.$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] || nullFormCtrl;
+
+ formCtrl.$addControl(modelCtrl);
+
+ element.bind('$destroy', function() {
+ 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
+ * <doc:example>
+ * <doc:source>
+ * <script>
+ * function Controller($scope) {
+ * $scope.counter = 0;
+ * $scope.change = function() {
+ * $scope.counter++;
+ * };
+ * }
+ * </script>
+ * <div ng-controller="Controller">
+ * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
+ * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
+ * <label for="ng-change-example2">Confirmed</label><br />
+ * debug = {{confirmed}}<br />
+ * counter = {{counter}}
+ * </div>
+ * </doc:source>
+ * <doc:scenario>
+ * 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');
+ * });
+ * </doc:scenario>
+ * </doc:example>
+ */
+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
+ * <doc:example>
+ * <doc:source>
+ * First name: <input type="text" ng-model="firstName" /><br />
+ * Last name: <input type="text" ng-model="lastName" ng-model-instant /><br />
+ *
+ * First name ({{firstName}}) is only updated on `blur` event, but the last name ({{lastName}})
+ * is updated immediately, because of using `ng-model-instant`.
+ * </doc:source>
+ * <doc:scenario>
+ * 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');
+ * });
+ * </doc:scenario>
+ * </doc:example>
+ */
+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
+ * @param {string=} ng-list 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
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.names = ['igor', 'misko', 'vojta'];
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ List: <input name="namesInput" ng-model="names" ng-list required>
+ <span class="error" ng-show="myForm.list.$error.required">
+ Required!</span>
+ <tt>names = {{names}}</tt><br/>
+ <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
+ <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ 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');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+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) {
+ 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) && !equals(parse(ctrl.$viewValue), value)) {
+ return value.join(', ');
+ }
+
+ return undefined;
+ });
+ }
+ };
+};
+
+
+var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
+
+var ngValueDirective = [function() {
+ return {
+ priority: 100,
+ compile: function(tpl, attr) {
+ if (CONSTANT_VALUE_REGEXP.test(attr.ngValue)) {
+ return function(scope) {
+ attr.$set('value', scope.$eval(attr.ngValue));
+ };
+ } else {
+ attr.$observers.value = [];
+
+ return function(scope) {
+ scope.$watch(attr.ngValue, function(value) {
+ attr.$set('value', value, false);
+ });
+ };
+ }
+ }
+ };
+}];
diff --git a/src/ng/directive/ngBind.js b/src/ng/directive/ngBind.js
new file mode 100644
index 00000000..32be2f4b
--- /dev/null
+++ b/src/ng/directive/ngBind.js
@@ -0,0 +1,155 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-bind
+ *
+ * @description
+ * The `ng-bind` attribute tells Angular to replace the text content of the specified HTML element
+ * with the value of a given expression, and to update the text content when the value of that
+ * expression changes.
+ *
+ * Typically, you don't use `ng-bind` directly, but instead you use the double curly markup like
+ * `{{ expression }}` and let the Angular compiler transform it to
+ * `<span ng-bind="expression"></span>` when the template is compiled.
+ *
+ * @element ANY
+ * @param {expression} ng-bind {@link guide/dev_guide.expressions Expression} to evaluate.
+ *
+ * @example
+ * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.name = 'Whirled';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Enter name: <input type="text" ng-model="name" ng-model-instant><br>
+ Hello <span ng-bind="name"></span>!
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('name')).toBe('Whirled');
+ using('.doc-example-live').input('name').enter('world');
+ expect(using('.doc-example-live').binding('name')).toBe('world');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngBindDirective = ngDirective(function(scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.ngBind);
+ scope.$watch(attr.ngBind, function(value) {
+ element.text(value == undefined ? '' : value);
+ });
+});
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-bind-html-unsafe
+ *
+ * @description
+ * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
+ * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if
+ * {@link angular.module.ng.$compileProvider.directive.ng-bind-html ng-bind-html} directive is too
+ * restrictive and when you absolutely trust the source of the content you are binding to.
+ *
+ * See {@link angular.module.ng.$sanitize $sanitize} docs for examples.
+ *
+ * @element ANY
+ * @param {expression} ng-bind-html-unsafe {@link guide/dev_guide.expressions Expression} to evaluate.
+ */
+var ngBindHtmlUnsafeDirective = ngDirective(function(scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
+ scope.$watch(attr.ngBindHtmlUnsafe, function(value) {
+ element.html(value == undefined ? '' : value);
+ });
+});
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-bind-html
+ *
+ * @description
+ * Creates a binding that will sanitize the result of evaluating the `expression` with the
+ * {@link angular.module.ng.$sanitize $sanitize} service and innerHTML the result into the current
+ * element.
+ *
+ * See {@link angular.module.ng.$sanitize $sanitize} docs for examples.
+ *
+ * @element ANY
+ * @param {expression} ng-bind-html {@link guide/dev_guide.expressions Expression} to evaluate.
+ */
+var ngBindHtmlDirective = ['$sanitize', function($sanitize) {
+ return function(scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
+ scope.$watch(attr.ngBindHtml, function(value) {
+ if (value = $sanitize(value)) {
+ element.html(value);
+ }
+ });
+ }
+}];
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-bind-template
+ *
+ * @description
+ * The `ng-bind-template` attribute specifies that the element
+ * text should be replaced with the template in ng-bind-template.
+ * Unlike ng-bind the ng-bind-template can contain multiple `{{` `}}`
+ * expressions. (This is required since some HTML elements
+ * can not have SPAN elements such as TITLE, or OPTION to name a few.)
+ *
+ * @element ANY
+ * @param {string} ng-bind-template template of form
+ * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
+ *
+ * @example
+ * Try it here: enter text in text box and watch the greeting change.
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.salutation = 'Hello';
+ $scope.name = 'World';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Salutation: <input type="text" ng-model="salutation" ng-model-instant><br>
+ Name: <input type="text" ng-model="name" ng-model-instant><br>
+ <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Hello');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('World');
+ using('.doc-example-live').input('salutation').enter('Greetings');
+ using('.doc-example-live').input('name').enter('user');
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Greetings');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('user');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
+ return function(scope, element, attr) {
+ // TODO: move this to scenario runner
+ var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
+ element.addClass('ng-binding').data('$binding', interpolateFn);
+ attr.$observe('ngBindTemplate', function(value) {
+ element.text(value);
+ });
+ }
+}];
diff --git a/src/ng/directive/ngClass.js b/src/ng/directive/ngClass.js
new file mode 100644
index 00000000..21b75dd0
--- /dev/null
+++ b/src/ng/directive/ngClass.js
@@ -0,0 +1,143 @@
+'use strict';
+
+function classDirective(name, selector) {
+ name = 'ngClass' + name;
+ return ngDirective(function(scope, element, attr) {
+ scope.$watch(attr[name], function(newVal, oldVal) {
+ if (selector === true || scope.$index % 2 === selector) {
+ if (oldVal && (newVal !== oldVal)) {
+ if (isObject(oldVal) && !isArray(oldVal))
+ oldVal = map(oldVal, function(v, k) { if (v) return k });
+ element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal);
+ }
+ if (isObject(newVal) && !isArray(newVal))
+ newVal = map(newVal, function(v, k) { if (v) return k });
+ if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); }
+ }, true);
+ });
+}
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-class
+ *
+ * @description
+ * The `ng-class` allows you to set CSS class on HTML element dynamically by databinding an
+ * expression that represents all classes to be added.
+ *
+ * The directive won't add duplicate classes if a particular class was already set.
+ *
+ * When the expression changes, the previously added classes are removed and only then the classes
+ * new classes are added.
+ *
+ * @element ANY
+ * @param {expression} ng-class {@link guide/dev_guide.expressions Expression} to eval. The result
+ * of the evaluation can be a string representing space delimited class
+ * names, an array, or a map of class names to boolean values.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <input type="button" value="set" ng-click="myVar='ng-invalid'">
+ <input type="button" value="clear" ng-click="myVar=''">
+ <br>
+ <span ng-class="myVar">Sample Text &nbsp;&nbsp;&nbsp;&nbsp;</span>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-class', function() {
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/ng-invalid/);
+
+ using('.doc-example-live').element(':button:first').click();
+
+ expect(element('.doc-example-live span').prop('className')).
+ toMatch(/ng-invalid/);
+
+ using('.doc-example-live').element(':button:last').click();
+
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/ng-invalid/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngClassDirective = classDirective('', true);
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-class-odd
+ *
+ * @description
+ * The `ng-class-odd` and `ng-class-even` works exactly as
+ * {@link angular.module.ng.$compileProvider.directive.ng-class ng-class}, except it works in conjunction with `ng-repeat` and
+ * takes affect only on odd (even) rows.
+ *
+ * This directive can be applied only within a scope of an
+ * {@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat}.
+ *
+ * @element ANY
+ * @param {expression} ng-class-odd {@link guide/dev_guide.expressions Expression} to eval. The result
+ * of the evaluation can be a string representing space delimited class names or an array.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
+ <li ng-repeat="name in names">
+ <span ng-class-odd="'ng-format-negative'"
+ ng-class-even="'ng-invalid'">
+ {{name}} &nbsp; &nbsp; &nbsp;
+ </span>
+ </li>
+ </ol>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/ng-format-negative/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/ng-invalid/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngClassOddDirective = classDirective('Odd', 0);
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-class-even
+ *
+ * @description
+ * The `ng-class-odd` and `ng-class-even` works exactly as
+ * {@link angular.module.ng.$compileProvider.directive.ng-class ng-class}, except it works in
+ * conjunction with `ng-repeat` and takes affect only on odd (even) rows.
+ *
+ * This directive can be applied only within a scope of an
+ * {@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat}.
+ *
+ * @element ANY
+ * @param {expression} ng-class-even {@link guide/dev_guide.expressions Expression} to eval. The
+ * result of the evaluation can be a string representing space delimited class names or an array.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
+ <li ng-repeat="name in names">
+ <span ng-class-odd="'odd'" ng-class-even="'even'">
+ {{name}} &nbsp; &nbsp; &nbsp;
+ </span>
+ </li>
+ </ol>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/odd/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/even/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngClassEvenDirective = classDirective('Even', 1);
diff --git a/src/ng/directive/ngCloak.js b/src/ng/directive/ngCloak.js
new file mode 100644
index 00000000..7422e15a
--- /dev/null
+++ b/src/ng/directive/ngCloak.js
@@ -0,0 +1,61 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-cloak
+ *
+ * @description
+ * The `ng-cloak` directive is used to prevent the Angular html template from being briefly
+ * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
+ * directive to avoid the undesirable flicker effect caused by the html template display.
+ *
+ * The directive can be applied to the `<body>` element, but typically a fine-grained application is
+ * prefered in order to benefit from progressive rendering of the browser view.
+ *
+ * `ng-cloak` works in cooperation with a css rule that is embedded within `angular.js` and
+ * `angular.min.js` files. Following is the css rule:
+ *
+ * <pre>
+ * [ng\:cloak], .ng-cloak {
+ * display: none;
+ * }
+ * </pre>
+ *
+ * When this css rule is loaded by the browser, all html elements (including their children) that
+ * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive
+ * during the compilation of the template it deletes the `ng-cloak` element attribute, which
+ * makes the compiled element visible.
+ *
+ * For the best result, `angular.js` script must be loaded in the head section of the html file;
+ * alternatively, the css rule (above) must be included in the external stylesheet of the
+ * application.
+ *
+ * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
+ * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
+ * class `ng-cloak` in addition to `ng-cloak` directive as shown in the example below.
+ *
+ * @element ANY
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <div id="template1" ng-cloak>{{ 'hello' }}</div>
+ <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
+ </doc:source>
+ <doc:scenario>
+ it('should remove the template directive and css class', function() {
+ expect(element('.doc-example-live #template1').attr('ng-cloak')).
+ not().toBeDefined();
+ expect(element('.doc-example-live #template2').attr('ng-cloak')).
+ not().toBeDefined();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ */
+var ngCloakDirective = ngDirective({
+ compile: function(element, attr) {
+ attr.$set('ngCloak', undefined);
+ element.removeClass('ng-cloak');
+ }
+});
diff --git a/src/ng/directive/ngController.js b/src/ng/directive/ngController.js
new file mode 100644
index 00000000..8e2c6f3b
--- /dev/null
+++ b/src/ng/directive/ngController.js
@@ -0,0 +1,103 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-controller
+ *
+ * @description
+ * The `ng-controller` directive assigns behavior to a scope. This is a key aspect of how angular
+ * supports the principles behind the Model-View-Controller design pattern.
+ *
+ * MVC components in angular:
+ *
+ * * Model — The Model is data in scope properties; scopes are attached to the DOM.
+ * * View — The template (HTML with data bindings) is rendered into the View.
+ * * Controller — The `ng-controller` directive specifies a Controller class; the class has
+ * methods that typically express the business logic behind the application.
+ *
+ * Note that an alternative way to define controllers is via the `{@link angular.module.ng.$route}`
+ * service.
+ *
+ * @element ANY
+ * @scope
+ * @param {expression} ng-controller Name of a globally accessible constructor function or an
+ * {@link guide/dev_guide.expressions expression} that on the current scope evaluates to a
+ * constructor function.
+ *
+ * @example
+ * Here is a simple form for editing user contact information. Adding, removing, clearing, and
+ * greeting are methods declared on the controller (see source tab). These methods can
+ * easily be called from the angular markup. Notice that the scope becomes the `this` for the
+ * controller's instance. This allows for easy access to the view data from the controller. Also
+ * notice that any changes to the data are automatically reflected in the View without the need
+ * for a manual update.
+ <doc:example>
+ <doc:source>
+ <script type="text/javascript">
+ function SettingsController($scope) {
+ $scope.name = "John Smith";
+ $scope.contacts = [
+ {type:'phone', value:'408 555 1212'},
+ {type:'email', value:'john.smith@example.org'} ];
+
+ $scope.greet = function() {
+ alert(this.name);
+ };
+
+ $scope.addContact = function() {
+ this.contacts.push({type:'email', value:'yourname@example.org'});
+ };
+
+ $scope.removeContact = function(contactToRemove) {
+ var index = this.contacts.indexOf(contactToRemove);
+ this.contacts.splice(index, 1);
+ };
+
+ $scope.clearContact = function(contact) {
+ contact.type = 'phone';
+ contact.value = '';
+ };
+ }
+ </script>
+ <div ng-controller="SettingsController">
+ Name: <input type="text" ng-model="name"/>
+ [ <a href="" ng-click="greet()">greet</a> ]<br/>
+ Contact:
+ <ul>
+ <li ng-repeat="contact in contacts">
+ <select ng-model="contact.type">
+ <option>phone</option>
+ <option>email</option>
+ </select>
+ <input type="text" ng-model="contact.value"/>
+ [ <a href="" ng-click="clearContact(contact)">clear</a>
+ | <a href="" ng-click="removeContact(contact)">X</a> ]
+ </li>
+ <li>[ <a href="" ng-click="addContact()">add</a> ]</li>
+ </ul>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check controller', function() {
+ expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
+ expect(element('.doc-example-live li:nth-child(1) input').val())
+ .toBe('408 555 1212');
+ expect(element('.doc-example-live li:nth-child(2) input').val())
+ .toBe('john.smith@example.org');
+
+ element('.doc-example-live li:first a:contains("clear")').click();
+ expect(element('.doc-example-live li:first input').val()).toBe('');
+
+ element('.doc-example-live li:last a:contains("add")').click();
+ expect(element('.doc-example-live li:nth-child(3) input').val())
+ .toBe('yourname@example.org');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngControllerDirective = [function() {
+ return {
+ scope: true,
+ controller: '@'
+ };
+}];
diff --git a/src/ng/directive/ngEventDirs.js b/src/ng/directive/ngEventDirs.js
new file mode 100644
index 00000000..74028fee
--- /dev/null
+++ b/src/ng/directive/ngEventDirs.js
@@ -0,0 +1,222 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-click
+ *
+ * @description
+ * The ng-click allows you to specify custom behavior when
+ * element is clicked.
+ *
+ * @element ANY
+ * @param {expression} ng-click {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * click. (Event object is available as `$event`)
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <button ng-click="count = count + 1" ng-init="count=0">
+ Increment
+ </button>
+ count: {{count}}
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-click', function() {
+ expect(binding('count')).toBe('0');
+ element('.doc-example-live :button').click();
+ expect(binding('count')).toBe('1');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+/*
+ * A directive that allows creation of custom onclick handlers that are defined as angular
+ * expressions and are compiled and executed within the current scope.
+ *
+ * Events that are handled via these handler are always configured not to propagate further.
+ */
+var ngEventDirectives = {};
+forEach(
+ 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
+ function(name) {
+ var directiveName = directiveNormalize('ng-' + name);
+ ngEventDirectives[directiveName] = ['$parse', function($parse) {
+ return function(scope, element, attr) {
+ var fn = $parse(attr[directiveName]);
+ element.bind(lowercase(name), function(event) {
+ scope.$apply(function() {
+ fn(scope, {$event:event});
+ });
+ });
+ };
+ }];
+ }
+);
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-dblclick
+ *
+ * @description
+ * The ng-dblclick allows you to specify custom behavior on dblclick event.
+ *
+ * @element ANY
+ * @param {expression} ng-dblclick {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * dblclick. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link angular.module.ng.$compileProvider.directive.ng-click ng-click}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-mousedown
+ *
+ * @description
+ * The ng-mousedown allows you to specify custom behavior on mousedown event.
+ *
+ * @element ANY
+ * @param {expression} ng-mousedown {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * mousedown. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link angular.module.ng.$compileProvider.directive.ng-click ng-click}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-mouseup
+ *
+ * @description
+ * Specify custom behavior on mouseup event.
+ *
+ * @element ANY
+ * @param {expression} ng-mouseup {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * mouseup. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link angular.module.ng.$compileProvider.directive.ng-click ng-click}
+ */
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-mouseover
+ *
+ * @description
+ * Specify custom behavior on mouseover event.
+ *
+ * @element ANY
+ * @param {expression} ng-mouseover {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * mouseover. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link angular.module.ng.$compileProvider.directive.ng-click ng-click}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-mouseenter
+ *
+ * @description
+ * Specify custom behavior on mouseenter event.
+ *
+ * @element ANY
+ * @param {expression} ng-mouseenter {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * mouseenter. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link angular.module.ng.$compileProvider.directive.ng-click ng-click}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-mouseleave
+ *
+ * @description
+ * Specify custom behavior on mouseleave event.
+ *
+ * @element ANY
+ * @param {expression} ng-mouseleave {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * mouseleave. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link angular.module.ng.$compileProvider.directive.ng-click ng-click}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-mousemove
+ *
+ * @description
+ * Specify custom behavior on mousemove event.
+ *
+ * @element ANY
+ * @param {expression} ng-mousemove {@link guide/dev_guide.expressions Expression} to evaluate upon
+ * mousemove. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link angular.module.ng.$compileProvider.directive.ng-click ng-click}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-submit
+ *
+ * @description
+ * Enables binding angular expressions to onsubmit events.
+ *
+ * Additionally it prevents the default action (which for form means sending the request to the
+ * server and reloading the current page).
+ *
+ * @element form
+ * @param {expression} ng-submit {@link guide/dev_guide.expressions Expression} to eval.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.list = [];
+ $scope.text = 'hello';
+ $scope.submit = function() {
+ if (this.text) {
+ this.list.push(this.text);
+ this.text = '';
+ }
+ };
+ }
+ </script>
+ <form ng-submit="submit()" ng-controller="Ctrl">
+ Enter text and hit enter:
+ <input type="text" ng-model="text" name="text" />
+ <input type="submit" id="submit" value="Submit" />
+ <pre>list={{list}}</pre>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-submit', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ expect(input('text').val()).toBe('');
+ });
+ it('should ignore empty strings', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
+ element.bind('submit', function() {
+ scope.$apply(attrs.ngSubmit);
+ });
+});
diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js
new file mode 100644
index 00000000..90fd0b40
--- /dev/null
+++ b/src/ng/directive/ngInclude.js
@@ -0,0 +1,131 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-include
+ * @restrict EA
+ *
+ * @description
+ * Fetches, compiles and includes an external HTML fragment.
+ *
+ * Keep in mind that Same Origin Policy applies to included resources
+ * (e.g. ng-include won't work for file:// access).
+ *
+ * @scope
+ *
+ * @param {string} ng-include|src angular expression evaluating to URL. If the source is a string constant,
+ * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
+ * @param {Scope=} [scope=new_child_scope] optional expression which evaluates to an
+ * instance of angular.module.ng.$rootScope.Scope to set the HTML fragment to.
+ * @param {string=} onload Expression to evaluate when a new partial is loaded.
+ *
+ * @param {string=} autoscroll Whether `ng-include` should call {@link angular.module.ng.$anchorScroll
+ * $anchorScroll} to scroll the viewport after the content is loaded.
+ *
+ * - If the attribute is not set, disable scrolling.
+ * - If the attribute is set without value, enable scrolling.
+ * - Otherwise enable scrolling only if the expression evaluates to truthy value.
+ *
+ * @example
+ <doc:example>
+ <doc:source jsfiddle="false">
+ <script>
+ function Ctrl($scope) {
+ $scope.templates =
+ [ { name: 'template1.html', url: 'examples/ng-include/template1.html'}
+ , { name: 'template2.html', url: 'examples/ng-include/template2.html'} ];
+ $scope.template = $scope.templates[0];
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <select ng-model="template" ng-options="t.name for t in templates">
+ <option value="">(blank)</option>
+ </select>
+ url of the template: <tt><a href="{{template.url}}">{{template.url}}</a></tt>
+ <hr/>
+ <div ng-include src="template.url"></div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should load template1.html', function() {
+ expect(element('.doc-example-live [ng-include]').text()).
+ toBe('Content of template1.html\n');
+ });
+ it('should load template2.html', function() {
+ select('template').option('1');
+ expect(element('.doc-example-live [ng-include]').text()).
+ toBe('Content of template2.html\n');
+ });
+ it('should change to blank', function() {
+ select('template').option('');
+ expect(element('.doc-example-live [ng-include]').text()).toEqual('');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+
+/**
+ * @ngdoc event
+ * @name angular.module.ng.$compileProvider.directive.ng-include#$includeContentLoaded
+ * @eventOf angular.module.ng.$compileProvider.directive.ng-include
+ * @eventType emit on the current ng-include scope
+ * @description
+ * Emitted every time the ng-include content is reloaded.
+ */
+var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
+ function($http, $templateCache, $anchorScroll, $compile) {
+ return {
+ restrict: 'EA',
+ compile: function(element, attr) {
+ var srcExp = attr.ngInclude || attr.src,
+ scopeExp = attr.scope || '',
+ onloadExp = attr.onload || '',
+ autoScrollExp = attr.autoscroll;
+
+ return function(scope, element, attr) {
+ var changeCounter = 0,
+ childScope;
+
+ function incrementChange() { changeCounter++;}
+ scope.$watch(srcExp, incrementChange);
+ scope.$watch(function() {
+ var includeScope = scope.$eval(scopeExp);
+ if (includeScope) return includeScope.$id;
+ }, incrementChange);
+ scope.$watch(function() {return changeCounter;}, function(newChangeCounter) {
+ var src = scope.$eval(srcExp),
+ useScope = scope.$eval(scopeExp);
+
+ function clearContent() {
+ // if this callback is still desired
+ if (newChangeCounter === changeCounter) {
+ if (childScope) childScope.$destroy();
+ childScope = null;
+ element.html('');
+ }
+ }
+
+ if (src) {
+ $http.get(src, {cache: $templateCache}).success(function(response) {
+ // if this callback is still desired
+ if (newChangeCounter === changeCounter) {
+ element.html(response);
+ if (childScope) childScope.$destroy();
+ childScope = useScope ? useScope : scope.$new();
+ $compile(element.contents())(childScope);
+ if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
+ $anchorScroll();
+ }
+ scope.$emit('$includeContentLoaded');
+ scope.$eval(onloadExp);
+ }
+ }).error(clearContent);
+ } else {
+ clearContent();
+ }
+ });
+ };
+ }
+ }
+}];
diff --git a/src/ng/directive/ngInit.js b/src/ng/directive/ngInit.js
new file mode 100644
index 00000000..cbd1b3ed
--- /dev/null
+++ b/src/ng/directive/ngInit.js
@@ -0,0 +1,37 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-init
+ *
+ * @description
+ * The `ng-init` attribute specifies initialization tasks to be executed
+ * before the template enters execution mode during bootstrap.
+ *
+ * @element ANY
+ * @param {expression} ng-init {@link guide/dev_guide.expressions Expression} to eval.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <div ng-init="greeting='Hello'; person='World'">
+ {{greeting}} {{person}}!
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check greeting', function() {
+ expect(binding('greeting')).toBe('Hello');
+ expect(binding('person')).toBe('World');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngInitDirective = ngDirective({
+ compile: function() {
+ return {
+ pre: function(scope, element, attrs) {
+ scope.$eval(attrs.ngInit);
+ }
+ }
+ }
+});
diff --git a/src/ng/directive/ngNonBindable.js b/src/ng/directive/ngNonBindable.js
new file mode 100644
index 00000000..2e9faa5a
--- /dev/null
+++ b/src/ng/directive/ngNonBindable.js
@@ -0,0 +1,33 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-non-bindable
+ * @priority 1000
+ *
+ * @description
+ * Sometimes it is necessary to write code which looks like bindings but which should be left alone
+ * by angular. Use `ng-non-bindable` to make angular ignore a chunk of HTML.
+ *
+ * @element ANY
+ *
+ * @example
+ * In this example there are two location where a simple binding (`{{}}`) is present, but the one
+ * wrapped in `ng-non-bindable` is left alone.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <div>Normal: {{1 + 2}}</div>
+ <div ng-non-bindable>Ignored: {{1 + 2}}</div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-non-bindable', function() {
+ expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
+ expect(using('.doc-example-live').element('div:last').text()).
+ toMatch(/1 \+ 2/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
diff --git a/src/ng/directive/ngPluralize.js b/src/ng/directive/ngPluralize.js
new file mode 100644
index 00000000..a8cc40a6
--- /dev/null
+++ b/src/ng/directive/ngPluralize.js
@@ -0,0 +1,204 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-pluralize
+ * @restrict EA
+ *
+ * @description
+ * # Overview
+ * ng-pluralize is a directive that displays messages according to en-US localization rules.
+ * These rules are bundled with angular.js and the rules can be overridden
+ * (see {@link guide/dev_guide.i18n Angular i18n} dev guide). You configure ng-pluralize by
+ * specifying the mappings between
+ * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+ * plural categories} and the strings to be displayed.
+ *
+ * # Plural categories and explicit number rules
+ * There are two
+ * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+ * plural categories} in Angular's default en-US locale: "one" and "other".
+ *
+ * While a pural category may match many numbers (for example, in en-US locale, "other" can match
+ * any number that is not 1), an explicit number rule can only match one number. For example, the
+ * explicit number rule for "3" matches the number 3. You will see the use of plural categories
+ * and explicit number rules throughout later parts of this documentation.
+ *
+ * # Configuring ng-pluralize
+ * You configure ng-pluralize by providing 2 attributes: `count` and `when`.
+ * You can also provide an optional attribute, `offset`.
+ *
+ * The value of the `count` attribute can be either a string or an {@link guide/dev_guide.expressions
+ * Angular expression}; these are evaluated on the current scope for its binded value.
+ *
+ * The `when` attribute specifies the mappings between plural categories and the actual
+ * string to be displayed. The value of the attribute should be a JSON object so that Angular
+ * can interpret it correctly.
+ *
+ * The following example shows how to configure ng-pluralize:
+ *
+ * <pre>
+ * <ng-pluralize count="personCount"
+ when="{'0': 'Nobody is viewing.',
+ * 'one': '1 person is viewing.',
+ * 'other': '{} people are viewing.'}">
+ * </ng-pluralize>
+ *</pre>
+ *
+ * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
+ * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
+ * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
+ * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
+ * show "a dozen people are viewing".
+ *
+ * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
+ * into pluralized strings. In the previous example, Angular will replace `{}` with
+ * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
+ * for <span ng-non-bindable>{{numberExpression}}</span>.
+ *
+ * # Configuring ng-pluralize with offset
+ * The `offset` attribute allows further customization of pluralized text, which can result in
+ * a better user experience. For example, instead of the message "4 people are viewing this document",
+ * you might display "John, Kate and 2 others are viewing this document".
+ * The offset attribute allows you to offset a number by any desired value.
+ * Let's take a look at an example:
+ *
+ * <pre>
+ * <ng-pluralize count="personCount" offset=2
+ * when="{'0': 'Nobody is viewing.',
+ * '1': '{{person1}} is viewing.',
+ * '2': '{{person1}} and {{person2}} are viewing.',
+ * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
+ * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
+ * </ng-pluralize>
+ * </pre>
+ *
+ * Notice that we are still using two plural categories(one, other), but we added
+ * three explicit number rules 0, 1 and 2.
+ * When one person, perhaps John, views the document, "John is viewing" will be shown.
+ * When three people view the document, no explicit number rule is found, so
+ * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
+ * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
+ * is shown.
+ *
+ * Note that when you specify offsets, you must provide explicit number rules for
+ * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
+ * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
+ * plural categories "one" and "other".
+ *
+ * @param {string|expression} count The variable to be bounded to.
+ * @param {string} when The mapping between plural category to its correspoding strings.
+ * @param {number=} offset Offset to deduct from the total number.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.person1 = 'Igor';
+ $scope.person2 = 'Misko';
+ $scope.personCount = 1;
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Person 1:<input type="text" ng-model="person1" value="Igor" /><br/>
+ Person 2:<input type="text" ng-model="person2" value="Misko" /><br/>
+ Number of People:<input type="text" ng-model="personCount" value="1" /><br/>
+
+ <!--- Example with simple pluralization rules for en locale --->
+ Without Offset:
+ <ng-pluralize count="personCount"
+ when="{'0': 'Nobody is viewing.',
+ 'one': '1 person is viewing.',
+ 'other': '{} people are viewing.'}">
+ </ng-pluralize><br>
+
+ <!--- Example with offset --->
+ With Offset(2):
+ <ng-pluralize count="personCount" offset=2
+ when="{'0': 'Nobody is viewing.',
+ '1': '{{person1}} is viewing.',
+ '2': '{{person1}} and {{person2}} are viewing.',
+ 'one': '{{person1}}, {{person2}} and one other person are viewing.',
+ 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
+ </ng-pluralize>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should show correct pluralized string', function() {
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('1 person is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('0');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('Nobody is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Nobody is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('2');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('2 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor and Misko are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('3');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('3 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and one other person are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('4 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+ });
+
+ it('should show data-binded names', function() {
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+
+ using('.doc-example-live').input('person1').enter('Di');
+ using('.doc-example-live').input('person2').enter('Vojta');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Di, Vojta and 2 other people are viewing.');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
+ var BRACE = /{}/g;
+ return {
+ restrict: 'EA',
+ link: function(scope, element, attr) {
+ var numberExp = attr.count,
+ whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs
+ offset = attr.offset || 0,
+ whens = scope.$eval(whenExp),
+ whensExpFns = {};
+
+ forEach(whens, function(expression, key) {
+ whensExpFns[key] =
+ $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}'));
+ });
+
+ scope.$watch(function() {
+ var value = parseFloat(scope.$eval(numberExp));
+
+ if (!isNaN(value)) {
+ //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
+ //check it against pluralization rules in $locale service
+ if (!whens[value]) value = $locale.pluralCat(value - offset);
+ return whensExpFns[value](scope, element, true);
+ } else {
+ return '';
+ }
+ }, function(newVal) {
+ element.text(newVal);
+ });
+ }
+ };
+}];
diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js
new file mode 100644
index 00000000..82f8b9c7
--- /dev/null
+++ b/src/ng/directive/ngRepeat.js
@@ -0,0 +1,181 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-repeat
+ *
+ * @description
+ * The `ng-repeat` directive instantiates a template once per item from a collection. Each template
+ * instance gets its own scope, where the given loop variable is set to the current collection item,
+ * and `$index` is set to the item index or key.
+ *
+ * Special properties are exposed on the local scope of each template instance, including:
+ *
+ * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
+ * * `$position` – `{string}` – position of the repeated element in the iterator. One of:
+ * * `'first'`,
+ * * `'middle'`
+ * * `'last'`
+ *
+ *
+ * @element ANY
+ * @scope
+ * @priority 1000
+ * @param {repeat_expression} ng-repeat The expression indicating how to enumerate a collection. Two
+ * formats are currently supported:
+ *
+ * * `variable in expression` – where variable is the user defined loop variable and `expression`
+ * is a scope expression giving the collection to enumerate.
+ *
+ * For example: `track in cd.tracks`.
+ *
+ * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
+ * and `expression` is the scope expression giving the collection to enumerate.
+ *
+ * For example: `(name, age) in {'adam':10, 'amalie':12}`.
+ *
+ * @example
+ * This example initializes the scope to a list of names and
+ * then uses `ng-repeat` to display every person:
+ <doc:example>
+ <doc:source>
+ <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
+ I have {{friends.length}} friends. They are:
+ <ul>
+ <li ng-repeat="friend in friends">
+ [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
+ </li>
+ </ul>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-repeat', function() {
+ var r = using('.doc-example-live').repeater('ul li');
+ expect(r.count()).toBe(2);
+ expect(r.row(0)).toEqual(["1","John","25"]);
+ expect(r.row(1)).toEqual(["2","Mary","28"]);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngRepeatDirective = ngDirective({
+ transclude: 'element',
+ priority: 1000,
+ terminal: true,
+ compile: function(element, attr, linker) {
+ return function(scope, iterStartElement, attr){
+ var expression = attr.ngRepeat;
+ var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
+ lhs, rhs, valueIdent, keyIdent;
+ if (! match) {
+ throw Error("Expected ng-repeat in form of '_item_ in _collection_' but got '" +
+ expression + "'.");
+ }
+ lhs = match[1];
+ rhs = match[2];
+ match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
+ if (!match) {
+ throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
+ lhs + "'.");
+ }
+ valueIdent = match[3] || match[1];
+ keyIdent = match[2];
+
+ // Store a list of elements from previous run. This is a hash where key is the item from the
+ // iterator, and the value is an array of objects with following properties.
+ // - scope: bound scope
+ // - element: previous element.
+ // - index: position
+ // We need an array of these objects since the same object can be returned from the iterator.
+ // We expect this to be a rare case.
+ var lastOrder = new HashQueueMap();
+ scope.$watch(function(scope){
+ var index, length,
+ collection = scope.$eval(rhs),
+ collectionLength = size(collection, true),
+ childScope,
+ // Same as lastOrder but it has the current state. It will become the
+ // lastOrder on the next iteration.
+ nextOrder = new HashQueueMap(),
+ key, value, // key/value of iteration
+ array, last, // last object information {scope, element, index}
+ cursor = iterStartElement; // current position of the node
+
+ if (!isArray(collection)) {
+ // if object, extract keys, sort them and use to determine order of iteration over obj props
+ array = [];
+ for(key in collection) {
+ if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
+ array.push(key);
+ }
+ }
+ array.sort();
+ } else {
+ array = collection || [];
+ }
+
+ // we are not using forEach for perf reasons (trying to avoid #call)
+ for (index = 0, length = array.length; index < length; index++) {
+ key = (collection === array) ? index : array[index];
+ value = collection[key];
+ last = lastOrder.shift(value);
+ if (last) {
+ // if we have already seen this object, then we need to reuse the
+ // associated scope/element
+ childScope = last.scope;
+ nextOrder.push(value, last);
+
+ if (index === last.index) {
+ // do nothing
+ cursor = last.element;
+ } else {
+ // existing item which got moved
+ last.index = index;
+ // This may be a noop, if the element is next, but I don't know of a good way to
+ // figure this out, since it would require extra DOM access, so let's just hope that
+ // the browsers realizes that it is noop, and treats it as such.
+ cursor.after(last.element);
+ cursor = last.element;
+ }
+ } else {
+ // new item which we don't know about
+ childScope = scope.$new();
+ }
+
+ childScope[valueIdent] = value;
+ if (keyIdent) childScope[keyIdent] = key;
+ childScope.$index = index;
+ childScope.$position = index === 0 ?
+ 'first' :
+ (index == collectionLength - 1 ? 'last' : 'middle');
+
+ if (!last) {
+ linker(childScope, function(clone){
+ cursor.after(clone);
+ last = {
+ scope: childScope,
+ element: (cursor = clone),
+ index: index
+ };
+ nextOrder.push(value, last);
+ });
+ }
+ }
+
+ //shrink children
+ for (key in lastOrder) {
+ if (lastOrder.hasOwnProperty(key)) {
+ array = lastOrder[key];
+ while(array.length) {
+ value = array.pop();
+ value.element.remove();
+ value.scope.$destroy();
+ }
+ }
+ }
+
+ lastOrder = nextOrder;
+ });
+ };
+ }
+});
diff --git a/src/ng/directive/ngShowHide.js b/src/ng/directive/ngShowHide.js
new file mode 100644
index 00000000..40a8a68e
--- /dev/null
+++ b/src/ng/directive/ngShowHide.js
@@ -0,0 +1,80 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-show
+ *
+ * @description
+ * The `ng-show` and `ng-hide` directives show or hide a portion of the DOM tree (HTML)
+ * conditionally.
+ *
+ * @element ANY
+ * @param {expression} ng-show If the {@link guide/dev_guide.expressions expression} is truthy
+ * then the element is shown or hidden respectively.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Click me: <input type="checkbox" ng-model="checked"><br/>
+ Show: <span ng-show="checked">I show up when your checkbox is checked.</span> <br/>
+ Hide: <span ng-hide="checked">I hide when your checkbox is checked.</span>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+//TODO(misko): refactor to remove element from the DOM
+var ngShowDirective = ngDirective(function(scope, element, attr){
+ scope.$watch(attr.ngShow, function(value){
+ element.css('display', toBoolean(value) ? '' : 'none');
+ });
+});
+
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-hide
+ *
+ * @description
+ * The `ng-hide` and `ng-show` directives hide or show a portion
+ * of the HTML conditionally.
+ *
+ * @element ANY
+ * @param {expression} ng-hide If the {@link guide/dev_guide.expressions expression} truthy then
+ * the element is shown or hidden respectively.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Click me: <input type="checkbox" ng-model="checked"><br/>
+ Show: <span ng-show="checked">I show up when you checkbox is checked?</span> <br/>
+ Hide: <span ng-hide="checked">I hide when you checkbox is checked?</span>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+//TODO(misko): refactor to remove element from the DOM
+var ngHideDirective = ngDirective(function(scope, element, attr){
+ scope.$watch(attr.ngHide, function(value){
+ element.css('display', toBoolean(value) ? 'none' : '');
+ });
+});
diff --git a/src/ng/directive/ngStyle.js b/src/ng/directive/ngStyle.js
new file mode 100644
index 00000000..8a4e7458
--- /dev/null
+++ b/src/ng/directive/ngStyle.js
@@ -0,0 +1,42 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-style
+ *
+ * @description
+ * The ng-style allows you to set CSS style on an HTML element conditionally.
+ *
+ * @element ANY
+ * @param {expression} ng-style {@link guide/dev_guide.expressions Expression} which evals to an
+ * object whose keys are CSS style names and values are corresponding values for those CSS
+ * keys.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <input type="button" value="set" ng-click="myStyle={color:'red'}">
+ <input type="button" value="clear" ng-click="myStyle={}">
+ <br/>
+ <span ng-style="myStyle">Sample Text</span>
+ <pre>myStyle={{myStyle}}</pre>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-style', function() {
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ element('.doc-example-live :button[value=set]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
+ element('.doc-example-live :button[value=clear]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngStyleDirective = ngDirective(function(scope, element, attr) {
+ scope.$watch(attr.ngStyle, function(newStyles, oldStyles) {
+ if (oldStyles && (newStyles !== oldStyles)) {
+ forEach(oldStyles, function(val, style) { element.css(style, '');});
+ }
+ if (newStyles) element.css(newStyles);
+ }, true);
+});
diff --git a/src/ng/directive/ngSwitch.js b/src/ng/directive/ngSwitch.js
new file mode 100644
index 00000000..16b0c4d4
--- /dev/null
+++ b/src/ng/directive/ngSwitch.js
@@ -0,0 +1,112 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-switch
+ * @restrict EA
+ *
+ * @description
+ * Conditionally change the DOM structure.
+ *
+ * @usageContent
+ * <any ng-switch-when="matchValue1">...</any>
+ * <any ng-switch-when="matchValue2">...</any>
+ * ...
+ * <any ng-switch-default>...</any>
+ *
+ * @scope
+ * @param {*} ng-switch|on expression to match against <tt>ng-switch-when</tt>.
+ * @paramDescription
+ * On child elments add:
+ *
+ * * `ng-switch-when`: the case statement to match against. If match then this
+ * case will be displayed.
+ * * `ng-switch-default`: the default case when no other casses match.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.items = ['settings', 'home', 'other'];
+ $scope.selection = $scope.items[0];
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <select ng-model="selection" ng-options="item for item in items">
+ </select>
+ <tt>selection={{selection}}</tt>
+ <hr/>
+ <div ng-switch on="selection" >
+ <div ng-switch-when="settings">Settings Div</div>
+ <span ng-switch-when="home">Home Span</span>
+ <span ng-switch-default>default</span>
+ </div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should start in settings', function() {
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
+ });
+ it('should change to home', function() {
+ select('selection').option('home');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
+ });
+ it('should select deafault', function() {
+ select('selection').option('other');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var NG_SWITCH = 'ng-switch';
+var ngSwitchDirective = valueFn({
+ restrict: 'EA',
+ compile: function(element, attr) {
+ var watchExpr = attr.ngSwitch || attr.on,
+ cases = {};
+
+ element.data(NG_SWITCH, cases);
+ return function(scope, element){
+ var selectedTransclude,
+ selectedElement,
+ selectedScope;
+
+ scope.$watch(watchExpr, function(value) {
+ if (selectedElement) {
+ selectedScope.$destroy();
+ selectedElement.remove();
+ selectedElement = selectedScope = null;
+ }
+ if ((selectedTransclude = cases['!' + value] || cases['?'])) {
+ scope.$eval(attr.change);
+ selectedScope = scope.$new();
+ selectedTransclude(selectedScope, function(caseElement) {
+ selectedElement = caseElement;
+ element.append(caseElement);
+ });
+ }
+ });
+ };
+ }
+});
+
+var ngSwitchWhenDirective = ngDirective({
+ transclude: 'element',
+ priority: 500,
+ compile: function(element, attrs, transclude) {
+ var cases = element.inheritedData(NG_SWITCH);
+ assertArg(cases);
+ cases['!' + attrs.ngSwitchWhen] = transclude;
+ }
+});
+
+var ngSwitchDefaultDirective = ngDirective({
+ transclude: 'element',
+ priority: 500,
+ compile: function(element, attrs, transclude) {
+ var cases = element.inheritedData(NG_SWITCH);
+ assertArg(cases);
+ cases['?'] = transclude;
+ }
+});
diff --git a/src/ng/directive/ngTransclude.js b/src/ng/directive/ngTransclude.js
new file mode 100644
index 00000000..ab4011f0
--- /dev/null
+++ b/src/ng/directive/ngTransclude.js
@@ -0,0 +1,58 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-transclude
+ *
+ * @description
+ * Insert the transcluded DOM here.
+ *
+ * @element ANY
+ *
+ * @example
+ <doc:example module="transclude">
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.title = 'Lorem Ipsum';
+ $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
+ }
+
+ angular.module('transclude', [])
+ .directive('pane', function(){
+ return {
+ restrict: 'E',
+ transclude: true,
+ scope: 'isolate',
+ locals: { title:'bind' },
+ template: '<div style="border: 1px solid black;">' +
+ '<div style="background-color: gray">{{title}}</div>' +
+ '<div ng-transclude></div>' +
+ '</div>'
+ };
+ });
+ </script>
+ <div ng-controller="Ctrl">
+ <input ng-model="title"><br>
+ <textarea ng-model="text"></textarea> <br/>
+ <pane title="{{title}}">{{text}}</pane>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should have transcluded', function() {
+ input('title').enter('TITLE');
+ input('text').enter('TEXT');
+ expect(binding('title')).toEqual('TITLE');
+ expect(binding('text')).toEqual('TEXT');
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ */
+var ngTranscludeDirective = ngDirective({
+ controller: ['$transclude', '$element', function($transclude, $element) {
+ $transclude(function(clone) {
+ $element.append(clone);
+ });
+ }]
+});
diff --git a/src/ng/directive/ngView.js b/src/ng/directive/ngView.js
new file mode 100644
index 00000000..95b1546d
--- /dev/null
+++ b/src/ng/directive/ngView.js
@@ -0,0 +1,170 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.ng-view
+ * @restrict ECA
+ *
+ * @description
+ * # Overview
+ * `ng-view` is a directive that complements the {@link angular.module.ng.$route $route} service by
+ * including the rendered template of the current route into the main layout (`index.html`) file.
+ * Every time the current route changes, the included view changes with it according to the
+ * configuration of the `$route` service.
+ *
+ * @scope
+ * @example
+ <doc:example module="ngView">
+ <doc:source>
+ <script type="text/ng-template" id="examples/book.html">
+ controller: {{name}}<br />
+ Book Id: {{params.bookId}}<br />
+ </script>
+
+ <script type="text/ng-template" id="examples/chapter.html">
+ controller: {{name}}<br />
+ Book Id: {{params.bookId}}<br />
+ Chapter Id: {{params.chapterId}}
+ </script>
+
+ <script>
+ angular.module('ngView', [], function($routeProvider, $locationProvider) {
+ $routeProvider.when('/Book/:bookId', {
+ template: 'examples/book.html',
+ controller: BookCntl
+ });
+ $routeProvider.when('/Book/:bookId/ch/:chapterId', {
+ template: 'examples/chapter.html',
+ controller: ChapterCntl
+ });
+
+ // configure html5 to get links working on jsfiddle
+ $locationProvider.html5Mode(true);
+ });
+
+ function MainCntl($scope, $route, $routeParams, $location) {
+ $scope.$route = $route;
+ $scope.$location = $location;
+ $scope.$routeParams = $routeParams;
+ }
+
+ function BookCntl($scope, $routeParams) {
+ $scope.name = "BookCntl";
+ $scope.params = $routeParams;
+ }
+
+ function ChapterCntl($scope, $routeParams) {
+ $scope.name = "ChapterCntl";
+ $scope.params = $routeParams;
+ }
+ </script>
+
+ <div ng-controller="MainCntl">
+ Choose:
+ <a href="/Book/Moby">Moby</a> |
+ <a href="/Book/Moby/ch/1">Moby: Ch1</a> |
+ <a href="/Book/Gatsby">Gatsby</a> |
+ <a href="/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
+ <a href="/Book/Scarlet">Scarlet Letter</a><br/>
+
+ <div ng-view></div>
+ <hr />
+
+ <pre>$location.path() = {{$location.path()}}</pre>
+ <pre>$route.current.template = {{$route.current.template}}</pre>
+ <pre>$route.current.params = {{$route.current.params}}</pre>
+ <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
+ <pre>$routeParams = {{$routeParams}}</pre>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+
+/**
+ * @ngdoc event
+ * @name angular.module.ng.$compileProvider.directive.ng-view#$viewContentLoaded
+ * @eventOf angular.module.ng.$compileProvider.directive.ng-view
+ * @eventType emit on the current ng-view scope
+ * @description
+ * Emitted every time the ng-view content is reloaded.
+ */
+var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
+ '$controller',
+ function($http, $templateCache, $route, $anchorScroll, $compile,
+ $controller) {
+ return {
+ restrict: 'ECA',
+ terminal: true,
+ link: function(scope, element, attr) {
+ var changeCounter = 0,
+ lastScope,
+ onloadExp = attr.onload || '';
+
+ scope.$on('$afterRouteChange', function(event, next, previous) {
+ changeCounter++;
+ });
+
+ scope.$watch(function() {return changeCounter;}, function(newChangeCounter) {
+ var template = $route.current && $route.current.template;
+
+ function destroyLastScope() {
+ if (lastScope) {
+ lastScope.$destroy();
+ lastScope = null;
+ }
+ }
+
+ function clearContent() {
+ // ignore callback if another route change occured since
+ if (newChangeCounter == changeCounter) {
+ element.html('');
+ }
+ destroyLastScope();
+ }
+
+ if (template) {
+ $http.get(template, {cache: $templateCache}).success(function(response) {
+ // ignore callback if another route change occured since
+ if (newChangeCounter == changeCounter) {
+ element.html(response);
+ destroyLastScope();
+
+ var link = $compile(element.contents()),
+ current = $route.current;
+
+ lastScope = current.scope = scope.$new();
+ if (current.controller) {
+ element.contents().
+ data('$ngControllerController', $controller(current.controller, {$scope: lastScope}));
+ }
+
+ link(lastScope);
+ lastScope.$emit('$viewContentLoaded');
+ lastScope.$eval(onloadExp);
+
+ // $anchorScroll might listen on event...
+ $anchorScroll();
+ }
+ }).error(clearContent);
+ } else {
+ clearContent();
+ }
+ });
+ }
+ };
+}];
diff --git a/src/ng/directive/script.js b/src/ng/directive/script.js
new file mode 100644
index 00000000..4090ae24
--- /dev/null
+++ b/src/ng/directive/script.js
@@ -0,0 +1,43 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.script
+ *
+ * @description
+ * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
+ * template can be used by `ng-include`, `ng-view` or directive templates.
+ *
+ * @restrict E
+ * @param {'text/ng-template'} type must be set to `'text/ng-template'`
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script type="text/ng-template" id="/tpl.html">
+ Content of the template.
+ </script>
+
+ <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
+ <div id="tpl-content" ng-include src="currentTpl"></div>
+ </doc:source>
+ <doc:scenario>
+ it('should load template defined inside script tag', function() {
+ element('#tpl-link').click();
+ expect(element('#tpl-content').text()).toMatch(/Content of the template/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var scriptDirective = ['$templateCache', function($templateCache) {
+ return {
+ restrict: 'E',
+ terminal: true,
+ compile: function(element, attr) {
+ if (attr.type == 'text/ng-template') {
+ var templateUrl = attr.id;
+ $templateCache.put(templateUrl, element.text());
+ }
+ }
+ };
+}];
diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js
new file mode 100644
index 00000000..b70339fc
--- /dev/null
+++ b/src/ng/directive/select.js
@@ -0,0 +1,449 @@
+'use strict';
+
+/**
+ * @ngdoc directive
+ * @name angular.module.ng.$compileProvider.directive.select
+ * @restrict E
+ *
+ * @description
+ * HTML `SELECT` element with angular data-binding.
+ *
+ * # `ng-options`
+ *
+ * Optionally `ng-options` attribute can be used to dynamically generate a list of `<option>`
+ * elements for a `<select>` element using an array or an object obtained by evaluating the
+ * `ng-options` expression.
+ *˝˝
+ * When an item in the select menu is select, the value of array element or object property
+ * represented by the selected option will be bound to the model identified by the `ng-model` attribute
+ * of the parent select element.
+ *
+ * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
+ * be nested into the `<select>` element. This element will then represent `null` or "not selected"
+ * option. See example below for demonstration.
+ *
+ * Note: `ng-options` provides iterator facility for `<option>` element which must be used instead
+ * of {@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat}. `ng-repeat` is not suitable for use with
+ * `<option>` element because of the following reasons:
+ *
+ * * value attribute of the option element that we need to bind to requires a string, but the
+ * source of data for the iteration might be in a form of array containing objects instead of
+ * strings
+ * * {@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} unrolls after the select binds causing
+ * incorect rendering on most browsers.
+ * * binding to a value not in list confuses most browsers.
+ *
+ * @param {string} name assignable expression to data-bind to.
+ * @param {string=} required The control is considered valid only if value is entered.
+ * @param {comprehension_expression=} ng-options in one of the following forms:
+ *
+ * * for array data sources:
+ * * `label` **`for`** `value` **`in`** `array`
+ * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
+ * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
+ * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array`
+ * * for object data sources:
+ * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
+ * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
+ * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
+ * * `select` **`as`** `label` **`group by`** `group`
+ * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
+ *
+ * Where:
+ *
+ * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
+ * * `value`: local variable which will refer to each item in the `array` or each property value
+ * of `object` during iteration.
+ * * `key`: local variable which will refer to a property name in `object` during iteration.
+ * * `label`: The result of this expression will be the label for `<option>` element. The
+ * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
+ * * `select`: The result of this expression will be bound to the model of the parent `<select>`
+ * element. If not specified, `select` expression will default to `value`.
+ * * `group`: The result of this expression will be used to group options using the `<optgroup>`
+ * DOM element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function MyCntrl($scope) {
+ $scope.colors = [
+ {name:'black', shade:'dark'},
+ {name:'white', shade:'light'},
+ {name:'red', shade:'dark'},
+ {name:'blue', shade:'dark'},
+ {name:'yellow', shade:'light'}
+ ];
+ $scope.color = $scope.colors[2]; // red
+ }
+ </script>
+ <div ng-controller="MyCntrl">
+ <ul>
+ <li ng-repeat="color in colors">
+ Name: <input ng-model="color.name">
+ [<a href ng-click="colors.$remove(color)">X</a>]
+ </li>
+ <li>
+ [<a href ng-click="colors.push({})">add</a>]
+ </li>
+ </ul>
+ <hr/>
+ Color (null not allowed):
+ <select ng-model="color" ng-options="c.name for c in colors"></select><br>
+
+ Color (null allowed):
+ <div class="nullable">
+ <select ng-model="color" ng-options="c.name for c in colors">
+ <option value="">-- chose color --</option>
+ </select>
+ </div><br/>
+
+ Color grouped by shade:
+ <select ng-model="color" ng-options="c.name group by c.shade for c in colors">
+ </select><br/>
+
+
+ Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br>
+ <hr/>
+ Currently selected: {{ {selected_color:color} }}
+ <div style="border:solid 1px black; height:20px"
+ ng-style="{'background-color':color.name}">
+ </div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-options', function() {
+ expect(binding('{selected_color:color}')).toMatch('red');
+ select('color').option('0');
+ expect(binding('{selected_color:color}')).toMatch('black');
+ using('.nullable').select('color').option('');
+ expect(binding('{selected_color:color}')).toMatch('null');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+var ngOptionsDirective = valueFn({ terminal: true });
+var selectDirective = ['$compile', '$parse', function($compile, $parse) {
+ //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
+ var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/;
+
+ return {
+ restrict: 'E',
+ require: '?ngModel',
+ link: function(scope, element, attr, ctrl) {
+ if (!ctrl) return;
+
+ var multiple = attr.multiple,
+ optionsExp = attr.ngOptions;
+
+ // required validator
+ if (multiple && (attr.required || attr.ngRequired)) {
+ var requiredValidator = function(value) {
+ ctrl.$setValidity('required', !attr.required || (value && value.length));
+ return value;
+ };
+
+ ctrl.$parsers.push(requiredValidator);
+ ctrl.$formatters.unshift(requiredValidator);
+
+ attr.$observe('required', function() {
+ requiredValidator(ctrl.$viewValue);
+ });
+ }
+
+ if (optionsExp) Options(scope, element, ctrl);
+ else if (multiple) Multiple(scope, element, ctrl);
+ else Single(scope, element, ctrl);
+
+
+ ////////////////////////////
+
+
+
+ function Single(scope, selectElement, ctrl) {
+ ctrl.$render = function() {
+ selectElement.val(ctrl.$viewValue);
+ };
+
+ selectElement.bind('change', function() {
+ scope.$apply(function() {
+ ctrl.$setViewValue(selectElement.val());
+ });
+ });
+ }
+
+ function Multiple(scope, selectElement, ctrl) {
+ var lastView;
+ ctrl.$render = function() {
+ var items = new HashMap(ctrl.$viewValue);
+ forEach(selectElement.children(), function(option) {
+ option.selected = isDefined(items.get(option.value));
+ });
+ };
+
+ // we have to do it on each watch since ng-model watches reference, but
+ // we need to work of an array, so we need to see if anything was inserted/removed
+ scope.$watch(function() {
+ if (!equals(lastView, ctrl.$viewValue)) {
+ lastView = copy(ctrl.$viewValue);
+ ctrl.$render();
+ }
+ });
+
+ selectElement.bind('change', function() {
+ scope.$apply(function() {
+ var array = [];
+ forEach(selectElement.children(), function(option) {
+ if (option.selected) {
+ array.push(option.value);
+ }
+ });
+ ctrl.$setViewValue(array);
+ });
+ });
+ }
+
+ function Options(scope, selectElement, ctrl) {
+ var match;
+
+ if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
+ throw Error(
+ "Expected ng-options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
+ " but got '" + optionsExp + "'.");
+ }
+
+ var displayFn = $parse(match[2] || match[1]),
+ valueName = match[4] || match[6],
+ keyName = match[5],
+ groupByFn = $parse(match[3] || ''),
+ valueFn = $parse(match[2] ? match[1] : valueName),
+ valuesFn = $parse(match[7]),
+ // we can't just jqLite('<option>') since jqLite is not smart enough
+ // to create it in <select> and IE barfs otherwise.
+ optionTemplate = jqLite(document.createElement('option')),
+ optGroupTemplate = jqLite(document.createElement('optgroup')),
+ nullOption = false, // if false then user will not be able to select it
+ // This is an array of array of existing option groups in DOM. We try to reuse these if possible
+ // optionGroupsCache[0] is the options with no option group
+ // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
+ optionGroupsCache = [[{element: selectElement, label:''}]];
+
+ // find existing special options
+ forEach(selectElement.children(), function(option) {
+ if (option.value == '') {
+ // developer declared null option, so user should be able to select it
+ nullOption = jqLite(option).remove();
+ // compile the element since there might be bindings in it
+ $compile(nullOption)(scope);
+ }
+ });
+ selectElement.html(''); // clear contents
+
+ selectElement.bind('change', function() {
+ scope.$apply(function() {
+ var optionGroup,
+ collection = valuesFn(scope) || [],
+ locals = {},
+ key, value, optionElement, index, groupIndex, length, groupLength;
+
+ if (multiple) {
+ value = [];
+ for (groupIndex = 0, groupLength = optionGroupsCache.length;
+ groupIndex < groupLength;
+ groupIndex++) {
+ // list of options for that group. (first item has the parent)
+ optionGroup = optionGroupsCache[groupIndex];
+
+ for(index = 1, length = optionGroup.length; index < length; index++) {
+ if ((optionElement = optionGroup[index].element)[0].selected) {
+ key = optionElement.val();
+ if (keyName) locals[keyName] = key;
+ locals[valueName] = collection[key];
+ value.push(valueFn(scope, locals));
+ }
+ }
+ }
+ } else {
+ key = selectElement.val();
+ if (key == '?') {
+ value = undefined;
+ } else if (key == ''){
+ value = null;
+ } else {
+ locals[valueName] = collection[key];
+ if (keyName) locals[keyName] = key;
+ value = valueFn(scope, locals);
+ }
+ }
+ ctrl.$setViewValue(value);
+ });
+ });
+
+ ctrl.$render = render;
+
+ // TODO(vojta): can't we optimize this ?
+ scope.$watch(render);
+
+ function render() {
+ var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
+ optionGroupNames = [''],
+ optionGroupName,
+ optionGroup,
+ option,
+ existingParent, existingOptions, existingOption,
+ modelValue = ctrl.$modelValue,
+ values = valuesFn(scope) || [],
+ keys = keyName ? sortedKeys(values) : values,
+ groupLength, length,
+ groupIndex, index,
+ locals = {},
+ selected,
+ selectedSet = false, // nothing is selected yet
+ lastElement,
+ element;
+
+ if (multiple) {
+ selectedSet = new HashMap(modelValue);
+ } else if (modelValue === null || nullOption) {
+ // if we are not multiselect, and we are null then we have to add the nullOption
+ optionGroups[''].push({selected:modelValue === null, id:'', label:''});
+ selectedSet = true;
+ }
+
+ // We now build up the list of options we need (we merge later)
+ for (index = 0; length = keys.length, index < length; index++) {
+ locals[valueName] = values[keyName ? locals[keyName]=keys[index]:index];
+ optionGroupName = groupByFn(scope, locals) || '';
+ if (!(optionGroup = optionGroups[optionGroupName])) {
+ optionGroup = optionGroups[optionGroupName] = [];
+ optionGroupNames.push(optionGroupName);
+ }
+ if (multiple) {
+ selected = selectedSet.remove(valueFn(scope, locals)) != undefined;
+ } else {
+ selected = modelValue === valueFn(scope, locals);
+ selectedSet = selectedSet || selected; // see if at least one item is selected
+ }
+ optionGroup.push({
+ id: keyName ? keys[index] : index, // either the index into array or key from object
+ label: displayFn(scope, locals) || '', // what will be seen by the user
+ selected: selected // determine if we should be selected
+ });
+ }
+ if (!multiple && !selectedSet) {
+ // nothing was selected, we have to insert the undefined item
+ optionGroups[''].unshift({id:'?', label:'', selected:true});
+ }
+
+ // Now we need to update the list of DOM nodes to match the optionGroups we computed above
+ for (groupIndex = 0, groupLength = optionGroupNames.length;
+ groupIndex < groupLength;
+ groupIndex++) {
+ // current option group name or '' if no group
+ optionGroupName = optionGroupNames[groupIndex];
+
+ // list of options for that group. (first item has the parent)
+ optionGroup = optionGroups[optionGroupName];
+
+ if (optionGroupsCache.length <= groupIndex) {
+ // we need to grow the optionGroups
+ existingParent = {
+ element: optGroupTemplate.clone().attr('label', optionGroupName),
+ label: optionGroup.label
+ };
+ existingOptions = [existingParent];
+ optionGroupsCache.push(existingOptions);
+ selectElement.append(existingParent.element);
+ } else {
+ existingOptions = optionGroupsCache[groupIndex];
+ existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
+
+ // update the OPTGROUP label if not the same.
+ if (existingParent.label != optionGroupName) {
+ existingParent.element.attr('label', existingParent.label = optionGroupName);
+ }
+ }
+
+ lastElement = null; // start at the begining
+ for(index = 0, length = optionGroup.length; index < length; index++) {
+ option = optionGroup[index];
+ if ((existingOption = existingOptions[index+1])) {
+ // reuse elements
+ lastElement = existingOption.element;
+ if (existingOption.label !== option.label) {
+ lastElement.text(existingOption.label = option.label);
+ }
+ if (existingOption.id !== option.id) {
+ lastElement.val(existingOption.id = option.id);
+ }
+ if (existingOption.element.selected !== option.selected) {
+ lastElement.prop('selected', (existingOption.selected = option.selected));
+ }
+ } else {
+ // grow elements
+
+ // if it's a null option
+ if (option.id === '' && nullOption) {
+ // put back the pre-compiled element
+ element = nullOption;
+ } else {
+ // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
+ // in this version of jQuery on some browser the .text() returns a string
+ // rather then the element.
+ (element = optionTemplate.clone())
+ .val(option.id)
+ .attr('selected', option.selected)
+ .text(option.label);
+ }
+
+ existingOptions.push(existingOption = {
+ element: element,
+ label: option.label,
+ id: option.id,
+ selected: option.selected
+ });
+ if (lastElement) {
+ lastElement.after(element);
+ } else {
+ existingParent.element.append(element);
+ }
+ lastElement = element;
+ }
+ }
+ // remove any excessive OPTIONs in a group
+ index++; // increment since the existingOptions[0] is parent element not OPTION
+ while(existingOptions.length > index) {
+ existingOptions.pop().element.remove();
+ }
+ }
+ // remove any excessive OPTGROUPs from select
+ while(optionGroupsCache.length > groupIndex) {
+ optionGroupsCache.pop()[0].element.remove();
+ }
+ };
+ }
+ }
+ }
+}];
+
+var optionDirective = ['$interpolate', function($interpolate) {
+ return {
+ restrict: 'E',
+ priority: 100,
+ compile: function(element, attr) {
+ if (isUndefined(attr.value)) {
+ var interpolateFn = $interpolate(element.text(), true);
+ if (interpolateFn) {
+ return function (scope, element, attr) {
+ scope.$watch(interpolateFn, function(value) {
+ attr.$set('value', value);
+ });
+ }
+ } else {
+ attr.$set('value', element.text());
+ }
+ }
+ }
+ }
+}];
diff --git a/src/ng/directive/style.js b/src/ng/directive/style.js
new file mode 100644
index 00000000..68ea1465
--- /dev/null
+++ b/src/ng/directive/style.js
@@ -0,0 +1,6 @@
+'use strict';
+
+var styleDirective = valueFn({
+ restrict: 'E',
+ terminal: true
+});