lib/graph3d/Slider.js

Summary

Maintainability
A
2 hrs
Test Coverage
var util = require('../util');

/**
 * An html slider control with start/stop/prev/next buttons
 *
 * @constructor Slider
 * @param {Element} container  The element where the slider will be created
 * @param {Object} options   Available options:
 *                 {boolean} visible   If true (default) the
 *                           slider is visible.
 */
function Slider(container, options) {
  if (container === undefined) {
    throw new Error('No container element defined');
  }
  this.container = container;
  this.visible = (options && options.visible != undefined) ? options.visible : true;

  if (this.visible) {
    this.frame = document.createElement('DIV');
    //this.frame.style.backgroundColor = '#E5E5E5';
    this.frame.style.width = '100%';
    this.frame.style.position = 'relative';
    this.container.appendChild(this.frame);

    this.frame.prev = document.createElement('INPUT');
    this.frame.prev.type = 'BUTTON';
    this.frame.prev.value = 'Prev';
    this.frame.appendChild(this.frame.prev);

    this.frame.play = document.createElement('INPUT');
    this.frame.play.type = 'BUTTON';
    this.frame.play.value = 'Play';
    this.frame.appendChild(this.frame.play);

    this.frame.next = document.createElement('INPUT');
    this.frame.next.type = 'BUTTON';
    this.frame.next.value = 'Next';
    this.frame.appendChild(this.frame.next);

    this.frame.bar = document.createElement('INPUT');
    this.frame.bar.type = 'BUTTON';
    this.frame.bar.style.position = 'absolute';
    this.frame.bar.style.border = '1px solid red';
    this.frame.bar.style.width = '100px';
    this.frame.bar.style.height = '6px';
    this.frame.bar.style.borderRadius = '2px';
    this.frame.bar.style.MozBorderRadius = '2px';
    this.frame.bar.style.border = '1px solid #7F7F7F';
    this.frame.bar.style.backgroundColor = '#E5E5E5';
    this.frame.appendChild(this.frame.bar);

    this.frame.slide = document.createElement('INPUT');
    this.frame.slide.type = 'BUTTON';
    this.frame.slide.style.margin = '0px';
    this.frame.slide.value = ' ';
    this.frame.slide.style.position = 'relative';
    this.frame.slide.style.left = '-100px';
    this.frame.appendChild(this.frame.slide);

    // create events
    var me = this;
    this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
    this.frame.prev.onclick = function (event) {me.prev(event);};
    this.frame.play.onclick = function (event) {me.togglePlay(event);};
    this.frame.next.onclick = function (event) {me.next(event);};
  }

  this.onChangeCallback = undefined;

  this.values = [];
  this.index = undefined;

  this.playTimeout = undefined;
  this.playInterval = 1000; // milliseconds
  this.playLoop = true;
}

/**
 * Select the previous index
 */
Slider.prototype.prev = function() {
  var index = this.getIndex();
  if (index > 0) {
    index--;
    this.setIndex(index);
  }
};

/**
 * Select the next index
 */
Slider.prototype.next = function() {
  var index = this.getIndex();
  if (index < this.values.length - 1) {
    index++;
    this.setIndex(index);
  }
};

/**
 * Select the next index
 */
Slider.prototype.playNext = function() {
  var start = new Date();

  var index = this.getIndex();
  if (index < this.values.length - 1) {
    index++;
    this.setIndex(index);
  }
  else if (this.playLoop) {
    // jump to the start
    index = 0;
    this.setIndex(index);
  }

  var end = new Date();
  var diff = (end - start);

  // calculate how much time it to to set the index and to execute the callback
  // function.
  var interval = Math.max(this.playInterval - diff, 0);
  // document.title = diff // TODO: cleanup

  var me = this;
  this.playTimeout = setTimeout(function() {me.playNext();}, interval);
};

/**
 * Toggle start or stop playing
 */
Slider.prototype.togglePlay = function() {
  if (this.playTimeout === undefined) {
    this.play();
  } else {
    this.stop();
  }
};

/**
 * Start playing
 */
Slider.prototype.play = function() {
  // Test whether already playing
  if (this.playTimeout) return;

  this.playNext();

  if (this.frame) {
    this.frame.play.value = 'Stop';
  }
};

/**
 * Stop playing
 */
Slider.prototype.stop = function() {
  clearInterval(this.playTimeout);
  this.playTimeout = undefined;

  if (this.frame) {
    this.frame.play.value = 'Play';
  }
};

/**
 * Set a callback function which will be triggered when the value of the
 * slider bar has changed.
 *
 * @param {function} callback
 */
Slider.prototype.setOnChangeCallback = function(callback) {
  this.onChangeCallback = callback;
};

/**
 * Set the interval for playing the list
 * @param {number} interval   The interval in milliseconds
 */
Slider.prototype.setPlayInterval = function(interval) {
  this.playInterval = interval;
};

/**
 * Retrieve the current play interval
 * @return {number} interval   The interval in milliseconds
 */
Slider.prototype.getPlayInterval = function() {
  return this.playInterval;
};

/**
 * Set looping on or off
 * @param {boolean} doLoop  If true, the slider will jump to the start when
 *               the end is passed, and will jump to the end
 *               when the start is passed.
 *
 */
Slider.prototype.setPlayLoop = function(doLoop) {
  this.playLoop = doLoop;
};


/**
 * Execute the onchange callback function
 */
Slider.prototype.onChange = function() {
  if (this.onChangeCallback !== undefined) {
    this.onChangeCallback();
  }
};

/**
 * redraw the slider on the correct place
 */
Slider.prototype.redraw = function() {
  if (this.frame) {
    // resize the bar
    this.frame.bar.style.top = (this.frame.clientHeight/2 -
        this.frame.bar.offsetHeight/2) + 'px';
    this.frame.bar.style.width = (this.frame.clientWidth -
        this.frame.prev.clientWidth -
        this.frame.play.clientWidth -
        this.frame.next.clientWidth - 30)  + 'px';

    // position the slider button
    var left = this.indexToLeft(this.index);
    this.frame.slide.style.left = (left) + 'px';
  }
};


/**
 * Set the list with values for the slider
 * @param {Array} values   A javascript array with values (any type)
 */
Slider.prototype.setValues = function(values) {
  this.values = values;

  if (this.values.length > 0)
    this.setIndex(0);
  else
    this.index = undefined;
};

/**
 * Select a value by its index
 * @param {number} index
 */
Slider.prototype.setIndex = function(index) {
  if (index < this.values.length) {
    this.index = index;

    this.redraw();
    this.onChange();
  }
  else {
    throw new Error('Index out of range');
  }
};

/**
 * retrieve the index of the currently selected vaue
 * @return {number} index
 */
Slider.prototype.getIndex = function() {
  return this.index;
};


/**
 * retrieve the currently selected value
 * @return {*} value
 */
Slider.prototype.get = function() {
  return this.values[this.index];
};


Slider.prototype._onMouseDown = function(event) {
  // only react on left mouse button down
  var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  if (!leftButtonDown) return;

  this.startClientX = event.clientX;
  this.startSlideX = parseFloat(this.frame.slide.style.left);

  this.frame.style.cursor = 'move';

  // add event listeners to handle moving the contents
  // we store the function onmousemove and onmouseup in the graph, so we can
  // remove the eventlisteners lateron in the function mouseUp()
  var me = this;
  this.onmousemove = function (event) {me._onMouseMove(event);};
  this.onmouseup   = function (event) {me._onMouseUp(event);};
  util.addEventListener(document, 'mousemove', this.onmousemove);
  util.addEventListener(document, 'mouseup',   this.onmouseup);
  util.preventDefault(event);
};


Slider.prototype.leftToIndex = function (left) {
  var width = parseFloat(this.frame.bar.style.width) -
      this.frame.slide.clientWidth - 10;
  var x = left - 3;

  var index = Math.round(x / width * (this.values.length-1));
  if (index < 0) index = 0;
  if (index > this.values.length-1) index = this.values.length-1;

  return index;
};

Slider.prototype.indexToLeft = function (index) {
  var width = parseFloat(this.frame.bar.style.width) -
      this.frame.slide.clientWidth - 10;

  var x = index / (this.values.length-1) * width;
  var left = x + 3;

  return left;
};



Slider.prototype._onMouseMove = function (event) {
  var diff = event.clientX - this.startClientX;
  var x = this.startSlideX + diff;

  var index = this.leftToIndex(x);

  this.setIndex(index);

  util.preventDefault();
};


Slider.prototype._onMouseUp = function (event) {  // eslint-disable-line no-unused-vars
  this.frame.style.cursor = 'auto';

  // remove event listeners
  util.removeEventListener(document, 'mousemove', this.onmousemove);
  util.removeEventListener(document, 'mouseup', this.onmouseup);

  util.preventDefault();
};

module.exports = Slider;