aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgor Minar2010-11-17 22:32:35 -0800
committerIgor Minar2010-11-18 02:35:29 -0800
commit522ec1a9ec10e1fece3e5e855c1d7ef9770a8efc (patch)
treec4ae99f716539745d45b55e09e1aa6537db372e1
parent9cb57772a4030925318475fe93bc19e2916b37bf (diff)
downloadangular.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.template9
-rw-r--r--src/directives.js154
-rw-r--r--src/widgets.js160
-rw-r--r--test/directivesSpec.js79
-rw-r--r--test/widgetsSpec.js81
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>
&lt;{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}&gt;{{#usageContent}}
{{usageContent}}
{{/usageContent}}&lt;/{{shortName}}&gt;
</pre>
+{{/element}}
+{{#element}}
+ <pre>
+&lt;{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}"&gt;
+ ...
+&lt;/{{element}}&gt;
+ </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('');
+ });
+ });
});