1 // Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs
3 // script.aculo.us is freely distributable under the terms of an MIT-style license.
4 // For details, see the script.aculo.us web site: http://script.aculo.us/
8 if (!Control
) var Control
= { };
11 // axis: 'vertical', or 'horizontal' (default)
16 Control
.Slider
= Class
.create({
17 initialize: function(handle
, track
, options
) {
20 if (Object
.isArray(handle
)) {
21 this.handles
= handle
.collect( function(e
) { return $(e
) });
23 this.handles
= [$(handle
)];
26 this.track
= $(track
);
27 this.options
= options
|| { };
29 this.axis
= this.options
.axis
|| 'horizontal';
30 this.increment
= this.options
.increment
|| 1;
31 this.step
= parseInt(this.options
.step
|| '1');
32 this.range
= this.options
.range
|| $R(0,1);
34 this.value
= 0; // assure backwards compat
35 this.values
= this.handles
.map( function() { return 0 });
36 this.spans
= this.options
.spans
? this.options
.spans
.map(function(s
){ return $(s
) }) : false;
37 this.options
.startSpan
= $(this.options
.startSpan
|| null);
38 this.options
.endSpan
= $(this.options
.endSpan
|| null);
40 this.restricted
= this.options
.restricted
|| false;
42 this.maximum
= this.options
.maximum
|| this.range
.end
;
43 this.minimum
= this.options
.minimum
|| this.range
.start
;
45 // Will be used to align the handle onto the track, if necessary
46 this.alignX
= parseInt(this.options
.alignX
|| '0');
47 this.alignY
= parseInt(this.options
.alignY
|| '0');
49 this.trackLength
= this.maximumOffset() - this.minimumOffset();
51 this.handleLength
= this.isVertical() ?
52 (this.handles
[0].offsetHeight
!= 0 ?
53 this.handles
[0].offsetHeight
: this.handles
[0].style
.height
.replace(/px$/,"")) :
54 (this.handles
[0].offsetWidth
!= 0 ? this.handles
[0].offsetWidth
:
55 this.handles
[0].style
.width
.replace(/px$/,""));
58 this.dragging
= false;
59 this.disabled
= false;
61 if (this.options
.disabled
) this.setDisabled();
63 // Allowed values array
64 this.allowedValues
= this.options
.values
? this.options
.values
.sortBy(Prototype
.K
) : false;
65 if (this.allowedValues
) {
66 this.minimum
= this.allowedValues
.min();
67 this.maximum
= this.allowedValues
.max();
70 this.eventMouseDown
= this.startDrag
.bindAsEventListener(this);
71 this.eventMouseUp
= this.endDrag
.bindAsEventListener(this);
72 this.eventMouseMove
= this.update
.bindAsEventListener(this);
74 // Initialize handles in reverse (make sure first handle is active)
75 this.handles
.each( function(h
,i
) {
76 i
= slider
.handles
.length
-1-i
;
77 slider
.setValue(parseFloat(
78 (Object
.isArray(slider
.options
.sliderValue
) ?
79 slider
.options
.sliderValue
[i
] : slider
.options
.sliderValue
) ||
80 slider
.range
.start
), i
);
81 h
.makePositioned().observe("mousedown", slider
.eventMouseDown
);
84 this.track
.observe("mousedown", this.eventMouseDown
);
85 document
.observe("mouseup", this.eventMouseUp
);
86 document
.observe("mousemove", this.eventMouseMove
);
88 this.initialized
= true;
92 Event
.stopObserving(this.track
, "mousedown", this.eventMouseDown
);
93 Event
.stopObserving(document
, "mouseup", this.eventMouseUp
);
94 Event
.stopObserving(document
, "mousemove", this.eventMouseMove
);
95 this.handles
.each( function(h
) {
96 Event
.stopObserving(h
, "mousedown", slider
.eventMouseDown
);
99 setDisabled: function(){
100 this.disabled
= true;
102 setEnabled: function(){
103 this.disabled
= false;
105 getNearestValue: function(value
){
106 if (this.allowedValues
){
107 if (value
>= this.allowedValues
.max()) return(this.allowedValues
.max());
108 if (value
<= this.allowedValues
.min()) return(this.allowedValues
.min());
110 var offset
= Math
.abs(this.allowedValues
[0] - value
);
111 var newValue
= this.allowedValues
[0];
112 this.allowedValues
.each( function(v
) {
113 var currentOffset
= Math
.abs(v
- value
);
114 if (currentOffset
<= offset
){
116 offset
= currentOffset
;
121 if (value
> this.range
.end
) return this.range
.end
;
122 if (value
< this.range
.start
) return this.range
.start
;
125 setValue: function(sliderValue
, handleIdx
){
127 this.activeHandleIdx
= handleIdx
|| 0;
128 this.activeHandle
= this.handles
[this.activeHandleIdx
];
131 handleIdx
= handleIdx
|| this.activeHandleIdx
|| 0;
132 if (this.initialized
&& this.restricted
) {
133 if ((handleIdx
>0) && (sliderValue
<this.values
[handleIdx
-1]))
134 sliderValue
= this.values
[handleIdx
-1];
135 if ((handleIdx
< (this.handles
.length
-1)) && (sliderValue
>this.values
[handleIdx
+1]))
136 sliderValue
= this.values
[handleIdx
+1];
138 sliderValue
= this.getNearestValue(sliderValue
);
139 this.values
[handleIdx
] = sliderValue
;
140 this.value
= this.values
[0]; // assure backwards compat
142 this.handles
[handleIdx
].style
[this.isVertical() ? 'top' : 'left'] =
143 this.translateToPx(sliderValue
);
146 if (!this.dragging
|| !this.event
) this.updateFinished();
148 setValueBy: function(delta
, handleIdx
) {
149 this.setValue(this.values
[handleIdx
|| this.activeHandleIdx
|| 0] + delta
,
150 handleIdx
|| this.activeHandleIdx
|| 0);
152 translateToPx: function(value
) {
154 ((this.trackLength
-this.handleLength
)/(this.range
.end
-this.range
.start
)) *
155 (value
- this.range
.start
)) + "px";
157 translateToValue: function(offset
) {
158 return ((offset
/(this.trackLength
-this.handleLength
) *
159 (this.range
.end
-this.range
.start
)) + this.range
.start
);
161 getRange: function(range
) {
162 var v
= this.values
.sortBy(Prototype
.K
);
164 return $R(v
[range
],v
[range
+1]);
166 minimumOffset: function(){
167 return(this.isVertical() ? this.alignY
: this.alignX
);
169 maximumOffset: function(){
170 return(this.isVertical() ?
171 (this.track
.offsetHeight
!= 0 ? this.track
.offsetHeight
:
172 this.track
.style
.height
.replace(/px$/,"")) - this.alignY
:
173 (this.track
.offsetWidth
!= 0 ? this.track
.offsetWidth
:
174 this.track
.style
.width
.replace(/px$/,"")) - this.alignX
);
176 isVertical: function(){
177 return (this.axis
== 'vertical');
179 drawSpans: function() {
182 $R(0, this.spans
.length
-1).each(function(r
) { slider
.setSpan(slider
.spans
[r
], slider
.getRange(r
)) });
183 if (this.options
.startSpan
)
184 this.setSpan(this.options
.startSpan
,
185 $R(0, this.values
.length
>1 ? this.getRange(0).min() : this.value
));
186 if (this.options
.endSpan
)
187 this.setSpan(this.options
.endSpan
,
188 $R(this.values
.length
>1 ? this.getRange(this.spans
.length
-1).max() : this.value
, this.maximum
));
190 setSpan: function(span
, range
) {
191 if (this.isVertical()) {
192 span
.style
.top
= this.translateToPx(range
.start
);
193 span
.style
.height
= this.translateToPx(range
.end
- range
.start
+ this.range
.start
);
195 span
.style
.left
= this.translateToPx(range
.start
);
196 span
.style
.width
= this.translateToPx(range
.end
- range
.start
+ this.range
.start
);
199 updateStyles: function() {
200 this.handles
.each( function(h
){ Element
.removeClassName(h
, 'selected') });
201 Element
.addClassName(this.activeHandle
, 'selected');
203 startDrag: function(event
) {
204 if (Event
.isLeftClick(event
)) {
208 var handle
= Event
.element(event
);
209 var pointer
= [Event
.pointerX(event
), Event
.pointerY(event
)];
211 if (track
==this.track
) {
212 var offsets
= Position
.cumulativeOffset(this.track
);
214 this.setValue(this.translateToValue(
215 (this.isVertical() ? pointer
[1]-offsets
[1] : pointer
[0]-offsets
[0])-(this.handleLength
/2)
217 var offsets
= Position
.cumulativeOffset(this.activeHandle
);
218 this.offsetX
= (pointer
[0] - offsets
[0]);
219 this.offsetY
= (pointer
[1] - offsets
[1]);
221 // find the handle (prevents issues with Safari)
222 while((this.handles
.indexOf(handle
) == -1) && handle
.parentNode
)
223 handle
= handle
.parentNode
;
225 if (this.handles
.indexOf(handle
)!=-1) {
226 this.activeHandle
= handle
;
227 this.activeHandleIdx
= this.handles
.indexOf(this.activeHandle
);
230 var offsets
= Position
.cumulativeOffset(this.activeHandle
);
231 this.offsetX
= (pointer
[0] - offsets
[0]);
232 this.offsetY
= (pointer
[1] - offsets
[1]);
239 update: function(event
) {
241 if (!this.dragging
) this.dragging
= true;
243 if (Prototype
.Browser
.WebKit
) window
.scrollBy(0,0);
247 draw: function(event
) {
248 var pointer
= [Event
.pointerX(event
), Event
.pointerY(event
)];
249 var offsets
= Position
.cumulativeOffset(this.track
);
250 pointer
[0] -= this.offsetX
+ offsets
[0];
251 pointer
[1] -= this.offsetY
+ offsets
[1];
253 this.setValue(this.translateToValue( this.isVertical() ? pointer
[1] : pointer
[0] ));
254 if (this.initialized
&& this.options
.onSlide
)
255 this.options
.onSlide(this.values
.length
>1 ? this.values
: this.value
, this);
257 endDrag: function(event
) {
258 if (this.active
&& this.dragging
) {
259 this.finishDrag(event
, true);
263 this.dragging
= false;
265 finishDrag: function(event
, success
) {
267 this.dragging
= false;
268 this.updateFinished();
270 updateFinished: function() {
271 if (this.initialized
&& this.options
.onChange
)
272 this.options
.onChange(this.values
.length
>1 ? this.values
: this.value
, this);