UNPKG

213 kBJavaScriptView Raw
1import * as LogManager from 'aurelia-logging';
2import {metadata,Origin,protocol} from 'aurelia-metadata';
3import {DOM,PLATFORM,FEATURE} from 'aurelia-pal';
4import {TemplateRegistryEntry,Loader} from 'aurelia-loader';
5import {relativeToFile} from 'aurelia-path';
6import {Container,resolver,inject} from 'aurelia-dependency-injection';
7import {ValueConverterResource,BindingBehaviorResource,camelCase,Binding,createOverrideContext,subscriberCollection,bindingMode,ObserverLocator,EventManager} from 'aurelia-binding';
8import {TaskQueue} from 'aurelia-task-queue';
9
10/**
11* List the events that an Animator should raise.
12*/
13export const animationEvent = {
14 enterBegin: 'animation:enter:begin',
15 enterActive: 'animation:enter:active',
16 enterDone: 'animation:enter:done',
17 enterTimeout: 'animation:enter:timeout',
18
19 leaveBegin: 'animation:leave:begin',
20 leaveActive: 'animation:leave:active',
21 leaveDone: 'animation:leave:done',
22 leaveTimeout: 'animation:leave:timeout',
23
24 staggerNext: 'animation:stagger:next',
25
26 removeClassBegin: 'animation:remove-class:begin',
27 removeClassActive: 'animation:remove-class:active',
28 removeClassDone: 'animation:remove-class:done',
29 removeClassTimeout: 'animation:remove-class:timeout',
30
31 addClassBegin: 'animation:add-class:begin',
32 addClassActive: 'animation:add-class:active',
33 addClassDone: 'animation:add-class:done',
34 addClassTimeout: 'animation:add-class:timeout',
35
36 animateBegin: 'animation:animate:begin',
37 animateActive: 'animation:animate:active',
38 animateDone: 'animation:animate:done',
39 animateTimeout: 'animation:animate:timeout',
40
41 sequenceBegin: 'animation:sequence:begin',
42 sequenceDone: 'animation:sequence:done'
43};
44
45/**
46 * An abstract class representing a mechanism for animating the DOM during various DOM state transitions.
47 */
48export class Animator {
49 /**
50 * Execute an 'enter' animation on an element
51 * @param element Element to animate
52 * @returns Resolved when the animation is done
53 */
54 enter(element: HTMLElement): Promise<boolean> {
55 return Promise.resolve(false);
56 }
57
58 /**
59 * Execute a 'leave' animation on an element
60 * @param element Element to animate
61 * @returns Resolved when the animation is done
62 */
63 leave(element: HTMLElement): Promise<boolean> {
64 return Promise.resolve(false);
65 }
66
67 /**
68 * Add a class to an element to trigger an animation.
69 * @param element Element to animate
70 * @param className Properties to animate or name of the effect to use
71 * @returns Resolved when the animation is done
72 */
73 removeClass(element: HTMLElement, className: string): Promise<boolean> {
74 element.classList.remove(className);
75 return Promise.resolve(false);
76 }
77
78 /**
79 * Add a class to an element to trigger an animation.
80 * @param element Element to animate
81 * @param className Properties to animate or name of the effect to use
82 * @returns Resolved when the animation is done
83 */
84 addClass(element: HTMLElement, className: string): Promise<boolean> {
85 element.classList.add(className);
86 return Promise.resolve(false);
87 }
88
89 /**
90 * Execute a single animation.
91 * @param element Element to animate
92 * @param className Properties to animate or name of the effect to use. For css animators this represents the className to be added and removed right after the animation is done.
93 * @param options options for the animation (duration, easing, ...)
94 * @returns Resolved when the animation is done
95 */
96 animate(element: HTMLElement | Array<HTMLElement>, className: string): Promise<boolean> {
97 return Promise.resolve(false);
98 }
99
100 /**
101 * Run a sequence of animations one after the other.
102 * for example: animator.runSequence("fadeIn","callout")
103 * @param sequence An array of effectNames or classNames
104 * @returns Resolved when all animations are done
105 */
106 runSequence(animations:Array<any>): Promise<boolean> {}
107
108 /**
109 * Register an effect (for JS based animators)
110 * @param effectName identifier of the effect
111 * @param properties Object with properties for the effect
112 */
113 registerEffect(effectName: string, properties: Object): void {}
114
115 /**
116 * Unregister an effect (for JS based animators)
117 * @param effectName identifier of the effect
118 */
119 unregisterEffect(effectName: string): void {}
120}
121
122/**
123* A mechanism by which an enlisted async render operation can notify the owning transaction when its work is done.
124*/
125export class CompositionTransactionNotifier {
126 constructor(owner) {
127 this.owner = owner;
128 this.owner._compositionCount++;
129 }
130
131 /**
132 * Notifies the owning transaction that its work is done.
133 */
134 done(): void {
135 this.owner._compositionCount--;
136 this.owner._tryCompleteTransaction();
137 }
138}
139
140/**
141* Referenced by the subsytem which wishes to control a composition transaction.
142*/
143export class CompositionTransactionOwnershipToken {
144 constructor(owner) {
145 this.owner = owner;
146 this.owner._ownershipToken = this;
147 this.thenable = this._createThenable();
148 }
149
150 /**
151 * Allows the transaction owner to wait for the completion of all child compositions.
152 * @return A promise that resolves when all child compositions are done.
153 */
154 waitForCompositionComplete(): Promise<void> {
155 this.owner._tryCompleteTransaction();
156 return this.thenable;
157 }
158
159 /**
160 * Used internall to resolve the composition complete promise.
161 */
162 resolve(): void {
163 this._resolveCallback();
164 }
165
166 _createThenable() {
167 return new Promise((resolve, reject) => {
168 this._resolveCallback = resolve;
169 });
170 }
171}
172
173/**
174* Enables an initiator of a view composition to track any internal async rendering processes for completion.
175*/
176export class CompositionTransaction {
177 /**
178 * Creates an instance of CompositionTransaction.
179 */
180 constructor() {
181 this._ownershipToken = null;
182 this._compositionCount = 0;
183 }
184
185 /**
186 * Attempt to take ownership of the composition transaction.
187 * @return An ownership token if successful, otherwise null.
188 */
189 tryCapture(): CompositionTransactionOwnershipToken {
190 return this._ownershipToken === null
191 ? new CompositionTransactionOwnershipToken(this)
192 : null;
193 }
194
195 /**
196 * Enlist an async render operation into the transaction.
197 * @return A completion notifier.
198 */
199 enlist(): CompositionTransactionNotifier {
200 return new CompositionTransactionNotifier(this);
201 }
202
203 _tryCompleteTransaction() {
204 if (this._compositionCount <= 0) {
205 this._compositionCount = 0;
206
207 if (this._ownershipToken !== null) {
208 let token = this._ownershipToken;
209 this._ownershipToken = null;
210 token.resolve();
211 }
212 }
213 }
214}
215
216const capitalMatcher = /([A-Z])/g;
217
218function addHyphenAndLower(char) {
219 return '-' + char.toLowerCase();
220}
221
222export function _hyphenate(name) {
223 return (name.charAt(0).toLowerCase() + name.slice(1)).replace(capitalMatcher, addHyphenAndLower);
224}
225
226//https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace_in_the_DOM
227//We need to ignore whitespace so we don't mess up fallback rendering
228//However, we cannot ignore empty text nodes that container interpolations.
229export function _isAllWhitespace(node) {
230 // Use ECMA-262 Edition 3 String and RegExp features
231 return !(node.auInterpolationTarget || (/[^\t\n\r ]/.test(node.textContent)));
232}
233
234export class ViewEngineHooksResource {
235 constructor() {}
236
237 initialize(container, target) {
238 this.instance = container.get(target);
239 }
240
241 register(registry, name) {
242 registry.registerViewEngineHooks(this.instance);
243 }
244
245 load(container, target) {}
246
247 static convention(name) { // eslint-disable-line
248 if (name.endsWith('ViewEngineHooks')) {
249 return new ViewEngineHooksResource();
250 }
251 }
252}
253
254export function viewEngineHooks(target) { // eslint-disable-line
255 let deco = function(t) {
256 metadata.define(metadata.resource, new ViewEngineHooksResource(), t);
257 };
258
259 return target ? deco(target) : deco;
260}
261
262interface EventHandler {
263 eventName: string;
264 bubbles: boolean;
265 capture: boolean;
266 dispose: Function;
267 handler: Function;
268}
269
270/**
271 * Dispatches subscribets to and publishes events in the DOM.
272 * @param element
273 */
274export class ElementEvents {
275 constructor(element: EventTarget) {
276 this.element = element;
277 this.subscriptions = {};
278 }
279
280 _enqueueHandler(handler: EventHandler): void {
281 this.subscriptions[handler.eventName] = this.subscriptions[handler.eventName] || [];
282 this.subscriptions[handler.eventName].push(handler);
283 }
284
285 _dequeueHandler(handler: EventHandler): EventHandler {
286 let index;
287 let subscriptions = this.subscriptions[handler.eventName];
288 if (subscriptions) {
289 index = subscriptions.indexOf(handler);
290 if (index > -1) {
291 subscriptions.splice(index, 1);
292 }
293 }
294 return handler;
295 }
296
297 /**
298 * Dispatches an Event on the context element.
299 * @param eventName
300 * @param detail
301 * @param bubbles
302 * @param cancelable
303 */
304 publish(eventName: string, detail?: Object = {}, bubbles?: boolean = true, cancelable?: boolean = true) {
305 let event = DOM.createCustomEvent(eventName, {cancelable, bubbles, detail});
306 this.element.dispatchEvent(event);
307 }
308
309 /**
310 * Adds and Event Listener on the context element.
311 * @return Returns the eventHandler containing a dispose method
312 */
313 subscribe(eventName: string, handler: Function, captureOrOptions?: boolean = true): EventHandler {
314 if (typeof handler === 'function') {
315 const eventHandler = new EventHandlerImpl(this, eventName, handler, captureOrOptions, false);
316 return eventHandler;
317 }
318
319 return undefined;
320 }
321
322 /**
323 * Adds an Event Listener on the context element, that will be disposed on the first trigger.
324 * @return Returns the eventHandler containing a dispose method
325 */
326 subscribeOnce(eventName: string, handler: Function, captureOrOptions?: boolean = true): EventHandler {
327 if (typeof handler === 'function') {
328 const eventHandler = new EventHandlerImpl(this, eventName, handler, captureOrOptions, true);
329 return eventHandler;
330 }
331
332 return undefined;
333 }
334
335 /**
336 * Removes all events that are listening to the specified eventName.
337 * @param eventName
338 */
339 dispose(eventName: string): void {
340 if (eventName && typeof eventName === 'string') {
341 let subscriptions = this.subscriptions[eventName];
342 if (subscriptions) {
343 while (subscriptions.length) {
344 let subscription = subscriptions.pop();
345 if (subscription) {
346 subscription.dispose();
347 }
348 }
349 }
350 } else {
351 this.disposeAll();
352 }
353 }
354
355 /**
356 * Removes all event handlers.
357 */
358 disposeAll() {
359 for (let key in this.subscriptions) {
360 this.dispose(key);
361 }
362 }
363}
364
365class EventHandlerImpl {
366 constructor(owner: ElementEvents, eventName: string, handler: Function, captureOrOptions: boolean, once: boolean) {
367 this.owner = owner;
368 this.eventName = eventName;
369 this.handler = handler;
370 // For compat with interface
371 this.capture = typeof captureOrOptions === 'boolean' ? captureOrOptions : captureOrOptions.capture;
372 this.bubbles = !this.capture;
373 this.captureOrOptions = captureOrOptions;
374 this.once = once;
375 owner.element.addEventListener(eventName, this, captureOrOptions);
376 owner._enqueueHandler(this);
377 }
378
379 handleEvent(e) {
380 // To keep `undefined` as context, same as the old way
381 const fn = this.handler;
382 fn(e);
383 if (this.once) {
384 this.dispose();
385 }
386 }
387
388 dispose() {
389 this.owner.element.removeEventListener(this.eventName, this, this.captureOrOptions);
390 this.owner._dequeueHandler(this);
391 this.owner = this.handler = null;
392 }
393}
394
395/**
396* A context that flows through the view resource load process.
397*/
398export class ResourceLoadContext {
399 dependencies: Object;
400
401 /**
402 * Creates an instance of ResourceLoadContext.
403 */
404 constructor() {
405 this.dependencies = {};
406 }
407
408 /**
409 * Tracks a dependency that is being loaded.
410 * @param url The url of the dependency.
411 */
412 addDependency(url: string): void {
413 this.dependencies[url] = true;
414 }
415
416 /**
417 * Checks if the current context includes a load of the specified url.
418 * @return True if the url is being loaded in the context; false otherwise.
419 */
420 hasDependency(url: string): boolean {
421 return url in this.dependencies;
422 }
423}
424
425/**
426* Specifies how a view should be compiled.
427*/
428export class ViewCompileInstruction {
429 targetShadowDOM: boolean;
430 compileSurrogate: boolean;
431 associatedModuleId: any;
432
433 /**
434 * The normal configuration for view compilation.
435 */
436 static normal: ViewCompileInstruction;
437
438 /**
439 * Creates an instance of ViewCompileInstruction.
440 * @param targetShadowDOM Should the compilation target the Shadow DOM.
441 * @param compileSurrogate Should the compilation also include surrogate bindings and behaviors.
442 */
443 constructor(targetShadowDOM?: boolean = false, compileSurrogate?: boolean = false) {
444 this.targetShadowDOM = targetShadowDOM;
445 this.compileSurrogate = compileSurrogate;
446 this.associatedModuleId = null;
447 }
448}
449
450ViewCompileInstruction.normal = new ViewCompileInstruction();
451
452/**
453* Specifies how a view should be created.
454*/
455interface ViewCreateInstruction {
456 /**
457 * Indicates that the view is being created by enhancing existing DOM.
458 */
459 enhance?: boolean;
460 /**
461 * Specifies a key/value lookup of part replacements for the view being created.
462 */
463 partReplacements?: Object;
464}
465
466/**
467* Indicates how a custom attribute or element should be instantiated in a view.
468*/
469export class BehaviorInstruction {
470
471 initiatedByBehavior: boolean;
472 enhance: boolean;
473 partReplacements: any;
474 viewFactory: ViewFactory;
475 originalAttrName: string;
476 skipContentProcessing: boolean;
477 contentFactory: any;
478 viewModel: Object;
479 anchorIsContainer: boolean;
480 host: Element;
481 attributes: Object;
482 type: HtmlBehaviorResource;
483 attrName: string;
484 inheritBindingContext: boolean;
485
486 /**
487 * A default behavior used in scenarios where explicit configuration isn't available.
488 */
489 static normal: BehaviorInstruction;
490
491 /**
492 * Creates an instruction for element enhancement.
493 * @return The created instruction.
494 */
495 static enhance(): BehaviorInstruction {
496 let instruction = new BehaviorInstruction();
497 instruction.enhance = true;
498 return instruction;
499 }
500
501 /**
502 * Creates an instruction for unit testing.
503 * @param type The HtmlBehaviorResource to create.
504 * @param attributes A key/value lookup of attributes for the behaior.
505 * @return The created instruction.
506 */
507 static unitTest(type: HtmlBehaviorResource, attributes: Object): BehaviorInstruction {
508 let instruction = new BehaviorInstruction();
509 instruction.type = type;
510 instruction.attributes = attributes || {};
511 return instruction;
512 }
513
514 /**
515 * Creates a custom element instruction.
516 * @param node The node that represents the custom element.
517 * @param type The HtmlBehaviorResource to create.
518 * @return The created instruction.
519 */
520 static element(node: Node, type: HtmlBehaviorResource): BehaviorInstruction {
521 let instruction = new BehaviorInstruction();
522 instruction.type = type;
523 instruction.attributes = {};
524 instruction.anchorIsContainer = !(node.hasAttribute('containerless') || type.containerless);
525 instruction.initiatedByBehavior = true;
526 return instruction;
527 }
528
529 /**
530 * Creates a custom attribute instruction.
531 * @param attrName The name of the attribute.
532 * @param type The HtmlBehaviorResource to create.
533 * @return The created instruction.
534 */
535 static attribute(attrName: string, type?: HtmlBehaviorResource): BehaviorInstruction {
536 let instruction = new BehaviorInstruction();
537 instruction.attrName = attrName;
538 instruction.type = type || null;
539 instruction.attributes = {};
540 return instruction;
541 }
542
543 /**
544 * Creates a dynamic component instruction.
545 * @param host The element that will parent the dynamic component.
546 * @param viewModel The dynamic component's view model instance.
547 * @param viewFactory A view factory used in generating the component's view.
548 * @return The created instruction.
549 */
550 static dynamic(host: Element, viewModel: Object, viewFactory: ViewFactory): BehaviorInstruction {
551 let instruction = new BehaviorInstruction();
552 instruction.host = host;
553 instruction.viewModel = viewModel;
554 instruction.viewFactory = viewFactory;
555 instruction.inheritBindingContext = true;
556 return instruction;
557 }
558
559 /**
560 * Creates an instance of BehaviorInstruction.
561 */
562 constructor() {
563 this.initiatedByBehavior = false;
564 this.enhance = false;
565 this.partReplacements = null;
566 this.viewFactory = null;
567 this.originalAttrName = null;
568 this.skipContentProcessing = false;
569 this.contentFactory = null;
570 this.viewModel = null;
571 this.anchorIsContainer = false;
572 this.host = null;
573 this.attributes = null;
574 this.type = null;
575 this.attrName = null;
576 this.inheritBindingContext = false;
577 }
578}
579
580BehaviorInstruction.normal = new BehaviorInstruction();
581
582/**
583* Provides all the instructions for how a target element should be enhanced inside of a view.
584*/
585export class TargetInstruction {
586
587 injectorId:number;
588 parentInjectorId:number;
589
590 shadowSlot:boolean;
591 slotName:string;
592 slotFallbackFactory:any;
593
594 contentExpression:any;
595
596 expressions:Array<Object>;
597 behaviorInstructions:Array<BehaviorInstruction>;
598 providers:Array<Function>;
599
600 viewFactory:ViewFactory;
601
602 anchorIsContainer:boolean;
603 elementInstruction:BehaviorInstruction;
604 lifting:boolean;
605
606 values:Object;
607
608 /**
609 * An empty array used to represent a target with no binding expressions.
610 */
611 static noExpressions = Object.freeze([]);
612
613 /**
614 * Creates an instruction that represents a shadow dom slot.
615 * @param parentInjectorId The id of the parent dependency injection container.
616 * @return The created instruction.
617 */
618 static shadowSlot(parentInjectorId: number): TargetInstruction {
619 let instruction = new TargetInstruction();
620 instruction.parentInjectorId = parentInjectorId;
621 instruction.shadowSlot = true;
622 return instruction;
623 }
624
625 /**
626 * Creates an instruction that represents a binding expression in the content of an element.
627 * @param expression The binding expression.
628 * @return The created instruction.
629 */
630 static contentExpression(expression): TargetInstruction {
631 let instruction = new TargetInstruction();
632 instruction.contentExpression = expression;
633 return instruction;
634 }
635
636 /**
637 * Creates an instruction that represents content that was lifted out of the DOM and into a ViewFactory.
638 * @param parentInjectorId The id of the parent dependency injection container.
639 * @param liftingInstruction The behavior instruction of the lifting behavior.
640 * @return The created instruction.
641 */
642 static lifting(parentInjectorId: number, liftingInstruction: BehaviorInstruction): TargetInstruction {
643 let instruction = new TargetInstruction();
644 instruction.parentInjectorId = parentInjectorId;
645 instruction.expressions = TargetInstruction.noExpressions;
646 instruction.behaviorInstructions = [liftingInstruction];
647 instruction.viewFactory = liftingInstruction.viewFactory;
648 instruction.providers = [liftingInstruction.type.target];
649 instruction.lifting = true;
650 return instruction;
651 }
652
653 /**
654 * Creates an instruction that represents an element with behaviors and bindings.
655 * @param injectorId The id of the dependency injection container.
656 * @param parentInjectorId The id of the parent dependency injection container.
657 * @param providers The types which will provide behavior for this element.
658 * @param behaviorInstructions The instructions for creating behaviors on this element.
659 * @param expressions Bindings, listeners, triggers, etc.
660 * @param elementInstruction The element behavior for this element.
661 * @return The created instruction.
662 */
663 static normal(injectorId: number, parentInjectorId: number, providers: Array<Function>, behaviorInstructions: Array<BehaviorInstruction>, expressions: Array<Object>, elementInstruction: BehaviorInstruction): TargetInstruction {
664 let instruction = new TargetInstruction();
665 instruction.injectorId = injectorId;
666 instruction.parentInjectorId = parentInjectorId;
667 instruction.providers = providers;
668 instruction.behaviorInstructions = behaviorInstructions;
669 instruction.expressions = expressions;
670 instruction.anchorIsContainer = elementInstruction ? elementInstruction.anchorIsContainer : true;
671 instruction.elementInstruction = elementInstruction;
672 return instruction;
673 }
674
675 /**
676 * Creates an instruction that represents the surrogate behaviors and bindings for an element.
677 * @param providers The types which will provide behavior for this element.
678 * @param behaviorInstructions The instructions for creating behaviors on this element.
679 * @param expressions Bindings, listeners, triggers, etc.
680 * @param values A key/value lookup of attributes to transplant.
681 * @return The created instruction.
682 */
683 static surrogate(providers: Array<Function>, behaviorInstructions: Array<BehaviorInstruction>, expressions: Array<Object>, values: Object): TargetInstruction {
684 let instruction = new TargetInstruction();
685 instruction.expressions = expressions;
686 instruction.behaviorInstructions = behaviorInstructions;
687 instruction.providers = providers;
688 instruction.values = values;
689 return instruction;
690 }
691
692 /**
693 * Creates an instance of TargetInstruction.
694 */
695 constructor() {
696 this.injectorId = null;
697 this.parentInjectorId = null;
698
699 this.shadowSlot = false;
700 this.slotName = null;
701 this.slotFallbackFactory = null;
702
703 this.contentExpression = null;
704
705 this.expressions = null;
706 this.behaviorInstructions = null;
707 this.providers = null;
708
709 this.viewFactory = null;
710
711 this.anchorIsContainer = false;
712 this.elementInstruction = null;
713 this.lifting = false;
714
715 this.values = null;
716 }
717}
718
719/**
720* Implemented by classes that describe how a view factory should be loaded.
721*/
722interface ViewStrategy {
723 /**
724 * Loads a view factory.
725 * @param viewEngine The view engine to use during the load process.
726 * @param compileInstruction Additional instructions to use during compilation of the view.
727 * @param loadContext The loading context used for loading all resources and dependencies.
728 * @param target A class from which to extract metadata of additional resources to load.
729 * @return A promise for the view factory that is produced by this strategy.
730 */
731 loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory>;
732}
733
734/**
735* Decorator: Indicates that the decorated class/object is a view strategy.
736*/
737export const viewStrategy: Function = protocol.create('aurelia:view-strategy', {
738 validate(target) {
739 if (!(typeof target.loadViewFactory === 'function')) {
740 return 'View strategies must implement: loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext): Promise<ViewFactory>';
741 }
742
743 return true;
744 },
745 compose(target) {
746 if (!(typeof target.makeRelativeTo === 'function')) {
747 target.makeRelativeTo = PLATFORM.noop;
748 }
749 }
750});
751
752/**
753* A view strategy that loads a view relative to its associated view-model.
754*/
755@viewStrategy()
756export class RelativeViewStrategy {
757 /**
758 * Creates an instance of RelativeViewStrategy.
759 * @param path The relative path to the view.
760 */
761 constructor(path: string) {
762 this.path = path;
763 this.absolutePath = null;
764 }
765
766 /**
767 * Loads a view factory.
768 * @param viewEngine The view engine to use during the load process.
769 * @param compileInstruction Additional instructions to use during compilation of the view.
770 * @param loadContext The loading context used for loading all resources and dependencies.
771 * @param target A class from which to extract metadata of additional resources to load.
772 * @return A promise for the view factory that is produced by this strategy.
773 */
774 loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
775 if (this.absolutePath === null && this.moduleId) {
776 this.absolutePath = relativeToFile(this.path, this.moduleId);
777 }
778
779 compileInstruction.associatedModuleId = this.moduleId;
780 return viewEngine.loadViewFactory(this.absolutePath || this.path, compileInstruction, loadContext, target);
781 }
782
783 /**
784 * Makes the view loaded by this strategy relative to the provided file path.
785 * @param file The path to load the view relative to.
786 */
787 makeRelativeTo(file: string): void {
788 if (this.absolutePath === null) {
789 this.absolutePath = relativeToFile(this.path, file);
790 }
791 }
792}
793
794/**
795* A view strategy based on naming conventions.
796*/
797@viewStrategy()
798export class ConventionalViewStrategy {
799 /**
800 * Creates an instance of ConventionalViewStrategy.
801 * @param viewLocator The view locator service for conventionally locating the view.
802 * @param origin The origin of the view model to conventionally load the view for.
803 */
804 constructor(viewLocator: ViewLocator, origin: Origin) {
805 this.moduleId = origin.moduleId;
806 this.viewUrl = viewLocator.convertOriginToViewUrl(origin);
807 }
808
809 /**
810 * Loads a view factory.
811 * @param viewEngine The view engine to use during the load process.
812 * @param compileInstruction Additional instructions to use during compilation of the view.
813 * @param loadContext The loading context used for loading all resources and dependencies.
814 * @param target A class from which to extract metadata of additional resources to load.
815 * @return A promise for the view factory that is produced by this strategy.
816 */
817 loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
818 compileInstruction.associatedModuleId = this.moduleId;
819 return viewEngine.loadViewFactory(this.viewUrl, compileInstruction, loadContext, target);
820 }
821}
822
823/**
824* A view strategy that indicates that the component has no view that the templating engine needs to manage.
825* Typically used when the component author wishes to take over fine-grained rendering control.
826*/
827@viewStrategy()
828export class NoViewStrategy {
829 /**
830 * Creates an instance of NoViewStrategy.
831 * @param dependencies A list of view resource dependencies of this view.
832 * @param dependencyBaseUrl The base url for the view dependencies.
833 */
834 constructor(dependencies?: Array<string|Function|Object>, dependencyBaseUrl?: string) {
835 this.dependencies = dependencies || null;
836 this.dependencyBaseUrl = dependencyBaseUrl || '';
837 }
838
839 /**
840 * Loads a view factory.
841 * @param viewEngine The view engine to use during the load process.
842 * @param compileInstruction Additional instructions to use during compilation of the view.
843 * @param loadContext The loading context used for loading all resources and dependencies.
844 * @param target A class from which to extract metadata of additional resources to load.
845 * @return A promise for the view factory that is produced by this strategy.
846 */
847 loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
848 let entry = this.entry;
849 let dependencies = this.dependencies;
850
851 if (entry && entry.factoryIsReady) {
852 return Promise.resolve(null);
853 }
854
855 this.entry = entry = new TemplateRegistryEntry(this.moduleId || this.dependencyBaseUrl);
856 // since we're not invoking the TemplateRegistryEntry template setter
857 // we need to create the dependencies Array manually and set it as loaded:
858 entry.dependencies = [];
859 entry.templateIsLoaded = true;
860
861 if (dependencies !== null) {
862 for (let i = 0, ii = dependencies.length; i < ii; ++i) {
863 let current = dependencies[i];
864
865 if (typeof current === 'string' || typeof current === 'function') {
866 entry.addDependency(current);
867 } else {
868 entry.addDependency(current.from, current.as);
869 }
870 }
871 }
872
873 compileInstruction.associatedModuleId = this.moduleId;
874
875 // loadViewFactory will resolve as 'null' because entry template is not set:
876 return viewEngine.loadViewFactory(entry, compileInstruction, loadContext, target);
877 }
878}
879
880/**
881* A view strategy created directly from the template registry entry.
882*/
883@viewStrategy()
884export class TemplateRegistryViewStrategy {
885 /**
886 * Creates an instance of TemplateRegistryViewStrategy.
887 * @param moduleId The associated moduleId of the view to be loaded.
888 * @param entry The template registry entry used in loading the view factory.
889 */
890 constructor(moduleId: string, entry: TemplateRegistryEntry) {
891 this.moduleId = moduleId;
892 this.entry = entry;
893 }
894
895 /**
896 * Loads a view factory.
897 * @param viewEngine The view engine to use during the load process.
898 * @param compileInstruction Additional instructions to use during compilation of the view.
899 * @param loadContext The loading context used for loading all resources and dependencies.
900 * @param target A class from which to extract metadata of additional resources to load.
901 * @return A promise for the view factory that is produced by this strategy.
902 */
903 loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
904 let entry = this.entry;
905
906 if (entry.factoryIsReady) {
907 return Promise.resolve(entry.factory);
908 }
909
910 compileInstruction.associatedModuleId = this.moduleId;
911 return viewEngine.loadViewFactory(entry, compileInstruction, loadContext, target);
912 }
913}
914
915/**
916* A view strategy that allows the component author to inline the html for the view.
917*/
918@viewStrategy()
919export class InlineViewStrategy {
920 /**
921 * Creates an instance of InlineViewStrategy.
922 * @param markup The markup for the view. Be sure to include the wrapping template tag.
923 * @param dependencies A list of view resource dependencies of this view.
924 * @param dependencyBaseUrl The base url for the view dependencies.
925 */
926 constructor(markup: string, dependencies?: Array<string|Function|Object>, dependencyBaseUrl?: string) {
927 this.markup = markup;
928 this.dependencies = dependencies || null;
929 this.dependencyBaseUrl = dependencyBaseUrl || '';
930 }
931
932 /**
933 * Loads a view factory.
934 * @param viewEngine The view engine to use during the load process.
935 * @param compileInstruction Additional instructions to use during compilation of the view.
936 * @param loadContext The loading context used for loading all resources and dependencies.
937 * @param target A class from which to extract metadata of additional resources to load.
938 * @return A promise for the view factory that is produced by this strategy.
939 */
940 loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
941 let entry = this.entry;
942 let dependencies = this.dependencies;
943
944 if (entry && entry.factoryIsReady) {
945 return Promise.resolve(entry.factory);
946 }
947
948 this.entry = entry = new TemplateRegistryEntry(this.moduleId || this.dependencyBaseUrl);
949 entry.template = DOM.createTemplateFromMarkup(this.markup);
950
951 if (dependencies !== null) {
952 for (let i = 0, ii = dependencies.length; i < ii; ++i) {
953 let current = dependencies[i];
954
955 if (typeof current === 'string' || typeof current === 'function') {
956 entry.addDependency(current);
957 } else {
958 entry.addDependency(current.from, current.as);
959 }
960 }
961 }
962
963 compileInstruction.associatedModuleId = this.moduleId;
964 return viewEngine.loadViewFactory(entry, compileInstruction, loadContext, target);
965 }
966}
967
968interface IStaticViewConfig {
969 template: string | HTMLTemplateElement;
970 dependencies?: Function[] | (() => Array<Function | Promise<Function | Record<string, Function>>>);
971}
972
973@viewStrategy()
974export class StaticViewStrategy {
975
976 /**@internal */
977 template: string | HTMLTemplateElement;
978 /**@internal */
979 dependencies: Function[] | (() => Array<Function | Promise<Function | Record<string, Function>>>);
980 factoryIsReady: boolean;
981 factory: ViewFactory;
982
983 constructor(config: string | HTMLTemplateElement | IStaticViewConfig) {
984 if (typeof config === 'string' || config instanceof HTMLTemplateElement) {
985 config = {
986 template: config
987 };
988 }
989 this.template = config.template;
990 this.dependencies = config.dependencies || [];
991 this.factoryIsReady = false;
992 this.onReady = null;
993 }
994
995 /**
996 * Loads a view factory.
997 * @param viewEngine The view engine to use during the load process.
998 * @param compileInstruction Additional instructions to use during compilation of the view.
999 * @param loadContext The loading context used for loading all resources and dependencies.
1000 * @param target A class from which to extract metadata of additional resources to load.
1001 * @return A promise for the view factory that is produced by this strategy.
1002 */
1003 loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext: ResourceLoadContext, target: any): Promise<ViewFactory> {
1004 if (this.factoryIsReady) {
1005 return Promise.resolve(this.factory);
1006 }
1007 let deps = this.dependencies;
1008 deps = typeof deps === 'function' ? deps() : deps;
1009 deps = deps ? deps : [];
1010 deps = Array.isArray(deps) ? deps : [deps];
1011 // Promise.all() to normalize dependencies into an array of either functions, or records that contain function
1012 return Promise.all(deps).then((dependencies) => {
1013 const container = viewEngine.container;
1014 const appResources = viewEngine.appResources;
1015 const viewCompiler = viewEngine.viewCompiler;
1016 const viewResources = new ViewResources(appResources);
1017
1018 let resource;
1019 let elDeps = [];
1020
1021 if (target) {
1022 // when composing without a view mode, but view specified, target will be undefined
1023 viewResources.autoRegister(container, target);
1024 }
1025
1026 for (let dep of dependencies) {
1027 if (typeof dep === 'function') {
1028 // dependencies: [class1, class2, import('module').then(m => m.class3)]
1029 resource = viewResources.autoRegister(container, dep);
1030 } else if (dep && typeof dep === 'object') {
1031 // dependencies: [import('module1'), import('module2')]
1032 for (let key in dep) {
1033 let exported = dep[key];
1034 if (typeof exported === 'function') {
1035 resource = viewResources.autoRegister(container, exported);
1036 }
1037 }
1038 } else {
1039 throw new Error(`dependency neither function nor object. Received: "${typeof dep}"`);
1040 }
1041 if (resource.elementName !== null) {
1042 elDeps.push(resource);
1043 }
1044 }
1045 // only load custom element as first step.
1046 return Promise.all(elDeps.map(el => el.load(container, el.target))).then(() => {
1047 const factory = viewCompiler.compile(this.template, viewResources, compileInstruction);
1048 this.factoryIsReady = true;
1049 this.factory = factory;
1050 return factory;
1051 });
1052 });
1053 }
1054}
1055
1056/**
1057* Locates a view for an object.
1058*/
1059export class ViewLocator {
1060 /**
1061 * The metadata key for storing/finding view strategies associated with an class/object.
1062 */
1063 static viewStrategyMetadataKey = 'aurelia:view-strategy';
1064
1065 /**
1066 * Gets the view strategy for the value.
1067 * @param value The value to locate the view strategy for.
1068 * @return The located ViewStrategy instance.
1069 */
1070 getViewStrategy(value: any): ViewStrategy {
1071 if (!value) {
1072 return null;
1073 }
1074
1075 if (typeof value === 'object' && 'getViewStrategy' in value) {
1076 let origin = Origin.get(value.constructor);
1077
1078 value = value.getViewStrategy();
1079
1080 if (typeof value === 'string') {
1081 value = new RelativeViewStrategy(value);
1082 }
1083
1084 viewStrategy.assert(value);
1085
1086 if (origin.moduleId) {
1087 value.makeRelativeTo(origin.moduleId);
1088 }
1089
1090 return value;
1091 }
1092
1093 if (typeof value === 'string') {
1094 value = new RelativeViewStrategy(value);
1095 }
1096
1097 if (viewStrategy.validate(value)) {
1098 return value;
1099 }
1100
1101 if (typeof value !== 'function') {
1102 value = value.constructor;
1103 }
1104
1105 // static view strategy
1106 if ('$view' in value) {
1107 let c = value.$view;
1108 let view;
1109 c = typeof c === 'function' ? c.call(value) : c;
1110 if (c === null) {
1111 view = new NoViewStrategy();
1112 } else {
1113 view = c instanceof StaticViewStrategy ? c : new StaticViewStrategy(c);
1114 }
1115 metadata.define(ViewLocator.viewStrategyMetadataKey, view, value);
1116 return view;
1117 }
1118
1119 let origin = Origin.get(value);
1120 let strategy = metadata.get(ViewLocator.viewStrategyMetadataKey, value);
1121
1122 if (!strategy) {
1123 if (!origin.moduleId) {
1124 throw new Error('Cannot determine default view strategy for object.', value);
1125 }
1126
1127 strategy = this.createFallbackViewStrategy(origin);
1128 } else if (origin.moduleId) {
1129 strategy.moduleId = origin.moduleId;
1130 }
1131
1132 return strategy;
1133 }
1134
1135 /**
1136 * Creates a fallback View Strategy. Used when unable to locate a configured strategy.
1137 * The default implementation returns and instance of ConventionalViewStrategy.
1138 * @param origin The origin of the view model to return the strategy for.
1139 * @return The fallback ViewStrategy.
1140 */
1141 createFallbackViewStrategy(origin: Origin): ViewStrategy {
1142 return new ConventionalViewStrategy(this, origin);
1143 }
1144
1145 /**
1146 * Conventionally converts a view model origin to a view url.
1147 * Used by the ConventionalViewStrategy.
1148 * @param origin The origin of the view model to convert.
1149 * @return The view url.
1150 */
1151 convertOriginToViewUrl(origin: Origin): string {
1152 let moduleId = origin.moduleId;
1153 let id = (moduleId.endsWith('.js') || moduleId.endsWith('.ts')) ? moduleId.substring(0, moduleId.length - 3) : moduleId;
1154 return id + '.html';
1155 }
1156}
1157
1158function mi(name) {
1159 throw new Error(`BindingLanguage must implement ${name}().`);
1160}
1161
1162/**
1163* An abstract base class for implementations of a binding language.
1164*/
1165export class BindingLanguage {
1166 /**
1167 * Inspects an attribute for bindings.
1168 * @param resources The ViewResources for the view being compiled.
1169 * @param elementName The element name to inspect.
1170 * @param attrName The attribute name to inspect.
1171 * @param attrValue The attribute value to inspect.
1172 * @return An info object with the results of the inspection.
1173 */
1174 inspectAttribute(resources: ViewResources, elementName: string, attrName: string, attrValue: string): Object {
1175 mi('inspectAttribute');
1176 }
1177
1178 /**
1179 * Creates an attribute behavior instruction.
1180 * @param resources The ViewResources for the view being compiled.
1181 * @param element The element that the attribute is defined on.
1182 * @param info The info object previously returned from inspectAttribute.
1183 * @param existingInstruction A previously created instruction for this attribute.
1184 * @return The instruction instance.
1185 */
1186 createAttributeInstruction(resources: ViewResources, element: Element, info: Object, existingInstruction?: Object): BehaviorInstruction {
1187 mi('createAttributeInstruction');
1188 }
1189
1190 /**
1191 * Parses the text for bindings.
1192 * @param resources The ViewResources for the view being compiled.
1193 * @param value The value of the text to parse.
1194 * @return A binding expression.
1195 */
1196 inspectTextContent(resources: ViewResources, value: string): Object {
1197 mi('inspectTextContent');
1198 }
1199}
1200
1201let noNodes = Object.freeze([]);
1202
1203export class SlotCustomAttribute {
1204 static inject() {
1205 return [DOM.Element];
1206 }
1207
1208 constructor(element) {
1209 this.element = element;
1210 this.element.auSlotAttribute = this;
1211 }
1212
1213 valueChanged(newValue, oldValue) {
1214 //console.log('au-slot', newValue);
1215 }
1216}
1217
1218export class PassThroughSlot {
1219 constructor(anchor, name, destinationName, fallbackFactory) {
1220 this.anchor = anchor;
1221 this.anchor.viewSlot = this;
1222 this.name = name;
1223 this.destinationName = destinationName;
1224 this.fallbackFactory = fallbackFactory;
1225 this.destinationSlot = null;
1226 this.projections = 0;
1227 this.contentView = null;
1228
1229 let attr = new SlotCustomAttribute(this.anchor);
1230 attr.value = this.destinationName;
1231 }
1232
1233 get needsFallbackRendering() {
1234 return this.fallbackFactory && this.projections === 0;
1235 }
1236
1237 renderFallbackContent(view, nodes, projectionSource, index) {
1238 if (this.contentView === null) {
1239 this.contentView = this.fallbackFactory.create(this.ownerView.container);
1240 this.contentView.bind(this.ownerView.bindingContext, this.ownerView.overrideContext);
1241
1242 let slots = Object.create(null);
1243 slots[this.destinationSlot.name] = this.destinationSlot;
1244
1245 ShadowDOM.distributeView(this.contentView, slots, projectionSource, index, this.destinationSlot.name);
1246 }
1247 }
1248
1249 passThroughTo(destinationSlot) {
1250 this.destinationSlot = destinationSlot;
1251 }
1252
1253 addNode(view, node, projectionSource, index) {
1254 if (this.contentView !== null) {
1255 this.contentView.removeNodes();
1256 this.contentView.detached();
1257 this.contentView.unbind();
1258 this.contentView = null;
1259 }
1260
1261 if (node.viewSlot instanceof PassThroughSlot) {
1262 node.viewSlot.passThroughTo(this);
1263 return;
1264 }
1265
1266 this.projections++;
1267 this.destinationSlot.addNode(view, node, projectionSource, index);
1268 }
1269
1270 removeView(view, projectionSource) {
1271 this.projections--;
1272 this.destinationSlot.removeView(view, projectionSource);
1273
1274 if (this.needsFallbackRendering) {
1275 this.renderFallbackContent(null, noNodes, projectionSource);
1276 }
1277 }
1278
1279 removeAll(projectionSource) {
1280 this.projections = 0;
1281 this.destinationSlot.removeAll(projectionSource);
1282
1283 if (this.needsFallbackRendering) {
1284 this.renderFallbackContent(null, noNodes, projectionSource);
1285 }
1286 }
1287
1288 projectFrom(view, projectionSource) {
1289 this.destinationSlot.projectFrom(view, projectionSource);
1290 }
1291
1292 created(ownerView) {
1293 this.ownerView = ownerView;
1294 }
1295
1296 bind(view) {
1297 if (this.contentView) {
1298 this.contentView.bind(view.bindingContext, view.overrideContext);
1299 }
1300 }
1301
1302 attached() {
1303 if (this.contentView) {
1304 this.contentView.attached();
1305 }
1306 }
1307
1308 detached() {
1309 if (this.contentView) {
1310 this.contentView.detached();
1311 }
1312 }
1313
1314 unbind() {
1315 if (this.contentView) {
1316 this.contentView.unbind();
1317 }
1318 }
1319}
1320
1321export class ShadowSlot {
1322 constructor(anchor, name, fallbackFactory) {
1323 this.anchor = anchor;
1324 this.anchor.isContentProjectionSource = true;
1325 this.anchor.viewSlot = this;
1326 this.name = name;
1327 this.fallbackFactory = fallbackFactory;
1328 this.contentView = null;
1329 this.projections = 0;
1330 this.children = [];
1331 this.projectFromAnchors = null;
1332 this.destinationSlots = null;
1333 }
1334
1335 get needsFallbackRendering() {
1336 return this.fallbackFactory && this.projections === 0;
1337 }
1338
1339 addNode(view, node, projectionSource, index, destination) {
1340 if (this.contentView !== null) {
1341 this.contentView.removeNodes();
1342 this.contentView.detached();
1343 this.contentView.unbind();
1344 this.contentView = null;
1345 }
1346
1347 if (node.viewSlot instanceof PassThroughSlot) {
1348 node.viewSlot.passThroughTo(this);
1349 return;
1350 }
1351
1352 if (this.destinationSlots !== null) {
1353 ShadowDOM.distributeNodes(view, [node], this.destinationSlots, this, index);
1354 } else {
1355 node.auOwnerView = view;
1356 node.auProjectionSource = projectionSource;
1357 node.auAssignedSlot = this;
1358
1359 let anchor = this._findAnchor(view, node, projectionSource, index);
1360 let parent = anchor.parentNode;
1361
1362 parent.insertBefore(node, anchor);
1363 this.children.push(node);
1364 this.projections++;
1365 }
1366 }
1367
1368 removeView(view, projectionSource) {
1369 if (this.destinationSlots !== null) {
1370 ShadowDOM.undistributeView(view, this.destinationSlots, this);
1371 } else if (this.contentView && this.contentView.hasSlots) {
1372 ShadowDOM.undistributeView(view, this.contentView.slots, projectionSource);
1373 } else {
1374 let found = this.children.find(x => x.auSlotProjectFrom === projectionSource);
1375 if (found) {
1376 let children = found.auProjectionChildren;
1377
1378 for (let i = 0, ii = children.length; i < ii; ++i) {
1379 let child = children[i];
1380
1381 if (child.auOwnerView === view) {
1382 children.splice(i, 1);
1383 view.fragment.appendChild(child);
1384 i--; ii--;
1385 this.projections--;
1386 }
1387 }
1388
1389 if (this.needsFallbackRendering) {
1390 this.renderFallbackContent(view, noNodes, projectionSource);
1391 }
1392 }
1393 }
1394 }
1395
1396 removeAll(projectionSource) {
1397 if (this.destinationSlots !== null) {
1398 ShadowDOM.undistributeAll(this.destinationSlots, this);
1399 } else if (this.contentView && this.contentView.hasSlots) {
1400 ShadowDOM.undistributeAll(this.contentView.slots, projectionSource);
1401 } else {
1402 let found = this.children.find(x => x.auSlotProjectFrom === projectionSource);
1403
1404 if (found) {
1405 let children = found.auProjectionChildren;
1406 for (let i = 0, ii = children.length; i < ii; ++i) {
1407 let child = children[i];
1408 child.auOwnerView.fragment.appendChild(child);
1409 this.projections--;
1410 }
1411
1412 found.auProjectionChildren = [];
1413
1414 if (this.needsFallbackRendering) {
1415 this.renderFallbackContent(null, noNodes, projectionSource);
1416 }
1417 }
1418 }
1419 }
1420
1421 _findAnchor(view, node, projectionSource, index) {
1422 if (projectionSource) {
1423 //find the anchor associated with the projected view slot
1424 let found = this.children.find(x => x.auSlotProjectFrom === projectionSource);
1425 if (found) {
1426 if (index !== undefined) {
1427 let children = found.auProjectionChildren;
1428 let viewIndex = -1;
1429 let lastView;
1430
1431 for (let i = 0, ii = children.length; i < ii; ++i) {
1432 let current = children[i];
1433
1434 if (current.auOwnerView !== lastView) {
1435 viewIndex++;
1436 lastView = current.auOwnerView;
1437
1438 if (viewIndex >= index && lastView !== view) {
1439 children.splice(i, 0, node);
1440 return current;
1441 }
1442 }
1443 }
1444 }
1445
1446 found.auProjectionChildren.push(node);
1447 return found;
1448 }
1449 }
1450
1451 return this.anchor;
1452 }
1453
1454 projectTo(slots) {
1455 this.destinationSlots = slots;
1456 }
1457
1458 projectFrom(view, projectionSource) {
1459 let anchor = DOM.createComment('anchor');
1460 let parent = this.anchor.parentNode;
1461 anchor.auSlotProjectFrom = projectionSource;
1462 anchor.auOwnerView = view;
1463 anchor.auProjectionChildren = [];
1464 parent.insertBefore(anchor, this.anchor);
1465 this.children.push(anchor);
1466
1467 if (this.projectFromAnchors === null) {
1468 this.projectFromAnchors = [];
1469 }
1470
1471 this.projectFromAnchors.push(anchor);
1472 }
1473
1474 renderFallbackContent(view, nodes, projectionSource, index) {
1475 if (this.contentView === null) {
1476 this.contentView = this.fallbackFactory.create(this.ownerView.container);
1477 this.contentView.bind(this.ownerView.bindingContext, this.ownerView.overrideContext);
1478 this.contentView.insertNodesBefore(this.anchor);
1479 }
1480
1481 if (this.contentView.hasSlots) {
1482 let slots = this.contentView.slots;
1483 let projectFromAnchors = this.projectFromAnchors;
1484
1485 if (projectFromAnchors !== null) {
1486 for (let slotName in slots) {
1487 let slot = slots[slotName];
1488
1489 for (let i = 0, ii = projectFromAnchors.length; i < ii; ++i) {
1490 let anchor = projectFromAnchors[i];
1491 slot.projectFrom(anchor.auOwnerView, anchor.auSlotProjectFrom);
1492 }
1493 }
1494 }
1495
1496 this.fallbackSlots = slots;
1497 ShadowDOM.distributeNodes(view, nodes, slots, projectionSource, index);
1498 }
1499 }
1500
1501 created(ownerView) {
1502 this.ownerView = ownerView;
1503 }
1504
1505 bind(view) {
1506 if (this.contentView) {
1507 this.contentView.bind(view.bindingContext, view.overrideContext);
1508 }
1509 }
1510
1511 attached() {
1512 if (this.contentView) {
1513 this.contentView.attached();
1514 }
1515 }
1516
1517 detached() {
1518 if (this.contentView) {
1519 this.contentView.detached();
1520 }
1521 }
1522
1523 unbind() {
1524 if (this.contentView) {
1525 this.contentView.unbind();
1526 }
1527 }
1528}
1529
1530export class ShadowDOM {
1531 static defaultSlotKey = '__au-default-slot-key__';
1532
1533 static getSlotName(node) {
1534 if (node.auSlotAttribute === undefined) {
1535 return ShadowDOM.defaultSlotKey;
1536 }
1537
1538 return node.auSlotAttribute.value;
1539 }
1540
1541 static distributeView(view, slots, projectionSource, index, destinationOverride) {
1542 let nodes;
1543
1544 if (view === null) {
1545 nodes = noNodes;
1546 } else {
1547 let childNodes = view.fragment.childNodes;
1548 let ii = childNodes.length;
1549 nodes = new Array(ii);
1550
1551 for (let i = 0; i < ii; ++i) {
1552 nodes[i] = childNodes[i];
1553 }
1554 }
1555
1556 ShadowDOM.distributeNodes(
1557 view,
1558 nodes,
1559 slots,
1560 projectionSource,
1561 index,
1562 destinationOverride
1563 );
1564 }
1565
1566 static undistributeView(view, slots, projectionSource) {
1567 for (let slotName in slots) {
1568 slots[slotName].removeView(view, projectionSource);
1569 }
1570 }
1571
1572 static undistributeAll(slots, projectionSource) {
1573 for (let slotName in slots) {
1574 slots[slotName].removeAll(projectionSource);
1575 }
1576 }
1577
1578 static distributeNodes(view, nodes, slots, projectionSource, index, destinationOverride) {
1579 for (let i = 0, ii = nodes.length; i < ii; ++i) {
1580 let currentNode = nodes[i];
1581 let nodeType = currentNode.nodeType;
1582
1583 if (currentNode.isContentProjectionSource) {
1584 currentNode.viewSlot.projectTo(slots);
1585
1586 for (let slotName in slots) {
1587 slots[slotName].projectFrom(view, currentNode.viewSlot);
1588 }
1589
1590 nodes.splice(i, 1);
1591 ii--; i--;
1592 } else if (nodeType === 1 || nodeType === 3 || currentNode.viewSlot instanceof PassThroughSlot) { //project only elements and text
1593 if (nodeType === 3 && _isAllWhitespace(currentNode)) {
1594 nodes.splice(i, 1);
1595 ii--; i--;
1596 } else {
1597 let found = slots[destinationOverride || ShadowDOM.getSlotName(currentNode)];
1598
1599 if (found) {
1600 found.addNode(view, currentNode, projectionSource, index);
1601 nodes.splice(i, 1);
1602 ii--; i--;
1603 }
1604 }
1605 } else {
1606 nodes.splice(i, 1);
1607 ii--; i--;
1608 }
1609 }
1610
1611 for (let slotName in slots) {
1612 let slot = slots[slotName];
1613
1614 if (slot.needsFallbackRendering) {
1615 slot.renderFallbackContent(view, nodes, projectionSource, index);
1616 }
1617 }
1618 }
1619}
1620
1621function register(lookup, name, resource, type) {
1622 if (!name) {
1623 return;
1624 }
1625
1626 let existing = lookup[name];
1627 if (existing) {
1628 if (existing !== resource) {
1629 throw new Error(`Attempted to register ${type} when one with the same name already exists. Name: ${name}.`);
1630 }
1631
1632 return;
1633 }
1634
1635 lookup[name] = resource;
1636}
1637
1638/**
1639* View engine hooks that enable a view resource to provide custom processing during the compilation or creation of a view.
1640*/
1641interface ViewEngineHooks {
1642 /**
1643 * Invoked before a template is compiled.
1644 * @param content The DocumentFragment to compile.
1645 * @param resources The resources to compile the view against.
1646 * @param instruction The compilation instruction associated with the compilation process.
1647 */
1648 beforeCompile?: (content: DocumentFragment, resources: ViewResources, instruction: ViewCompileInstruction) => void;
1649 /**
1650 * Invoked after a template is compiled.
1651 * @param viewFactory The view factory that was produced from the compilation process.
1652 */
1653 afterCompile?: (viewFactory: ViewFactory) => void;
1654 /**
1655 * Invoked before a view is created.
1656 * @param viewFactory The view factory that will be used to create the view.
1657 * @param container The DI container used during view creation.
1658 * @param content The cloned document fragment representing the view.
1659 * @param instruction The view creation instruction associated with this creation process.
1660 */
1661 beforeCreate?: (viewFactory: ViewFactory, container: Container, content: DocumentFragment, instruction: ViewCreateInstruction) => void;
1662 /**
1663 * Invoked after a view is created.
1664 * @param view The view that was created by the factory.
1665 */
1666 afterCreate?: (view: View) => void;
1667
1668 /**
1669 * Invoked after the bindingContext and overrideContext are configured on the view but before the view is bound.
1670 * @param view The view that was created by the factory.
1671 */
1672 beforeBind?: (view: View) => void;
1673
1674 /**
1675 * Invoked before the view is unbind. The bindingContext and overrideContext are still available on the view.
1676 * @param view The view that was created by the factory.
1677 */
1678 beforeUnbind?: (view: View) => void;
1679}
1680
1681interface IBindablePropertyConfig {
1682 /**
1683 * The name of the property.
1684 */
1685 name?: string;
1686 attribute?: string;
1687 /**
1688 * The default binding mode of the property. If given string, will use to lookup
1689 */
1690 defaultBindingMode?: bindingMode | 'oneTime' | 'oneWay' | 'twoWay' | 'fromView' | 'toView';
1691 /**
1692 * The name of a view model method to invoke when the property is updated.
1693 */
1694 changeHandler?: string;
1695 /**
1696 * A default value for the property.
1697 */
1698 defaultValue?: any;
1699 /**
1700 * Designates the property as the default bindable property among all the other bindable properties when used in a custom attribute with multiple bindable properties.
1701 */
1702 primaryProperty?: boolean;
1703 // For compatibility and future extension
1704 [key: string]: any;
1705}
1706
1707interface IStaticResourceConfig {
1708 /**
1709 * Resource type of this class, omit equals to custom element
1710 */
1711 type?: 'element' | 'attribute' | 'valueConverter' | 'bindingBehavior' | 'viewEngineHooks';
1712 /**
1713 * Name of this resource. Reccommended to explicitly set to works better with minifier
1714 */
1715 name?: string;
1716 /**
1717 * Used to tell if a custom attribute is a template controller
1718 */
1719 templateController?: boolean;
1720 /**
1721 * Used to set default binding mode of default custom attribute view model "value" property
1722 */
1723 defaultBindingMode?: bindingMode | 'oneTime' | 'oneWay' | 'twoWay' | 'fromView' | 'toView';
1724 /**
1725 * Flags a custom attribute has dynamic options
1726 */
1727 hasDynamicOptions?: boolean;
1728 /**
1729 * Flag if this custom element uses native shadow dom instead of emulation
1730 */
1731 usesShadowDOM?: boolean;
1732 /**
1733 * Options that will be used if the element is flagged with usesShadowDOM
1734 */
1735 shadowDOMOptions?: ShadowRootInit;
1736 /**
1737 * Flag a custom element as containerless. Which will remove their render target
1738 */
1739 containerless?: boolean;
1740 /**
1741 * Custom processing of the attributes on an element before the framework inspects them.
1742 */
1743 processAttributes?: (viewCompiler: ViewCompiler, resources: ViewResources, node: Element, attributes: NamedNodeMap, elementInstruction: BehaviorInstruction) => void;
1744 /**
1745 * Enables custom processing of the content that is places inside the custom element by its consumer.
1746 * Pass a boolean to direct the template compiler to not process
1747 * the content placed inside this element. Alternatively, pass a function which
1748 * can provide custom processing of the content. This function should then return
1749 * a boolean indicating whether the compiler should also process the content.
1750 */
1751 processContent?: (viewCompiler: ViewCompiler, resources: ViewResources, node: Element, instruction: BehaviorInstruction) => boolean;
1752 /**
1753 * List of bindable properties of this custom element / custom attribute, by name or full config object
1754 */
1755 bindables?: (string | IBindablePropertyConfig)[];
1756}
1757
1758export function validateBehaviorName(name: string, type: string) {
1759 if (/[A-Z]/.test(name)) {
1760 let newName = _hyphenate(name);
1761 LogManager
1762 .getLogger('templating')
1763 .warn(`'${name}' is not a valid ${type} name and has been converted to '${newName}'. Upper-case letters are not allowed because the DOM is not case-sensitive.`);
1764 return newName;
1765 }
1766 return name;
1767}
1768
1769const conventionMark = '__au_resource__';
1770
1771/**
1772 * Represents a collection of resources used during the compilation of a view.
1773 * Will optinally add information to an existing HtmlBehaviorResource if given
1774 */
1775export class ViewResources {
1776
1777 /**
1778 * Checks whether the provided class contains any resource conventions
1779 * @param target Target class to extract metadata based on convention
1780 * @param existing If supplied, all custom element / attribute metadata extracted from convention will be apply to this instance
1781 */
1782 static convention(target: Function, existing?: HtmlBehaviorResource): HtmlBehaviorResource | ValueConverterResource | BindingBehaviorResource | ViewEngineHooksResource {
1783 let resource;
1784 // Use a simple string to mark that an HtmlBehaviorResource instance
1785 // has been applied all resource information from its target view model class
1786 // to prevent subsequence call re initialization all info again
1787 if (existing && conventionMark in existing) {
1788 return existing;
1789 }
1790 if ('$resource' in target) {
1791 let config = target.$resource;
1792 // 1. check if resource config is a string
1793 if (typeof config === 'string') {
1794 // it's a custom element, with name is the resource variable
1795 // static $resource = 'my-element'
1796 resource = existing || new HtmlBehaviorResource();
1797 resource[conventionMark] = true;
1798 if (!resource.elementName) {
1799 // if element name was not specified before
1800 resource.elementName = validateBehaviorName(config, 'custom element');
1801 }
1802 } else {
1803 // 2. if static config is not a string, normalize into an config object
1804 if (typeof config === 'function') {
1805 // static $resource() { }
1806 config = config.call(target);
1807 }
1808 if (typeof config === 'string') {
1809 // static $resource() { return 'my-custom-element-name' }
1810 // though rare case, still needs to handle properly
1811 config = { name: config };
1812 }
1813 // after normalization, copy to another obj
1814 // as the config could come from a static field, which subject to later reuse
1815 // it shouldn't be modified
1816 config = Object.assign({}, config);
1817 // no type specified = custom element
1818 let resourceType = config.type || 'element';
1819 // cannot do name = config.name || target.name
1820 // depends on resource type, it may need to use different strategies to normalize name
1821 let name = config.name;
1822 switch (resourceType) { // eslint-disable-line default-case
1823 case 'element': case 'attribute':
1824 // if a metadata is supplied, use it
1825 resource = existing || new HtmlBehaviorResource();
1826 resource[conventionMark] = true;
1827 if (resourceType === 'element') {
1828 // if element name was defined before applying convention here
1829 // it's a result from `@customElement` call (or manual modification)
1830 // need not to redefine name
1831 // otherwise, fall into following if
1832 if (!resource.elementName) {
1833 resource.elementName = name
1834 ? validateBehaviorName(name, 'custom element')
1835 : _hyphenate(target.name);
1836 }
1837 } else {
1838 // attribute name was defined before applying convention here
1839 // it's a result from `@customAttribute` call (or manual modification)
1840 // need not to redefine name
1841 // otherwise, fall into following if
1842 if (!resource.attributeName) {
1843 resource.attributeName = name
1844 ? validateBehaviorName(name, 'custom attribute')
1845 : _hyphenate(target.name);
1846 }
1847 }
1848 if ('templateController' in config) {
1849 // map templateController to liftsContent
1850 config.liftsContent = config.templateController;
1851 delete config.templateController;
1852 }
1853 if ('defaultBindingMode' in config && resource.attributeDefaultBindingMode !== undefined) {
1854 // map defaultBindingMode to attributeDefaultBinding mode
1855 // custom element doesn't have default binding mode
1856 config.attributeDefaultBindingMode = config.defaultBindingMode;
1857 delete config.defaultBindingMode;
1858 }
1859 // not bringing over the name.
1860 delete config.name;
1861 // just copy over. Devs are responsible for what specified in the config
1862 Object.assign(resource, config);
1863 break;
1864 case 'valueConverter':
1865 resource = new ValueConverterResource(camelCase(name || target.name));
1866 break;
1867 case 'bindingBehavior':
1868 resource = new BindingBehaviorResource(camelCase(name || target.name));
1869 break;
1870 case 'viewEngineHooks':
1871 resource = new ViewEngineHooksResource();
1872 break;
1873 }
1874 }
1875
1876 if (resource instanceof HtmlBehaviorResource) {
1877 // check for bindable registration
1878 // This will concat bindables specified in static field / method with bindables specified via decorators
1879 // Which means if `name` is specified in both decorator and static config, it will be duplicated here
1880 // though it will finally resolves to only 1 `name` attribute
1881 // Will not break if it's done in that way but probably only happenned in inheritance scenarios.
1882 let bindables = typeof config === 'string' ? undefined : config.bindables;
1883 let currentProps = resource.properties;
1884 if (Array.isArray(bindables)) {
1885 for (let i = 0, ii = bindables.length; ii > i; ++i) {
1886 let prop = bindables[i];
1887 if (!prop || (typeof prop !== 'string' && !prop.name)) {
1888 throw new Error(`Invalid bindable property at "${i}" for class "${target.name}". Expected either a string or an object with "name" property.`);
1889 }
1890 let newProp = new BindableProperty(prop);
1891 // Bindable properties defined in $resource convention
1892 // shouldn't override existing prop with the same name
1893 // as they could be explicitly defined via decorator, thus more trust worthy ?
1894 let existed = false;
1895 for (let j = 0, jj = currentProps.length; jj > j; ++j) {
1896 if (currentProps[j].name === newProp.name) {
1897 existed = true;
1898 break;
1899 }
1900 }
1901 if (existed) {
1902 continue;
1903 }
1904 newProp.registerWith(target, resource);
1905 }
1906 }
1907 }
1908 }
1909 return resource;
1910 }
1911
1912 /**
1913 * A custom binding language used in the view.
1914 */
1915 bindingLanguage = null;
1916
1917 /**
1918 * Creates an instance of ViewResources.
1919 * @param parent The parent resources. This resources can override them, but if a resource is not found, it will be looked up in the parent.
1920 * @param viewUrl The url of the view to which these resources apply.
1921 */
1922 constructor(parent?: ViewResources, viewUrl?: string) {
1923 this.parent = parent || null;
1924 this.hasParent = this.parent !== null;
1925 this.viewUrl = viewUrl || '';
1926 this.lookupFunctions = {
1927 valueConverters: this.getValueConverter.bind(this),
1928 bindingBehaviors: this.getBindingBehavior.bind(this)
1929 };
1930 this.attributes = Object.create(null);
1931 this.elements = Object.create(null);
1932 this.valueConverters = Object.create(null);
1933 this.bindingBehaviors = Object.create(null);
1934 this.attributeMap = Object.create(null);
1935 this.values = Object.create(null);
1936 this.beforeCompile = this.afterCompile = this.beforeCreate = this.afterCreate = this.beforeBind = this.beforeUnbind = false;
1937 }
1938
1939 _tryAddHook(obj, name) {
1940 if (typeof obj[name] === 'function') {
1941 let func = obj[name].bind(obj);
1942 let counter = 1;
1943 let callbackName;
1944
1945 while (this[callbackName = name + counter.toString()] !== undefined) {
1946 counter++;
1947 }
1948
1949 this[name] = true;
1950 this[callbackName] = func;
1951 }
1952 }
1953
1954 _invokeHook(name, one, two, three, four) {
1955 if (this.hasParent) {
1956 this.parent._invokeHook(name, one, two, three, four);
1957 }
1958
1959 if (this[name]) {
1960 this[name + '1'](one, two, three, four);
1961
1962 let callbackName = name + '2';
1963 if (this[callbackName]) {
1964 this[callbackName](one, two, three, four);
1965
1966 callbackName = name + '3';
1967 if (this[callbackName]) {
1968 this[callbackName](one, two, three, four);
1969
1970 let counter = 4;
1971
1972 while (this[callbackName = name + counter.toString()] !== undefined) {
1973 this[callbackName](one, two, three, four);
1974 counter++;
1975 }
1976 }
1977 }
1978 }
1979 }
1980
1981 /**
1982 * Registers view engine hooks for the view.
1983 * @param hooks The hooks to register.
1984 */
1985 registerViewEngineHooks(hooks: ViewEngineHooks): void {
1986 this._tryAddHook(hooks, 'beforeCompile');
1987 this._tryAddHook(hooks, 'afterCompile');
1988 this._tryAddHook(hooks, 'beforeCreate');
1989 this._tryAddHook(hooks, 'afterCreate');
1990 this._tryAddHook(hooks, 'beforeBind');
1991 this._tryAddHook(hooks, 'beforeUnbind');
1992 }
1993
1994 /**
1995 * Gets the binding language associated with these resources, or return the provided fallback implementation.
1996 * @param bindingLanguageFallback The fallback binding language implementation to use if no binding language is configured locally.
1997 * @return The binding language.
1998 */
1999 getBindingLanguage(bindingLanguageFallback: BindingLanguage): BindingLanguage {
2000 return this.bindingLanguage || (this.bindingLanguage = bindingLanguageFallback);
2001 }
2002
2003 /**
2004 * Patches an immediate parent into the view resource resolution hierarchy.
2005 * @param newParent The new parent resources to patch in.
2006 */
2007 patchInParent(newParent: ViewResources): void {
2008 let originalParent = this.parent;
2009
2010 this.parent = newParent || null;
2011 this.hasParent = this.parent !== null;
2012
2013 if (newParent.parent === null) {
2014 newParent.parent = originalParent;
2015 newParent.hasParent = originalParent !== null;
2016 }
2017 }
2018
2019 /**
2020 * Maps a path relative to the associated view's origin.
2021 * @param path The relative path.
2022 * @return The calcualted path.
2023 */
2024 relativeToView(path: string): string {
2025 return relativeToFile(path, this.viewUrl);
2026 }
2027
2028 /**
2029 * Registers an HTML element.
2030 * @param tagName The name of the custom element.
2031 * @param behavior The behavior of the element.
2032 */
2033 registerElement(tagName: string, behavior: HtmlBehaviorResource): void {
2034 register(this.elements, tagName, behavior, 'an Element');
2035 }
2036
2037 /**
2038 * Gets an HTML element behavior.
2039 * @param tagName The tag name to search for.
2040 * @return The HtmlBehaviorResource for the tag name or null.
2041 */
2042 getElement(tagName: string): HtmlBehaviorResource {
2043 return this.elements[tagName] || (this.hasParent ? this.parent.getElement(tagName) : null);
2044 }
2045
2046 /**
2047 * Gets the known attribute name based on the local attribute name.
2048 * @param attribute The local attribute name to lookup.
2049 * @return The known name.
2050 */
2051 mapAttribute(attribute: string): string {
2052 return this.attributeMap[attribute] || (this.hasParent ? this.parent.mapAttribute(attribute) : null);
2053 }
2054
2055 /**
2056 * Registers an HTML attribute.
2057 * @param attribute The name of the attribute.
2058 * @param behavior The behavior of the attribute.
2059 * @param knownAttribute The well-known name of the attribute (in lieu of the local name).
2060 */
2061 registerAttribute(attribute: string, behavior: HtmlBehaviorResource, knownAttribute: string): void {
2062 this.attributeMap[attribute] = knownAttribute;
2063 register(this.attributes, attribute, behavior, 'an Attribute');
2064 }
2065
2066 /**
2067 * Gets an HTML attribute behavior.
2068 * @param attribute The name of the attribute to lookup.
2069 * @return The HtmlBehaviorResource for the attribute or null.
2070 */
2071 getAttribute(attribute: string): HtmlBehaviorResource {
2072 return this.attributes[attribute] || (this.hasParent ? this.parent.getAttribute(attribute) : null);
2073 }
2074
2075 /**
2076 * Registers a value converter.
2077 * @param name The name of the value converter.
2078 * @param valueConverter The value converter instance.
2079 */
2080 registerValueConverter(name: string, valueConverter: Object): void {
2081 register(this.valueConverters, name, valueConverter, 'a ValueConverter');
2082 }
2083
2084 /**
2085 * Gets a value converter.
2086 * @param name The name of the value converter.
2087 * @return The value converter instance.
2088 */
2089 getValueConverter(name: string): Object {
2090 return this.valueConverters[name] || (this.hasParent ? this.parent.getValueConverter(name) : null);
2091 }
2092
2093 /**
2094 * Registers a binding behavior.
2095 * @param name The name of the binding behavior.
2096 * @param bindingBehavior The binding behavior instance.
2097 */
2098 registerBindingBehavior(name: string, bindingBehavior: Object): void {
2099 register(this.bindingBehaviors, name, bindingBehavior, 'a BindingBehavior');
2100 }
2101
2102 /**
2103 * Gets a binding behavior.
2104 * @param name The name of the binding behavior.
2105 * @return The binding behavior instance.
2106 */
2107 getBindingBehavior(name: string): Object {
2108 return this.bindingBehaviors[name] || (this.hasParent ? this.parent.getBindingBehavior(name) : null);
2109 }
2110
2111 /**
2112 * Registers a value.
2113 * @param name The name of the value.
2114 * @param value The value.
2115 */
2116 registerValue(name: string, value: any): void {
2117 register(this.values, name, value, 'a value');
2118 }
2119
2120 /**
2121 * Gets a value.
2122 * @param name The name of the value.
2123 * @return The value.
2124 */
2125 getValue(name: string): any {
2126 return this.values[name] || (this.hasParent ? this.parent.getValue(name) : null);
2127 }
2128
2129 /**
2130 * @internal
2131 * Not supported for public use. Can be changed without warning.
2132 *
2133 * Auto register a resources based on its metadata or convention
2134 * Will fallback to custom element if no metadata found and all conventions fail
2135 * @param {Container} container
2136 * @param {Function} impl
2137 * @returns {HtmlBehaviorResource | ValueConverterResource | BindingBehaviorResource | ViewEngineHooksResource}
2138 */
2139 autoRegister(container, impl) {
2140 let resourceTypeMeta = metadata.getOwn(metadata.resource, impl);
2141 if (resourceTypeMeta) {
2142 if (resourceTypeMeta instanceof HtmlBehaviorResource) {
2143 // first use static resource
2144 ViewResources.convention(impl, resourceTypeMeta);
2145
2146 // then fallback to traditional convention
2147 if (resourceTypeMeta.attributeName === null && resourceTypeMeta.elementName === null) {
2148 //no customeElement or customAttribute but behavior added by other metadata
2149 HtmlBehaviorResource.convention(impl.name, resourceTypeMeta);
2150 }
2151 if (resourceTypeMeta.attributeName === null && resourceTypeMeta.elementName === null) {
2152 //no convention and no customeElement or customAttribute but behavior added by other metadata
2153 resourceTypeMeta.elementName = _hyphenate(impl.name);
2154 }
2155 }
2156 } else {
2157 resourceTypeMeta = ViewResources.convention(impl)
2158 || HtmlBehaviorResource.convention(impl.name)
2159 || ValueConverterResource.convention(impl.name)
2160 || BindingBehaviorResource.convention(impl.name)
2161 || ViewEngineHooksResource.convention(impl.name);
2162 if (!resourceTypeMeta) {
2163 // doesn't match any convention, and is an exported value => custom element
2164 resourceTypeMeta = new HtmlBehaviorResource();
2165 resourceTypeMeta.elementName = _hyphenate(impl.name);
2166 }
2167 metadata.define(metadata.resource, resourceTypeMeta, impl);
2168 }
2169 resourceTypeMeta.initialize(container, impl);
2170 resourceTypeMeta.register(this);
2171 return resourceTypeMeta;
2172 }
2173}
2174
2175/* eslint no-unused-vars: 0, no-constant-condition: 0 */
2176/**
2177* Represents a node in the view hierarchy.
2178*/
2179interface ViewNode {
2180 /**
2181 * Binds the node and it's children.
2182 * @param bindingContext The binding context to bind to.
2183 * @param overrideContext A secondary binding context that can override the standard context.
2184 */
2185 bind(bindingContext: Object, overrideContext?: Object): void;
2186 /**
2187 * Triggers the attach for the node and its children.
2188 */
2189 attached(): void;
2190 /**
2191 * Triggers the detach for the node and its children.
2192 */
2193 detached(): void;
2194 /**
2195 * Unbinds the node and its children.
2196 */
2197 unbind(): void;
2198}
2199
2200export class View {
2201 /**
2202 * The Dependency Injection Container that was used to create this View instance.
2203 */
2204 container: Container;
2205
2206 /**
2207 * The ViewFactory that built this View instance.
2208 */
2209 viewFactory: ViewFactory;
2210
2211 /**
2212 * Contains the DOM Nodes which represent this View. If the view was created via the "enhance" API, this will be an Element, otherwise it will be a DocumentFragment. If not created via "enhance" then the fragment will only contain nodes when the View is detached from the DOM.
2213 */
2214 fragment: DocumentFragment | Element;
2215
2216 /**
2217 * The primary binding context that this view is data-bound to.
2218 */
2219 bindingContext: Object;
2220
2221 /**
2222 * The override context which contains properties capable of overriding those found on the binding context.
2223 */
2224 overrideContext: Object;
2225
2226 /**
2227 * The Controller instance that owns this View.
2228 */
2229 controller: Controller;
2230
2231 /**
2232 * Creates a View instance.
2233 * @param container The container from which the view was created.
2234 * @param viewFactory The factory that created this view.
2235 * @param fragment The DOM fragement representing the view.
2236 * @param controllers The controllers inside this view.
2237 * @param bindings The bindings inside this view.
2238 * @param children The children of this view.
2239 */
2240 constructor(container: Container, viewFactory: ViewFactory, fragment: DocumentFragment, controllers: Controller[], bindings: Binding[], children: ViewNode[], slots: Object) {
2241 this.container = container;
2242 this.viewFactory = viewFactory;
2243 this.resources = viewFactory.resources;
2244 this.fragment = fragment;
2245 this.firstChild = fragment.firstChild;
2246 this.lastChild = fragment.lastChild;
2247 this.controllers = controllers;
2248 this.bindings = bindings;
2249 this.children = children;
2250 this.slots = slots;
2251 this.hasSlots = false;
2252 this.fromCache = false;
2253 this.isBound = false;
2254 this.isAttached = false;
2255 this.bindingContext = null;
2256 this.overrideContext = null;
2257 this.controller = null;
2258 this.viewModelScope = null;
2259 this.animatableElement = undefined;
2260 this._isUserControlled = false;
2261 this.contentView = null;
2262
2263 for (let key in slots) {
2264 this.hasSlots = true;
2265 break;
2266 }
2267 }
2268
2269 /**
2270 * Returns this view to the appropriate view cache.
2271 */
2272 returnToCache(): void {
2273 this.viewFactory.returnViewToCache(this);
2274 }
2275
2276 /**
2277 * Triggers the created callback for this view and its children.
2278 */
2279 created(): void {
2280 let i;
2281 let ii;
2282 let controllers = this.controllers;
2283
2284 for (i = 0, ii = controllers.length; i < ii; ++i) {
2285 controllers[i].created(this);
2286 }
2287 }
2288
2289 /**
2290 * Binds the view and it's children.
2291 * @param bindingContext The binding context to bind to.
2292 * @param overrideContext A secondary binding context that can override the standard context.
2293 */
2294 bind(bindingContext: Object, overrideContext?: Object, _systemUpdate?: boolean): void {
2295 let controllers;
2296 let bindings;
2297 let children;
2298 let i;
2299 let ii;
2300
2301 if (_systemUpdate && this._isUserControlled) {
2302 return;
2303 }
2304
2305 if (this.isBound) {
2306 if (this.bindingContext === bindingContext) {
2307 return;
2308 }
2309
2310 this.unbind();
2311 }
2312
2313 this.isBound = true;
2314 this.bindingContext = bindingContext;
2315 this.overrideContext = overrideContext || createOverrideContext(bindingContext);
2316
2317 this.resources._invokeHook('beforeBind', this);
2318
2319 bindings = this.bindings;
2320 for (i = 0, ii = bindings.length; i < ii; ++i) {
2321 bindings[i].bind(this);
2322 }
2323
2324 if (this.viewModelScope !== null) {
2325 bindingContext.bind(this.viewModelScope.bindingContext, this.viewModelScope.overrideContext);
2326 this.viewModelScope = null;
2327 }
2328
2329 controllers = this.controllers;
2330 for (i = 0, ii = controllers.length; i < ii; ++i) {
2331 controllers[i].bind(this);
2332 }
2333
2334 children = this.children;
2335 for (i = 0, ii = children.length; i < ii; ++i) {
2336 children[i].bind(bindingContext, overrideContext, true);
2337 }
2338
2339 if (this.hasSlots) {
2340 ShadowDOM.distributeView(this.contentView, this.slots);
2341 }
2342 }
2343
2344 /**
2345 * Adds a binding instance to this view.
2346 * @param binding The binding instance.
2347 */
2348 addBinding(binding: Object): void {
2349 this.bindings.push(binding);
2350
2351 if (this.isBound) {
2352 binding.bind(this);
2353 }
2354 }
2355
2356 /**
2357 * Unbinds the view and its children.
2358 */
2359 unbind(): void {
2360 let controllers;
2361 let bindings;
2362 let children;
2363 let i;
2364 let ii;
2365
2366 if (this.isBound) {
2367 this.isBound = false;
2368 this.resources._invokeHook('beforeUnbind', this);
2369
2370 if (this.controller !== null) {
2371 this.controller.unbind();
2372 }
2373
2374 bindings = this.bindings;
2375 for (i = 0, ii = bindings.length; i < ii; ++i) {
2376 bindings[i].unbind();
2377 }
2378
2379 controllers = this.controllers;
2380 for (i = 0, ii = controllers.length; i < ii; ++i) {
2381 controllers[i].unbind();
2382 }
2383
2384 children = this.children;
2385 for (i = 0, ii = children.length; i < ii; ++i) {
2386 children[i].unbind();
2387 }
2388
2389 this.bindingContext = null;
2390 this.overrideContext = null;
2391 }
2392 }
2393
2394 /**
2395 * Inserts this view's nodes before the specified DOM node.
2396 * @param refNode The node to insert this view's nodes before.
2397 */
2398 insertNodesBefore(refNode: Node): void {
2399 refNode.parentNode.insertBefore(this.fragment, refNode);
2400 }
2401
2402 /**
2403 * Appends this view's to the specified DOM node.
2404 * @param parent The parent element to append this view's nodes to.
2405 */
2406 appendNodesTo(parent: Element): void {
2407 parent.appendChild(this.fragment);
2408 }
2409
2410 /**
2411 * Removes this view's nodes from the DOM.
2412 */
2413 removeNodes(): void {
2414 let fragment = this.fragment;
2415 let current = this.firstChild;
2416 let end = this.lastChild;
2417 let next;
2418
2419 while (current) {
2420 next = current.nextSibling;
2421 fragment.appendChild(current);
2422
2423 if (current === end) {
2424 break;
2425 }
2426
2427 current = next;
2428 }
2429 }
2430
2431 /**
2432 * Triggers the attach for the view and its children.
2433 */
2434 attached(): void {
2435 let controllers;
2436 let children;
2437 let i;
2438 let ii;
2439
2440 if (this.isAttached) {
2441 return;
2442 }
2443
2444 this.isAttached = true;
2445
2446 if (this.controller !== null) {
2447 this.controller.attached();
2448 }
2449
2450 controllers = this.controllers;
2451 for (i = 0, ii = controllers.length; i < ii; ++i) {
2452 controllers[i].attached();
2453 }
2454
2455 children = this.children;
2456 for (i = 0, ii = children.length; i < ii; ++i) {
2457 children[i].attached();
2458 }
2459 }
2460
2461 /**
2462 * Triggers the detach for the view and its children.
2463 */
2464 detached(): void {
2465 let controllers;
2466 let children;
2467 let i;
2468 let ii;
2469
2470 if (this.isAttached) {
2471 this.isAttached = false;
2472
2473 if (this.controller !== null) {
2474 this.controller.detached();
2475 }
2476
2477 controllers = this.controllers;
2478 for (i = 0, ii = controllers.length; i < ii; ++i) {
2479 controllers[i].detached();
2480 }
2481
2482 children = this.children;
2483 for (i = 0, ii = children.length; i < ii; ++i) {
2484 children[i].detached();
2485 }
2486 }
2487 }
2488}
2489
2490/**
2491* An optional interface describing the created convention.
2492*/
2493interface ComponentCreated {
2494 /**
2495 * Implement this hook if you want to perform custom logic after the constructor has been called.
2496 * At this point in time, the view has also been created and both the view-model and the view
2497 * are connected to their controller. The hook will recieve the instance of the "owningView".
2498 * This is the view that the component is declared inside of. If the component itself has a view,
2499 * this will be passed second.
2500 */
2501 created(owningView: View, myView: View): void;
2502}
2503
2504/**
2505* An optional interface describing the bind convention.
2506*/
2507interface ComponentBind {
2508 /**
2509 * Implement this hook if you want to perform custom logic when databinding is activated on the view and view-model.
2510 * The "binding context" to which the component is being bound will be passed first.
2511 * An "override context" will be passed second. The override context contains information used to traverse
2512 * the parent hierarchy and can also be used to add any contextual properties that the component wants to add.
2513 */
2514 bind(bindingContext: any, overrideContext: any): void;
2515}
2516
2517/**
2518* An optional interface describing the attached convention.
2519*/
2520interface ComponentAttached {
2521 /**
2522 * Implement this hook if you want to perform custom logic when the component is attached to the DOM (in document).
2523 */
2524 attached(): void;
2525}
2526
2527/**
2528* An optional interface describing the detached convention.
2529*/
2530interface ComponentDetached {
2531 /**
2532 * Implement this hook if you want to perform custom logic if/when the component is removed from the the DOM.
2533 */
2534 detached(): void;
2535}
2536
2537/**
2538* An optional interface describing the unbind convention.
2539*/
2540interface ComponentUnbind {
2541 /**
2542 * Implement this hook if you want to perform custom logic after the component is detached and unbound.
2543 */
2544 unbind(): void;
2545}
2546
2547/**
2548* An optional interface describing the getViewStrategy convention for dynamic components (used with the compose element or the router).
2549*/
2550interface DynamicComponentGetViewStrategy {
2551 /**
2552 * Implement this hook if you want to provide custom view strategy when this component is used with the compose element or the router.
2553 */
2554 getViewStrategy(): string|ViewStrategy;
2555}
2556
2557function getAnimatableElement(view) {
2558 if (view.animatableElement !== undefined) {
2559 return view.animatableElement;
2560 }
2561
2562 let current = view.firstChild;
2563
2564 while (current && current.nodeType !== 1) {
2565 current = current.nextSibling;
2566 }
2567
2568 if (current && current.nodeType === 1) {
2569 return (view.animatableElement = current.classList.contains('au-animate') ? current : null);
2570 }
2571
2572 return (view.animatableElement = null);
2573}
2574
2575/**
2576* Represents a slot or location within the DOM to which views can be added and removed.
2577* Manages the view lifecycle for its children.
2578*/
2579export class ViewSlot {
2580 /**
2581 * Creates an instance of ViewSlot.
2582 * @param anchor The DOM node which will server as the anchor or container for insertion.
2583 * @param anchorIsContainer Indicates whether the node is a container.
2584 * @param animator The animator that will controll enter/leave transitions for this slot.
2585 */
2586 constructor(anchor: Node, anchorIsContainer: boolean, animator?: Animator = Animator.instance) {
2587 this.anchor = anchor;
2588 this.anchorIsContainer = anchorIsContainer;
2589 this.bindingContext = null;
2590 this.overrideContext = null;
2591 this.animator = animator;
2592 this.children = [];
2593 this.isBound = false;
2594 this.isAttached = false;
2595 this.contentSelectors = null;
2596 anchor.viewSlot = this;
2597 anchor.isContentProjectionSource = false;
2598 }
2599
2600 /**
2601 * Runs the animator against the first animatable element found within the view's fragment
2602 * @param view The view to use when searching for the element.
2603 * @param direction The animation direction enter|leave.
2604 * @returns An animation complete Promise or undefined if no animation was run.
2605 */
2606 animateView(view: View, direction: string = 'enter'): void | Promise<any> {
2607 let animatableElement = getAnimatableElement(view);
2608
2609 if (animatableElement !== null) {
2610 switch (direction) {
2611 case 'enter':
2612 return this.animator.enter(animatableElement);
2613 case 'leave':
2614 return this.animator.leave(animatableElement);
2615 default:
2616 throw new Error('Invalid animation direction: ' + direction);
2617 }
2618 }
2619 }
2620
2621 /**
2622 * Takes the child nodes of an existing element that has been converted into a ViewSlot
2623 * and makes those nodes into a View within the slot.
2624 */
2625 transformChildNodesIntoView(): void {
2626 let parent = this.anchor;
2627
2628 this.children.push({
2629 fragment: parent,
2630 firstChild: parent.firstChild,
2631 lastChild: parent.lastChild,
2632 returnToCache() {},
2633 removeNodes() {
2634 let last;
2635
2636 while (last = parent.lastChild) {
2637 parent.removeChild(last);
2638 }
2639 },
2640 created() {},
2641 bind() {},
2642 unbind() {},
2643 attached() {},
2644 detached() {}
2645 });
2646 }
2647
2648 /**
2649 * Binds the slot and it's children.
2650 * @param bindingContext The binding context to bind to.
2651 * @param overrideContext A secondary binding context that can override the standard context.
2652 */
2653 bind(bindingContext: Object, overrideContext: Object): void {
2654 let i;
2655 let ii;
2656 let children;
2657
2658 if (this.isBound) {
2659 if (this.bindingContext === bindingContext) {
2660 return;
2661 }
2662
2663 this.unbind();
2664 }
2665
2666 this.isBound = true;
2667 this.bindingContext = bindingContext = bindingContext || this.bindingContext;
2668 this.overrideContext = overrideContext = overrideContext || this.overrideContext;
2669
2670 children = this.children;
2671 for (i = 0, ii = children.length; i < ii; ++i) {
2672 children[i].bind(bindingContext, overrideContext, true);
2673 }
2674 }
2675
2676 /**
2677 * Unbinds the slot and its children.
2678 */
2679 unbind(): void {
2680 if (this.isBound) {
2681 let i;
2682 let ii;
2683 let children = this.children;
2684
2685 this.isBound = false;
2686 this.bindingContext = null;
2687 this.overrideContext = null;
2688
2689 for (i = 0, ii = children.length; i < ii; ++i) {
2690 children[i].unbind();
2691 }
2692 }
2693 }
2694
2695 /**
2696 * Adds a view to the slot.
2697 * @param view The view to add.
2698 * @return May return a promise if the view addition triggered an animation.
2699 */
2700 add(view: View): void | Promise<any> {
2701 if (this.anchorIsContainer) {
2702 view.appendNodesTo(this.anchor);
2703 } else {
2704 view.insertNodesBefore(this.anchor);
2705 }
2706
2707 this.children.push(view);
2708
2709 if (this.isAttached) {
2710 view.attached();
2711 return this.animateView(view, 'enter');
2712 }
2713 }
2714
2715 /**
2716 * Inserts a view into the slot.
2717 * @param index The index to insert the view at.
2718 * @param view The view to insert.
2719 * @return May return a promise if the view insertion triggered an animation.
2720 */
2721 insert(index: number, view: View): void | Promise<any> {
2722 let children = this.children;
2723 let length = children.length;
2724
2725 if ((index === 0 && length === 0) || index >= length) {
2726 return this.add(view);
2727 }
2728
2729 view.insertNodesBefore(children[index].firstChild);
2730 children.splice(index, 0, view);
2731
2732 if (this.isAttached) {
2733 view.attached();
2734 return this.animateView(view, 'enter');
2735 }
2736 }
2737
2738 /**
2739 * Moves a view across the slot.
2740 * @param sourceIndex The index the view is currently at.
2741 * @param targetIndex The index to insert the view at.
2742 */
2743 move(sourceIndex, targetIndex) {
2744 if (sourceIndex === targetIndex) {
2745 return;
2746 }
2747
2748 const children = this.children;
2749 const view = children[sourceIndex];
2750
2751 view.removeNodes();
2752 view.insertNodesBefore(children[targetIndex].firstChild);
2753 children.splice(sourceIndex, 1);
2754 children.splice(targetIndex, 0, view);
2755 }
2756
2757 /**
2758 * Removes a view from the slot.
2759 * @param view The view to remove.
2760 * @param returnToCache Should the view be returned to the view cache?
2761 * @param skipAnimation Should the removal animation be skipped?
2762 * @return May return a promise if the view removal triggered an animation.
2763 */
2764 remove(view: View, returnToCache?: boolean, skipAnimation?: boolean): View | Promise<View> {
2765 return this.removeAt(this.children.indexOf(view), returnToCache, skipAnimation);
2766 }
2767
2768 /**
2769 * Removes many views from the slot.
2770 * @param viewsToRemove The array of views to remove.
2771 * @param returnToCache Should the views be returned to the view cache?
2772 * @param skipAnimation Should the removal animation be skipped?
2773 * @return May return a promise if the view removal triggered an animation.
2774 */
2775 removeMany(viewsToRemove: View[], returnToCache?: boolean, skipAnimation?: boolean): void | Promise<void> {
2776 const children = this.children;
2777 let ii = viewsToRemove.length;
2778 let i;
2779 let rmPromises = [];
2780
2781 viewsToRemove.forEach(child => {
2782 if (skipAnimation) {
2783 child.removeNodes();
2784 return;
2785 }
2786
2787 let animation = this.animateView(child, 'leave');
2788 if (animation) {
2789 rmPromises.push(animation.then(() => child.removeNodes()));
2790 } else {
2791 child.removeNodes();
2792 }
2793 });
2794
2795 let removeAction = () => {
2796 if (this.isAttached) {
2797 for (i = 0; i < ii; ++i) {
2798 viewsToRemove[i].detached();
2799 }
2800 }
2801
2802 if (returnToCache) {
2803 for (i = 0; i < ii; ++i) {
2804 viewsToRemove[i].returnToCache();
2805 }
2806 }
2807
2808 for (i = 0; i < ii; ++i) {
2809 const index = children.indexOf(viewsToRemove[i]);
2810 if (index >= 0) {
2811 children.splice(index, 1);
2812 }
2813 }
2814 };
2815
2816 if (rmPromises.length > 0) {
2817 return Promise.all(rmPromises).then(() => removeAction());
2818 }
2819
2820 return removeAction();
2821 }
2822
2823 /**
2824 * Removes a view an a specified index from the slot.
2825 * @param index The index to remove the view at.
2826 * @param returnToCache Should the view be returned to the view cache?
2827 * @param skipAnimation Should the removal animation be skipped?
2828 * @return May return a promise if the view removal triggered an animation.
2829 */
2830 removeAt(index: number, returnToCache?: boolean, skipAnimation?: boolean): View | Promise<View> {
2831 let view = this.children[index];
2832
2833 let removeAction = () => {
2834 index = this.children.indexOf(view);
2835 view.removeNodes();
2836 this.children.splice(index, 1);
2837
2838 if (this.isAttached) {
2839 view.detached();
2840 }
2841
2842 if (returnToCache) {
2843 view.returnToCache();
2844 }
2845
2846 return view;
2847 };
2848
2849 if (!skipAnimation) {
2850 let animation = this.animateView(view, 'leave');
2851 if (animation) {
2852 return animation.then(() => removeAction());
2853 }
2854 }
2855
2856 return removeAction();
2857 }
2858
2859 /**
2860 * Removes all views from the slot.
2861 * @param returnToCache Should the view be returned to the view cache?
2862 * @param skipAnimation Should the removal animation be skipped?
2863 * @return May return a promise if the view removals triggered an animation.
2864 */
2865 removeAll(returnToCache?: boolean, skipAnimation?: boolean): void | Promise<any> {
2866 let children = this.children;
2867 let ii = children.length;
2868 let i;
2869 let rmPromises = [];
2870
2871 children.forEach(child => {
2872 if (skipAnimation) {
2873 child.removeNodes();
2874 return;
2875 }
2876
2877 let animation = this.animateView(child, 'leave');
2878 if (animation) {
2879 rmPromises.push(animation.then(() => child.removeNodes()));
2880 } else {
2881 child.removeNodes();
2882 }
2883 });
2884
2885 let removeAction = () => {
2886 if (this.isAttached) {
2887 for (i = 0; i < ii; ++i) {
2888 children[i].detached();
2889 }
2890 }
2891
2892 if (returnToCache) {
2893 for (i = 0; i < ii; ++i) {
2894 const child = children[i];
2895
2896 if (child) {
2897 child.returnToCache();
2898 }
2899 }
2900 }
2901
2902 this.children = [];
2903 };
2904
2905 if (rmPromises.length > 0) {
2906 return Promise.all(rmPromises).then(() => removeAction());
2907 }
2908
2909 return removeAction();
2910 }
2911
2912 /**
2913 * Triggers the attach for the slot and its children.
2914 */
2915 attached(): void {
2916 let i;
2917 let ii;
2918 let children;
2919 let child;
2920
2921 if (this.isAttached) {
2922 return;
2923 }
2924
2925 this.isAttached = true;
2926
2927 children = this.children;
2928 for (i = 0, ii = children.length; i < ii; ++i) {
2929 child = children[i];
2930 child.attached();
2931 this.animateView(child, 'enter');
2932 }
2933 }
2934
2935 /**
2936 * Triggers the detach for the slot and its children.
2937 */
2938 detached(): void {
2939 let i;
2940 let ii;
2941 let children;
2942
2943 if (this.isAttached) {
2944 this.isAttached = false;
2945 children = this.children;
2946 for (i = 0, ii = children.length; i < ii; ++i) {
2947 children[i].detached();
2948 }
2949 }
2950 }
2951
2952 projectTo(slots: Object): void {
2953 this.projectToSlots = slots;
2954 this.add = this._projectionAdd;
2955 this.insert = this._projectionInsert;
2956 this.move = this._projectionMove;
2957 this.remove = this._projectionRemove;
2958 this.removeAt = this._projectionRemoveAt;
2959 this.removeMany = this._projectionRemoveMany;
2960 this.removeAll = this._projectionRemoveAll;
2961 this.children.forEach(view => ShadowDOM.distributeView(view, slots, this));
2962 }
2963
2964 _projectionAdd(view) {
2965 ShadowDOM.distributeView(view, this.projectToSlots, this);
2966
2967 this.children.push(view);
2968
2969 if (this.isAttached) {
2970 view.attached();
2971 }
2972 }
2973
2974 _projectionInsert(index, view) {
2975 if ((index === 0 && !this.children.length) || index >= this.children.length) {
2976 this.add(view);
2977 } else {
2978 ShadowDOM.distributeView(view, this.projectToSlots, this, index);
2979
2980 this.children.splice(index, 0, view);
2981
2982 if (this.isAttached) {
2983 view.attached();
2984 }
2985 }
2986 }
2987
2988 _projectionMove(sourceIndex, targetIndex) {
2989 if (sourceIndex === targetIndex) {
2990 return;
2991 }
2992
2993 const children = this.children;
2994 const view = children[sourceIndex];
2995
2996 ShadowDOM.undistributeView(view, this.projectToSlots, this);
2997 ShadowDOM.distributeView(view, this.projectToSlots, this, targetIndex);
2998
2999 children.splice(sourceIndex, 1);
3000 children.splice(targetIndex, 0, view);
3001 }
3002
3003 _projectionRemove(view, returnToCache) {
3004 ShadowDOM.undistributeView(view, this.projectToSlots, this);
3005 this.children.splice(this.children.indexOf(view), 1);
3006
3007 if (this.isAttached) {
3008 view.detached();
3009 }
3010 if (returnToCache) {
3011 view.returnToCache();
3012 }
3013 }
3014
3015 _projectionRemoveAt(index, returnToCache) {
3016 let view = this.children[index];
3017
3018 ShadowDOM.undistributeView(view, this.projectToSlots, this);
3019 this.children.splice(index, 1);
3020
3021 if (this.isAttached) {
3022 view.detached();
3023 }
3024 if (returnToCache) {
3025 view.returnToCache();
3026 }
3027 }
3028
3029 _projectionRemoveMany(viewsToRemove, returnToCache?) {
3030 viewsToRemove.forEach(view => this.remove(view, returnToCache));
3031 }
3032
3033 _projectionRemoveAll(returnToCache) {
3034 ShadowDOM.undistributeAll(this.projectToSlots, this);
3035
3036 let children = this.children;
3037 let ii = children.length;
3038
3039 if (this.isAttached) {
3040 for (let i = 0; i < ii; ++i) {
3041 if (returnToCache) {
3042 children[i].returnToCache();
3043 } else {
3044 children[i].detached();
3045 }
3046 }
3047 }
3048
3049 this.children = [];
3050 }
3051}
3052
3053@resolver
3054class ProviderResolver {
3055 get(container, key) {
3056 let id = key.__providerId__;
3057 return id in container ? container[id] : (container[id] = container.invoke(key));
3058 }
3059}
3060
3061let providerResolverInstance = new ProviderResolver();
3062
3063function elementContainerGet(key) {
3064 if (key === DOM.Element) {
3065 return this.element;
3066 }
3067
3068 if (key === BoundViewFactory) {
3069 if (this.boundViewFactory) {
3070 return this.boundViewFactory;
3071 }
3072
3073 let factory = this.instruction.viewFactory;
3074 let partReplacements = this.partReplacements;
3075
3076 if (partReplacements) {
3077 factory = partReplacements[factory.part] || factory;
3078 }
3079
3080 this.boundViewFactory = new BoundViewFactory(this, factory, partReplacements);
3081 return this.boundViewFactory;
3082 }
3083
3084 if (key === ViewSlot) {
3085 if (this.viewSlot === undefined) {
3086 this.viewSlot = new ViewSlot(this.element, this.instruction.anchorIsContainer);
3087 this.element.isContentProjectionSource = this.instruction.lifting;
3088 this.children.push(this.viewSlot);
3089 }
3090
3091 return this.viewSlot;
3092 }
3093
3094 if (key === ElementEvents) {
3095 return this.elementEvents || (this.elementEvents = new ElementEvents(this.element));
3096 }
3097
3098 if (key === CompositionTransaction) {
3099 return this.compositionTransaction || (this.compositionTransaction = this.parent.get(key));
3100 }
3101
3102 if (key === ViewResources) {
3103 return this.viewResources;
3104 }
3105
3106 if (key === TargetInstruction) {
3107 return this.instruction;
3108 }
3109
3110 return this.superGet(key);
3111}
3112
3113function createElementContainer(parent, element, instruction, children, partReplacements, resources) {
3114 let container = parent.createChild();
3115 let providers;
3116 let i;
3117
3118 container.element = element;
3119 container.instruction = instruction;
3120 container.children = children;
3121 container.viewResources = resources;
3122 container.partReplacements = partReplacements;
3123
3124 providers = instruction.providers;
3125 i = providers.length;
3126
3127 while (i--) {
3128 container._resolvers.set(providers[i], providerResolverInstance);
3129 }
3130
3131 container.superGet = container.get;
3132 container.get = elementContainerGet;
3133
3134 return container;
3135}
3136
3137function hasAttribute(name) {
3138 return this._element.hasAttribute(name);
3139}
3140
3141function getAttribute(name) {
3142 return this._element.getAttribute(name);
3143}
3144
3145function setAttribute(name, value) {
3146 this._element.setAttribute(name, value);
3147}
3148
3149function makeElementIntoAnchor(element, elementInstruction) {
3150 let anchor = DOM.createComment('anchor');
3151
3152 if (elementInstruction) {
3153 let firstChild = element.firstChild;
3154
3155 if (firstChild && firstChild.tagName === 'AU-CONTENT') {
3156 anchor.contentElement = firstChild;
3157 }
3158
3159 anchor._element = element;
3160
3161 anchor.hasAttribute = hasAttribute;
3162 anchor.getAttribute = getAttribute;
3163 anchor.setAttribute = setAttribute;
3164 }
3165
3166 DOM.replaceNode(anchor, element);
3167
3168 return anchor;
3169}
3170
3171function applyInstructions(containers, element, instruction, controllers, bindings, children, shadowSlots, partReplacements, resources) {
3172 let behaviorInstructions = instruction.behaviorInstructions;
3173 let expressions = instruction.expressions;
3174 let elementContainer;
3175 let i;
3176 let ii;
3177 let current;
3178 let instance;
3179
3180 if (instruction.contentExpression) {
3181 bindings.push(instruction.contentExpression.createBinding(element.nextSibling));
3182 element.nextSibling.auInterpolationTarget = true;
3183 element.parentNode.removeChild(element);
3184 return;
3185 }
3186
3187 if (instruction.shadowSlot) {
3188 let commentAnchor = DOM.createComment('slot');
3189 let slot;
3190
3191 if (instruction.slotDestination) {
3192 slot = new PassThroughSlot(commentAnchor, instruction.slotName, instruction.slotDestination, instruction.slotFallbackFactory);
3193 } else {
3194 slot = new ShadowSlot(commentAnchor, instruction.slotName, instruction.slotFallbackFactory);
3195 }
3196
3197 DOM.replaceNode(commentAnchor, element);
3198 shadowSlots[instruction.slotName] = slot;
3199 controllers.push(slot);
3200 return;
3201 }
3202
3203 if (behaviorInstructions.length) {
3204 if (!instruction.anchorIsContainer) {
3205 element = makeElementIntoAnchor(element, instruction.elementInstruction);
3206 }
3207
3208 containers[instruction.injectorId] = elementContainer =
3209 createElementContainer(
3210 containers[instruction.parentInjectorId],
3211 element,
3212 instruction,
3213 children,
3214 partReplacements,
3215 resources
3216 );
3217
3218 for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
3219 current = behaviorInstructions[i];
3220 instance = current.type.create(elementContainer, current, element, bindings);
3221 controllers.push(instance);
3222 }
3223 }
3224
3225 for (i = 0, ii = expressions.length; i < ii; ++i) {
3226 bindings.push(expressions[i].createBinding(element));
3227 }
3228}
3229
3230function styleStringToObject(style, target) {
3231 let attributes = style.split(';');
3232 let firstIndexOfColon;
3233 let i;
3234 let current;
3235 let key;
3236 let value;
3237
3238 target = target || {};
3239
3240 for (i = 0; i < attributes.length; i++) {
3241 current = attributes[i];
3242 firstIndexOfColon = current.indexOf(':');
3243 key = current.substring(0, firstIndexOfColon).trim();
3244 value = current.substring(firstIndexOfColon + 1).trim();
3245 target[key] = value;
3246 }
3247
3248 return target;
3249}
3250
3251function styleObjectToString(obj) {
3252 let result = '';
3253
3254 for (let key in obj) {
3255 result += key + ':' + obj[key] + ';';
3256 }
3257
3258 return result;
3259}
3260
3261function applySurrogateInstruction(container, element, instruction, controllers, bindings, children) {
3262 let behaviorInstructions = instruction.behaviorInstructions;
3263 let expressions = instruction.expressions;
3264 let providers = instruction.providers;
3265 let values = instruction.values;
3266 let i;
3267 let ii;
3268 let current;
3269 let instance;
3270 let currentAttributeValue;
3271
3272 i = providers.length;
3273 while (i--) {
3274 container._resolvers.set(providers[i], providerResolverInstance);
3275 }
3276
3277 //apply surrogate attributes
3278 for (let key in values) {
3279 currentAttributeValue = element.getAttribute(key);
3280
3281 if (currentAttributeValue) {
3282 if (key === 'class') {
3283 //merge the surrogate classes
3284 element.setAttribute('class', currentAttributeValue + ' ' + values[key]);
3285 } else if (key === 'style') {
3286 //merge the surrogate styles
3287 let styleObject = styleStringToObject(values[key]);
3288 styleStringToObject(currentAttributeValue, styleObject);
3289 element.setAttribute('style', styleObjectToString(styleObject));
3290 }
3291
3292 //otherwise, do not overwrite the consumer's attribute
3293 } else {
3294 //copy the surrogate attribute
3295 element.setAttribute(key, values[key]);
3296 }
3297 }
3298
3299 //apply surrogate behaviors
3300 if (behaviorInstructions.length) {
3301 for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
3302 current = behaviorInstructions[i];
3303 instance = current.type.create(container, current, element, bindings);
3304
3305 if (instance.contentView) {
3306 children.push(instance.contentView);
3307 }
3308
3309 controllers.push(instance);
3310 }
3311 }
3312
3313 //apply surrogate bindings
3314 for (i = 0, ii = expressions.length; i < ii; ++i) {
3315 bindings.push(expressions[i].createBinding(element));
3316 }
3317}
3318
3319/**
3320* A factory capable of creating View instances, bound to a location within another view hierarchy.
3321*/
3322export class BoundViewFactory {
3323 /**
3324 * Creates an instance of BoundViewFactory.
3325 * @param parentContainer The parent DI container.
3326 * @param viewFactory The internal unbound factory.
3327 * @param partReplacements Part replacement overrides for the internal factory.
3328 */
3329 constructor(parentContainer: Container, viewFactory: ViewFactory, partReplacements?: Object) {
3330 this.parentContainer = parentContainer;
3331 this.viewFactory = viewFactory;
3332 this.factoryCreateInstruction = { partReplacements: partReplacements }; //This is referenced internally in the controller's bind method.
3333 }
3334
3335 /**
3336 * Creates a view or returns one from the internal cache, if available.
3337 * @return The created view.
3338 */
3339 create(): View {
3340 let view = this.viewFactory.create(this.parentContainer.createChild(), this.factoryCreateInstruction);
3341 view._isUserControlled = true;
3342 return view;
3343 }
3344
3345 /**
3346 * Indicates whether this factory is currently using caching.
3347 */
3348 get isCaching() {
3349 return this.viewFactory.isCaching;
3350 }
3351
3352 /**
3353 * Sets the cache size for this factory.
3354 * @param size The number of views to cache or "*" to cache all.
3355 * @param doNotOverrideIfAlreadySet Indicates that setting the cache should not override the setting if previously set.
3356 */
3357 setCacheSize(size: number | string, doNotOverrideIfAlreadySet: boolean): void {
3358 this.viewFactory.setCacheSize(size, doNotOverrideIfAlreadySet);
3359 }
3360
3361 /**
3362 * Gets a cached view if available...
3363 * @return A cached view or null if one isn't available.
3364 */
3365 getCachedView(): View {
3366 return this.viewFactory.getCachedView();
3367 }
3368
3369 /**
3370 * Returns a view to the cache.
3371 * @param view The view to return to the cache if space is available.
3372 */
3373 returnViewToCache(view: View): void {
3374 this.viewFactory.returnViewToCache(view);
3375 }
3376}
3377
3378/**
3379* A factory capable of creating View instances.
3380*/
3381export class ViewFactory {
3382 /**
3383 * Indicates whether this factory is currently using caching.
3384 */
3385 isCaching = false;
3386
3387 /**
3388 * Creates an instance of ViewFactory.
3389 * @param template The document fragment that serves as a template for the view to be created.
3390 * @param instructions The instructions to be applied ot the template during the creation of a view.
3391 * @param resources The resources used to compile this factory.
3392 */
3393 constructor(template: DocumentFragment, instructions: Object, resources: ViewResources) {
3394 this.template = template;
3395 this.instructions = instructions;
3396 this.resources = resources;
3397 this.cacheSize = -1;
3398 this.cache = null;
3399 }
3400
3401 /**
3402 * Sets the cache size for this factory.
3403 * @param size The number of views to cache or "*" to cache all.
3404 * @param doNotOverrideIfAlreadySet Indicates that setting the cache should not override the setting if previously set.
3405 */
3406 setCacheSize(size: number | string, doNotOverrideIfAlreadySet: boolean): void {
3407 if (size) {
3408 if (size === '*') {
3409 size = Number.MAX_VALUE;
3410 } else if (typeof size === 'string') {
3411 size = parseInt(size, 10);
3412 }
3413 }
3414
3415 if (this.cacheSize === -1 || !doNotOverrideIfAlreadySet) {
3416 this.cacheSize = size;
3417 }
3418
3419 if (this.cacheSize > 0) {
3420 this.cache = [];
3421 } else {
3422 this.cache = null;
3423 }
3424
3425 this.isCaching = this.cacheSize > 0;
3426 }
3427
3428 /**
3429 * Gets a cached view if available...
3430 * @return A cached view or null if one isn't available.
3431 */
3432 getCachedView(): View {
3433 return this.cache !== null ? (this.cache.pop() || null) : null;
3434 }
3435
3436 /**
3437 * Returns a view to the cache.
3438 * @param view The view to return to the cache if space is available.
3439 */
3440 returnViewToCache(view: View): void {
3441 if (view.isAttached) {
3442 view.detached();
3443 }
3444
3445 if (view.isBound) {
3446 view.unbind();
3447 }
3448
3449 if (this.cache !== null && this.cache.length < this.cacheSize) {
3450 view.fromCache = true;
3451 this.cache.push(view);
3452 }
3453 }
3454
3455 /**
3456 * Creates a view or returns one from the internal cache, if available.
3457 * @param container The container to create the view from.
3458 * @param createInstruction The instruction used to customize view creation.
3459 * @param element The custom element that hosts the view.
3460 * @return The created view.
3461 */
3462 create(container: Container, createInstruction?: ViewCreateInstruction, element?: Element): View {
3463 createInstruction = createInstruction || BehaviorInstruction.normal;
3464
3465 let cachedView = this.getCachedView();
3466 if (cachedView !== null) {
3467 return cachedView;
3468 }
3469
3470 let fragment = createInstruction.enhance ? this.template : this.template.cloneNode(true);
3471 let instructables = fragment.querySelectorAll('.au-target');
3472 let instructions = this.instructions;
3473 let resources = this.resources;
3474 let controllers = [];
3475 let bindings = [];
3476 let children = [];
3477 let shadowSlots = Object.create(null);
3478 let containers = { root: container };
3479 let partReplacements = createInstruction.partReplacements;
3480 let i;
3481 let ii;
3482 let view;
3483 let instructable;
3484 let instruction;
3485
3486 this.resources._invokeHook('beforeCreate', this, container, fragment, createInstruction);
3487
3488 if (element && this.surrogateInstruction !== null) {
3489 applySurrogateInstruction(container, element, this.surrogateInstruction, controllers, bindings, children);
3490 }
3491
3492 if (createInstruction.enhance && fragment.hasAttribute('au-target-id')) {
3493 instructable = fragment;
3494 instruction = instructions[instructable.getAttribute('au-target-id')];
3495 applyInstructions(containers, instructable, instruction, controllers, bindings, children, shadowSlots, partReplacements, resources);
3496 }
3497
3498 for (i = 0, ii = instructables.length; i < ii; ++i) {
3499 instructable = instructables[i];
3500 instruction = instructions[instructable.getAttribute('au-target-id')];
3501 applyInstructions(containers, instructable, instruction, controllers, bindings, children, shadowSlots, partReplacements, resources);
3502 }
3503
3504 view = new View(container, this, fragment, controllers, bindings, children, shadowSlots);
3505
3506 //if iniated by an element behavior, let the behavior trigger this callback once it's done creating the element
3507 if (!createInstruction.initiatedByBehavior) {
3508 view.created();
3509 }
3510
3511 this.resources._invokeHook('afterCreate', view);
3512
3513 return view;
3514 }
3515}
3516
3517let nextInjectorId = 0;
3518function getNextInjectorId() {
3519 return ++nextInjectorId;
3520}
3521
3522let lastAUTargetID = 0;
3523function getNextAUTargetID() {
3524 return (++lastAUTargetID).toString();
3525}
3526
3527function makeIntoInstructionTarget(element) {
3528 let value = element.getAttribute('class');
3529 let auTargetID = getNextAUTargetID();
3530
3531 element.setAttribute('class', (value ? value + ' au-target' : 'au-target'));
3532 element.setAttribute('au-target-id', auTargetID);
3533
3534 return auTargetID;
3535}
3536
3537function makeShadowSlot(compiler, resources, node, instructions, parentInjectorId) {
3538 let auShadowSlot = DOM.createElement('au-shadow-slot');
3539 DOM.replaceNode(auShadowSlot, node);
3540
3541 let auTargetID = makeIntoInstructionTarget(auShadowSlot);
3542 let instruction = TargetInstruction.shadowSlot(parentInjectorId);
3543
3544 instruction.slotName = node.getAttribute('name') || ShadowDOM.defaultSlotKey;
3545 instruction.slotDestination = node.getAttribute('slot');
3546
3547 if (node.innerHTML.trim()) {
3548 let fragment = DOM.createDocumentFragment();
3549 let child;
3550
3551 while (child = node.firstChild) {
3552 fragment.appendChild(child);
3553 }
3554
3555 instruction.slotFallbackFactory = compiler.compile(fragment, resources);
3556 }
3557
3558 instructions[auTargetID] = instruction;
3559
3560 return auShadowSlot;
3561}
3562
3563/**
3564* Compiles html templates, dom fragments and strings into ViewFactory instances, capable of instantiating Views.
3565*/
3566@inject(BindingLanguage, ViewResources)
3567export class ViewCompiler {
3568 /**
3569 * Creates an instance of ViewCompiler.
3570 * @param bindingLanguage The default data binding language and syntax used during view compilation.
3571 * @param resources The global resources used during compilation when none are provided for compilation.
3572 */
3573 constructor(bindingLanguage: BindingLanguage, resources: ViewResources) {
3574 this.bindingLanguage = bindingLanguage;
3575 this.resources = resources;
3576 }
3577
3578 /**
3579 * Compiles an html template, dom fragment or string into ViewFactory instances, capable of instantiating Views.
3580 * @param source The template, fragment or string to compile.
3581 * @param resources The view resources used during compilation.
3582 * @param compileInstruction A set of instructions that customize how compilation occurs.
3583 * @return The compiled ViewFactory.
3584 */
3585 compile(source: Element|DocumentFragment|string, resources?: ViewResources, compileInstruction?: ViewCompileInstruction): ViewFactory {
3586 resources = resources || this.resources;
3587 compileInstruction = compileInstruction || ViewCompileInstruction.normal;
3588 source = typeof source === 'string' ? DOM.createTemplateFromMarkup(source) : source;
3589
3590 let content;
3591 let part;
3592 let cacheSize;
3593
3594 if (source.content) {
3595 part = source.getAttribute('part');
3596 cacheSize = source.getAttribute('view-cache');
3597 content = DOM.adoptNode(source.content);
3598 } else {
3599 content = source;
3600 }
3601
3602 compileInstruction.targetShadowDOM = compileInstruction.targetShadowDOM && FEATURE.shadowDOM;
3603 resources._invokeHook('beforeCompile', content, resources, compileInstruction);
3604
3605 let instructions = {};
3606 this._compileNode(content, resources, instructions, source, 'root', !compileInstruction.targetShadowDOM);
3607
3608 let firstChild = content.firstChild;
3609 if (firstChild && firstChild.nodeType === 1) {
3610 let targetId = firstChild.getAttribute('au-target-id');
3611 if (targetId) {
3612 let ins = instructions[targetId];
3613
3614 if (ins.shadowSlot || ins.lifting || (ins.elementInstruction && !ins.elementInstruction.anchorIsContainer)) {
3615 content.insertBefore(DOM.createComment('view'), firstChild);
3616 }
3617 }
3618 }
3619
3620 let factory = new ViewFactory(content, instructions, resources);
3621
3622 factory.surrogateInstruction = compileInstruction.compileSurrogate ? this._compileSurrogate(source, resources) : null;
3623 factory.part = part;
3624
3625 if (cacheSize) {
3626 factory.setCacheSize(cacheSize);
3627 }
3628
3629 resources._invokeHook('afterCompile', factory);
3630
3631 return factory;
3632 }
3633
3634 _compileNode(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM) {
3635 switch (node.nodeType) {
3636 case 1: //element node
3637 return this._compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM);
3638 case 3: //text node
3639 //use wholeText to retrieve the textContent of all adjacent text nodes.
3640 let expression = resources.getBindingLanguage(this.bindingLanguage).inspectTextContent(resources, node.wholeText);
3641 if (expression) {
3642 let marker = DOM.createElement('au-marker');
3643 let auTargetID = makeIntoInstructionTarget(marker);
3644 (node.parentNode || parentNode).insertBefore(marker, node);
3645 node.textContent = ' ';
3646 instructions[auTargetID] = TargetInstruction.contentExpression(expression);
3647 //remove adjacent text nodes.
3648 while (node.nextSibling && node.nextSibling.nodeType === 3) {
3649 (node.parentNode || parentNode).removeChild(node.nextSibling);
3650 }
3651 } else {
3652 //skip parsing adjacent text nodes.
3653 while (node.nextSibling && node.nextSibling.nodeType === 3) {
3654 node = node.nextSibling;
3655 }
3656 }
3657 return node.nextSibling;
3658 case 11: //document fragment node
3659 let currentChild = node.firstChild;
3660 while (currentChild) {
3661 currentChild = this._compileNode(currentChild, resources, instructions, node, parentInjectorId, targetLightDOM);
3662 }
3663 break;
3664 default:
3665 break;
3666 }
3667
3668 return node.nextSibling;
3669 }
3670
3671 _compileSurrogate(node, resources) {
3672 let tagName = node.tagName.toLowerCase();
3673 let attributes = node.attributes;
3674 let bindingLanguage = resources.getBindingLanguage(this.bindingLanguage);
3675 let knownAttribute;
3676 let property;
3677 let instruction;
3678 let i;
3679 let ii;
3680 let attr;
3681 let attrName;
3682 let attrValue;
3683 let info;
3684 let type;
3685 let expressions = [];
3686 let expression;
3687 let behaviorInstructions = [];
3688 let values = {};
3689 let hasValues = false;
3690 let providers = [];
3691
3692 for (i = 0, ii = attributes.length; i < ii; ++i) {
3693 attr = attributes[i];
3694 attrName = attr.name;
3695 attrValue = attr.value;
3696
3697 info = bindingLanguage.inspectAttribute(resources, tagName, attrName, attrValue);
3698 type = resources.getAttribute(info.attrName);
3699
3700 if (type) { //do we have an attached behavior?
3701 knownAttribute = resources.mapAttribute(info.attrName); //map the local name to real name
3702 if (knownAttribute) {
3703 property = type.attributes[knownAttribute];
3704
3705 if (property) { //if there's a defined property
3706 info.defaultBindingMode = property.defaultBindingMode; //set the default binding mode
3707
3708 if (!info.command && !info.expression) { // if there is no command or detected expression
3709 info.command = property.hasOptions ? 'options' : null; //and it is an optons property, set the options command
3710 }
3711
3712 // if the attribute itself is bound to a default attribute value then we have to
3713 // associate the attribute value with the name of the default bindable property
3714 // (otherwise it will remain associated with "value")
3715 if (info.command && (info.command !== 'options') && type.primaryProperty) {
3716 const primaryProperty = type.primaryProperty;
3717 attrName = info.attrName = primaryProperty.attribute;
3718 // note that the defaultBindingMode always overrides the attribute bindingMode which is only used for "single-value" custom attributes
3719 // when using the syntax `<div square.bind="color"></div>`
3720 info.defaultBindingMode = primaryProperty.defaultBindingMode;
3721 }
3722 }
3723 }
3724 }
3725
3726 instruction = bindingLanguage.createAttributeInstruction(resources, node, info, undefined, type);
3727
3728 if (instruction) { //HAS BINDINGS
3729 if (instruction.alteredAttr) {
3730 type = resources.getAttribute(instruction.attrName);
3731 }
3732
3733 if (instruction.discrete) { //ref binding or listener binding
3734 expressions.push(instruction);
3735 } else { //attribute bindings
3736 if (type) { //templator or attached behavior found
3737 instruction.type = type;
3738 this._configureProperties(instruction, resources);
3739
3740 if (type.liftsContent) { //template controller
3741 throw new Error('You cannot place a template controller on a surrogate element.');
3742 } else { //attached behavior
3743 behaviorInstructions.push(instruction);
3744 }
3745 } else { //standard attribute binding
3746 expressions.push(instruction.attributes[instruction.attrName]);
3747 }
3748 }
3749 } else { //NO BINDINGS
3750 if (type) { //templator or attached behavior found
3751 instruction = BehaviorInstruction.attribute(attrName, type);
3752 instruction.attributes[resources.mapAttribute(attrName)] = attrValue;
3753
3754 if (type.liftsContent) { //template controller
3755 throw new Error('You cannot place a template controller on a surrogate element.');
3756 } else { //attached behavior
3757 behaviorInstructions.push(instruction);
3758 }
3759 } else if (attrName !== 'id' && attrName !== 'part' && attrName !== 'replace-part') {
3760 hasValues = true;
3761 values[attrName] = attrValue;
3762 }
3763 }
3764 }
3765
3766 if (expressions.length || behaviorInstructions.length || hasValues) {
3767 for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
3768 instruction = behaviorInstructions[i];
3769 instruction.type.compile(this, resources, node, instruction);
3770 providers.push(instruction.type.target);
3771 }
3772
3773 for (i = 0, ii = expressions.length; i < ii; ++i) {
3774 expression = expressions[i];
3775 if (expression.attrToRemove !== undefined) {
3776 node.removeAttribute(expression.attrToRemove);
3777 }
3778 }
3779
3780 return TargetInstruction.surrogate(providers, behaviorInstructions, expressions, values);
3781 }
3782
3783 return null;
3784 }
3785
3786 _compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM) {
3787 let tagName = node.tagName.toLowerCase();
3788 let attributes = node.attributes;
3789 let expressions = [];
3790 let expression;
3791 let behaviorInstructions = [];
3792 let providers = [];
3793 let bindingLanguage = resources.getBindingLanguage(this.bindingLanguage);
3794 let liftingInstruction;
3795 let viewFactory;
3796 let type;
3797 let elementInstruction;
3798 let elementProperty;
3799 let i;
3800 let ii;
3801 let attr;
3802 let attrName;
3803 let attrValue;
3804 let originalAttrName;
3805 let instruction;
3806 let info;
3807 let property;
3808 let knownAttribute;
3809 let auTargetID;
3810 let injectorId;
3811
3812 if (tagName === 'slot') {
3813 if (targetLightDOM) {
3814 node = makeShadowSlot(this, resources, node, instructions, parentInjectorId);
3815 }
3816 return node.nextSibling;
3817 } else if (tagName === 'template') {
3818 if (!('content' in node)) {
3819 throw new Error('You cannot place a template element within ' + node.namespaceURI + ' namespace');
3820 }
3821 viewFactory = this.compile(node, resources);
3822 viewFactory.part = node.getAttribute('part');
3823 } else {
3824 type = resources.getElement(node.getAttribute('as-element') || tagName);
3825 if (type) {
3826 elementInstruction = BehaviorInstruction.element(node, type);
3827 type.processAttributes(this, resources, node, attributes, elementInstruction);
3828 behaviorInstructions.push(elementInstruction);
3829 }
3830 }
3831
3832 for (i = 0, ii = attributes.length; i < ii; ++i) {
3833 attr = attributes[i];
3834 originalAttrName = attrName = attr.name;
3835 attrValue = attr.value;
3836 info = bindingLanguage.inspectAttribute(resources, tagName, attrName, attrValue);
3837
3838 if (targetLightDOM && info.attrName === 'slot') {
3839 info.attrName = attrName = 'au-slot';
3840 }
3841
3842 type = resources.getAttribute(info.attrName);
3843 elementProperty = null;
3844
3845 if (type) { //do we have an attached behavior?
3846 knownAttribute = resources.mapAttribute(info.attrName); //map the local name to real name
3847 if (knownAttribute) {
3848 property = type.attributes[knownAttribute];
3849
3850 if (property) { //if there's a defined property
3851 info.defaultBindingMode = property.defaultBindingMode; //set the default binding mode
3852
3853 if (!info.command && !info.expression) { // if there is no command or detected expression
3854 info.command = property.hasOptions ? 'options' : null; //and it is an optons property, set the options command
3855 }
3856
3857 // if the attribute itself is bound to a default attribute value then we have to
3858 // associate the attribute value with the name of the default bindable property
3859 // (otherwise it will remain associated with "value")
3860 if (info.command && (info.command !== 'options') && type.primaryProperty) {
3861 const primaryProperty = type.primaryProperty;
3862 attrName = info.attrName = primaryProperty.attribute;
3863 // note that the defaultBindingMode always overrides the attribute bindingMode which is only used for "single-value" custom attributes
3864 // when using the syntax `<div square.bind="color"></div>`
3865 info.defaultBindingMode = primaryProperty.defaultBindingMode;
3866 }
3867 }
3868 }
3869 } else if (elementInstruction) { //or if this is on a custom element
3870 elementProperty = elementInstruction.type.attributes[info.attrName];
3871 if (elementProperty) { //and this attribute is a custom property
3872 info.defaultBindingMode = elementProperty.defaultBindingMode; //set the default binding mode
3873 }
3874 }
3875
3876 if (elementProperty) {
3877 instruction = bindingLanguage.createAttributeInstruction(resources, node, info, elementInstruction);
3878 } else {
3879 instruction = bindingLanguage.createAttributeInstruction(resources, node, info, undefined, type);
3880 }
3881
3882 if (instruction) { //HAS BINDINGS
3883 if (instruction.alteredAttr) {
3884 type = resources.getAttribute(instruction.attrName);
3885 }
3886
3887 if (instruction.discrete) { //ref binding or listener binding
3888 expressions.push(instruction);
3889 } else { //attribute bindings
3890 if (type) { //templator or attached behavior found
3891 instruction.type = type;
3892 this._configureProperties(instruction, resources);
3893
3894 if (type.liftsContent) { //template controller
3895 instruction.originalAttrName = originalAttrName;
3896 liftingInstruction = instruction;
3897 break;
3898 } else { //attached behavior
3899 behaviorInstructions.push(instruction);
3900 }
3901 } else if (elementProperty) { //custom element attribute
3902 elementInstruction.attributes[info.attrName].targetProperty = elementProperty.name;
3903 } else { //standard attribute binding
3904 expressions.push(instruction.attributes[instruction.attrName]);
3905 }
3906 }
3907 } else { //NO BINDINGS
3908 if (type) { //templator or attached behavior found
3909 instruction = BehaviorInstruction.attribute(attrName, type);
3910 instruction.attributes[resources.mapAttribute(attrName)] = attrValue;
3911
3912 if (type.liftsContent) { //template controller
3913 instruction.originalAttrName = originalAttrName;
3914 liftingInstruction = instruction;
3915 break;
3916 } else { //attached behavior
3917 behaviorInstructions.push(instruction);
3918 }
3919 } else if (elementProperty) { //custom element attribute
3920 elementInstruction.attributes[attrName] = attrValue;
3921 }
3922
3923 //else; normal attribute; do nothing
3924 }
3925 }
3926
3927 if (liftingInstruction) {
3928 liftingInstruction.viewFactory = viewFactory;
3929 node = liftingInstruction.type.compile(this, resources, node, liftingInstruction, parentNode);
3930 auTargetID = makeIntoInstructionTarget(node);
3931 instructions[auTargetID] = TargetInstruction.lifting(parentInjectorId, liftingInstruction);
3932 } else {
3933 let skipContentProcessing = false;
3934
3935 if (expressions.length || behaviorInstructions.length) {
3936 injectorId = behaviorInstructions.length ? getNextInjectorId() : false;
3937
3938 for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
3939 instruction = behaviorInstructions[i];
3940 instruction.type.compile(this, resources, node, instruction, parentNode);
3941 providers.push(instruction.type.target);
3942 skipContentProcessing = skipContentProcessing || instruction.skipContentProcessing;
3943 }
3944
3945 for (i = 0, ii = expressions.length; i < ii; ++i) {
3946 expression = expressions[i];
3947 if (expression.attrToRemove !== undefined) {
3948 node.removeAttribute(expression.attrToRemove);
3949 }
3950 }
3951
3952 auTargetID = makeIntoInstructionTarget(node);
3953 instructions[auTargetID] = TargetInstruction.normal(
3954 injectorId,
3955 parentInjectorId,
3956 providers,
3957 behaviorInstructions,
3958 expressions,
3959 elementInstruction
3960 );
3961 }
3962
3963 if (skipContentProcessing) {
3964 return node.nextSibling;
3965 }
3966
3967 let currentChild = node.firstChild;
3968 while (currentChild) {
3969 currentChild = this._compileNode(currentChild, resources, instructions, node, injectorId || parentInjectorId, targetLightDOM);
3970 }
3971 }
3972
3973 return node.nextSibling;
3974 }
3975
3976 _configureProperties(instruction, resources) {
3977 let type = instruction.type;
3978 let attrName = instruction.attrName;
3979 let attributes = instruction.attributes;
3980 let property;
3981 let key;
3982 let value;
3983
3984 let knownAttribute = resources.mapAttribute(attrName);
3985 if (knownAttribute && attrName in attributes && knownAttribute !== attrName) {
3986 attributes[knownAttribute] = attributes[attrName];
3987 delete attributes[attrName];
3988 }
3989
3990 for (key in attributes) {
3991 value = attributes[key];
3992
3993 if (value !== null && typeof value === 'object') {
3994 property = type.attributes[key];
3995
3996 if (property !== undefined) {
3997 value.targetProperty = property.name;
3998 } else {
3999 value.targetProperty = key;
4000 }
4001 }
4002 }
4003 }
4004}
4005
4006/**
4007* Represents a module with view resources.
4008*/
4009export class ResourceModule {
4010 /**
4011 * Creates an instance of ResourceModule.
4012 * @param moduleId The id of the module that contains view resources.
4013 */
4014 constructor(moduleId: string) {
4015 this.id = moduleId;
4016 this.moduleInstance = null;
4017 this.mainResource = null;
4018 this.resources = null;
4019 this.viewStrategy = null;
4020 this.isInitialized = false;
4021 this.onLoaded = null;
4022 this.loadContext = null;
4023 }
4024
4025 /**
4026 * Initializes the resources within the module.
4027 * @param container The dependency injection container usable during resource initialization.
4028 */
4029 initialize(container: Container): void {
4030 let current = this.mainResource;
4031 let resources = this.resources;
4032 let vs = this.viewStrategy;
4033
4034 if (this.isInitialized) {
4035 return;
4036 }
4037
4038 this.isInitialized = true;
4039
4040 if (current !== undefined) {
4041 current.metadata.viewStrategy = vs;
4042 current.initialize(container);
4043 }
4044
4045 for (let i = 0, ii = resources.length; i < ii; ++i) {
4046 current = resources[i];
4047 current.metadata.viewStrategy = vs;
4048 current.initialize(container);
4049 }
4050 }
4051
4052 /**
4053 * Registers the resources in the module with the view resources.
4054 * @param registry The registry of view resources to regiser within.
4055 * @param name The name to use in registering the default resource.
4056 */
4057 register(registry:ViewResources, name?:string): void {
4058 let main = this.mainResource;
4059 let resources = this.resources;
4060
4061 if (main !== undefined) {
4062 main.register(registry, name);
4063 name = null;
4064 }
4065
4066 for (let i = 0, ii = resources.length; i < ii; ++i) {
4067 resources[i].register(registry, name);
4068 name = null;
4069 }
4070 }
4071
4072 /**
4073 * Loads any dependencies of the resources within this module.
4074 * @param container The DI container to use during dependency resolution.
4075 * @param loadContext The loading context used for loading all resources and dependencies.
4076 * @return A promise that resolves when all loading is complete.
4077 */
4078 load(container: Container, loadContext?: ResourceLoadContext): Promise<void> {
4079 if (this.onLoaded !== null) {
4080 //if it's trying to load the same thing again during the same load, this is a circular dep, so just resolve
4081 return this.loadContext === loadContext ? Promise.resolve() : this.onLoaded;
4082 }
4083
4084 let main = this.mainResource;
4085 let resources = this.resources;
4086 let loads;
4087
4088 if (main !== undefined) {
4089 loads = new Array(resources.length + 1);
4090 loads[0] = main.load(container, loadContext);
4091 for (let i = 0, ii = resources.length; i < ii; ++i) {
4092 loads[i + 1] = resources[i].load(container, loadContext);
4093 }
4094 } else {
4095 loads = new Array(resources.length);
4096 for (let i = 0, ii = resources.length; i < ii; ++i) {
4097 loads[i] = resources[i].load(container, loadContext);
4098 }
4099 }
4100
4101 this.loadContext = loadContext;
4102 this.onLoaded = Promise.all(loads);
4103 return this.onLoaded;
4104 }
4105}
4106
4107/**
4108* Represents a single view resource with a ResourceModule.
4109*/
4110export class ResourceDescription {
4111 /**
4112 * Creates an instance of ResourceDescription.
4113 * @param key The key that the resource was exported as.
4114 * @param exportedValue The exported resource.
4115 * @param resourceTypeMeta The metadata located on the resource.
4116 */
4117 constructor(key: string, exportedValue: any, resourceTypeMeta?: Object) {
4118 if (!resourceTypeMeta) {
4119 resourceTypeMeta = metadata.get(metadata.resource, exportedValue);
4120
4121 if (!resourceTypeMeta) {
4122 resourceTypeMeta = new HtmlBehaviorResource();
4123 resourceTypeMeta.elementName = _hyphenate(key);
4124 metadata.define(metadata.resource, resourceTypeMeta, exportedValue);
4125 }
4126 }
4127
4128 if (resourceTypeMeta instanceof HtmlBehaviorResource) {
4129 if (resourceTypeMeta.elementName === undefined) {
4130 //customeElement()
4131 resourceTypeMeta.elementName = _hyphenate(key);
4132 } else if (resourceTypeMeta.attributeName === undefined) {
4133 //customAttribute()
4134 resourceTypeMeta.attributeName = _hyphenate(key);
4135 } else if (resourceTypeMeta.attributeName === null && resourceTypeMeta.elementName === null) {
4136 //no customeElement or customAttribute but behavior added by other metadata
4137 HtmlBehaviorResource.convention(key, resourceTypeMeta);
4138 }
4139 } else if (!resourceTypeMeta.name) {
4140 resourceTypeMeta.name = _hyphenate(key);
4141 }
4142
4143 this.metadata = resourceTypeMeta;
4144 this.value = exportedValue;
4145 }
4146
4147 /**
4148 * Initializes the resource.
4149 * @param container The dependency injection container usable during resource initialization.
4150 */
4151 initialize(container: Container): void {
4152 this.metadata.initialize(container, this.value);
4153 }
4154
4155 /**
4156 * Registrers the resource with the view resources.
4157 * @param registry The registry of view resources to regiser within.
4158 * @param name The name to use in registering the resource.
4159 */
4160 register(registry: ViewResources, name?: string): void {
4161 this.metadata.register(registry, name);
4162 }
4163
4164 /**
4165 * Loads any dependencies of the resource.
4166 * @param container The DI container to use during dependency resolution.
4167 * @param loadContext The loading context used for loading all resources and dependencies.
4168 * @return A promise that resolves when all loading is complete.
4169 */
4170 load(container: Container, loadContext?: ResourceLoadContext): Promise<void> | void {
4171 return this.metadata.load(container, this.value, loadContext);
4172 }
4173}
4174
4175/**
4176* Analyzes a module in order to discover the view resources that it exports.
4177*/
4178export class ModuleAnalyzer {
4179 /**
4180 * Creates an instance of ModuleAnalyzer.
4181 */
4182 constructor() {
4183 this.cache = Object.create(null);
4184 }
4185
4186 /**
4187 * Retrieves the ResourceModule analysis for a previously analyzed module.
4188 * @param moduleId The id of the module to lookup.
4189 * @return The ResouceModule if found, undefined otherwise.
4190 */
4191 getAnalysis(moduleId: string): ResourceModule {
4192 return this.cache[moduleId];
4193 }
4194
4195 /**
4196 * Analyzes a module.
4197 * @param moduleId The id of the module to analyze.
4198 * @param moduleInstance The module instance to analyze.
4199 * @param mainResourceKey The name of the main resource.
4200 * @return The ResouceModule representing the analysis.
4201 */
4202 analyze(moduleId: string, moduleInstance: any, mainResourceKey?: string): ResourceModule {
4203 let mainResource;
4204 let fallbackValue;
4205 let fallbackKey;
4206 let resourceTypeMeta;
4207 let key;
4208 let exportedValue;
4209 let resources = [];
4210 let conventional;
4211 let vs;
4212 let resourceModule;
4213
4214 resourceModule = this.cache[moduleId];
4215 if (resourceModule) {
4216 return resourceModule;
4217 }
4218
4219 resourceModule = new ResourceModule(moduleId);
4220 this.cache[moduleId] = resourceModule;
4221
4222 if (typeof moduleInstance === 'function') {
4223 moduleInstance = {'default': moduleInstance};
4224 }
4225
4226 if (mainResourceKey) {
4227 mainResource = new ResourceDescription(mainResourceKey, moduleInstance[mainResourceKey]);
4228 }
4229
4230 for (key in moduleInstance) {
4231 exportedValue = moduleInstance[key];
4232
4233 if (key === mainResourceKey || typeof exportedValue !== 'function') {
4234 continue;
4235 }
4236
4237 // This is an unexpected behavior for inheritance as it will walk through the whole prototype chain
4238 // to look for metadata. Should be `getOwn` instead. Though it's subjected to a breaking changes change
4239 resourceTypeMeta = metadata.get(metadata.resource, exportedValue);
4240
4241 if (resourceTypeMeta) {
4242 if (resourceTypeMeta instanceof HtmlBehaviorResource) {
4243 // first used static resource
4244 ViewResources.convention(exportedValue, resourceTypeMeta);
4245
4246 if (resourceTypeMeta.attributeName === null && resourceTypeMeta.elementName === null) {
4247 //no customeElement or customAttribute but behavior added by other metadata
4248 HtmlBehaviorResource.convention(key, resourceTypeMeta);
4249 }
4250
4251 if (resourceTypeMeta.attributeName === null && resourceTypeMeta.elementName === null) {
4252 //no convention and no customeElement or customAttribute but behavior added by other metadata
4253 resourceTypeMeta.elementName = _hyphenate(key);
4254 }
4255 }
4256
4257 if (!mainResource && resourceTypeMeta instanceof HtmlBehaviorResource && resourceTypeMeta.elementName !== null) {
4258 mainResource = new ResourceDescription(key, exportedValue, resourceTypeMeta);
4259 } else {
4260 resources.push(new ResourceDescription(key, exportedValue, resourceTypeMeta));
4261 }
4262 } else if (viewStrategy.decorates(exportedValue)) {
4263 vs = exportedValue;
4264 } else if (exportedValue instanceof TemplateRegistryEntry) {
4265 vs = new TemplateRegistryViewStrategy(moduleId, exportedValue);
4266 } else {
4267 if (conventional = ViewResources.convention(exportedValue)) {
4268 if (conventional.elementName !== null && !mainResource) {
4269 mainResource = new ResourceDescription(key, exportedValue, conventional);
4270 } else {
4271 resources.push(new ResourceDescription(key, exportedValue, conventional));
4272 }
4273 metadata.define(metadata.resource, conventional, exportedValue);
4274 } else if (conventional = HtmlBehaviorResource.convention(key)) {
4275 if (conventional.elementName !== null && !mainResource) {
4276 mainResource = new ResourceDescription(key, exportedValue, conventional);
4277 } else {
4278 resources.push(new ResourceDescription(key, exportedValue, conventional));
4279 }
4280
4281 metadata.define(metadata.resource, conventional, exportedValue);
4282 } else if (conventional = ValueConverterResource.convention(key)
4283 || BindingBehaviorResource.convention(key)
4284 || ViewEngineHooksResource.convention(key)) {
4285 resources.push(new ResourceDescription(key, exportedValue, conventional));
4286 metadata.define(metadata.resource, conventional, exportedValue);
4287 } else if (!fallbackValue) {
4288 fallbackValue = exportedValue;
4289 fallbackKey = key;
4290 }
4291 }
4292 }
4293
4294 if (!mainResource && fallbackValue) {
4295 mainResource = new ResourceDescription(fallbackKey, fallbackValue);
4296 }
4297
4298 resourceModule.moduleInstance = moduleInstance;
4299 resourceModule.mainResource = mainResource;
4300 resourceModule.resources = resources;
4301 resourceModule.viewStrategy = vs;
4302
4303 return resourceModule;
4304 }
4305}
4306
4307let logger = LogManager.getLogger('templating');
4308
4309function ensureRegistryEntry(loader, urlOrRegistryEntry) {
4310 if (urlOrRegistryEntry instanceof TemplateRegistryEntry) {
4311 return Promise.resolve(urlOrRegistryEntry);
4312 }
4313
4314 return loader.loadTemplate(urlOrRegistryEntry);
4315}
4316
4317class ProxyViewFactory {
4318 constructor(promise) {
4319 promise.then(x => this.viewFactory = x);
4320 }
4321
4322 create(container: Container, bindingContext?: Object, createInstruction?: ViewCreateInstruction, element?: Element): View {
4323 return this.viewFactory.create(container, bindingContext, createInstruction, element);
4324 }
4325
4326 get isCaching() {
4327 return this.viewFactory.isCaching;
4328 }
4329
4330 setCacheSize(size: number | string, doNotOverrideIfAlreadySet: boolean): void {
4331 this.viewFactory.setCacheSize(size, doNotOverrideIfAlreadySet);
4332 }
4333
4334 getCachedView(): View {
4335 return this.viewFactory.getCachedView();
4336 }
4337
4338 returnViewToCache(view: View): void {
4339 this.viewFactory.returnViewToCache(view);
4340 }
4341}
4342
4343let auSlotBehavior = null;
4344
4345/**
4346* Controls the view resource loading pipeline.
4347*/
4348@inject(Loader, Container, ViewCompiler, ModuleAnalyzer, ViewResources)
4349export class ViewEngine {
4350 /**
4351 * The metadata key for storing requires declared in a ViewModel.
4352 */
4353 static viewModelRequireMetadataKey = 'aurelia:view-model-require';
4354
4355 /**
4356 * Creates an instance of ViewEngine.
4357 * @param loader The module loader.
4358 * @param container The root DI container for the app.
4359 * @param viewCompiler The view compiler.
4360 * @param moduleAnalyzer The module analyzer.
4361 * @param appResources The app-level global resources.
4362 */
4363 constructor(loader: Loader, container: Container, viewCompiler: ViewCompiler, moduleAnalyzer: ModuleAnalyzer, appResources: ViewResources) {
4364 this.loader = loader;
4365 this.container = container;
4366 this.viewCompiler = viewCompiler;
4367 this.moduleAnalyzer = moduleAnalyzer;
4368 this.appResources = appResources;
4369 this._pluginMap = {};
4370
4371 if (auSlotBehavior === null) {
4372 auSlotBehavior = new HtmlBehaviorResource();
4373 auSlotBehavior.attributeName = 'au-slot';
4374 metadata.define(metadata.resource, auSlotBehavior, SlotCustomAttribute);
4375 }
4376
4377 auSlotBehavior.initialize(container, SlotCustomAttribute);
4378 auSlotBehavior.register(appResources);
4379 }
4380
4381 /**
4382 * Adds a resource plugin to the resource loading pipeline.
4383 * @param extension The file extension to match in require elements.
4384 * @param implementation The plugin implementation that handles the resource type.
4385 */
4386 addResourcePlugin(extension: string, implementation: Object): void {
4387 let name = extension.replace('.', '') + '-resource-plugin';
4388 this._pluginMap[extension] = name;
4389 this.loader.addPlugin(name, implementation);
4390 }
4391
4392 /**
4393 * Loads and compiles a ViewFactory from a url or template registry entry.
4394 * @param urlOrRegistryEntry A url or template registry entry to generate the view factory for.
4395 * @param compileInstruction Instructions detailing how the factory should be compiled.
4396 * @param loadContext The load context if this factory load is happening within the context of a larger load operation.
4397 * @param target A class from which to extract metadata of additional resources to load.
4398 * @return A promise for the compiled view factory.
4399 */
4400 loadViewFactory(urlOrRegistryEntry: string|TemplateRegistryEntry, compileInstruction?: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
4401 loadContext = loadContext || new ResourceLoadContext();
4402
4403 return ensureRegistryEntry(this.loader, urlOrRegistryEntry).then(registryEntry => {
4404 const url = registryEntry.address;
4405
4406 if (registryEntry.onReady) {
4407 if (!loadContext.hasDependency(url)) {
4408 loadContext.addDependency(url);
4409 return registryEntry.onReady;
4410 }
4411
4412 if (registryEntry.template === null) {
4413 // handle NoViewStrategy:
4414 return registryEntry.onReady;
4415 }
4416
4417 return Promise.resolve(new ProxyViewFactory(registryEntry.onReady));
4418 }
4419
4420 loadContext.addDependency(url);
4421
4422 registryEntry.onReady = this.loadTemplateResources(registryEntry, compileInstruction, loadContext, target).then(resources => {
4423 registryEntry.resources = resources;
4424
4425 if (registryEntry.template === null) {
4426 // handle NoViewStrategy:
4427 return registryEntry.factory = null;
4428 }
4429
4430 let viewFactory = this.viewCompiler.compile(registryEntry.template, resources, compileInstruction);
4431 return registryEntry.factory = viewFactory;
4432 });
4433
4434 return registryEntry.onReady;
4435 });
4436 }
4437
4438 /**
4439 * Loads all the resources specified by the registry entry.
4440 * @param registryEntry The template registry entry to load the resources for.
4441 * @param compileInstruction The compile instruction associated with the load.
4442 * @param loadContext The load context if this is happening within the context of a larger load operation.
4443 * @param target A class from which to extract metadata of additional resources to load.
4444 * @return A promise of ViewResources for the registry entry.
4445 */
4446 loadTemplateResources(registryEntry: TemplateRegistryEntry, compileInstruction?: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewResources> {
4447 let resources = new ViewResources(this.appResources, registryEntry.address);
4448 let dependencies = registryEntry.dependencies;
4449 let importIds;
4450 let names;
4451
4452 compileInstruction = compileInstruction || ViewCompileInstruction.normal;
4453
4454 if (dependencies.length === 0 && !compileInstruction.associatedModuleId) {
4455 return Promise.resolve(resources);
4456 }
4457
4458 importIds = dependencies.map(x => x.src);
4459 names = dependencies.map(x => x.name);
4460 logger.debug(`importing resources for ${registryEntry.address}`, importIds);
4461
4462 if (target) {
4463 let viewModelRequires = metadata.get(ViewEngine.viewModelRequireMetadataKey, target);
4464 if (viewModelRequires) {
4465 let templateImportCount = importIds.length;
4466 for (let i = 0, ii = viewModelRequires.length; i < ii; ++i) {
4467 let req = viewModelRequires[i];
4468 let importId = typeof req === 'function' ? Origin.get(req).moduleId : relativeToFile(req.src || req, registryEntry.address);
4469
4470 if (importIds.indexOf(importId) === -1) {
4471 importIds.push(importId);
4472 names.push(req.as);
4473 }
4474 }
4475 logger.debug(`importing ViewModel resources for ${compileInstruction.associatedModuleId}`, importIds.slice(templateImportCount));
4476 }
4477 }
4478
4479 return this.importViewResources(importIds, names, resources, compileInstruction, loadContext);
4480 }
4481
4482 /**
4483 * Loads a view model as a resource.
4484 * @param moduleImport The module to import.
4485 * @param moduleMember The export from the module to generate the resource for.
4486 * @return A promise for the ResourceDescription.
4487 */
4488 importViewModelResource(moduleImport: string, moduleMember: string): Promise<ResourceDescription> {
4489 return this.loader.loadModule(moduleImport).then(viewModelModule => {
4490 let normalizedId = Origin.get(viewModelModule).moduleId;
4491 let resourceModule = this.moduleAnalyzer.analyze(normalizedId, viewModelModule, moduleMember);
4492
4493 if (!resourceModule.mainResource) {
4494 throw new Error(`No view model found in module "${moduleImport}".`);
4495 }
4496
4497 resourceModule.initialize(this.container);
4498
4499 return resourceModule.mainResource;
4500 });
4501 }
4502
4503 /**
4504 * Imports the specified resources with the specified names into the view resources object.
4505 * @param moduleIds The modules to load.
4506 * @param names The names associated with resource modules to import.
4507 * @param resources The resources lookup to add the loaded resources to.
4508 * @param compileInstruction The compilation instruction associated with the resource imports.
4509 * @return A promise for the ViewResources.
4510 */
4511 importViewResources(moduleIds: string[], names: string[], resources: ViewResources, compileInstruction?: ViewCompileInstruction, loadContext?: ResourceLoadContext): Promise<ViewResources> {
4512 loadContext = loadContext || new ResourceLoadContext();
4513 compileInstruction = compileInstruction || ViewCompileInstruction.normal;
4514
4515 moduleIds = moduleIds.map(x => this._applyLoaderPlugin(x));
4516
4517 return this.loader.loadAllModules(moduleIds).then(imports => {
4518 let i;
4519 let ii;
4520 let analysis;
4521 let normalizedId;
4522 let current;
4523 let associatedModule;
4524 let container = this.container;
4525 let moduleAnalyzer = this.moduleAnalyzer;
4526 let allAnalysis = new Array(imports.length);
4527
4528 //initialize and register all resources first
4529 //this enables circular references for global refs
4530 //and enables order independence
4531 for (i = 0, ii = imports.length; i < ii; ++i) {
4532 current = imports[i];
4533 normalizedId = Origin.get(current).moduleId;
4534
4535 analysis = moduleAnalyzer.analyze(normalizedId, current);
4536 analysis.initialize(container);
4537 analysis.register(resources, names[i]);
4538
4539 allAnalysis[i] = analysis;
4540 }
4541
4542 if (compileInstruction.associatedModuleId) {
4543 associatedModule = moduleAnalyzer.getAnalysis(compileInstruction.associatedModuleId);
4544
4545 if (associatedModule) {
4546 associatedModule.register(resources);
4547 }
4548 }
4549
4550 //cause compile/load of any associated views second
4551 //as a result all globals have access to all other globals during compilation
4552 for (i = 0, ii = allAnalysis.length; i < ii; ++i) {
4553 allAnalysis[i] = allAnalysis[i].load(container, loadContext);
4554 }
4555
4556 return Promise.all(allAnalysis).then(() => resources);
4557 });
4558 }
4559
4560 _applyLoaderPlugin(id) {
4561 let index = id.lastIndexOf('.');
4562 if (index !== -1) {
4563 let ext = id.substring(index);
4564 let pluginName = this._pluginMap[ext];
4565
4566 if (pluginName === undefined) {
4567 return id;
4568 }
4569
4570 return this.loader.applyPluginToUrl(id, pluginName);
4571 }
4572
4573 return id;
4574 }
4575}
4576
4577/**
4578* Controls a view model (and optionally its view), according to a particular behavior and by following a set of instructions.
4579*/
4580export class Controller {
4581 /**
4582 * The HtmlBehaviorResource that provides the base behavior for this controller.
4583 */
4584 behavior: HtmlBehaviorResource;
4585
4586 /**
4587 * The developer's view model instance which provides the custom behavior for this controller.
4588 */
4589 viewModel: Object;
4590
4591 /**
4592 * The view associated with the component being controlled by this controller.
4593 * Note: Not all components will have a view, so the value may be null.
4594 */
4595 view: View;
4596
4597 /**
4598 * Creates an instance of Controller.
4599 * @param behavior The HtmlBehaviorResource that provides the base behavior for this controller.
4600 * @param instruction The instructions pertaining to the controller's behavior.
4601 * @param viewModel The developer's view model instance which provides the custom behavior for this controller.
4602 * @param container The container that the controller's view was created from.
4603 */
4604 constructor(behavior: HtmlBehaviorResource, instruction: BehaviorInstruction, viewModel: Object, container: Container) {
4605 this.behavior = behavior;
4606 this.instruction = instruction;
4607 this.viewModel = viewModel;
4608 this.isAttached = false;
4609 this.view = null;
4610 this.isBound = false;
4611 this.scope = null;
4612 this.container = container;
4613 this.elementEvents = container.elementEvents || null;
4614
4615 let observerLookup = behavior.observerLocator.getOrCreateObserversLookup(viewModel);
4616 let handlesBind = behavior.handlesBind;
4617 let attributes = instruction.attributes;
4618 let boundProperties = this.boundProperties = [];
4619 let properties = behavior.properties;
4620 let i;
4621 let ii;
4622
4623 behavior._ensurePropertiesDefined(viewModel, observerLookup);
4624
4625 for (i = 0, ii = properties.length; i < ii; ++i) {
4626 properties[i]._initialize(viewModel, observerLookup, attributes, handlesBind, boundProperties);
4627 }
4628 }
4629
4630 /**
4631 * Invoked when the view which contains this controller is created.
4632 * @param owningView The view inside which this controller resides.
4633 */
4634 created(owningView: View): void {
4635 if (this.behavior.handlesCreated) {
4636 this.viewModel.created(owningView, this.view);
4637 }
4638 }
4639
4640 /**
4641 * Used to automate the proper binding of this controller and its view. Used by the composition engine for dynamic component creation.
4642 * This should be considered a semi-private API and is subject to change without notice, even across minor or patch releases.
4643 * @param overrideContext An override context for binding.
4644 * @param owningView The view inside which this controller resides.
4645 */
4646 automate(overrideContext?: Object, owningView?: View): void {
4647 this.view.bindingContext = this.viewModel;
4648 this.view.overrideContext = overrideContext || createOverrideContext(this.viewModel);
4649 this.view._isUserControlled = true;
4650
4651 if (this.behavior.handlesCreated) {
4652 this.viewModel.created(owningView || null, this.view);
4653 }
4654
4655 this.bind(this.view);
4656 }
4657
4658 /**
4659 * Binds the controller to the scope.
4660 * @param scope The binding scope.
4661 */
4662 bind(scope: Object): void {
4663 let skipSelfSubscriber = this.behavior.handlesBind;
4664 let boundProperties = this.boundProperties;
4665 let i;
4666 let ii;
4667 let x;
4668 let observer;
4669 let selfSubscriber;
4670
4671 if (this.isBound) {
4672 if (this.scope === scope) {
4673 return;
4674 }
4675
4676 this.unbind();
4677 }
4678
4679 this.isBound = true;
4680 this.scope = scope;
4681
4682 for (i = 0, ii = boundProperties.length; i < ii; ++i) {
4683 x = boundProperties[i];
4684 observer = x.observer;
4685 selfSubscriber = observer.selfSubscriber;
4686 observer.publishing = false;
4687
4688 if (skipSelfSubscriber) {
4689 observer.selfSubscriber = null;
4690 }
4691
4692 x.binding.bind(scope);
4693 observer.call();
4694
4695 observer.publishing = true;
4696 observer.selfSubscriber = selfSubscriber;
4697 }
4698
4699 let overrideContext;
4700 if (this.view !== null) {
4701 if (skipSelfSubscriber) {
4702 this.view.viewModelScope = scope;
4703 }
4704 // do we need to create an overrideContext or is the scope's overrideContext
4705 // valid for this viewModel?
4706 if (this.viewModel === scope.overrideContext.bindingContext) {
4707 overrideContext = scope.overrideContext;
4708 // should we inherit the parent scope? (eg compose / router)
4709 } else if (this.instruction.inheritBindingContext) {
4710 overrideContext = createOverrideContext(this.viewModel, scope.overrideContext);
4711 // create the overrideContext and capture the parent without making it
4712 // available to AccessScope. We may need it later for template-part replacements.
4713 } else {
4714 overrideContext = createOverrideContext(this.viewModel);
4715 overrideContext.__parentOverrideContext = scope.overrideContext;
4716 }
4717
4718 this.view.bind(this.viewModel, overrideContext);
4719 } else if (skipSelfSubscriber) {
4720 overrideContext = scope.overrideContext;
4721 // the factoryCreateInstruction's partReplacements will either be null or an object
4722 // containing the replacements. If there are partReplacements we need to preserve the parent
4723 // context to allow replacement parts to bind to both the custom element scope and the ambient scope.
4724 // Note that factoryCreateInstruction is a property defined on BoundViewFactory. The code below assumes the
4725 // behavior stores a the BoundViewFactory on its viewModel under the name of viewFactory. This is implemented
4726 // by the replaceable custom attribute.
4727 if (scope.overrideContext.__parentOverrideContext !== undefined
4728 && this.viewModel.viewFactory && this.viewModel.viewFactory.factoryCreateInstruction.partReplacements) {
4729 // clone the overrideContext and connect the ambient context.
4730 overrideContext = Object.assign({}, scope.overrideContext);
4731 overrideContext.parentOverrideContext = scope.overrideContext.__parentOverrideContext;
4732 }
4733 this.viewModel.bind(scope.bindingContext, overrideContext);
4734 }
4735 }
4736
4737 /**
4738 * Unbinds the controller.
4739 */
4740 unbind(): void {
4741 if (this.isBound) {
4742 let boundProperties = this.boundProperties;
4743 let i;
4744 let ii;
4745
4746 this.isBound = false;
4747 this.scope = null;
4748
4749 if (this.view !== null) {
4750 this.view.unbind();
4751 }
4752
4753 if (this.behavior.handlesUnbind) {
4754 this.viewModel.unbind();
4755 }
4756
4757 if (this.elementEvents !== null) {
4758 this.elementEvents.disposeAll();
4759 }
4760
4761 for (i = 0, ii = boundProperties.length; i < ii; ++i) {
4762 boundProperties[i].binding.unbind();
4763 }
4764 }
4765 }
4766
4767 /**
4768 * Attaches the controller.
4769 */
4770 attached(): void {
4771 if (this.isAttached) {
4772 return;
4773 }
4774
4775 this.isAttached = true;
4776
4777 if (this.behavior.handlesAttached) {
4778 this.viewModel.attached();
4779 }
4780
4781 if (this.view !== null) {
4782 this.view.attached();
4783 }
4784 }
4785
4786 /**
4787 * Detaches the controller.
4788 */
4789 detached(): void {
4790 if (this.isAttached) {
4791 this.isAttached = false;
4792
4793 if (this.view !== null) {
4794 this.view.detached();
4795 }
4796
4797 if (this.behavior.handlesDetached) {
4798 this.viewModel.detached();
4799 }
4800 }
4801 }
4802}
4803
4804/**
4805* An implementation of Aurelia's Observer interface that is used to back bindable properties defined on a behavior.
4806*/
4807@subscriberCollection()
4808export class BehaviorPropertyObserver {
4809 /**
4810 * Creates an instance of BehaviorPropertyObserver.
4811 * @param taskQueue The task queue used to schedule change notifications.
4812 * @param obj The object that the property is defined on.
4813 * @param propertyName The name of the property.
4814 * @param selfSubscriber The callback function that notifies the object which defines the properties, if present.
4815 * @param initialValue The initial value of the property.
4816 */
4817 constructor(taskQueue: TaskQueue, obj: Object, propertyName: string, selfSubscriber: Function, initialValue: any) {
4818 this.taskQueue = taskQueue;
4819 this.obj = obj;
4820 this.propertyName = propertyName;
4821 this.notqueued = true;
4822 this.publishing = false;
4823 this.selfSubscriber = selfSubscriber;
4824 this.currentValue = this.oldValue = initialValue;
4825 }
4826
4827 /**
4828 * Gets the property's value.
4829 */
4830 getValue(): any {
4831 return this.currentValue;
4832 }
4833
4834 /**
4835 * Sets the property's value.
4836 * @param newValue The new value to set.
4837 */
4838 setValue(newValue: any): void {
4839 let oldValue = this.currentValue;
4840
4841 if (!Object.is(newValue, oldValue)) {
4842 this.oldValue = oldValue;
4843 this.currentValue = newValue;
4844
4845 if (this.publishing && this.notqueued) {
4846 if (this.taskQueue.flushing) {
4847 this.call();
4848 } else {
4849 this.notqueued = false;
4850 this.taskQueue.queueMicroTask(this);
4851 }
4852 }
4853 }
4854 }
4855
4856 /**
4857 * Invoked by the TaskQueue to publish changes to subscribers.
4858 */
4859 call(): void {
4860 let oldValue = this.oldValue;
4861 let newValue = this.currentValue;
4862
4863 this.notqueued = true;
4864
4865 if (Object.is(newValue, oldValue)) {
4866 return;
4867 }
4868
4869 if (this.selfSubscriber) {
4870 this.selfSubscriber(newValue, oldValue);
4871 }
4872
4873 this.callSubscribers(newValue, oldValue);
4874 this.oldValue = newValue;
4875 }
4876
4877 /**
4878 * Subscribes to the observerable.
4879 * @param context A context object to pass along to the subscriber when it's called.
4880 * @param callable A function or object with a "call" method to be invoked for delivery of changes.
4881 */
4882 subscribe(context: any, callable: Function): void {
4883 this.addSubscriber(context, callable);
4884 }
4885
4886 /**
4887 * Unsubscribes from the observerable.
4888 * @param context The context object originally subscribed with.
4889 * @param callable The callable that was originally subscribed with.
4890 */
4891 unsubscribe(context: any, callable: Function): void {
4892 this.removeSubscriber(context, callable);
4893 }
4894}
4895
4896function getObserver(instance, name) {
4897 let lookup = instance.__observers__;
4898
4899 if (lookup === undefined) {
4900 // We need to lookup the actual behavior for this instance,
4901 // as it might be a derived class (and behavior) rather than
4902 // the class (and behavior) that declared the property calling getObserver().
4903 // This means we can't capture the behavior in property get/set/getObserver and pass it here.
4904 // Note that it's probably for the best, as passing the behavior is an overhead
4905 // that is only useful in the very first call of the first property of the instance.
4906 let ctor = Object.getPrototypeOf(instance).constructor; // Playing safe here, user could have written to instance.constructor.
4907 let behavior = metadata.get(metadata.resource, ctor);
4908 if (!behavior.isInitialized) {
4909 behavior.initialize(Container.instance || new Container(), instance.constructor);
4910 }
4911
4912 lookup = behavior.observerLocator.getOrCreateObserversLookup(instance);
4913 behavior._ensurePropertiesDefined(instance, lookup);
4914 }
4915
4916 return lookup[name];
4917}
4918
4919/**
4920* Represents a bindable property on a behavior.
4921*/
4922export class BindableProperty {
4923 /**
4924 * Creates an instance of BindableProperty.
4925 * @param nameOrConfig The name of the property or a cofiguration object.
4926 */
4927 constructor(nameOrConfig: string | Object) {
4928 if (typeof nameOrConfig === 'string') {
4929 this.name = nameOrConfig;
4930 } else {
4931 Object.assign(this, nameOrConfig);
4932 }
4933
4934 this.attribute = this.attribute || _hyphenate(this.name);
4935 let defaultBindingMode = this.defaultBindingMode;
4936 if (defaultBindingMode === null || defaultBindingMode === undefined) {
4937 this.defaultBindingMode = bindingMode.oneWay;
4938 } else if (typeof defaultBindingMode === 'string') {
4939 // to avoid import from aurelia
4940 this.defaultBindingMode = bindingMode[defaultBindingMode] || bindingMode.oneWay;
4941 }
4942 this.changeHandler = this.changeHandler || null;
4943 this.owner = null;
4944 this.descriptor = null;
4945 }
4946
4947 /**
4948 * Registers this bindable property with particular Class and Behavior instance.
4949 * @param target The class to register this behavior with.
4950 * @param behavior The behavior instance to register this property with.
4951 * @param descriptor The property descriptor for this property.
4952 */
4953 registerWith(target: Function, behavior: HtmlBehaviorResource, descriptor?: Object): void {
4954 behavior.properties.push(this);
4955 behavior.attributes[this.attribute] = this;
4956 this.owner = behavior;
4957
4958 if (descriptor) {
4959 this.descriptor = descriptor;
4960 return this._configureDescriptor(descriptor);
4961 }
4962
4963 return undefined;
4964 }
4965
4966 _configureDescriptor(descriptor: Object): Object {
4967 let name = this.name;
4968
4969 descriptor.configurable = true;
4970 descriptor.enumerable = true;
4971
4972 if ('initializer' in descriptor) {
4973 this.defaultValue = descriptor.initializer;
4974 delete descriptor.initializer;
4975 delete descriptor.writable;
4976 }
4977
4978 if ('value' in descriptor) {
4979 this.defaultValue = descriptor.value;
4980 delete descriptor.value;
4981 delete descriptor.writable;
4982 }
4983
4984 descriptor.get = function() {
4985 return getObserver(this, name).getValue();
4986 };
4987
4988 descriptor.set = function(value) {
4989 getObserver(this, name).setValue(value);
4990 };
4991
4992 descriptor.get.getObserver = function(obj) {
4993 return getObserver(obj, name);
4994 };
4995
4996 return descriptor;
4997 }
4998
4999 /**
5000 * Defines this property on the specified class and behavior.
5001 * @param target The class to define the property on.
5002 * @param behavior The behavior to define the property on.
5003 */
5004 defineOn(target: Function, behavior: HtmlBehaviorResource): void {
5005 let name = this.name;
5006 let handlerName;
5007
5008 if (this.changeHandler === null) {
5009 handlerName = name + 'Changed';
5010 if (handlerName in target.prototype) {
5011 this.changeHandler = handlerName;
5012 }
5013 }
5014
5015 if (this.descriptor === null) {
5016 Object.defineProperty(target.prototype, name, this._configureDescriptor(behavior, {}));
5017 }
5018 }
5019
5020 /**
5021 * Creates an observer for this property.
5022 * @param viewModel The view model instance on which to create the observer.
5023 * @return The property observer.
5024 */
5025 createObserver(viewModel: Object): BehaviorPropertyObserver {
5026 let selfSubscriber = null;
5027 let defaultValue = this.defaultValue;
5028 let changeHandlerName = this.changeHandler;
5029 let name = this.name;
5030 let initialValue;
5031
5032 if (this.hasOptions) {
5033 return undefined;
5034 }
5035
5036 if (changeHandlerName in viewModel) {
5037 if ('propertyChanged' in viewModel) {
5038 selfSubscriber = (newValue, oldValue) => {
5039 viewModel[changeHandlerName](newValue, oldValue);
5040 viewModel.propertyChanged(name, newValue, oldValue);
5041 };
5042 } else {
5043 selfSubscriber = (newValue, oldValue) => viewModel[changeHandlerName](newValue, oldValue);
5044 }
5045 } else if ('propertyChanged' in viewModel) {
5046 selfSubscriber = (newValue, oldValue) => viewModel.propertyChanged(name, newValue, oldValue);
5047 } else if (changeHandlerName !== null) {
5048 throw new Error(`Change handler ${changeHandlerName} was specified but not declared on the class.`);
5049 }
5050
5051 if (defaultValue !== undefined) {
5052 initialValue = typeof defaultValue === 'function' ? defaultValue.call(viewModel) : defaultValue;
5053 }
5054
5055 return new BehaviorPropertyObserver(this.owner.taskQueue, viewModel, this.name, selfSubscriber, initialValue);
5056 }
5057
5058 _initialize(viewModel, observerLookup, attributes, behaviorHandlesBind, boundProperties): void {
5059 let selfSubscriber;
5060 let observer;
5061 let attribute;
5062 let defaultValue = this.defaultValue;
5063
5064 if (this.isDynamic) {
5065 for (let key in attributes) {
5066 this._createDynamicProperty(viewModel, observerLookup, behaviorHandlesBind, key, attributes[key], boundProperties);
5067 }
5068 } else if (!this.hasOptions) {
5069 observer = observerLookup[this.name];
5070
5071 if (attributes !== null) {
5072 selfSubscriber = observer.selfSubscriber;
5073 attribute = attributes[this.attribute];
5074
5075 if (behaviorHandlesBind) {
5076 observer.selfSubscriber = null;
5077 }
5078
5079 if (typeof attribute === 'string') {
5080 viewModel[this.name] = attribute;
5081 observer.call();
5082 } else if (attribute) {
5083 boundProperties.push({observer: observer, binding: attribute.createBinding(viewModel)});
5084 } else if (defaultValue !== undefined) {
5085 observer.call();
5086 }
5087
5088 observer.selfSubscriber = selfSubscriber;
5089 }
5090
5091 observer.publishing = true;
5092 }
5093 }
5094
5095 _createDynamicProperty(viewModel, observerLookup, behaviorHandlesBind, name, attribute, boundProperties) {
5096 let changeHandlerName = name + 'Changed';
5097 let selfSubscriber = null;
5098 let observer;
5099 let info;
5100
5101 if (changeHandlerName in viewModel) {
5102 if ('propertyChanged' in viewModel) {
5103 selfSubscriber = (newValue, oldValue) => {
5104 viewModel[changeHandlerName](newValue, oldValue);
5105 viewModel.propertyChanged(name, newValue, oldValue);
5106 };
5107 } else {
5108 selfSubscriber = (newValue, oldValue) => viewModel[changeHandlerName](newValue, oldValue);
5109 }
5110 } else if ('propertyChanged' in viewModel) {
5111 selfSubscriber = (newValue, oldValue) => viewModel.propertyChanged(name, newValue, oldValue);
5112 }
5113
5114 observer = observerLookup[name] = new BehaviorPropertyObserver(
5115 this.owner.taskQueue,
5116 viewModel,
5117 name,
5118 selfSubscriber
5119 );
5120
5121 Object.defineProperty(viewModel, name, {
5122 configurable: true,
5123 enumerable: true,
5124 get: observer.getValue.bind(observer),
5125 set: observer.setValue.bind(observer)
5126 });
5127
5128 if (behaviorHandlesBind) {
5129 observer.selfSubscriber = null;
5130 }
5131
5132 if (typeof attribute === 'string') {
5133 viewModel[name] = attribute;
5134 observer.call();
5135 } else if (attribute) {
5136 info = {observer: observer, binding: attribute.createBinding(viewModel)};
5137 boundProperties.push(info);
5138 }
5139
5140 observer.publishing = true;
5141 observer.selfSubscriber = selfSubscriber;
5142 }
5143}
5144
5145let lastProviderId = 0;
5146
5147function nextProviderId() {
5148 return ++lastProviderId;
5149}
5150
5151function doProcessContent() { return true; }
5152function doProcessAttributes() {}
5153
5154/**
5155* Identifies a class as a resource that implements custom element or custom
5156* attribute functionality.
5157*/
5158export class HtmlBehaviorResource {
5159 /**
5160 * Creates an instance of HtmlBehaviorResource.
5161 */
5162 constructor() {
5163 this.elementName = null;
5164 this.attributeName = null;
5165 this.attributeDefaultBindingMode = undefined;
5166 this.liftsContent = false;
5167 this.targetShadowDOM = false;
5168 this.shadowDOMOptions = null;
5169 this.processAttributes = doProcessAttributes;
5170 this.processContent = doProcessContent;
5171 this.usesShadowDOM = false;
5172 this.childBindings = null;
5173 this.hasDynamicOptions = false;
5174 this.containerless = false;
5175 this.properties = [];
5176 this.attributes = {};
5177 this.isInitialized = false;
5178 this.primaryProperty = null;
5179 }
5180
5181 /**
5182 * Checks whether the provided name matches any naming conventions for HtmlBehaviorResource.
5183 * @param name The name of the potential resource.
5184 * @param existing An already existing resource that may need a convention name applied.
5185 */
5186 static convention(name: string, existing?: HtmlBehaviorResource): HtmlBehaviorResource {
5187 let behavior;
5188
5189 if (name.endsWith('CustomAttribute')) {
5190 behavior = existing || new HtmlBehaviorResource();
5191 behavior.attributeName = _hyphenate(name.substring(0, name.length - 15));
5192 }
5193
5194 if (name.endsWith('CustomElement')) {
5195 behavior = existing || new HtmlBehaviorResource();
5196 behavior.elementName = _hyphenate(name.substring(0, name.length - 13));
5197 }
5198
5199 return behavior;
5200 }
5201
5202 /**
5203 * Adds a binding expression to the component created by this resource.
5204 * @param behavior The binding expression.
5205 */
5206 addChildBinding(behavior: Object): void {
5207 if (this.childBindings === null) {
5208 this.childBindings = [];
5209 }
5210
5211 this.childBindings.push(behavior);
5212 }
5213
5214 /**
5215 * Provides an opportunity for the resource to initialize iteself.
5216 * @param container The dependency injection container from which the resource
5217 * can aquire needed services.
5218 * @param target The class to which this resource metadata is attached.
5219 */
5220 initialize(container: Container, target: Function): void {
5221 let proto = target.prototype;
5222 let properties = this.properties;
5223 let attributeName = this.attributeName;
5224 let attributeDefaultBindingMode = this.attributeDefaultBindingMode;
5225 let i;
5226 let ii;
5227 let current;
5228
5229 if (this.isInitialized) {
5230 return;
5231 }
5232
5233 this.isInitialized = true;
5234 target.__providerId__ = nextProviderId();
5235
5236 this.observerLocator = container.get(ObserverLocator);
5237 this.taskQueue = container.get(TaskQueue);
5238
5239 this.target = target;
5240 this.usesShadowDOM = this.targetShadowDOM && FEATURE.shadowDOM;
5241 this.handlesCreated = ('created' in proto);
5242 this.handlesBind = ('bind' in proto);
5243 this.handlesUnbind = ('unbind' in proto);
5244 this.handlesAttached = ('attached' in proto);
5245 this.handlesDetached = ('detached' in proto);
5246 this.htmlName = this.elementName || this.attributeName;
5247
5248 if (attributeName !== null) {
5249 if (properties.length === 0) { //default for custom attributes
5250 new BindableProperty({
5251 name: 'value',
5252 changeHandler: 'valueChanged' in proto ? 'valueChanged' : null,
5253 attribute: attributeName,
5254 defaultBindingMode: attributeDefaultBindingMode
5255 }).registerWith(target, this);
5256 }
5257
5258 current = properties[0];
5259
5260 if (properties.length === 1 && current.name === 'value') { //default for custom attributes
5261 current.isDynamic = current.hasOptions = this.hasDynamicOptions;
5262 current.defineOn(target, this);
5263 } else { //custom attribute with options
5264 for (i = 0, ii = properties.length; i < ii; ++i) {
5265 properties[i].defineOn(target, this);
5266 if (properties[i].primaryProperty) {
5267 if (this.primaryProperty) {
5268 throw new Error('Only one bindable property on a custom element can be defined as the default');
5269 }
5270 this.primaryProperty = properties[i];
5271 }
5272 }
5273
5274 current = new BindableProperty({
5275 name: 'value',
5276 changeHandler: 'valueChanged' in proto ? 'valueChanged' : null,
5277 attribute: attributeName,
5278 defaultBindingMode: attributeDefaultBindingMode
5279 });
5280
5281 current.hasOptions = true;
5282 current.registerWith(target, this);
5283 }
5284 } else {
5285 for (i = 0, ii = properties.length; i < ii; ++i) {
5286 properties[i].defineOn(target, this);
5287 }
5288 // Because how inherited properties would interact with the default 'value' property
5289 // in a custom attribute is not well defined yet, we only inherit properties on
5290 // custom elements, where it's not a problem.
5291 this._copyInheritedProperties(container, target);
5292 }
5293 }
5294
5295 /**
5296 * Allows the resource to be registered in the view resources for the particular
5297 * view into which it was required.
5298 * @param registry The view resource registry for the view that required this resource.
5299 * @param name The name provided by the end user for this resource, within the
5300 * particular view it's being used.
5301 */
5302 register(registry: ViewResources, name?: string): void {
5303 if (this.attributeName !== null) {
5304 registry.registerAttribute(name || this.attributeName, this, this.attributeName);
5305
5306 if (Array.isArray(this.aliases)) {
5307 this.aliases
5308 .forEach( (alias) => {
5309 registry.registerAttribute(alias, this, this.attributeName);
5310 });
5311 }
5312 }
5313
5314 if (this.elementName !== null) {
5315 registry.registerElement(name || this.elementName, this);
5316 }
5317 }
5318
5319 /**
5320 * Enables the resource to asynchronously load additional resources.
5321 * @param container The dependency injection container from which the resource
5322 * can aquire needed services.
5323 * @param target The class to which this resource metadata is attached.
5324 * @param loadContext The loading context object provided by the view engine.
5325 * @param viewStrategy A view strategy to overload the default strategy defined by the resource.
5326 * @param transientView Indicated whether the view strategy is transient or
5327 * permanently tied to this component.
5328 */
5329 load(container: Container, target: Function, loadContext?: ResourceLoadContext, viewStrategy?: ViewStrategy, transientView?: boolean): Promise<HtmlBehaviorResource> {
5330 let options;
5331
5332 if (this.elementName !== null) {
5333 viewStrategy = container.get(ViewLocator).getViewStrategy(viewStrategy || this.viewStrategy || target);
5334 options = new ViewCompileInstruction(this.targetShadowDOM, true);
5335
5336 if (!viewStrategy.moduleId) {
5337 viewStrategy.moduleId = Origin.get(target).moduleId;
5338 }
5339
5340 return viewStrategy.loadViewFactory(container.get(ViewEngine), options, loadContext, target).then(viewFactory => {
5341 if (!transientView || !this.viewFactory) {
5342 this.viewFactory = viewFactory;
5343 }
5344
5345 return viewFactory;
5346 });
5347 }
5348
5349 return Promise.resolve(this);
5350 }
5351
5352 /**
5353 * Plugs into the compiler and enables custom processing of the node on which this behavior is located.
5354 * @param compiler The compiler that is currently compiling the view that this behavior exists within.
5355 * @param resources The resources for the view that this behavior exists within.
5356 * @param node The node on which this behavior exists.
5357 * @param instruction The behavior instruction created for this behavior.
5358 * @param parentNode The parent node of the current node.
5359 * @return The current node.
5360 */
5361 compile(compiler: ViewCompiler, resources: ViewResources, node: Node, instruction: BehaviorInstruction, parentNode?: Node): Node {
5362 if (this.liftsContent) {
5363 if (!instruction.viewFactory) {
5364 let template = DOM.createElement('template');
5365 let fragment = DOM.createDocumentFragment();
5366 let cacheSize = node.getAttribute('view-cache');
5367 let part = node.getAttribute('part');
5368
5369 node.removeAttribute(instruction.originalAttrName);
5370 DOM.replaceNode(template, node, parentNode);
5371 fragment.appendChild(node);
5372 instruction.viewFactory = compiler.compile(fragment, resources);
5373
5374 if (part) {
5375 instruction.viewFactory.part = part;
5376 node.removeAttribute('part');
5377 }
5378
5379 if (cacheSize) {
5380 instruction.viewFactory.setCacheSize(cacheSize);
5381 node.removeAttribute('view-cache');
5382 }
5383
5384 node = template;
5385 }
5386 } else if (this.elementName !== null) { //custom element
5387 let partReplacements = {};
5388
5389 if (this.processContent(compiler, resources, node, instruction) && node.hasChildNodes()) {
5390 let currentChild = node.firstChild;
5391 let contentElement = this.usesShadowDOM ? null : DOM.createElement('au-content');
5392 let nextSibling;
5393 let toReplace;
5394
5395 while (currentChild) {
5396 nextSibling = currentChild.nextSibling;
5397
5398 if (currentChild.tagName === 'TEMPLATE' && (toReplace = currentChild.getAttribute('replace-part'))) {
5399 partReplacements[toReplace] = compiler.compile(currentChild, resources);
5400 DOM.removeNode(currentChild, parentNode);
5401 instruction.partReplacements = partReplacements;
5402 } else if (contentElement !== null) {
5403 if (currentChild.nodeType === 3 && _isAllWhitespace(currentChild)) {
5404 DOM.removeNode(currentChild, parentNode);
5405 } else {
5406 contentElement.appendChild(currentChild);
5407 }
5408 }
5409
5410 currentChild = nextSibling;
5411 }
5412
5413 if (contentElement !== null && contentElement.hasChildNodes()) {
5414 node.appendChild(contentElement);
5415 }
5416
5417 instruction.skipContentProcessing = false;
5418 } else {
5419 instruction.skipContentProcessing = true;
5420 }
5421 } else if (!this.processContent(compiler, resources, node, instruction)) {
5422 instruction.skipContentProcessing = true;
5423 }
5424
5425 return node;
5426 }
5427
5428 /**
5429 * Creates an instance of this behavior.
5430 * @param container The DI container to create the instance in.
5431 * @param instruction The instruction for this behavior that was constructed during compilation.
5432 * @param element The element on which this behavior exists.
5433 * @param bindings The bindings that are associated with the view in which this behavior exists.
5434 * @return The Controller of this behavior.
5435 */
5436 create(container: Container, instruction?: BehaviorInstruction, element?: Element, bindings?: Binding[]): Controller {
5437 let viewHost;
5438 let au = null;
5439
5440 instruction = instruction || BehaviorInstruction.normal;
5441 element = element || null;
5442 bindings = bindings || null;
5443
5444 if (this.elementName !== null && element) {
5445 if (this.usesShadowDOM) {
5446 viewHost = element.attachShadow(this.shadowDOMOptions);
5447 container.registerInstance(DOM.boundary, viewHost);
5448 } else {
5449 viewHost = element;
5450 if (this.targetShadowDOM) {
5451 container.registerInstance(DOM.boundary, viewHost);
5452 }
5453 }
5454 }
5455
5456 if (element !== null) {
5457 element.au = au = element.au || {};
5458 }
5459
5460 let viewModel = instruction.viewModel || container.get(this.target);
5461 let controller = new Controller(this, instruction, viewModel, container);
5462 let childBindings = this.childBindings;
5463 let viewFactory;
5464
5465 if (this.liftsContent) {
5466 //template controller
5467 au.controller = controller;
5468 } else if (this.elementName !== null) {
5469 //custom element
5470 viewFactory = instruction.viewFactory || this.viewFactory;
5471 container.viewModel = viewModel;
5472
5473 if (viewFactory) {
5474 controller.view = viewFactory.create(container, instruction, element);
5475 }
5476
5477 if (element !== null) {
5478 au.controller = controller;
5479
5480 if (controller.view) {
5481 if (!this.usesShadowDOM && (element.childNodes.length === 1 || element.contentElement)) { //containerless passes content view special contentElement property
5482 let contentElement = element.childNodes[0] || element.contentElement;
5483 controller.view.contentView = { fragment: contentElement }; //store the content before appending the view
5484 contentElement.parentNode && DOM.removeNode(contentElement); //containerless content element has no parent
5485 }
5486
5487 if (instruction.anchorIsContainer) {
5488 if (childBindings !== null) {
5489 for (let i = 0, ii = childBindings.length; i < ii; ++i) {
5490 controller.view.addBinding(childBindings[i].create(element, viewModel, controller));
5491 }
5492 }
5493
5494 controller.view.appendNodesTo(viewHost);
5495 } else {
5496 controller.view.insertNodesBefore(viewHost);
5497 }
5498 } else if (childBindings !== null) {
5499 for (let i = 0, ii = childBindings.length; i < ii; ++i) {
5500 bindings.push(childBindings[i].create(element, viewModel, controller));
5501 }
5502 }
5503 } else if (controller.view) {
5504 //dynamic element with view
5505 controller.view.controller = controller;
5506
5507 if (childBindings !== null) {
5508 for (let i = 0, ii = childBindings.length; i < ii; ++i) {
5509 controller.view.addBinding(childBindings[i].create(instruction.host, viewModel, controller));
5510 }
5511 }
5512 } else if (childBindings !== null) {
5513 //dynamic element without view
5514 for (let i = 0, ii = childBindings.length; i < ii; ++i) {
5515 bindings.push(childBindings[i].create(instruction.host, viewModel, controller));
5516 }
5517 }
5518 } else if (childBindings !== null) {
5519 //custom attribute
5520 for (let i = 0, ii = childBindings.length; i < ii; ++i) {
5521 bindings.push(childBindings[i].create(element, viewModel, controller));
5522 }
5523 }
5524
5525 if (au !== null) {
5526 au[this.htmlName] = controller;
5527 }
5528
5529 if (instruction.initiatedByBehavior && viewFactory) {
5530 controller.view.created();
5531 }
5532
5533 return controller;
5534 }
5535
5536 _ensurePropertiesDefined(instance: Object, lookup: Object) {
5537 let properties;
5538 let i;
5539 let ii;
5540 let observer;
5541
5542 if ('__propertiesDefined__' in lookup) {
5543 return;
5544 }
5545
5546 lookup.__propertiesDefined__ = true;
5547 properties = this.properties;
5548
5549 for (i = 0, ii = properties.length; i < ii; ++i) {
5550 observer = properties[i].createObserver(instance);
5551
5552 if (observer !== undefined) {
5553 lookup[observer.propertyName] = observer;
5554 }
5555 }
5556 }
5557
5558 _copyInheritedProperties(container: Container, target: Function) {
5559 // This methods enables inherited @bindable properties.
5560 // We look for the first base class with metadata, make sure it's initialized
5561 // and copy its properties.
5562 // We don't need to walk further than the first parent with metadata because
5563 // it had also inherited properties during its own initialization.
5564 let behavior;
5565 let derived = target;
5566
5567 while (true) {
5568 let proto = Object.getPrototypeOf(target.prototype);
5569 target = proto && proto.constructor;
5570 if (!target) {
5571 return;
5572 }
5573 behavior = metadata.getOwn(metadata.resource, target);
5574 if (behavior) {
5575 break;
5576 }
5577 }
5578 behavior.initialize(container, target);
5579 for (let i = 0, ii = behavior.properties.length; i < ii; ++i) {
5580 let prop = behavior.properties[i];
5581 // Check that the property metadata was not overriden or re-defined in this class
5582 if (this.properties.some(p => p.name === prop.name)) {
5583 continue;
5584 }
5585 // We don't need to call .defineOn() for those properties because it was done
5586 // on the parent prototype during initialization.
5587 new BindableProperty(prop).registerWith(derived, this);
5588 }
5589 }
5590}
5591
5592function createChildObserverDecorator(selectorOrConfig, all) {
5593 return function(target, key, descriptor) {
5594 let actualTarget = typeof key === 'string' ? target.constructor : target; //is it on a property or a class?
5595 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, actualTarget);
5596
5597 if (typeof selectorOrConfig === 'string') {
5598 selectorOrConfig = {
5599 selector: selectorOrConfig,
5600 name: key
5601 };
5602 }
5603
5604 if (descriptor) {
5605 descriptor.writable = true;
5606 descriptor.configurable = true;
5607 }
5608
5609 selectorOrConfig.all = all;
5610 r.addChildBinding(new ChildObserver(selectorOrConfig));
5611 };
5612}
5613
5614/**
5615* Creates a behavior property that references an array of immediate content child elements that matches the provided selector.
5616*/
5617export function children(selectorOrConfig: string | Object): any {
5618 return createChildObserverDecorator(selectorOrConfig, true);
5619}
5620
5621/**
5622* Creates a behavior property that references an immediate content child element that matches the provided selector.
5623*/
5624export function child(selectorOrConfig: string | Object): any {
5625 return createChildObserverDecorator(selectorOrConfig, false);
5626}
5627
5628class ChildObserver {
5629 constructor(config) {
5630 this.name = config.name;
5631 this.changeHandler = config.changeHandler || this.name + 'Changed';
5632 this.selector = config.selector;
5633 this.all = config.all;
5634 }
5635
5636 create(viewHost, viewModel, controller) {
5637 return new ChildObserverBinder(this.selector, viewHost, this.name, viewModel, controller, this.changeHandler, this.all);
5638 }
5639}
5640
5641const noMutations = [];
5642
5643function trackMutation(groupedMutations, binder, record) {
5644 let mutations = groupedMutations.get(binder);
5645
5646 if (!mutations) {
5647 mutations = [];
5648 groupedMutations.set(binder, mutations);
5649 }
5650
5651 mutations.push(record);
5652}
5653
5654function onChildChange(mutations, observer) {
5655 let binders = observer.binders;
5656 let bindersLength = binders.length;
5657 let groupedMutations = new Map();
5658
5659 for (let i = 0, ii = mutations.length; i < ii; ++i) {
5660 let record = mutations[i];
5661 let added = record.addedNodes;
5662 let removed = record.removedNodes;
5663
5664 for (let j = 0, jj = removed.length; j < jj; ++j) {
5665 let node = removed[j];
5666 if (node.nodeType === 1) {
5667 for (let k = 0; k < bindersLength; ++k) {
5668 let binder = binders[k];
5669 if (binder.onRemove(node)) {
5670 trackMutation(groupedMutations, binder, record);
5671 }
5672 }
5673 }
5674 }
5675
5676 for (let j = 0, jj = added.length; j < jj; ++j) {
5677 let node = added[j];
5678 if (node.nodeType === 1) {
5679 for (let k = 0; k < bindersLength; ++k) {
5680 let binder = binders[k];
5681 if (binder.onAdd(node)) {
5682 trackMutation(groupedMutations, binder, record);
5683 }
5684 }
5685 }
5686 }
5687 }
5688
5689 groupedMutations.forEach((value, key) => {
5690 if (key.changeHandler !== null) {
5691 key.viewModel[key.changeHandler](value);
5692 }
5693 });
5694}
5695
5696class ChildObserverBinder {
5697 constructor(selector, viewHost, property, viewModel, controller, changeHandler, all) {
5698 this.selector = selector;
5699 this.viewHost = viewHost;
5700 this.property = property;
5701 this.viewModel = viewModel;
5702 this.controller = controller;
5703 this.changeHandler = changeHandler in viewModel ? changeHandler : null;
5704 this.usesShadowDOM = controller.behavior.usesShadowDOM;
5705 this.all = all;
5706
5707 if (!this.usesShadowDOM && controller.view && controller.view.contentView) {
5708 this.contentView = controller.view.contentView;
5709 } else {
5710 this.contentView = null;
5711 }
5712 }
5713
5714 matches(element) {
5715 if (element.matches(this.selector)) {
5716 if (this.contentView === null) {
5717 return true;
5718 }
5719
5720 let contentView = this.contentView;
5721 let assignedSlot = element.auAssignedSlot;
5722
5723 if (assignedSlot && assignedSlot.projectFromAnchors) {
5724 let anchors = assignedSlot.projectFromAnchors;
5725
5726 for (let i = 0, ii = anchors.length; i < ii; ++i) {
5727 if (anchors[i].auOwnerView === contentView) {
5728 return true;
5729 }
5730 }
5731
5732 return false;
5733 }
5734
5735 return element.auOwnerView === contentView;
5736 }
5737
5738 return false;
5739 }
5740
5741 bind(source) {
5742 let viewHost = this.viewHost;
5743 let viewModel = this.viewModel;
5744 let observer = viewHost.__childObserver__;
5745
5746 if (!observer) {
5747 observer = viewHost.__childObserver__ = DOM.createMutationObserver(onChildChange);
5748
5749 let options = {
5750 childList: true,
5751 subtree: !this.usesShadowDOM
5752 };
5753
5754 observer.observe(viewHost, options);
5755 observer.binders = [];
5756 }
5757
5758 observer.binders.push(this);
5759
5760 if (this.usesShadowDOM) { //if using shadow dom, the content is already present, so sync the items
5761 let current = viewHost.firstElementChild;
5762
5763 if (this.all) {
5764 let items = viewModel[this.property];
5765 if (!items) {
5766 items = viewModel[this.property] = [];
5767 } else {
5768 // The existing array may alread be observed in other bindings
5769 // Setting length to 0 will not work properly, unless we intercept it
5770 items.splice(0);
5771 }
5772
5773 while (current) {
5774 if (this.matches(current)) {
5775 items.push(current.au && current.au.controller ? current.au.controller.viewModel : current);
5776 }
5777
5778 current = current.nextElementSibling;
5779 }
5780
5781 if (this.changeHandler !== null) {
5782 this.viewModel[this.changeHandler](noMutations);
5783 }
5784 } else {
5785 while (current) {
5786 if (this.matches(current)) {
5787 let value = current.au && current.au.controller ? current.au.controller.viewModel : current;
5788 this.viewModel[this.property] = value;
5789
5790 if (this.changeHandler !== null) {
5791 this.viewModel[this.changeHandler](value);
5792 }
5793
5794 break;
5795 }
5796
5797 current = current.nextElementSibling;
5798 }
5799 }
5800 }
5801 }
5802
5803 onRemove(element) {
5804 if (this.matches(element)) {
5805 let value = element.au && element.au.controller ? element.au.controller.viewModel : element;
5806
5807 if (this.all) {
5808 let items = (this.viewModel[this.property] || (this.viewModel[this.property] = []));
5809 let index = items.indexOf(value);
5810
5811 if (index !== -1) {
5812 items.splice(index, 1);
5813 }
5814
5815 return true;
5816 }
5817
5818 return false;
5819 }
5820
5821 return false;
5822 }
5823
5824 onAdd(element) {
5825 if (this.matches(element)) {
5826 let value = element.au && element.au.controller ? element.au.controller.viewModel : element;
5827
5828 if (this.all) {
5829 let items = (this.viewModel[this.property] || (this.viewModel[this.property] = []));
5830
5831 if (this.selector === '*') {
5832 items.push(value);
5833 return true;
5834 }
5835
5836 let index = 0;
5837 let prev = element.previousElementSibling;
5838
5839 while (prev) {
5840 if (this.matches(prev)) {
5841 index++;
5842 }
5843
5844 prev = prev.previousElementSibling;
5845 }
5846
5847 items.splice(index, 0, value);
5848 return true;
5849 }
5850
5851 this.viewModel[this.property] = value;
5852
5853 if (this.changeHandler !== null) {
5854 this.viewModel[this.changeHandler](value);
5855 }
5856 }
5857
5858 return false;
5859 }
5860
5861 unbind() {
5862 if (this.viewHost.__childObserver__) {
5863 this.viewHost.__childObserver__.disconnect();
5864 this.viewHost.__childObserver__ = null;
5865 this.viewModel[this.property] = null;
5866 }
5867 }
5868}
5869
5870function remove(viewSlot, previous) {
5871 return Array.isArray(previous)
5872 ? viewSlot.removeMany(previous, true)
5873 : viewSlot.remove(previous, true);
5874}
5875
5876export const SwapStrategies = {
5877 // animate the next view in before removing the current view;
5878 before(viewSlot, previous, callback) {
5879 return (previous === undefined)
5880 ? callback()
5881 : callback().then(() => remove(viewSlot, previous));
5882 },
5883
5884 // animate the next view at the same time the current view is removed
5885 with(viewSlot, previous, callback) {
5886 return (previous === undefined)
5887 ? callback()
5888 : Promise.all([remove(viewSlot, previous), callback()]);
5889 },
5890
5891 // animate the next view in after the current view has been removed
5892 after(viewSlot, previous, callback) {
5893 return Promise.resolve(viewSlot.removeAll(true)).then(callback);
5894 }
5895};
5896
5897/**
5898* Instructs the composition engine how to dynamically compose a component.
5899*/
5900interface CompositionContext {
5901 /**
5902 * The parent Container for the component creation.
5903 */
5904 container: Container;
5905 /**
5906 * The child Container for the component creation. One will be created from the parent if not provided.
5907 */
5908 childContainer?: Container;
5909 /**
5910 * The context in which the view model is executed in.
5911 */
5912 bindingContext: any;
5913 /**
5914 * A secondary binding context that can override the standard context.
5915 */
5916 overrideContext?: any;
5917 /**
5918 * The view model url or instance for the component.
5919 */
5920 viewModel?: any;
5921 /**
5922 * Data to be passed to the "activate" hook on the view model.
5923 */
5924 model?: any;
5925 /**
5926 * The HtmlBehaviorResource for the component.
5927 */
5928 viewModelResource?: HtmlBehaviorResource;
5929 /**
5930 * The view resources for the view in which the component should be created.
5931 */
5932 viewResources: ViewResources;
5933 /**
5934 * The view inside which this composition is happening.
5935 */
5936 owningView?: View;
5937 /**
5938 * The view url or view strategy to override the default view location convention.
5939 */
5940 view?: string | ViewStrategy;
5941 /**
5942 * The slot to push the dynamically composed component into.
5943 */
5944 viewSlot: ViewSlot;
5945 /**
5946 * Should the composition system skip calling the "activate" hook on the view model.
5947 */
5948 skipActivation?: boolean;
5949 /**
5950 * The element that will parent the dynamic component.
5951 * It will be registered in the child container of this composition.
5952 */
5953 host?: Element;
5954}
5955
5956function tryActivateViewModel(context) {
5957 if (context.skipActivation || typeof context.viewModel.activate !== 'function') {
5958 return Promise.resolve();
5959 }
5960
5961 return context.viewModel.activate(context.model) || Promise.resolve();
5962}
5963
5964/**
5965* Used to dynamically compose components.
5966*/
5967@inject(ViewEngine, ViewLocator)
5968export class CompositionEngine {
5969 /**
5970 * Creates an instance of the CompositionEngine.
5971 * @param viewEngine The ViewEngine used during composition.
5972 */
5973 constructor(viewEngine: ViewEngine, viewLocator: ViewLocator) {
5974 this.viewEngine = viewEngine;
5975 this.viewLocator = viewLocator;
5976 }
5977
5978 _swap(context, view) {
5979 let swapStrategy = SwapStrategies[context.swapOrder] || SwapStrategies.after;
5980 let previousViews = context.viewSlot.children.slice();
5981
5982 return swapStrategy(context.viewSlot, previousViews, () => {
5983 return Promise.resolve(context.viewSlot.add(view)).then(() => {
5984 if (context.currentController) {
5985 context.currentController.unbind();
5986 }
5987 });
5988 }).then(() => {
5989 if (context.compositionTransactionNotifier) {
5990 context.compositionTransactionNotifier.done();
5991 }
5992 });
5993 }
5994
5995 _createControllerAndSwap(context) {
5996 return this.createController(context).then(controller => {
5997 if (context.compositionTransactionOwnershipToken) {
5998 return context.compositionTransactionOwnershipToken
5999 .waitForCompositionComplete()
6000 .then(() => {
6001 controller.automate(context.overrideContext, context.owningView);
6002
6003 return this._swap(context, controller.view);
6004 })
6005 .then(() => controller);
6006 }
6007
6008 controller.automate(context.overrideContext, context.owningView);
6009
6010 return this._swap(context, controller.view).then(() => controller);
6011 });
6012 }
6013
6014 /**
6015 * Creates a controller instance for the component described in the context.
6016 * @param context The CompositionContext that describes the component.
6017 * @return A Promise for the Controller.
6018 */
6019 createController(context: CompositionContext): Promise<Controller> {
6020 let childContainer;
6021 let viewModel;
6022 let viewModelResource;
6023 /**@type {HtmlBehaviorResource} */
6024 let m;
6025
6026 return this
6027 .ensureViewModel(context)
6028 .then(tryActivateViewModel)
6029 .then(() => {
6030 childContainer = context.childContainer;
6031 viewModel = context.viewModel;
6032 viewModelResource = context.viewModelResource;
6033 m = viewModelResource.metadata;
6034
6035 let viewStrategy = this.viewLocator.getViewStrategy(context.view || viewModel);
6036
6037 if (context.viewResources) {
6038 viewStrategy.makeRelativeTo(context.viewResources.viewUrl);
6039 }
6040
6041 return m.load(
6042 childContainer,
6043 viewModelResource.value,
6044 null,
6045 viewStrategy,
6046 true
6047 );
6048 }).then(viewFactory => m.create(
6049 childContainer,
6050 BehaviorInstruction.dynamic(context.host, viewModel, viewFactory)
6051 ));
6052 }
6053
6054 /**
6055 * Ensures that the view model and its resource are loaded for this context.
6056 * @param context The CompositionContext to load the view model and its resource for.
6057 * @return A Promise for the context.
6058 */
6059 ensureViewModel(context: CompositionContext): Promise<CompositionContext> {
6060 let childContainer = context.childContainer = (context.childContainer || context.container.createChild());
6061
6062 if (typeof context.viewModel === 'string') {
6063 context.viewModel = context.viewResources
6064 ? context.viewResources.relativeToView(context.viewModel)
6065 : context.viewModel;
6066
6067 return this.viewEngine.importViewModelResource(context.viewModel).then(viewModelResource => {
6068 childContainer.autoRegister(viewModelResource.value);
6069
6070 if (context.host) {
6071 childContainer.registerInstance(DOM.Element, context.host);
6072 }
6073
6074 context.viewModel = childContainer.viewModel = childContainer.get(viewModelResource.value);
6075 context.viewModelResource = viewModelResource;
6076 return context;
6077 });
6078 }
6079 // When viewModel in context is not a module path
6080 // only prepare the metadata and ensure the view model instance is ready
6081 // if viewModel is a class, instantiate it
6082 let ctor = context.viewModel.constructor;
6083 let isClass = typeof context.viewModel === 'function';
6084 if (isClass) {
6085 ctor = context.viewModel;
6086 childContainer.autoRegister(ctor);
6087 }
6088 let m = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, ctor);
6089 // We don't call ViewResources.prototype.convention here as it should be called later
6090 // Just need to prepare the metadata for later usage
6091 m.elementName = m.elementName || 'dynamic-element';
6092 // HtmlBehaviorResource has its own guard to prevent unnecessary subsequent initialization calls
6093 // so it's safe to call initialize this way
6094 m.initialize(isClass ? childContainer : (context.container || childContainer), ctor);
6095 // simulate the metadata of view model, like it was analyzed by module analyzer
6096 // Cannot create a ResourceDescription instance here as it does too much
6097 context.viewModelResource = { metadata: m, value: ctor };
6098 // register the host element in case custom element view model declares it
6099 if (context.host) {
6100 childContainer.registerInstance(DOM.Element, context.host);
6101 }
6102 childContainer.viewModel = context.viewModel = isClass ? childContainer.get(ctor) : context.viewModel;
6103 return Promise.resolve(context);
6104 }
6105
6106 /**
6107 * Dynamically composes a component.
6108 * @param context The CompositionContext providing information on how the composition should occur.
6109 * @return A Promise for the View or the Controller that results from the dynamic composition.
6110 */
6111 compose(context: CompositionContext): Promise<View | Controller> {
6112 context.childContainer = context.childContainer || context.container.createChild();
6113 context.view = this.viewLocator.getViewStrategy(context.view);
6114
6115 let transaction = context.childContainer.get(CompositionTransaction);
6116 let compositionTransactionOwnershipToken = transaction.tryCapture();
6117
6118 if (compositionTransactionOwnershipToken) {
6119 context.compositionTransactionOwnershipToken = compositionTransactionOwnershipToken;
6120 } else {
6121 context.compositionTransactionNotifier = transaction.enlist();
6122 }
6123
6124 if (context.viewModel) {
6125 return this._createControllerAndSwap(context);
6126 } else if (context.view) {
6127 if (context.viewResources) {
6128 context.view.makeRelativeTo(context.viewResources.viewUrl);
6129 }
6130
6131 return context.view.loadViewFactory(this.viewEngine, new ViewCompileInstruction()).then(viewFactory => {
6132 let result = viewFactory.create(context.childContainer);
6133 result.bind(context.bindingContext, context.overrideContext);
6134
6135 if (context.compositionTransactionOwnershipToken) {
6136 return context.compositionTransactionOwnershipToken.waitForCompositionComplete()
6137 .then(() => this._swap(context, result))
6138 .then(() => result);
6139 }
6140
6141 return this._swap(context, result).then(() => result);
6142 });
6143 } else if (context.viewSlot) {
6144 context.viewSlot.removeAll();
6145
6146 if (context.compositionTransactionNotifier) {
6147 context.compositionTransactionNotifier.done();
6148 }
6149
6150 return Promise.resolve(null);
6151 }
6152
6153 return Promise.resolve(null);
6154 }
6155}
6156
6157/**
6158* Identifies a class as a resource that configures the EventManager with information
6159* about how events relate to properties for the purpose of two-way data-binding
6160* to Web Components.
6161*/
6162export class ElementConfigResource {
6163 /**
6164 * Provides an opportunity for the resource to initialize iteself.
6165 * @param container The dependency injection container from which the resource
6166 * can aquire needed services.
6167 * @param target The class to which this resource metadata is attached.
6168 */
6169 initialize(container: Container, target: Function): void {}
6170
6171 /**
6172 * Allows the resource to be registered in the view resources for the particular
6173 * view into which it was required.
6174 * @param registry The view resource registry for the view that required this resource.
6175 * @param name The name provided by the end user for this resource, within the
6176 * particular view it's being used.
6177 */
6178 register(registry: ViewResources, name?: string): void {}
6179
6180 /**
6181 * Enables the resource to asynchronously load additional resources.
6182 * @param container The dependency injection container from which the resource
6183 * can aquire needed services.
6184 * @param target The class to which this resource metadata is attached.
6185 */
6186 load(container: Container, target: Function): void {
6187 let config = new target();
6188 let eventManager = container.get(EventManager);
6189 eventManager.registerElementConfig(config);
6190 }
6191}
6192
6193/**
6194* Decorator: Specifies a resource instance that describes the decorated class.
6195* @param instanceOrConfig The resource instance.
6196*/
6197export function resource(instanceOrConfig: string | object): any {
6198 return function(target) {
6199 let isConfig = typeof instanceOrConfig === 'string' || Object.getPrototypeOf(instanceOrConfig) === Object.prototype;
6200 if (isConfig) {
6201 target.$resource = instanceOrConfig;
6202 } else {
6203 metadata.define(metadata.resource, instanceOrConfig, target);
6204 }
6205 };
6206}
6207
6208/**
6209* Decorator: Specifies a custom HtmlBehaviorResource instance or an object that overrides various implementation details of the default HtmlBehaviorResource.
6210* @param override The customized HtmlBehaviorResource or an object to override the default with.
6211*/
6212export function behavior(override: HtmlBehaviorResource | Object): any {
6213 return function(target) {
6214 if (override instanceof HtmlBehaviorResource) {
6215 metadata.define(metadata.resource, override, target);
6216 } else {
6217 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, target);
6218 Object.assign(r, override);
6219 }
6220 };
6221}
6222
6223/**
6224* Decorator: Indicates that the decorated class is a custom element.
6225* @param name The name of the custom element.
6226*/
6227export function customElement(name: string): any {
6228 return function(target) {
6229 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, target);
6230 r.elementName = validateBehaviorName(name, 'custom element');
6231 };
6232}
6233
6234/**
6235* Decorator: Indicates that the decorated class is a custom attribute.
6236* @param name The name of the custom attribute.
6237* @param defaultBindingMode The default binding mode to use when the attribute is bound with .bind.
6238* @param aliases The array of aliases to associate to the custom attribute.
6239*/
6240export function customAttribute(name: string, defaultBindingMode?: number, aliases?: string[]): any {
6241 return function(target) {
6242 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, target);
6243 r.attributeName = validateBehaviorName(name, 'custom attribute');
6244 r.attributeDefaultBindingMode = defaultBindingMode;
6245 r.aliases = aliases;
6246 };
6247}
6248
6249/**
6250* Decorator: Applied to custom attributes. Indicates that whatever element the
6251* attribute is placed on should be converted into a template and that this
6252* attribute controls the instantiation of the template.
6253*/
6254export function templateController(target?): any {
6255 let deco = function(t) {
6256 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t);
6257 r.liftsContent = true;
6258 };
6259
6260 return target ? deco(target) : deco;
6261}
6262
6263/**
6264* Decorator: Specifies that a property is bindable through HTML.
6265* @param nameOrConfigOrTarget The name of the property, or a configuration object.
6266*/
6267export function bindable(nameOrConfigOrTarget?: string | Object, key?, descriptor?): any {
6268 let deco = function(target, key2, descriptor2) {
6269 let actualTarget = key2 ? target.constructor : target; //is it on a property or a class?
6270 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, actualTarget);
6271 let prop;
6272
6273 if (key2) { //is it on a property or a class?
6274 nameOrConfigOrTarget = nameOrConfigOrTarget || {};
6275 nameOrConfigOrTarget.name = key2;
6276 }
6277
6278 prop = new BindableProperty(nameOrConfigOrTarget);
6279 return prop.registerWith(actualTarget, r, descriptor2);
6280 };
6281
6282 if (!nameOrConfigOrTarget) { //placed on property initializer with parens
6283 return deco;
6284 }
6285
6286 if (key) { //placed on a property initializer without parens
6287 let target = nameOrConfigOrTarget;
6288 nameOrConfigOrTarget = null;
6289 return deco(target, key, descriptor);
6290 }
6291
6292 return deco; //placed on a class
6293}
6294
6295/**
6296* Decorator: Specifies that the decorated custom attribute has options that
6297* are dynamic, based on their presence in HTML and not statically known.
6298*/
6299export function dynamicOptions(target?): any {
6300 let deco = function(t) {
6301 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t);
6302 r.hasDynamicOptions = true;
6303 };
6304
6305 return target ? deco(target) : deco;
6306}
6307
6308const defaultShadowDOMOptions = { mode: 'open' };
6309/**
6310* Decorator: Indicates that the custom element should render its view in Shadow
6311* DOM. This decorator may change slightly when Aurelia updates to Shadow DOM v1.
6312*/
6313export function useShadowDOM(targetOrOptions?): any {
6314 let options = typeof targetOrOptions === 'function' || !targetOrOptions
6315 ? defaultShadowDOMOptions
6316 : targetOrOptions;
6317
6318 let deco = function(t) {
6319 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t);
6320 r.targetShadowDOM = true;
6321 r.shadowDOMOptions = options;
6322 };
6323
6324 return typeof targetOrOptions === 'function' ? deco(targetOrOptions) : deco;
6325}
6326
6327/**
6328* Decorator: Enables custom processing of the attributes on an element before the framework inspects them.
6329* @param processor Pass a function which can provide custom processing of the content.
6330*/
6331export function processAttributes(processor: Function): any {
6332 return function(t) {
6333 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t);
6334 r.processAttributes = function(compiler, resources, node, attributes, elementInstruction) {
6335 try {
6336 processor(compiler, resources, node, attributes, elementInstruction);
6337 } catch (error) {
6338 LogManager.getLogger('templating').error(error);
6339 }
6340 };
6341 };
6342}
6343
6344function doNotProcessContent() { return false; }
6345
6346/**
6347* Decorator: Enables custom processing of the content that is places inside the
6348* custom element by its consumer.
6349* @param processor Pass a boolean to direct the template compiler to not process
6350* the content placed inside this element. Alternatively, pass a function which
6351* can provide custom processing of the content. This function should then return
6352* a boolean indicating whether the compiler should also process the content.
6353*/
6354export function processContent(processor: boolean | Function): any {
6355 return function(t) {
6356 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t);
6357 r.processContent = processor ? function(compiler, resources, node, instruction) {
6358 try {
6359 return processor(compiler, resources, node, instruction);
6360 } catch (error) {
6361 LogManager.getLogger('templating').error(error);
6362 return false;
6363 }
6364 } : doNotProcessContent;
6365 };
6366}
6367
6368/**
6369* Decorator: Indicates that the custom element should be rendered without its
6370* element container.
6371*/
6372export function containerless(target?): any {
6373 let deco = function(t) {
6374 let r = metadata.getOrCreateOwn(metadata.resource, HtmlBehaviorResource, t);
6375 r.containerless = true;
6376 };
6377
6378 return target ? deco(target) : deco;
6379}
6380
6381/**
6382* Decorator: Associates a custom view strategy with the component.
6383* @param strategy The view strategy instance.
6384*/
6385export function useViewStrategy(strategy: Object): any {
6386 return function(target) {
6387 metadata.define(ViewLocator.viewStrategyMetadataKey, strategy, target);
6388 };
6389}
6390
6391/**
6392* Decorator: Provides a relative path to a view for the component.
6393* @param path The path to the view.
6394*/
6395export function useView(path: string): any {
6396 return useViewStrategy(new RelativeViewStrategy(path));
6397}
6398
6399/**
6400* Decorator: Provides a view template, directly inline, for the component. Be
6401* sure to wrap the markup in a template element.
6402* @param markup The markup for the view.
6403* @param dependencies A list of dependencies that the template has.
6404* @param dependencyBaseUrl A base url from which the dependencies will be loaded.
6405*/
6406export function inlineView(markup:string, dependencies?:Array<string|Function|Object>, dependencyBaseUrl?:string): any {
6407 return useViewStrategy(new InlineViewStrategy(markup, dependencies, dependencyBaseUrl));
6408}
6409
6410/**
6411* Decorator: Indicates that the component has no view.
6412*/
6413export function noView(targetOrDependencies?:Function|Array<any>, dependencyBaseUrl?:string): any {
6414 let target;
6415 let dependencies;
6416 if (typeof targetOrDependencies === 'function') {
6417 target = targetOrDependencies;
6418 } else {
6419 dependencies = targetOrDependencies;
6420 target = undefined;
6421 }
6422
6423 let deco = function(t) {
6424 metadata.define(ViewLocator.viewStrategyMetadataKey, new NoViewStrategy(dependencies, dependencyBaseUrl), t);
6425 };
6426
6427 return target ? deco(target) : deco;
6428}
6429
6430interface IStaticViewStrategyConfig {
6431 template: string | HTMLTemplateElement;
6432 dependencies?: Function[] | { (): (Promise<Record<string, Function>> | Function)[] }
6433}
6434
6435/**
6436 * Decorator: Indicates that the element use static view
6437 */
6438export function view(templateOrConfig:string|HTMLTemplateElement|IStaticViewStrategyConfig): any {
6439 return function(target) {
6440 target.$view = templateOrConfig;
6441 };
6442}
6443
6444/**
6445* Decorator: Indicates that the decorated class provides element configuration
6446* to the EventManager for one or more Web Components.
6447*/
6448export function elementConfig(target?): any {
6449 let deco = function(t) {
6450 metadata.define(metadata.resource, new ElementConfigResource(), t);
6451 };
6452
6453 return target ? deco(target) : deco;
6454}
6455
6456/**
6457* Decorator: Provides the ability to add resources to the related View
6458* Same as: <require from="..."></require>
6459* @param resources Either: strings with moduleIds, Objects with 'src' and optionally 'as' properties or one of the classes of the module to be included.
6460*/
6461export function viewResources(...resources) { // eslint-disable-line
6462 return function(target) {
6463 metadata.define(ViewEngine.viewModelRequireMetadataKey, resources, target);
6464 };
6465}
6466
6467/**
6468* Instructs the framework in how to enhance an existing DOM structure.
6469*/
6470interface EnhanceInstruction {
6471 /**
6472 * The DI container to use as the root for UI enhancement.
6473 */
6474 container?: Container;
6475 /**
6476 * The element to enhance.
6477 */
6478 element: Element;
6479 /**
6480 * The resources available for enhancement.
6481 */
6482 resources?: ViewResources;
6483 /**
6484 * A binding context for the enhancement.
6485 */
6486 bindingContext?: Object;
6487 /**
6488 * A secondary binding context that can override the standard context.
6489 */
6490 overrideContext?: any;
6491}
6492
6493/**
6494* A facade of the templating engine capabilties which provides a more user friendly API for common use cases.
6495*/
6496@inject(Container, ModuleAnalyzer, ViewCompiler, CompositionEngine)
6497export class TemplatingEngine {
6498 /**
6499 * Creates an instance of TemplatingEngine.
6500 * @param container The root DI container.
6501 * @param moduleAnalyzer The module analyzer for discovering view resources.
6502 * @param viewCompiler The view compiler for compiling views.
6503 * @param compositionEngine The composition engine used during dynamic component composition.
6504 */
6505 constructor(container: Container, moduleAnalyzer: ModuleAnalyzer, viewCompiler: ViewCompiler, compositionEngine: CompositionEngine) {
6506 this._container = container;
6507 this._moduleAnalyzer = moduleAnalyzer;
6508 this._viewCompiler = viewCompiler;
6509 this._compositionEngine = compositionEngine;
6510 container.registerInstance(Animator, Animator.instance = new Animator());
6511 }
6512
6513 /**
6514 * Configures the default animator.
6515 * @param animator The animator instance.
6516 */
6517 configureAnimator(animator: Animator): void {
6518 this._container.unregister(Animator);
6519 this._container.registerInstance(Animator, Animator.instance = animator);
6520 }
6521
6522 /**
6523 * Dynamically composes components and views.
6524 * @param context The composition context to use.
6525 * @return A promise for the resulting Controller or View. Consumers of this API
6526 * are responsible for enforcing the Controller/View lifecycle.
6527 */
6528 compose(context: CompositionContext): Promise<View | Controller> {
6529 return this._compositionEngine.compose(context);
6530 }
6531
6532 /**
6533 * Enhances existing DOM with behaviors and bindings.
6534 * @param instruction The element to enhance or a set of instructions for the enhancement process.
6535 * @return A View representing the enhanced UI. Consumers of this API
6536 * are responsible for enforcing the View lifecycle.
6537 */
6538 enhance(instruction: Element | EnhanceInstruction): View {
6539 if (instruction instanceof DOM.Element) {
6540 instruction = { element: instruction };
6541 }
6542
6543 let compilerInstructions = {};
6544 let resources = instruction.resources || this._container.get(ViewResources);
6545
6546 this._viewCompiler._compileNode(instruction.element, resources, compilerInstructions, instruction.element.parentNode, 'root', true);
6547
6548 let factory = new ViewFactory(instruction.element, compilerInstructions, resources);
6549 let container = instruction.container || this._container.createChild();
6550 let view = factory.create(container, BehaviorInstruction.enhance());
6551
6552 view.bind(instruction.bindingContext || {}, instruction.overrideContext);
6553
6554 view.firstChild = view.lastChild = view.fragment;
6555 view.fragment = DOM.createDocumentFragment();
6556 view.attached();
6557
6558 return view;
6559 }
6560}