1 | import { forwardRef, EventEmitter, Component, ChangeDetectionStrategy, ViewEncapsulation, ElementRef, ChangeDetectorRef, Input, Output, ViewChild, ContentChild, ContentChildren, NgModule } from '@angular/core';
|
2 | import { CommonModule } from '@angular/common';
|
3 | import { Header, Footer, PrimeTemplate, SharedModule } from 'primeng/api';
|
4 | import { DomHandler } from 'primeng/dom';
|
5 | import { ObjectUtils, FilterUtils } from 'primeng/utils';
|
6 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
7 | import { RippleModule } from 'primeng/ripple';
|
8 |
|
9 | const LISTBOX_VALUE_ACCESSOR = {
|
10 | provide: NG_VALUE_ACCESSOR,
|
11 | useExisting: forwardRef(() => Listbox),
|
12 | multi: true
|
13 | };
|
14 | class Listbox {
|
15 | constructor(el, cd) {
|
16 | this.el = el;
|
17 | this.cd = cd;
|
18 | this.checkbox = false;
|
19 | this.filter = false;
|
20 | this.filterMode = 'contains';
|
21 | this.metaKeySelection = true;
|
22 | this.showToggleAll = true;
|
23 | this.onChange = new EventEmitter();
|
24 | this.onClick = new EventEmitter();
|
25 | this.onDblClick = new EventEmitter();
|
26 | this.onModelChange = () => { };
|
27 | this.onModelTouched = () => { };
|
28 | this.disabledSelectedOptions = [];
|
29 | }
|
30 | get options() {
|
31 | return this._options;
|
32 | }
|
33 | set options(val) {
|
34 | let opts = this.optionLabel ? ObjectUtils.generateSelectItems(val, this.optionLabel) : val;
|
35 | this._options = opts;
|
36 | }
|
37 | get filterValue() {
|
38 | return this._filterValue;
|
39 | }
|
40 | set filterValue(val) {
|
41 | this._filterValue = val;
|
42 | }
|
43 | ngAfterContentInit() {
|
44 | this.templates.forEach((item) => {
|
45 | switch (item.getType()) {
|
46 | case 'item':
|
47 | this.itemTemplate = item.template;
|
48 | break;
|
49 | case 'header':
|
50 | this.headerTemplate = item.template;
|
51 | break;
|
52 | case 'footer':
|
53 | this.footerTemplate = item.template;
|
54 | break;
|
55 | default:
|
56 | this.itemTemplate = item.template;
|
57 | break;
|
58 | }
|
59 | });
|
60 | }
|
61 | writeValue(value) {
|
62 | this.value = value;
|
63 | this.setDisabledSelectedOptions();
|
64 | this.cd.markForCheck();
|
65 | }
|
66 | registerOnChange(fn) {
|
67 | this.onModelChange = fn;
|
68 | }
|
69 | registerOnTouched(fn) {
|
70 | this.onModelTouched = fn;
|
71 | }
|
72 | setDisabledState(val) {
|
73 | this.disabled = val;
|
74 | this.cd.markForCheck();
|
75 | }
|
76 | onOptionClick(event, option) {
|
77 | if (this.disabled || option.disabled || this.readonly) {
|
78 | return;
|
79 | }
|
80 | if (this.multiple) {
|
81 | if (this.checkbox)
|
82 | this.onOptionClickCheckbox(event, option);
|
83 | else
|
84 | this.onOptionClickMultiple(event, option);
|
85 | }
|
86 | else {
|
87 | this.onOptionClickSingle(event, option);
|
88 | }
|
89 | this.onClick.emit({
|
90 | originalEvent: event,
|
91 | option: option,
|
92 | value: this.value
|
93 | });
|
94 | this.optionTouched = false;
|
95 | }
|
96 | onOptionTouchEnd(event, option) {
|
97 | if (this.disabled || option.disabled || this.readonly) {
|
98 | return;
|
99 | }
|
100 | this.optionTouched = true;
|
101 | }
|
102 | onOptionDoubleClick(event, option) {
|
103 | if (this.disabled || option.disabled || this.readonly) {
|
104 | return;
|
105 | }
|
106 | this.onDblClick.emit({
|
107 | originalEvent: event,
|
108 | option: option,
|
109 | value: this.value
|
110 | });
|
111 | }
|
112 | onOptionClickSingle(event, option) {
|
113 | let selected = this.isSelected(option);
|
114 | let valueChanged = false;
|
115 | let metaSelection = this.optionTouched ? false : this.metaKeySelection;
|
116 | if (metaSelection) {
|
117 | let metaKey = (event.metaKey || event.ctrlKey);
|
118 | if (selected) {
|
119 | if (metaKey) {
|
120 | this.value = null;
|
121 | valueChanged = true;
|
122 | }
|
123 | }
|
124 | else {
|
125 | this.value = option.value;
|
126 | valueChanged = true;
|
127 | }
|
128 | }
|
129 | else {
|
130 | this.value = selected ? null : option.value;
|
131 | valueChanged = true;
|
132 | }
|
133 | if (valueChanged) {
|
134 | this.onModelChange(this.value);
|
135 | this.onChange.emit({
|
136 | originalEvent: event,
|
137 | value: this.value
|
138 | });
|
139 | }
|
140 | }
|
141 | onOptionClickMultiple(event, option) {
|
142 | let selected = this.isSelected(option);
|
143 | let valueChanged = false;
|
144 | let metaSelection = this.optionTouched ? false : this.metaKeySelection;
|
145 | if (metaSelection) {
|
146 | let metaKey = (event.metaKey || event.ctrlKey);
|
147 | if (selected) {
|
148 | if (metaKey) {
|
149 | this.removeOption(option);
|
150 | }
|
151 | else {
|
152 | this.value = [option.value];
|
153 | }
|
154 | valueChanged = true;
|
155 | }
|
156 | else {
|
157 | this.value = (metaKey) ? this.value || [] : [];
|
158 | this.value = [...this.value, option.value];
|
159 | valueChanged = true;
|
160 | }
|
161 | }
|
162 | else {
|
163 | if (selected) {
|
164 | this.removeOption(option);
|
165 | }
|
166 | else {
|
167 | this.value = [...this.value || [], option.value];
|
168 | }
|
169 | valueChanged = true;
|
170 | }
|
171 | if (valueChanged) {
|
172 | this.onModelChange(this.value);
|
173 | this.onChange.emit({
|
174 | originalEvent: event,
|
175 | value: this.value
|
176 | });
|
177 | }
|
178 | }
|
179 | onOptionClickCheckbox(event, option) {
|
180 | if (this.disabled || this.readonly) {
|
181 | return;
|
182 | }
|
183 | let selected = this.isSelected(option);
|
184 | if (selected) {
|
185 | this.removeOption(option);
|
186 | }
|
187 | else {
|
188 | this.value = this.value ? this.value : [];
|
189 | this.value = [...this.value, option.value];
|
190 | }
|
191 | this.onModelChange(this.value);
|
192 | this.onChange.emit({
|
193 | originalEvent: event,
|
194 | value: this.value
|
195 | });
|
196 | }
|
197 | removeOption(option) {
|
198 | this.value = this.value.filter(val => !ObjectUtils.equals(val, option.value, this.dataKey));
|
199 | }
|
200 | isSelected(option) {
|
201 | let selected = false;
|
202 | if (this.multiple) {
|
203 | if (this.value) {
|
204 | for (let val of this.value) {
|
205 | if (ObjectUtils.equals(val, option.value, this.dataKey)) {
|
206 | selected = true;
|
207 | break;
|
208 | }
|
209 | }
|
210 | }
|
211 | }
|
212 | else {
|
213 | selected = ObjectUtils.equals(this.value, option.value, this.dataKey);
|
214 | }
|
215 | return selected;
|
216 | }
|
217 | get allChecked() {
|
218 | if (this.filterValue) {
|
219 | return this.allFilteredSelected();
|
220 | }
|
221 | else {
|
222 | let optionCount = this.getEnabledOptionCount();
|
223 | let disabledSelectedOptionCount = this.disabledSelectedOptions.length;
|
224 | return this.value && this.options && (this.value.length > 0 && this.value.length == optionCount + disabledSelectedOptionCount);
|
225 | }
|
226 | }
|
227 | getEnabledOptionCount() {
|
228 | if (this.options) {
|
229 | let count = 0;
|
230 | for (let opt of this.options) {
|
231 | if (!opt.disabled) {
|
232 | count++;
|
233 | }
|
234 | }
|
235 | return count;
|
236 | }
|
237 | else {
|
238 | return 0;
|
239 | }
|
240 | }
|
241 | allFilteredSelected() {
|
242 | let allSelected;
|
243 | let options = this.filterValue ? this.getFilteredOptions() : this.options;
|
244 | if (this.value && options && options.length) {
|
245 | allSelected = true;
|
246 | for (let opt of this.options) {
|
247 | if (this.isItemVisible(opt)) {
|
248 | if (!this.isSelected(opt)) {
|
249 | allSelected = false;
|
250 | break;
|
251 | }
|
252 | }
|
253 | }
|
254 | }
|
255 | return allSelected;
|
256 | }
|
257 | onFilter(event) {
|
258 | this._filterValue = event.target.value;
|
259 | }
|
260 | toggleAll(event) {
|
261 | if (this.disabled || this.readonly || !this.options || this.options.length === 0) {
|
262 | return;
|
263 | }
|
264 | if (this.allChecked) {
|
265 | if (this.disabledSelectedOptions && this.disabledSelectedOptions.length > 0) {
|
266 | let value = [];
|
267 | value = [...this.disabledSelectedOptions];
|
268 | this.value = value;
|
269 | }
|
270 | else {
|
271 | this.value = [];
|
272 | }
|
273 | }
|
274 | else {
|
275 | if (this.options) {
|
276 | this.value = [];
|
277 | if (this.disabledSelectedOptions && this.disabledSelectedOptions.length > 0) {
|
278 | this.value = [...this.disabledSelectedOptions];
|
279 | }
|
280 | for (let i = 0; i < this.options.length; i++) {
|
281 | let opt = this.options[i];
|
282 | if (this.isItemVisible(opt) && !opt.disabled) {
|
283 | this.value.push(opt.value);
|
284 | }
|
285 | }
|
286 | }
|
287 | }
|
288 | this.onModelChange(this.value);
|
289 | this.onChange.emit({ originalEvent: event, value: this.value });
|
290 | event.preventDefault();
|
291 | }
|
292 | isItemVisible(option) {
|
293 | if (this.filterValue) {
|
294 | let visible;
|
295 | if (this.filterMode) {
|
296 | visible = FilterUtils[this.filterMode](option.label, this.filterValue, this.filterLocale);
|
297 | }
|
298 | else {
|
299 | visible = true;
|
300 | }
|
301 | return visible;
|
302 | }
|
303 | else {
|
304 | return true;
|
305 | }
|
306 | }
|
307 | onOptionKeyDown(event, option) {
|
308 | if (this.readonly) {
|
309 | return;
|
310 | }
|
311 | let item = event.currentTarget;
|
312 | switch (event.which) {
|
313 |
|
314 | case 40:
|
315 | var nextItem = this.findNextItem(item);
|
316 | if (nextItem) {
|
317 | nextItem.focus();
|
318 | }
|
319 | event.preventDefault();
|
320 | break;
|
321 |
|
322 | case 38:
|
323 | var prevItem = this.findPrevItem(item);
|
324 | if (prevItem) {
|
325 | prevItem.focus();
|
326 | }
|
327 | event.preventDefault();
|
328 | break;
|
329 |
|
330 | case 13:
|
331 | this.onOptionClick(event, option);
|
332 | event.preventDefault();
|
333 | break;
|
334 | }
|
335 | }
|
336 | findNextItem(item) {
|
337 | let nextItem = item.nextElementSibling;
|
338 | if (nextItem)
|
339 | return DomHandler.hasClass(nextItem, 'p-disabled') || DomHandler.isHidden(nextItem) ? this.findNextItem(nextItem) : nextItem;
|
340 | else
|
341 | return null;
|
342 | }
|
343 | findPrevItem(item) {
|
344 | let prevItem = item.previousElementSibling;
|
345 | if (prevItem)
|
346 | return DomHandler.hasClass(prevItem, 'p-disabled') || DomHandler.isHidden(prevItem) ? this.findPrevItem(prevItem) : prevItem;
|
347 | else
|
348 | return null;
|
349 | }
|
350 | getFilteredOptions() {
|
351 | let filteredOptions = [];
|
352 | if (this.filterValue) {
|
353 | for (let i = 0; i < this.options.length; i++) {
|
354 | let opt = this.options[i];
|
355 | if (this.isItemVisible(opt) && !opt.disabled) {
|
356 | filteredOptions.push(opt);
|
357 | }
|
358 | }
|
359 | return filteredOptions;
|
360 | }
|
361 | else {
|
362 | return this.options;
|
363 | }
|
364 | }
|
365 | onHeaderCheckboxFocus() {
|
366 | this.headerCheckboxFocus = true;
|
367 | }
|
368 | onHeaderCheckboxBlur() {
|
369 | this.headerCheckboxFocus = false;
|
370 | }
|
371 | setDisabledSelectedOptions() {
|
372 | if (this.options) {
|
373 | this.disabledSelectedOptions = [];
|
374 | if (this.value) {
|
375 | for (let opt of this.options) {
|
376 | if (opt.disabled && this.isSelected(opt)) {
|
377 | this.disabledSelectedOptions.push(opt.value);
|
378 | }
|
379 | }
|
380 | }
|
381 | }
|
382 | }
|
383 | }
|
384 | Listbox.decorators = [
|
385 | { type: Component, args: [{
|
386 | selector: 'p-listbox',
|
387 | template: `
|
388 | <div [ngClass]="'p-listbox p-component'" [ngStyle]="style" [class]="styleClass">
|
389 | <div class="p-listbox-header" *ngIf="headerFacet || headerTemplate">
|
390 | <ng-content select="p-header"></ng-content>
|
391 | <ng-container *ngTemplateOutlet="headerTemplate"></ng-container>
|
392 | </div>
|
393 | <div class="p-listbox-header" *ngIf="(checkbox && multiple && showToggleAll) || filter">
|
394 | <div class="p-checkbox p-component" *ngIf="checkbox && multiple && showToggleAll">
|
395 | <div class="p-hidden-accessible">
|
396 | <input type="checkbox" readonly="readonly" [checked]="allChecked" (focus)="onHeaderCheckboxFocus()" (blur)="onHeaderCheckboxBlur()" (keydown.space)="toggleAll($event)">
|
397 | </div>
|
398 | <div #headerchkbox class="p-checkbox-box" [ngClass]="{'p-highlight': allChecked, 'p-focus': headerCheckboxFocus}" (click)="toggleAll($event)">
|
399 | <span class="p-checkbox-icon" [ngClass]="{'pi pi-check':allChecked}"></span>
|
400 | </div>
|
401 | </div>
|
402 | <div class="p-listbox-filter-container" *ngIf="filter">
|
403 | <input type="text" [value]="filterValue||''" (input)="onFilter($event)" class="p-listbox-filter p-inputtext p-component" [disabled]="disabled" [attr.placeholder]="filterPlaceHolder" [attr.aria-label]="ariaFilterLabel">
|
404 | <span class="p-listbox-filter-icon pi pi-search"></span>
|
405 | </div>
|
406 | </div>
|
407 | <div [ngClass]="'p-listbox-list-wrapper'" [ngStyle]="listStyle" [class]="listStyleClass">
|
408 | <ul class="p-listbox-list" role="listbox" aria-multiselectable="multiple">
|
409 | <li *ngFor="let option of options; let i = index;" [style.display]="isItemVisible(option) ? 'flex' : 'none'" [attr.tabindex]="option.disabled ? null : '0'" pRipple
|
410 | [ngClass]="{'p-listbox-item':true,'p-highlight':isSelected(option), 'p-disabled': option.disabled}" role="option" [attr.aria-label]="option.label"
|
411 | [attr.aria-selected]="isSelected(option)" (click)="onOptionClick($event,option)" (dblclick)="onOptionDoubleClick($event,option)" (touchend)="onOptionTouchEnd($event,option)" (keydown)="onOptionKeyDown($event,option)">
|
412 | <div class="p-checkbox p-component" *ngIf="checkbox && multiple">
|
413 | <div class="p-checkbox-box" [ngClass]="{'p-highlight':isSelected(option)}">
|
414 | <span class="p-checkbox-icon" [ngClass]="{'pi pi-check':isSelected(option)}"></span>
|
415 | </div>
|
416 | </div>
|
417 | <span *ngIf="!itemTemplate">{{option.label}}</span>
|
418 | <ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: option, index: i}"></ng-container>
|
419 | </li>
|
420 | </ul>
|
421 | </div>
|
422 | <div class="p-listbox-footer" *ngIf="footerFacet || footerTemplate">
|
423 | <ng-content select="p-footer"></ng-content>
|
424 | <ng-container *ngTemplateOutlet="footerTemplate"></ng-container>
|
425 | </div>
|
426 | </div>
|
427 | `,
|
428 | providers: [LISTBOX_VALUE_ACCESSOR],
|
429 | changeDetection: ChangeDetectionStrategy.OnPush,
|
430 | encapsulation: ViewEncapsulation.None,
|
431 | styles: [".p-listbox-list-wrapper{overflow:auto}.p-listbox-list{list-style-type:none;margin:0;padding:0}.p-listbox-item{cursor:pointer;overflow:hidden;position:relative}.p-listbox-header,.p-listbox-item{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.p-listbox-filter-container{-ms-flex:1 1 auto;flex:1 1 auto;position:relative}.p-listbox-filter-icon{margin-top:-.5rem;position:absolute;top:50%}.p-listbox-filter{width:100%}"]
|
432 | },] }
|
433 | ];
|
434 | Listbox.ctorParameters = () => [
|
435 | { type: ElementRef },
|
436 | { type: ChangeDetectorRef }
|
437 | ];
|
438 | Listbox.propDecorators = {
|
439 | multiple: [{ type: Input }],
|
440 | style: [{ type: Input }],
|
441 | styleClass: [{ type: Input }],
|
442 | listStyle: [{ type: Input }],
|
443 | listStyleClass: [{ type: Input }],
|
444 | readonly: [{ type: Input }],
|
445 | disabled: [{ type: Input }],
|
446 | checkbox: [{ type: Input }],
|
447 | filter: [{ type: Input }],
|
448 | filterMode: [{ type: Input }],
|
449 | filterLocale: [{ type: Input }],
|
450 | metaKeySelection: [{ type: Input }],
|
451 | dataKey: [{ type: Input }],
|
452 | showToggleAll: [{ type: Input }],
|
453 | optionLabel: [{ type: Input }],
|
454 | ariaFilterLabel: [{ type: Input }],
|
455 | filterPlaceHolder: [{ type: Input }],
|
456 | onChange: [{ type: Output }],
|
457 | onClick: [{ type: Output }],
|
458 | onDblClick: [{ type: Output }],
|
459 | headerCheckboxViewChild: [{ type: ViewChild, args: ['headerchkbox',] }],
|
460 | headerFacet: [{ type: ContentChild, args: [Header,] }],
|
461 | footerFacet: [{ type: ContentChild, args: [Footer,] }],
|
462 | templates: [{ type: ContentChildren, args: [PrimeTemplate,] }],
|
463 | options: [{ type: Input }],
|
464 | filterValue: [{ type: Input }]
|
465 | };
|
466 | class ListboxModule {
|
467 | }
|
468 | ListboxModule.decorators = [
|
469 | { type: NgModule, args: [{
|
470 | imports: [CommonModule, SharedModule, RippleModule],
|
471 | exports: [Listbox, SharedModule],
|
472 | declarations: [Listbox]
|
473 | },] }
|
474 | ];
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 | export { LISTBOX_VALUE_ACCESSOR, Listbox, ListboxModule };
|
481 |
|