Source: ui/scroll.bar.js

/**
 * @module stb/ui/scroll.bar
 * @author Igor Zaporozhets <deadbyelpy@gmail.com>
 * @license GNU GENERAL PUBLIC LICENSE Version 3
 */

'use strict';

var Component = require('../component');


/**
 * Base scroll bar implementation.
 *
 * @constructor
 * @extends Component
 *
 * @param {Object} [config={}] init parameters (all inherited from the parent)
 * @param {number} [config.value=0] initial thumb position
 * @param {number} [config.realSize=100] actual scroll size
 * @param {number} [config.viewSize=10] visible area size
 *
 * @example
 * var ScrollBar = require('stb/ui/scroll.bar'),
 *     scrollBar = new ScrollBar({
 *         viewSize: 5,
 *         realSize: 25,
 *         events: {
 *             done: function () {
 *                 debug.log('ScrollBar: done');
 *             },
 *             change: function ( data ) {
 *                 debug.log('ScrollBar: change to ' + data.curr + ' from ' + data.prev);
 *             }
 *         }
 *     });
 */
function ScrollBar ( config ) {
	// sanitize
	config = config || {};

	/**
	 * Visible area size.
	 *
	 * @type {number}
	 */
	this.viewSize = 10;

	/**
	 * Scroll area actual height or width (if scroll is horizontal).
	 *
	 * @type {number}
	 */
	this.realSize = 100;

	/**
	 * Scroll thumb position.
	 *
	 * @type {number}
	 */
	this.value = 0;

	/**
	 * Component orientation.
	 *
	 * @type {number}
	 */
	this.type = this.TYPE_VERTICAL;

	/**
	 * Geometry of the scroll thumb element.
	 *
	 * @type {ClientRect}
	 */
	this.thumbRect = null;

	/**
	 * Geometry of the scroll track element.
	 *
	 * @type {ClientRect}
	 */
	this.trackRect = null;

	// can't accept focus
	config.focusable = config.focusable || false;

	// parent init
	Component.call(this, config);

	// create $body if not provided
	if ( this.$node === this.$body ) {
		// insert thumb line
		this.$body = this.$node.appendChild(document.createElement('div'));
	}

	// horizontal or vertical
	if ( config.type !== undefined ) {
		if ( DEBUG ) {
			if ( Number(config.type) !== config.type ) { throw 'config.type must be a number'; }
		}
		// apply
		this.type = config.type;
	}

	// correct CSS class names
	this.$node.classList.add('scrollBar');
	this.$body.classList.add('thumb');

	if ( this.type === this.TYPE_HORIZONTAL ) {
		this.$node.classList.add('horizontal');
	}

	// component setup
	this.init(config);
}


// inheritance
ScrollBar.prototype = Object.create(Component.prototype);
ScrollBar.prototype.constructor = ScrollBar;


ScrollBar.prototype.TYPE_VERTICAL   = 1;
ScrollBar.prototype.TYPE_HORIZONTAL = 2;


/**
 * Init or re-init realSize/viewSize/value parameters.
 *
 * @param {Object} config init parameters (subset of constructor config params)
 */
ScrollBar.prototype.init = function ( config ) {
	config = config || {};

	if ( DEBUG ) {
		if ( arguments.length !== 1 ) { throw 'wrong arguments number'; }
		if ( typeof config !== 'object' ) { throw 'wrong config type'; }
	}

	// set actual scroll size
	if ( config.realSize !== undefined ) {
		if ( DEBUG ) {
			if ( Number(config.realSize) !== config.realSize ) { throw 'config.realSize value must be a number'; }
		}
		// apply
		this.realSize = config.realSize;
	}

	// set visible area size
	if ( config.viewSize !== undefined ) {
		if ( DEBUG ) {
			if ( Number(config.viewSize) !== config.viewSize ) { throw 'config.viewSize value must be a number'; }
			if ( config.viewSize <= 0 ) { throw 'config.viewSize value must be greater than 0'; }
		}
		// apply
		this.viewSize = config.viewSize;
	}

	// show or hide thumb
	if ( this.viewSize >= this.realSize ) {
		this.$body.classList.add('hidden');
	} else {
		this.$body.classList.remove('hidden');
	}

	// set thumb position
	if ( config.value !== undefined ) {
		// apply
		this.scrollTo(config.value);
	}

	// set thumb size
	if ( this.type === this.TYPE_VERTICAL ) {
		this.$body.style.height = (this.viewSize / this.realSize * 100) + '%';
	} else {
		this.$body.style.width = (this.viewSize / this.realSize * 100) + '%';
	}

	// geometry
	this.thumbRect = this.$body.getBoundingClientRect();
	this.trackRect = this.$node.getBoundingClientRect();
};


/**
 * Set position of the given value.
 * Does nothing in case when scroll is in the end and passed value is more than scroll bar length.
 *
 * @param {number} value new value to set
 * @return {boolean} operation result
 *
 * @fires module:stb/ui/scroll.bar~ScrollBar#done
 * @fires module:stb/ui/scroll.bar~ScrollBar#change
 */
ScrollBar.prototype.scrollTo = function ( value ) {
	if ( DEBUG ) {
		if ( arguments.length !== 1 ) { throw 'wrong arguments number'; }
		if ( Number(value) !== value ) { throw 'value must be a number'; }
		if ( this.realSize > this.viewSize && value > this.realSize - this.viewSize ) { throw 'value is greater than this.realSize-this.viewSize'; }
		if ( value < 0 ) { throw 'value is less then 0'; }
	}

	// value has changed
	if ( this.value !== value ) {
		// track and thumb geometry was not set
		if ( this.thumbRect.height === 0 || this.thumbRect.width === 0 ) {
			// apply
			this.trackRect = this.$node.getBoundingClientRect();
			this.thumbRect = this.$body.getBoundingClientRect();
		}

		// set scroll bar width
		if ( this.type === this.TYPE_VERTICAL ) {
			this.$body.style.marginTop = ((this.trackRect.height - this.thumbRect.height) * value / (this.realSize - this.viewSize)) + 'px';
		} else {
			this.$body.style.marginLeft = ((this.trackRect.width - this.thumbRect.width) * value / (this.realSize - this.viewSize)) + 'px';
		}

		// there are some listeners
		if ( this.events['change'] !== undefined ) {
			/**
			 * Update scroll value.
			 *
			 * @event module:stb/ui/scroll.bar~ScrollBar#change
			 *
			 * @type {Object}
			 * @property {number} prev old/previous scroll value
			 * @property {number} curr new/current scroll value
			 */
			this.emit('change', {curr: value, prev: this.value});
		}

		// is it the end?
		if ( value >= this.realSize ) {
			value = this.realSize;

			// there are some listeners
			if ( this.events['done'] !== undefined ) {
				/**
				 * Set scroll to its maximum value.
				 *
				 * @event module:stb/ui/scroll.bar~ScrollBar#done
				 */
				this.emit('done');
			}
		}

		// set new value
		this.value = value;

		return true;
	}

	// nothing was done
	return false;
};


// public
module.exports = ScrollBar;