Skip to content

Commit 683bb6e

Browse files
committed
feat(directive): add ng-switch directive
1 parent 769e974 commit 683bb6e

File tree

3 files changed

+352
-1
lines changed

3 files changed

+352
-1
lines changed

modules/directives/src/ng_switch.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import {Decorator, Template} from 'core/annotations/annotations';
2+
import {ViewPort} from 'core/compiler/viewport';
3+
import {NgElement} from 'core/dom/element';
4+
import {DOM} from 'facade/dom';
5+
import {isPresent, isBlank} from 'facade/lang';
6+
import {ListWrapper, List, MapWrapper, Map} from 'facade/collection';
7+
import {Parent} from 'core/annotations/visibility';
8+
9+
/**
10+
* The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a
11+
* scope expression.
12+
* Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be
13+
* preserved at the location as specified in the template.
14+
*
15+
* `ngSwitch` simply chooses nested elements and makes them visible based on which element matches
16+
* the value obtained from the evaluated expression. In other words, you define a container element
17+
* (where you place the directive), place an expression on the **`[ng-switch]="..."` attribute**),
18+
* define any inner elements inside of the directive and place a `[ng-switch-when]` attribute per
19+
* element.
20+
* The when attribute is used to inform ngSwitch which element to display when the expression is
21+
* evaluated. If a matching expression is not found via a when attribute then an element with the
22+
* default attribute is displayed.
23+
*
24+
* Example:
25+
*
26+
* ```
27+
* <ANY [ng-switch]="expression">
28+
* <template [ng-switch-when]="whenExpression1">...</template>
29+
* <template [ng-switch-when]="whenExpression1">...</template>
30+
* <template [ng-switch-default]>...</template>
31+
* </ANY>
32+
* ```
33+
*/
34+
@Decorator({
35+
selector: '[ng-switch]',
36+
bind: {
37+
'ng-switch': 'value'
38+
}
39+
})
40+
export class NgSwitch {
41+
_switchValue: any;
42+
_useDefault: boolean;
43+
_valueViewPorts: Map;
44+
_activeViewPorts: List;
45+
46+
constructor() {
47+
this._valueViewPorts = MapWrapper.create();
48+
this._activeViewPorts = ListWrapper.create();
49+
this._useDefault = false;
50+
}
51+
52+
set value(value) {
53+
// Remove the currently active viewports
54+
this._removeAllActiveViewPorts();
55+
56+
// Add the viewports matching the value (with a fallback to default)
57+
this._useDefault = false;
58+
var viewPorts = MapWrapper.get(this._valueViewPorts, value);
59+
if (isBlank(viewPorts)) {
60+
this._useDefault = true;
61+
viewPorts = MapWrapper.get(this._valueViewPorts, _whenDefault);
62+
}
63+
this._activateViewPorts(viewPorts);
64+
65+
this._switchValue = value;
66+
}
67+
68+
_onWhenValueChanged(oldWhen, newWhen, viewPort: ViewPort) {
69+
this._deregisterViewPort(oldWhen, viewPort);
70+
this._registerViewPort(newWhen, viewPort);
71+
72+
if (oldWhen === this._switchValue) {
73+
viewPort.remove();
74+
ListWrapper.remove(this._activeViewPorts, viewPort);
75+
} else if (newWhen === this._switchValue) {
76+
if (this._useDefault) {
77+
this._useDefault = false;
78+
this._removeAllActiveViewPorts();
79+
}
80+
viewPort.create();
81+
ListWrapper.push(this._activeViewPorts, viewPort);
82+
}
83+
84+
// Switch to default when there is no more active viewports
85+
if (this._activeViewPorts.length === 0 && !this._useDefault) {
86+
this._useDefault = true;
87+
this._activateViewPorts(MapWrapper.get(this._valueViewPorts, _whenDefault));
88+
}
89+
}
90+
91+
_removeAllActiveViewPorts() {
92+
var activeViewPorts = this._activeViewPorts;
93+
for (var i = 0; i < activeViewPorts.length; i++) {
94+
activeViewPorts[i].remove();
95+
}
96+
this._activeViewPorts = ListWrapper.create();
97+
}
98+
99+
_activateViewPorts(viewPorts) {
100+
// TODO(vicb): assert(this._activeViewPorts.length === 0);
101+
if (isPresent(viewPorts)) {
102+
for (var i = 0; i < viewPorts.length; i++) {
103+
viewPorts[i].create();
104+
}
105+
this._activeViewPorts = viewPorts;
106+
}
107+
}
108+
109+
_registerViewPort(value, viewPort: ViewPort) {
110+
var viewPorts = MapWrapper.get(this._valueViewPorts, value);
111+
if (isBlank(viewPorts)) {
112+
viewPorts = ListWrapper.create();
113+
MapWrapper.set(this._valueViewPorts, value, viewPorts);
114+
}
115+
ListWrapper.push(viewPorts, viewPort);
116+
}
117+
118+
_deregisterViewPort(value, viewPort: ViewPort) {
119+
// `_whenDefault` is used a marker for non-registered whens
120+
if (value == _whenDefault) return;
121+
var viewPorts = MapWrapper.get(this._valueViewPorts, value);
122+
if (viewPorts.length == 1) {
123+
MapWrapper.delete(this._valueViewPorts, value);
124+
} else {
125+
ListWrapper.remove(viewPorts, viewPort);
126+
}
127+
}
128+
}
129+
130+
/**
131+
* Defines a case statement as an expression.
132+
*
133+
* If multiple `ngSwitchWhen` match the `ngSwitch` value, all of them are displayed.
134+
*
135+
* Example:
136+
*
137+
* ```
138+
* // match against a context variable
139+
* <template [ng-switch-when]="contextVariable">...</template>
140+
*
141+
* // match against a constant string
142+
* <template [ng-switch-when]="'stringValue'">...</template>
143+
* ```
144+
*/
145+
@Template({
146+
selector: '[ng-switch-when]',
147+
bind: {
148+
'ng-switch-when' : 'when'
149+
}
150+
})
151+
export class NgSwitchWhen {
152+
_value: any;
153+
_ngSwitch: NgSwitch;
154+
_viewPort: ViewPort;
155+
156+
constructor(el: NgElement, viewPort: ViewPort, @Parent() ngSwitch: NgSwitch) {
157+
// `_whenDefault` is used as a marker for a not yet initialized value
158+
this._value = _whenDefault;
159+
this._ngSwitch = ngSwitch;
160+
this._viewPort = viewPort;
161+
}
162+
163+
set when(value) {
164+
this._ngSwitch._onWhenValueChanged(this._value, value, this._viewPort);
165+
this._value = value;
166+
}
167+
}
168+
169+
170+
/**
171+
* Defines a default case statement.
172+
*
173+
* Default case statements are displayed when no `NgSwitchWhen` match the `ngSwitch` value.
174+
*
175+
* Example:
176+
*
177+
* ```
178+
* <template [ng-switch-default]>...</template>
179+
* ```
180+
*/
181+
@Template({
182+
selector: '[ng-switch-default]'
183+
})
184+
export class NgSwitchDefault {
185+
constructor(viewPort: ViewPort, @Parent() ngSwitch: NgSwitch) {
186+
ngSwitch._registerViewPort(_whenDefault, viewPort);
187+
}
188+
}
189+
190+
var _whenDefault = new Object();

modules/directives/test/ng_repeat_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_li
33
import {DOM} from 'facade/dom';
44

55
import {Injector} from 'di/di';
6-
import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
6+
import {Lexer, Parser} from 'change_detection/change_detection';
77

88
import {Compiler, CompilerCache} from 'core/compiler/compiler';
99
import {OnChange} from 'core/compiler/interfaces';
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
2+
import {DOM} from 'facade/dom';
3+
import {Injector} from 'di/di';
4+
import {Lexer, Parser} from 'change_detection/change_detection';
5+
import {Compiler, CompilerCache} from 'core/compiler/compiler';
6+
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
7+
import {Component} from 'core/annotations/annotations';
8+
import {TemplateConfig} from 'core/annotations/template_config';
9+
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from 'directives/ng_switch';
10+
11+
export function main() {
12+
describe('ng-switch', () => {
13+
var view, cd, compiler, component;
14+
beforeEach(() => {
15+
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
16+
});
17+
18+
function createView(pv) {
19+
component = new TestComponent();
20+
view = pv.instantiate(null);
21+
view.hydrate(new Injector([]), null, component);
22+
cd = view.changeDetector;
23+
}
24+
25+
function compileWithTemplate(template) {
26+
return compiler.compile(TestComponent, el(template));
27+
}
28+
29+
describe('switch value changes', () => {
30+
it('should switch amongst when values', (done) => {
31+
var template = '<div>' +
32+
'<ul [ng-switch]="switchValue">' +
33+
'<template [ng-switch-when]="\'a\'"><li>when a</li></template>' +
34+
'<template [ng-switch-when]="\'b\'"><li>when b</li></template>' +
35+
'</ul></div>';
36+
compileWithTemplate(template).then((pv) => {
37+
createView(pv);
38+
cd.detectChanges();
39+
expect(DOM.getText(view.nodes[0])).toEqual('');
40+
41+
component.switchValue = 'a';
42+
cd.detectChanges();
43+
expect(DOM.getText(view.nodes[0])).toEqual('when a');
44+
45+
component.switchValue = 'b';
46+
cd.detectChanges();
47+
expect(DOM.getText(view.nodes[0])).toEqual('when b');
48+
49+
done();
50+
});
51+
});
52+
53+
it('should switch amongst when values with fallback to default', (done) => {
54+
var template = '<div>' +
55+
'<ul [ng-switch]="switchValue">' +
56+
'<li template="ng-switch-when \'a\'">when a</li>' +
57+
'<li template="ng-switch-default">when default</li>' +
58+
'</ul></div>';
59+
compileWithTemplate(template).then((pv) => {
60+
createView(pv);
61+
cd.detectChanges();
62+
expect(DOM.getText(view.nodes[0])).toEqual('when default');
63+
64+
component.switchValue = 'a';
65+
cd.detectChanges();
66+
expect(DOM.getText(view.nodes[0])).toEqual('when a');
67+
68+
component.switchValue = 'b';
69+
cd.detectChanges();
70+
expect(DOM.getText(view.nodes[0])).toEqual('when default');
71+
72+
done();
73+
});
74+
});
75+
76+
it('should support multiple whens with the same value', (done) => {
77+
var template = '<div>' +
78+
'<ul [ng-switch]="switchValue">' +
79+
'<template [ng-switch-when]="\'a\'"><li>when a1;</li></template>' +
80+
'<template [ng-switch-when]="\'b\'"><li>when b1;</li></template>' +
81+
'<template [ng-switch-when]="\'a\'"><li>when a2;</li></template>' +
82+
'<template [ng-switch-when]="\'b\'"><li>when b2;</li></template>' +
83+
'<template [ng-switch-default]><li>when default1;</li></template>' +
84+
'<template [ng-switch-default]><li>when default2;</li></template>' +
85+
'</ul></div>';
86+
compileWithTemplate(template).then((pv) => {
87+
createView(pv);
88+
cd.detectChanges();
89+
expect(DOM.getText(view.nodes[0])).toEqual('when default1;when default2;');
90+
91+
component.switchValue = 'a';
92+
cd.detectChanges();
93+
expect(DOM.getText(view.nodes[0])).toEqual('when a1;when a2;');
94+
95+
component.switchValue = 'b';
96+
cd.detectChanges();
97+
expect(DOM.getText(view.nodes[0])).toEqual('when b1;when b2;');
98+
99+
done();
100+
});
101+
});
102+
});
103+
104+
describe('when values changes', () => {
105+
it('should switch amongst when values', (done) => {
106+
var template = '<div>' +
107+
'<ul [ng-switch]="switchValue">' +
108+
'<template [ng-switch-when]="when1"><li>when 1;</li></template>' +
109+
'<template [ng-switch-when]="when2"><li>when 2;</li></template>' +
110+
'<template [ng-switch-default]><li>when default;</li></template>' +
111+
'</ul></div>';
112+
compileWithTemplate(template).then((pv) => {
113+
createView(pv);
114+
115+
component.when1 = 'a';
116+
component.when2 = 'b';
117+
component.switchValue = 'a';
118+
cd.detectChanges();
119+
expect(DOM.getText(view.nodes[0])).toEqual('when 1;');
120+
121+
component.switchValue = 'b';
122+
cd.detectChanges();
123+
expect(DOM.getText(view.nodes[0])).toEqual('when 2;');
124+
125+
component.switchValue = 'c';
126+
cd.detectChanges();
127+
expect(DOM.getText(view.nodes[0])).toEqual('when default;');
128+
129+
component.when1 = 'c';
130+
cd.detectChanges();
131+
expect(DOM.getText(view.nodes[0])).toEqual('when 1;');
132+
133+
component.when1 = 'd';
134+
cd.detectChanges();
135+
expect(DOM.getText(view.nodes[0])).toEqual('when default;');
136+
137+
done();
138+
});
139+
});
140+
});
141+
});
142+
}
143+
144+
@Component({
145+
selector: 'test-cmp',
146+
template: new TemplateConfig({
147+
inline: '', // each test swaps with a custom template.
148+
directives: [NgSwitch, NgSwitchWhen, NgSwitchDefault]
149+
})
150+
})
151+
class TestComponent {
152+
switchValue: any;
153+
when1: any;
154+
when2: any;
155+
156+
constructor() {
157+
this.switchValue = null;
158+
this.when1 = null;
159+
this.when2 = null;
160+
}
161+
}

0 commit comments

Comments
 (0)