/** * Dragdealer (Dgd) JS v0.9.5 * http://skidding.github.io/dragdealer/ * * Copyright (c) 2010, Ovidiu Chereches * MIT License * http://legal.ovidiu.ch/licenses/MIT * * Dependencies: * x.js * * 2014-11-24 wabis: LayoutChange events integrated via x-modules xAddEventLayoutChange() (see x.js) * 2015-10-12 wabis: New Formats, Context Menu disabled * 2016-01-08 wabis: Fixed: disabling Context Menu error on IE8 (addEventListener not implemented) * 2016-08-14 wabis: use of addEventListener/removeEventListener; * new: free() to release event listeners and delete slider DOM * new: option autoUpdateLayout=false to suppress handling of layout/resize changes here */ /* Cursor */ var DgdCursor = { x: 0, y: 0, init: function() { var mouseHandler = function CB_OnMouse(e) { //console.log( 'DgdCursor.on'+type+'move' ); DgdCursor.refresh(e); } document.addEventListener( 'mousemove', mouseHandler, false ); document.addEventListener( 'touchmove', mouseHandler, false ); }, refresh: function(e) { if (e.type == 'mousemove') { this.set(e); } else if (e.touches) { this.set(e.touches[0]); } }, set: function(e) { if(e.pageX || e.pageY) { this.x = e.pageX; this.y = e.pageY; } else if(e.clientX || e.clientY) { this.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; this.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; } } }; DgdCursor.init(); /* Position */ var DgdPosition = { get: function(obj) { var curleft = 0; var curtop = 0; if(obj.offsetParent) { do { curleft += obj.offsetLeft; curtop += obj.offsetTop; } while((obj = obj.offsetParent)); } return [curleft, curtop]; } }; /* Dragdealer Slider */ function DgdSliderHtml( aID, aCaption, aHandleColor, aSliderColor ) { aCaption = xDefStr( aCaption, '⇔' ); aHandleColor = xDefStr( aHandleColor, '' ); aSliderColor = xDefStr( aSliderColor, '' ); var s = ''; var style = ''; if (aSliderColor != '') style = ' style="background-color: ' + aSliderColor + ';"'; s += '<div id="' + aID + '" class="Slider"' + style + '>'; style = ''; if (aHandleColor != '') style = ' style="background-color: ' + aHandleColor + ';"'; s += '<div id="' + aID + '-Handle" class="Handle"' + style + '>' + aCaption + '</div></div>'; return s; } var DgdSlider = function(wrapper, options) { if (!(wrapper = xElement( wrapper ))) return; var handle = xGetByTag( 'div', wrapper )[0]; if(!xHasClass(handle,'Handle')) return; this.init(wrapper, handle, options || {}); this.addListeners(); this.setup(false); this.IsLife = true; }; DgdSlider.prototype = { sliderList: [], animationFrameId: null, init: function(wrapper, handle, options) { DgdSlider.prototype.sliderList.push( this ); this.isSetup = false; this.wrapper = wrapper; this.handle = handle; this.options = options; this.disabled = this.getOption('disabled', false); this.horizontal = this.getOption('horizontal', true); this.vertical = this.getOption('vertical', false); this.slide = this.getOption('slide', true); this.steps = this.getOption('steps', 0); this.snap = this.getOption('snap', false); this.loose = this.getOption('loose', false); this.speed = this.getOption('speed', 10) / 100; this.xPrecision = this.getOption('xPrecision', 0); this.yPrecision = this.getOption('yPrecision', 0); this.autoUpdateLayout = this.getOption('autoUpdateLayout', true); this.callback = options.callback || null; this.animationCallback = options.animationCallback || null; this.bounds = { left: options.left || 0, right: -(options.right || 0), top: options.top || 0, bottom: -(options.bottom || 0), x0: 0, x1: 0, xRange: 0, y0: 0, y1: 0, yRange: 0 }; this.value = { prev: [-1, -1], current: [options.x || 0, options.y || 0], target: [options.x || 0, options.y || 0] }; this.offset = { wrapper: [0, 0], mouse: [0, 0], prev: [-999999, -999999], current: [0, 0], target: [0, 0] }; this.change = [0, 0]; this.activity = false; this.dragging = false; this.tapping = false; // event handler -------------------------------------- var self = this; this.selectstartHandler = function CB_OnSelectStart() { //console.log( 'DgdSlider.wrapper.onselectstart' ); return false; }; this.mousedownHandler = function CB_OnMouseDown(e) { //console.log( 'DgdSlider.handle.onmousedown' ); self.handleDownHandler(e); }; this.contextmenuHandler = function CB_OnContextmenu(e){ //e.stopPropagation(); e.preventDefault(); //e.cancelBubble = true; return false; }; this.wrapperMousedownHandler = function CB_OnMouseDown(e) { //console.log( 'DgdSlider.wrapper.onmousedown' ); self.wrapperDownHandler(e); }; this.mouseupHandler = function CB_OnMouseUp(e) { //console.log( 'DgdSlider.document.onmouseup' ); self.documentUpHandler(e); }; this.touchendHandler = function CB_OnTouchEnd(e) { //console.log( 'DgdSlider.document.ontouchend' ); self.documentUpHandler(e); }; this.windowResizeHandler = function CB_OnWindowResize() { //console.log( 'DgdSlider.window.onresize' ); self.documentResizeHandler(); }; this.mousemoveHandler = function CB_OnMouseMove() { //console.log( 'DgdSlider.wrapper.onmousemove' ); self.activity = true; } this.clickHandler = function CB_OnClick() { //console.log( 'DgdSlider.wrapper.onclick' ); return !self.activity; } this.IsLife = false; }, free: function( deleteDom ) { deleteDom = xDefBool( deleteDom, false ); // remove this.slider from sliderList xArrRemove( DgdSlider.prototype.sliderList, function CB_Compare_Slider(slider){ return slider == this; }, this ); // if last slider is removed, cancel animation if (DgdSlider.prototype.sliderList.length == 0) { cancelAnimationFrame( DgdSlider.prototype.animationFrameId ); DgdSlider.prototype.animationFrameId = null; } this.removeListeners(); if (deleteDom) { var domObj = this.wrapper; if (domObj) xRemoveChild( xParent( domObj ), domObj ); } this.IsLife = false; }, updateLayout: function() { this.documentResizeHandler(); }, getOption: function(name, defaultValue) { return xDefAnyOrNull( this.options[name], defaultValue ); }, setup: function(reset) { if (!reset && (this.isSetup || !xIsDisplayed(this.wrapper))) return false; this.setWrapperOffset(); this.setBoundsPadding(); this.setBounds(); this.isSetup = true; return true; }, setWrapperOffset: function() { this.offset.wrapper = DgdPosition.get(this.wrapper); }, setBoundsPadding: function() { if(!this.bounds.left && !this.bounds.right) { this.bounds.left = DgdPosition.get(this.handle)[0] - this.offset.wrapper[0]; this.bounds.right = -this.bounds.left; } if(!this.bounds.top && !this.bounds.bottom) { this.bounds.top = DgdPosition.get(this.handle)[1] - this.offset.wrapper[1]; this.bounds.bottom = -this.bounds.top; } }, setBounds: function() { this.bounds.x0 = this.bounds.left; this.bounds.x1 = this.wrapper.offsetWidth + this.bounds.right; this.bounds.xRange = (this.bounds.x1 - this.bounds.x0) - this.handle.offsetWidth; this.bounds.y0 = this.bounds.top; this.bounds.y1 = this.wrapper.offsetHeight + this.bounds.bottom; this.bounds.yRange = (this.bounds.y1 - this.bounds.y0) - this.handle.offsetHeight; this.bounds.xStep = 1 / (this.xPrecision || Math.max(this.wrapper.offsetWidth, this.handle.offsetWidth)); this.bounds.yStep = 1 / (this.yPrecision || Math.max(this.wrapper.offsetHeight, this.handle.offsetHeight)); }, addListeners: function() { this.wrapper.addEventListener( 'selectstart', this.selectstartHandler ); this.wrapper.addEventListener( 'mousedown', this.wrapperMousedownHandler ); this.handle.addEventListener( 'mousedown', this.mousedownHandler ); document.addEventListener( 'mouseup', this.mouseupHandler ); this.wrapper.addEventListener( 'touchstart', this.wrapperMousedownHandler ); this.handle.addEventListener( 'touchstart', this.mousedownHandler ); document.addEventListener( 'touchend', this.touchendHandler ); this.wrapper.addEventListener( 'mousemove', this.mousemoveHandler ); this.wrapper.addEventListener( 'click', this.clickHandler ); this.handle.addEventListener( 'contextmenu', this.contextmenuHandler ); this.wrapper.addEventListener( 'contextmenu', this.contextmenuHandler ); if (this.autoUpdateLayout) { window.addEventListener( 'resize', this.windowResizeHandler ); xAddEventLayoutChange( this.windowResizeHandler ); } // start animation frames if first slider is created if (DgdSlider.prototype.sliderList.length == 1) { this.startAnimation(); } }, removeListeners: function() { this.wrapper.removeEventListener( 'selectstart', this.selectstartHandler ); this.wrapper.removeEventListener( 'mousedown', this.wrapperMousedownHandler ); this.handle.removeEventListener( 'mousedown', this.mousedownHandler ); document.removeEventListener( 'mouseup', this.mouseupHandler ); this.wrapper.removeEventListener( 'touchstart', this.wrapperMousedownHandler ); this.handle.removeEventListener( 'touchstart', this.mousedownHandler ); document.removeEventListener( 'touchend', this.touchendHandler ); this.wrapper.removeEventListener( 'mousemove', this.mousemoveHandler ); this.wrapper.removeEventListener( 'click', this.clickHandler ); this.handle.removeEventListener( 'contextmenu', this.contextmenuHandler ); this.wrapper.removeEventListener( 'contextmenu', this.contextmenuHandler ); if (this.autoUpdateLayout) { window.removeEventListener( 'resize', this.windowResizeHandler ); xRemoveEventLayoutChange( this.windowResizeHandler ); } }, handleDownHandler: function(e) { this.activity = false; DgdCursor.refresh(e); this.preventDefaults(e, true); this.startDrag(); this.cancelEvent(e); }, wrapperDownHandler: function(e) { DgdCursor.refresh(e); this.preventDefaults(e, true); this.startTap(); }, documentUpHandler: function(e) { this.stopDrag(); this.stopTap(); //this.cancelEvent(e); }, documentResizeHandler: function() { if (!this.setup(true)) return; this.update(); }, enable: function() { this.disabled = false; xRemoveClass( this.handle, 'Disabled' ); }, disable: function() { this.disabled = true; xAddClass( this.handle, 'Disabled' ); }, readonly: function() { this.disabled = true; }, setStep: function(x, y, snap) { this.setValue( this.steps && x > 1 ? (x - 1) / (this.steps - 1) : 0, this.steps && y > 1 ? (y - 1) / (this.steps - 1) : 0, snap ); }, setValue: function(x, y, snap) { this.setTargetValue([x, y || 0]); if(snap) { this.groupCopy(this.value.current, this.value.target); this.update(); // wabis } }, startTap: function(target) { if(this.disabled) { return; } this.tapping = true; if(target === undefined) { target = [ DgdCursor.x - this.offset.wrapper[0] - (this.handle.offsetWidth / 2), DgdCursor.y - this.offset.wrapper[1] - (this.handle.offsetHeight / 2) ]; } this.setTargetOffset(target); }, stopTap: function() { if(this.disabled || !this.tapping) { return; } this.tapping = false; this.setTargetValue(this.value.current); this.result(); }, startDrag: function() { if(this.disabled) { return; } this.offset.mouse = [ DgdCursor.x - DgdPosition.get(this.handle)[0], DgdCursor.y - DgdPosition.get(this.handle)[1] ]; this.dragging = true; }, stopDrag: function() { if(this.disabled || !this.dragging) { return; } this.dragging = false; var target = this.groupClone(this.value.current); if(this.slide) { var ratioChange = this.change; target[0] += ratioChange[0] * 4; target[1] += ratioChange[1] * 4; } this.setTargetValue(target); this.result(); }, feedback: function() { var value = this.value.current; if(this.snap && this.steps > 1) { value = this.getClosestSteps(value); } if(!this.groupCompare(value, this.value.prev)) { if(xFunc(this.animationCallback)) { //console.log( 'DgdSlider.animationCallback' ); this.animationCallback(value[0], value[1]); } this.groupCopy(this.value.prev, value); } }, result: function() { if(xFunc(this.callback)) { //console.log( 'DgdSlider.callback' ); this.callback(this.value.target[0], this.value.target[1]); } }, startAnimation: function() { DgdSlider.prototype.animationFrameId = requestAnimationFrame( DgdSlider.prototype.onAnimationFrame ); this.animate(false, true); }, onAnimationFrame: function( time ) { if (DgdSlider.prototype.sliderList.length > 0) { DgdSlider.prototype.animationFrameId = requestAnimationFrame( DgdSlider.prototype.onAnimationFrame ); DgdSlider.prototype.animateAll(); } else { DgdSlider.prototype.animationFrameId = null; } }, animateAll: function() { var sl = DgdSlider.prototype.sliderList; var last = sl.length; for (var i = 0; i < last; i++) { if (sl[i].IsLife) sl[i].animate(); } }, animate: function(direct, first) { //console.log( 'DgdSlider.animate' ); if(direct && !this.dragging) { return; } if(this.dragging) { var prevTarget = this.groupClone(this.value.target); var offset = [ DgdCursor.x - this.offset.wrapper[0] - this.offset.mouse[0], DgdCursor.y - this.offset.wrapper[1] - this.offset.mouse[1] ]; this.setTargetOffset(offset, this.loose); this.change = [ this.value.target[0] - prevTarget[0], this.value.target[1] - prevTarget[1] ]; } if(this.dragging || first) { this.groupCopy(this.value.current, this.value.target); } if(this.dragging || this.glide() || first) { this.update(); this.feedback(); } }, glide: function() { var diff = [ this.value.target[0] - this.value.current[0], this.value.target[1] - this.value.current[1] ]; if(!diff[0] && !diff[1]) { return false; } if(Math.abs(diff[0]) > this.bounds.xStep || Math.abs(diff[1]) > this.bounds.yStep) { this.value.current[0] += diff[0] * this.speed; this.value.current[1] += diff[1] * this.speed; } else { this.groupCopy(this.value.current, this.value.target); } return true; }, update: function() { if(!this.snap) { this.offset.current = this.getOffsetsByRatios(this.value.current); } else { this.offset.current = this.getOffsetsByRatios( this.getClosestSteps(this.value.current) ); } this.show(); }, show: function() { //console.log( 'DgdSlider.show' ); if(!this.groupCompare(this.offset.current, this.offset.prev)) { if(this.horizontal) { this.handle.style.left = String(this.offset.current[0]) + 'px'; } if(this.vertical) { this.handle.style.top = String(this.offset.current[1]) + 'px'; } this.groupCopy(this.offset.prev, this.offset.current); } }, setTargetValue: function(value, loose) { var target = loose ? this.getLooseValue(value) : this.getProperValue(value); this.groupCopy(this.value.target, target); this.offset.target = this.getOffsetsByRatios(target); }, setTargetOffset: function(offset, loose) { var value = this.getRatiosByOffsets(offset); var target = loose ? this.getLooseValue(value) : this.getProperValue(value); this.groupCopy(this.value.target, target); this.offset.target = this.getOffsetsByRatios(target); }, getLooseValue: function(value) { var proper = this.getProperValue(value); return [ proper[0] + ((value[0] - proper[0]) / 4), proper[1] + ((value[1] - proper[1]) / 4) ]; }, getProperValue: function(value) { var proper = this.groupClone(value); proper[0] = Math.max(proper[0], 0); proper[1] = Math.max(proper[1], 0); proper[0] = Math.min(proper[0], 1); proper[1] = Math.min(proper[1], 1); if((!this.dragging && !this.tapping) || this.snap) { if(this.steps > 1) { proper = this.getClosestSteps(proper); } } return proper; }, getRatiosByOffsets: function(group) { return [ this.getRatioByOffset(group[0], this.bounds.xRange, this.bounds.x0), this.getRatioByOffset(group[1], this.bounds.yRange, this.bounds.y0) ]; }, getRatioByOffset: function(offset, range, padding) { return range ? (offset - padding) / range : 0; }, getOffsetsByRatios: function(group) { return [ this.getOffsetByRatio(group[0], this.bounds.xRange, this.bounds.x0), this.getOffsetByRatio(group[1], this.bounds.yRange, this.bounds.y0) ]; }, getOffsetByRatio: function(ratio, range, padding) { return Math.round(ratio * range) + padding; }, getClosestSteps: function(group) { return [ this.getClosestStep(group[0]), this.getClosestStep(group[1]) ]; }, getClosestStep: function(value) { if (this.steps <= 1) return value; var n = this.steps - 1; return Math.round( value * n ) / n; }, groupCompare: function(a, b) { return a[0] == b[0] && a[1] == b[1]; }, groupCopy: function(a, b) { a[0] = b[0]; a[1] = b[1]; }, groupClone: function(a) { return [a[0], a[1]]; }, preventDefaults: function(e, selection) { if(!e) { e = window.event; } if(e.preventDefault) { e.preventDefault(); } e.returnValue = false; if(selection && document.selection) { document.selection.empty(); } }, cancelEvent: function(e) { if(!e) { e = window.event; } if(e.stopPropagation) { e.stopPropagation(); } e.cancelBubble = true; } };