Skip to content

Commit

Permalink
feat: Implement custom template for an option #20
Browse files Browse the repository at this point in the history
Example: <ng-template ngx-select-option let-option let-i="index" let-si="subIndex" let-highlight="highlight">...
  • Loading branch information
optimistex committed Feb 15, 2018
1 parent 32ba202 commit 0ad0271
Show file tree
Hide file tree
Showing 11 changed files with 65 additions and 39 deletions.
9 changes: 8 additions & 1 deletion src/app/demo/select/rich-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ <h3>Select a color</h3>
<div class="example-block">
<div class="example-block__item">
<ngx-select [allowClear]="true"
[items]="items"
[items]="items" optionValueField="hex" optionTextField="name"
[disabled]="ngxDisabled"
[(ngModel)]="ngxValue"
placeholder="No city selected">

<ng-template ngx-select-option let-option let-highlight="highlight">
<span class="color-box" [style]="style('background-color:' + option.value)"></span>
<span [innerHtml]="highlight"></span>
({{option.data.hex}})
</ng-template>

</ngx-select>
<p></p>
<div class="alert alert-secondary">
Expand Down
22 changes: 10 additions & 12 deletions src/app/demo/select/rich-demo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
import {Component, ViewEncapsulation} from '@angular/core';
import {DomSanitizer, SafeStyle} from '@angular/platform-browser';

const COLORS = [
{'name': 'Blue 10', 'hex': '#C0E6FF'},
Expand Down Expand Up @@ -46,7 +47,7 @@ const COLORS = [
@Component({
selector: 'rich-demo',
templateUrl: './rich-demo.html',
styles: [`colorbox, .colorbox {
styles: [`.color-box {
display: inline-block;
height: 14px;
width: 14px;
Expand All @@ -55,19 +56,16 @@ const COLORS = [
}`],
encapsulation: ViewEncapsulation.None // Enable dynamic HTML styles
})
export class RichDemoComponent implements OnInit {
public items: any[] = [];
export class RichDemoComponent {
public items: any[] = COLORS;

public ngxValue: any = [];
public ngxDisabled = false;

public ngOnInit(): any {
COLORS.forEach((color: { name: string, hex: string, disabled: boolean }) => {
this.items.push({
id: color.hex,
text: `<colorbox style='background-color:${color.hex};'></colorbox>${color.name} (${color.hex})`,
disabled: color.disabled
});
});
constructor(public sanitizer: DomSanitizer) {
}

style(data: string): SafeStyle {
return this.sanitizer.bypassSecurityTrustStyle(data);
}
}
1 change: 1 addition & 0 deletions src/app/lib/ngx-select/ngx-select.classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class NgxSelectOption implements INgxSelectOption, INgxSelectOptionBase {
constructor(public value: number | string,
public text: string,
public disabled: boolean,
public data: any,
private _parent: NgxSelectOptGroup = null) {
}

Expand Down
21 changes: 13 additions & 8 deletions src/app/lib/ngx-select/ngx-select.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
'ngx-select__selected-single ui-select-match-text pull-left float-left': optionsSelected.length,
'ngx-select__allow-clear ui-select-allow-clear': optionsSelected.length && allowClear
}"
[innerHtml]="sanitize(optionsSelected.length ? optionsSelected[0].text : placeholder)"></span>
[innerHtml]="optionsSelected.length ? sanitize(optionsSelected[0].text) : placeholder"></span>
<i class="dropdown-toggle pull-right float-right"></i>
<i class="ngx-select__toggle-caret caret pull-right float-right"></i>
<a class="ngx-select__clear btn btn-sm btn-link pull-right float-right" *ngIf="canClearNotMultiple()"
Expand Down Expand Up @@ -61,18 +61,23 @@
<ul #choiceMenu role="menu" class="ngx-select__choices ui-select-choices dropdown-menu"
[class.show]="optionsOpened && subjOptions.value.length">
<li class="ngx-select__item-group" role="menuitem"
*ngFor="let opt of optionsFiltered; trackBy: trackByOption; let idx=index">
<div class="divider dropdown-divider" *ngIf="opt.type === 'optgroup' && (idx > 0)"></div>
*ngFor="let opt of optionsFiltered; trackBy: trackByOption; let idxGroup=index">
<div class="divider dropdown-divider" *ngIf="opt.type === 'optgroup' && (idxGroup > 0)"></div>
<div class="dropdown-header" *ngIf="opt.type === 'optgroup'">{{opt.label}}</div>

<a href="#" #choiceItem *ngFor="let option of (opt.optionsFiltered || [opt]); trackBy: trackByOption"
class="ngx-select__item ui-select-choices-row dropdown-item"
<a href="#" #choiceItem class="ngx-select__item ui-select-choices-row dropdown-item"
*ngFor="let option of (opt.optionsFiltered || [opt]); trackBy: trackByOption; let idxOption = index"
[ngClass]="{
'ngx-select__item_active active': isOptionActive(option, choiceItem),
'ngx-select__item_disabled disabled': option.disabled
}"
(mouseenter)="optionActivate(option)" (click)="optionSelect(option, $event)"
[innerHtml]="highlightOption(option)">
}" (mouseenter)="optionActivate(option)" (click)="optionSelect(option, $event)">
<ng-template #defaultTemplateOption let-option let-highlight="highlight">
<span [innerHtml]="highlight"></span>
</ng-template>

<ng-container [ngTemplateOutlet]="templateOption || defaultTemplateOption"
[ngTemplateOutletContext]="{$implicit: option, highlight: highlightOption(option),
index: idxGroup, subIndex: idxOption}"></ng-container>
</a>
</li>
<li class="ngx-select__item ngx-select__item_no-found dropdown-header" *ngIf="!optionsFiltered.length">
Expand Down
22 changes: 11 additions & 11 deletions src/app/lib/ngx-select/ngx-select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ describe('NgxSelectComponent', () => {
});

it('first item is active', () => {
expect(selectItemActive(1).innerHTML).toBe('item zero');
expect(selectItemActive(1).innerHTML).toContain('item zero');
});
});

Expand All @@ -398,27 +398,27 @@ describe('NgxSelectComponent', () => {
it('activate last item by press the button arrow right', () => {
formControlInput(1).dispatchEvent(createKeyboardEvent('keydown', 39)); // arrow right
fixture.detectChanges();
expect(selectItemActive(1).innerHTML).toBe('item 100');
expect(selectItemActive(1).innerHTML).toContain('item 100');
});

it('activate previous item by press the button arrow up', () => {
formControlInput(1).dispatchEvent(createKeyboardEvent('keydown', 39)); // arrow right
formControlInput(1).dispatchEvent(createKeyboardEvent('keydown', 38)); // arrow up
fixture.detectChanges();
expect(selectItemActive(1).innerHTML).toBe('item 99');
expect(selectItemActive(1).innerHTML).toContain('item 99');
});

it('activate first item by press the button arrow left', () => {
formControlInput(1).dispatchEvent(createKeyboardEvent('keydown', 39)); // arrow right
formControlInput(1).dispatchEvent(createKeyboardEvent('keydown', 37)); // arrow left
fixture.detectChanges();
expect(selectItemActive(1).innerHTML).toBe('item 1');
expect(selectItemActive(1).innerHTML).toContain('item 1');
});

it('activate next item by press the button arrow down', () => {
formControlInput(1).dispatchEvent(createKeyboardEvent('keydown', 40)); // arrow down
fixture.detectChanges();
expect(selectItemActive(1).innerHTML).toBe('item 2');
expect(selectItemActive(1).innerHTML).toContain('item 2');
});

afterEach(() => {
Expand Down Expand Up @@ -763,8 +763,8 @@ describe('NgxSelectComponent', () => {
formControl(1).click();
fixture.detectChanges();
expect(selectItemList(1).length).toBe(4);
expect(selectItemList(1)[0].innerHTML).toBe('i0');
expect(selectItemList(1)[1].innerHTML).toBe('i1');
expect(selectItemList(1)[0].innerHTML).toContain('i0');
expect(selectItemList(1)[1].innerHTML).toContain('i1');
});

it('objects with children fields by default field names', () => {
Expand Down Expand Up @@ -822,14 +822,14 @@ describe('NgxSelectComponent', () => {
expect(selectedItem(1).innerHTML).toBe(items1[0].text);
formControl(1).click();
fixture.detectChanges();
expect(selectItemActive(1).innerHTML).toBe(items1[0].text);
expect(selectItemActive(1).innerHTML).toContain(items1[0].text);
});

it('by a FormControl attribute and selected item must be active in menu', () => {
expect(selectedItem(2).innerHTML).toBe(items1[0].text);
formControl(2).click();
fixture.detectChanges();
expect(selectItemActive(2).innerHTML).toBe(items1[0].text);
expect(selectItemActive(2).innerHTML).toContain(items1[0].text);
});
});

Expand Down Expand Up @@ -881,14 +881,14 @@ describe('NgxSelectComponent', () => {
expect(selectedItem(1).innerHTML).toBe(items1[1].text);
formControl(1).click();
fixture.detectChanges();
expect(selectItemActive(1).innerHTML).toBe(items1[1].text);
expect(selectItemActive(1).innerHTML).toContain(items1[1].text);
});

it('by a FormControl attribute and selected item must be active in menu', () => {
expect(selectedItem(2).innerHTML).toBe(items1[1].text);
formControl(2).click();
fixture.detectChanges();
expect(selectItemActive(2).innerHTML).toBe(items1[1].text);
expect(selectItemActive(2).innerHTML).toContain(items1[1].text);
});
});

Expand Down
8 changes: 6 additions & 2 deletions src/app/lib/ngx-select/ngx-select.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
AfterContentChecked, DoCheck, Input, Output, ViewChild,
Component, ElementRef, EventEmitter, forwardRef, HostListener, IterableDiffer, IterableDiffers, ChangeDetectorRef
Component, ElementRef, EventEmitter, forwardRef, HostListener, IterableDiffer, IterableDiffers, ChangeDetectorRef, ContentChild,
TemplateRef
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
Expand All @@ -22,6 +23,7 @@ import 'rxjs/add/operator/do';
import * as lodashNs from 'lodash';
import * as escapeStringNs from 'escape-string-regexp';
import {NgxSelectOptGroup, NgxSelectOption, TSelectOption} from './ngx-select.classes';
import {NgxSelectOptionDirective} from './ngx-templates.directive';

const _ = lodashNs;
const escapeString = escapeStringNs;
Expand Down Expand Up @@ -76,6 +78,8 @@ export class NgxSelectComponent implements ControlValueAccessor, DoCheck, AfterC
@ViewChild('input') protected inputElRef: ElementRef;
@ViewChild('choiceMenu') protected choiceMenuElRef: ElementRef;

@ContentChild(NgxSelectOptionDirective, {read: TemplateRef}) templateOption: NgxSelectOptionDirective;

public optionsOpened = false;
public optionsFiltered: TSelectOption[];

Expand Down Expand Up @@ -461,7 +465,7 @@ export class NgxSelectComponent implements ControlValueAccessor, DoCheck, AfterC
} else {
return null;
}
return new NgxSelectOption(value, text, disabled, parent);
return new NgxSelectOption(value, text, disabled, data, parent);
}

//////////// interface ControlValueAccessor ////////////
Expand Down
3 changes: 2 additions & 1 deletion src/app/lib/ngx-select/ngx-select.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ export interface INgxSelectOptionBase {

export interface INgxSelectOption {
value: number | string;
text: string;
text: string; // text for displaying and searching
disabled: boolean;
data: any; // original data
}

export interface INgxSelectOptGroup {
Expand Down
5 changes: 3 additions & 2 deletions src/app/lib/ngx-select/ngx-select.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {NgxSelectComponent} from './ngx-select.component';
import {NgxSelectOptionDirective} from './ngx-templates.directive';

@NgModule({
imports: [
CommonModule
],
declarations: [NgxSelectComponent],
exports: [NgxSelectComponent]
declarations: [NgxSelectComponent, NgxSelectOptionDirective],
exports: [NgxSelectComponent, NgxSelectOptionDirective]
})
export class NgxSelectModule {
}
7 changes: 7 additions & 0 deletions src/app/lib/ngx-select/ngx-templates.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {Directive, TemplateRef} from '@angular/core';

@Directive({selector: '[ngx-select-option]'})
export class NgxSelectOptionDirective {
constructor(public template: TemplateRef<any>) {
}
}
2 changes: 2 additions & 0 deletions src/app/lib/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './ngx-select/ngx-select.module';
export * from './ngx-select/ngx-select.component';
export * from './ngx-select/ngx-select.interfaces';
export * from './ngx-select/ngx-select.classes';

export * from './ngx-select/ngx-templates.directive';
4 changes: 2 additions & 2 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
[],
"kebab-case"
],
"component-selector": [
true,
Expand Down

0 comments on commit 0ad0271

Please sign in to comment.