2011-07-17 08:05:43 +00:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
2011-06-06 21:44:49 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc overview
|
|
|
|
|
|
* @name angular.widget
|
|
|
|
|
|
* @description
|
|
|
|
|
|
*
|
2011-07-30 01:42:16 +00:00
|
|
|
|
* An angular widget can be either a custom attribute that modifies an existing DOM element or an
|
2011-07-07 20:45:53 +00:00
|
|
|
|
* entirely new DOM element.
|
|
|
|
|
|
*
|
|
|
|
|
|
* During html compilation, widgets are processed after {@link angular.markup markup}, but before
|
|
|
|
|
|
* {@link angular.directive directives}.
|
2011-06-06 21:44:49 +00:00
|
|
|
|
*
|
|
|
|
|
|
* Following is the list of built-in angular widgets:
|
|
|
|
|
|
*
|
2011-06-07 05:02:30 +00:00
|
|
|
|
* * {@link angular.widget.@ng:non-bindable ng:non-bindable} - Blocks angular from processing an
|
2011-06-06 21:44:49 +00:00
|
|
|
|
* HTML element.
|
2011-06-07 05:02:30 +00:00
|
|
|
|
* * {@link angular.widget.@ng:repeat ng:repeat} - Creates and manages a collection of cloned HTML
|
2011-06-06 21:44:49 +00:00
|
|
|
|
* elements.
|
2011-09-08 20:56:29 +00:00
|
|
|
|
* * {@link angular.inputType HTML input elements} - Standard HTML input elements data-bound by
|
2011-07-25 20:26:45 +00:00
|
|
|
|
* angular.
|
2011-06-07 05:02:30 +00:00
|
|
|
|
* * {@link angular.widget.ng:view ng:view} - Works with $route to "include" partial templates
|
|
|
|
|
|
* * {@link angular.widget.ng:switch ng:switch} - Conditionally changes DOM structure
|
|
|
|
|
|
* * {@link angular.widget.ng:include ng:include} - Includes an external HTML fragment
|
|
|
|
|
|
*
|
|
|
|
|
|
* For more information about angular widgets, see {@link guide/dev_guide.compiler.widgets
|
|
|
|
|
|
* Understanding Angular Widgets} in the angular Developer Guide.
|
2011-06-06 21:44:49 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
2010-11-04 21:24:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc widget
|
|
|
|
|
|
* @name angular.widget.ng:include
|
2010-10-27 22:31:10 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @description
|
2011-06-30 18:16:56 +00:00
|
|
|
|
* Fetches, compiles and includes an external HTML fragment.
|
2011-01-13 18:35:26 +00:00
|
|
|
|
*
|
|
|
|
|
|
* Keep in mind that Same Origin Policy applies to included resources
|
2010-11-04 21:24:31 +00:00
|
|
|
|
* (e.g. ng:include won't work for file:// access).
|
|
|
|
|
|
*
|
2011-06-30 18:16:56 +00:00
|
|
|
|
* @param {string} 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'"`.
|
2011-01-13 18:35:26 +00:00
|
|
|
|
* @param {Scope=} [scope=new_child_scope] optional expression which evaluates to an
|
2010-12-22 23:44:27 +00:00
|
|
|
|
* instance of angular.scope to set the HTML fragment to.
|
2010-11-16 19:31:41 +00:00
|
|
|
|
* @param {string=} onload Expression to evaluate when a new partial is loaded.
|
2010-10-27 22:31:10 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @example
|
2011-02-01 00:21:29 +00:00
|
|
|
|
<doc:example>
|
2011-08-11 03:53:56 +00:00
|
|
|
|
<doc:source jsfiddle="false">
|
2011-09-08 20:56:29 +00:00
|
|
|
|
<script>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function Ctrl() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
this.templates =
|
|
|
|
|
|
[ { name: 'template1.html', url: 'examples/ng-include/template1.html'}
|
|
|
|
|
|
, { name: 'template2.html', url: 'examples/ng-include/template2.html'} ];
|
|
|
|
|
|
this.template = this.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/>
|
|
|
|
|
|
<ng:include src="template.url"></ng:include>
|
|
|
|
|
|
</div>
|
2011-02-01 00:21:29 +00:00
|
|
|
|
</doc:source>
|
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should load template1.html', function() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
expect(element('.doc-example-live .ng-include').text()).
|
2011-07-17 05:01:19 +00:00
|
|
|
|
toBe('Content of template1.html\n');
|
2011-02-01 00:21:29 +00:00
|
|
|
|
});
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should load template2.html', function() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
select('template').option('1');
|
|
|
|
|
|
expect(element('.doc-example-live .ng-include').text()).
|
2011-07-17 05:01:19 +00:00
|
|
|
|
toBe('Content of template2.html\n');
|
2011-02-01 00:21:29 +00:00
|
|
|
|
});
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should change to blank', function() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
select('template').option('');
|
|
|
|
|
|
expect(element('.doc-example-live .ng-include').text()).toEqual('');
|
2011-02-01 00:21:29 +00:00
|
|
|
|
});
|
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
|
</doc:example>
|
2010-10-27 22:31:10 +00:00
|
|
|
|
*/
|
2010-07-20 23:55:32 +00:00
|
|
|
|
angularWidget('ng:include', function(element){
|
2010-04-02 18:10:36 +00:00
|
|
|
|
var compiler = this,
|
2010-04-16 21:01:29 +00:00
|
|
|
|
srcExp = element.attr("src"),
|
2010-11-16 19:31:41 +00:00
|
|
|
|
scopeExp = element.attr("scope") || '',
|
|
|
|
|
|
onloadExp = element[0].getAttribute('onload') || ''; //workaround for jquery bug #7537
|
2010-07-02 22:39:47 +00:00
|
|
|
|
if (element[0]['ng:compiled']) {
|
2010-04-07 17:17:15 +00:00
|
|
|
|
this.descend(true);
|
|
|
|
|
|
this.directives(true);
|
|
|
|
|
|
} else {
|
2010-07-02 22:39:47 +00:00
|
|
|
|
element[0]['ng:compiled'] = true;
|
Introduced injector and $new to scope, and injection into link methods and controllers
- added angular.injector(scope, services, instanceCache) which returns inject
- inject method can return, instance, or call function which have $inject
property
- initialize services with $creation=[eager|eager-publish] this means that
only some of the services are now globally accessible
- upgraded $become on scope to use injector hence respect the $inject property
for injection
- $become should not be run multiple times and will most likely be removed
in future version
- added $new on scope to create a child scope
- $inject is respected on constructor function
- simplified scopes so that they no longer have separate __proto__ for
parent, api, behavior and instance this should speed up execution since
scope will now create one __proto__ chain per scope (not three).
BACKWARD COMPATIBILITY WARNING:
- services now need to have $inject instead of inject property for proper
injection this breaks backward compatibility
- not all services are now published into root scope
(only: $location, $cookie, $window)
- if you have widget/directive which uses services on scope
(such as this.$xhr), you will now have to inject that service in
(as it is not published on the root scope anymore)
2010-10-09 00:30:13 +00:00
|
|
|
|
return extend(function(xhr, element){
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var scope = this,
|
|
|
|
|
|
changeCounter = 0,
|
|
|
|
|
|
releaseScopes = [],
|
|
|
|
|
|
childScope,
|
|
|
|
|
|
oldScope;
|
|
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function incrementChange() { changeCounter++;}
|
2011-08-10 20:15:43 +00:00
|
|
|
|
this.$watch(srcExp, incrementChange);
|
|
|
|
|
|
this.$watch(function(scope){
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var newScope = scope.$eval(scopeExp);
|
|
|
|
|
|
if (newScope !== oldScope) {
|
|
|
|
|
|
oldScope = newScope;
|
|
|
|
|
|
incrementChange();
|
2011-01-24 06:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2011-10-07 18:27:49 +00:00
|
|
|
|
this.$watch(function() {return changeCounter;}, function(scope) {
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var src = scope.$eval(srcExp),
|
|
|
|
|
|
useScope = scope.$eval(scopeExp);
|
2010-11-16 19:31:41 +00:00
|
|
|
|
|
2011-03-23 16:33:29 +00:00
|
|
|
|
while(releaseScopes.length) {
|
|
|
|
|
|
releaseScopes.pop().$destroy();
|
|
|
|
|
|
}
|
2010-04-16 21:01:29 +00:00
|
|
|
|
if (src) {
|
2011-03-30 16:35:59 +00:00
|
|
|
|
xhr('GET', src, null, function(code, response){
|
2010-04-16 21:01:29 +00:00
|
|
|
|
element.html(response);
|
2011-03-23 16:33:29 +00:00
|
|
|
|
if (useScope) {
|
|
|
|
|
|
childScope = useScope;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
releaseScopes.push(childScope = scope.$new());
|
|
|
|
|
|
}
|
2011-02-12 18:13:28 +00:00
|
|
|
|
compiler.compile(element)(childScope);
|
2010-11-16 19:31:41 +00:00
|
|
|
|
scope.$eval(onloadExp);
|
2011-03-30 16:35:59 +00:00
|
|
|
|
}, false, true);
|
2010-08-16 23:47:39 +00:00
|
|
|
|
} else {
|
2010-10-05 17:50:16 +00:00
|
|
|
|
childScope = null;
|
2010-08-16 23:47:39 +00:00
|
|
|
|
element.html('');
|
2010-04-16 21:01:29 +00:00
|
|
|
|
}
|
2010-04-07 17:17:15 +00:00
|
|
|
|
});
|
Introduced injector and $new to scope, and injection into link methods and controllers
- added angular.injector(scope, services, instanceCache) which returns inject
- inject method can return, instance, or call function which have $inject
property
- initialize services with $creation=[eager|eager-publish] this means that
only some of the services are now globally accessible
- upgraded $become on scope to use injector hence respect the $inject property
for injection
- $become should not be run multiple times and will most likely be removed
in future version
- added $new on scope to create a child scope
- $inject is respected on constructor function
- simplified scopes so that they no longer have separate __proto__ for
parent, api, behavior and instance this should speed up execution since
scope will now create one __proto__ chain per scope (not three).
BACKWARD COMPATIBILITY WARNING:
- services now need to have $inject instead of inject property for proper
injection this breaks backward compatibility
- not all services are now published into root scope
(only: $location, $cookie, $window)
- if you have widget/directive which uses services on scope
(such as this.$xhr), you will now have to inject that service in
(as it is not published on the root scope anymore)
2010-10-09 00:30:13 +00:00
|
|
|
|
}, {$inject:['$xhr.cache']});
|
2010-04-07 17:17:15 +00:00
|
|
|
|
}
|
2010-04-02 18:10:36 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2010-11-04 21:24:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc widget
|
|
|
|
|
|
* @name angular.widget.ng:switch
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* Conditionally change the DOM structure.
|
2011-01-13 18:35:26 +00:00
|
|
|
|
*
|
2010-11-04 21:24:31 +00:00
|
|
|
|
* @usageContent
|
2010-11-11 00:08:54 +00:00
|
|
|
|
* <any ng:switch-when="matchValue1">...</any>
|
|
|
|
|
|
* <any ng:switch-when="matchValue2">...</any>
|
2010-11-04 21:24:31 +00:00
|
|
|
|
* ...
|
2010-11-11 00:08:54 +00:00
|
|
|
|
* <any ng:switch-default>...</any>
|
2011-01-13 18:35:26 +00:00
|
|
|
|
*
|
2010-11-04 21:24:31 +00:00
|
|
|
|
* @param {*} on expression to match against <tt>ng:switch-when</tt>.
|
2011-01-13 18:35:26 +00:00
|
|
|
|
* @paramDescription
|
2010-11-05 22:05:24 +00:00
|
|
|
|
* On child elments add:
|
2011-01-13 18:35:26 +00:00
|
|
|
|
*
|
2010-11-05 22:05:24 +00:00
|
|
|
|
* * `ng:switch-when`: the case statement to match against. If match then this
|
|
|
|
|
|
* case will be displayed.
|
2010-11-11 00:08:54 +00:00
|
|
|
|
* * `ng:switch-default`: the default case when no other casses match.
|
2010-11-04 21:24:31 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @example
|
2011-02-01 00:21:29 +00:00
|
|
|
|
<doc:example>
|
|
|
|
|
|
<doc:source>
|
2011-09-08 20:56:29 +00:00
|
|
|
|
<script>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function Ctrl() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
this.items = ['settings', 'home', 'other'];
|
|
|
|
|
|
this.selection = this.items[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<div ng:controller="Ctrl">
|
|
|
|
|
|
<select ng:model="selection" ng:options="item for item in items">
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<tt>selection={{selection}}</tt>
|
|
|
|
|
|
<hr/>
|
|
|
|
|
|
<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>
|
|
|
|
|
|
</ng:switch>
|
|
|
|
|
|
</div>
|
2011-02-01 00:21:29 +00:00
|
|
|
|
</doc:source>
|
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should start in settings', function() {
|
2011-03-03 17:52:35 +00:00
|
|
|
|
expect(element('.doc-example-live ng\\:switch').text()).toEqual('Settings Div');
|
2011-02-01 00:21:29 +00:00
|
|
|
|
});
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should change to home', function() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
select('selection').option('home');
|
2011-03-03 17:52:35 +00:00
|
|
|
|
expect(element('.doc-example-live ng\\:switch').text()).toEqual('Home Span');
|
2011-02-01 00:21:29 +00:00
|
|
|
|
});
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should select deafault', function() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
select('selection').option('other');
|
2011-03-03 17:52:35 +00:00
|
|
|
|
expect(element('.doc-example-live ng\\:switch').text()).toEqual('default');
|
2011-02-01 00:21:29 +00:00
|
|
|
|
});
|
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
|
</doc:example>
|
2010-11-04 21:24:31 +00:00
|
|
|
|
*/
|
2011-10-07 18:27:49 +00:00
|
|
|
|
angularWidget('ng:switch', function(element) {
|
2010-04-02 18:10:36 +00:00
|
|
|
|
var compiler = this,
|
2010-04-05 18:46:53 +00:00
|
|
|
|
watchExpr = element.attr("on"),
|
2011-03-23 16:33:29 +00:00
|
|
|
|
changeExpr = element.attr('change'),
|
|
|
|
|
|
casesTemplate = {},
|
|
|
|
|
|
defaultCaseTemplate,
|
|
|
|
|
|
children = element.children(),
|
|
|
|
|
|
length = children.length,
|
|
|
|
|
|
child,
|
|
|
|
|
|
when;
|
|
|
|
|
|
|
|
|
|
|
|
if (!watchExpr) throw new Error("Missing 'on' attribute.");
|
|
|
|
|
|
while(length--) {
|
|
|
|
|
|
child = jqLite(children[length]);
|
|
|
|
|
|
// this needs to be here for IE
|
|
|
|
|
|
child.remove();
|
|
|
|
|
|
when = child.attr('ng:switch-when');
|
2010-11-11 00:08:54 +00:00
|
|
|
|
if (isString(when)) {
|
2011-03-23 16:33:29 +00:00
|
|
|
|
casesTemplate[when] = compiler.compile(child);
|
|
|
|
|
|
} else if (isString(child.attr('ng:switch-default'))) {
|
|
|
|
|
|
defaultCaseTemplate = compiler.compile(child);
|
2010-04-05 18:46:53 +00:00
|
|
|
|
}
|
2011-03-23 16:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
children = null; // release memory;
|
2010-04-05 18:46:53 +00:00
|
|
|
|
element.html('');
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
2010-04-05 18:46:53 +00:00
|
|
|
|
return function(element){
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var changeCounter = 0;
|
|
|
|
|
|
var childScope;
|
|
|
|
|
|
var selectedTemplate;
|
|
|
|
|
|
|
|
|
|
|
|
this.$watch(watchExpr, function(scope, value) {
|
2010-04-05 18:46:53 +00:00
|
|
|
|
element.html('');
|
2011-08-14 08:26:56 +00:00
|
|
|
|
if ((selectedTemplate = casesTemplate[value] || defaultCaseTemplate)) {
|
2011-03-23 16:33:29 +00:00
|
|
|
|
changeCounter++;
|
|
|
|
|
|
if (childScope) childScope.$destroy();
|
|
|
|
|
|
childScope = scope.$new();
|
|
|
|
|
|
childScope.$eval(changeExpr);
|
|
|
|
|
|
}
|
2011-08-10 20:15:43 +00:00
|
|
|
|
});
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
this.$watch(function() {return changeCounter;}, function() {
|
2011-03-23 16:33:29 +00:00
|
|
|
|
element.html('');
|
|
|
|
|
|
if (selectedTemplate) {
|
|
|
|
|
|
selectedTemplate(childScope, function(caseElement) {
|
|
|
|
|
|
element.append(caseElement);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2010-04-07 17:17:15 +00:00
|
|
|
|
});
|
2010-04-02 18:10:36 +00:00
|
|
|
|
};
|
|
|
|
|
|
});
|
2010-09-30 15:07:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* 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>
|
|
|
|
|
|
*/
|
2010-11-18 06:32:35 +00:00
|
|
|
|
angularWidget('a', function() {
|
2010-09-30 15:07:36 +00:00
|
|
|
|
this.descend(true);
|
|
|
|
|
|
this.directives(true);
|
|
|
|
|
|
|
|
|
|
|
|
return function(element) {
|
2011-05-31 12:51:47 +00:00
|
|
|
|
var hasNgHref = ((element.attr('ng:bind-attr') || '').indexOf('"href":') !== -1);
|
|
|
|
|
|
|
|
|
|
|
|
// 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 (!hasNgHref && !element.attr('name') && !element.attr('href')) {
|
|
|
|
|
|
element.attr('href', '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (element.attr('href') === '' && !hasNgHref) {
|
2010-09-30 15:07:36 +00:00
|
|
|
|
element.bind('click', function(event){
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2010-11-18 06:32:35 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc widget
|
|
|
|
|
|
* @name angular.widget.@ng:repeat
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
2011-08-17 06:08:13 +00:00
|
|
|
|
* The `ng:repeat` widget 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.
|
2010-11-18 06:32:35 +00:00
|
|
|
|
*
|
2011-04-08 23:03:39 +00:00
|
|
|
|
* Special properties are exposed on the local scope of each template instance, including:
|
2010-11-18 06:32:35 +00:00
|
|
|
|
*
|
|
|
|
|
|
* * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
|
2011-05-19 15:38:23 +00:00
|
|
|
|
* * `$position` – `{string}` – position of the repeated element in the iterator. One of:
|
2011-04-08 23:03:39 +00:00
|
|
|
|
* * `'first'`,
|
2011-05-19 15:38:23 +00:00
|
|
|
|
* * `'middle'`
|
2011-04-08 23:03:39 +00:00
|
|
|
|
* * `'last'`
|
2010-11-18 06:32:35 +00:00
|
|
|
|
*
|
2011-04-08 23:03:39 +00:00
|
|
|
|
* Note: Although `ng:repeat` looks like a directive, it is actually an attribute widget.
|
2010-11-18 06:32:35 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @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`.
|
2011-04-08 23:03:39 +00:00
|
|
|
|
*
|
2010-11-18 06:32:35 +00:00
|
|
|
|
* * `(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}`.
|
|
|
|
|
|
*
|
2011-02-01 00:21:29 +00:00
|
|
|
|
* @example
|
2010-11-18 06:32:35 +00:00
|
|
|
|
* This example initializes the scope to a list of names and
|
2011-04-08 23:03:39 +00:00
|
|
|
|
* then uses `ng:repeat` to display every person:
|
2011-02-01 00:21:29 +00:00
|
|
|
|
<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>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should check ng:repeat', function() {
|
2011-02-01 00:21:29 +00:00
|
|
|
|
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>
|
2010-11-18 06:32:35 +00:00
|
|
|
|
*/
|
2011-02-14 00:13:21 +00:00
|
|
|
|
angularWidget('@ng:repeat', function(expression, element){
|
2010-11-18 06:32:35 +00:00
|
|
|
|
element.removeAttr('ng:repeat');
|
2011-06-23 22:13:15 +00:00
|
|
|
|
element.replaceWith(jqLite('<!-- ng:repeat: ' + expression + ' -->'));
|
2011-02-07 23:29:56 +00:00
|
|
|
|
var linker = this.compile(element);
|
2011-02-14 00:13:21 +00:00
|
|
|
|
return function(iterStartElement){
|
2010-11-18 06:32:35 +00:00
|
|
|
|
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
|
|
|
|
|
|
lhs, rhs, valueIdent, keyIdent;
|
|
|
|
|
|
if (! match) {
|
2011-04-19 23:34:49 +00:00
|
|
|
|
throw Error("Expected ng:repeat in form of '_item_ in _collection_' but got '" +
|
2010-11-18 06:32:35 +00:00
|
|
|
|
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];
|
|
|
|
|
|
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var parentScope = this;
|
2011-08-17 06:08:13 +00:00
|
|
|
|
// 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();
|
2011-08-10 20:15:43 +00:00
|
|
|
|
this.$watch(function(scope){
|
2010-11-18 06:32:35 +00:00
|
|
|
|
var index = 0,
|
2011-03-23 16:33:29 +00:00
|
|
|
|
collection = scope.$eval(rhs),
|
2011-03-27 22:58:24 +00:00
|
|
|
|
collectionLength = size(collection, true),
|
2010-11-18 06:32:35 +00:00
|
|
|
|
childScope,
|
2011-08-17 06:08:13 +00:00
|
|
|
|
// 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
|
2010-11-18 06:32:35 +00:00
|
|
|
|
|
|
|
|
|
|
for (key in collection) {
|
2011-10-18 23:46:27 +00:00
|
|
|
|
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
|
2011-08-17 06:08:13 +00:00
|
|
|
|
last = lastOrder.shift(value = collection[key]);
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2010-11-18 06:32:35 +00:00
|
|
|
|
} else {
|
2011-08-17 06:08:13 +00:00
|
|
|
|
// new item which we don't know about
|
2011-03-23 16:33:29 +00:00
|
|
|
|
childScope = parentScope.$new();
|
2011-08-17 06:08:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
childScope[valueIdent] = collection[key];
|
|
|
|
|
|
if (keyIdent) childScope[keyIdent] = key;
|
|
|
|
|
|
childScope.$index = index;
|
|
|
|
|
|
childScope.$position = index == 0
|
|
|
|
|
|
? 'first'
|
|
|
|
|
|
: (index == collectionLength - 1 ? 'last' : 'middle');
|
|
|
|
|
|
|
|
|
|
|
|
if (!last) {
|
2011-02-14 00:13:21 +00:00
|
|
|
|
linker(childScope, function(clone){
|
2011-08-17 06:08:13 +00:00
|
|
|
|
cursor.after(clone);
|
|
|
|
|
|
last = {
|
|
|
|
|
|
scope: childScope,
|
|
|
|
|
|
element: (cursor = clone),
|
|
|
|
|
|
index: index
|
|
|
|
|
|
};
|
|
|
|
|
|
nextOrder.push(value, last);
|
2011-02-14 00:13:21 +00:00
|
|
|
|
});
|
2010-11-18 06:32:35 +00:00
|
|
|
|
}
|
2011-08-17 06:08:13 +00:00
|
|
|
|
|
2010-11-18 06:32:35 +00:00
|
|
|
|
index ++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-03-29 00:51:51 +00:00
|
|
|
|
|
2011-08-17 06:08:13 +00:00
|
|
|
|
//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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-03-29 00:51:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-08-17 06:08:13 +00:00
|
|
|
|
lastOrder = nextOrder;
|
2011-03-23 16:33:29 +00:00
|
|
|
|
});
|
2010-11-18 06:32:35 +00:00
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @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.
|
|
|
|
|
|
*
|
2011-07-30 01:42:16 +00:00
|
|
|
|
* Note: `ng:non-bindable` looks like a directive, but is actually an attribute widget.
|
2010-11-18 06:32:35 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @element ANY
|
|
|
|
|
|
*
|
2011-02-01 00:21:29 +00:00
|
|
|
|
* @example
|
2011-07-30 01:42:16 +00:00
|
|
|
|
* In this example there are two location where a simple binding (`{{}}`) is present, but the one
|
2010-11-18 06:32:35 +00:00
|
|
|
|
* wrapped in `ng:non-bindable` is left alone.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
2011-02-01 00:21:29 +00:00
|
|
|
|
<doc:example>
|
|
|
|
|
|
<doc:source>
|
|
|
|
|
|
<div>Normal: {{1 + 2}}</div>
|
|
|
|
|
|
<div ng:non-bindable>Ignored: {{1 + 2}}</div>
|
|
|
|
|
|
</doc:source>
|
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should check ng:non-bindable', function() {
|
2011-02-01 00:21:29 +00:00
|
|
|
|
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>
|
2010-11-18 06:32:35 +00:00
|
|
|
|
*/
|
|
|
|
|
|
angularWidget("@ng:non-bindable", noop);
|
2011-01-19 22:50:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc widget
|
|
|
|
|
|
* @name angular.widget.ng:view
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* # Overview
|
|
|
|
|
|
* `ng:view` is a widget that complements the {@link angular.service.$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.
|
|
|
|
|
|
*
|
2011-05-18 12:16:09 +00:00
|
|
|
|
* This widget provides functionality similar to {@link angular.widget.ng:include ng:include} when
|
2011-01-19 22:50:29 +00:00
|
|
|
|
* used like this:
|
|
|
|
|
|
*
|
|
|
|
|
|
* <ng:include src="$route.current.template" scope="$route.current.scope"></ng:include>
|
|
|
|
|
|
*
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Advantages
|
|
|
|
|
|
* Compared to `ng:include`, `ng:view` offers these advantages:
|
|
|
|
|
|
*
|
|
|
|
|
|
* - shorter syntax
|
|
|
|
|
|
* - more efficient execution
|
|
|
|
|
|
* - doesn't require `$route` service to be available on the root scope
|
|
|
|
|
|
*
|
|
|
|
|
|
*
|
2011-02-01 00:21:29 +00:00
|
|
|
|
* @example
|
|
|
|
|
|
<doc:example>
|
2011-08-11 03:53:56 +00:00
|
|
|
|
<doc:source jsfiddle="false">
|
2011-02-01 00:21:29 +00:00
|
|
|
|
<script>
|
|
|
|
|
|
function MyCtrl($route) {
|
2011-07-18 17:42:42 +00:00
|
|
|
|
$route.when('/overview',
|
|
|
|
|
|
{ controller: OverviewCtrl,
|
2011-08-30 09:47:24 +00:00
|
|
|
|
template: 'partials/guide/dev_guide.overview.html'});
|
2011-07-18 17:42:42 +00:00
|
|
|
|
$route.when('/bootstrap',
|
|
|
|
|
|
{ controller: BootstrapCtrl,
|
2011-08-30 09:47:24 +00:00
|
|
|
|
template: 'partials/guide/dev_guide.bootstrap.auto_bootstrap.html'});
|
2011-02-01 00:21:29 +00:00
|
|
|
|
};
|
|
|
|
|
|
MyCtrl.$inject = ['$route'];
|
2011-01-19 22:50:29 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function BootstrapCtrl() {}
|
|
|
|
|
|
function OverviewCtrl() {}
|
2011-02-01 00:21:29 +00:00
|
|
|
|
</script>
|
|
|
|
|
|
<div ng:controller="MyCtrl">
|
2011-08-30 09:47:24 +00:00
|
|
|
|
<a href="overview">overview</a> |
|
|
|
|
|
|
<a href="bootstrap">bootstrap</a> |
|
|
|
|
|
|
<a href="undefined">undefined</a>
|
2011-07-18 17:42:42 +00:00
|
|
|
|
|
|
|
|
|
|
<br/>
|
|
|
|
|
|
|
2011-02-01 00:21:29 +00:00
|
|
|
|
The view is included below:
|
|
|
|
|
|
<hr/>
|
|
|
|
|
|
<ng:view></ng:view>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</doc:source>
|
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should load templates', function() {
|
2011-07-18 17:42:42 +00:00
|
|
|
|
element('.doc-example-live a:contains(overview)').click();
|
|
|
|
|
|
expect(element('.doc-example-live ng\\:view').text()).toMatch(/Developer Guide: Overview/);
|
|
|
|
|
|
|
|
|
|
|
|
element('.doc-example-live a:contains(bootstrap)').click();
|
2011-10-13 05:50:35 +00:00
|
|
|
|
expect(element('.doc-example-live ng\\:view').text()).toMatch(/Developer Guide: Initializing Angular: Automatic Initialization/);
|
2011-07-18 17:42:42 +00:00
|
|
|
|
});
|
2011-02-01 00:21:29 +00:00
|
|
|
|
</doc:scenario>
|
|
|
|
|
|
</doc:example>
|
2011-01-19 22:50:29 +00:00
|
|
|
|
*/
|
|
|
|
|
|
angularWidget('ng:view', function(element) {
|
|
|
|
|
|
var compiler = this;
|
|
|
|
|
|
|
|
|
|
|
|
if (!element[0]['ng:compiled']) {
|
|
|
|
|
|
element[0]['ng:compiled'] = true;
|
2011-04-18 23:33:30 +00:00
|
|
|
|
return annotate('$xhr.cache', '$route', function($xhr, $route, element){
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var template;
|
|
|
|
|
|
var changeCounter = 0;
|
2011-01-24 21:27:28 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
this.$on('$afterRouteChange', function() {
|
2011-03-23 16:33:29 +00:00
|
|
|
|
changeCounter++;
|
2011-08-24 05:30:14 +00:00
|
|
|
|
});
|
2011-01-19 22:50:29 +00:00
|
|
|
|
|
2011-10-26 19:15:07 +00:00
|
|
|
|
this.$watch(function() {return changeCounter;}, function(scope, newChangeCounter) {
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var template = $route.current && $route.current.template;
|
|
|
|
|
|
if (template) {
|
2011-04-04 22:28:21 +00:00
|
|
|
|
//xhr's callback must be async, see commit history for more info
|
2011-03-23 16:33:29 +00:00
|
|
|
|
$xhr('GET', template, function(code, response) {
|
2011-10-26 19:15:07 +00:00
|
|
|
|
// ignore callback if another route change occured since
|
|
|
|
|
|
if (newChangeCounter == changeCounter) {
|
|
|
|
|
|
element.html(response);
|
|
|
|
|
|
compiler.compile(element)($route.current.scope);
|
|
|
|
|
|
}
|
2011-04-04 22:28:21 +00:00
|
|
|
|
});
|
2011-01-19 22:50:29 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
element.html('');
|
|
|
|
|
|
}
|
2011-01-24 21:27:28 +00:00
|
|
|
|
});
|
2011-01-19 22:50:29 +00:00
|
|
|
|
});
|
|
|
|
|
|
} else {
|
2011-03-23 16:33:29 +00:00
|
|
|
|
compiler.descend(true);
|
|
|
|
|
|
compiler.directives(true);
|
2011-01-19 22:50:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2011-08-16 23:00:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc widget
|
|
|
|
|
|
* @name angular.widget.ng:pluralize
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* # Overview
|
|
|
|
|
|
* ng:pluralize is a widget 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>
|
2011-09-08 20:56:29 +00:00
|
|
|
|
<script>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function Ctrl() {
|
2011-09-08 20:56:29 +00:00
|
|
|
|
this.person1 = 'Igor';
|
|
|
|
|
|
this.person2 = 'Misko';
|
|
|
|
|
|
this.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>
|
2011-08-16 23:00:16 +00:00
|
|
|
|
</doc:source>
|
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should show correct pluralized string', function() {
|
2011-08-16 23:00:16 +00:00
|
|
|
|
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.');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
it('should show data-binded names', function() {
|
2011-08-16 23:00:16 +00:00
|
|
|
|
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>
|
|
|
|
|
|
*/
|
|
|
|
|
|
angularWidget('ng:pluralize', function(element) {
|
|
|
|
|
|
var numberExp = element.attr('count'),
|
|
|
|
|
|
whenExp = element.attr('when'),
|
|
|
|
|
|
offset = element.attr('offset') || 0;
|
|
|
|
|
|
|
|
|
|
|
|
return annotate('$locale', function($locale, element) {
|
|
|
|
|
|
var scope = this,
|
|
|
|
|
|
whens = scope.$eval(whenExp),
|
|
|
|
|
|
whensExpFns = {};
|
|
|
|
|
|
|
|
|
|
|
|
forEach(whens, function(expression, key) {
|
|
|
|
|
|
whensExpFns[key] = compileBindTemplate(expression.replace(/{}/g,
|
|
|
|
|
|
'{{' + 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(scope, newVal) {
|
|
|
|
|
|
element.text(newVal);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|