/*!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.
*
* k.o.call(this);
*
*/
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; // deprecated 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.w = 0; // canvas width
this.h = 0; // canvas height
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.scale = 1; // scale factor
this.relative = false;
this.relativeWidth = false;
this.relativeHeight = false;
this.$div = null; // component div
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' ) || (this.$.attr( 'readonly' ) == 'readonly'),
// UI
cursor : (this.$.data( 'cursor' ) === true && 30)
|| this.$.data( 'cursor' )
|| 0,
thickness : (
this.$.data( 'thickness' )
&& Math.max( Math.min( this.$.data( 'thickness' ), 1 ), 0.01 )
)
|| 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' ),
font: this.$.data( 'font' ) || 'Arial',
fontWeight: this.$.data( 'font-weight' ) || 'bold',
inline : false,
step : this.$.data( 'step' ) || 1,
// Hooks
draw : null, // function () {}
change : null, // function (value) {}
cancel : null, // function () {}
release : null, // function (value) {}
error : null // function () {}
}, this.o
);
// finalize options
if ( ! this.o.inputColor) {
this.o.inputColor = this.o.fgColor;
}
// 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 keyup'
, 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 keyup'
, function () {
s.val( s._validate( s.$.val() ) );
}
);
}
( ! this.o.displayInput) && this.$.hide();
// adds needed DOM elements (canvas, div)
this.$c = $( document.createElement( 'canvas' ) );
if (typeof G_vmlCanvasManager !== 'undefined') {
G_vmlCanvasManager.initElement( this.$c[0] );
}
this.c = this.$c[0].getContext ? this.$c[0].getContext( '2d' ) : null;
if ( ! this.c) {
this.o.error && this.o.error();
return;
}
// hdpi support
this.scale = (window.devicePixelRatio || 1) /
(
this.c.webkitBackingStorePixelRatio ||
this.c.mozBackingStorePixelRatio ||
this.c.msBackingStorePixelRatio ||
this.c.oBackingStorePixelRatio ||
this.c.backingStorePixelRatio || 1
);
// detects relative width / height
this.relativeWidth = ((this.o.width % 1 !== 0)
&& this.o.width.indexOf( '%' ));
this.relativeHeight = ((this.o.height % 1 !== 0)
&& this.o.height.indexOf( '%' ));
this.relative = (this.relativeWidth || this.relativeHeight);
// wraps all elements in a div
this.$div = $(
'
'
);
this.$.wrap( this.$div ).before( this.$c );
this.$div = this.$.parent();
// computes size and carves the component
this._carve();
// prepares props for transaction
if (this.v instanceof Object) {
this.cv = {};
this.copy( this.v, this.cv );
} else {
this.cv = this.v;
}
// binds configure event
this.$
.bind( "configure", cf )
.parent()
.bind( "configure", cf );
// finalize init
this._listen()
._configure()
._xy()
.init();
this.isInit = true;
// the most important !
this._draw();
return this;
};
this._carve = function() {
if (this.relative) {
var w = this.relativeWidth
? this.$div.parent().width()
* parseInt( this.o.width ) / 100
: this.$div.parent().width(),
h = this.relativeHeight
? this.$div.parent().height()
* parseInt( this.o.height ) / 100
: this.$div.parent().height();
// apply relative
this.w = this.h = Math.min( w, h );
} else {
this.w = this.o.width;
this.h = this.o.height;
}
// finalize div
this.$div.css(
{
'width': this.w + 'px',
'height': this.h + 'px'
}
);
// finalize canvas with computed width
this.$c.attr(
{
width: this.w,
height: this.h
}
);
// scaling
if (this.scale !== 1) {
this.$c[0].width = this.$c[0].width * this.scale;
this.$c[0].height = this.$c[0].height * this.scale;
this.$c.width( this.w );
this.$c.height( this.h );
}
return this;
}
this._draw = function () {
// canvas pre-rendering
var d = true;
s.g = s.c;
s.clear();
s.dH
&& (d = s.dH());
(d !== false) && s.draw();
};
this._touch = function (e) {
var touchMove = function (e) {
var v = s.xy2val(
e.originalEvent.touches[s.t].pageX,
e.originalEvent.touches[s.t].pageY
);
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 );
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' );
}
if (this.relative) {
$( window ).resize(
function() {
s._carve()
.init();
s._draw();
}
);
}
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, mwTimerStop, mwTimerRelease,
mw = function (e) {
e.preventDefault();
var ori = e.originalEvent
,deltaX = ori.detail || ori.wheelDeltaX
,deltaY = ori.detail || ori.wheelDeltaY
,v = s._validate( s.$.val() )
+ (deltaX > 0 || deltaY > 0 ? s.o.step : deltaX < 0 || deltaY < 0 ? -s.o.step : 0);
v = max( min( v, s.o.max ), s.o.min );
s.val( v );
if (s.rH) {
// Handle mousewheel stop
clearTimeout( mwTimerStop );
mwTimerStop = setTimeout(
function() {
s.rH( v );
mwTimerStop = null;
}, 100
);
// Handle mousewheel releases
if ( ! mwTimerRelease) {
mwTimerRelease = setTimeout(
function() {
if (mwTimerStop) {
s.rH( v );
}
mwTimerRelease = null;
}, 200
);
}
}
}
, 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.w / 2;
this.cursorExt = this.o.cursor / 100;
this.xy = this.w2 * this.scale;
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.w / 2 + 4) >> 0) + 'px'
,'height' : ((this.w / 3) >> 0) + 'px'
,'position' : 'absolute'
,'vertical-align' : 'middle'
,'margin-top' : ((this.w / 3) >> 0) + 'px'
,'margin-left' : '-' + ((this.w * 3 / 4 + 2) >> 0) + 'px'
,'border' : 0
,'background' : 'none'
,'font' : this.o.fontWeight + ' ' + ((this.w / s) >> 0) + 'px ' + this.o.font
,'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) {
if (v == this.cv) {
return;
}
this.cv = v;
if (
this.cH
&& (this.cH( v ) === false)
) {
return;
}
};
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 - 0.00001, this.startAngle + 0.00001, 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 - 0.00001, ea + 0.00001, 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 - 0.00001, eat + 0.00001, 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 );