Skip to content

Commit 3e53b7d

Browse files
committed
feat(components): new component loader provided
PS: added popovers - demo page required
1 parent 8daa4be commit 3e53b7d

28 files changed

+1771
-252
lines changed

demo/src/app/components/accordion/demos/accordion-demo.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<div accordion-heading>
3333
I can have markup, too!
3434
<i class="pull-right glyphicon"
35-
[ngClass]="{'glyphicon-chevron-down': group?.isOpen, 'glyphicon-chevron-right': !group?.isOpen}"></i>
35+
[ngClass]="{'glyphicon-chevron-down': group?._isOpen, 'glyphicon-chevron-right': !group?._isOpen}"></i>
3636
</div>
3737
This is just some content to illustrate fancy headings.
3838
</accordion-group>

demo/src/app/components/tooltip/demos/tooltip-demo.component.html

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,72 +10,97 @@
1010
Pellentesque <a href="#" [tooltip]="dynamicTooltip">{{dynamicTooltipText}}</a>,
1111
sit amet venenatis urna cursus eget nunc scelerisque viverra mauris, in
1212
aliquam. Tincidunt lobortis feugiat vivamus at
13-
<a href="#" tooltipPlacement="left" tooltip="On the Left!">left</a> eget
13+
<a href="#" placement="left" tooltip="On the Left!">left</a> eget
1414
arcu dictum varius duis at consectetur lorem. Vitae elementum curabitur
15-
<a href="#" tooltipPlacement="right" tooltip="On the Right!">right</a>
15+
<a href="#" placement="right" tooltip="On the Right!">right</a>
1616
nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas
17-
<a href="#" tooltipPlacement="bottom" tooltip="On the Bottom!">bottom</a>
17+
<a href="#" placement="bottom" tooltip="On the Bottom!">bottom</a>
1818
pharetra convallis posuere morbi leo urna,
1919
<a href="#" [tooltipAnimation]="false" tooltip="I don't fade. :-(">fading</a>
2020
at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus
21-
<a href="#" tooltipPopupDelay='1000' tooltip='appears with delay'>delayed</a> turpis massa tincidunt dui ut.
21+
<a href="#" tooltipPopupDelay='1000' tooltip='appears with delay'>delayed</a>
22+
turpis massa tincidunt dui ut.
2223
nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas
2324
</p>
2425

2526
<p>
26-
I can even contain HTML. <a href="#" [tooltipPopupDelay]="500" [tooltipHtml]="htmlTooltip" (tooltipStateChanged)="tooltipStateChanged($event)">Check me out!</a>
27+
I can even contain HTML.
28+
<a href="#" [tooltip]="tipContent">Check me out!</a>
29+
<template #tipContent>Say, <b>{{dynamicTooltipText}}</b>!</template>
2730
</p>
2831

29-
<template #toolTipTemplate let-model="model">
32+
<template #toolTipTemplate>
3033
<h4>Tool tip custom content defined inside a template</h4>
31-
<h5>With context binding: {{model.text}}</h5>
34+
<h5>With context binding: {{dynamicTooltip}}</h5>
3235
</template>
3336

3437
<p>
35-
Or use a TemplateRef. <a href="#" [tooltipHtml]="toolTipTemplate"
36-
[tooltipContext]="tooltipModel"
37-
(tooltipStateChanged)="tooltipStateChanged($event)">Check me out!</a>
38+
Or use a TemplateRef.
39+
<a href="#" [tooltip]="toolTipTemplate">Check me out!</a>
3840
</p>
3941
<p>
4042
Programatically show/hide tooltip
41-
<a href="#" [tooltip]="'Foo'"
42-
(tooltipStateChanged)="tooltipStateChanged($event)"
43-
#tooltip="bs-tooltip">Check me out!</a>
44-
<button class="btn btn-primary" (click)="tooltip.show()">Show tooltip</button>
45-
<button class="btn btn-danger" (click)="tooltip.hide()">Hide tooltip</button>
46-
</p>
47-
48-
<p>
49-
I can have a custom class. <a href="#" [tooltip]="'I can have a custom class applied to me!'" [tooltipClass]="'customClass'" [tooltipFadeDuration]="1000">Check me out!</a>
43+
<a href="#" tooltip="Foo"
44+
(onShown)="tooltipStateChanged($event)"
45+
(onHidden)="tooltipStateChanged($event)"
46+
#tooltip="bs-tooltip">Check me out!</a>
47+
<button class="btn btn-primary" (click)="tooltip.show()">Show tooltip</button>
48+
<button class="btn btn-danger" (click)="tooltip.hide()">Hide tooltip</button>
5049
</p>
5150

51+
<!--<p>-->
52+
<!--I can have a custom class.-->
53+
<!--<a href="#"-->
54+
<!--[tooltip]="'I can have a custom class applied to me!'"-->
55+
<!--[tooltipClass]="'customClass'"-->
56+
<!--[tooltipFadeDuration]="1000">Check me out!</a>-->
57+
<!--</p>-->
58+
5259
<p>
53-
I can triggered by the custom events. For example, by the click. <a href="#" tooltip="I displayed after click event" tooltipTrigger="click">Check me out</a>
60+
I can triggered by the custom events. For example, by the click.
61+
<a href="#"
62+
onclick="return false"
63+
tooltip="I displayed after click event"
64+
triggers="click">Check
65+
me out</a>
5466
</p>
5567

5668
<p>
57-
I can combine trigger events. Now I can be displayed by the "click" and "focus" events.
58-
<a href="#" tooltip="I displayed after click or focus event" [tooltipTrigger]="['focusin', 'click']">Click or tab me.</a>
69+
I can combine trigger events. Now I can be displayed by the "click" and
70+
"focus" events.
71+
<a href="#" tooltip="I displayed after click or focus event"
72+
onclick="return false"
73+
triggers="focus click">Click or tab me.</a>
5974
</p>
6075

61-
62-
<p style="overflow:hidden; position:relative; background-color: #f6f6f6" class="alert">
63-
And if I am in <a href="#" tooltip="That ruins the tooltip">overflow: hidden</a> container, then just <a href="#" tooltip="So the tooltip is visible always correctly" [tooltipAppendToBody]="true">tooltipAppendToBody</a> me instead!
76+
<p style="overflow:hidden; position:relative; background-color: #f6f6f6"
77+
class="alert">
78+
And if I am in <a href="#" tooltip="That ruins the tooltip">overflow:
79+
hidden</a> container, then just
80+
<a href="#"
81+
tooltip="So the tooltip is visible always correctly"
82+
container="body">tooltipAppendToBody</a>
83+
me instead!
6484
</p>
6585

6686
<form role="form">
6787
<div class="form-group">
6888
<label>Or use custom triggers, like focus: </label>
69-
<input type="text" name="clickMe" value="Click me!" tooltip="See? Now click away..." tooltipTrigger="focus" tooltipPlacement="right" class="form-control" />
89+
<input type="text" name="clickMe" value="Click me!"
90+
tooltip="See? Now click away..."
91+
triggers="focus"
92+
placement="bottom" class="form-control"/>
7093
</div>
7194

7295
<div class="form-group" ngClass="{'has-error' : !inputModel}">
7396
<label>Disable tooltips conditionally:</label>
74-
<input type="text" name="inputModel" [(ngModel)]="inputModel" class="form-control"
97+
<input type="text" name="inputModel"
98+
[(ngModel)]="inputModel"
99+
class="form-control"
75100
placeholder="Hover over this for a tooltip until this is filled"
76101
tooltip="Enter something in this input field to disable this tooltip"
77-
tooltipPlacement="top"
78-
tooltipTrigger="mouseenter"
79-
[tooltipEnable]="!inputModel || inputModel.length==0" />
102+
placement="top"
103+
triggers="hover"
104+
[isDisabled]="inputModel"/>
80105
</div>
81106
</form>

demo/src/app/components/tooltip/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { TooltipModule } from 'ng2-bootstrap';
1515
CommonModule,
1616
FormsModule,
1717
SharedModule,
18-
TooltipModule
18+
TooltipModule.forRoot()
1919
],
2020
exports: [TooltipSectionComponent]
2121
})
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// todo: add delay support
2+
// todo: merge events onShow, onShown, etc...
3+
// todo: add global positioning configuration?
4+
import {
5+
NgZone, ViewContainerRef, ComponentFactoryResolver, Injector, Renderer,
6+
ElementRef, ComponentRef, ComponentFactory, Type, TemplateRef, EventEmitter
7+
} from '@angular/core';
8+
import { ContentRef } from './content-ref.class';
9+
import { PositioningService, PositioningOptions } from '../positioning';
10+
import { listenToTriggers } from '../utils/triggers';
11+
12+
export interface ListenOptions {
13+
target?: ElementRef;
14+
triggers?: string;
15+
show?: Function;
16+
hide?: Function;
17+
toggle?: Function;
18+
}
19+
20+
export class ComponentLoader<T> {
21+
public onBeforeShow: EventEmitter<any> = new EventEmitter();
22+
public onShown: EventEmitter<any> = new EventEmitter();
23+
public onBeforeHide: EventEmitter<any> = new EventEmitter();
24+
public onHidden: EventEmitter<any> = new EventEmitter();
25+
26+
public instance: T;
27+
28+
private _componentFactory: ComponentFactory<T>;
29+
private _elementRef: ElementRef;
30+
private _componentRef: ComponentRef<T>;
31+
private _zoneSubscription: any;
32+
private _contentRef: ContentRef;
33+
private _viewContainerRef: ViewContainerRef;
34+
private _injector: Injector;
35+
private _renderer: Renderer;
36+
private _ngZone: NgZone;
37+
private _componentFactoryResolver: ComponentFactoryResolver;
38+
private _posService: PositioningService;
39+
40+
private _unregisterListenersFn: Function;
41+
42+
public get isShown(): boolean {
43+
return !!this._componentRef;
44+
};
45+
46+
/**
47+
* Placement of a component. Accepts: "top", "bottom", "left", "right"
48+
*/
49+
private attachment: string;
50+
51+
/**
52+
* A selector specifying the element the popover should be appended to.
53+
* Currently only supports "body".
54+
*/
55+
private container: string | ElementRef | any;
56+
57+
/**
58+
* Specifies events that should trigger. Supports a space separated list of
59+
* event names.
60+
*/
61+
private triggers: string;
62+
63+
/**
64+
* Do not use this directly, it should be instanced via
65+
* `ComponentLoadFactory.attach`
66+
* @internal
67+
* @param _viewContainerRef
68+
* @param _elementRef
69+
* @param _injector
70+
* @param _renderer
71+
* @param _componentFactoryResolver
72+
* @param _ngZone
73+
* @param _posService
74+
*/
75+
// tslint:disable-next-line
76+
public constructor(_viewContainerRef: ViewContainerRef, _renderer: Renderer,
77+
_elementRef: ElementRef,
78+
_injector: Injector, _componentFactoryResolver: ComponentFactoryResolver,
79+
_ngZone: NgZone, _posService: PositioningService) {
80+
this._ngZone = _ngZone;
81+
this._injector = _injector;
82+
this._renderer = _renderer;
83+
this._elementRef = _elementRef;
84+
this._posService = _posService;
85+
this._viewContainerRef = _viewContainerRef;
86+
this._componentFactoryResolver = _componentFactoryResolver;
87+
}
88+
89+
public attach(compType: Type<T>): ComponentLoader<T> {
90+
this._componentFactory = this._componentFactoryResolver
91+
.resolveComponentFactory<T>(compType);
92+
return this;
93+
}
94+
95+
// todo: add behaviour: to target element, `body`, custom element
96+
public to(container?: string): ComponentLoader<T> {
97+
this.container = container || this.container;
98+
return this;
99+
}
100+
101+
public position(opts?: PositioningOptions): ComponentLoader<T> {
102+
this.attachment = opts.attachment || this.attachment;
103+
this._elementRef = opts.target as ElementRef || this._elementRef;
104+
return this;
105+
}
106+
107+
public show(content?: string | TemplateRef<any>, mixin?: any): ComponentRef<T> {
108+
this._subscribePositioning();
109+
110+
if (!this._componentRef) {
111+
this.onBeforeShow.emit();
112+
this._contentRef = this._getContentRef(content);
113+
this._componentRef = this._viewContainerRef
114+
.createComponent(this._componentFactory, 0, this._injector, this._contentRef.nodes);
115+
this.instance = this._componentRef.instance;
116+
117+
Object.assign(this._componentRef.instance, mixin || {});
118+
119+
if (this.container === 'body' && typeof document !== 'undefined') {
120+
document.querySelector(this.container as string)
121+
.appendChild(this._componentRef.location.nativeElement);
122+
}
123+
124+
// we need to manually invoke change detection since events registered
125+
// via
126+
// Renderer::listen() are not picked up by change detection with the
127+
// OnPush strategy
128+
this._componentRef.changeDetectorRef.markForCheck();
129+
this.onShown.emit(this._componentRef.instance);
130+
}
131+
return this._componentRef;
132+
}
133+
134+
public hide(): ComponentLoader<T> {
135+
if (this._componentRef) {
136+
this.onBeforeHide.emit(this._componentRef.instance);
137+
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._componentRef.hostView));
138+
this._componentRef = null;
139+
140+
if (this._contentRef.viewRef) {
141+
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._contentRef.viewRef));
142+
this._contentRef = null;
143+
}
144+
145+
this._componentRef = null;
146+
this.onHidden.emit();
147+
}
148+
return this;
149+
}
150+
151+
public toggle(): void {
152+
if (this.isShown) {
153+
this.hide();
154+
return;
155+
}
156+
157+
this.show();
158+
}
159+
160+
public dispose(): void {
161+
if (this.isShown) {
162+
this.hide();
163+
}
164+
165+
this._unsubscribePositioning();
166+
167+
if (this._unregisterListenersFn) {
168+
this._unregisterListenersFn();
169+
}
170+
}
171+
172+
public listen(listenOpts: ListenOptions): ComponentLoader<T> {
173+
if (this._unregisterListenersFn) {
174+
this._unregisterListenersFn();
175+
}
176+
177+
this.triggers = listenOpts.triggers || this.triggers;
178+
179+
listenOpts.target = listenOpts.target || this._elementRef;
180+
listenOpts.show = listenOpts.show || (() => this.show());
181+
listenOpts.hide = listenOpts.hide || (() => this.hide());
182+
listenOpts.toggle = listenOpts.toggle || (() => this.isShown
183+
? listenOpts.hide()
184+
: listenOpts.show());
185+
186+
this._unregisterListenersFn = listenToTriggers(
187+
this._renderer,
188+
listenOpts.target.nativeElement,
189+
this.triggers,
190+
listenOpts.show,
191+
listenOpts.hide,
192+
listenOpts.toggle);
193+
194+
return this;
195+
}
196+
197+
private _subscribePositioning(): void {
198+
if (this._zoneSubscription) {
199+
return;
200+
}
201+
202+
this._zoneSubscription = this._ngZone
203+
.onStable.subscribe(() => {
204+
if (!this._componentRef) {
205+
return;
206+
}
207+
this._posService.position({
208+
element: this._componentRef.location,
209+
target: this._elementRef,
210+
attachment: this.attachment,
211+
appendToBody: this.container === 'body'
212+
});
213+
});
214+
}
215+
216+
private _unsubscribePositioning(): void {
217+
if (!this._zoneSubscription) {
218+
return;
219+
}
220+
this._zoneSubscription.unsubscribe();
221+
this._zoneSubscription = null;
222+
}
223+
224+
private _getContentRef(content: string | TemplateRef<any>): ContentRef {
225+
if (!content) {
226+
return new ContentRef([]);
227+
}
228+
229+
if (content instanceof TemplateRef) {
230+
const viewRef = this._viewContainerRef
231+
.createEmbeddedView<TemplateRef<T>>(content);
232+
return new ContentRef([viewRef.rootNodes], viewRef);
233+
}
234+
235+
return new ContentRef([[this._renderer.createText(null, `${content}`)]]);
236+
}
237+
}

0 commit comments

Comments
 (0)