diff --git a/demo/src/app/pages/behaviors/localization/localization.page.ts b/demo/src/app/pages/behaviors/localization/localization.page.ts
index 310e54d30..b862752dc 100644
--- a/demo/src/app/pages/behaviors/localization/localization.page.ts
+++ b/demo/src/app/pages/behaviors/localization/localization.page.ts
@@ -15,7 +15,7 @@ const exampleTemplate = `
#lang>
+ [option]="l">
@@ -138,7 +130,6 @@ const exampleTemplateTemplate = `
[options]="options"
[optionFormatter]="formatter"
#formatted>
-
@@ -152,10 +143,9 @@ const exampleSearchLookupTemplate = `
[(ngModel)]="selectedOption"
[optionsLookup]="optionsLookup"
labelField="name"
- valueField="id"
+ optionField="id"
[isSearchable]="true"
#searchSelect>
-
Currently selected: {{ selectedOption | json }}
@@ -257,7 +247,7 @@ export class SelectPage {
},
{
name: "localeOverrides",
- type: "RecursivePartial
",
+ type: "RecursivePartial",
description: "Overrides the values from the localization service."
}
],
@@ -374,7 +364,7 @@ export class SelectPage {
},
{
name: "localeOverrides",
- type: "Partial",
+ type: "Partial",
description: "Overrides the values from the localization service."
}
],
@@ -395,9 +385,9 @@ export class SelectPage {
selector: "",
properties: [
{
- name: "value",
+ name: "option",
type: "T",
- description: "Sets the value of the option.",
+ description: "Sets the option the component represents.",
required: true
}
]
diff --git a/demo/src/app/pages/modules/transition/transition.page.ts b/demo/src/app/pages/modules/transition/transition.page.ts
index 427c0df27..645411637 100644
--- a/demo/src/app/pages/modules/transition/transition.page.ts
+++ b/demo/src/app/pages/modules/transition/transition.page.ts
@@ -7,7 +7,7 @@ const exampleStandardTemplate = `
-
+
`;
diff --git a/package-lock.json b/package-lock.json
index a2315a2d0..d23e09b67 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,7 +52,7 @@
"raw-loader": "0.5.1",
"resolve": "1.3.3",
"rsvp": "3.6.0",
- "rxjs": "5.4.1",
+ "rxjs": "5.4.2",
"sass-loader": "6.0.6",
"script-loader": "0.7.0",
"semver": "5.3.0",
@@ -427,7 +427,7 @@
"aproba": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz",
- "integrity": "sha1-RcZikJTeTpb2k+9+q3SuB5wkD8E=",
+ "integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==",
"dev": true
},
"are-we-there-yet": {
@@ -4170,7 +4170,7 @@
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
+ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
@@ -4203,7 +4203,7 @@
"globals": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
- "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=",
+ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
"dev": true
},
"globby": {
@@ -5395,7 +5395,7 @@
"istanbul-lib-coverage": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz",
- "integrity": "sha1-c7+5mIhSmUFck9OKPprfeEp3qdo=",
+ "integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==",
"dev": true
},
"istanbul-lib-hook": {
@@ -6249,7 +6249,7 @@
"lru-cache": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
- "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=",
+ "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
"dev": true,
"optional": true,
"requires": {
@@ -6426,7 +6426,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.8"
@@ -8025,7 +8025,7 @@
"randomatic": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
- "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=",
+ "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
"dev": true,
"requires": {
"is-number": "3.0.0",
@@ -8066,7 +8066,7 @@
"randombytes": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz",
- "integrity": "sha1-3ACaJGuNCaF3tLegrne8Vw9LG3k=",
+ "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
@@ -8758,9 +8758,9 @@
}
},
"rxjs": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.1.tgz",
- "integrity": "sha1-ti91fyeURdJloYpY+wpw3JDpFiY=",
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz",
+ "integrity": "sha1-KjI2/L8D31e64G/Wly/ZnlwI/Pc=",
"requires": {
"symbol-observable": "1.0.4"
}
@@ -9470,7 +9470,7 @@
"stream-http": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz",
- "integrity": "sha1-QKBQ7I3DtTsz2ZCUFcAsC/Gr+60=",
+ "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==",
"dev": true,
"requires": {
"builtin-status-codes": "3.0.0",
@@ -10059,7 +10059,7 @@
"url-loader": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.5.9.tgz",
- "integrity": "sha1-zI/qgse5Bud3cBklCGnlaemVwpU=",
+ "integrity": "sha512-B7QYFyvv+fOBqBVeefsxv6koWWtjmHaMFT6KZWti4KRw8YUD/hOU+3AECvXuzyVawIBx3z7zQRejXCDSO5kk1Q==",
"dev": true,
"requires": {
"loader-utils": "1.1.0",
@@ -10269,7 +10269,7 @@
"walk-sync": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.3.2.tgz",
- "integrity": "sha1-SCcoCvxC0OA1NnxKTjHurA0Tb3U=",
+ "integrity": "sha512-FMB5VqpLqOCcqrzA9okZFc0wq0Qbmdm396qJxvQZhDpyu0W95G9JCmp74tx7iyYnyOcBtUuKJsgIKAqjozvmmQ==",
"dev": true,
"requires": {
"ensure-posix-path": "1.0.2",
@@ -10634,7 +10634,7 @@
"webpack-sources": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz",
- "integrity": "sha1-xzVkNqTRMSO+LiQmoF0drZy+Zc8=",
+ "integrity": "sha512-05tMxipUCwHqYaVS8xc7sYPTly8PzXayRCB4dTxLhWTqlKUiwH6ezmEe0OSreL1c30LAuA3Zqmc+uEBUGFJDjw==",
"dev": true,
"requires": {
"source-list-map": "2.0.0",
@@ -10644,7 +10644,7 @@
"source-list-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
- "integrity": "sha1-qqR0A/eyRakvvJfqCPJQ1gh+0IU=",
+ "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==",
"dev": true
}
}
@@ -10694,7 +10694,7 @@
"wide-align": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
- "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=",
+ "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"dev": true,
"requires": {
"string-width": "1.0.2"
diff --git a/package.json b/package.json
index 84a10edb8..1c25f70c1 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,7 @@
"element-closest": "^2.0.2",
"extend": "^3.0.1",
"popper.js": "~1.10.6",
- "rxjs": "^5.0.1"
+ "rxjs": "^5.4.2"
},
"devDependencies": {
"@angular/common": "^4.3.1",
diff --git a/src/misc/util/services/component-factory.service.ts b/src/misc/util/services/component-factory.service.ts
index 2796e79b3..ef6cbb7dc 100644
--- a/src/misc/util/services/component-factory.service.ts
+++ b/src/misc/util/services/component-factory.service.ts
@@ -34,8 +34,8 @@ export class SuiComponentFactory {
}
// Inserts the component into the specified view container.
- public attachToView
(componentRef:ComponentRef, viewContainer:ViewContainerRef):void {
- viewContainer.insert(componentRef.hostView, 0);
+ public attachToView(componentRef:ComponentRef, viewContainer:ViewContainerRef, index:number = 0):void {
+ viewContainer.insert(componentRef.hostView, index);
}
// Inserts the component in the root application node.
diff --git a/src/modules/search/services/search.service.ts b/src/modules/search/services/search.service.ts
index 9b54754fb..1c4e7d2fd 100644
--- a/src/modules/search/services/search.service.ts
+++ b/src/modules/search/services/search.service.ts
@@ -1,3 +1,4 @@
+import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { Util } from "../../../misc/util";
import { LookupFn, LookupFnResult, FilterFn } from "../helpers/lookup-fn";
@@ -59,6 +60,9 @@ export class SearchService {
return this._results;
}
+ // Results in observable form.
+ public results$:BehaviorSubject;
+
private _query:string;
// Allows the empty query to produce results.
public allowEmptyQuery:boolean;
@@ -97,6 +101,8 @@ export class SearchService {
return false;
};
+ this.results$ = new BehaviorSubject([]);
+
// Set default values and reset.
this.allowEmptyQuery = allowEmptyQuery;
this.searchDelay = 0;
@@ -128,7 +134,7 @@ export class SearchService {
if (this._resultsCache.hasOwnProperty(this._query)) {
// If the query is already cached, make use of it.
- this._results = this._resultsCache[this._query];
+ this.updateResults(this._resultsCache[this._query], false);
return callback();
}
@@ -163,9 +169,12 @@ export class SearchService {
}
// Updates & caches the new set of results.
- private updateResults(results:T[]):void {
- this._resultsCache[this._query] = results;
+ private updateResults(results:T[], cache:boolean = true):void {
+ if (cache) {
+ this._resultsCache[this._query] = results;
+ }
this._results = results;
+ this.results$.next(results);
}
// tslint:disable-next-line:promise-function-async
@@ -201,6 +210,7 @@ export class SearchService {
// Resets the search back to a pristine state.
private reset():void {
this._results = [];
+ this.results$.next([]);
this._resultsCache = {};
this._isSearching = false;
this.updateQuery("");
diff --git a/src/modules/select/classes/select-base.ts b/src/modules/select/classes/select-base.ts
index 041bc9d69..07ac69c57 100644
--- a/src/modules/select/classes/select-base.ts
+++ b/src/modules/select/classes/select-base.ts
@@ -1,14 +1,18 @@
import {
- ViewChild, HostBinding, ElementRef, HostListener, Input, ContentChildren, QueryList,
- AfterContentInit, TemplateRef, ViewContainerRef, ContentChild, EventEmitter, Output
+ ViewChild, HostBinding, ElementRef, HostListener, Input, ContentChildren, QueryList, AfterViewInit,
+ AfterContentInit, TemplateRef, ViewContainerRef, ContentChild, EventEmitter, Output, ViewChildren, ComponentRef
} from "@angular/core";
import { Subscription } from "rxjs/Subscription";
+import { BehaviorSubject } from "rxjs/BehaviorSubject";
+import { Observable } from "rxjs/Observable";
+import "rxjs/add/observable/merge";
import { DropdownService, SuiDropdownMenu } from "../../dropdown";
import { SearchService, LookupFn, FilterFn } from "../../search";
-import { Util, ITemplateRefContext, HandledEvent, KeyCode } from "../../../misc/util";
+import { Util, ITemplateRefContext, HandledEvent, KeyCode, SuiComponentFactory } from "../../../misc/util";
import { ISelectLocaleValues, RecursivePartial, SuiLocalizationService } from "../../../behaviors/localization";
-import { SuiSelectOption, ISelectRenderedOption } from "../components/select-option";
+import { SuiSelectOption } from "../components/select-option";
import { SuiSelectSearch } from "../directives/select-search";
+import { SuiSelectOptions } from "../components/select-options";
export interface IOptionContext extends ITemplateRefContext {
query?:string;
@@ -16,7 +20,7 @@ export interface IOptionContext extends ITemplateRefContext {
// We use generic type T to specify the type of the options we are working with,
// and U to specify the type of the property of the option used as the value.
-export abstract class SuiSelectBase implements AfterContentInit {
+export abstract class SuiSelectBase implements AfterContentInit, AfterViewInit {
public dropdownService:DropdownService;
public searchService:SearchService;
@@ -24,11 +28,18 @@ export abstract class SuiSelectBase implements AfterContentInit {
protected _menu:SuiDropdownMenu;
// Keep track of all of the rendered select options. (Rendered by the user using *ngFor).
- @ContentChildren(SuiSelectOption, { descendants: true })
- protected _renderedOptions:QueryList>;
+ @ContentChildren(SuiSelectOption)
+ protected _manualOptions:QueryList>;
- // Keep track of all of the subscriptions to the selected events on the rendered options.
- private _renderedSubscriptions:Subscription[];
+ @ViewChild(SuiSelectOptions, { read: ViewContainerRef })
+ private _internalOptionsContainer:ViewContainerRef;
+
+ @ContentChild(SuiSelectOptions, { read: ViewContainerRef })
+ private _manualOptionsContainer?:ViewContainerRef;
+
+ public get optionsContainer():ViewContainerRef {
+ return this._manualOptionsContainer || this._internalOptionsContainer;
+ }
// Sets the Semantic UI classes on the host element.
@HostBinding("class.ui")
@@ -132,14 +143,12 @@ export abstract class SuiSelectBase implements AfterContentInit {
}
}
- public get filteredOptions():T[] {
- return this.searchService.results;
+ public get filteredOptions$():BehaviorSubject {
+ return this.searchService.results$;
}
- // Deprecated
- public get availableOptions():T[] {
- return this.filteredOptions;
- }
+ private _renderedOptions:ComponentRef>[];
+ private _renderedSubscription:Subscription;
public get query():string | undefined {
return this.isSearchable ? this.searchService.query : undefined;
@@ -150,7 +159,7 @@ export abstract class SuiSelectBase implements AfterContentInit {
this.queryUpdateHook();
this.updateQuery(query);
// Update the rendered text as query has changed.
- this._renderedOptions.forEach(ro => ro.formatter = this.configuredFormatter);
+ this._manualOptions.forEach(ro => ro.formatter = this.configuredFormatter);
if (this.searchInput) {
this.searchInput.query = query;
@@ -225,16 +234,22 @@ export abstract class SuiSelectBase implements AfterContentInit {
@Output("touched")
public onTouched:EventEmitter;
- constructor(private _element:ElementRef, protected _localizationService:SuiLocalizationService) {
+ constructor(private _element:ElementRef,
+ private _componentFactory:SuiComponentFactory,
+ protected _localizationService:SuiLocalizationService) {
+
this.dropdownService = new DropdownService();
// We do want an empty query to return all results.
this.searchService = new SearchService(true);
+ this._renderedOptions = [];
+
this.isSearchable = false;
this.onLocaleUpdate();
this._localizationService.onLanguageUpdate.subscribe(() => this.onLocaleUpdate());
- this._renderedSubscriptions = [];
+
+ this.searchService.results$.subscribe(rs => this.renderOptions(rs));
this.icon = "dropdown";
this.transition = "slide down";
@@ -248,8 +263,10 @@ export abstract class SuiSelectBase implements AfterContentInit {
public ngAfterContentInit():void {
this._menu.service = this.dropdownService;
// We manually specify the menu items to the menu because the @ContentChildren doesn't pick up our dynamically rendered items.
- this._menu.items = this._renderedOptions;
+ this._menu.items = this._manualOptions;
+ }
+ public ngAfterViewInit():void {
if (this._manualSearch) {
this.isSearchable = true;
this.isSearchExternal = true;
@@ -261,8 +278,8 @@ export abstract class SuiSelectBase implements AfterContentInit {
}
// We must call this immediately as changes doesn't fire when you subscribe.
- this.onAvailableOptionsRendered();
- this._renderedOptions.changes.subscribe(() => this.onAvailableOptionsRendered());
+ this.onManualOptionsRendered();
+ this._manualOptions.changes.subscribe(() => this.onManualOptionsRendered());
}
private onLocaleUpdate():void {
@@ -296,30 +313,53 @@ export abstract class SuiSelectBase implements AfterContentInit {
}
}
- protected onAvailableOptionsRendered():void {
- // Unsubscribe from all previous subscriptions to avoid memory leaks on large selects.
- this._renderedSubscriptions.forEach(rs => rs.unsubscribe());
- this._renderedSubscriptions = [];
+ private renderOptions(options:T[]):void {
+ if (this.optionsContainer) {
+ this.optionsContainer.clear();
+ }
- this._renderedOptions.forEach(ro => {
- // Slightly delay initialisation to avoid change after checked errors. TODO - look into avoiding this!
- setTimeout(() => this.initialiseRenderedOption(ro));
+ if (this._renderedSubscription) {
+ this._renderedSubscription.unsubscribe();
+ }
- this._renderedSubscriptions.push(ro.onSelected.subscribe(() => this.selectOption(ro.value)));
- });
+ this._renderedOptions.forEach(ro => ro.destroy());
+ this._renderedOptions = [];
- // If no options have been provided, autogenerate them from the rendered ones.
- if (this.searchService.options.length === 0 && !this.searchService.optionsLookup) {
- this.options = this._renderedOptions.map(ro => ro.value);
- }
+ options
+ .slice()
+ .reverse()
+ .forEach(option => {
+ const component = this._componentFactory.createComponent(SuiSelectOption);
+ component.instance.option = option;
+
+ this._componentFactory.attachToView(component, this.optionsContainer);
+ this._renderedOptions.push(component);
+ });
+
+ this._renderedSubscription = Observable
+ .merge(...this._renderedOptions.map(ro => ro.instance.onSelected))
+ .subscribe((o:T) => {
+ this.selectOption(o);
+ this.updateRenderedOptions();
+ });
+
+ this.updateRenderedOptions();
}
- protected initialiseRenderedOption(option:ISelectRenderedOption):void {
- option.usesTemplate = !!this.optionTemplate;
+ private updateRenderedOptions():void {
+ this._renderedOptions.forEach(ro => this.updateRenderedOption(ro.instance));
+ }
+
+ protected updateRenderedOption(option:SuiSelectOption):void {
+ option.query = this.query;
option.formatter = this.configuredFormatter;
+ option.template = this.optionTemplate;
+ }
- if (option.usesTemplate) {
- this.drawTemplate(option.templateSibling, option.value);
+ protected onManualOptionsRendered():void {
+ // If no options have been provided, autogenerate them from the rendered ones.
+ if (this.searchService.options.length === 0 && !this.searchService.optionsLookup) {
+ this.options = this._manualOptions.map(ro => ro.option);
}
}
diff --git a/src/modules/select/components/multi-select-label.ts b/src/modules/select/components/multi-select-label.ts
index 0d59b03e2..9412e631c 100644
--- a/src/modules/select/components/multi-select-label.ts
+++ b/src/modules/select/components/multi-select-label.ts
@@ -13,7 +13,7 @@ const templateRef = TemplateRef;
selector: "sui-multi-select-label",
template: `
-
+
`
})
@@ -27,7 +27,7 @@ export class SuiMultiSelectLabel extends SuiTransition {
private _transitionController:TransitionController;
@Input()
- public value:T;
+ public option:T;
@Input()
public query?:string;
@@ -36,7 +36,7 @@ export class SuiMultiSelectLabel extends SuiTransition {
public onDeselected:EventEmitter;
@Input()
- public formatter:(obj:T) => string;
+ public formatter?:(obj:T) => string;
private _template?:TemplateRef>;
@@ -49,7 +49,7 @@ export class SuiMultiSelectLabel extends SuiTransition {
this._template = template;
if (this.template) {
this.componentFactory.createView(this.templateSibling, this.template, {
- $implicit: this.value,
+ $implicit: this.option,
query: this.query
});
}
@@ -82,7 +82,7 @@ export class SuiMultiSelectLabel extends SuiTransition {
this._transitionController.animate(
new Transition("scale", 100, TransitionDirection.Out, () =>
- this.onDeselected.emit(this.value)));
+ this.onDeselected.emit(this.option)));
}
@HostListener("click", ["$event"])
diff --git a/src/modules/select/components/multi-select.ts b/src/modules/select/components/multi-select.ts
index 91f914853..b5aecc81e 100644
--- a/src/modules/select/components/multi-select.ts
+++ b/src/modules/select/components/multi-select.ts
@@ -1,8 +1,11 @@
import { Component, HostBinding, ElementRef, EventEmitter, Output, Input, Directive } from "@angular/core";
-import { ICustomValueAccessorHost, KeyCode, customValueAccessorFactory, CustomValueAccessor } from "../../../misc/util";
+import {
+ ICustomValueAccessorHost, KeyCode, customValueAccessorFactory,
+ CustomValueAccessor, SuiComponentFactory
+} from "../../../misc/util";
import { SuiLocalizationService } from "../../../behaviors/localization";
import { SuiSelectBase } from "../classes/select-base";
-import { ISelectRenderedOption } from "./select-option";
+import { SuiSelectOption } from "./select-option";
@Component({
selector: "sui-multi-select",
@@ -13,7 +16,7 @@ import { ISelectRenderedOption } from "./select-option";
-
+
+
{{ localeValues.noResultsMessage }}
{{ maxSelectedMessage }}
@@ -134,8 +138,11 @@ export class SuiMultiSelect extends SuiSelectBase implements ICustom
@HostBinding("class.multiple")
private _multiSelectClasses:boolean;
- constructor(element:ElementRef, localizationService:SuiLocalizationService) {
- super(element, localizationService);
+ constructor(element:ElementRef,
+ componentFactory:SuiComponentFactory,
+ localizationService:SuiLocalizationService) {
+
+ super(element, componentFactory, localizationService);
this.selectedOptions = [];
this.selectedOptionsChange = new EventEmitter();
@@ -158,11 +165,11 @@ export class SuiMultiSelect extends SuiSelectBase implements ICustom
}
}
- protected initialiseRenderedOption(option:ISelectRenderedOption):void {
- super.initialiseRenderedOption(option);
+ protected updateRenderedOption(rendered:SuiSelectOption):void {
+ super.updateRenderedOption(rendered);
// Boldens the item so it appears selected in the dropdown.
- option.isActive = !this.hasLabels && this.selectedOptions.indexOf(option.value) !== -1;
+ rendered.isActive = !this.hasLabels && this.selectedOptions.indexOf(rendered.option) !== -1;
}
public selectOption(option:T):void {
@@ -177,10 +184,6 @@ export class SuiMultiSelect extends SuiSelectBase implements ICustom
// Automatically refocus the search input for better keyboard accessibility.
this.focus();
-
- if (!this.hasLabels) {
- this.onAvailableOptionsRendered();
- }
}
public writeValue(values:U[]):void {
@@ -216,10 +219,6 @@ export class SuiMultiSelect extends SuiSelectBase implements ICustom
// Automatically refocus the search input for better keyboard accessibility.
this.focus();
-
- if (!this.hasLabels) {
- this.onAvailableOptionsRendered();
- }
}
public onQueryInputKeydown(event:KeyboardEvent):void {
diff --git a/src/modules/select/components/select-option.ts b/src/modules/select/components/select-option.ts
index 717901429..43ab1ea38 100644
--- a/src/modules/select/components/select-option.ts
+++ b/src/modules/select/components/select-option.ts
@@ -1,75 +1,75 @@
import {
Component, Input, HostBinding, HostListener, EventEmitter, ViewContainerRef,
- ViewChild, Renderer2, ElementRef, Output, ChangeDetectorRef
+ ViewChild, Renderer2, ElementRef, Output, ChangeDetectorRef, TemplateRef
} from "@angular/core";
import { SuiDropdownMenuItem } from "../../dropdown";
-import { HandledEvent } from "../../../misc/util";
-
-export interface ISelectRenderedOption {
- value:T;
- isActive?:boolean;
- formatter:(o:T) => string;
- usesTemplate:boolean;
- templateSibling:ViewContainerRef;
-}
+import { HandledEvent, SuiComponentFactory } from "../../../misc/util";
+import { IOptionContext } from "../classes/select-base";
@Component({
selector: "sui-select-option",
template: `
-
+
`
})
-export class SuiSelectOption extends SuiDropdownMenuItem implements ISelectRenderedOption {
+export class SuiSelectOption extends SuiDropdownMenuItem {
// Sets the Semantic UI classes on the host element.
@HostBinding("class.item")
private _optionClasses:boolean;
@Input()
- public value:T;
+ public option:T;
- // Fires when the option is selected, whether by clicking or by keyboard.
- @Output()
- public onSelected:EventEmitter;
+ @Input()
+ public query?:string;
@HostBinding("class.active")
public isActive:boolean;
- public renderedText?:string;
+ @Input()
+ public formatter?:(obj:T) => string;
- public set formatter(formatter:(obj:T) => string) {
- if (!this.usesTemplate) {
- this.renderedText = formatter(this.value);
- } else {
- this.renderedText = undefined;
- }
+ private _template?:TemplateRef>;
+
+ @Input()
+ public get template():TemplateRef> | undefined {
+ return this._template;
}
- public usesTemplate:boolean;
+ public set template(template:TemplateRef> | undefined) {
+ this._template = template;
+ if (this.template) {
+ this.componentFactory.createView(this.templateSibling, this.template, {
+ $implicit: this.option,
+ query: this.query
+ });
+ }
+ }
// Placeholder to draw template beside.
@ViewChild("templateSibling", { read: ViewContainerRef })
public templateSibling:ViewContainerRef;
- constructor(renderer:Renderer2, element:ElementRef) {
+ // Fires when the option is selected, whether by clicking or by keyboard.
+ @Output()
+ public onSelected:EventEmitter;
+
+ constructor(renderer:Renderer2, element:ElementRef, public componentFactory:SuiComponentFactory) {
// We inherit SuiDropdownMenuItem to automatically gain all keyboard navigation functionality.
// This is not done via adding the .item class because it isn't supported by Angular.
super(renderer, element);
- this._optionClasses = true;
this.isActive = false;
this.onSelected = new EventEmitter();
- // By default we make this function return an empty string, for the brief moment when it isn't displaying the correct label.
- this.formatter = o => "";
-
- this.usesTemplate = false;
+ this._optionClasses = true;
}
@HostListener("click", ["$event"])
public onClick(e:HandledEvent):void {
e.eventHandled = true;
- setTimeout(() => this.onSelected.emit(this.value));
+ this.onSelected.emit(this.option);
}
}
diff --git a/src/modules/select/components/select-options.ts b/src/modules/select/components/select-options.ts
new file mode 100644
index 000000000..4b99113de
--- /dev/null
+++ b/src/modules/select/components/select-options.ts
@@ -0,0 +1,12 @@
+import { Component, Input } from "@angular/core";
+
+@Component({
+ selector: "sui-select-options",
+ template: ``,
+ styles: [`
+:host {
+ display: none;
+}
+`]
+})
+export class SuiSelectOptions {}
diff --git a/src/modules/select/components/select.ts b/src/modules/select/components/select.ts
index f7f175b51..8155703e4 100644
--- a/src/modules/select/components/select.ts
+++ b/src/modules/select/components/select.ts
@@ -1,8 +1,8 @@
import { Component, ViewContainerRef, ViewChild, Output, EventEmitter, ElementRef, Directive, Input } from "@angular/core";
-import { ICustomValueAccessorHost, customValueAccessorFactory, CustomValueAccessor } from "../../../misc/util";
+import { ICustomValueAccessorHost, customValueAccessorFactory, CustomValueAccessor, SuiComponentFactory } from "../../../misc/util";
import { SuiLocalizationService } from "../../../behaviors/localization";
import { SuiSelectBase } from "../classes/select-base";
-import { ISelectRenderedOption } from "./select-option";
+import { SuiSelectOption } from "./select-option";
@Component({
selector: "sui-select",
@@ -29,7 +29,8 @@ import { ISelectRenderedOption } from "./select-option";
[menuAutoSelectFirst]="isSearchable">
-
+
+
{{ localeValues.noResultsMessage }}
@@ -57,8 +58,11 @@ export class SuiSelect extends SuiSelectBase implements ICustomValue
this._placeholder = placeholder;
}
- constructor(element:ElementRef, localizationService:SuiLocalizationService) {
- super(element, localizationService);
+ constructor(element:ElementRef,
+ componentFactory:SuiComponentFactory,
+ localizationService:SuiLocalizationService) {
+
+ super(element, componentFactory, localizationService);
this.selectedOptionChange = new EventEmitter();
}
@@ -122,17 +126,17 @@ export class SuiSelect extends SuiSelectBase implements ICustomValue
}
}
- protected initialiseRenderedOption(option:ISelectRenderedOption):void {
- super.initialiseRenderedOption(option);
+ protected updateRenderedOption(rendered:SuiSelectOption):void {
+ super.updateRenderedOption(rendered);
// Boldens the item so it appears selected in the dropdown.
- option.isActive = option.value === this.selectedOption;
+ rendered.isActive = rendered.option === this.selectedOption;
}
private drawSelectedOption():void {
// Updates the active class on the newly selected option.
- if (this._renderedOptions) {
- this.onAvailableOptionsRendered();
+ if (this._manualOptions) {
+ this.onManualOptionsRendered();
}
if (this.selectedOption != undefined && this.optionTemplate) {
diff --git a/src/modules/select/select.module.ts b/src/modules/select/select.module.ts
index 24fdfe55d..2d4ed6cad 100644
--- a/src/modules/select/select.module.ts
+++ b/src/modules/select/select.module.ts
@@ -6,6 +6,7 @@ import { SuiUtilityModule } from "../../misc/util";
import { SuiLocalizationModule } from "../../behaviors/localization";
import { SuiSelect, SuiSelectValueAccessor } from "./components/select";
import { SuiSelectOption } from "./components/select-option";
+import { SuiSelectOptions } from "./components/select-options";
import { SuiSelectSearch } from "./directives/select-search";
import { SuiMultiSelect, SuiMultiSelectValueAccessor } from "./components/multi-select";
import { SuiMultiSelectLabel } from "./components/multi-select-label";
@@ -21,6 +22,7 @@ import { SuiMultiSelectLabel } from "./components/multi-select-label";
declarations: [
SuiSelect,
SuiSelectOption,
+ SuiSelectOptions,
SuiSelectSearch,
SuiSelectValueAccessor,
SuiMultiSelect,
@@ -30,10 +32,14 @@ import { SuiMultiSelectLabel } from "./components/multi-select-label";
exports: [
SuiSelect,
SuiSelectOption,
+ SuiSelectOptions,
SuiSelectSearch,
SuiSelectValueAccessor,
SuiMultiSelect,
SuiMultiSelectValueAccessor
+ ],
+ entryComponents: [
+ SuiSelectOption
]
})
export class SuiSelectModule {}