Skip to content

Commit 2da2710

Browse files
committed
Merge pull request #171 from dreame4/issue-142
Transcluding the of ui-view directive, ui-view spec, example of the initial view in the sample app
2 parents 85d04f6 + 33913a5 commit 2da2710

File tree

5 files changed

+259
-61
lines changed

5 files changed

+259
-61
lines changed

sample/empty.content.html

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<h3>Current initial view title:</h3>
2+
<p><input type="text" ng-model="data.initialViewTitle"></p>
3+
<p><button class="btn" ng-click="showInitialView($event)">Back</button></p>

sample/empty.html

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div>This view contains a nested view below:</div>
2+
<div ui-view="emptycontent">
3+
<h3>{{data.initialViewTitle}}</h3>
4+
<p><button class="btn" ng-click="changeInitialViewTitle($event)">Change</button></p>
5+
</div>

sample/index.html

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!doctype html>
22
<html lang="en" ng-app="sample"><head>
33
<meta charset="utf-8">
4-
<link rel="stylesheet" type="text/css" href="bootstrap.min.css">
4+
<link rel="stylesheet" type="text/css" href="bootstrap.min.css">
55
<style type="text/css">
66
.fade-enter-setup, .fade-leave-setup {
77
transition: opacity 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0s;
@@ -17,7 +17,7 @@
1717
</style>
1818
<script src="../lib/angular-1.1.4.js"></script>
1919
<script src="../build/angular-ui-router.js"></script>
20-
20+
2121
<!-- could easily use a custom property of the state here instead of 'name' -->
2222
<title ng-bind="$state.current.name + ' - ui-router'">ui-router</title>
2323
</head><body>
@@ -195,6 +195,32 @@
195195
function ($timeout) {
196196
return $timeout(function () { return "Hello world" }, 100);
197197
}],
198+
})
199+
.state('empty', {
200+
url: '/empty',
201+
templateUrl: 'empty.html',
202+
controller:
203+
[ '$scope', '$state',
204+
function ($scope, $state) {
205+
// Using an object to access it via ng-model from child scope
206+
$scope.data = {
207+
initialViewTitle: "I am an initial view"
208+
}
209+
$scope.changeInitialViewTitle = function($event) {
210+
$state.transitionTo('empty.emptycontent');
211+
};
212+
$scope.showInitialView = function($event) {
213+
$state.transitionTo('empty');
214+
};
215+
}]
216+
})
217+
.state('empty.emptycontent', {
218+
url: '/content',
219+
views: {
220+
'emptycontent': {
221+
templateUrl: 'empty.content.html'
222+
}
223+
}
198224
});
199225
}])
200226
.run(

src/viewDirective.js

+65-59
Original file line numberDiff line numberDiff line change
@@ -7,80 +7,86 @@ function $ViewDirective( $state, $compile, $controller, $injector, $an
77
var directive = {
88
restrict: 'ECA',
99
terminal: true,
10-
link: function(scope, element, attr) {
11-
var viewScope, viewLocals,
12-
initialContent = element.contents(),
13-
name = attr[directive.name] || attr.name || '',
14-
onloadExp = attr.onload || '',
15-
animate = isDefined($animator) && $animator(scope, attr);
10+
transclude: true,
11+
compile: function (element, attr, transclude) {
12+
return function(scope, element, attr) {
13+
var viewScope, viewLocals,
14+
name = attr[directive.name] || attr.name || '',
15+
onloadExp = attr.onload || '',
16+
animate = isDefined($animator) && $animator(scope, attr);
1617

17-
// Find the details of the parent view directive (if any) and use it
18-
// to derive our own qualified view name, then hang our own details
19-
// off the DOM so child directives can find it.
20-
var parent = element.parent().inheritedData('$uiView');
21-
if (name.indexOf('@') < 0) name = name + '@' + (parent ? parent.state.name : '');
22-
var view = { name: name, state: null };
23-
element.data('$uiView', view);
18+
// Put back the compiled initial view
19+
element.append(transclude(scope));
2420

25-
scope.$on('$stateChangeSuccess', function() { updateView(true); });
26-
updateView(false);
21+
// Find the details of the parent view directive (if any) and use it
22+
// to derive our own qualified view name, then hang our own details
23+
// off the DOM so child directives can find it.
24+
var parent = element.parent().inheritedData('$uiView');
25+
if (name.indexOf('@') < 0) name = name + '@' + (parent ? parent.state.name : '');
26+
var view = { name: name, state: null };
27+
element.data('$uiView', view);
2728

28-
function updateView(doAnimate) {
29-
var locals = $state.$current && $state.$current.locals[name];
30-
if (locals === viewLocals) return; // nothing to do
29+
scope.$on('$stateChangeSuccess', function() { updateView(true); });
30+
updateView(false);
3131

32-
// Remove existing content
33-
if (animate && doAnimate) {
34-
animate.leave(element.contents(), element);
35-
} else {
36-
element.html('');
37-
}
38-
39-
// Destroy previous view scope
40-
if (viewScope) {
41-
viewScope.$destroy();
42-
viewScope = null;
43-
}
44-
45-
if (locals) {
46-
viewLocals = locals;
47-
view.state = locals.$$state;
32+
function updateView(doAnimate) {
33+
var locals = $state.$current && $state.$current.locals[name];
34+
if (locals === viewLocals) return; // nothing to do
4835

49-
var contents;
36+
// Remove existing content
5037
if (animate && doAnimate) {
51-
contents = angular.element('<div></div>').html(locals.$template).contents();
52-
animate.enter(contents, element);
38+
animate.leave(element.contents(), element);
5339
} else {
54-
element.html(locals.$template);
55-
contents = element.contents();
40+
element.html('');
5641
}
5742

58-
var link = $compile(contents);
59-
viewScope = scope.$new();
60-
if (locals.$$controller) {
61-
locals.$scope = viewScope;
62-
var controller = $controller(locals.$$controller, locals);
63-
element.children().data('$ngControllerController', controller);
43+
// Destroy previous view scope
44+
if (viewScope) {
45+
viewScope.$destroy();
46+
viewScope = null;
6447
}
65-
link(viewScope);
66-
viewScope.$emit('$viewContentLoaded');
67-
viewScope.$eval(onloadExp);
6848

69-
// TODO: This seems strange, shouldn't $anchorScroll listen for $viewContentLoaded if necessary?
70-
// $anchorScroll might listen on event...
71-
$anchorScroll();
72-
} else {
73-
viewLocals = null;
74-
view.state = null;
49+
if (locals) {
50+
viewLocals = locals;
51+
view.state = locals.$$state;
7552

76-
// Restore initial view
77-
if (doAnimate) {
78-
animate.enter(initialContent, element);
53+
var contents;
54+
if (animate && doAnimate) {
55+
contents = angular.element('<div></div>').html(locals.$template).contents();
56+
animate.enter(contents, element);
57+
} else {
58+
element.html(locals.$template);
59+
contents = element.contents();
60+
}
61+
62+
var link = $compile(contents);
63+
viewScope = scope.$new();
64+
if (locals.$$controller) {
65+
locals.$scope = viewScope;
66+
var controller = $controller(locals.$$controller, locals);
67+
element.children().data('$ngControllerController', controller);
68+
}
69+
link(viewScope);
70+
viewScope.$emit('$viewContentLoaded');
71+
viewScope.$eval(onloadExp);
72+
73+
// TODO: This seems strange, shouldn't $anchorScroll listen for $viewContentLoaded if necessary?
74+
// $anchorScroll might listen on event...
75+
$anchorScroll();
7976
} else {
80-
element.html(initialContent);
77+
viewLocals = null;
78+
view.state = null;
79+
80+
// Restore the initial view
81+
var compiledElem = transclude(scope);
82+
if (animate && doAnimate) {
83+
animate.enter(compiledElem, element);
84+
} else {
85+
element.append(compiledElem);
86+
}
8187
}
8288
}
83-
}
89+
};
8490
}
8591
};
8692
return directive;

test/viewDirectiveSpec.js

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*jshint browser: true, indent: 2 */
2+
/*global describe: false, it: false, beforeEach: false, expect: false, resolvedValue: false, module: false, inject: false, angular: false */
3+
4+
describe('uiView', function () {
5+
'use strict';
6+
7+
var scope, $compile, elem;
8+
9+
beforeEach(module('ui.state'));
10+
11+
var aState = {
12+
template: 'aState template'
13+
},
14+
bState = {
15+
template: 'bState template'
16+
},
17+
cState = {
18+
views: {
19+
'cview': {
20+
template: 'cState cview template'
21+
}
22+
}
23+
},
24+
dState = {
25+
views: {
26+
'dview1': {
27+
template: 'dState dview1 template'
28+
},
29+
'dview2': {
30+
template: 'dState dview2 template'
31+
}
32+
}
33+
},
34+
eState = {
35+
template: '<div ui-view="eview" class="eview"></div>'
36+
},
37+
fState = {
38+
views: {
39+
'eview': {
40+
template: 'fState eview template'
41+
}
42+
}
43+
},
44+
gState = {
45+
template: '<div ui-view="inner"><span ng-class="{ test: true }">{{content}}</span></div>'
46+
},
47+
hState = {
48+
views: {
49+
'inner': {
50+
template: 'hState inner template'
51+
}
52+
}
53+
};
54+
55+
beforeEach(module(function ($stateProvider) {
56+
$stateProvider
57+
.state('a', aState)
58+
.state('b', bState)
59+
.state('c', cState)
60+
.state('d', dState)
61+
.state('e', eState)
62+
.state('e.f', fState)
63+
.state('g', gState)
64+
.state('g.h', hState);
65+
}));
66+
67+
beforeEach(inject(function ($rootScope, _$compile_) {
68+
scope = $rootScope.$new();
69+
$compile = _$compile_;
70+
elem = angular.element('<div>');
71+
}));
72+
73+
describe('linking ui-directive', function () {
74+
it('anonymous ui-view should be replaced with the template of the current $state', inject(function ($state, $q) {
75+
elem.append($compile('<div ui-view></div>')(scope));
76+
77+
$state.transitionTo(aState);
78+
$q.flush();
79+
80+
expect(elem.text()).toBe(aState.template);
81+
}));
82+
83+
it('named ui-view should be replaced with the template of the current $state', inject(function ($state, $q) {
84+
elem.append($compile('<div ui-view="cview"></div>')(scope));
85+
86+
$state.transitionTo(cState);
87+
$q.flush();
88+
89+
expect(elem.text()).toBe(cState.views.cview.template);
90+
}));
91+
92+
it('ui-view should be updated after transition to another state', inject(function ($state, $q) {
93+
elem.append($compile('<div ui-view></div>')(scope));
94+
95+
$state.transitionTo(aState);
96+
$q.flush();
97+
98+
expect(elem.text()).toBe(aState.template);
99+
100+
$state.transitionTo(bState);
101+
$q.flush();
102+
103+
expect(elem.text()).toBe(bState.template);
104+
}));
105+
106+
it('should handle NOT nested ui-views', inject(function ($state, $q) {
107+
elem.append($compile('<div ui-view="dview1" class="dview1"></div><div ui-view="dview2" class="dview2"></div>')(scope));
108+
109+
$state.transitionTo(dState);
110+
$q.flush();
111+
112+
expect(elem[0].querySelector('.dview1').innerText).toBe(dState.views.dview1.template);
113+
expect(elem[0].querySelector('.dview2').innerText).toBe(dState.views.dview2.template);
114+
}));
115+
116+
it('should handle nested ui-views (testing two levels deep)', inject(function ($state, $q) {
117+
elem.append($compile('<div ui-view class="view"></div>')(scope));
118+
119+
$state.transitionTo(fState);
120+
$q.flush();
121+
122+
expect(elem[0].querySelector('.view').querySelector('.eview').innerText).toBe(fState.views.eview.template);
123+
}));
124+
});
125+
126+
describe('handling initial view', function () {
127+
it('initial view should be compiled if the view is empty', inject(function ($state, $q) {
128+
var content = 'inner content';
129+
130+
elem.append($compile('<div ui-view></div>')(scope));
131+
scope.$apply('content = "' + content + '"');
132+
133+
$state.transitionTo(gState);
134+
$q.flush();
135+
136+
expect(elem[0].querySelector('.test').innerText).toBe(content);
137+
}));
138+
139+
it('initial view should be put back after removal of the view', inject(function ($state, $q) {
140+
var content = 'inner content';
141+
142+
elem.append($compile('<div ui-view></div>')(scope));
143+
scope.$apply('content = "' + content + '"');
144+
145+
$state.transitionTo(hState);
146+
$q.flush();
147+
148+
expect(elem.text()).toBe(hState.views.inner.template);
149+
150+
// going to the parent state which makes the inner view empty
151+
$state.transitionTo(gState);
152+
$q.flush();
153+
154+
expect(elem[0].querySelector('.test').innerText).toBe(content);
155+
}));
156+
});
157+
158+
});

0 commit comments

Comments
 (0)