'use strict'; /*@ Ox.Range Range Object ([options[, self]]) -> Range Object options Options object arrows if true, show arrows arrowStep step when clicking arrows arrowSymbols <[s]> arrow symbols, like ['minus', 'plus'] max maximum value min minimum value orientation 'horizontal' or 'vertical' step step between values size width or height, in px thumbSize minimum width or height of thumb, in px thumbStyle Thumb style ('opaque' or 'transparent') thumbValue if true, display value on thumb trackColors <[s]> CSS colors trackGradient if true, display track colors as gradient trackImages one or multiple track background image URLs trackStep 0 (scroll here) or step when clicking track value initial value values <[s]> values to display on thumb self shared private variable change triggered on change of the range @*/ Ox.Range = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ arrows: false, arrowStep: 1, arrowSymbols: ['left', 'right'], changeOnDrag: false, max: 100, min: 0, orientation: 'horizontal', step: 1, size: 128, // fixme: shouldn't this be width? thumbSize: 16, thumbStyle: 'default', thumbValue: false, trackColors: [], trackGradient: false, trackImages: [], trackStep: 0, value: 0, values: [] }) .options(options || {}) .addClass('OxRange') .css({ width: self.options.size + 'px' }); self.hasValues = !Ox.isEmpty(self.options.values); if (self.hasValues) { self.options.max = self.options.values.length - 1; self.options.min = 0; self.options.step = 1; self.options.thumbValue = true; self.options.value = Ox.isNumber(self.options.value) ? self.options.values[self.options.value] : self.options.value; } self.options.arrowStep = options.arrowStep || self.options.step; self.options.trackImages = Ox.makeArray(self.options.trackImages); self.trackColors = self.options.trackColors.length; self.trackImages = self.options.trackImages.length; self.values = ( self.options.max - self.options.min + self.options.step ) / self.options.step; setSizes(); if (self.options.arrows) { self.$arrows = []; Ox.range(0, 2).forEach(function(i) { self.$arrows[i] = Ox.Button({ overlap: i == 0 ? 'right' : 'left', title: self.options.arrowSymbols[i], type: 'image' }) .addClass('OxArrow') .bindEvent({ mousedown: function(data) { clickArrow(data, i, true); }, mouserepeat: function(data) { clickArrow(data, i, false); } }) .appendTo(that.$element); }); } self.$track = Ox.Element() .addClass('OxTrack') .css(Ox.extend({ width: (self.trackSize - 2) + 'px' }, self.trackImages == 1 ? { background: 'rgb(0, 0, 0)' } : {})) .bindEvent(Ox.extend({ mousedown: clickTrack, drag: dragTrack }, self.options.changeOnDrag ? {} : { dragend: dragendTrack })) .appendTo(that.$element); self.trackColors && setTrackColors(); if (self.trackImages) { self.$trackImages = $('
') .css({ width: self.trackSize + 'px', marginRight: (-self.trackSize - 1) + 'px' }) .appendTo(self.$track.$element); self.options.trackImages.forEach(function(v, i) { $('') .attr({ src: v }) .addClass(i == 0 ? 'OxFirstChild' : '') .addClass(i == self.trackImages - 1 ? 'OxLastChild' : '') .css({ width: self.trackImageWidths[i] + 'px' }) .mousedown(function(e) { e.preventDefault(); // prevent drag }) .appendTo(self.$trackImages); //left += self.trackImageWidths[i]; }); } self.$thumb = Ox.Button({ id: self.options.id + 'Thumb', width: self.thumbSize }) .addClass('OxThumb' + ( self.options.thumbStyle == 'transparent' ? ' OxTransparent' : '' )) .appendTo(self.$track); setThumb(); function clickArrow(data, i, animate) { // fixme: shift doesn't work, see menu scrolling setValue( self.options.value + self.options.arrowStep * (i == 0 ? -1 : 1) * (data.shiftKey ? 2 : 1), animate, true ); } function clickTrack(data) { // fixme: thumb ends up a bit too far on the right var isThumb = $(data.target).hasClass('OxThumb'); self.drag = { left: self.$track.offset().left, offset: isThumb ? data.clientX - self.$thumb.offset().left - 8 /*self.thumbSize / 2*/ : 0 }; setValue(getValue(data.clientX - self.drag.left - self.drag.offset), !isThumb, true); } function dragTrack(data) { setValue( getValue(data.clientX - self.drag.left - self.drag.offset), false, self.options.changeOnDrag ); } function dragendTrack(data) { self.options.value = void 0; setValue(getValue(data.clientX - self.drag.left - self.drag.offset), false, true); } function getPx(value) { var pxPerValue = (self.trackSize - self.thumbSize) / (self.options.max - self.options.min); value = self.hasValues ? self.options.values.indexOf(value) : value; return Math.ceil((value - self.options.min) * pxPerValue); } /* function getTime(oldValue, newValue) { return self.animationTime * Math.abs(oldValue - newValue) / (self.options.max - self.options.min); } */ function getValue(px) { var px = self.trackSize / self.values >= 16 ? px : px - 8, valuePerPx = (self.options.max - self.options.min) / (self.trackSize - self.thumbSize), value = Ox.limit( self.options.min + Math.floor(px * valuePerPx / self.options.step) * self.options.step, self.options.min, self.options.max ); return self.hasValues ? self.options.values[value] : value; } function setSizes() { self.trackSize = self.options.size - self.options.arrows * 32; self.thumbSize = Math.max(self.trackSize / self.values, self.options.thumbSize); self.trackImageWidths = self.trackImages == 1 ? [self.trackSize - 16] : Ox.divideInt(self.trackSize - 2, self.trackImages); self.trackColorStart = self.options.trackGradient ? self.thumbSize / 2 / self.options.size : 0; self.trackColorStep = self.options.trackGradient ? (self.options.size - self.thumbSize) / (self.trackColors - 1) / self.options.size : 1 / self.trackColors; that.css({ width: self.options.size + 'px' }); self.$track && self.$track.css({ width: (self.trackSize - 2) + 'px' }); if (self.$thumb) { self.$thumb.options({width: self.thumbSize}); setThumb(); } } function setThumb(animate) { self.$thumb.stop().animate({ marginLeft: getPx(self.options.value) - 1 + 'px' //, width: self.thumbSize + 'px' }, animate ? 250 : 0, function() { self.options.thumbValue && self.$thumb.options({ title: self.options.value }); }); } function setTrackColors() { ['moz', 'o', 'webkit'].forEach(function(browser) { self.$track.css({ background: '-' + browser + '-linear-gradient(left, ' + self.options.trackColors[0] + ' 0%, ' + self.options.trackColors.map(function(v, i) { var ret = v + ' ' + ( self.trackColorStart + self.trackColorStep * i ) * 100 + '%'; if (!self.options.trackGradient) { ret += ', ' + v + ' ' + ( self.trackColorStart + self.trackColorStep * (i + 1) ) * 100 + '%'; } return ret; }).join(', ') + ', ' + self.options.trackColors[self.trackColors - 1] + ' 100%)' }); }); } function setValue(value, animate, trigger) { // fixme: toPrecision helps with imprecise results of divisions, // but won't work for very large values. And is 10 a good value? value = Ox.limit( self.hasValues ? self.options.values.indexOf(value) : value.toPrecision(10), self.options.min, self.options.max ); value = self.hasValues ? self.options.values[value] : value; if (value != self.options.value) { //time = getTime(self.options.value, value); self.options.value = value; setThumb(animate); trigger && that.triggerEvent('change', {value: self.options.value}); } } self.setOption = function(key, value) { if (key == 'size') { setSizes(); } else if (key == 'trackColors') { setTrackColors(); } else if (key == 'value') { setThumb(); } } return that; };