@@ -2,26 +2,36 @@ import {Component, Directive, Provider, Input, ViewChild, HostBinding, ElementRe
22import { ControlValueAccessor , NG_VALUE_ACCESSOR } from 'angular2/common' ;
33import { Search , SearchValueAccessor } from '../search' ;
44import { DropdownMenu } from '../dropdown' ;
5+ import { KEYCODE } from '../dropdown/dropdown.service' ;
56
67@Component ( {
78 selector : 'sui-select' ,
89 directives : [ DropdownMenu ] ,
9- inputs : [ 'placeholder' , 'options' , 'optionsField' , 'isSearchable' , 'searchDelay' , 'isDisabled' ] ,
10+ inputs : [ 'placeholder' , 'options' , 'optionsField' , 'isSearchable' , 'searchDelay' , 'isDisabled' , 'allowMultiple' ] ,
1011 outputs : [ 'selectedOptionChange' ] ,
1112 host : {
1213 '[class.visible]' : 'isOpen' ,
1314 '[class.disabled]' : 'isDisabled'
1415 } ,
1516 template : `
1617<i class="dropdown icon"></i>
17- <input *ngIf="isSearchable" class="search" type="text" autocomplete="off" [(ngModel)]="query">
18- <div *ngIf="!selectedOption" class="default text" [class.filtered]="query" (click)="focus()">{{ placeholder }}</div>
18+ <!-- Multi-select labels -->
19+ <a *ngFor="#selected of selectedOptions; #i = index" class="ui label" (click)="selectedOptionClick($event)">
20+ <content [innerHTML]="selectedOptionsHTML[i]"></content>
21+ <i class="delete icon" (click)="deselectOption(selected); selectedOptionClick($event)"></i>
22+ </a>
23+ <!-- Search input box -->
24+ <input *ngIf="isSearchable" class="search" type="text" autocomplete="off" [(ngModel)]="query" (keydown)="searchKeyDown($event)">
25+ <!-- Single-select label -->
26+ <div *ngIf="!selectedOption" class="default text" [class.filtered]="query">{{ placeholder }}</div>
1927<div *ngIf="selectedOption" class="text" [class.filtered]="query" [innerHTML]="selectedOptionHTML"></div>
28+ <!-- Select dropdown menu -->
2029<div class="menu" suiDropdownMenu>
2130 <ng-content></ng-content>
2231 <div *ngIf="!results.length" class="message">No Results</div>
2332</div>
24- `
33+ ` ,
34+ styles : [ `:host input.search { width: 12em !important; }` ]
2535} )
2636export class Select extends Search {
2737 @ViewChild ( DropdownMenu ) protected _menu :DropdownMenu ;
@@ -32,12 +42,17 @@ export class Select extends Search {
3242
3343 @HostBinding ( 'class.search' )
3444 public isSearchable :boolean = false ;
45+ @HostBinding ( 'class.multiple' )
46+ public allowMultiple :boolean = false ;
3547 protected searchDelay :number = 0 ;
3648 @HostBinding ( 'class.loading' )
3749 protected _loading :boolean = false ;
3850 public placeholder :string = "Select one" ;
3951 public selectedOptionHTML :string ;
4052
53+ public selectedOptions :any = [ ] ;
54+ public selectedOptionsHTML :Array < string > = [ ] ;
55+
4156 @HostBinding ( 'class.active' )
4257 public get isOpen ( ) :boolean {
4358 return this . _service . isOpen ;
@@ -48,27 +63,33 @@ export class Select extends Search {
4863 }
4964
5065 protected get results ( ) :Array < any > {
66+ var results = this . options ;
5167 if ( this . isSearchable || this . _optionsLookup ) {
52- return this . _results ;
68+ results = this . _results ;
69+ }
70+ if ( this . allowMultiple ) {
71+ results = results . filter ( r => ( this . selectedOptions || [ ] ) . indexOf ( r ) == - 1 ) ;
5372 }
54- return this . options ;
73+ return results ;
5574 }
5675
5776 constructor ( private el :ElementRef ) {
5877 super ( el ) ;
5978 this . _allowEmptyQuery = true ;
6079
80+ this . _service . autoClose = "outsideClick" ;
81+
6182 this . _service . itemClass = "item" ;
6283 this . _service . itemSelectedClass = "selected" ;
6384
6485 this . _service . isOpenChange . subscribe ( ( isOpen :boolean ) => {
6586 if ( isOpen ) {
66- if ( this . isSearchable ) {
87+ if ( this . isSearchable && ! this . _service . selectedItem ) {
6788 this . _service . selectNextItem ( ) ;
6889 }
6990 }
7091 else {
71- if ( this . query ) {
92+ if ( this . query && ! this . allowMultiple ) {
7293 if ( this . _service . selectedItem ) {
7394 ( < HTMLElement > this . _service . selectedItem ) . click ( ) ;
7495 return ;
@@ -79,18 +100,62 @@ export class Select extends Search {
79100 } ) ;
80101 }
81102
82- public selectOption ( result :any , valueHTML :string ) :void {
83- super . select ( result ) ;
103+ public selectOption ( option :any , valueHTML :string ) :void {
104+ if ( ! this . allowMultiple ) {
105+ super . select ( option ) ;
106+ this . selectedOptionHTML = valueHTML ;
107+ }
108+ else {
109+ this . selectedOptions = this . selectedOptions || [ ] ;
110+ this . selectedOptions . push ( option ) ;
111+ this . selectedOptionsHTML . push ( valueHTML ) ;
112+
113+ this . selectedOptionChange . emit ( this . selectedOptions ) ;
114+ this . onItemSelected . emit ( option ) ;
115+ }
116+ if ( this . isSearchable ) {
117+ this . focusFirstItem ( ) ;
118+ this . focusSearch ( ) ;
119+ }
84120 this . _query = "" ;
85- this . selectedOptionHTML = valueHTML ;
121+ if ( this . isSearchable ) {
122+ this . search ( ( ) => { } ) ;
123+ }
124+ }
125+
126+ public deselectOption ( option :any ) {
127+ var index = this . selectedOptions . indexOf ( option ) ;
128+ this . selectedOptions . splice ( index , 1 ) ;
129+ this . selectedOptionsHTML . splice ( index , 1 ) ;
130+ this . selectedOptionChange . emit ( this . selectedOptions ) ;
131+ }
132+
133+ //noinspection JSMethodCanBeStatic
134+ private selectedOptionClick ( event ) {
135+ event . stopPropagation ( ) ;
86136 }
87137
88- private focus ( ) {
138+ private focusSearch ( ) {
89139 if ( this . isSearchable ) {
90140 this . _service . dropdownElement . nativeElement . querySelector ( "input" ) . focus ( ) ;
91141 }
92142 }
93143
144+ private focusFirstItem ( ) {
145+ setTimeout ( ( ) => {
146+ this . _service . selectedItem = null ;
147+ this . _service . selectNextItem ( ) ;
148+ } )
149+ }
150+
151+ public writeValue ( value :any ) {
152+ if ( this . allowMultiple ) {
153+ this . selectedOptions = value ;
154+ return ;
155+ }
156+ this . selectedOption = value ;
157+ }
158+
94159 @HostListener ( 'click' , [ '$event' ] )
95160 public click ( event :MouseEvent ) :boolean {
96161 event . stopPropagation ( ) ;
@@ -100,6 +165,7 @@ export class Select extends Search {
100165 this . search ( ( ) => {
101166 this . _loading = false ;
102167 this . isOpen = true ;
168+ this . focusSearch ( ) ;
103169 } ) ;
104170 }
105171 else if ( ( < Element > event . target ) . tagName != "INPUT" ) {
@@ -108,6 +174,16 @@ export class Select extends Search {
108174 }
109175 return false ;
110176 }
177+
178+ public searchKeyDown ( event ) {
179+ if ( event . which == KEYCODE . BACKSPACE && ! this . _query ) {
180+ var selectedOptions = this . selectedOptions || [ ] ;
181+ var lastSelectedOption = selectedOptions [ selectedOptions . length - 1 ] ;
182+ if ( lastSelectedOption ) {
183+ this . deselectOption ( lastSelectedOption ) ;
184+ }
185+ }
186+ }
111187}
112188
113189@Component ( {
@@ -122,8 +198,9 @@ export class SelectOption {
122198
123199 constructor ( private host :Select , private el :ElementRef ) { }
124200
125- @HostListener ( 'click' )
126- public click ( ) :boolean {
201+ @HostListener ( 'click' , [ '$event' ] )
202+ public click ( event ) :boolean {
203+ event . stopPropagation ( ) ;
127204 this . host . selectOption ( this . value , this . el . nativeElement . innerHTML ) ;
128205 return false ;
129206 }
0 commit comments