diff options
| author | Igor Minar | 2010-11-17 22:32:35 -0800 |
|---|---|---|
| committer | Igor Minar | 2010-11-18 02:35:29 -0800 |
| commit | 522ec1a9ec10e1fece3e5e855c1d7ef9770a8efc (patch) | |
| tree | c4ae99f716539745d45b55e09e1aa6537db372e1 | |
| parent | 9cb57772a4030925318475fe93bc19e2916b37bf (diff) | |
| download | angular.js-522ec1a9ec10e1fece3e5e855c1d7ef9770a8efc.tar.bz2 | |
move attribute widgets to widgets.js file
- move @ng:repeat to widgets.js and its specs to widgetsSpecs.js
- move @ng:non-bindable to widgets.js and its specs to widgetsSpecs.js
- make widget.template suitable for attribute widgets
- fix up the js docs for attribute widgets
| -rw-r--r-- | docs/widget.template | 9 | ||||
| -rw-r--r-- | src/directives.js | 154 | ||||
| -rw-r--r-- | src/widgets.js | 160 | ||||
| -rw-r--r-- | test/directivesSpec.js | 79 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 81 |
5 files changed, 249 insertions, 234 deletions
diff --git a/docs/widget.template b/docs/widget.template index 99692950..b954f07c 100644 --- a/docs/widget.template +++ b/docs/widget.template @@ -13,12 +13,21 @@ <h2>Usage</h2> <h3>In HTML Template Binding</h3> <tt> +{{^element}} <pre> <{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}>{{#usageContent}} {{usageContent}} {{/usageContent}}</{{shortName}}> </pre> +{{/element}} +{{#element}} + <pre> +<{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}"> + ... +</{{element}}> + </pre> +{{/element}} </tt> <h3>Parameters</h3> diff --git a/src/directives.js b/src/directives.js index 2958773d..d47d993d 100644 --- a/src/directives.js +++ b/src/directives.js @@ -378,160 +378,6 @@ angularDirective("ng:bind-attr", function(expression){ }; }); -/** - * @ngdoc directive - * @name angular.directive.ng:non-bindable - * - * @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 ignore a chunk of HTML. - * - * @element ANY - * @param {string} ignore - * - * @exampleDescription - * In this example there are two location where - * <tt ng:non-bindable>{{1 + 2}}</tt> is present, but the one - * wrapped in `ng:non-bindable` is left alone - * @example - <div>Normal: {{1 + 2}}</div> - <div ng:non-bindable>Ignored: {{1 + 2}}</div> - * - * @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/); - }); - */ -angularWidget("@ng:non-bindable", noop); - -/** - * @ngdoc directive - * @name angular.directive.ng:repeat - * - * @description - * `ng:repeat` instantiates a template once per item from a - * collection. The collection is enumerated with - * `ng:repeat-index` attribute starting from 0. 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. - * - * NOTE: `ng:repeat` looks like a directive, but is actually a - * attribute widget. - * - * @element ANY - * @param {repeat} repeat_expression to itterate over. - * - * * `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}`. - * - * Special properties set on the local scope: - * * {number} $index - iterator offset of the repeated element (0..length-1) - * * {string} $position - position of the repeated element in the iterator ('first', 'middle', 'last') - * - * @exampleDescription - * This example initializes the scope to a list of names and - * than uses `ng:repeat` to display every person. - * @example - <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> - * @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"]); - }); - */ -angularWidget("@ng:repeat", function(expression, element){ - element.removeAttr('ng:repeat'); - element.replaceWith(this.comment("ng:repeat: " + expression)); - var template = this.compile(element); - return function(reference){ - 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 '" + - keyValue + "'."); - } - valueIdent = match[3] || match[1]; - keyIdent = match[2]; - - var children = [], currentScope = this; - this.$onEval(function(){ - var index = 0, - childCount = children.length, - lastElement = reference, - collection = this.$tryEval(rhs, reference), - is_array = isArray(collection), - collectionLength = 0, - childScope, - key; - - if (is_array) { - collectionLength = collection.length; - } else { - for (key in collection) - if (collection.hasOwnProperty(key)) - collectionLength++; - } - - for (key in collection) { - if (!is_array || collection.hasOwnProperty(key)) { - if (index < childCount) { - // reuse existing child - childScope = children[index]; - childScope[valueIdent] = collection[key]; - if (keyIdent) childScope[keyIdent] = key; - } else { - // grow children - childScope = template(quickClone(element), createScope(currentScope)); - childScope[valueIdent] = collection[key]; - if (keyIdent) childScope[keyIdent] = key; - lastElement.after(childScope.$element); - childScope.$index = index; - childScope.$position = index == 0 ? - 'first' : - (index == collectionLength - 1 ? 'last' : 'middle'); - childScope.$element.attr('ng:repeat-index', index); - childScope.$init(); - children.push(childScope); - } - childScope.$eval(); - lastElement = childScope.$element; - index ++; - } - } - // shrink children - while(children.length > index) { - children.pop().$element.remove(); - } - }, reference); - }; -}); - /** * @ngdoc directive diff --git a/src/widgets.js b/src/widgets.js index 887b31ac..930a6788 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -691,7 +691,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){ * changing the location or causing page reloads, e.g.: * <a href="" ng:click="model.$save()">Save</a> */ -angular.widget('a', function() { +angularWidget('a', function() { this.descend(true); this.directives(true); @@ -702,4 +702,160 @@ angular.widget('a', function() { }); } }; -});
\ No newline at end of file +}); + + +/** + * @ngdoc widget + * @name angular.widget.@ng:repeat + * + * @description + * `ng:repeat` instantiates a template once per item from a collection. The collection is enumerated + * with `ng:repeat-index` attribute starting from 0. 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. + * + * There are special properties exposed on the local scope of each template instance: + * + * * `$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'` or `'last'`. + * + * NOTE: `ng:repeat` looks like a directive, but is actually an attribute widget. + * + * @element ANY + * @param {string} repeat_expression 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}`. + * + * @exampleDescription + * This example initializes the scope to a list of names and + * than uses `ng:repeat` to display every person. + * @example + <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> + * @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"]); + }); + */ +angularWidget("@ng:repeat", function(expression, element){ + element.removeAttr('ng:repeat'); + element.replaceWith(this.comment("ng:repeat: " + expression)); + var template = this.compile(element); + return function(reference){ + 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 '" + + keyValue + "'."); + } + valueIdent = match[3] || match[1]; + keyIdent = match[2]; + + var children = [], currentScope = this; + this.$onEval(function(){ + var index = 0, + childCount = children.length, + lastElement = reference, + collection = this.$tryEval(rhs, reference), + is_array = isArray(collection), + collectionLength = 0, + childScope, + key; + + if (is_array) { + collectionLength = collection.length; + } else { + for (key in collection) + if (collection.hasOwnProperty(key)) + collectionLength++; + } + + for (key in collection) { + if (!is_array || collection.hasOwnProperty(key)) { + if (index < childCount) { + // reuse existing child + childScope = children[index]; + childScope[valueIdent] = collection[key]; + if (keyIdent) childScope[keyIdent] = key; + } else { + // grow children + childScope = template(quickClone(element), createScope(currentScope)); + childScope[valueIdent] = collection[key]; + if (keyIdent) childScope[keyIdent] = key; + lastElement.after(childScope.$element); + childScope.$index = index; + childScope.$position = index == 0 ? + 'first' : + (index == collectionLength - 1 ? 'last' : 'middle'); + childScope.$element.attr('ng:repeat-index', index); + childScope.$init(); + children.push(childScope); + } + childScope.$eval(); + lastElement = childScope.$element; + index ++; + } + } + // shrink children + while(children.length > index) { + children.pop().$element.remove(); + } + }, reference); + }; +}); + + +/** + * @ngdoc widget + * @name angular.widget.@ng:non-bindable + * + * @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. + * + * NOTE: `ng:non-bindable` looks like a directive, but is actually an attribute widget. + * + * @element ANY + * + * @exampleDescription + * In this example there are two location where a siple binding (`{{}}`) is present, but the one + * wrapped in `ng:non-bindable` is left alone. + * + * @example + <div>Normal: {{1 + 2}}</div> + <div ng:non-bindable>Ignored: {{1 + 2}}</div> + * + * @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/); + }); + */ +angularWidget("@ng:non-bindable", noop); diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 4b949fcb..b70067ba 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -1,4 +1,4 @@ -describe("directives", function(){ +describe("directive", function(){ var compile, model, element; @@ -128,83 +128,6 @@ describe("directives", function(){ expect(input.checked).toEqual(true); }); - it('should ng:non-bindable', function(){ - var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>'); - scope.$set('name', 'misko'); - scope.$eval(); - expect(element.text()).toEqual(''); - }); - - - describe('ng:repeat', function() { - - it('should ng:repeat over array', function(){ - var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>'); - - Array.prototype.extraProperty = "should be ignored"; - scope.items = ['misko', 'shyam']; - scope.$eval(); - expect(element.text()).toEqual('misko;shyam;'); - delete Array.prototype.extraProperty; - - scope.items = ['adam', 'kai', 'brad']; - scope.$eval(); - expect(element.text()).toEqual('adam;kai;brad;'); - - scope.items = ['brad']; - scope.$eval(); - expect(element.text()).toEqual('brad;'); - }); - - it('should ng:repeat over object', function(){ - var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>'); - scope.$set('items', {misko:'swe', shyam:'set'}); - scope.$eval(); - expect(element.text()).toEqual('misko:swe;shyam:set;'); - }); - - it('should error on wrong parsing of ng:repeat', function(){ - var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>'); - var log = ""; - log += element.attr('ng-exception') + ';'; - log += element.hasClass('ng-exception') + ';'; - expect(log.match(/Expected ng:repeat in form of 'item in collection' but got 'i dont parse'./)).toBeTruthy(); - }); - - it('should expose iterator offset as $index when iterating over arrays', function() { - var scope = compile('<ul><li ng:repeat="item in items" ' + - 'ng:bind="item + $index + \'|\'"></li></ul>'); - scope.items = ['misko', 'shyam', 'frodo']; - scope.$eval(); - expect(element.text()).toEqual('misko0|shyam1|frodo2|'); - }); - - it('should expose iterator offset as $index when iterating over objects', function() { - var scope = compile('<ul><li ng:repeat="(key, val) in items" ' + - 'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>'); - scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; - scope.$eval(); - expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|'); - }); - - it('should expose iterator position as $position when iterating over arrays', function() { - var scope = compile('<ul><li ng:repeat="item in items" ' + - 'ng:bind="item + \':\' + $position + \'|\'"></li></ul>'); - scope.items = ['misko', 'shyam', 'doug', 'frodo']; - scope.$eval(); - expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|'); - }); - - it('should expose iterator position as $position when iterating over objects', function() { - var scope = compile('<ul><li ng:repeat="(key, val) in items" ' + - 'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>'); - scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'}; - scope.$eval(); - expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|'); - }); - }); - - it('should ng:watch', function(){ var scope = compile('<div ng:watch="i: count = count + 1" ng:init="count = 0">'); scope.$eval(); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 03eb3acd..d3957e66 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -11,6 +11,7 @@ describe("widget", function(){ (before||noop).apply(scope); if (parent) parent.append(element); scope.$init(); + return scope; }; }); @@ -581,5 +582,85 @@ describe("widget", function(){ expect(document.location.href).toEqual(orgLocation); }); }); + + + describe('@ng:repeat', function() { + + it('should ng:repeat over array', function(){ + var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>'); + + Array.prototype.extraProperty = "should be ignored"; + scope.items = ['misko', 'shyam']; + scope.$eval(); + expect(element.text()).toEqual('misko;shyam;'); + delete Array.prototype.extraProperty; + + scope.items = ['adam', 'kai', 'brad']; + scope.$eval(); + expect(element.text()).toEqual('adam;kai;brad;'); + + scope.items = ['brad']; + scope.$eval(); + expect(element.text()).toEqual('brad;'); + }); + + it('should ng:repeat over object', function(){ + var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>'); + scope.$set('items', {misko:'swe', shyam:'set'}); + scope.$eval(); + expect(element.text()).toEqual('misko:swe;shyam:set;'); + }); + + it('should error on wrong parsing of ng:repeat', function(){ + var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>'); + var log = ""; + log += element.attr('ng-exception') + ';'; + log += element.hasClass('ng-exception') + ';'; + expect(log.match(/Expected ng:repeat in form of 'item in collection' but got 'i dont parse'./)).toBeTruthy(); + }); + + it('should expose iterator offset as $index when iterating over arrays', function() { + var scope = compile('<ul><li ng:repeat="item in items" ' + + 'ng:bind="item + $index + \'|\'"></li></ul>'); + scope.items = ['misko', 'shyam', 'frodo']; + scope.$eval(); + expect(element.text()).toEqual('misko0|shyam1|frodo2|'); + }); + + it('should expose iterator offset as $index when iterating over objects', function() { + var scope = compile('<ul><li ng:repeat="(key, val) in items" ' + + 'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>'); + scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; + scope.$eval(); + expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|'); + }); + + it('should expose iterator position as $position when iterating over arrays', function() { + var scope = compile('<ul><li ng:repeat="item in items" ' + + 'ng:bind="item + \':\' + $position + \'|\'"></li></ul>'); + scope.items = ['misko', 'shyam', 'doug', 'frodo']; + scope.$eval(); + expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|'); + }); + + it('should expose iterator position as $position when iterating over objects', function() { + var scope = compile('<ul><li ng:repeat="(key, val) in items" ' + + 'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>'); + scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'}; + scope.$eval(); + expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|'); + }); + }); + + + describe('@ng:non-bindable', function() { + + it('should prevent compilation of the owning element and its children', function(){ + var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>'); + scope.$set('name', 'misko'); + scope.$eval(); + expect(element.text()).toEqual(''); + }); + }); }); |
