Home Reference Source

src/base.js

'use strict';

import Constants from './constants';
import Immutable from 'immutable';
import {EventEmitter} from 'fbemitter';

export default class Base extends EventEmitter {
  constructor() {
    super();
    this.__parentFlow = null;
    this.__autoLink = false;
    this.__state = new Immutable.Map();
    this.__readyState = new Immutable.Set();
    this.__changeEvent = 'change';
  }

  __doStep(step) {
    this.__emit(Constants.StepStatus.STARTED, this.__changeEvent, step);
    if (step instanceof Base) {
      // Going to the next step is safer than calling start
      // because the Flow could have already started.
      if (this.__autoLink) {
        step.linkToParentState(this);
      }
      step.gotoNextStep();
    }
    else {
      try {
        step(this);
      }
      catch (e) {
        this.__handleError(step, e);
      }
    }
  }

  __emit(stepStatus, opt_event, opt_step) {
    if (opt_event == null) {
      opt_event = this.__changeEvent;
    }
    this.emit(opt_event, this, stepStatus, opt_step);
  }

  __handleError(step, error) {
    this.setState({'error': error});
    this.failStep(step);
  }

  autoLinkChildFlows(enabled) {
    this.__autoLink = enabled;
  }

  failStep(step) {
    this.__readyState = this.__readyState.add(
      Constants.States.FAILED
    );
    this.__emit(Constants.States.FAILED, Constants.States.FAILED, step);
    this.__emit(Constants.StepStatus.FAILED, null, step);
  }

  finish() {
    this.__readyState = this.__readyState.add(
      Constants.States.FINISHED
    );
    this.__emit(Constants.States.FINISHED, Constants.States.FINISHED);
  }

  getState(opt_key, opt_value) {
    if (this.__parentFlow != null) {
      return this.__parentFlow.getState(opt_key, opt_value);
    }
    if (opt_key != null) {
      return this.__state.get(opt_key, opt_value);
    }
    return this.__state;
  }

  gotoNextStep() {
    throw 'Not Implemented';
  }

  isFailed() {
    return this.__readyState.has(Constants.States.FAILED);
  }

  isFinished() {
    return (this.__readyState.has(Constants.States.FINISHED) ||
            this.__readyState.has(Constants.States.FAILED));
  }

  isStarted() {
    return this.__readyState.has(Constants.States.STARTED);
  }

  linkToParentState(parentFlow) {
    if (!(parentFlow instanceof Base)) {
      throw 'Expected flow object';
    }

    this.__parentFlow = parentFlow;
  }

  on(event, cb) {
    return this.addListener(event, cb);
  }

  onOnce(event, cb) {
    return this.once(event, cb);
  }

  setState(nextState) {
    if (this.__parentFlow != null) {
      return this.__parentFlow.setState(nextState);
    }
    // Do a manual soft merge to prevent array's and objects
    // from being converted to immutable List's and Maps
    Object.keys(nextState).forEach(key => {
      this.__state = this.__state.set(key, nextState[key]);
    });
  }

  start() {
    this.__readyState = this.__readyState.add(
      Constants.States.STARTED
    );
    this.__emit(Constants.States.STARTED, Constants.States.STARTED);
    this.gotoNextStep();
  }

  watch(cb) {
    return this.addListener(this.__changeEvent, cb);
  }

  watchOnce(cb) {
    return this.once(this.__changeEvent, cb);
  }
}