froiden-angular2-select
Version:
Angular2 ajax based replacement for select boxes
797 lines (794 loc) • 30.9 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
const core_1 = require('@angular/core');
const http_1 = require("@angular/http");
const Observable_1 = require("rxjs/Observable");
const forms_1 = require('@angular/forms');
const select_item_1 = require('./select-item');
const select_pipes_1 = require('./select-pipes');
const common_1 = require('./common');
const Subject_1 = require("rxjs/Subject");
require("rxjs/Rx");
let optionsTemplate = `
<ul *ngIf="optionsOpened && options && options.length > 0 && !firstItemHasChildren"
class="ui-select-choices dropdown-menu" role="menu">
<li *ngFor="let o of options" role="menuitem">
<div class="ui-select-choices-row"
[class.active]="isActive(o)"
(mouseenter)="selectActive(o)"
(click)="selectMatch(o, $event)">
<a href="javascript:void(0)" class="dropdown-item">
<div [innerHtml]="o.text | highlight:inputValue"></div>
</a>
</div>
</li>
</ul>
<ul *ngIf="optionsOpened && options && options.length > 0 && firstItemHasChildren"
class="ui-select-choices dropdown-menu" role="menu">
<li *ngFor="let c of options; let index=index" role="menuitem">
<div class="divider dropdown-divider" *ngIf="index > 0"></div>
<div class="dropdown-header">{{c.text}}</div>
<div *ngFor="let o of c.children"
class="ui-select-choices-row"
[class.active]="isActive(o)"
(mouseenter)="selectActive(o)"
(click)="selectMatch(o, $event)"
[ngClass]="{'active': isActive(o)}">
<a href="javascript:void(0)" class="dropdown-item">
<div [innerHtml]="o.text | highlight:inputValue"></div>
</a>
</div>
</li>
</ul>
`;
const noop = () => {
};
exports.CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = {
provide: forms_1.NG_VALUE_ACCESSOR,
useExisting: core_1.forwardRef(() => SelectComponent),
multi: true
};
let SelectComponent = class SelectComponent {
constructor(element, http) {
this.http = http;
this.allowClear = false;
this.placeholder = '';
this.idField = 'id';
this.textField = 'text';
this.multiple = false;
this.settings = {};
this.ajaxLoad = false;
this.searchSubject = new Subject_1.Subject();
this.searchObserve = this.searchSubject.asObservable();
this.requestType = "get";
this.data = new core_1.EventEmitter();
this.selected = new core_1.EventEmitter();
this.removed = new core_1.EventEmitter();
this.typed = new core_1.EventEmitter();
this.options = [];
this.itemObjects = [];
//The internal data model
this.innerValue = '';
//Placeholders for the callbacks which are later providesd
//by the Control Value Accessor
this.onTouchedCallback = noop;
this.onChangeCallback = noop;
this.inputMode = false;
this.optionsOpened = false;
this.inputValue = '';
this._items = [];
this._disabled = false;
this._active = [];
this.element = element;
this.clickedOutside = this.clickedOutside.bind(this);
this.sendUrlRequest();
}
set disabled(value) {
this._disabled = value;
if (this._disabled === true) {
this.hideOptions();
}
}
get disabled() {
return this._disabled;
}
set active(selectedItems) {
if (!selectedItems || selectedItems.length === 0 || !selectedItems[0]) {
this._active = [];
}
else {
let areItemsStrings = typeof selectedItems[0] === 'string';
this._active = selectedItems.map((item) => {
let data = areItemsStrings
? item
: { id: item[this.idField], text: item[this.textField], properties: item };
return new select_item_1.SelectItem(data);
});
}
}
//get accessor
get value() {
return '';
}
;
//set accessor including call the onchange callback
set value(v) {
if (v !== this.inputValue) {
this.inputValue = v;
}
}
//Set touched on blur
onBlur() {
this.onTouchedCallback();
}
//From ControlValueAccessor interface
writeValue(value) {
if (value !== this.inputValue) {
this.inputValue = value;
this.active = this.inputValue;
}
}
//From ControlValueAccessor interface
registerOnChange(fn) {
this.onChangeCallback = fn;
}
//From ControlValueAccessor interface
registerOnTouched(fn) {
this.onTouchedCallback = fn;
}
get active() {
return this._active;
}
inputEvent(e, isUpMode = false) {
// tab
if (e.keyCode === 9) {
return;
}
if (isUpMode && (e.keyCode === 37 || e.keyCode === 39 || e.keyCode === 38 ||
e.keyCode === 40 || e.keyCode === 13)) {
e.preventDefault();
return;
}
// backspace
if (!isUpMode && e.keyCode === 8) {
let el = this.element.nativeElement
.querySelector('div.ui-select-container > input');
if (!el.value || el.value.length <= 0) {
if (this.active.length > 0) {
this.remove(this.active[this.active.length - 1]);
}
e.preventDefault();
}
}
// esc
if (!isUpMode && e.keyCode === 27) {
this.hideOptions();
this.element.nativeElement.children[0].focus();
e.preventDefault();
return;
}
// del
if (!isUpMode && e.keyCode === 46) {
if (this.active.length > 0) {
this.remove(this.active[this.active.length - 1]);
}
e.preventDefault();
}
// left
if (!isUpMode && e.keyCode === 37 && this._items.length > 0) {
this.behavior.first();
e.preventDefault();
return;
}
// right
if (!isUpMode && e.keyCode === 39 && this._items.length > 0) {
this.behavior.last();
e.preventDefault();
return;
}
// up
if (!isUpMode && e.keyCode === 38) {
this.behavior.prev();
e.preventDefault();
return;
}
// down
if (!isUpMode && e.keyCode === 40) {
this.behavior.next();
e.preventDefault();
return;
}
// enter
if (!isUpMode && e.keyCode === 13) {
if (this.active.indexOf(this.activeOption) === -1) {
this.selectActiveMatch();
this.behavior.next();
}
e.preventDefault();
return;
}
let target = e.target || e.srcElement;
if (target) {
this.inputValue = target.value;
if (target.value) {
this.behavior.filter(new RegExp(common_1.escapeRegexp(this.inputValue), 'ig'));
}
this.doEvent('typed', this.inputValue);
}
if (this.settings.ajax != undefined) {
let urlString = this.originalUrl;
this.settings.ajax.url = urlString.replace('SEARCH_VALUE', e.target.value);
}
this.searchSubject.next(this.inputValue);
}
sendUrlRequest() {
this.searchObserve.debounceTime(this.settings.debounceTime).subscribe(event => {
if (this.settings.data != undefined) {
let value = this.settings.data;
if (!value) {
this._items = this.itemObjects = [];
}
else {
this._items = value.filter((item) => {
if ((typeof item === 'string' && item) || (typeof item === 'object' && item && item[this.textField] && item[this.idField])) {
return item;
}
});
this.itemObjects = this._items.map((item) => (typeof item === 'string' ? new select_item_1.SelectItem(item) : new select_item_1.SelectItem({ id: item[this.idField], text: item[this.textField], properties: item })));
}
if (this.ajaxLoad === true) {
this.open();
}
}
else if (this.settings.ajax != undefined) {
// Send http request
let headers = new http_1.Headers();
// Add auth header
if (this.settings.ajax.authToken != undefined) {
headers.append("Authorization", "Bearer " + this.settings.ajax.authToken);
}
// Create request options
let requestOptions = new http_1.RequestOptions({
headers: headers
});
if (this.requestType == "get" || this.requestType == "GET") {
this.http.get(this.settings.ajax.url, requestOptions)
.map((res) => res.json())
.catch((error) => Observable_1.Observable.throw(error.json().error || 'Server error'))
.subscribe((data) => {
let value = this.settings.ajax.responseData(data);
if (!value) {
this._items = this.itemObjects = [];
}
else {
this._items = value.filter((item) => {
if ((typeof item === 'string' && item) || (typeof item === 'object' && item && item[this.textField] && item[this.idField])) {
return item;
}
});
this.itemObjects = this._items.map((item) => (typeof item === 'string' ? new select_item_1.SelectItem(item) : new select_item_1.SelectItem({ id: item[this.idField], text: item[this.textField], properties: item })));
}
if (this.ajaxLoad === true) {
this.open();
}
}, (error) => {
});
}
else if (this.requestType == "post" || this.requestType == "POST") {
this.http.post(this.settings.ajax.url, requestOptions)
.map((res) => res.json())
.catch((error) => Observable_1.Observable.throw(error.json().error || 'Server error'))
.subscribe((data) => {
let value = this.settings.ajax.responseData(data);
if (!value) {
this._items = this.itemObjects = [];
}
else {
this._items = value.filter((item) => {
if ((typeof item === 'string' && item) || (typeof item === 'object' && item && item[this.textField] && item[this.idField])) {
return item;
}
});
this.itemObjects = this._items.map((item) => (typeof item === 'string' ? new select_item_1.SelectItem(item) : new select_item_1.SelectItem({ id: item[this.idField], text: item[this.textField], properties: item })));
}
if (this.ajaxLoad === true) {
this.open();
}
}, (error) => {
});
}
}
});
}
ngOnInit() {
this.behavior = (this.firstItemHasChildren) ?
new ChildrenBehavior(this) : new GenericBehavior(this);
let jsonObject = this.settings;
if (jsonObject.ajax != undefined) {
this.ajaxLoad = true;
this.originalUrl = jsonObject.ajax.url;
if (jsonObject.ajax.requestType != undefined) {
this.requestType = jsonObject.ajax.requestType;
}
}
if (jsonObject.idField != undefined) {
this.idField = jsonObject.idField;
}
if (jsonObject.textField != undefined) {
this.textField = jsonObject.textField;
}
if (jsonObject.allowClear) {
this.allowClear = jsonObject.allowClear;
}
if (jsonObject.multiple) {
this.multiple = jsonObject.multiple;
}
if (jsonObject.placeholder) {
this.placeholder = jsonObject.placeholder;
}
}
remove(item) {
if (this._disabled === true) {
return;
}
if (this.multiple === true && this.active) {
let index = this.active.indexOf(item);
this.active.splice(index, 1);
this.data.next(this.active);
this.doEvent('removed', item);
if (this.settings.processResults != undefined) {
let activeObject = this.active;
let previousValues = [];
activeObject.forEach((item) => {
previousValues.push(item.properties);
});
let modelValue = this.settings.processResults(previousValues);
this.onChangeCallback(modelValue);
}
else {
let modelValue = this.multiProcessResults(this.active);
this.onChangeCallback(modelValue);
}
}
if (this.multiple === false) {
this.active = [];
this.data.next(this.active);
this.doEvent('removed', item);
if (this.settings.processResults != undefined) {
let modelValue = this.settings.processResults(this.active);
this.onChangeCallback(modelValue);
}
else {
let modelValue = this.singleProcessResults(this.active);
this.onChangeCallback(modelValue);
}
}
}
singleProcessResults(modelObject) {
let selectValues = [];
selectValues.push({
id: modelObject.id,
text: modelObject.text,
});
return selectValues;
}
multiProcessResults(modelObject) {
let selectValues = [];
modelObject.forEach((item) => {
selectValues.push({
id: item.id,
text: item.text,
});
});
return selectValues;
}
doEvent(type, value) {
if (this[type]) {
this[type].next(value);
}
}
clickedOutside() {
this.inputMode = false;
this.optionsOpened = false;
}
get firstItemHasChildren() {
return this.itemObjects[0] && this.itemObjects[0].hasChildren();
}
matchClick(e) {
if (this._disabled === true) {
return;
}
this.inputMode = !this.inputMode;
if (this.inputMode === true && ((this.multiple === true && e) || this.multiple === false)) {
this.focusToInput();
this.open();
}
}
mainClick(event) {
if (this.inputMode === true || this._disabled === true) {
return;
}
if (event.keyCode === 46) {
event.preventDefault();
this.inputEvent(event);
return;
}
if (event.keyCode === 8) {
event.preventDefault();
this.inputEvent(event, true);
return;
}
if (event.keyCode === 9 || event.keyCode === 13 ||
event.keyCode === 27 || (event.keyCode >= 37 && event.keyCode <= 40)) {
event.preventDefault();
return;
}
this.inputMode = true;
let value = String
.fromCharCode(96 <= event.keyCode && event.keyCode <= 105 ? event.keyCode - 48 : event.keyCode)
.toLowerCase();
this.focusToInput(value);
this.open();
let target = event.target || event.srcElement;
target.value = value;
this.inputEvent(event);
}
selectActive(value) {
this.activeOption = value;
}
isActive(value) {
return this.activeOption.text === value.text;
}
focusToInput(value = '') {
setTimeout(() => {
let el = this.element.nativeElement.querySelector('div.ui-select-container > input');
if (el) {
el.focus();
el.value = value;
}
}, 0);
}
open() {
this.options = this.itemObjects
.filter((option) => (this.multiple === false ||
this.multiple === true &&
!this.active.find((o) => option.text === o.text)));
if (this.options.length > 0) {
this.behavior.first();
}
this.optionsOpened = true;
}
hideOptions() {
this.inputMode = false;
this.optionsOpened = false;
}
selectActiveMatch() {
this.selectMatch(this.activeOption);
}
selectMatch(value, e = void 0) {
if (e) {
e.stopPropagation();
e.preventDefault();
}
if (this.options.length <= 0) {
return;
}
if (this.multiple === true) {
this.active.push(value);
this.data.next(this.active);
if (this.settings.processResults != undefined) {
let activeObject = this.active;
let previousValues = [];
activeObject.forEach((item) => {
previousValues.push(item.properties);
});
let modelValue = this.settings.processResults(previousValues);
console.log(previousValues);
this.onChangeCallback(modelValue);
}
else {
let modelValue = this.multiProcessResults(this.active);
this.onChangeCallback(modelValue);
}
}
if (this.multiple === false) {
if (this.settings.processResults != undefined) {
let modelValue = this.settings.processResults(value.properties);
this.onChangeCallback(modelValue);
}
else {
let modelValue = this.singleProcessResults(value);
this.onChangeCallback(modelValue);
}
this.active[0] = value;
this.data.next(this.active[0]);
}
this.doEvent('selected', value);
this.hideOptions();
if (this.multiple === true) {
this.focusToInput('');
}
else {
this.focusToInput(select_pipes_1.stripTags(value.text));
this.element.nativeElement.querySelector('.ui-select-container').focus();
}
}
};
__decorate([
core_1.Input(),
__metadata('design:type', Object)
], SelectComponent.prototype, "settings", void 0);
__decorate([
core_1.Input(),
__metadata('design:type', Boolean),
__metadata('design:paramtypes', [Boolean])
], SelectComponent.prototype, "disabled", null);
__decorate([
core_1.Input(),
__metadata('design:type', Array),
__metadata('design:paramtypes', [Array])
], SelectComponent.prototype, "active", null);
__decorate([
core_1.Output(),
__metadata('design:type', core_1.EventEmitter)
], SelectComponent.prototype, "data", void 0);
__decorate([
core_1.Output(),
__metadata('design:type', core_1.EventEmitter)
], SelectComponent.prototype, "selected", void 0);
__decorate([
core_1.Output(),
__metadata('design:type', core_1.EventEmitter)
], SelectComponent.prototype, "removed", void 0);
__decorate([
core_1.Output(),
__metadata('design:type', core_1.EventEmitter)
], SelectComponent.prototype, "typed", void 0);
SelectComponent = __decorate([
core_1.Component({
selector: 'ng-select',
template: `
<div tabindex="0"
*ngIf="multiple === false"
(keyup)="mainClick($event)"
[offClick]="clickedOutside"
class="ui-select-container singleselect dropdown open">
<div [ngClass]="{'ui-disabled': disabled}"></div>
<div class="ui-select-match"
*ngIf="!inputMode">
<span tabindex="-1"
class="btn-select btn-select-default btn-select-secondary single-span ui-select-toggle"
(click)="matchClick($event)"
style="outline: 0;">
<span *ngIf="active.length <= 0" class="ui-select-placeholder text-muted">{{placeholder}}</span>
<span *ngIf="active.length > 0" class="ui-select-match-text pull-left"
[ngClass]="{'ui-select-allow-clear': allowClear && active.length > 0}"
[innerHTML]="active[0].text"></span>
<i class="dropdown-toggle pull-right"></i>
<i class="caret pull-right"></i>
<a *ngIf="allowClear && active.length>0" style="margin-right: 10px; padding: 0;"
(click)="remove(activeOption)" class="close pull-right">
×
</a>
</span>
</div>
<input type="text" autocomplete="false" tabindex="-1"
[(ngModel)]="value"
(keydown)="inputEvent($event)"
(keyup)="inputEvent($event, true)"
[disabled]="disabled"
class="ui-select-search singleInput"
*ngIf="inputMode"
placeholder="{{active.length <= 0 ? placeholder : ''}}">
${optionsTemplate}
</div>
<div tabindex="0"
*ngIf="multiple === true"
(keyup)="mainClick($event)"
(focus)="focusToInput('')"
class="ui-select-container multiselect ui-select-multiple dropdown open">
<div [ngClass]="{'ui-disabled': disabled}"></div>
<span class="ui-select-match">
<span *ngFor="let a of active">
<span class="ui-select-match-item btn-select btn-select-default btn-select-secondary multiple-span btn-select-sm"
tabindex="-1"
type="button"
[ngClass]="{'btn-select-default': true}">
<a class="close"
style="margin-left: 10px; padding: 0;"
(click)="remove(a)">×</a>
<span>{{a.text}}</span>
</span>
</span>
</span>
<input type="text"
[(ngModel)]="value"
(keydown)="inputEvent($event)"
(keyup)="inputEvent($event, true)"
(click)="matchClick($event)"
[disabled]="disabled"
autocomplete="false"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
class="ui-select-search multiInput"
placeholder="{{active.length <= 0 ? placeholder : ''}}"
role="combobox">
${optionsTemplate}
</div>
`,
providers: [exports.CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
}),
__metadata('design:paramtypes', [core_1.ElementRef, http_1.Http])
], SelectComponent);
exports.SelectComponent = SelectComponent;
class Behavior {
constructor(actor) {
this.optionsMap = new Map();
this.actor = actor;
}
fillOptionsMap() {
this.optionsMap.clear();
let startPos = 0;
this.actor.itemObjects
.map((item) => {
startPos = item.fillChildrenHash(this.optionsMap, startPos);
});
}
ensureHighlightVisible(optionsMap = void 0) {
let container = this.actor.element.nativeElement.querySelector('.ui-select-choices-content');
if (!container) {
return;
}
let choices = container.querySelectorAll('.ui-select-choices-row');
if (choices.length < 1) {
return;
}
let activeIndex = this.getActiveIndex(optionsMap);
if (activeIndex < 0) {
return;
}
let highlighted = choices[activeIndex];
if (!highlighted) {
return;
}
let posY = highlighted.offsetTop + highlighted.clientHeight - container.scrollTop;
let height = container.offsetHeight;
if (posY > height) {
container.scrollTop += posY - height;
}
else if (posY < highlighted.clientHeight) {
container.scrollTop -= highlighted.clientHeight - posY;
}
}
getActiveIndex(optionsMap = void 0) {
let ai = this.actor.options.indexOf(this.actor.activeOption);
if (ai < 0 && optionsMap !== void 0) {
ai = optionsMap.get(this.actor.activeOption.id);
}
return ai;
}
}
exports.Behavior = Behavior;
class GenericBehavior extends Behavior {
constructor(actor) {
super(actor);
}
first() {
this.actor.activeOption = this.actor.options[0];
super.ensureHighlightVisible();
}
last() {
this.actor.activeOption = this.actor.options[this.actor.options.length - 1];
super.ensureHighlightVisible();
}
prev() {
let index = this.actor.options.indexOf(this.actor.activeOption);
this.actor.activeOption = this.actor
.options[index - 1 < 0 ? this.actor.options.length - 1 : index - 1];
super.ensureHighlightVisible();
}
next() {
let index = this.actor.options.indexOf(this.actor.activeOption);
this.actor.activeOption = this.actor
.options[index + 1 > this.actor.options.length - 1 ? 0 : index + 1];
super.ensureHighlightVisible();
}
filter(query) {
let options = this.actor.itemObjects
.filter((option) => {
return select_pipes_1.stripTags(option.text).match(query) &&
(this.actor.multiple === false ||
(this.actor.multiple === true && this.actor.active.map((item) => item.id).indexOf(option.id) < 0));
});
this.actor.options = options;
if (this.actor.options.length > 0) {
this.actor.activeOption = this.actor.options[0];
super.ensureHighlightVisible();
}
}
}
exports.GenericBehavior = GenericBehavior;
class ChildrenBehavior extends Behavior {
constructor(actor) {
super(actor);
}
first() {
this.actor.activeOption = this.actor.options[0].children[0];
this.fillOptionsMap();
this.ensureHighlightVisible(this.optionsMap);
}
last() {
this.actor.activeOption =
this.actor
.options[this.actor.options.length - 1]
.children[this.actor.options[this.actor.options.length - 1].children.length - 1];
this.fillOptionsMap();
this.ensureHighlightVisible(this.optionsMap);
}
prev() {
let indexParent = this.actor.options
.findIndex((option) => this.actor.activeOption.parent && this.actor.activeOption.parent.id === option.id);
let index = this.actor.options[indexParent].children
.findIndex((option) => this.actor.activeOption && this.actor.activeOption.id === option.id);
this.actor.activeOption = this.actor.options[indexParent].children[index - 1];
if (!this.actor.activeOption) {
if (this.actor.options[indexParent - 1]) {
this.actor.activeOption = this.actor
.options[indexParent - 1]
.children[this.actor.options[indexParent - 1].children.length - 1];
}
}
if (!this.actor.activeOption) {
this.last();
}
this.fillOptionsMap();
this.ensureHighlightVisible(this.optionsMap);
}
next() {
let indexParent = this.actor.options
.findIndex((option) => this.actor.activeOption.parent && this.actor.activeOption.parent.id === option.id);
let index = this.actor.options[indexParent].children
.findIndex((option) => this.actor.activeOption && this.actor.activeOption.id === option.id);
this.actor.activeOption = this.actor.options[indexParent].children[index + 1];
if (!this.actor.activeOption) {
if (this.actor.options[indexParent + 1]) {
this.actor.activeOption = this.actor.options[indexParent + 1].children[0];
}
}
if (!this.actor.activeOption) {
this.first();
}
this.fillOptionsMap();
this.ensureHighlightVisible(this.optionsMap);
}
filter(query) {
let options = [];
let optionsMap = new Map();
let startPos = 0;
for (let si of this.actor.itemObjects) {
let children = si.children.filter((option) => query.test(option.text));
startPos = si.fillChildrenHash(optionsMap, startPos);
if (children.length > 0) {
let newSi = si.getSimilar();
newSi.children = children;
options.push(newSi);
}
}
this.actor.options = options;
if (this.actor.options.length > 0) {
this.actor.activeOption = this.actor.options[0].children[0];
super.ensureHighlightVisible(optionsMap);
}
}
}
exports.ChildrenBehavior = ChildrenBehavior;
//# sourceMappingURL=select.js.map