1
+ import { PromiseWrapper } from 'angular2/src/facade/async' ;
2
+ import { isBlank , isPresent } from 'angular2/src/facade/lang' ;
3
+
4
+ import { Directive , Attribute , DynamicComponentLoader , ComponentRef , ElementRef ,
5
+ Injector , provide , Type , Component , OpaqueToken , Inject } from 'angular2/core' ;
6
+
7
+ import * as routerHooks from 'angular2/src/router/lifecycle_annotations' ;
8
+ import { hasLifecycleHook } from 'angular2/src/router/route_lifecycle_reflector' ;
9
+
10
+ import { ComponentInstruction , RouteParams , RouteData , RouterOutlet , LocationStrategy , Router ,
11
+ OnActivate , OnDeactivate } from 'angular2/router' ;
12
+
13
+ import { topmost } from "ui" ;
14
+ import { Page , NavigatedData } from "ui/page" ;
15
+ import { log } from "./common" ;
16
+
17
+ let COMPONENT = new OpaqueToken ( "COMPONENT" ) ;
18
+ let _resolveToTrue = PromiseWrapper . resolve ( true ) ;
19
+ let _resolveToFalse = PromiseWrapper . resolve ( false ) ;
20
+
21
+ /**
22
+ * Reference Cache
23
+ */
24
+ class RefCache {
25
+ private cache : Array < ComponentRef > = new Array < ComponentRef > ( ) ;
26
+
27
+ public push ( comp : ComponentRef ) {
28
+ this . cache . push ( comp ) ;
29
+ }
30
+
31
+ public pop ( ) : ComponentRef {
32
+ return this . cache . pop ( ) ;
33
+ }
34
+
35
+ public peek ( ) : ComponentRef {
36
+ return this . cache [ this . cache . length - 1 ] ;
37
+ }
38
+ }
39
+
40
+ var _isGoingBack = false ;
41
+ function startGoBack ( ) {
42
+ log ( "startGoBack()" ) ;
43
+ if ( _isGoingBack ) {
44
+ throw new Error ( "Calling startGoBack while going back." )
45
+ }
46
+ _isGoingBack = true ;
47
+ }
48
+
49
+ function endGoBack ( ) {
50
+ log ( "endGoBack()" ) ;
51
+ if ( ! _isGoingBack ) {
52
+ throw new Error ( "Calling endGoBack while not going back." )
53
+ }
54
+ _isGoingBack = false ;
55
+ }
56
+
57
+ function isGoingBack ( ) {
58
+ return _isGoingBack ;
59
+ }
60
+
61
+
62
+ /**
63
+ * A router outlet that does page navigation in NativeScript
64
+ *
65
+ * ## Use
66
+ *
67
+ * ```
68
+ * <page-router-outlet></page-router-outlet>
69
+ * ```
70
+ */
71
+ @Directive ( { selector : 'page-router-outlet' } )
72
+ export class PageRouterOutlet extends RouterOutlet {
73
+ private isInitalPage : boolean = true ;
74
+ private refCache : RefCache = new RefCache ( ) ;
75
+
76
+ private componentRef : ComponentRef = null ;
77
+ private currentComponentType : ComponentRef = null ;
78
+ private currentInstruction : ComponentInstruction = null ;
79
+
80
+ constructor ( private elementRef : ElementRef ,
81
+ private loader : DynamicComponentLoader ,
82
+ private parentRouter : Router ,
83
+ @Attribute ( 'name' ) nameAttr : string ) {
84
+ super ( elementRef , loader , parentRouter , nameAttr )
85
+ }
86
+
87
+ /**
88
+ * Called by the Router to instantiate a new component during the commit phase of a navigation.
89
+ * This method in turn is responsible for calling the `routerOnActivate` hook of its child.
90
+ */
91
+ activate ( nextInstruction : ComponentInstruction ) : Promise < any > {
92
+ this . log ( "activate" , nextInstruction ) ;
93
+
94
+ let previousInstruction = this . currentInstruction ;
95
+ let componentType = nextInstruction . componentType ;
96
+ this . currentInstruction = nextInstruction ;
97
+
98
+ if ( isGoingBack ( ) ) {
99
+ log ( "PageRouterOutlet.activate() - Back naviation, so load from cache: " + componentType . name ) ;
100
+
101
+ endGoBack ( ) ;
102
+
103
+ // Get Component form ref and just call the activate hook
104
+ this . componentRef = this . refCache . peek ( ) ;
105
+ this . currentComponentType = componentType ;
106
+ this . checkComponentRef ( this . componentRef , nextInstruction ) ;
107
+
108
+ if ( hasLifecycleHook ( routerHooks . routerOnActivate , componentType ) ) {
109
+ return ( < OnActivate > this . componentRef . instance )
110
+ . routerOnActivate ( nextInstruction , previousInstruction ) ;
111
+ }
112
+ }
113
+ else {
114
+ let childRouter = this . parentRouter . childRouter ( componentType ) ;
115
+ let providers = Injector . resolve ( [
116
+ provide ( RouteData , { useValue : nextInstruction . routeData } ) ,
117
+ provide ( RouteParams , { useValue : new RouteParams ( nextInstruction . params ) } ) ,
118
+ provide ( Router , { useValue : childRouter } ) ,
119
+ provide ( COMPONENT , { useValue : componentType } ) ,
120
+ ] ) ;
121
+
122
+ // TODO: Is there a better way to check first load?
123
+ if ( this . isInitalPage ) {
124
+ log ( "PageRouterOutlet.activate() inital page - just load component: " + componentType . name ) ;
125
+ this . isInitalPage = false ;
126
+ }
127
+ else {
128
+ log ( "PageRouterOutlet.activate() forward navigation - wrap component in page: " + componentType . name ) ;
129
+ componentType = PageShim ;
130
+ }
131
+
132
+ return this . loader . loadNextToLocation ( componentType , this . elementRef , providers )
133
+ . then ( ( componentRef ) => {
134
+ this . componentRef = componentRef ;
135
+ this . currentComponentType = componentType ;
136
+ this . refCache . push ( componentRef ) ;
137
+
138
+ if ( hasLifecycleHook ( routerHooks . routerOnActivate , componentType ) ) {
139
+ return ( < OnActivate > this . componentRef . instance )
140
+ . routerOnActivate ( nextInstruction , previousInstruction ) ;
141
+ }
142
+ } ) ;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Called by the {@link Router} when an outlet disposes of a component's contents.
148
+ * This method in turn is responsible for calling the `routerOnDeactivate` hook of its child.
149
+ */
150
+ deactivate ( nextInstruction : ComponentInstruction ) : Promise < any > {
151
+ this . log ( "deactivate" , nextInstruction ) ;
152
+ var instruction = this . currentInstruction ;
153
+ var compType = this . currentComponentType ;
154
+
155
+ var next = _resolveToTrue ;
156
+ if ( isPresent ( this . componentRef ) &&
157
+ isPresent ( instruction ) &&
158
+ isPresent ( compType ) &&
159
+ hasLifecycleHook ( routerHooks . routerOnDeactivate , compType ) ) {
160
+ next = PromiseWrapper . resolve (
161
+ ( < OnDeactivate > this . componentRef . instance ) . routerOnDeactivate ( nextInstruction , this . currentInstruction ) ) ;
162
+ }
163
+
164
+ if ( isGoingBack ( ) ) {
165
+ log ( "PageRouterOutlet.deactivate() while going back - should dispose: " + instruction . componentType . name )
166
+ return next . then ( ( _ ) => {
167
+ let popedRef = this . refCache . pop ( ) ;
168
+
169
+ if ( this . componentRef !== popedRef ) {
170
+ throw new Error ( "Current componentRef is different for cached componentRef" ) ;
171
+ }
172
+ this . checkComponentRef ( popedRef , instruction ) ;
173
+
174
+ if ( isPresent ( this . componentRef ) ) {
175
+ this . componentRef . dispose ( ) ;
176
+ this . componentRef = null ;
177
+ }
178
+ } ) ;
179
+ }
180
+ else {
181
+ return next ;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Called by the {@link Router} during recognition phase of a navigation.
187
+ * PageRouterOutlet will aways return true as cancelling navigation
188
+ * is currently not supported in NativeScript.
189
+ */
190
+ routerCanDeactivate ( nextInstruction : ComponentInstruction ) : Promise < boolean > {
191
+ return _resolveToTrue ;
192
+ }
193
+
194
+ /**
195
+ * Called by the {@link Router} during recognition phase of a navigation.
196
+ * For PageRouterOutlet it always reurns false, as there is no way to reuse
197
+ * the same componenet between two pages.
198
+ */
199
+ routerCanReuse ( nextInstruction : ComponentInstruction ) : Promise < boolean > {
200
+ return _resolveToFalse ;
201
+ }
202
+
203
+ /**
204
+ * Called by the {@link Router} during the commit phase of a navigation when an outlet
205
+ * reuses a component between different routes.
206
+ * For PageRouterOutlet this method should never be called,
207
+ * because routerCanReuse always returns false.
208
+ */
209
+ reuse ( nextInstruction : ComponentInstruction ) : Promise < any > {
210
+ throw new Error ( "reuse() method should never be called for PageRouterOutlet." )
211
+ return _resolveToFalse ;
212
+ }
213
+
214
+ private checkComponentRef ( popedRef : ComponentRef , instruction : ComponentInstruction ) {
215
+ if ( popedRef . instance instanceof PageShim ) {
216
+ var shim = < PageShim > popedRef . instance ;
217
+ if ( shim . componentType !== instruction . componentType ) {
218
+ throw new Error ( "ComponentRef value is different form expected!" ) ;
219
+ }
220
+ }
221
+ }
222
+
223
+ private log ( method : string , nextInstruction : ComponentInstruction ) {
224
+ log ( "PageRouterOutlet." + method + " isBack: " + isGoingBack ( ) + " nextUrl: " + nextInstruction . urlPath ) ;
225
+ }
226
+ }
227
+
228
+ @Component ( {
229
+ selector : 'nativescript-page-shim' ,
230
+ template : `
231
+ <StackLayout visibility="collapse" style="background-color: hotpink">
232
+ <Placeholder #content></Placeholder>
233
+ <StackLayout>
234
+ `
235
+ } )
236
+ class PageShim implements OnActivate , OnDeactivate {
237
+ private static pageShimCount : number = 0 ;
238
+ private id : number ;
239
+ private isInitialized : boolean ;
240
+ private componentRef : ComponentRef ;
241
+
242
+ constructor (
243
+ private element : ElementRef ,
244
+ private loader : DynamicComponentLoader ,
245
+ private locationStrategy : LocationStrategy ,
246
+ @Inject ( COMPONENT ) public componentType : Type
247
+ ) {
248
+ this . id = PageShim . pageShimCount ++ ;
249
+ this . log ( "constructor" ) ;
250
+ }
251
+
252
+ routerOnActivate ( nextInstruction : ComponentInstruction , prevInstruction : ComponentInstruction ) : any {
253
+ this . log ( "routerOnActivate" ) ;
254
+ let result = PromiseWrapper . resolve ( true ) ;
255
+
256
+ // On first activation:
257
+ // 1. Load componenet using loadIntoLocation.
258
+ // 2. Hijack its native element.
259
+ // 3. Put that element into a new page and navigate to it.
260
+ if ( ! this . isInitialized ) {
261
+ result = new Promise ( ( resolve , reject ) => {
262
+ this . isInitialized = true ;
263
+ this . loader . loadIntoLocation ( this . componentType , this . element , 'content' )
264
+ . then ( ( componentRef ) => {
265
+ this . componentRef = componentRef ;
266
+
267
+ //Component loaded. Find its root native view.
268
+ const viewContainer = this . componentRef . location . nativeElement ;
269
+ //Remove from original native parent.
270
+ //TODO: assuming it's a Layout.
271
+ ( < any > viewContainer . parent ) . removeChild ( viewContainer ) ;
272
+
273
+ topmost ( ) . navigate ( {
274
+ animated : true ,
275
+ create : ( ) => {
276
+ const page = new Page ( ) ;
277
+ page . on ( 'loaded' , ( ) => {
278
+ // Finish activation when page is fully loaded.
279
+ resolve ( )
280
+ } ) ;
281
+
282
+ page . on ( 'navigatingFrom' , global . zone . bind ( ( args : NavigatedData ) => {
283
+ if ( args . isBackNavigation ) {
284
+ startGoBack ( ) ;
285
+ this . locationStrategy . back ( ) ;
286
+ }
287
+ } ) ) ;
288
+
289
+ // Add to new page.
290
+ page . content = viewContainer ;
291
+ return page ;
292
+ }
293
+ } ) ;
294
+ } ) ;
295
+ } ) ;
296
+ }
297
+
298
+ if ( hasLifecycleHook ( routerHooks . routerOnActivate , this . componentType ) ) {
299
+ result = result . then ( ( ) => {
300
+ return ( < OnActivate > this . componentRef . instance ) . routerOnActivate ( nextInstruction , prevInstruction ) ;
301
+ } ) ;
302
+ }
303
+ return result ;
304
+ }
305
+
306
+ routerOnDeactivate ( nextInstruction : ComponentInstruction , prevInstruction : ComponentInstruction ) : any {
307
+ this . log ( "routerOnDeactivate" ) ;
308
+ if ( hasLifecycleHook ( routerHooks . routerOnDeactivate , this . componentType ) ) {
309
+ return ( < OnDeactivate > this . componentRef . instance ) . routerOnDeactivate ( nextInstruction , prevInstruction ) ;
310
+ }
311
+ }
312
+
313
+ private log ( methodName : string ) {
314
+ log ( "PageShim(" + this . id + ")." + methodName )
315
+ }
316
+ }
0 commit comments