1// script.aculo.us slider.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 2 3// Copyright (c) 2005, 2006 Marty Haught, Thomas Fuchs 4// 5// script.aculo.us is freely distributable under the terms of an MIT-style license. 6// For details, see the script.aculo.us web site: http://script.aculo.us/ 7 8if(!Control) var Control = {}; 9Control.Slider = Class.create(); 10 11// options: 12// axis: 'vertical', or 'horizontal' (default) 13// 14// callbacks: 15// onChange(value) 16// onSlide(value) 17Control.Slider.prototype = { 18 initialize: function(handle, track, options) { 19 var slider = this; 20 21 if(handle instanceof Array) { 22 this.handles = handle.collect( function(e) { return $(e) }); 23 } else { 24 this.handles = [$(handle)]; 25 } 26 27 this.track = $(track); 28 this.options = options || {}; 29 30 this.axis = this.options.axis || 'horizontal'; 31 this.increment = this.options.increment || 1; 32 this.step = parseInt(this.options.step || '1'); 33 this.range = this.options.range || $R(0,1); 34 35 this.value = 0; // assure backwards compat 36 this.values = this.handles.map( function() { return 0 }); 37 this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false; 38 this.options.startSpan = $(this.options.startSpan || null); 39 this.options.endSpan = $(this.options.endSpan || null); 40 41 this.restricted = this.options.restricted || false; 42 43 this.maximum = this.options.maximum || this.range.end; 44 this.minimum = this.options.minimum || this.range.start; 45 46 // Will be used to align the handle onto the track, if necessary 47 this.alignX = parseInt(this.options.alignX || '0'); 48 this.alignY = parseInt(this.options.alignY || '0'); 49 50 this.trackLength = this.maximumOffset() - this.minimumOffset(); 51 52 this.handleLength = this.isVertical() ? 53 (this.handles[0].offsetHeight != 0 ? 54 this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 55 (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 56 this.handles[0].style.width.replace(/px$/,"")); 57 58 this.active = false; 59 this.dragging = false; 60 this.disabled = false; 61 62 if(this.options.disabled) this.setDisabled(); 63 64 // Allowed values array 65 this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; 66 if(this.allowedValues) { 67 this.minimum = this.allowedValues.min(); 68 this.maximum = this.allowedValues.max(); 69 } 70 71 this.eventMouseDown = this.startDrag.bindAsEventListener(this); 72 this.eventMouseUp = this.endDrag.bindAsEventListener(this); 73 this.eventMouseMove = this.update.bindAsEventListener(this); 74 75 // Initialize handles in reverse (make sure first handle is active) 76 this.handles.each( function(h,i) { 77 i = slider.handles.length-1-i; 78 slider.setValue(parseFloat( 79 (slider.options.sliderValue instanceof Array ? 80 slider.options.sliderValue[i] : slider.options.sliderValue) || 81 slider.range.start), i); 82 Element.makePositioned(h); // fix IE 83 Event.observe(h, "mousedown", slider.eventMouseDown); 84 }); 85 86 Event.observe(this.track, "mousedown", this.eventMouseDown); 87 Event.observe(document, "mouseup", this.eventMouseUp); 88 Event.observe(document, "mousemove", this.eventMouseMove); 89 90 this.initialized = true; 91 }, 92 dispose: function() { 93 var slider = this; 94 Event.stopObserving(this.track, "mousedown", this.eventMouseDown); 95 Event.stopObserving(document, "mouseup", this.eventMouseUp); 96 Event.stopObserving(document, "mousemove", this.eventMouseMove); 97 this.handles.each( function(h) { 98 Event.stopObserving(h, "mousedown", slider.eventMouseDown); 99 }); 100 }, 101 setDisabled: function(){ 102 this.disabled = true; 103 }, 104 setEnabled: function(){ 105 this.disabled = false; 106 }, 107 getNearestValue: function(value){ 108 if(this.allowedValues){ 109 if(value >= this.allowedValues.max()) return(this.allowedValues.max()); 110 if(value <= this.allowedValues.min()) return(this.allowedValues.min()); 111 112 var offset = Math.abs(this.allowedValues[0] - value); 113 var newValue = this.allowedValues[0]; 114 this.allowedValues.each( function(v) { 115 var currentOffset = Math.abs(v - value); 116 if(currentOffset <= offset){ 117 newValue = v; 118 offset = currentOffset; 119 } 120 }); 121 return newValue; 122 } 123 if(value > this.range.end) return this.range.end; 124 if(value < this.range.start) return this.range.start; 125 return value; 126 }, 127 setValue: function(sliderValue, handleIdx){ 128 if(!this.active) { 129 this.activeHandleIdx = handleIdx || 0; 130 this.activeHandle = this.handles[this.activeHandleIdx]; 131 this.updateStyles(); 132 } 133 handleIdx = handleIdx || this.activeHandleIdx || 0; 134 if(this.initialized && this.restricted) { 135 if((handleIdx>0) && (sliderValue<this.values[handleIdx-1])) 136 sliderValue = this.values[handleIdx-1]; 137 if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1])) 138 sliderValue = this.values[handleIdx+1]; 139 } 140 sliderValue = this.getNearestValue(sliderValue); 141 this.values[handleIdx] = sliderValue; 142 this.value = this.values[0]; // assure backwards compat 143 144 this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 145 this.translateToPx(sliderValue); 146 147 this.drawSpans(); 148 if(!this.dragging || !this.event) this.updateFinished(); 149 }, 150 setValueBy: function(delta, handleIdx) { 151 this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 152 handleIdx || this.activeHandleIdx || 0); 153 }, 154 translateToPx: function(value) { 155 return Math.round( 156 ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 157 (value - this.range.start)) + "px"; 158 }, 159 translateToValue: function(offset) { 160 return ((offset/(this.trackLength-this.handleLength) * 161 (this.range.end-this.range.start)) + this.range.start); 162 }, 163 getRange: function(range) { 164 var v = this.values.sortBy(Prototype.K); 165 range = range || 0; 166 return $R(v[range],v[range+1]); 167 }, 168 minimumOffset: function(){ 169 return(this.isVertical() ? this.alignY : this.alignX); 170 }, 171 maximumOffset: function(){ 172 return(this.isVertical() ? 173 (this.track.offsetHeight != 0 ? this.track.offsetHeight : 174 this.track.style.height.replace(/px$/,"")) - this.alignY : 175 (this.track.offsetWidth != 0 ? this.track.offsetWidth : 176 this.track.style.width.replace(/px$/,"")) - this.alignY); 177 }, 178 isVertical: function(){ 179 return (this.axis == 'vertical'); 180 }, 181 drawSpans: function() { 182 var slider = this; 183 if(this.spans) 184 $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) }); 185 if(this.options.startSpan) 186 this.setSpan(this.options.startSpan, 187 $R(0, this.values.length>1 ? this.getRange(0).min() : this.value )); 188 if(this.options.endSpan) 189 this.setSpan(this.options.endSpan, 190 $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum)); 191 }, 192 setSpan: function(span, range) { 193 if(this.isVertical()) { 194 span.style.top = this.translateToPx(range.start); 195 span.style.height = this.translateToPx(range.end - range.start + this.range.start); 196 } else { 197 span.style.left = this.translateToPx(range.start); 198 span.style.width = this.translateToPx(range.end - range.start + this.range.start); 199 } 200 }, 201 updateStyles: function() { 202 this.handles.each( function(h){ Element.removeClassName(h, 'selected') }); 203 Element.addClassName(this.activeHandle, 'selected'); 204 }, 205 startDrag: function(event) { 206 if(Event.isLeftClick(event)) { 207 if(!this.disabled){ 208 this.active = true; 209 210 var handle = Event.element(event); 211 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 212 var track = handle; 213 if(track==this.track) { 214 var offsets = Position.cumulativeOffset(this.track); 215 this.event = event; 216 this.setValue(this.translateToValue( 217 (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2) 218 )); 219 var offsets = Position.cumulativeOffset(this.activeHandle); 220 this.offsetX = (pointer[0] - offsets[0]); 221 this.offsetY = (pointer[1] - offsets[1]); 222 } else { 223 // find the handle (prevents issues with Safari) 224 while((this.handles.indexOf(handle) == -1) && handle.parentNode) 225 handle = handle.parentNode; 226 227 if(this.handles.indexOf(handle)!=-1) { 228 this.activeHandle = handle; 229 this.activeHandleIdx = this.handles.indexOf(this.activeHandle); 230 this.updateStyles(); 231 232 var offsets = Position.cumulativeOffset(this.activeHandle); 233 this.offsetX = (pointer[0] - offsets[0]); 234 this.offsetY = (pointer[1] - offsets[1]); 235 } 236 } 237 } 238 Event.stop(event); 239 } 240 }, 241 update: function(event) { 242 if(this.active) { 243 if(!this.dragging) this.dragging = true; 244 this.draw(event); 245 // fix AppleWebKit rendering 246 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 247 Event.stop(event); 248 } 249 }, 250 draw: function(event) { 251 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 252 var offsets = Position.cumulativeOffset(this.track); 253 pointer[0] -= this.offsetX + offsets[0]; 254 pointer[1] -= this.offsetY + offsets[1]; 255 this.event = event; 256 this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); 257 if(this.initialized && this.options.onSlide) 258 this.options.onSlide(this.values.length>1 ? this.values : this.value, this); 259 }, 260 endDrag: function(event) { 261 if(this.active && this.dragging) { 262 this.finishDrag(event, true); 263 Event.stop(event); 264 } 265 this.active = false; 266 this.dragging = false; 267 }, 268 finishDrag: function(event, success) { 269 this.active = false; 270 this.dragging = false; 271 this.updateFinished(); 272 }, 273 updateFinished: function() { 274 if(this.initialized && this.options.onChange) 275 this.options.onChange(this.values.length>1 ? this.values : this.value, this); 276 this.event = null; 277 } 278}