| 
									
										
										
										
											2017-01-20 13:10:57 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google Inc. All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-05 13:49:59 -07:00
										 |  |  | import {ElementData, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewState} from './types'; | 
					
						
							|  |  |  | import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, visitRootRenderNodes} from './util'; | 
					
						
							| 
									
										
										
										
											2017-01-20 13:10:57 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  | export function attachEmbeddedView( | 
					
						
							| 
									
										
										
										
											2017-03-29 09:34:45 -07:00
										 |  |  |     parentView: ViewData, elementData: ElementData, viewIndex: number | undefined | null, | 
					
						
							|  |  |  |     view: ViewData) { | 
					
						
							|  |  |  |   let embeddedViews = elementData.viewContainer !._embeddedViews; | 
					
						
							|  |  |  |   if (viewIndex === null || viewIndex === undefined) { | 
					
						
							| 
									
										
										
										
											2017-01-20 13:10:57 -08:00
										 |  |  |     viewIndex = embeddedViews.length; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  |   view.viewContainerParent = parentView; | 
					
						
							| 
									
										
										
										
											2017-03-29 09:34:45 -07:00
										 |  |  |   addToArray(embeddedViews, viewIndex !, view); | 
					
						
							| 
									
										
										
										
											2017-05-05 13:50:41 -07:00
										 |  |  |   attachProjectedView(elementData, view); | 
					
						
							| 
									
										
										
										
											2017-01-23 16:59:20 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  |   Services.dirtyParentQueries(view); | 
					
						
							| 
									
										
										
										
											2017-01-23 16:59:20 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-29 09:34:45 -07:00
										 |  |  |   const prevView = viewIndex ! > 0 ? embeddedViews[viewIndex ! - 1] : null; | 
					
						
							| 
									
										
										
										
											2017-02-01 11:32:27 -08:00
										 |  |  |   renderAttachEmbeddedView(elementData, prevView, view); | 
					
						
							| 
									
										
										
										
											2017-01-20 13:10:57 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-05 13:50:41 -07:00
										 |  |  | function attachProjectedView(vcElementData: ElementData, view: ViewData) { | 
					
						
							|  |  |  |   const dvcElementData = declaredViewContainer(view); | 
					
						
							|  |  |  |   if (!dvcElementData || dvcElementData === vcElementData || | 
					
						
							|  |  |  |       view.state & ViewState.IsProjectedView) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   // Note: For performance reasons, we
 | 
					
						
							|  |  |  |   // - add a view to template._projectedViews only 1x throughout its lifetime,
 | 
					
						
							|  |  |  |   //   and remove it not until the view is destroyed.
 | 
					
						
							|  |  |  |   //   (hard, as when a parent view is attached/detached we would need to attach/detach all
 | 
					
						
							|  |  |  |   //    nested projected views as well, even accross component boundaries).
 | 
					
						
							|  |  |  |   // - don't track the insertion order of views in the projected views array
 | 
					
						
							|  |  |  |   //   (hard, as when the views of the same template are inserted different view containers)
 | 
					
						
							|  |  |  |   view.state |= ViewState.IsProjectedView; | 
					
						
							|  |  |  |   let projectedViews = dvcElementData.template._projectedViews; | 
					
						
							|  |  |  |   if (!projectedViews) { | 
					
						
							|  |  |  |     projectedViews = dvcElementData.template._projectedViews = []; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   projectedViews.push(view); | 
					
						
							|  |  |  |   // Note: we are changing the NodeDef here as we cannot calculate
 | 
					
						
							|  |  |  |   // the fact whether a template is used for projection during compilation.
 | 
					
						
							|  |  |  |   markNodeAsProjectedTemplate(view.parent !.def, view.parentNodeDef !); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-05 13:49:59 -07:00
										 |  |  | function markNodeAsProjectedTemplate(viewDef: ViewDefinition, nodeDef: NodeDef) { | 
					
						
							|  |  |  |   if (nodeDef.flags & NodeFlags.ProjectedTemplate) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   viewDef.nodeFlags |= NodeFlags.ProjectedTemplate; | 
					
						
							|  |  |  |   nodeDef.flags |= NodeFlags.ProjectedTemplate; | 
					
						
							|  |  |  |   let parentNodeDef = nodeDef.parent; | 
					
						
							|  |  |  |   while (parentNodeDef) { | 
					
						
							|  |  |  |     parentNodeDef.childFlags |= NodeFlags.ProjectedTemplate; | 
					
						
							|  |  |  |     parentNodeDef = parentNodeDef.parent; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-29 09:34:45 -07:00
										 |  |  | export function detachEmbeddedView(elementData: ElementData, viewIndex?: number): ViewData|null { | 
					
						
							|  |  |  |   const embeddedViews = elementData.viewContainer !._embeddedViews; | 
					
						
							| 
									
										
										
										
											2017-02-20 14:34:15 -08:00
										 |  |  |   if (viewIndex == null || viewIndex >= embeddedViews.length) { | 
					
						
							|  |  |  |     viewIndex = embeddedViews.length - 1; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (viewIndex < 0) { | 
					
						
							|  |  |  |     return null; | 
					
						
							| 
									
										
										
										
											2017-01-20 13:10:57 -08:00
										 |  |  |   } | 
					
						
							|  |  |  |   const view = embeddedViews[viewIndex]; | 
					
						
							| 
									
										
										
										
											2017-03-29 09:34:45 -07:00
										 |  |  |   view.viewContainerParent = null; | 
					
						
							| 
									
										
										
										
											2017-01-23 16:59:20 -08:00
										 |  |  |   removeFromArray(embeddedViews, viewIndex); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-05 13:50:41 -07:00
										 |  |  |   // See attachProjectedView for why we don't update projectedViews here.
 | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  |   Services.dirtyParentQueries(view); | 
					
						
							| 
									
										
										
										
											2017-01-23 16:59:20 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  |   renderDetachView(view); | 
					
						
							| 
									
										
										
										
											2017-02-01 11:32:27 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return view; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-05 13:50:41 -07:00
										 |  |  | export function detachProjectedView(view: ViewData) { | 
					
						
							|  |  |  |   if (!(view.state & ViewState.IsProjectedView)) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const dvcElementData = declaredViewContainer(view); | 
					
						
							|  |  |  |   if (dvcElementData) { | 
					
						
							|  |  |  |     const projectedViews = dvcElementData.template._projectedViews; | 
					
						
							|  |  |  |     if (projectedViews) { | 
					
						
							|  |  |  |       removeFromArray(projectedViews, projectedViews.indexOf(view)); | 
					
						
							|  |  |  |       Services.dirtyParentQueries(view); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 11:32:27 -08:00
										 |  |  | export function moveEmbeddedView( | 
					
						
							|  |  |  |     elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData { | 
					
						
							| 
									
										
										
										
											2017-03-29 09:34:45 -07:00
										 |  |  |   const embeddedViews = elementData.viewContainer !._embeddedViews; | 
					
						
							| 
									
										
										
										
											2017-02-01 11:32:27 -08:00
										 |  |  |   const view = embeddedViews[oldViewIndex]; | 
					
						
							|  |  |  |   removeFromArray(embeddedViews, oldViewIndex); | 
					
						
							|  |  |  |   if (newViewIndex == null) { | 
					
						
							|  |  |  |     newViewIndex = embeddedViews.length; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   addToArray(embeddedViews, newViewIndex, view); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Note: Don't need to change projectedViews as the order in there
 | 
					
						
							|  |  |  |   // as always invalid...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  |   Services.dirtyParentQueries(view); | 
					
						
							| 
									
										
										
										
											2017-02-01 11:32:27 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  |   renderDetachView(view); | 
					
						
							| 
									
										
										
										
											2017-02-01 11:32:27 -08:00
										 |  |  |   const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null; | 
					
						
							|  |  |  |   renderAttachEmbeddedView(elementData, prevView, view); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return view; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-29 09:34:45 -07:00
										 |  |  | function renderAttachEmbeddedView( | 
					
						
							|  |  |  |     elementData: ElementData, prevView: ViewData | null, view: ViewData) { | 
					
						
							|  |  |  |   const prevRenderNode = prevView ? renderNode(prevView, prevView.def.lastRenderRootNode !) : | 
					
						
							|  |  |  |                                     elementData.renderElement; | 
					
						
							| 
									
										
										
										
											2017-02-16 13:55:55 -08:00
										 |  |  |   const parentNode = view.renderer.parentNode(prevRenderNode); | 
					
						
							|  |  |  |   const nextSibling = view.renderer.nextSibling(prevRenderNode); | 
					
						
							| 
									
										
										
										
											2017-02-03 15:20:50 -08:00
										 |  |  |   // Note: We can't check if `nextSibling` is present, as on WebWorkers it will always be!
 | 
					
						
							|  |  |  |   // However, browsers automatically do `appendChild` when there is no `nextSibling`.
 | 
					
						
							|  |  |  |   visitRootRenderNodes(view, RenderNodeAction.InsertBefore, parentNode, nextSibling, undefined); | 
					
						
							| 
									
										
										
										
											2017-02-01 11:32:27 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-22 10:05:56 -08:00
										 |  |  | export function renderDetachView(view: ViewData) { | 
					
						
							|  |  |  |   visitRootRenderNodes(view, RenderNodeAction.RemoveChild, null, null, undefined); | 
					
						
							| 
									
										
										
										
											2017-01-20 13:10:57 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-23 16:59:20 -08:00
										 |  |  | function addToArray(arr: any[], index: number, value: any) { | 
					
						
							|  |  |  |   // perf: array.push is faster than array.splice!
 | 
					
						
							|  |  |  |   if (index >= arr.length) { | 
					
						
							|  |  |  |     arr.push(value); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     arr.splice(index, 0, value); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function removeFromArray(arr: any[], index: number) { | 
					
						
							|  |  |  |   // perf: array.pop is faster than array.splice!
 | 
					
						
							|  |  |  |   if (index >= arr.length - 1) { | 
					
						
							|  |  |  |     arr.pop(); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     arr.splice(index, 1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |