From d8f0a3f93ca80fa64435dde8100d0eae6110207c Mon Sep 17 00:00:00 2001 From: Thomas Fuchs Date: Sat, 29 Oct 2005 11:48:53 +0000 Subject: Update script.aculo.us to V1.5_rc4 git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2802 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/CHANGELOG | 2 + .../lib/action_view/helpers/javascripts/builder.js | 97 +++++ .../action_view/helpers/javascripts/controls.js | 23 +- .../action_view/helpers/javascripts/dragdrop.js | 9 +- .../lib/action_view/helpers/javascripts/effects.js | 471 ++++++++------------- .../lib/action_view/helpers/javascripts/slider.js | 282 ++++++------ 6 files changed, 429 insertions(+), 455 deletions(-) create mode 100644 actionpack/lib/action_view/helpers/javascripts/builder.js (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 402ec82f0c..453800dab1 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Update script.aculo.us to V1.5_rc4 [Thomas Fuchs] + * Fix that render :text didn't interpolate instance variables #2629, #2626 [skaes] * Fix line number detection and escape RAILS_ROOT in backtrace Regexp [Nicholas Seckar] diff --git a/actionpack/lib/action_view/helpers/javascripts/builder.js b/actionpack/lib/action_view/helpers/javascripts/builder.js new file mode 100644 index 0000000000..5e00f451e2 --- /dev/null +++ b/actionpack/lib/action_view/helpers/javascripts/builder.js @@ -0,0 +1,97 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// See scriptaculous.js for full license. + +var Builder = { + NODEMAP: { + AREA: 'map', + CAPTION: 'table', + COL: 'table', + COLGROUP: 'table', + LEGEND: 'fieldset', + OPTGROUP: 'select', + OPTION: 'select', + PARAM: 'object', + TBODY: 'table', + TD: 'table', + TFOOT: 'table', + TH: 'table', + THEAD: 'table', + TR: 'table' + }, + // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, + // due to a Firefox bug + node: function(elementName) { + elementName = elementName.toUpperCase(); + + // try innerHTML approach + var parentTag = this.NODEMAP[elementName] || 'div'; + var parentElement = document.createElement(parentTag); + parentElement.innerHTML = "<" + elementName + ">"; + var element = parentElement.firstChild || null; + + // see if browser added wrapping tags + if(element && (element.tagName != elementName)) + element = element.getElementsByTagName(elementName)[0]; + + // fallback to createElement approach + if(!element) element = document.createElement(elementName); + + // abort if nothing could be created + if(!element) return; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array)) { + this._children(element, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) { + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + element = parentElement.firstChild || null; + // workaround firefox 1.0.X bug + if(!element) { + element = document.createElement(elementName); + for(attr in arguments[1]) + element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; + } + if(element.tagName != elementName) + element = parentElement.getElementsByTagName(elementName)[0]; + } + } + + // text, or array of children + if(arguments[2]) + this._children(element, arguments[2]); + + return element; + }, + _text: function(text) { + return document.createTextNode(text); + }, + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute=='className' ? 'class' : attribute) + + '="' + attributes[attribute].toString().escapeHTML() + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(typeof children=='object') { // array can hold nodes and text + children.flatten().each( function(e) { + if(typeof e=='object') + element.appendChild(e) + else + if(Builder._isStringOrNumber(e)) + element.appendChild(Builder._text(e)); + }); + } else + if(Builder._isStringOrNumber(children)) + element.appendChild(Builder._text(children)); + }, + _isStringOrNumber: function(param) { + return(typeof param=='string' || typeof param=='number'); + } +} \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/controls.js b/actionpack/lib/action_view/helpers/javascripts/controls.js index a7436bcf17..6da588543b 100644 --- a/actionpack/lib/action_view/helpers/javascripts/controls.js +++ b/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -184,7 +184,10 @@ Autocompleter.Base.prototype = { this.show(); this.active = true; } - } else this.hide(); + } else { + this.active = false; + this.hide(); + } }, markPrevious: function() { @@ -425,6 +428,15 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { // // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + Ajax.InPlaceEditor = Class.create(); Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; Ajax.InPlaceEditor.prototype = { @@ -490,7 +502,7 @@ Ajax.InPlaceEditor.prototype = { Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); } }, - enterEditMode: function() { + enterEditMode: function(evt) { if (this.saving) return; if (this.editing) return; this.editing = true; @@ -501,11 +513,12 @@ Ajax.InPlaceEditor.prototype = { Element.hide(this.element); this.createForm(); this.element.parentNode.insertBefore(this.form, this.element); - Field.focus(this.editField); + Field.scrollFreeActivate(this.editField); // stop the event to avoid a page refresh in Safari - if (arguments.length > 1) { - Event.stop(arguments[0]); + if (evt) { + Event.stop(evt); } + return false; }, createForm: function() { this.form = document.createElement("form"); diff --git a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js index 5445d748c3..63a68243b6 100644 --- a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -280,7 +280,7 @@ Draggable.prototype = { style.position = "relative"; if(this.options.zindex) { - this.options.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); style.zIndex = this.options.zindex; } @@ -355,8 +355,8 @@ var Sortable = { hoverclass: null, ghosting: false, format: null, - onChange: function() {}, - onUpdate: function() {} + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction }, arguments[1] || {}); // clear any old sortable with same element @@ -472,7 +472,10 @@ var Sortable = { onEmptyHover: function(element, dropon) { if(element.parentNode!=dropon) { + var oldParentNode = element.parentNode; dropon.appendChild(element); + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon).onChange(element); } }, diff --git a/actionpack/lib/action_view/helpers/javascripts/effects.js b/actionpack/lib/action_view/helpers/javascripts/effects.js index 7e65d9223c..3f92992065 100644 --- a/actionpack/lib/action_view/helpers/javascripts/effects.js +++ b/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -4,296 +4,181 @@ // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki // -// See scriptaculous.js for full license. - -Object.debug = function(obj) { - var info = []; - - if(typeof obj in ["string","number"]) { - return obj; - } else { - for(property in obj) - if(typeof obj[property]!="function") - info.push(property + ' => ' + - (typeof obj[property] == "string" ? - '"' + obj[property] + '"' : - obj[property])); - } - - return ("'" + obj + "' #" + typeof obj + - ": {" + info.join(", ") + "}"); -} - - -/*--------------------------------------------------------------------------*/ - -var Builder = { - NODEMAP: { - AREA: 'map', - CAPTION: 'table', - COL: 'table', - COLGROUP: 'table', - LEGEND: 'fieldset', - OPTGROUP: 'select', - OPTION: 'select', - PARAM: 'object', - TBODY: 'table', - TD: 'table', - TFOOT: 'table', - TH: 'table', - THEAD: 'table', - TR: 'table' - }, - // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, - // due to a Firefox bug - node: function(elementName) { - elementName = elementName.toUpperCase(); - - // try innerHTML approach - var parentTag = this.NODEMAP[elementName] || 'div'; - var parentElement = document.createElement(parentTag); - parentElement.innerHTML = "<" + elementName + ">"; - var element = parentElement.firstChild || null; - - // see if browser added wrapping tags - if(element && (element.tagName != elementName)) - element = element.getElementsByTagName(elementName)[0]; - - // fallback to createElement approach - if(!element) element = document.createElement(elementName); - - // abort if nothing could be created - if(!element) return; - - // attributes (or text) - if(arguments[1]) - if(this._isStringOrNumber(arguments[1]) || - (arguments[1] instanceof Array)) { - this._children(element, arguments[1]); - } else { - var attrs = this._attributes(arguments[1]); - if(attrs.length) { - parentElement.innerHTML = "<" +elementName + " " + - attrs + ">"; - element = parentElement.firstChild || null; - // workaround firefox 1.0.X bug - if(!element) { - element = document.createElement(elementName); - for(attr in arguments[1]) - element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; - } - if(element.tagName != elementName) - element = parentElement.getElementsByTagName(elementName)[0]; - } - } - - // text, or array of children - if(arguments[2]) - this._children(element, arguments[2]); - - return element; - }, - _text: function(text) { - return document.createTextNode(text); - }, - _attributes: function(attributes) { - var attrs = []; - for(attribute in attributes) - attrs.push((attribute=='className' ? 'class' : attribute) + - '="' + attributes[attribute].toString().escapeHTML() + '"'); - return attrs.join(" "); - }, - _children: function(element, children) { - if(typeof children=='object') { // array can hold nodes and text - children.flatten().each( function(e) { - if(typeof e=='object') - element.appendChild(e) - else - if(Builder._isStringOrNumber(e)) - element.appendChild(Builder._text(e)); - }); - } else - if(Builder._isStringOrNumber(children)) - element.appendChild(Builder._text(children)); - }, - _isStringOrNumber: function(param) { - return(typeof param=='string' || typeof param=='number'); - } -} - -/* ------------- element ext -------------- */ - -// converts rgb() and #xxx to #xxxxxx format, -// returns self (or first argument) if not convertable -String.prototype.parseColor = function() { - color = "#"; - if(this.slice(0,4) == "rgb(") { - var cols = this.slice(4,this.length-1).split(','); - var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); - } else { - if(this.slice(0,1) == '#') { - if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); - if(this.length==7) color = this.toLowerCase(); - } - } - return(color.length==7 ? color : (arguments[0] || this)); -} - -Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { - var children = $(element).childNodes; - var text = ""; - var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); - - for (var i = 0; i < children.length; i++) { - if(children[i].nodeType==3) { - text+=children[i].nodeValue; - } else { - if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) - text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); - } - } - - return text; -} - -Element.setContentZoom = function(element, percent) { - element = $(element); - element.style.fontSize = (percent/100) + "em"; - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); -} - -Element.getOpacity = function(element){ - var opacity; - if (opacity = Element.getStyle(element, "opacity")) - return parseFloat(opacity); - if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/)) - if(opacity[1]) return parseFloat(opacity[1]) / 100; - return 1.0; -} - -Element.setOpacity = function(element, value){ - element= $(element); - var els = element.style; - if (value == 1){ - els.opacity = '0.999999'; - if(/MSIE/.test(navigator.userAgent)) - els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,''); - } else { - if(value < 0.00001) value = 0; - els.opacity = value; - if(/MSIE/.test(navigator.userAgent)) - els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + - "alpha(opacity="+value*100+")"; +// See scriptaculous.js for full license. + +/* ------------- element ext -------------- */ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + color = "#"; + if(this.slice(0,4) == "rgb(") { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } } + return(color.length==7 ? color : (arguments[0] || this)); +} + +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ""; + var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); + + for (var i = 0; i < children.length; i++) { + if(children[i].nodeType==3) { + text+=children[i].nodeValue; + } else { + if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) + text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); + } + } + + return text; } -Element.getInlineOpacity = function(element){ - element= $(element); - var op; - op = element.style.opacity; - if (typeof op != "undefined" && op != "") return op; - return ""; -} - -Element.setInlineOpacity = function(element, value){ - element= $(element); - var els = element.style; - els.opacity = value; -} - -/*--------------------------------------------------------------------------*/ - -Element.Class = { - // Element.toggleClass(element, className) toggles the class being on/off - // Element.toggleClass(element, className1, className2) toggles between both classes, - // defaulting to className1 if neither exist - toggle: function(element, className) { - if(Element.Class.has(element, className)) { - Element.Class.remove(element, className); - if(arguments.length == 3) Element.Class.add(element, arguments[2]); - } else { - Element.Class.add(element, className); - if(arguments.length == 3) Element.Class.remove(element, arguments[2]); - } - }, - - // gets space-delimited classnames of an element as an array - get: function(element) { - return $(element).className.split(' '); - }, - - // functions adapted from original functions by Gavin Kistner - remove: function(element) { - element = $(element); - var removeClasses = arguments; - $R(1,arguments.length-1).each( function(index) { - element.className = - element.className.split(' ').reject( - function(klass) { return (klass == removeClasses[index]) } ).join(' '); - }); - }, - - add: function(element) { - element = $(element); - for(var i = 1; i < arguments.length; i++) { - Element.Class.remove(element, arguments[i]); - element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; - } - }, - - // returns true if all given classes exist in said element - has: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - if((typeof arguments[i] == 'object') && - (arguments[i].constructor == Array)) { - for(var j = 0; j < arguments[i].length; j++) { - regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); - if(!regEx.test(element.className)) return false; - } - } else { - regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); - if(!regEx.test(element.className)) return false; - } - } - return true; - }, - - // expects arrays of strings and/or strings as optional paramters - // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') - has_any: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - if((typeof arguments[i] == 'object') && - (arguments[i].constructor == Array)) { - for(var j = 0; j < arguments[i].length; j++) { - regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); - if(regEx.test(element.className)) return true; - } - } else { - regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); - if(regEx.test(element.className)) return true; - } - } - return false; - }, - - childrenWith: function(element, className) { - var children = $(element).getElementsByTagName('*'); - var elements = new Array(); - - for (var i = 0; i < children.length; i++) - if (Element.Class.has(children[i], className)) - elements.push(children[i]); - - return elements; - } -} - +Element.setContentZoom = function(element, percent) { + element = $(element); + element.style.fontSize = (percent/100) + "em"; + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, "opacity")) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + var els = element.style; + if (value == 1){ + els.opacity = '0.999999'; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + els.opacity = value; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + "alpha(opacity="+value*100+")"; + } +} + +Element.getInlineOpacity = function(element){ + element= $(element); + var op; + op = element.style.opacity; + if (typeof op != "undefined" && op != "") return op; + return ""; +} + +Element.setInlineOpacity = function(element, value){ + element= $(element); + var els = element.style; + els.opacity = value; +} + +/*--------------------------------------------------------------------------*/ + +Element.Class = { + // Element.toggleClass(element, className) toggles the class being on/off + // Element.toggleClass(element, className1, className2) toggles between both classes, + // defaulting to className1 if neither exist + toggle: function(element, className) { + if(Element.Class.has(element, className)) { + Element.Class.remove(element, className); + if(arguments.length == 3) Element.Class.add(element, arguments[2]); + } else { + Element.Class.add(element, className); + if(arguments.length == 3) Element.Class.remove(element, arguments[2]); + } + }, + + // gets space-delimited classnames of an element as an array + get: function(element) { + return $(element).className.split(' '); + }, + + // functions adapted from original functions by Gavin Kistner + remove: function(element) { + element = $(element); + var removeClasses = arguments; + $R(1,arguments.length-1).each( function(index) { + element.className = + element.className.split(' ').reject( + function(klass) { return (klass == removeClasses[index]) } ).join(' '); + }); + }, + + add: function(element) { + element = $(element); + for(var i = 1; i < arguments.length; i++) { + Element.Class.remove(element, arguments[i]); + element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; + } + }, + + // returns true if all given classes exist in said element + has: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } + return true; + }, + + // expects arrays of strings and/or strings as optional paramters + // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') + has_any: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } + return false; + }, + + childrenWith: function(element, className) { + var children = $(element).getElementsByTagName('*'); + var elements = new Array(); + + for (var i = 0; i < children.length; i++) + if (Element.Class.has(children[i], className)) + elements.push(children[i]); + + return elements; + } +} + /*--------------------------------------------------------------------------*/ var Effect = { @@ -371,6 +256,9 @@ Effect.Transitions.full = function(pos) { Effect.Queue = { effects: [], + _each: function(iterator) { + this.effects._each(iterator); + }, interval: null, add: function(effect) { var timestamp = new Date().getTime(); @@ -407,6 +295,7 @@ Effect.Queue = { this.effects.invoke('loop', timePos); } } +Object.extend(Effect.Queue, Enumerable); Effect.Base = function() {}; Effect.Base.prototype = { @@ -632,6 +521,8 @@ Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), this.start(options); }, setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.style.display=='none') { this.cancel(); return; } // Disable background image during the effect this.oldBgImage = this.element.style.backgroundImage; this.element.style.backgroundImage = "none"; @@ -862,7 +753,7 @@ Effect.SlideDown = function(element) { }, afterUpdateInternal: function(effect) { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + (effect.dims[0] - effect.element.clientHeight) + 'px'; }, afterFinishInternal: function(effect) { Element.undoClipping(effect.element); Element.undoPositioned(effect.element.firstChild); @@ -889,7 +780,7 @@ Effect.SlideUp = function(element) { }, afterUpdateInternal: function(effect) { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + (effect.dims[0] - effect.element.clientHeight) + 'px'; }, afterFinishInternal: function(effect) { Element.hide(effect.element); Element.undoClipping(effect.element); @@ -987,7 +878,7 @@ Effect.Grow = function(element) { els.top = oldTop; els.left = oldLeft; els.height = oldHeight; - els.width = originalWidth; + els.width = originalWidth + 'px'; Element.setInlineOpacity(el, oldOpacity); } }, options) diff --git a/actionpack/lib/action_view/helpers/javascripts/slider.js b/actionpack/lib/action_view/helpers/javascripts/slider.js index 1712b98943..736ed41a86 100644 --- a/actionpack/lib/action_view/helpers/javascripts/slider.js +++ b/actionpack/lib/action_view/helpers/javascripts/slider.js @@ -7,166 +7,182 @@ Control.Slider = Class.create(); // options: // axis: 'vertical', or 'horizontal' (default) -// increment: (default: 1) -// step: (default: 1) // // callbacks: // onChange(value) // onSlide(value) Control.Slider.prototype = { initialize: function(handle, track, options) { - this.handle = $(handle); + var slider = this; + + if(handle instanceof Array) { + this.handles = handle.collect( function(e) { return $(e) }); + } else { + this.handles = [$(handle)]; + } + this.track = $(track); - this.options = options || {}; this.axis = this.options.axis || 'horizontal'; this.increment = this.options.increment || 1; - this.step = parseInt(this.options.step) || 1; - this.value = 0; - - var defaultMaximum = Math.round(this.track.offsetWidth / this.increment); - if(this.isVertical()) defaultMaximum = Math.round(this.track.offsetHeight / this.increment); + this.step = parseInt(this.options.step || '1'); + this.range = this.options.range || $R(0,1); - this.maximum = this.options.maximum || defaultMaximum; - this.minimum = this.options.minimum || 0; - - // Will be used to align the handle onto the track, if necessary - this.alignX = parseInt (this.options.alignX) || 0; - this.alignY = parseInt (this.options.alignY) || 0; - - // Zero out the slider position - this.setCurrentLeft(Position.cumulativeOffset(this.track)[0] - Position.cumulativeOffset(this.handle)[0] + this.alignX); - this.setCurrentTop(this.trackTop() - Position.cumulativeOffset(this.handle)[1] + this.alignY); + this.value = 0; // assure backwards compat + this.values = this.handles.map( function() { return 0 }); + this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false; + this.restricted = this.options.restricted || false; - this.offsetX = 0; - this.offsetY = 0; + this.maximum = this.options.maximum || this.range.end; + this.minimum = this.options.minimum || this.range.start; - this.originalLeft = this.currentLeft(); - this.originalTop = this.currentTop(); - this.originalZ = parseInt(this.handle.style.zIndex || "0"); - - // Prepopulate Slider value - this.setSliderValue(parseInt(this.options.sliderValue) || 0); + // Will be used to align the handle onto the track, if necessary + this.alignX = parseInt(this.options.alignX || '0'); + this.alignY = parseInt(this.options.alignY || '0'); + + this.trackLength = this.maximumOffset() - this.minimumOffset(); this.active = false; this.dragging = false; this.disabled = false; - // FIXME: use css - this.handleImage = $(this.options.handleImage) || false; - this.handleDisabled = this.options.handleDisabled || false; - this.handleEnabled = false; - if(this.handleImage) - this.handleEnabled = this.handleImage.src || false; - - if(this.options.disabled) - this.setDisabled(); - - // Value Array - this.values = this.options.values || false; // Add method to validate and sort?? + if(this.options.disabled) this.setDisabled(); - Element.makePositioned(this.handle); // fix IE + // Allowed values array + this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; + if(this.allowedValues) { + this.minimum = this.allowedValues.min(); + this.maximum = this.allowedValues.max(); + } this.eventMouseDown = this.startDrag.bindAsEventListener(this); this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.update.bindAsEventListener(this); - this.eventKeypress = this.keyPress.bindAsEventListener(this); - Event.observe(this.handle, "mousedown", this.eventMouseDown); + // Initialize handles + this.handles.each( function(h,i) { + slider.setValue(parseInt(slider.options.sliderValue || slider.range.start), i); + Element.makePositioned(h); // fix IE + Event.observe(h, "mousedown", slider.eventMouseDown); + }); + Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); - Event.observe(document, "keypress", this.eventKeypress); }, dispose: function() { - Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + var slider = this; Event.stopObserving(document, "mouseup", this.eventMouseUp); Event.stopObserving(document, "mousemove", this.eventMouseMove); - Event.stopObserving(document, "keypress", this.eventKeypress); + this.handles.each( function(h) { + Event.stopObserving(h, "mousedown", slider.eventMouseDown); + }); }, setDisabled: function(){ this.disabled = true; - if(this.handleDisabled) - this.handleImage.src = this.handleDisabled; }, setEnabled: function(){ this.disabled = false; - if(this.handleEnabled) - this.handleImage.src = this.handleEnabled; }, - currentLeft: function() { - return parseInt(this.handle.style.left || '0'); - }, - currentTop: function() { - return parseInt(this.handle.style.top || '0'); - }, - setCurrentLeft: function(left) { - this.handle.style.left = left +"px"; - }, - setCurrentTop: function(top) { - this.handle.style.top = top +"px"; - }, - trackLeft: function(){ - return Position.cumulativeOffset(this.track)[0]; - }, - trackTop: function(){ - return Position.cumulativeOffset(this.track)[1]; - }, getNearestValue: function(value){ - if(this.values){ - var i = 0; - var offset = Math.abs(this.values[0] - value); - var newValue = this.values[0]; - - for(i=0; i < this.values.length; i++){ - var currentOffset = Math.abs(this.values[i] - value); - if(currentOffset < offset){ - newValue = this.values[i]; + if(this.allowedValues){ + if(value >= this.allowedValues.max()) return(this.allowedValues.max()); + if(value <= this.allowedValues.min()) return(this.allowedValues.min()); + + var offset = Math.abs(this.allowedValues[0] - value); + var newValue = this.allowedValues[0]; + this.allowedValues.each( function(v) { + var currentOffset = Math.abs(v - value); + if(currentOffset <= offset){ + newValue = v; offset = currentOffset; - } - } + } + }); return newValue; } + if(value > this.range.end) return this.range.end; + if(value < this.range.start) return this.range.start; return value; }, - setSliderValue: function(sliderValue){ - // First check our max and minimum and nearest values - sliderValue = this.getNearestValue(sliderValue); - if(sliderValue > this.maximum) sliderValue = this.maximum; - if(sliderValue < this.minimum) sliderValue = this.minimum; - var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment; - - if(this.isVertical()){ - this.setCurrentTop(offsetDiff + this.currentTop()); - } else { - this.setCurrentLeft(offsetDiff + this.currentLeft()); + setValue: function(sliderValue, handleIdx){ + if(!this.active) { + this.activeHandle = this.handles[handleIdx]; + this.activeHandleIdx = handleIdx; + } + handleIdx = handleIdx || this.activeHandleIdx || 0; + if(this.restricted) { + if((handleIdx>0) && (sliderValuethis.values[handleIdx+1])) + sliderValue = this.values[handleIdx+1]; } - this.value = sliderValue; + sliderValue = this.getNearestValue(sliderValue); + this.values[handleIdx] = sliderValue; + this.value = this.values[0]; // assure backwards compat + + this.handles[handleIdx].style[ this.isVertical() ? 'top' : 'left'] = + this.translateToPx(sliderValue); + + this.drawSpans(); this.updateFinished(); - }, + }, + setValueBy: function(delta, handleIdx) { + this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, + handleIdx || this.activeHandleIdx || 0); + }, + translateToPx: function(value) { + return Math.round((this.trackLength / (this.range.end - this.range.start)) * (value - this.range.start)) + "px"; + }, + translateToValue: function(offset) { + return ((offset/this.trackLength) * (this.range.end - this.range.start)) + this.range.start; + }, + getRange: function(range) { + var v = this.values.sortBy(Prototype.K); + range = range || 0; + return $R(v[range],v[range+1]); + }, minimumOffset: function(){ - return(this.isVertical() ? - this.trackTop() + this.alignY : - this.trackLeft() + this.alignX); + return(this.isVertical() ? this.alignY : this.alignX); }, maximumOffset: function(){ return(this.isVertical() ? - this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment : - this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment); + this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX); }, isVertical: function(){ return (this.axis == 'vertical'); }, + drawSpans: function() { + var slider = this; + if(this.spans) + $R(0, this.spans.length-1).each(function(r) { slider.setSpan(r, slider.getRange(r)) }); + }, + setSpan: function(span, range) { + if(this.isVertical()) { + this.spans[span].style.top = this.translateToPx(range.start); + this.spans[span].style.height = this.translateToPx(range.end - range.start); + } else { + this.spans[span].style.left = this.translateToPx(range.start); + this.spans[span].style.width = this.translateToPx(range.end - range.start); + } + }, startDrag: function(event) { if(Event.isLeftClick(event)) { if(!this.disabled){ this.active = true; - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var offsets = Position.cumulativeOffset(this.handle); - this.offsetX = (pointer[0] - offsets[0]); - this.offsetY = (pointer[1] - offsets[1]); - this.originalLeft = this.currentLeft(); - this.originalTop = this.currentTop(); + + // find the handle (prevents issues with Safari) + var handle = Event.element(event); + while((this.handles.indexOf(handle) == -1) && handle.parentNode) + handle = handle.parentNode; + + this.activeHandle = handle; + this.activeHandleIdx = this.handles.indexOf(this.activeHandle); + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.activeHandle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } Event.stop(event); } @@ -174,10 +190,8 @@ Control.Slider.prototype = { update: function(event) { if(this.active) { if(!this.dragging) { - var style = this.handle.style; this.dragging = true; - if(style.position=="") style.position = "relative"; - style.zIndex = this.options.zindex; + if(this.activeHandle.style.position=="") style.position = "relative"; } this.draw(event); // fix AppleWebKit rendering @@ -187,43 +201,11 @@ Control.Slider.prototype = { }, draw: function(event) { var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var offsets = Position.cumulativeOffset(this.handle); - - offsets[0] -= this.currentLeft(); - offsets[1] -= this.currentTop(); - - // Adjust for the pointer's position on the handle - pointer[0] -= this.offsetX; - pointer[1] -= this.offsetY; - var style = this.handle.style; - - if(this.isVertical()){ - if(pointer[1] > this.maximumOffset()) - pointer[1] = this.maximumOffset(); - if(pointer[1] < this.minimumOffset()) - pointer[1] = this.minimumOffset(); - - // Increment by values - if(this.values){ - this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum); - pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment; - } else { - this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum; - } - style.top = pointer[1] - offsets[1] + "px"; - } else { - if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset(); - if(pointer[0] < this.minimumOffset()) pointer[0] = this.minimumOffset(); - // Increment by values - if(this.values){ - this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum); - pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment; - } else { - this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum; - } - style.left = (pointer[0] - offsets[0]) + "px"; - } - if(this.options.onSlide) this.options.onSlide(this.value); + var offsets = Position.cumulativeOffset(this.track); + pointer[0] -= this.offsetX + offsets[0]; + pointer[1] -= this.offsetY + offsets[1]; + this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); + if(this.options.onSlide) this.options.onSlide(this.values.length>1 ? this.values : this.value, this); }, endDrag: function(event) { if(this.active && this.dragging) { @@ -236,23 +218,9 @@ Control.Slider.prototype = { finishDrag: function(event, success) { this.active = false; this.dragging = false; - this.handle.style.zIndex = this.originalZ; - this.originalLeft = this.currentLeft(); - this.originalTop = this.currentTop(); this.updateFinished(); }, updateFinished: function() { - if(this.options.onChange) this.options.onChange(this.value); - }, - keyPress: function(event) { - if(this.active && !this.disabled) { - switch(event.keyCode) { - case Event.KEY_ESC: - this.finishDrag(event, false); - Event.stop(event); - break; - } - if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); - } + if(this.options.onChange) this.options.onChange(this.values.length>1 ? this.values : this.value, this); } } -- cgit v1.2.3