<!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;

}