View file imageupload/styles/prosilver/template/js/jquery.knob.js

File size: 13.96Kb
/*!jQuery Knob*/
/**
 * Downward compatible, touchable dial
 *
 * Version: 1.2.0 (15/07/2012)
 * Requires: jQuery v1.7+
 *
 * Copyright (c) 2012 Anthony Terrien
 * Under MIT and GPL licenses:
 *	http://www.opensource.org/licenses/mit-license.php
 *	http://www.gnu.org/licenses/gpl.html
 *
 * Thanks to vor, eskimoblood, spiffistan, FabrizioC
 */
(function($) {

	/**
	 * Kontrol library
	 */
	"use strict";

	/**
	 * Definition of globals and core
	 */
	var k = {}, // kontrol
		max = Math.max,
		min = Math.min;

	k.c = {};
	k.c.d = $(document);
	k.c.t = function (e) {
		return e.originalEvent.touches.length - 1;
	};

	/**
	 * Kontrol Object
	 *
	 * Definition of an abstract UI control
	 *
	 * Each concrete component must call this one.
	 * <code>
	 * k.o.call(this);
	 * </code>
	 */
	k.o = function () {
		var s = this;

		this.o = null; // array of options
		this.$ = null; // jQuery wrapped element
		this.i = null; // mixed HTMLInputElement or array of HTMLInputElement
		this.g = null; // 2D graphics context for 'pre-rendering'
		this.v = null; // value ; mixed array or integer
		this.cv = null; // change value ; not commited value
		this.x = 0; // canvas x position
		this.y = 0; // canvas y position
		this.$c = null; // jQuery canvas element
		this.c = null; // rendered canvas context
		this.t = 0; // touches index
		this.isInit = false;
		this.fgColor = null; // main color
		this.pColor = null; // previous color
		this.dH = null; // draw hook
		this.cH = null; // change hook
		this.eH = null; // cancel hook
		this.rH = null; // release hook

		this.run = function () {
			var cf = function (e, conf) {
				var k;
				for (k in conf) {
					s.o[k] = conf[k];
				}
				s.init();
				s._configure()
				 ._draw();
			};

			if(this.$.data('kontroled')) return;
			this.$.data('kontroled', true);

			this.extend();
			this.o = $.extend(
				{
					// Config
					min : this.$.data('min') || 0,
					max : this.$.data('max') || 100,
					stopper : true,
					readOnly : this.$.data('readonly'),

					// UI
					cursor : (this.$.data('cursor') === true && 30)
								|| this.$.data('cursor')
								|| 0,
					thickness : this.$.data('thickness') || 0.35,
					lineCap : this.$.data('linecap') || 'butt',
					width : this.$.data('width') || 200,
					height : this.$.data('height') || 200,
					displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'),
					displayPrevious : this.$.data('displayprevious'),
					fgColor : this.$.data('fgcolor') || '#87CEEB',
					inputColor: this.$.data('inputcolor') || this.$.data('fgcolor') || '#87CEEB',
					inline : false,
					step : this.$.data('step') || 1,

					// Hooks
					draw : null, // function () {}
					change : null, // function (value) {}
					cancel : null, // function () {}
					release : null // function (value) {}
				}, this.o
			);

			// routing value
			if(this.$.is('fieldset')) {

				// fieldset = array of integer
				this.v = {};
				this.i = this.$.find('input')
				this.i.each(function(k) {
					var $this = $(this);
					s.i[k] = $this;
					s.v[k] = $this.val();

					$this.bind(
						'change'
						, function () {
							var val = {};
							val[k] = $this.val();
							s.val(val);
						}
					);
				});
				this.$.find('legend').remove();

			} else {
				// input = integer
				this.i = this.$;
				this.v = this.$.val();
				(this.v == '') && (this.v = this.o.min);

				this.$.bind(
					'change'
					, function () {
						s.val(s._validate(s.$.val()));
					}
				);
			}

			(!this.o.displayInput) && this.$.hide();

			this.$c = $('<canvas width="' +
							this.o.width + 'px" height="' +
							this.o.height + 'px"></canvas>');
			this.c = this.$c[0].getContext("2d");

			this.$
				.wrap($('<div style="' + (this.o.inline ? 'display:inline;' : '') +
						'width:' + this.o.width + 'px;height:' +
						this.o.height + 'px;"></div>'))
				.before(this.$c);

			if (this.v instanceof Object) {
				this.cv = {};
				this.copy(this.v, this.cv);
			} else {
				this.cv = this.v;
			}

			this.$
				.bind("configure", cf)
				.parent()
				.bind("configure", cf);

			this._listen()
				._configure()
				._xy()
				.init();

			this.isInit = true;

			this._draw();

			return this;
		};

		this._draw = function () {

			// canvas pre-rendering
			var d = true,
				c = document.createElement('canvas');

			c.width = s.o.width;
			c.height = s.o.height;
			s.g = c.getContext('2d');

			s.clear();

			s.dH
			&& (d = s.dH());

			(d !== false) && s.draw();

			s.c.drawImage(c, 0, 0);
			c = null;
		};

		this._touch = function (e) {

			var touchMove = function (e) {

				var v = s.xy2val(
							e.originalEvent.touches[s.t].pageX,
							e.originalEvent.touches[s.t].pageY
							);

				if (v == s.cv) return;

				if (
					s.cH
					&& (s.cH(v) === false)
				) return;

				s.change(s._validate(v));
				s._draw();
			};

			// get touches index
			this.t = k.c.t(e);

			// First touch
			touchMove(e);

			// Touch events listeners
			k.c.d
				.bind("touchmove.k", touchMove)
				.bind(
					"touchend.k"
					, function () {
						k.c.d.unbind('touchmove.k touchend.k');

						if (
							s.rH
							&& (s.rH(s.cv) === false)
						) return;

						s.val(s.cv);
					}
				);

			return this;
		};

		this._mouse = function (e) {

			var mouseMove = function (e) {
				var v = s.xy2val(e.pageX, e.pageY);
				if (v == s.cv) return;

				if (
					s.cH
					&& (s.cH(v) === false)
				) return;

				s.change(s._validate(v));
				s._draw();
			};

			// First click
			mouseMove(e);

			// Mouse events listeners
			k.c.d
				.bind("mousemove.k", mouseMove)
				.bind(
					// Escape key cancel current change
					"keyup.k"
					, function (e) {
						if (e.keyCode === 27) {
							k.c.d.unbind("mouseup.k mousemove.k keyup.k");

							if (
								s.eH
								&& (s.eH() === false)
							) return;

							s.cancel();
						}
					}
				)
				.bind(
					"mouseup.k"
					, function (e) {
						k.c.d.unbind('mousemove.k mouseup.k keyup.k');

						if (
							s.rH
							&& (s.rH(s.cv) === false)
						) return;

						s.val(s.cv);
					}
				);

			return this;
		};

		this._xy = function () {
			var o = this.$c.offset();
			this.x = o.left;
			this.y = o.top;
			return this;
		};

		this._listen = function () {

			if (!this.o.readOnly) {
				this.$c
					.bind(
						"mousedown"
						, function (e) {
							e.preventDefault();
							s._xy()._mouse(e);
						 }
					)
					.bind(
						"touchstart"
						, function (e) {
							e.preventDefault();
							s._xy()._touch(e);
						 }
					);
				this.listen();
			} else {
				this.$.attr('readonly', 'readonly');
			}

			return this;
		};

		this._configure = function () {

			// Hooks
			if (this.o.draw) this.dH = this.o.draw;
			if (this.o.change) this.cH = this.o.change;
			if (this.o.cancel) this.eH = this.o.cancel;
			if (this.o.release) this.rH = this.o.release;

			if (this.o.displayPrevious) {
				this.pColor = this.h2rgba(this.o.fgColor, "0.4");
				this.fgColor = this.h2rgba(this.o.fgColor, "0.6");
			} else {
				this.fgColor = this.o.fgColor;
			}

			return this;
		};

		this._clear = function () {
			this.$c[0].width = this.$c[0].width;
		};

		this._validate = function(v) {
			return (~~ (((v < 0) ? -0.5 : 0.5) + (v/this.o.step))) * this.o.step;
		};

		// Abstract methods
		this.listen = function () {}; // on start, one time
		this.extend = function () {}; // each time configure triggered
		this.init = function () {}; // each time configure triggered
		this.change = function (v) {}; // on change
		this.val = function (v) {}; // on release
		this.xy2val = function (x, y) {}; //
		this.draw = function () {}; // on change / on release
		this.clear = function () { this._clear(); };

		// Utils
		this.h2rgba = function (h, a) {
			var rgb;
			h = h.substring(1,7)
			rgb = [parseInt(h.substring(0,2),16)
					,parseInt(h.substring(2,4),16)
					,parseInt(h.substring(4,6),16)];
			return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")";
		};

		this.copy = function (f, t) {
			for (var i in f) { t[i] = f[i]; }
		};
	};

	/**
	 * k.Dial
	 */
	k.Dial = function () {
		k.o.call(this);

		this.startAngle = null;
		this.xy = null;
		this.radius = null;
		this.lineWidth = null;
		this.cursorExt = null;
		this.w2 = null;
		this.PI2 = 2*Math.PI;

		this.extend = function () {
			this.o = $.extend(
				{
					bgColor : this.$.data('bgcolor') || '#EEEEEE',
					angleOffset : this.$.data('angleoffset') || 0,
					angleArc : this.$.data('anglearc') || 360,
					inline : true
				}, this.o
			);
		};

		this.val = function (v) {
			if (null != v) {
				this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v;
				this.v = this.cv;
				this.$.val(this.v);
				this._draw();
			} else {
				return this.v;
			}
		};

		this.xy2val = function (x, y) {
			var a, ret;

			a = Math.atan2(
						x - (this.x + this.w2)
						, - (y - this.y - this.w2)
					) - this.angleOffset;

			if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) {
				// if isset angleArc option, set to min if .5 under min
				a = 0;
			} else if (a < 0) {
				a += this.PI2;
			}

			ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc))
					+ this.o.min;

			this.o.stopper
			&& (ret = max(min(ret, this.o.max), this.o.min));

			return ret;
		};

		this.listen = function () {
			// bind MouseWheel
			var s = this,
				mw = function (e) {
							e.preventDefault();
							var ori = e.originalEvent
								,deltaX = ori.detail || ori.wheelDeltaX
								,deltaY = ori.detail || ori.wheelDeltaY
								,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? s.o.step : deltaX<0 || deltaY<0 ? -s.o.step : 0);

							if (
								s.cH
								&& (s.cH(v) === false)
							) return;

							s.val(v);
						}
				, kval, to, m = 1, kv = {37:-s.o.step, 38:s.o.step, 39:s.o.step, 40:-s.o.step};

			this.$
				.bind(
					"keydown"
					,function (e) {
						var kc = e.keyCode;

						// numpad support
						if(kc >= 96 && kc <= 105) {
							kc = e.keyCode = kc - 48;
						}

						kval = parseInt(String.fromCharCode(kc));

						if (isNaN(kval)) {

							(kc !== 13)		 // enter
							&& (kc !== 8)		// bs
							&& (kc !== 9)		// tab
							&& (kc !== 189)	 // -
							&& e.preventDefault();

							// arrows
							if ($.inArray(kc,[37,38,39,40]) > -1) {
								e.preventDefault();

								var v = parseInt(s.$.val()) + kv[kc] * m;

								s.o.stopper
								&& (v = max(min(v, s.o.max), s.o.min));

								s.change(v);
								s._draw();

								// long time keydown speed-up
								to = window.setTimeout(
									function () { m*=2; }
									,30
								);
							}
						}
					}
				)
				.bind(
					"keyup"
					,function (e) {
						if (isNaN(kval)) {
							if (to) {
								window.clearTimeout(to);
								to = null;
								m = 1;
								s.val(s.$.val());
							}
						} else {
							// kval postcond
							(s.$.val() > s.o.max && s.$.val(s.o.max))
							|| (s.$.val() < s.o.min && s.$.val(s.o.min));
						}

					}
				);

			this.$c.bind("mousewheel DOMMouseScroll", mw);
			this.$.bind("mousewheel DOMMouseScroll", mw)
		};

		this.init = function () {

			if (
				this.v < this.o.min
				|| this.v > this.o.max
			) this.v = this.o.min;

			this.$.val(this.v);
			this.w2 = this.o.width / 2;
			this.cursorExt = this.o.cursor / 100;
			this.xy = this.w2;
			this.lineWidth = this.xy * this.o.thickness;
			this.lineCap = this.o.lineCap;
			this.radius = this.xy - this.lineWidth / 2;

			this.o.angleOffset
			&& (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset);

			this.o.angleArc
			&& (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc);

			// deg to rad
			this.angleOffset = this.o.angleOffset * Math.PI / 180;
			this.angleArc = this.o.angleArc * Math.PI / 180;

			// compute start and end angles
			this.startAngle = 1.5 * Math.PI + this.angleOffset;
			this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc;

			var s = max(
							String(Math.abs(this.o.max)).length
							, String(Math.abs(this.o.min)).length
							, 2
							) + 2;

			this.o.displayInput
				&& this.i.css({
						'width' : ((this.o.width / 2 + 4) >> 0) + 'px'
						,'height' : ((this.o.width / 3) >> 0) + 'px'
						,'position' : 'absolute'
						,'vertical-align' : 'middle'
						,'margin-top' : ((this.o.width / 3) >> 0) + 'px'
						,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px'
						,'border' : 0
						,'background' : 'none'
						,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial'
						,'text-align' : 'center'
						,'color' : this.o.inputColor || this.o.fgColor
						,'padding' : '0px'
						,'-webkit-appearance': 'none'
						})
				|| this.i.css({
						'width' : '0px'
						,'visibility' : 'hidden'
						});
		};

		this.change = function (v) {
			this.cv = v;
			this.$.val(v);
		};

		this.angle = function (v) {
			return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min);
		};

		this.draw = function () {

			var c = this.g,				 // context
				a = this.angle(this.cv)	// Angle
				, sat = this.startAngle	 // Start angle
				, eat = sat + a			 // End angle
				, sa, ea					// Previous angles
				, r = 1;

			c.lineWidth = this.lineWidth;

			c.lineCap = this.lineCap;

			this.o.cursor
				&& (sat = eat - this.cursorExt)
				&& (eat = eat + this.cursorExt);

			c.beginPath();
				c.strokeStyle = this.o.bgColor;
				c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true);
			c.stroke();

			if (this.o.displayPrevious) {
				ea = this.startAngle + this.angle(this.v);
				sa = this.startAngle;
				this.o.cursor
					&& (sa = ea - this.cursorExt)
					&& (ea = ea + this.cursorExt);

				c.beginPath();
					c.strokeStyle = this.pColor;
					c.arc(this.xy, this.xy, this.radius, sa, ea, false);
				c.stroke();
				r = (this.cv == this.v);
			}

			c.beginPath();
				c.strokeStyle = r ? this.o.fgColor : this.fgColor ;
				c.arc(this.xy, this.xy, this.radius, sat, eat, false);
			c.stroke();
		};

		this.cancel = function () {
			this.val(this.v);
		};
	};

	$.fn.dial = $.fn.knob = function (o) {
		return this.each(
			function () {
				var d = new k.Dial();
				d.o = o;
				d.$ = $(this);
				d.run();
			}
		).parent();
	};

})(jQuery);