File

assets/angular/shared/form/field-vocab.component.ts

Description

Vocabulary Field

Author: Shilo Banihit

Extends

FieldBase

Index

Properties
Methods

Constructor

constructor(options: any, injector: any)
Parameters :
Name Type Optional Description
options any
injector any

Methods

createFormModel
createFormModel(valueElem: any, createFormGroup: boolean)
Parameters :
Name Type Optional Description
valueElem any
createFormGroup boolean
Returns : any
getFieldValuePair
getFieldValuePair(fldName: any, data: any, valObj: any)
Parameters :
Name Type Optional Description
fldName any
data any
valObj any
Returns : void
getTitle
getTitle(data: any)
Parameters :
Name Type Optional Description
data any
Returns : string
getValue
getValue(data: any)
Parameters :
Name Type Optional Description
data any
Returns : {}
initLookupData
initLookupData()
Returns : void
postInit
postInit(value: any)
Parameters :
Name Type Optional Description
value any
Returns : void
setEmptyValue
setEmptyValue()
Returns : any
setLookupServices
setLookupServices(completerService: any, lookupService: any)
Parameters :
Name Type Optional Description
completerService any
lookupService any
Returns : void

Properties

Public completerService
completerService: CompleterService
Type : CompleterService
Protected dataService
dataService: CompleterData
Type : CompleterData
Public fieldNames
fieldNames: string[]
Type : string[]
Public initialValue
initialValue: any
Type : any
Public lookupService
lookupService: any
Type : any
Public placeHolder
placeHolder: string
Type : string
Public searchFields
searchFields: string
Type : string
Public sourceData
sourceData: any
Type : any
Public sourceType
sourceType: string
Type : string
Public titleFieldArr
titleFieldArr: string[]
Type : string[]
Public titleFieldDelim
titleFieldDelim: any
Type : any
Public titleFieldName
titleFieldName: string
Type : string
Public vocabId
vocabId: string
Type : string
import { Input, Component, Injectable , Inject, OnInit, Output, EventEmitter} from '@angular/core';
import { SimpleComponent } from './field-simple.component';
import { FieldBase } from './field-base';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import * as _ from "lodash-lib";
import { Observable } from 'rxjs/Observable';
import { Subject } from "rxjs/Subject";
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/observable/of';
import { Http } from '@angular/http';
import { BaseService } from '../base-service';
import { CompleterService, CompleterData, CompleterItem } from 'ng2-completer';
import { ConfigService } from '../config-service';
/**
 * Vocabulary Field
 *
 * Author: <a href='https://github.com/shilob' target='_blank'>Shilo Banihit</a>
 *
 */
export class VocabField extends FieldBase<any> {
  public vocabId: string;
  public sourceData: any;
  public completerService: CompleterService;
  protected dataService: CompleterData;
  public initialValue: any;
  public titleFieldName: string;
  public titleFieldArr: string[];
  public titleFieldDelim: any;
  public searchFields: string;
  public fieldNames: string[];
  public sourceType: string;
  public lookupService: any;
  public placeHolder: string;

  constructor(options: any, injector: any) {
    super(options, injector);
    this.hasLookup = true;
    this.vocabId = options['vocabId'] || '';
    this.controlType = 'textbox';
    this.titleFieldName = options['titleFieldName'] || 'title';
    this.titleFieldArr = options['titleFieldArr'] || [];
    this.searchFields = options['searchFields'] || '';
    this.titleFieldDelim = options['titleFieldDelim'] || ' - ';
    this.fieldNames = options['fieldNames'] || [];
    this.sourceType = options['sourceType'] || 'vocab';
    this.placeHolder = options['placeHolder'] || 'Select a valid value';
  }

  createFormModel(valueElem: any = undefined, createFormGroup:boolean=false) {
    if (valueElem) {
      this.value = valueElem;
    }
    if (createFormGroup) {
      const flds = {};
      _.forEach(this.fieldNames, fld => {
        _.forOwn(fld, (srcFld, targetFld) => {
          flds[targetFld] = new FormControl(this.value[targetFld] || '');
        });
      });
      this.formModel = new FormGroup(flds);
    } else {
      this.formModel = new FormControl(this.value || '');
    }
    if (this.value) {
      const init = _.cloneDeep(this.value);
      init.title = this.getTitle(this.value);
      this.initialValue = init;
    }

    if (this.required) {
      this.formModel.setValidators([Validators.required]);
    }
    return this.formModel;
  }

  postInit(value: any) {
    if (value) {
      this.value = value;
    } else {
      this.setEmptyValue();
    }
    this.initLookupData();
  }

  setEmptyValue() {
    this.value = null;
    return this.value;
  }

  setLookupServices(completerService: any, lookupService: any) {
    this.completerService = completerService;
    this.lookupService = lookupService;
  }

  initLookupData() {
    if (this.sourceType == "vocab") {
      // Hack for creating the intended title...
      _.forEach(this.sourceData, (data: any) => {
        data.title = this.getTitle(data);
      });
      this.dataService = this.completerService.local(this.sourceData, this.searchFields, 'title');
    } else if (this.sourceType == "collection" || this.sourceType == "user") {
      let url = this.lookupService.getCollectionRootUrl(this.vocabId);
      if (this.sourceType == "user") {
        url = this.lookupService.getUserLookupUrl();
      }
      console.log(`Using: ${url}`);
      // at the moment, multiple titles arrays are not supported
      // TODO: consider replacing with ngx-bootstrap typeahead
      const title = this.titleFieldArr.length == 1 ? this.titleFieldArr[0] : 'title';
      console.log(`Using title: ${title}`);
      this.dataService = this.completerService.remote(url, this.searchFields, title);
    } else if (this.sourceType == "mint") {
      const url = this.lookupService.getMintRootUrl(this.vocabId);
      console.log(`Using: ${url}`);

      this.dataService = new MintLookupDataService(
        url,
        this.lookupService.http,
        this.fieldNames,
        this.titleFieldName,
        this.titleFieldArr,
        this.titleFieldDelim,
        this.searchFields);
    }
  }

  getTitle(data: any): string {
    let title = '';
    if (data) {
      if (_.isString(this.titleFieldDelim)) {
        _.forEach(this.titleFieldArr, (titleFld: string) => {
          const titleVal = data[titleFld];
          if (titleVal) {
            title = `${title}${_.isEmpty(title) ? '' : this.titleFieldDelim}${data[titleFld]}`;
          }
        });
      } else {
        // expecting a delim pair array, 'prefix', 'suffix'
        _.forEach(this.titleFieldArr, (titleFld: string, idx) => {
          const delimPair = this.titleFieldDelim[idx];
          title = `${title}${_.isEmpty(title) ? '' : delimPair.prefix}${data[titleFld]}${_.isEmpty(title) ? '' : delimPair.suffix}`;
        });
      }
    }
    return title;
  }

  getValue(data: any) {
    const valObj = {};
    _.forEach(this.fieldNames, (fldName: any) => {
      if (data.originalObject) {
        this.getFieldValuePair(fldName, data.originalObject, valObj)
      } else {
        this.getFieldValuePair(fldName, data, valObj)
      }
    });
    return valObj;
  }

  getFieldValuePair(fldName: any, data: any, valObj: any) {
    if (_.isString(fldName)) {
      valObj[fldName] = _.get(data, fldName);
    } else {
      // expects a value pair
      _.forOwn(fldName, (srcFld, targetFld) => {
        if (_.get(data, srcFld)) {
          valObj[targetFld] = _.get(data, srcFld);
        } else {
          valObj[targetFld] = _.get(data, targetFld);
        }
      });
    }
  }

}

class MintLookupDataService extends Subject<CompleterItem[]> implements CompleterData {

  searchFields: any[];

  constructor(private url:string,
    private http: Http,
    private fields: string[],
    private compositeTitleName: string,
    private titleFieldArr: string[],
    private titleFieldDelim: any[],
    searchFieldStr: any)
  {
    super();
    this.searchFields = searchFieldStr.split(',');
  }

  public search(term: string): void {
    term = _.trim(term);
    let searchString='';
    if (!_.isEmpty(term)) {
      term = _.toLower(term);
      _.forEach(this.searchFields, (searchFld) => {
        searchString = `${searchString}${_.isEmpty(searchString) ? '' : ' OR '}${searchFld}:${term}*`
      });
    }
    const searchUrl = `${this.url}${searchString}`;
    this.http.get(`${searchUrl}`).map((res: any, index:number) => {
      // Convert the result to CompleterItem[]
      let data = res.json();
      let matches: CompleterItem[] = _.map(data, (mintDataItem: any) => { return this.convertToItem(mintDataItem); });
      this.next(matches);
    }).subscribe();
  }

  public cancel() {
    // Handle cancel
  }

  public convertToItem(data: any): CompleterItem | null {
    if (!data) {
      return null;
    }
    const item = {};
    _.forEach(this.fields, (fieldName) => {
      if (_.isString(fieldName)) {
        item[fieldName] = data[fieldName];
      } else {
        // expects a value pair
        _.forOwn(fieldName, (srcFld, targetFld) => {
          if (_.get(data, srcFld)) {
            item[srcFld] = _.get(data, srcFld);
          } else {
            item[targetFld] = _.get(data, targetFld);
          }
        });
      }
    });
    // build the title,
    item[this.compositeTitleName] = this.getTitle(data);
    return item as CompleterItem;
  }

  getTitle(data: any): string {
    let title = '';
    if (data) {
      if (_.isString(this.titleFieldDelim)) {
        _.forEach(this.titleFieldArr, (titleFld: string) => {
          const titleVal = data[titleFld];
          if (titleVal) {
            title = `${title}${_.isEmpty(title) ? '' : this.titleFieldDelim}${data[titleFld]}`;
          }
        });
      } else {
        // expecting a delim pair array, 'prefix', 'suffix'
        _.forEach(this.titleFieldArr, (titleFld: string, idx) => {
          const delimPair = this.titleFieldDelim[idx];
          title = `${title}${delimPair.prefix}${data[titleFld]}${delimPair.suffix}`;
        });
      }
    }
    return title;
  }
}

@Injectable()
export class VocabFieldLookupService extends BaseService {

  constructor (@Inject(Http) http: Http, @Inject(ConfigService) protected configService: ConfigService) {
    super(http, configService);
  }

  getLookupData(field: VocabField) {
    const vocabId  = field.vocabId;
    // only retrieving static data when on vocab mode
    if (field.sourceType == "vocab") {
      const url = `${this.brandingAndPortalUrl}/${this.config.vocabRootUrl}/${vocabId}`;
      return this.http.get(url, this.options)
        .flatMap((res: any) => {
          const data = this.extractData(res);
          field.sourceData = data;
          field.postInit(field.value);
          return Observable.of(field);
        });
    }
    field.postInit(field.value);
    return Observable.of(field);
  }

  getCollectionRootUrl(collectionId: string) {
    return `${this.brandingAndPortalUrl}/${this.config.collectionRootUri}/${collectionId}/?search=`;
  }

  getUserLookupUrl(searchSource: string = '') {
    return `${this.brandingAndPortalUrl}/${this.config.userRootUri}/?source=${searchSource}&name=`;
  }

  findLookupData(field: VocabField, search: string) {

  }

  getMintRootUrl(source: string) {
    return `${this.brandingAndPortalUrl}/${this.config.mintRootUri}/${source}/?search=`;
  }
}

@Component({
  selector: 'rb-vocab',
  template: `
  <div *ngIf="field.editMode && !isEmbedded" [formGroup]='form' [ngClass]="getGroupClass()">
    <label>
      {{field.label}} {{getRequiredLabelStr()}}
      <button type="button" class="btn btn-default" *ngIf="field.help" (click)="toggleHelp()"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span></button>
    </label>
    <span id="{{ 'helpBlock_' + field.name }}" class="help-block" *ngIf="this.helpShow" >{{field.help}}</span>
    <ng2-completer [disableInput]="disableInput" [placeholder]="field.placeHolder" [clearUnselected]="true" (selected)="onSelect($event)" [datasource]="field.dataService" [minSearchLength]="0" [inputClass]="'form-control'" [initialValue]="field.initialValue"></ng2-completer>
    <div class="text-danger" *ngIf="hasRequiredError()">{{field.validationMessages.required}}</div>
  </div>
  <div *ngIf="field.editMode && isEmbedded" [formGroup]='form' [ngClass]="getGroupClass()">
    <div class="input-group">
      <span id="{{ 'helpBlock_' + field.name }}" class="help-block" *ngIf="this.helpShow" >{{field.help}}</span>
      <ng2-completer [disableInput]="disableInput" [placeholder]="field.placeHolder" [clearUnselected]="true" (selected)="onSelect($event)" [datasource]="field.dataService" [minSearchLength]="0" [inputClass]="'form-control'" [initialValue]="field.initialValue"></ng2-completer>
      <span class="input-group-btn">
        <button type='button' *ngIf="removeBtnText" [disabled]="!canRemove" (click)="onRemove($event)" [ngClass]="removeBtnClass" >{{removeBtnText}}</button>
        <button [disabled]="!canRemove" type='button' [ngClass]="removeBtnClass" (click)="onRemove($event)"></button>
      </span>
    </div>
    <div class="text-danger" *ngIf="hasRequiredError()">{{field.validationMessages.required}}</div>
  </div>

  <li *ngIf="!field.editMode" class="key-value-pair">
    <span *ngIf="field.label" class="key">{{field.label}}</span>
    <span class="value">{{field.getTitle(field.value)}}</span>
  </li>
  `,
})
export class VocabFieldComponent extends SimpleComponent {
  @Input() field: VocabField;
  @Input() isEmbedded: boolean = false;
  @Input() canRemove: boolean = false;
  @Input() removeBtnText: string = null;
  @Input() removeBtnClass: string = 'btn fa fa-minus-circle btn text-20 pull-left btn btn-danger';
  @Input() index: number;
  @Input() disableEditAfterSelect: boolean = true;
  @Output() onRemoveBtnClick: EventEmitter<any> = new EventEmitter<any>();
  disableInput: boolean;

  constructor() {
    super();
  }

  onSelect(selected: any) {
    if (selected) {
      this.field.formModel.setValue(this.field.getValue(selected));
      if (this.disableEditAfterSelect)
        this.disableInput = true;
    } else {
      if (!this.disableEditAfterSelect) {
        this.field.formModel.setValue(null);
      }
    }
  }

  onRemove(event: any) {
    this.onRemoveBtnClick.emit([event, this.index]);
  }
}

results matching ""

    No results matching ""