File

libs/time/src/lib/predefined-timespan-selector/predefined-timespan-selector.component.ts

Implements

OnInit

Metadata

selector n52-predefined-timespan-selector
styleUrls ./predefined-timespan-selector.component.scss
templateUrl ./predefined-timespan-selector.component.html

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(settingSrvc: SettingsService)
Parameters :
Name Type Optional
settingSrvc SettingsService<Settings> No

Inputs

timespan
Type : Timespan

Outputs

onTimespanChange
Type : EventEmitter<Timespan>

Methods

Public isSafeMomentExpression
isSafeMomentExpression(expression: string)
Parameters :
Name Type Optional
expression string No
Returns : boolean
Public isSafeTimespanPreset
isSafeTimespanPreset(preset: TimespanPreset)
Parameters :
Name Type Optional
preset TimespanPreset No
Returns : boolean
Public ngOnInit
ngOnInit()
Returns : void
Public parseMomentExpression
parseMomentExpression(expression: string)
Parameters :
Name Type Optional
expression string No
Returns : Date
Public timespanChanged
timespanChanged(preset: TimespanPreset)
Parameters :
Name Type Optional
preset TimespanPreset No
Returns : void

Properties

Public parsedTimespanPresets
Type : ParsedTimespanPreset[]
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ParsedTimespanPreset, Settings, SettingsService, Timespan, TimespanPreset } from '@helgoland/core';

@Component({
  selector: 'n52-predefined-timespan-selector',
  templateUrl: './predefined-timespan-selector.component.html',
  styleUrls: ['./predefined-timespan-selector.component.scss']
})

export class PredefinedTimespanSelectorComponent implements OnInit {

  @Input()
  public timespan: Timespan;

  @Output()
  public onTimespanChange: EventEmitter<Timespan> = new EventEmitter<Timespan>();

  public parsedTimespanPresets: ParsedTimespanPreset[];

  constructor(
    protected settingSrvc: SettingsService<Settings>
  ) { }

  public ngOnInit() {
    const timespanPresets = this.settingSrvc.getSettings().timespanPresets;
    if (timespanPresets) {
      this.parsedTimespanPresets = timespanPresets
        .filter((e) => this.isSafeTimespanPreset(e))
        .map((e) => ({
          name: e.name,
          label: e.label,
          timespan: {
            from: this.parseMomentExpression(e.timespan.from).getTime(),
            to: this.parseMomentExpression(e.timespan.to).getTime()
          },
          seperatorAfterThisItem: e.seperatorAfterThisItem
        }));
    }
  }

  public isSafeMomentExpression(expression: string): boolean {
    // tslint:disable:max-line-length
    // regex checks whether code to be eval'ed adhers to syntax given in https://momentjs.com/docs/#/manipulating/
    // explanation:               Start with "moment()"   Possible functions: add(number, string) and subtract(number, string)                            Further possible functions: startOf(string) and endOf(string)                           Further possible functions: year(number), ..., milliseconds(number).                         functions can be chained infinitely, or not at all
    // further explanation:       This is a MUST.         The strings have to be out of the options described in the docs (shortcuts permitted)           Again, the strings have to be out of a strict set.                                      These set the corresponding part of the Moment object to the number given.                   |  (i.e. "moment()" is the minimal case matched)
    // even further explanation:                          The number doesn't HAVE to be reasonable (e.g. month=20 is ok), but that's no security issue.   The quotes can incorrectly start with ' and then end with " (or vice versa), but that's no security problem either.                                                                  v v optional semicolon at the end
    const safeMomentExpression = new RegExp(/^moment\(\)(\.(((add|subtract)\(\d+, ?['"](years|y|quarters|Q|months|M|weeks|w|days|d|hours|h|minutes|m|seconds|s|milliseconds|ms)['"]\))|((startOf|endOf)\(['"](year|month|quarter|week|isoWeek|day|date|hour|minute|second)['"]\))|((year|month|date|hours|minutes|seconds|milliseconds)\(\d+\))))*;?$/);
    // brackets level in case you get lost:          * *1  234            4 *          4                                                                                      4     *3 34             4 *    4                                                           4     *3 34                                                  4 *    *321
    // * = this bracket is an escaped bracket and therefore not counted

    // test expression against regex above
    return safeMomentExpression.test(expression);
  }

  public isSafeTimespanPreset(preset: TimespanPreset): boolean {
    // test both inputs against the regex
    const isSafe = this.isSafeMomentExpression(preset.timespan.from) && this.isSafeMomentExpression(preset.timespan.to);

    if (isSafe) {
      return true;
    } else {
      console.log('Timespan preset "' + preset.name + '" has invalid moment() expression!');
      return false;
    }
  }

  public parseMomentExpression(expression: string): Date {
    // just to be sure not to eval() something nasty
    if (this.isSafeMomentExpression(expression)) {
      // if satisfied, eval the inputs -> the ._d property contains the corresponding Date objects from which the Timespan can be constructed
      // tslint:disable-next-line:no-eval
      return eval(expression)._d;
    } else {
      return null;
    }
  }

  public timespanChanged(preset: TimespanPreset) {
    // construct new Timespan
    this.timespan = new Timespan(parseInt(preset.timespan.from, 10), parseInt(preset.timespan.to, 10));
    // publicise new timespan
    this.onTimespanChange.emit(this.timespan);
  }
}
<span *ngFor="let item of parsedTimespanPresets">
  <button (click)="timespanChanged(item)" [ngClass]="{'seperator-after-this-item': item.seperatorAfterThisItem}" class="btn btn-sm btn-default">
    {{item.label}}
  </button>
  <br>
</span>

./predefined-timespan-selector.component.scss

:host {
     :not(.seperator-after-this-item)+br {
        display: none;
    }
    button {
        margin: 3px;
    }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""