<!DOCTYPE html>
<html>
<head>
<base href="." />
<title>angular playground</title>
<link rel="stylesheet" href="style.css" />
<script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js/dist/zone.js"></script>
<script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
</head>
<body>
<my-app>
loading...
</my-app>
</body>
</html>
.en-clearfix, .en-multiselect-container > div.en-ms-output {
*zoom: 1;
}
.en-clearfix:before, .en-multiselect-container > div.en-ms-output:before, .en-clearfix:after, .en-multiselect-container > div.en-ms-output:after {
content: " ";
display: table;
}
.en-clearfix:after, .en-multiselect-container > div.en-ms-output:after {
clear: both;
}
.en-input, .en-multiselect-container > div.en-ms-input > div:first-child > input {
padding: 10px;
border: none;
border-bottom: 3px solid #07ECB6;
border-radius: 0px;
background-color: transparent;
font-size: 1.14em;
font-family: "Nunito", sans-serif;
}
.en-input.ng-invalid, .en-multiselect-container > div.en-ms-input > div:first-child > input.ng-invalid {
border-left: 4px solid red;
}
.en-multiselect-container div.en-ms-preview-hidden {
display: none;
}
.en-multiselect-container > div.en-ms-input {
position: relative;
}
.en-multiselect-container > div.en-ms-input > div:first-child > input {
width: calc(100% - 20px);
}
.en-multiselect-container > div.en-ms-input > div:last-child {
position: absolute;
top: 50px;
left: 0;
min-width: 100%;
width: auto;
background-color: #ffffff;
border: 1px solid #F4F4F4;
z-index: 400;
max-height: 300px;
overflow: hidden;
border-radius: 4px;
box-shadow: 2px 2px 9px -1px rgba(0, 0, 0, 0.51);
}
.en-multiselect-container > div.en-ms-input > div:last-child > div {
border-bottom: 1px solid #F4F4F4;
padding: 15px;
}
.en-multiselect-container > div.en-ms-input > div:last-child > div > span.icon-add {
font-size: 0.89em;
position: relative;
top: 2px;
padding-left: 0px;
padding-right: 0px;
-webkit-transition: all ease-out 0.2s;
transition: all ease-out 0.2s;
overflow: hidden;
}
.en-multiselect-container > div.en-ms-input > div:last-child > div:hover {
cursor: pointer;
}
.en-multiselect-container > div.en-ms-input > div:last-child > div:hover span {
color: #07ECB6;
}
.en-multiselect-container > div.en-ms-input > div:last-child > div:last-child {
border-bottom: none;
}
.en-multiselect-container > div.en-ms-output {
padding: 10px;
}
.en-multiselect-container > div.en-ms-output > div {
margin-right: 4px;
margin-bottom: 4px;
border: 1px solid #07ECB6;
float: left;
border-radius: 15px;
padding: 6px 9px;
}
.en-multiselect-container > div.en-ms-output > div span {
display: block;
float: left;
font-size: 0.92em;
color: #4C4849;
}
.en-multiselect-container > div.en-ms-output > div:not(.en-ms-default-all):hover {
border-color: red;
cursor: pointer;
}
.en-multiselect-container > div.en-ms-output > div:not(.en-ms-default-all):hover > span {
color: red;
}
System.config({
//use typescript for compilation
transpiler: 'typescript',
//typescript compiler options
typescriptOptions: {
emitDecoratorMetadata: true
},
paths: {
'npm:': 'https://unpkg.com/'
},
//map tells the System loader where to look for things
map: {
'app': './src',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'rxjs': 'npm:rxjs',
'typescript': 'npm:typescript@2.2.1/lib/typescript.js'
},
//packages defines our app package
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
}
}
});
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app';
platformBrowserDynamic().bootstrapModule(AppModule)
//our root app component
import {Component, NgModule, VERSION} from '@angular/core'
import { FormBuilder, ReactiveFormsModule, FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'
import { URLSearchParams, Jsonp, JsonpModule } from '@angular/http'
import { MultiSelectComponent } from './multiselect.component'
import { MultiSelect } from './multiselect.model'
import { Observable } from 'rxjs/Observable'
import 'rxjs/Rx'
@Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<multiselect
[placeholder]="'These Options'"
[options]="selectOptions"
(onOptionsUpdate)="onMultiselectUpdate($event)">
</multiselect>
</div>
<div>
<h2>Currently Selected</h2>
<span [innerHtml]="selected"></span>
</div>
`,
})
export class App {
name:string;
selectOptions : Observable<MultiSelect[]>;
selected: string;
onMultiselectUpdate(selections: MultiSelect[]) {
this.selected = JSON.stringify(selections);
}
constructor(private jsonp: Jsonp) {
this.name = `Angular! v${VERSION.full}`;
this.selectOptions = this.jsonp
.get('//krow-devapi.herokuapp.com/api/engage/contributors?callback=JSONP_CALLBACK')
.map((request) => request.json())
.map((conts: Contributor[]) => conts.map((cont: Contributor) => {
var x = new MultiSelect(cont.id, cont.firstName + ' ' + cont.lastName;
console.log(x);
return x;
})));
// super();
}
}
@NgModule({
imports: [ BrowserModule, FormsModule, ReactiveFormsModule, JsonpModule ],
declarations: [ App, MultiSelectComponent ],
bootstrap: [ App ]
})
export class AppModule {}
import { Component, ViewChild, Output, Input, EventEmitter, HostListener } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import { MultiSelect } from './multiselect.model';
@Component({
selector: 'multiselect',
templateUrl: 'src/multiselect.template.html'
})
export class MultiSelectComponent {
// Parent comp. passes in observable of
// selectable data
@Input() options: Observable<MultiSelect[]>;
@Input() placeholder: string;
// When the data is chosen, we pass back the
// newly selected values
@Output() onOptionsUpdate = new EventEmitter<MultiSelect[]>();
@ViewChild('input') input;
public search: FormControl = new FormControl();
private model: MultiSelect[] = new Array<MultiSelect>();
public previewItems: MultiSelect[] = new Array<MultiSelect>();
public selected: MultiSelect[] = new Array<MultiSelect>();
public hidePreviewItems: boolean = true;
public hasPreviewItems: boolean = false;
constructor() {
this.search.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.subscribe((value: string) => {
if (value == '')
this.previewItems = [];
else
this.previewItems = this.getMatching(value);
});
}
ngOnInit() {
this.options.subscribe((selections: MultiSelect[]) => this.model = selections);
}
onItemDeselect(selected) : void {
// Remove from selected list
for (let i = 0; i < this.selected.length; i++) {
if (this.selected[i].value == selected.value) {
this.selected.splice(i, 1);
break;
}
}
// Mark not selected in model
for (let i = 0; i < this.model.length; i++) {
if (this.model[i].value == selected.value) {
this.model[i].selected = false;
break;
}
}
this.onOptionsUpdate.emit(this.selected);
}
onItemSelect(selected) : void {
this.selected.push(selected);
this.previewItems = [];
this.input.nativeElement.value = '';
for (let i = 0; i < this.model.length; i++) {
if (this.model[i].value == selected.value) {
this.model[i].selected = true;
break;
}
}
this.hidePreviewItems = true;
this.onOptionsUpdate.emit(this.selected);
}
onInputFocus() : void {
this.hidePreviewItems = false;
}
onInputBlur() : void {
let me = this;
setTimeout(() => {
if (!me.hidePreviewItems) me.hidePreviewItems = true;
}, 120);
}
getMatching(keyword: string) : MultiSelect[] {
let results : MultiSelect[] = [];
keyword = this.trim(keyword).toLowerCase();
for(let i = 0; i < this.model.length; i++) {
if (!this.model[i].selected && this.model[i].display.toString().toLowerCase().indexOf(keyword) != -1) {
if(!this.exists(results, this.model[i])) {
results.push(this.model[i]);
}
}
}
return results;
}
exists(objList : MultiSelect[], obj : MultiSelect) : boolean {
for(let i = 0; i < objList.length; i++) {
if (objList[i].value === obj.value) return true;
}
return false;
}
trim(s: string) : string {
let l = 0, r = s.length-1;
while(l < s.length && s[l] == ' ') l++;
while(r > l && s[r] == ' ') r--;
return s.substring(l, r + 1);
}
// private
}
<div class="en-multiselect-container">
<div class="en-ms-input">
<div>
<input type="text" #input [attr.placeholder]="placeholder" [formControl]="search" (focus)="onInputFocus()" (blur)="onInputBlur()"/>
</div>
<div class="en-ms-preview" [class.en-ms-preview-hidden]="hidePreviewItems">
<div *ngFor="let prev of previewItems" (click)="onItemSelect(prev)">
<span class="icon-add"></span>
<span>{{ prev.display }}</span>
</div>
</div>
</div>
<div class="en-ms-output">
<div [hidden]="selected.length > 0" class="en-ms-default-all">
<span>All Items</span>
</div>
<div *ngFor="let sel of selected" (click)="onItemDeselect(sel)">
<span>{{ sel.display }}</span>
</div>
</div>
</div>
export class MultiSelect {
constructor(_value: any, _display: any, _selected?: boolean) {
this.value = _value;
this.display = _display;
this.selected = _selected || false;
}
value: string | number;
display: string | number;
selected: boolean;
}