novo-elements
Version:
Bullhorn's NOVO Element Repository for Angular 2
326 lines (302 loc) • 10.9 kB
text/typescript
// NG2
import { ElementRef, HostListener, Input } from '@angular/core';
// APP
import { Helpers } from '../../../../utils/Helpers';
// Vendor
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
import { OverlayRef } from '@angular/cdk/overlay';
/**
* @name: PickerResults
*
* @description This is the actual list of matches that gets injected into the DOM. It's also the piece that can be
* overwritten if custom list options are needed.
*/
export class BasePickerResults {
_term: string = '';
selected: Array<any> = [];
matches: any = [];
hasError: boolean = false;
isLoading: boolean = false;
isStatic: boolean = true;
config: any;
activeMatch: any;
parent: any;
element: ElementRef;
page: number = 0;
lastPage: boolean = false;
autoSelectFirstOption: boolean = true;
overlay: OverlayRef;
constructor(element: ElementRef) {
this.element = element;
}
onScrollDown(target) {
if (target) {
let offset = target.offsetHeight + target.scrollTop,
bottom = target.scrollHeight;
if (offset >= bottom) {
event.stopPropagation();
if (!this.lastPage && !this.config.disableInfiniteScroll) {
this.processSearch();
}
}
}
}
get term() {
return this._term;
}
set term(value) {
if (value !== this._term || this.page === 0) {
this._term = value;
this.page = 0;
this.matches = [];
this.processSearch();
}
}
processSearch() {
this.hasError = false;
this.isLoading = true;
this.search(this.term)
.subscribe(
(results: any) => {
if (this.isStatic) {
this.matches = this.filterData(results);
} else {
this.matches = this.matches.concat(results);
this.lastPage = (results && !results.length);
}
if (this.matches.length > 0 && this.autoSelectFirstOption) {
this.nextActiveMatch();
}
this.isLoading = false;
setTimeout(() => this.overlay.updatePosition()); // @bkimball: This was added for Dylan Schulte, 9.18.2017 4:14PM EST, you're welcome!
},
(err) => {
this.hasError = this.term && this.term.length !== 0;
this.isLoading = false;
this.lastPage = true;
if (this.term && this.term.length !== 0) {
console.error(err); // tslint:disable-lineno
}
});
}
search(term, mode?) {
let options = this.config.options;
return Observable.fromPromise(new Promise((resolve, reject) => {
// Check if there is match data
if (options) {
// Resolve the data
if (Array.isArray(options)) {
this.isStatic = true;
// Arrays are returned immediately
resolve(this.structureArray(options));
} else if (term && term.length >= (this.config.minSearchLength || 1)) {
if ((options.hasOwnProperty('reject') && options.hasOwnProperty('resolve')) || Object.getPrototypeOf(options).hasOwnProperty('then')) {
this.isStatic = false;
// Promises (ES6 or Deferred) are resolved whenever they resolve
options
.then(this.structureArray.bind(this))
.then(resolve, reject);
} else if (typeof options === 'function') {
this.isStatic = false;
// Promises (ES6 or Deferred) are resolved whenever they resolve
options(term, ++this.page)
.then(this.structureArray.bind(this))
.then(resolve, reject);
} else {
// All other kinds of data are rejected
reject('The data provided is not an array or a promise');
throw new Error('The data provided is not an array or a promise');
}
} else {
if (this.config.defaultOptions) {
this.isStatic = false;
if (typeof this.config.defaultOptions === 'function') {
let defaultOptions = this.config.defaultOptions(term, ++this.page);
if (Object.getPrototypeOf(defaultOptions).hasOwnProperty('then')) {
defaultOptions
.then(this.structureArray.bind(this))
.then(resolve, reject);
} else {
resolve(this.structureArray(defaultOptions));
}
} else {
resolve(this.structureArray(this.config.defaultOptions));
}
} else {
// No search term gets rejected
reject('No search term');
}
}
} else {
// No data gets rejected
reject('error');
}
}));
}
/**
* @name structureArray
* @param collection - the data once getData resolves it
* @returns { Array }
*
* @description This function structures an array of nodes into an array of objects with a
* 'name' field by default.
*/
structureArray(collection: any): any {
let dataArray = collection.data ? collection.data : collection;
if (dataArray && (typeof dataArray[0] === 'string' || typeof dataArray[0] === 'number')) {
return collection.map((item) => {
return {
value: item,
label: item
};
});
}
return dataArray.map((data) => {
let value = this.config.field ? data[this.config.field] : (data.value || data);
if (this.config.valueFormat) {
value = Helpers.interpolate(this.config.valueFormat, data);
}
let label = this.config.format ? Helpers.interpolate(this.config.format, data) : data.label || String(value);
return { value, label, data };
});
}
/**
* @name filterData=
* @param matches - Collection of objects=
*
* @description This function loops through the picker options and creates a filtered list of objects that contain
* the newSearch.
*/
filterData(matches): Array<any> {
if (this.term && matches) {
return matches.filter((match) => {
return ~String(match.label).toLowerCase().indexOf(this.term.toLowerCase());
});
}
// Show no recent results template
return matches;
}
/**
* @name selectActiveMatch
*
* @description This function is called when the user presses the enter key to call the selectMatch method.
*/
selectActiveMatch() {
this.selectMatch();
}
/**
* @name prevActiveMatch
*
* @description This function sets activeMatch to the match before the current node.
*/
prevActiveMatch() {
let index = this.matches.indexOf(this.activeMatch);
this.activeMatch = this.matches[index - 1 < 0 ? this.matches.length - 1 : index - 1];
this.scrollToActive();
}
/**
* @name nextActiveMatch
*
* @description This function sets activeMatch to the match after the current node.
*/
nextActiveMatch() {
let index = this.matches.indexOf(this.activeMatch);
this.activeMatch = this.matches[index + 1 > this.matches.length - 1 ? 0 : index + 1];
this.scrollToActive();
}
getListElement() {
return this.element.nativeElement;
}
getChildrenOfListElement() {
let children = [];
if (this.getListElement()) {
children = this.getListElement().children;
}
return children;
}
scrollToActive() {
let list = this.getListElement();
let items = this.getChildrenOfListElement();
let index = this.matches.indexOf(this.activeMatch);
let item = items[index];
if (item) {
list.scrollTop = item.offsetTop;
}
}
/**
* @name selectActive
* @param match
*
* @description
*/
selectActive(match) {
this.activeMatch = match;
}
/**
* @name isActive
* @param match
*
* @description
*/
isActive(match) {
return this.activeMatch === match;
}
/**
* @name selectMatch
* @param event
* @param item
*
* @description
*/
selectMatch(event?: any, item?: any) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
let selected = this.activeMatch;
if (selected && this.parent) {
this.parent.value = selected;
if (this.parent.closeOnSelect) {
this.parent.hideResults();
}
}
return false;
}
/**
* @name escapeRegexp
* @param queryToEscape
*
* @description This function captures the whole query string and replace it with the string that will be used to
* match.
*/
escapeRegexp(queryToEscape) {
// Ex: if the capture is "a" the result will be \a
return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
}
/**
* @name highlight
* @param match
* @param query
*
* @description This function should return a <strong>-tag wrapped HTML string.
*/
highlight(match, query) {
// Replaces the capture string with a the same string inside of a "strong" tag
return query ? match.replace(new RegExp(this.escapeRegexp(query), 'gi'), '<strong>$&</strong>') : match;
}
preselected(match) {
return this.selected.findIndex(item => {
let isPreselected = false;
if (item && item.value && match && match.value) {
if (item.value.id && match.value.id) {
isPreselected = item.value.id === match.value.id;
} else {
isPreselected = item.value === match.value;
}
}
return isPreselected;
}) !== -1;
}
}