/** * Class Ext.ux.SlideZone * @author Jared Gisin * @version $Id$ * @license http://www.opensource.org/licenses/mit-license.php * * TODO: make sure slider limits work when snap = 1 or snap < slider width/height * TODO: API to dynamically modify the snap * TODO: zone with slider zone responds to click and keypress * TODO: API to programatically set slider value with animation * */ Ext.ux.SlideZone = function(id, config) { if(id) { Ext.apply(this, config); this.init(id, config || {}); return this; } else { return null; } }; Ext.extend(Ext.ux.SlideZone, Ext.BoxComponent, { size: 100, sliderSnap: [0], type: 'horizontal', sliderWidth: 20, sliderHeight: 20, cls: null, minValue: 0, maxValue: 100, allowSliderCrossing: true, value: null, init: function(id, config) { this.size = this.size instanceof Array ? this.size : [this.size]; this.minValue = this.minValue instanceof Array ? this.minValue : [this.minValue]; this.maxValue = this.maxValue instanceof Array ? this.maxValue : [this.maxValue]; this.sliderSnap = this.sliderSnap instanceof Array ? this.sliderSnap : [this.sliderSnap]; this.el = Ext.DomHelper.append(Ext.get(id), { tag: 'div', id: Ext.id(), style: 'position: relative; ' }, true); this.el.addClass('x-slide-zone-' + this.type); this.el.addClass(this.cls); switch(this.type) { case 'horizontal': this.sliderSize = [this.sliderWidth]; this.el.applyStyles({width:this.size[0] + 'px'}); this.el.lowLimit = [this.el.getX()]; this.el.highLimit = [this.el.getRight()]; break; case 'vertical': this.sliderSize = [this.sliderHeight]; this.el.applyStyles({height:this.size[0] + 'px'}); this.el.lowLimit = [this.el.getY()]; this.el.highLimit = [this.el.getBottom()]; break; case 'area': this.sliderSize = [this.sliderWidth, this.sliderHeight]; this.el.applyStyles({width:this.size[0] + 'px'}); this.el.applyStyles({height:this.size[1] + 'px'}); this.el.lowLimit = this.el.getXY(); this.el.highLimit = [this.el.getRight(), this.el.getBottom()]; break; } //normalize width/height for slider centering calculations var l = this.sliderSize.length; for(var i = 0; i < l; i++) { this.sliderSize[i] = this.sliderSize[i] % 2 ? this.sliderSize[i] + 1: this.sliderSize[i]; } var s = this.sliders; this.sliders = new Ext.util.MixedCollection(); if(s){ this.add.apply(this, s); } }, add: function(){ var a = arguments, l = a.length; for(var i = 0; i < l; i++){ var el = a[i]; if (el instanceof Ext.ux.Slider) { this.initSlider(el); this.sliders.add(el); } else if (typeof el == 'object') { //initalize new slider and add to the items list var s = new Ext.ux.ThumbSlider(el); this.initSlider(s); this.sliders.add(s); } } this.updateValues(); this.updateConstraints(); }, enableCrossing: function() { this.allowSliderCrossing = true; this.updateConstraints(); }, disableCrossing: function() { this.allowSliderCrossing = false; this.updateConstraints(); }, getSlider: function(selector) { /* Given a slider index or name, returns a slider object * @selector slider index|name */ switch (typeof selector) { case 'number': return this.sliders.items[selector]; case 'string': var l = this.sliders.length; for(var i = 0; i < l; i++){ if(this.sliders.items[i].name == selector) return this.sliders.items[i]; } break; } return null; }, updateConstraints: function() { if(!this.allowSliderCrossing && this.sliders.length > 1 && this.type != 'area') { // multiple sliders exists and sliders can't cross, // so we have to take into account the // positions of all sliders sortFN = function(a,b){ var v1 = a.value instanceof Array ? a.value[0] : a.value; var v2 = b.value instanceof Array ? b.value[0] : b.value; return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0); }; this.sliders.sort('asc',sortFN);//put in order by their value var l = this.sliders.length; for (var i=0; i< l; i++) { //forloop required, as we have to get -1 and +1 slider positions var leftTravel = 0, rightTravel = 0; var sliderL = this.sliders.get(i-1); var slider = this.sliders.get(i); var sliderR = this.sliders.get(i+1); if (sliderL && sliderR) { if (sliderL instanceof Ext.ux.ThumbSlider) { var leftTravel = slider.getTL()[0] - sliderL.getTL()[0] - this.sliderSnap[0]; } if (sliderL instanceof Ext.ux.RangeSlider) { var leftTravel = slider.getTL()[0] - sliderL.getBR()[0] - this.sliderSnap[0]; } if (sliderR instanceof Ext.ux.ThumbSlider) { var rightTravel = sliderR.getTL()[0] - slider.getTL()[0] - this.sliderSnap[0]; } if (sliderR instanceof Ext.ux.RangeSlider) { var rightTravel = sliderR.getTL()[0] - slider.getBR()[0] - this.sliderSnap[0] ; } } else if(sliderL) { if (sliderL instanceof Ext.ux.ThumbSlider) { var rightTravel = this.el.highLimit[0] - slider.getTL()[0] - ( 0.5 * this.sliderSize[0]) ; var leftTravel = slider.getTL()[0] - sliderL.getTL()[0] - this.sliderSnap[0]; } if (sliderL instanceof Ext.ux.RangeSlider) { var rightTravel = this.el.highLimit[0] - slider.getBR()[0]; var leftTravel = slider.getTL()[0] - sliderL.getBR()[0] - this.sliderSnap[0]; } } else if(sliderR) { if (sliderR instanceof Ext.ux.ThumbSlider) { var leftTravel = slider.getTL()[0] - this.el.lowLimit[0] + ( 0.5 * this.sliderSize[0]); var rightTravel = sliderR.getTL()[0] - slider.getTL()[0] - this.sliderSnap[0] ; } if (sliderR instanceof Ext.ux.RangeSlider) { var leftTravel = slider.getTL()[0] - this.el.lowLimit[0]; var rightTravel = sliderR.getTL()[0] - slider.getBR()[0] - this.sliderSnap[0] ; } } if(slider instanceof Ext.ux.RangeSlider) { slider.resizable.leftTravel = [leftTravel]; slider.resizable.rightTravel = [rightTravel]; } slider.setConstraint([leftTravel], [rightTravel], this.sliderSnap); } } else { var l = this.sliders.length; for (var i=0; i< l; i++) { var slider = this.sliders.get(i); if (slider instanceof Ext.ux.ThumbSlider ) { slider.setConstraint([slider.getTL()[0]-this.el.lowLimit[0] + ( 1/2 * this.sliderSize[0]), slider.getTL()[1]-this.el.lowLimit[1] + ( 1/2 * this.sliderSize[1])], [this.el.highLimit[0]- (slider.getTL()[0] + this.sliderSize[0]) + ( 1/2 * this.sliderSize[0]), this.el.highLimit[1]- (slider.getTL()[1] + this.sliderSize[1]) + ( 1/2 * this.sliderSize[1]) ], this.sliderSnap); } if (slider instanceof Ext.ux.RangeSlider) { slider.setConstraint([slider.getTL()[0] - this.el.lowLimit[0], slider.getTL()[1] - this.el.lowLimit[1]], [this.el.highLimit[0] - slider.getBR()[0], this.el.highLimit[1] - slider.getBR()[1] ], this.sliderSnap); } } } }, updateValues: function() { var that = this; var vals = {}; this.sliders.each(function(i) { if(this.type == 'area') { if (i instanceof Ext.ux.ThumbSlider) { i.value = [(i.getTL()[0] + 1/2 * that.sliderSize[0] - that.el.lowLimit[0]) / (that.size[0]) * (that.maxValue[0] - that.minValue[0]) + that.minValue[0], (i.getTL()[1] + 1/2 * that.sliderSize[1] - that.el.lowLimit[1]) / (that.size[1]) * (that.maxValue[1] - that.minValue[1]) + that.minValue[1]]; i.percent = [parseFloat((i.getTL()[0] + 1/2 * that.sliderSize[0] - that.el.lowLimit[0]) / (that.size[0]) * 100), parseFloat((i.getTL()[1] + 1/2 * that.sliderSize[1] - that.el.lowLimit[1]) / (that.size[1]) * 100)]; } if (i instanceof Ext.ux.RangeSlider) { i.value = [[ (i.getTL()[0] - that.el.lowLimit[0]) / (that.size[0]) * (that.maxValue[0] - that.minValue[0]) + that.minValue[0], (i.getBR()[0] - that.el.lowLimit[0]) / (that.size[0]) * (that.maxValue[0] - that.minValue[0]) + that.minValue[0] ],[ (i.getTL()[1] - that.el.lowLimit[1]) / (that.size[1]) * (that.maxValue[1] - that.minValue[1]) + that.minValue[1], (i.getBR()[1] - that.el.lowLimit[1]) / (that.size[1]) * (that.maxValue[1] - that.minValue[1]) + that.minValue[1] ]]; i.percent = [[ parseFloat((i.getTL()[0] - that.el.lowLimit[0]) / (that.size[0]) * 100).toFixed(2), parseFloat((i.getBR()[0] - that.el.lowLimit[0]) / (that.size[0]) * 100).toFixed(2) ],[ parseFloat((i.getTL()[1] - that.el.lowLimit[1]) / (that.size[1]) * 100).toFixed(2), parseFloat((i.getBR()[1] - that.el.lowLimit[1]) / (that.size[1]) * 100).toFixed(2) ]]; } } else { if (i instanceof Ext.ux.ThumbSlider) { i.value = (i.getTL()[0] + 1/2 * that.sliderSize[0] - that.el.lowLimit[0]) / (that.size[0]) * (that.maxValue[0] - that.minValue[0]) + that.minValue[0]; i.percent = parseFloat((i.getTL()[0] + 1/2 * that.sliderSize[0] - that.el.lowLimit[0]) / (that.size[0]) * 100); } if (i instanceof Ext.ux.RangeSlider) { i.value = [(i.getTL()[0] - that.el.lowLimit[0]) / (that.size[0]) * (that.maxValue[0] - that.minValue[0]) + that.minValue[0], (i.getBR()[0] - that.el.lowLimit[0]) / (that.size[0]) * (that.maxValue[0] - that.minValue[0]) + that.minValue[0]]; i.percent = [ parseFloat((i.getTL()[0] - that.el.lowLimit[0]) / (that.size[0]) * 100), parseFloat((i.getBR()[0] - that.el.lowLimit[0]) / (that.size[0]) * 100)]; } } vals[this.name] = this.value; }); this.value = vals; }, initSliderPosition: function(slider) { /* Sets a sliders's initial position within the slide zone. * If slideZone has a sliderSnap set, the slider * is positioned at a multiple of the sliderSnap value that is * closest where the slider's value would otherwise position it. * After positioning, the slider's actual value will be modified * in Ext.ux.SlideZone.updateValues() * to jibe with the actual position in the slide zone given the * sliderSnap constraint. */ var initialPosition = [0,0]; slider.repositionDelta = [0,0]; var l = this.type == 'area' ? 2 : 1; for(var i=0; i < l; i++ ) { /* Make sure initial value is in slider bar range, if it's outside, set to closest limit. The slider value is modified if it's out of range. */ if(slider.value instanceof Array) { if (slider instanceof Ext.ux.ThumbSlider) { slider.value[i] = Number(slider.value[i]).constrain(this.minValue[i], this.maxValue[i]); initialPosition[i] = parseInt((slider.value[i] - this.minValue[i]) / (this.maxValue[i] - this.minValue[i]) * this.size[i] + this.el.lowLimit[i],10); } if (slider instanceof Ext.ux.RangeSlider) { switch (this.type) { case 'horizontal': case 'vertical': slider.value[i] = Number(slider.value[i]).constrain(this.minValue[i], this.maxValue[i]); initialPosition[i] = parseInt((slider.value[i] - this.minValue[i]) / (this.maxValue[i] - this.minValue[i]) * this.size[i] + this.el.lowLimit[i],10); break; case 'area': slider.value[i][0] = Number(slider.value[i][0]).constrain(this.minValue[i], this.maxValue[i]); slider.value[i][1] = Number(slider.value[i][1]).constrain(this.minValue[i], this.maxValue[i]); initialPosition[i] = parseInt((slider.value[i][0] - this.minValue[i]) / (this.maxValue[i] - this.minValue[i]) * this.size[i] + this.el.lowLimit[i],10); break; } } } else { slider.value = Number(slider.value).constrain(this.minValue[i], this.maxValue[i]); initialPosition[i] = parseInt((slider.value - this.minValue[i]) / (this.maxValue[i] - this.minValue[i]) * this.size[i] + this.el.lowLimit[i],10); } /* Calculate slider value versus closest snap-to location, and calculate an offset value to apply when positioning it. */ if (this.sliderSnap[i] > 1) { var positionToSnapDiff = [0,0]; positionToSnapDiff[i] = (initialPosition[i] - this.el.lowLimit[i]) % this.sliderSnap[i]; if (positionToSnapDiff[i]) { if(positionToSnapDiff[i] < 1/2 * this.sliderSnap[i]) { slider.repositionDelta[i] = -positionToSnapDiff[i]; } else { slider.repositionDelta[i] = this.sliderSnap[i] - positionToSnapDiff[i]; } } } } if (slider instanceof Ext.ux.ThumbSlider) { slider.setPosition([initialPosition[0] + slider.repositionDelta[0] - (0.5 * this.sliderSize[0]), initialPosition[1] + slider.repositionDelta[1] - (0.5 * this.sliderSize[1])]); } if (slider instanceof Ext.ux.RangeSlider) { slider.setPosition([initialPosition[0] + slider.repositionDelta[0], initialPosition[1] + slider.repositionDelta[1]]); } }, initSlider: function(slider) { slider.init(this); this.initSliderPosition(slider); } }); Ext.ux.Slider = function(config) { Ext.apply(this, config); this.addEvents( { "dragstart" : true, "dragend" : true, "drag" : true, "mouseover":true, "mouseout":true }); }; Ext.extend(Ext.ux.Slider, Ext.BoxComponent, { value: 0, ddEl: null, name: null, cls: null, allowMove:true, init: function() { //Do nothing }, getTL: function() { switch(this.type) { case 'horizontal': return [this.el.getX()]; case 'vertical': return [this.el.getY()]; case 'area': return this.el.getXY(); } return null; }, getBR: function() { switch(this.type) { case 'horizontal': return [this.el.getRight()]; case 'vertical': return [this.el.getBottom()]; case 'area': return [this.el.getRight(), this.el.getBottom()]; } return null; }, setPosition: function(position) { var i,l = position.length; for (i=0; i 0 ? box.right - this.startBox.right + this.startPoint[0] : tgt[0], tgt[1] - box.top < 0 ? box.top - this.startBox.y + this.startPoint[1] : tgt[1] - box.bottom > 0 ? box.bottom - this.startBox.bottom + this.startPoint[1] : tgt[1] ]; Ext.Resizable.prototype.onMouseMove.call(this, e); slideZoneInstance.updateValues(); sliderInstance.fireEvent('drag', sliderInstance); }; this.resizable.on('resize', function(width, height) { slideZoneInstance.updateConstraints(); slideZoneInstance.updateValues(); sliderInstance.fireEvent('dragend', sliderInstance); }); this.el.on('mouseover', function() { sliderInstance.fireEvent('mouseover', sliderInstance); sliderInstance.el.setStyle('cursor', sliderInstance.pointer); }); this.el.on('mouseout', function() { sliderInstance.fireEvent('mouseout', sliderInstance); sliderInstance.el.setStyle('cursor', 'default'); }); this.ddEl.onMouseDown = function(x, y) { sliderInstance.fireEvent('dragstart', sliderInstance); }; this.ddEl.onMouseUp = function(x, y) { slideZoneInstance.updateConstraints(); slideZoneInstance.updateValues(); sliderInstance.fireEvent('dragend', sliderInstance); }; this.ddEl.onDrag = function(e) { slideZoneInstance.updateValues(); sliderInstance.fireEvent('drag', sliderInstance); }; } Ext.ux.ThumbSlider.superclass.init.call(parentSlideZone); } });