Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit fa67850

Browse files
authored
Merge pull request #398 from SamGraber/dropdowns_click_off_to_close
Dropdowns click off to close
2 parents 7a95369 + ce21141 commit fa67850

File tree

16 files changed

+117
-100
lines changed

16 files changed

+117
-100
lines changed

bootstrapper/popup/popupNg2.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ <h3>Popup content</h3>
1818
<div *rlDialogHeader>Header 2</div>
1919
<div *rlDialogContent>
2020
<rlTextbox label="Textbox" name="textbox" rlRequired="Required"></rlTextbox>
21-
<rlSelect label="Select" name="select" [options]="options" rlRequired="Required"></rlSelect>
22-
<rlTypeahead label="Typeahead" name="typeahead" [getItems]="getOptions" rlRequired="Required" allowCollapse="true"></rlTypeahead>
21+
<rlSelect label="Select" name="select" [options]="options" rlRequired="Required" nullOption="None"></rlSelect>
22+
<rlTypeahead label="Typeahead" name="typeahead" [getItems]="getOptions" rlRequired="Required" allowCollapse="true" [create]="create"></rlTypeahead>
2323
</div>
2424
</rlDialog>
2525
<rlPromptDialog #prompt

bootstrapper/popup/popupNg2Bootstrapper.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ export class PopupBootstrapper {
3131
save = (data) => {
3232
console.log(data);
3333
}
34+
35+
create = x => x;
3436
}

source/behaviors/offClick/offClick.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Directive, Host, Input, Output, EventEmitter, OnInit, OnDestroy, Simple
33
import { services } from 'typescript-angular-utilities';
44
import __guid = services.guid;
55

6+
import { DocumentWrapper } from '../../services/document/document.provider';
7+
68
export interface IOffClickEvent extends MouseEvent {
79
rlEventIdentifier: string;
810
}
@@ -15,7 +17,17 @@ export interface IOffClickEvent extends MouseEvent {
1517
})
1618
export class OffClickDirective implements OnInit, OnDestroy {
1719
@Output('offClick') offClick: EventEmitter<any> = new EventEmitter();
18-
@Input('offClickActive') active: boolean = true;
20+
21+
private _active: boolean = true;
22+
@Input('offClickActive') set active(value: boolean)
23+
{
24+
if (value) {
25+
this.addListener();
26+
} else {
27+
this.removeListener();
28+
}
29+
this._active = value;
30+
}
1931

2032
listener: { ($event: MouseEvent): void } = ($event: IOffClickEvent) => {
2133
if ($event.rlEventIdentifier !== this.identifier) {
@@ -24,35 +36,28 @@ export class OffClickDirective implements OnInit, OnDestroy {
2436
};
2537

2638
identifier: string;
39+
document: Document;
2740

28-
constructor(guidService: __guid.GuidService) {
41+
constructor(guidService: __guid.GuidService
42+
, document: DocumentWrapper) {
2943
this.identifier = guidService.random();
44+
this.document = <any>document;
3045
}
3146

3247
ngOnInit() {
33-
if (this.active) {
48+
if (this._active) {
3449
setTimeout(() => {
3550
this.addListener();
3651
});
3752
}
3853
}
3954

40-
ngOnChanges(changes: SimpleChanges): void {
41-
if (changes['active']) {
42-
if (changes['active'].currentValue) {
43-
this.addListener();
44-
} else {
45-
this.removeListener();
46-
}
47-
}
48-
}
49-
5055
addListener(): void {
51-
document.addEventListener('click', this.listener);
56+
this.document.addEventListener('click', this.listener);
5257
}
5358

5459
removeListener(): void {
55-
document.removeEventListener('click', this.listener);
60+
this.document.removeEventListener('click', this.listener);
5661
}
5762

5863
ngOnDestroy() {

source/componentProviders.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { MergeSort } from './components/cardContainer/sorts/mergeSort/mergeSort.
1515

1616
import { AsyncHelper } from './services/async/async.service';
1717
import { AutosaveActionService } from './services/autosaveAction/autosaveAction.service';
18+
import { DOCUMENT_PROVIDER } from './services/document/document.provider';
1819
import { DocumentService } from './services/documentWrapper/documentWrapper.service';
1920
import { FormService } from './services/form/form.service';
2021
import { JQUERY_PROVIDER } from './services/jquery/jquery.provider';
@@ -66,8 +67,9 @@ const defaultThemeNg1: FactoryProvider = {
6667
DefaultTheme,
6768
defaultThemeNg1,
6869
DialogRootService,
69-
FormService,
70+
DOCUMENT_PROVIDER,
7071
DocumentService,
72+
FormService,
7173
BreakpointService,
7274
VisibleBreakpointService,
7375
JQUERY_PROVIDER,

source/components/inputs/select/select.html

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@
33
[class.error]="componentValidator.error$ | async"
44
[class.rl-select-loading]="busy?.loading">
55
<label [@slide]="labelState">{{label}}</label>
6-
<div class="form-control rl-select-trigger"
7-
[class.disabled]="disabled"
8-
[class.rl-select-open]="list.showOptions"
9-
(focus)="showLabel()"
10-
(blur)="hideLabelIfEmpty()"
11-
tabindex="0"
12-
rlPopoutTrigger>
13-
<span class="placeholder"
14-
[class.hide-placeholder]="hidePlaceholder"
15-
[hidden]="value">{{label}}</span>
16-
<span class="rl-select-choice">{{getDisplayName(value)}}</span>
6+
<div class="rl-select-wrap"
7+
(offClick)="list.close()"
8+
[offClickActive]="list.showOptions">
9+
<div class="form-control rl-select-trigger"
10+
[class.disabled]="disabled"
11+
[class.rl-select-open]="list.showOptions"
12+
(focus)="showLabel()"
13+
(blur)="hideLabelIfEmpty()"
14+
tabindex="0"
15+
rlPopoutTrigger>
16+
<span class="placeholder"
17+
[class.hide-placeholder]="hidePlaceholder"
18+
[hidden]="value">{{label}}</span>
19+
<span class="rl-select-choice">{{getDisplayName(value)}}</span>
20+
</div>
21+
<rlPopoutList #list
22+
[options]="options"
23+
[template]="template"
24+
[transform]="transform"
25+
(select)="select($event)">
26+
<rlPopoutItem class="rl-select-option-null" *ngIf="nullOption" (trigger)="select(null)">{{nullOption}}</rlPopoutItem>
27+
</rlPopoutList>
1728
</div>
18-
<rlPopoutList #list
19-
[options]="options"
20-
[template]="template"
21-
[transform]="transform"
22-
(select)="select($event)">
23-
<rlPopoutItem class="rl-select-option-null" *ngIf="nullOption" (trigger)="select(null)">{{nullOption}}</rlPopoutItem>
24-
</rlPopoutList>
2529
<rlBusy [loading]="!options"></rlBusy>
2630
<div *ngIf="componentValidator.error$ | async" class="error-string">
2731
{{componentValidator.error$ | async}}

source/components/inputs/select/select.tests.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,29 +44,18 @@ describe('SelectComponent', () => {
4444
];
4545
});
4646

47-
it('should set the value and close the options', (): void => {
48-
const closeSpy = sinon.spy();
49-
dropdown.list = <any>{
50-
close: closeSpy,
51-
};
52-
47+
it('should set the value', (): void => {
5348
dropdown.select(options[1]);
5449

55-
sinon.assert.calledOnce(closeSpy);
5650
sinon.assert.calledOnce(setValue);
5751
sinon.assert.calledWith(setValue, options[1]);
5852
});
5953

60-
it('should close the options without setting the value if the current value is reselected', (): void => {
61-
const closeSpy = sinon.spy();
62-
dropdown.list = <any>{
63-
close: closeSpy,
64-
};
54+
it('should not set the value if the current value is reselected', (): void => {
6555
dropdown.value = options[1];
6656

6757
dropdown.select(options[1]);
6858

69-
sinon.assert.calledOnce(closeSpy);
7059
sinon.assert.notCalled(setValue);
7160
});
7261

source/components/inputs/select/select.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export class SelectComponent<T> extends ValidatedInputComponent<T> implements Af
3131
@Input() externalTemplate: TemplateRef<any>;
3232

3333
@ViewChild(BusyComponent) busy: BusyComponent;
34-
@ViewChild(PopoutListComponent) list: PopoutListComponent<T>;
3534
@ContentChild(TemplateRef) template: TemplateRef<any>;
3635

3736
private transformService: __transform.ITransformService;
@@ -56,7 +55,6 @@ export class SelectComponent<T> extends ValidatedInputComponent<T> implements Af
5655
if (value != this.value) {
5756
this.setValue(value);
5857
}
59-
this.list.close();
6058
this.hideLabelIfEmpty();
6159
}
6260

source/components/inputs/typeahead/typeahead.html

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@
33
[class.error]="componentValidator.error$ | async">
44
<label [@slide]="labelState">{{label}}</label>
55
<div [hidden]="collapsed">
6-
<input class="form-control rl-select-trigger"
7-
#input
8-
type="text"
9-
[disabled]="disabled"
10-
[class.rl-select-open]="list.showOptions"
11-
[class.hide-placeholder]="hidePlaceholder"
12-
[placeholder]="placeholder"
13-
[value]="search"
14-
(focus)="showLabel()"
15-
(blur)="hideLabelIfEmpty()"
16-
rlPopoutTrigger
17-
(input)="searchStream.next($event.target.value)" />
18-
<rlPopoutList [disabled]="!canShowOptions"
19-
[options]="visibleItems$ | async"
20-
[template]="template"
21-
[transform]="transform"
22-
(select)="selectItem($event)">
23-
<rlPopoutItem class="rl-select-option-custom" *ngIf="allowCustomOption" (trigger)="selectCustom()">{{search}}</rlPopoutItem>
24-
</rlPopoutList>
6+
<div class="rl-select-wrap"
7+
(offClick)="list.close()"
8+
[offClickActive]="list.showOptions || false">
9+
<input class="form-control rl-select-trigger"
10+
#input
11+
type="text"
12+
[disabled]="disabled"
13+
[class.rl-select-open]="list.showOptions"
14+
[class.hide-placeholder]="hidePlaceholder"
15+
[placeholder]="placeholder"
16+
[value]="search"
17+
(input)="searchStream.next($event.target.value)"
18+
(focus)="showLabel()"
19+
(blur)="hideLabelIfEmpty()"
20+
rlPopoutTrigger />
21+
<rlPopoutList [disabled]="!canShowOptions"
22+
[options]="visibleItems$ | async"
23+
[template]="template"
24+
[transform]="transform"
25+
(select)="selectItem($event)">
26+
<rlPopoutItem class="rl-select-option-custom" *ngIf="allowCustomOption" (trigger)="selectCustom()">{{search}}</rlPopoutItem>
27+
</rlPopoutList>
28+
</div>
2529
</div>
2630
<div class="collapsed" [hidden]="!collapsed">
2731
<span>{{getDisplayName(value)}}</span>

source/components/inputs/typeahead/typeahead.tests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ describe('TypeaheadComponent', () => {
3838
validate: sinon.spy(() => Observable.empty()),
3939
};
4040

41-
typeahead = new TypeaheadComponent(__transform.transform, null, validator, __object.objectUtility, __array.arrayUtility, __guid.guid, __search.searchUtility);
41+
const changeDetectorMock = { detectChanges: sinon.spy() };
42+
typeahead = new TypeaheadComponent(__transform.transform, null, validator, __object.objectUtility, __array.arrayUtility, __guid.guid, __search.searchUtility, <any>changeDetectorMock);
4243

4344
setValue = sinon.spy();
4445
typeahead.setValue = setValue;

source/components/inputs/typeahead/typeahead.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, Input, Output, EventEmitter, Optional, OnInit, OnChanges, SimpleChange, ViewChild, ContentChild, TemplateRef, ElementRef } from '@angular/core';
1+
import { Component, Input, Output, EventEmitter, Optional, OnInit, OnChanges, SimpleChange, ViewChild, ContentChild, TemplateRef, ElementRef, ChangeDetectorRef } from '@angular/core';
22
import { Observable, BehaviorSubject, Subject } from 'rxjs';
33
import { find, filter, isEqual, cloneDeep } from 'lodash';
44

@@ -64,6 +64,7 @@ export class TypeaheadComponent<T> extends ValidatedInputComponent<T> implements
6464

6565
transformService: __transform.ITransformService;
6666
searchUtility: __search.ISearchUtility;
67+
changeDetector: ChangeDetectorRef;
6768

6869
get visibleItems$(): Observable<T[]> {
6970
return this._visibleItems.asObservable();
@@ -83,10 +84,12 @@ export class TypeaheadComponent<T> extends ValidatedInputComponent<T> implements
8384
, object: __object.ObjectUtility
8485
, array: __array.ArrayUtility
8586
, guid: __guid.GuidService
86-
, searchService: __search.SearchUtility) {
87+
, searchService: __search.SearchUtility
88+
, changeDetector: ChangeDetectorRef) {
8789
super(rlForm, componentValidator, object, array, guid);
8890
this.transformService = transformService;
8991
this.searchUtility = searchService;
92+
this.changeDetector = changeDetector;
9093
this.inputType = 'typeahead';
9194
this.search = '';
9295
this._visibleItems = new BehaviorSubject(null);
@@ -116,7 +119,6 @@ export class TypeaheadComponent<T> extends ValidatedInputComponent<T> implements
116119
}
117120

118121
selectItem(item: T): void {
119-
this.list.close();
120122
this.search = '';
121123

122124
this.selector.emit(item);
@@ -145,6 +147,7 @@ export class TypeaheadComponent<T> extends ValidatedInputComponent<T> implements
145147
if (!isEqual(data, this.cacheDisplayList)) {
146148
this.list.open();
147149
this.cacheDisplayList = cloneDeep(data);
150+
this.changeDetector.detectChanges();
148151
return this._visibleItems.next(this.cacheDisplayList);
149152
}
150153
});
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<li class="rl-select-option"
22
[class.focused]="focused"
33
(mouseover)="focus()"
4-
(mouseleave)="blur()">
4+
(mouseleave)="blur()"
5+
(click)="select()">
56
<ng-content></ng-content>
6-
</li>
7+
</li>

source/components/popoutList/popoutItem.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ export class PopoutItemComponent<T> {
2626
blur(): void {
2727
this.list.blur();
2828
}
29+
30+
select(): void {
31+
this.list.selectCurrent();
32+
}
2933
}

source/components/popoutList/popoutList.service.tests.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@ describe('PopoutListService', () => {
77
listService = new PopoutListService<string>();
88
});
99

10-
it('should close the list on a select event', (): void => {
11-
const closeSpy = sinon.spy();
12-
listService.close = closeSpy;
13-
14-
listService.select.next('value');
15-
16-
sinon.assert.calledOnce(closeSpy);
17-
});
18-
1910
describe('showOptions', (): void => {
2011
it('should close the options', (): void => {
2112
listService._showOptions = true;
@@ -168,5 +159,14 @@ describe('PopoutListService', () => {
168159

169160
sinon.assert.notCalled(emitSpy);
170161
});
162+
163+
it('should close the list on a selection', (): void => {
164+
const closeSpy = sinon.spy();
165+
listService.close = closeSpy;
166+
167+
listService.selectCurrent();
168+
169+
sinon.assert.calledOnce(closeSpy);
170+
});
171171
});
172172
});

source/components/popoutList/popoutList.service.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ export class PopoutListService<T> {
2020
customItems: QueryList<PopoutItemComponent<T>>;
2121
listItems: QueryList<PopoutItemComponent<T>>;
2222

23-
constructor() {
24-
this.select.subscribe(() => this.close());
25-
}
26-
2723
open(): void {
2824
this._showOptions = true;
2925
}
@@ -78,6 +74,7 @@ export class PopoutListService<T> {
7874
this.current.trigger.emit(null);
7975
this.focusIndex = null;
8076
}
77+
this.close();
8178
}
8279

8380
get current(): PopoutItemComponent<T> {

0 commit comments

Comments
 (0)