UNPKG

novo-elements

Version:

Bullhorn's NOVO Element Repository for Angular 2

326 lines (302 loc) 10.9 kB
// 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> = []; @Input() 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; } @HostListener('scroll', ['$event.target']) 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; } }