/** * PSWrap.js is built on top of Prototype and Scriptaculous. It aims to provide wrappers to simplify * the use of the scriptaculous queueing system and improve syntax, semantics, and general ease of use. * * @author Peter Swan * @copyright 2008 * @license MIT */ /* TODO: * add a preventIf callback which prevents an event from firing if it returns true/false * determine how to leverage position: with-last to provide multiple effects on single element * * add default open, close, move functionality to every animatable element * * must do bind as event listener and stop event if it is present * * can probably make a collection without parallel by using a single queue for all events on objects. * could NOT set a limit on the queue and would have to use with-last. * * use identify instead of readAttribute('id') to get around no id provided errors! * * modify _action_open/_action_close/etc. to allow for event specification as well; e.g. elmid_action_open_click * (id of element)_(type)_(function)_(event) * * / /** * @namespace contains all library functionality */ var PSWrap = { /** Version of the Library * @type String */ Version: '0.1.0', /** Required version of Scriptaculous * @type String */ REQUIRED_SCRIPTACULOUS: '1.8.0', /** * Checks dependencies of library * @return {Void} */ load: function(){ function convertVersionString(versionString){ var r = versionString.split('.'); return parseInt(r[0]) * 100000 + parseInt(r[1]) * 1000 + parseInt(r[2]); } if ((typeof Scriptaculous == 'undefined') || (convertVersionString(Scriptaculous.Version) < convertVersionString(PSWrap.REQUIRED_SCRIPTACULOUS)) || typeof(Effect) == 'undefined' ) //|| typeof(Builder) == 'undefined' ) throw ("PSWrap requires the Scriptaculous JavaScript framework >= " + PSWrap.REQUIRED_SCRIPTACULOUS + ' with Effects and Builder'); } }; PSWrap.load(); /* TODO: make this deep copy arrays as well */ Object.extend( Object, { recursiveExtend: function( destination, source){ for( var k in source){ if( typeof( destination[k]) == 'object' && typeof( source[k]) == 'object'){ Object.recursiveExtend( destination[k], source[k]); }else{ destination[k] = source[k]; } } return destination; } }); Element.addMethods({ selectChildren: function( element, c){ return $(element).childElements().findAll( function( elm ){ return elm.match(c); }); } }); /* modify Effect.Base and Effect.ScopedQueue to only execute beforeStart callbacks * if effect was successfully added to the queue. */ Effect.Base.addMethods( { start: function(options) { function codeForEvent(options,eventName){ return ( (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') + (options[eventName] ? 'this.options.'+eventName+'(this);' : '') ); } if (options && options.transition === false) options.transition = Effect.Transitions.linear; this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { }); this.currentFrame = 0; this.state = 'idle'; this.startOn = this.options.delay*1000; this.finishOn = this.startOn+(this.options.duration*1000); this.fromToDelta = this.options.to-this.options.from; this.totalTime = this.finishOn-this.startOn; this.totalFrames = this.options.fps*this.options.duration; this.render = (function() { function dispatch(effect, eventName) { if (effect.options[eventName + 'Internal']) effect.options[eventName + 'Internal'](effect); if (effect.options[eventName]) effect.options[eventName](effect); } return function(pos) { if (this.state === "idle") { this.state = "running"; dispatch(this, 'beforeSetup'); if (this.setup) this.setup(); dispatch(this, 'afterSetup'); } if (this.state === "running") { pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from; this.position = pos; dispatch(this, 'beforeUpdate'); if (this.update) this.update(pos); dispatch(this, 'afterUpdate'); } } })(); if (!this.options.sync) { // if the effect was successfully added to the queue, execute beforeStart if( Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).add(this)) this.event('beforeStart'); } // if this is a synched event it's definitely happening, so execute beforeStart else{ this.event('beforeStart'); } } }); Effect.ScopedQueue.addMethods( { add: function(effect){ var timestamp = new Date().getTime(); var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; switch (position) { case 'front': // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state == 'idle' }).each(function(e){ e.startOn += effect.finishOn; e.finishOn += effect.finishOn; }); break; case 'with-last': timestamp = this.effects.pluck('startOn').max() || timestamp; break; case 'end': // start effect after last queued effect has finished timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } effect.startOn += timestamp; effect.finishOn += timestamp; /* return true if effect was inserted into the queue false otherwise */ var ret = false; if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)){ ret = true; this.effects.push(effect); } if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); return ret; } }); /* patch BlindUp and BlindDown for IE */ Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, restoreAfterFinish: true, /* Change here: IE will not correctly clip if there are layers inside element! */ afterSetupInternal: function( effect ){ effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned(); } }, arguments[1] || { }) ); }; Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetupInternal: function(effect) { /* Change here: IE will not correctly clip if there are layers inside the element! effect.element.makeClipping().setStyle({height: '0px'}).show();*/ effect.element.makePositioned().makeClipping().setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { /*effect.element.undoClipping();*/ effect.element.undoClipping().undoPositioned(); } }, arguments[1] || { })); }; Effect.Appear = function(element) { element = $(element); var options = Object.extend({ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), to: 1.0, // force Safari to render floated elements properly afterFinishInternal: function(effect) { effect.element.forceRerendering(); }, beforeSetupInternal: function(effect) { effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Scroll = Class.create( Effect.Base, { initialize: function( element ){ this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ x: 0, y: 0, mode: 'absolute' }, arguments[1] || {}); this.start(options); }, setup: function(){ if( this.element === window){ if( !Object.isUndefined(this.element.scrollY)){ this.originalTop = this.element.scrollY; this.originalLeft = this.element.scrollX; //console.log('scrollY/X: ' + this.originalTop + ', ' + this.originalLeft); }else if( !Object.isUndefined(document.documentElement.scrollTop) ){ this.originalTop = document.documentElement.scrollTop; this.originalLeft = document.documentElement.scrollLeft; //console.log('documentElement.scrollTop/Left: ' + this.originalTop + ', ' + this.originalLeft); }else if( !Object.isUndefined(document.scrollTop) ){ this.originalTop = document.scrollTop; this.originalLeft = document.scrollLeft; //console.log('scrollTop/Left: ' + this.originalTop + ', ' + this.originalLeft); }else{ this.orginalTop = this.originalLeft = 0; } }else{ this.originalTop = this.element.scrollTop; this.originalLeft = this.element.scrollLeft; } if( this.options.mode == 'absolute'){ this.options.x -= this.originalLeft; this.options.y -= this.originalTop; } }, update: function( pos ){ var new_x = this.originalLeft + this.options.x * pos; var new_y = this.originalTop + this.options.y * pos; this._setScroll( new_x, new_y); }, _setScroll: function( x, y){ if( this.element === window){ window.scrollTo(x, y); }else{ this.element.scrollTop = x; this.element.scrollLeft = y; } } }); /** * @namespace contains all debug functionality */ PSWrap.Debug = { debug: false, /** toggle debugging * @param {Boolean} d */ setDebug: function( d ){ if( d ){ this.debug = true; }else{ this.debug = false; } }, /** * print to the console if it is defined * @param {String} msg */ printToConsole: function(msg, doit){ if( PSWrap.Debug.debug || doit ){ try{ console.log(msg); }catch(e){} } }, printObjectToConsole: function(obj, spaces){ if (PSWrap.Debug.debug) { spaces = spaces || ''; for (k in obj) { if (typeof(obj[k]) == 'object') { } else { } } } }, /** * print to alert box * @param {String} msg */ printAlert: function(msg){ if (PSWrap.Debug.debug) { alert(msg); } } }; /** * @namespace Contains utility functions */ PSWrap.Util = { /** * Does deep copies of objects to create a new combined object. Properties of each object in the array * will overwrite the properties of the previous object. * * @param {Mixed} options... one or more objects containing options * @return {Object} An object containing the */ assembleOptions: function(){ var options = new Object(); $A(arguments).each(function(o){ Object.recursiveExtend(options, o); }); return options; }, removeOptions: function(obj, arr){ $A(arr).each( function(e){ if(obj[e]){ obj[e] = null; delete obj[e]; } }); }, deepCopy: function( dest, source){ for( k in source){ if( typeof(source[k]) == 'object'){ dest[k] = new Object(); deepCopy( dest[k], source[k]); }else if( typeof(source[k]) == 'array'){ dest[k] = $A(source[k]).clone(); }else{ dest[k] = source[k]; } } } }; /** * @namespace Contains error/exception handling functions */ PSWrap.Error = { /** * @param {String} type * @param {String} required_type */ TypeError: function(type, required_type){ throw { name: 'PSWrap.TypeError', message: type + ' found where ' + required_type + ' required.'}; }, /** * @param {Mixed} id */ ElementNotFoundError: function(){ throw { name: 'PSWrap.ElementNotFoundError', message: 'element not found'}; }, /** * @param {Mixed} elm */ ScopeWarning: function( elm ){ throw { name: 'PSWrap.ScopeWarning', message: 'no scope was provided for ' + elm + ', this may result in unexpected behavior'}; } }; PSWrap.State = { state_string: '', objects: {}, anchor: null, recordState: function(obj){ /* probably want to remove state from state string if state is '' * implemented in old version on vanalen.org */ PSWrap.State.addObject(obj); var r = new RegExp(obj.getId() + '=[^&]+'); var s = obj.getState(); if( r.test(PSWrap.State.state_string)){ PSWrap.State.state_string = PSWrap.State.state_string.replace(r, s); }else{ if( PSWrap.State.state_string != ''){ PSWrap.State.state_string += '&'; } PSWrap.State.state_string += s; } }, addObject: function(obj){ if( Object.isUndefined(PSWrap.State.objects[obj.getId()]) ){ PSWrap.State.objects[obj.getId()] = obj; } }, getStateString: function(){ return PSWrap.State.state_string; }, applyState: function(){ var s = window.location.hash; if(s){ s = s.split('#')[1]; var a = s.split('&'); if (Object.isArray(a)) { a.each(function(e){ e = e.split('='); if (!Object.isUndefined(PSWrap.State.objects[e[0]])) { PSWrap.State.objects[e[0]].setState(e[1]); } }); //PSWrap.State.state_string = s; } } }, getUrl: function(){ var href = window.location.href.split('?')[0].split('#')[0]; var query = window.location.search; /* want to append to query instead of using hash (like google maps) */ return href + query + this.getHash(); }, getHash: function(){ return '#' + PSWrap.State.getStateString(); } } /** * @namespace Contains functions and classes for easily animating elements */ PSWrap.Animation = { HOOKS: ['beforeStart', 'afterFinish', 'beforeSetup', 'afterSetup', 'beforeUpdate', 'afterUpdate'], PARALLEL: 1, SEQUENTIAL: 2 }; PSWrap.Animation.Elements = { _elements: $H(), addElement: function(elm){ var id = elm.getElement().readAttribute('id'); if( id ){ PSWrap.Animation.Elements._elements.set(id, elm); } }, getElement: function(id){ if( id ){ return PSWrap.Animation.Elements._elements.get(id); } } } PSWrap.DynamicLoader = Class.create({ initialize: function(element, options){ options = options || {}; this._after = null; this._before = null; this._parent = null; this._first = this; this._loaded = false; this._loading = false; this._element_list = $A(); this._attempts = 0; this._max_attempts = options['max_attempts'] || 3; this._attempt_interval = options['attempt_interval'] || 0.25; options = options || {}; this._containing_element = $(element); if( this._containing_element ){ var content_path = this._containing_element.down('a'); if( content_path && content_path.hasClassName('dynamic_content_path')){ this._url = content_path.readAttribute('href'); if( this._url){ var beforeLoad = this._containing_element.down('.beforeLoad'); if( beforeLoad ){ beforeLoad = eval( beforeLoad.innerHTML.stripTags().stripScripts()); } var afterLoad = this._containing_element.down('.afterLoad'); if( afterLoad ){ afterLoad = eval( afterLoad.innerHTML.stripTags().stripScripts()); } this._options = { beforeLoad: Object.isFunction(options['beforeLoad'])?options['beforeLoad']:Object.isFunction(beforeLoad)?beforeLoad:Prototype.emptyFunction, afterLoad: Object.isFunction(options['afterLoad'])?options['afterLoad']:Object.isFunction(afterLoad)?afterLoad:Prototype.emptyFunction } this._element_list.push(this._containing_element); var dependencies = this._containing_element.select('ul.dependencies > li'); this._containing_element.select('ul.dependencies > li').each( function(e){ if( e.innerHTML ){ var elm = $( e.innerHTML.stripScripts().stripTags().strip() ); if( elm ){ var after = 0; this._addElement(elm); if( e.hasClassName('after') ){ after = 1; } this._addDependency(new PSWrap.DynamicLoader(elm), after); }else{ } }else{ } }, this); return true; }else{ return false; } }else{ return false; } }else{ return false; } }, getElements: function(){ return this._element_list; }, _addElement: function(elm){ this._element_list.push(elm); }, _addDependency: function( dl, after){ if( after ){ if( !this._after ){ this._setParent(dl); }else{ this._after._setParent(dl); } this._after = dl; }else{ if( !this._before){ this._first = dl; }else{ this._before._setParent(dl); } dl._setParent(this); this._before = dl; } }, _setParent: function(dl){ this._parent = dl; }, load: function(){ if( this._loaded == false && this._loading == false){ this._loading = true; this._first._loadSelf(); } }, _loadSelf: function(){ this._options.beforeLoad(this); new Ajax.Request( this._url, { method: 'get', onSuccess: this._afterLoad.bind(this), onFailure: this._onFailure.bind(this) }); }, _loadParent: function(){ if( this._parent ){ this._parent._loadSelf(); } }, _afterLoad: function(r){ this._loading = false; this._loaded = true; this._containing_element.update(r.responseText); this._options.afterLoad.defer(this); this._loadParent(); }, _onFailure: function(r){ this._loading = false; if( r.status == 503 ){ this._attempts++; if( this._attempts < this._max_attempts){ this._loadSelf.bind(this).delay(this._attempt_interval); }else{ alert('The application is currently unable to process your request, please try again later.'); } }else{ alert("Sorry! We can't find the data you requested."); } } }); PSWrap.DynamicLoader.Util = { afterLoadFuncs: $A(), beforeLoadFuncs: $A(), isLoadable: function(elm){ elm = $(elm); if( elm ){ var link = elm.down('a'); if( link && link.hasClassName('dynamic_content_path')){ var url = link.readAttribute('href'); if( url ){ return true; } } } return false; }, addBeforeLoad: function( f ){ if( Object.isFunction(f)){ PSWrap.DynamicLoader.Util.beforeLoadFuncs.push(f); } }, addAfterLoad: function(f){ if( Object.isFunction(f) ){ PSWrap.DynamicLoader.Util.afterLoadFuncs.push(f); } }, beforeLoad: function( dl ){ PSWrap.DynamicLoader.Util.onEvent(PSWrap.DynamicLoader.Util.afterLoadFuncs, dl); }, afterLoad: function( dl ){ PSWrap.DynamicLoader.Util.onEvent(PSWrap.DynamicLoader.Util.afterLoadFuncs, dl); }, onEvent: function( fl, dl ){ var l = fl.length; for( var i=0; i 0 && i < this._elements.length){ /* could potentially add options at the end of _doOpen to pass one time callbacks */ this._simpleOpen(this._elements[i]); } }, getId: function(){ return this._container.readAttribute('id'); }, identify: function(){ return this.getId(); }, observe: function(){ return this._container.observe.apply(this._container, arguments); }, fire: function(){ return this._container.fire.apply(this._container, arguments); } }); PSWrap.Animation.SequentialOpenCloseCollection = Class.create( PSWrap.Animation.OpenCloseCollection, { initialize: function( $super, container, collection_options, options, open_options, close_options){ $super( container, collection_options, options, open_options, close_options); this._current_index = this._collection_options['startindex'] || 0; this._wrap = this._collection_options['wrap'] || 1; this._elements.invoke('addCallback', 'beforeStart', this._updateDisplayCurrentElements.bind(this), 'open'); /*var c = this._container.readAttribute('id') + '_action_next'; this._next_elements = $$( '.' + c); this._next_elements.invoke('removeClassName', c); c = this._container.readAttribute('id') + '_action_previous'; this._prev_elements = $$( '.' + c); this._prev_elements.invoke('removeClassName', c);*/ var elements = PSWrap.Animation.Classnames.getElements(this, 'display', ['current', 'total'], true); this._display_current_elements = elements['current']; this._display_total_elements = elements['total']; if( this._current_index != this._elements.indexOf(this.getCurrent())){ //this._doOpen(null, this._elements[this._current_index]); this._elements[this._current_index].open(); } elements = PSWrap.Animation.Binding.bindActionElements(this, '_bindElement', {}, ['previous', 'next'], ['previous', 'next']); this._next_elements = elements['next'] || []; this._previous_elements = elements['previous'] || []; this._updateDisplayTotalElements(); this._updateDisplayCurrentElements(); }, _bindElement: function(method, element, event, options, ret_effect, useCapture){ //console.log('_bindElement: ' + element + ', ' + event) try{ var e = $(element); e.observe( event, this[method].bindAsEventListener(this, options, ret_effect), useCapture); }catch( e ){ } }, previous: function(e, options, ret_effect){ try{ e.stop(); }catch(ex){} var new_index = this._current_index - 1; new_index = new_index<0? this._wrap?this._elements.length-1:0 : new_index; if( new_index != this._current_index ){ this._current_index = new_index; //this._doOpen(null, this._elements[this._current_index]); this._elements[this._current_index].open(options, ret_effect); } }, next: function(e, options, ret_effect){ if (e) { e.stop(); } var new_index = this._current_index + 1; new_index = new_index>this._elements.length - 1? !this._wrap?this._elements.length-1:0 : new_index; if( new_index != this._current_index ){ this._current_index = new_index; //this._doOpen(null, this._elements[this._current_index]); this._elements[this._current_index].open(options, ret_effect); } }, _updateDisplayCurrentElements: function(s_obj, oc_obj){ var i = this._current_index + 1; if( oc_obj ){ i = this._elements.indexOf(oc_obj) + 1; } this._display_current_elements.invoke('update', i); }, _updateDisplayTotalElements: function(){ this._display_total_elements.invoke('update', this._elements.length); } }); PSWrap.Event = { 'start' : 'pswrap:start', 'stop' : 'pswrap:stop' } PSWrap.Animation.AutoOpenCloseCollection = Class.create( PSWrap.Animation.SequentialOpenCloseCollection, { initialize: function($super, container, collection_options, options, open_options, close_options){ $super(container, collection_options, options, open_options, close_options); this._time = this._collection_options['time'] || 3; this._stopafter = this._collection_options['stopafter'] || 0; this._interval = null; this._iterations = 0; this._direction = this._collection_options['direction'] || PSWrap.Animation.Directions.FORWARD; this._running = false; if( this._current_item ){ this._current_index = this._elements.indexOf(this._current_item); }else{ this._current_index = 0; } if( this._collection_options['autostart']){ this.start(); } /* need to look for start and stop links */ var elements = PSWrap.Animation.Binding.bindActionElements(this, '_bindElement', {}, ['stop', 'start'], ['stop', 'start']); this._start_elements = elements['start'] || []; this._stop_elements = elements['stop'] || []; }, previous: function($super){ var stop = arguments.length==2?arguments[1]:true; if (stop) { this.stop(); } $super(); }, next: function($super){ var stop = arguments.length==2?arguments[1]:true; if (stop) { this.stop(); } $super(); }, start: function(){ this.stop(); this._iterations = 0; /* * Firefox has a bug in setInterval which causes erratic behavior * when multiple intervals are set. for now use setTimeout * * this._interval = new PeriodicalExecuter(this.swap.bind(this), this._time);*/ this._running = true; this._interval = setTimeout( this.swap.bind(this), this._time * 1000); this.fire(PSWrap.Event.start, { elements:{ 'start': this._start_elements, 'stop': this._stop_elements } }); }, stop: function(){ try{ /* this._interval.stop(); * delete this._interval;*/ this._running = false; this.fire(PSWrap.Event.stop, { elements:{ 'start': this._start_elements, 'stop': this._stop_elements } }); }catch(ex){} }, swap: function(){ if (this._running) { if (this._direction < 0) { this.previous(false); } else { this.next(false); } this._iterations++; if (this._iterations == this._stopafter) { this.stop(); } else { this._interval = setTimeout(this.swap.bind(this), this._time * 1000); } } } }); PSWrap.Animation.MovableCollection = Class.create({ initialize: function(container, options){ this._container = $(container); this._options = Object.extend({ 'loop': true },options || {}); if( Object.isUndefined(this._options.direction) ){ this._options.direction = PSWrap.Animation.Directions.VERTICAL; } if( this._container && this._container.readAttribute('id')){ this._container.makePositioned().makeClipping(); this._original_offset = this._container.positionedOffset(); this._content = this._container.selectChildren('#' + this._container.readAttribute('id') + '_content')[0]; this._goto_elements = $H(); if( !this._content){ var content = new Element('div', {id: this._container.readAttribute('id') + '_content'}); this._container.selectChildren('.item').each( function(i){ content.insert(i.remove()); }); this._content = content; this._container.insert(this._content); } if( this._content ){ this._content.makePositioned(); this._children = this._content.selectChildren('.item'); var temp = this._content.getStyle('overflow'); this._content.setStyle({'overflow': 'auto'}); this._offsets = this._children.invoke('positionedOffset'); this._content.setStyle({'overflow': temp}); if (this._options.direction == PSWrap.Animation.Directions.HORIZONTAL) { this._children.invoke('setStyle', {'float': 'left'}); this._initWidth(1); } this._current_index = 0; this._element = new PSWrap.Animation.AnimatableElement( this._content, {}, {move: {effect: Effect.Move, options: {mode: 'absolute', transition: Effect.Transitions.sinoidal}}}); var c = this._container.readAttribute('id') + '_action_previous'; this._prev_elements = $$('.' + c); this._prev_elements.invoke('observe', 'click', this.prev.bindAsEventListener(this)); this._prev_elements.invoke('removeClassName', c); c = this._container.readAttribute('id') + '_action_next'; this._next_elements = $$('.' + c); this._next_elements.invoke('observe', 'click', this.next.bindAsEventListener(this)); this._next_elements.invoke('removeClassName', c); c = this._container.readAttribute('id') + '_display_current'; this._display_current_elements = $$( '.' + c); this._display_current_elements.invoke('removeClassName', c); c = this._container.readAttribute('id') + '_display_total'; this._display_total_elements = $$( '.' + c); this._display_total_elements.invoke('removeClassName', c); this._display_total_elements.invoke('update', this._children.length); this._display_current_elements.invoke('update', '1'); c = this._container.readAttribute('id') + '_goto'; var elements = $$('[class^=' + c + ']'); var reg = new RegExp('^' + c + '_(.+)$'); elements.each( function(elm){ $w(elm.className).each( function(c){ var result = reg.exec(c); if( result ){ if( /^[0-9]+$/.test(result[1])){ var index = parseInt(result[1]); this._addGoToElement(index, elm); elm.observe('click', this.goToItem.bindAsEventListener(this, index)); }else if(Object.isString(result[1])) { var item = this._content.down('#' + result[1]); if (item) { var index = this._children.indexOf(item); this._addGoToElement(index, elm); elm.observe('click', this.goToItem.bindAsEventListener(this, index)); } } elm.removeClassName(c); } }.bind(this)); }.bind(this)); }else{ return null; } }else{ return null; } }, _addGoToElement: function(index, e){ var curr = this._goto_elements.get(index); if( curr && Object.isArray(curr) ){ curr.push(e); }else{ curr = new Array(e); } this._goto_elements.set(index, curr); }, _initWidth: function( force ){ if (this._options.direction == PSWrap.Animation.Directions.HORIZONTAL && (force || this._content.getWidth() == 0)) { var temp = new Element('div', { style: 'float: left; width: 20px; border: 1px solid white;' }); this._content.setStyle({ width: '100000px' }); this._children.last().insert({ after: temp }); var width = temp.positionedOffset()[0]; temp.remove(); this._content.setStyle({ width: width + 'px' }); } }, goToItem: function(){ var item; if( arguments.length == 2 ){ try{ arguments[0].stop(); }catch(ex){} item = arguments[1]; }else{ item = arguments[0]; } if( /^[0-9]+$/.test(item) ){ this._moveToIndex(parseInt(item)); }else if( Object.isString(item) ){ item = this._content.down('#' + item); if( item ){ var index = this._children.indexOf(item); this._moveToIndex(parseInt(index)); } } }, _switchActive: function( old_index, new_index){ var elms = this._goto_elements.get(old_index); if( elms ){ //console.log(elms); elms.invoke('removeClassName', 'active'); } elms = this._goto_elements.get(new_index); if(elms){ //console.log(elms); elms.invoke('addClassName', 'active'); } }, _moveToIndex: function(new_index){ if( new_index != this._current_index){ if( this._options.direction == PSWrap.Animation.Directions.HORIZONTAL){ var new_x = this._offsets[new_index][0]; if( new_x == 0 && new_index != 0){ this._offsets[new_index] = this._children[new_index].positionedOffset(); new_x = this._offsets[new_index][0]; } //console.log(('new x: ' + new_x + ', ' + this._container.getWidth())); //if( this._content.getWidth() - new_x >= this._container.getWidth()){ var options = Object.extend( this._options, {x: -new_x}); this._element.move(options); this._switchActive(this._current_index, new_index); this._current_index = new_index; //} }else{ var new_y = this._offsets[new_index][1]; if( new_y == 0 && new_index!= 0){ this._offsets[new_index] = this._children[new_index].positionedOffset(); new_y = this._offsets[new_index][1]; } //if( this._content.getHeight() - new_y >= this._container.getHeight() ){ var options = Object.extend( this._options, {y: -new_y}); this._element.move(options); this._switchActive(this._current_index, new_index); this._current_index = new_index; //} } this._display_current_elements.invoke('update', this._current_index + 1); } }, next: function(e){ try{ e.stop(); }catch(ex){} this._initWidth(); var new_index = this._current_index + 1; if( new_index >= this._offsets.length){ if( this._options.loop ){ new_index = 0; }else{ new_index = this._offsets.length - 1; } } this._moveToIndex(new_index); }, prev: function(e){ try{ e.stop(); }catch(ex){} this._initWidth(); var new_index = this._current_index - 1; if( new_index < 0){ if( this._options.loop ){ new_index = this._offsets.length - 1; }else{ new_index = 0; } } this._moveToIndex(new_index); } }); /* based on code @ http://www.webtoolkit.info/ajax-file-upload.html */ PSWrap.AjaxUpload = Class.create({ initialize: function(elm, opts){ this._input_element = $(elm); this._element = this._input_element.up('form'); if( this._element && this._input_element.readAttribute('id')){ opts = opts || {}; this.onStart = Object.isFunction(opts['onStart'])?opts['onStart']:Prototype.emptyFunction; this.onComplete = Object.isFunction(opts['onComplete'])?opts['onComplete']:Prototype.emptyFunction; this._frame_action = !Object.isUndefined(opts['action'])?opts['action']:''; this._frame_id = this._input_element.readAttribute('id') + '_iframe'; if ($(this._frame_id)) { var i = 1; var id = this._frame_id + i; while( $(id) ){ i++; id = this._frame_id + i; } this._frame_id = id; } this._frame = new Element('iframe', { id: this._frame_id, name: this._frame_id, src: 'about:blank', style: 'display: none' }); $$('body')[0].appendChild(this._frame); this._frame.observe('load', this.load.bindAsEventListener(this)); }else{ return false; } }, submit: function(e){ if (this._input_element.value != '') { var old_action, old_target; // grab current action old_action = this._element.readAttribute('action'); if (this._frame_action) { // if we have an alternate action, set it this._element.setAttribute('action', this._frame_action); } // grab old target old_target = this._element.readAttribute('target'); // set hidden iframe target this._element.setAttribute('target', this._frame_id); // execute before start this.onStart(); // submit the form this._element.submit(); this._input_element.setAttribute('disabled', 'disabled'); // reset originals this._element.setAttribute('action', old_action); this._element.setAttribute('target', old_target); } }, load: function(e){ this._input_element.value = ''; this._input_element.writeAttribute('disabled', null); if (this._frame.contentDocument) { var d = this._frame.contentDocument; } else if (this._frame.contentWindow) { var d = this._frame.contentWindow.document; } else { var d = window.frames[this._frame.readAttribute('id')].document; } if (d.location.href == "about:blank") { return; } this.onComplete(d.body.innerHTML, this._input_element); } }); PSWrap.FormControls = {}; PSWrap.FormControls.SelectBase = Class.create({ initialize: function(elm, options){ elm = $(elm); if( elm && elm.tagName == 'SELECT'){ this._elm = elm; this._data = $H(); this._select_options = $H(); this._currently_selected = []; this._num_selected = 0; this._options = Object.extend({ max_selected: 0, min_selected: 0 }, options || {}); this._multiple = this._elm.readAttribute('multiple'); if( !this._multiple ){ this.options.max_selected = 1; this.options.min_selected = 1; } if( this._elm.options.length ){ $A(this._elm.options).each( function(o){ this._select_options.set(o.value, {option: $(o)}); }, this); } }else{ throw "PSWrap.FormControls.SelectBase: element is not a select object"; } }, addOption: function( text, value, selected){ /* * this doesn't work in IE: * var opt = new Option( text, value, false, selected); */ var att = { 'value': value } if( selected ) att.selected = 'selected'; var opt = new Element('option', att).update(text); if(opt){ this._elm.insert(opt); opt = this._select_options.set( value, {option: opt}); if( selected ){ this._selectOption(opt); } } }, removeOption: function( value ){ var opt = this.getOption(value); if( opt ){ if( opt.option.selected ){ this._deselectOption(opt); } opt.option.remove(); this._select_options.unset( value); } }, getOption: function( value ){ return this._select_options.get(value); }, selectOption: function( value ){ var selected = true; if( arguments.length == 2 ) selected = arguments[1]; var opt = this._select_options.get(value); if( opt.option.selected != selected ){ if( selected ){ if( this._multiple ){ if (!this._max_selected || this._num_selected < this._max_selected) { this._selectOption(opt); } }else{ var cs = this._currently_selected.pop(); if( cs ){ this._deselectOption( this._select_options.get(cs) ); } this._selectOption( opt ); } }else{ this._deselectOption(opt); } } }, _selectOption: function(opt){ opt.option.selected = true; this._currently_selected.push( opt.option.value ); }, _deselectOption: function( opt ){ opt.option.selected = false; this._currently_selected = this._currently_selected.without(opt.option.value); }, identify: function(){ return this._elm.identify(); } }); PSWrap.FormControls.SingleContainerMultiselects = { _items: new Hash(), add: function(elm){ elm = $(elm); if( elm ){ PSWrap.FormControls.SingleContainerMultiselects._items.set(elm.identify(), elm); } }, remove: function( id ){ if( Object.isElement(id) ){ id = id.identify(); } var r = PSWrap.FormControls.SingleContainerMultiselects._items.get(id); PSWrap.FormControls.SingleContainerMultiselects._items.set(id, null); return r; }, get: function(id){ return PSWrap.FormControls.SingleContainerMultiselects._items.get(id); }, items: function(){ return PSWrap.FormControls.SingleContainerMultiselects._items.values(); } }; PSWrap.FormControls.SingleContainerMultiselect = Class.create( PSWrap.FormControls.SelectBase, { initialize: function( $super, elm, container_template, item_template, options){ options = Object.extend({ selected_class: 'selected', prevent_default: false, select_element: null }, options || {}); elm.setAttribute('multiple', 'multiple'); $super( elm, options); if( this._options.select_event && this._options.select_event == this._options.deselect_event ){ this._options.toggle_event = this._options.select_event; this._options.select_event = this._options.deselect_event = null; } this._elements = $H(); if( container_template instanceof Template && item_template instanceof Template){ this._elm.hide(); this._id = this._elm.identify() + '_single_container_multiselect'; var container = container_template.evaluate({'id': this._id}); this._elm.insert({'after': container}); (function(){ this._container = $(this._id); if( !container ) throw 'PSWrap.FormControls.SingleContainerMultiselect: container not found, please make sure the specified template is properly setting the id -> id="#{id}"'; this._item_template = item_template; if (this._elm.options.length) { $A(this._elm.options).each(function(o){ this.addOptionElement(o.text, o.value, o.selected); }, this); } }).bind(this).defer(); PSWrap.FormControls.SingleContainerMultiselects.add(this); }else{ throw 'PSWrap.FormControls.SingleContainerMultiselect: container_template and item_template must be of type Template'; } }, addOption: function( $super, text, value, selected){ $super(text, value, selected); this.addOptionElement(text, value, selected); }, addOptionElement: function(text, value, selected){ var id = this._id + '_' + value; var data = { 'text': text, 'value': value, 'id': id, 'selected': selected }; var option = this._item_template.evaluate(data); this._container.insert(option); this._addOptionElement.bind(this).defer(id, value, selected); }, _addOptionElement: function(id, value, selected){ //var option = this._container.childElements().last(); var option = $(id); var elm = null; if( this._options.select_element ){ elm = option.down(this._options.select_element); } elm = elm || option; if( this._options.toggle_event ){ try{ elm.observe(this._options.toggle_event, this.toggleOptionEventWrapper.bindAsEventListener(this, option, value)); }catch(ex){} }else{ if( this._options.select_event ){ try{ elm.observe(this._options.select_event, this.selectOptionEventWrapper.bindAsEventListener(this, option, value, 1)); }catch(ex){} } if( this._options.deselect_event ){ try{ elm.observe(this._options.deselect_event, this.selectOptionEventWrapper.bindAsEventListener(this, option, value, 0)); }catch(ex){} } } this._elements.set( value, option); this._elm.fire('pswrap:optionadded', {element: option, option: this._select_options.get(value), select: this}); if( selected ){ this.selectOption(option, value, selected); } }, removeOption: function( $super, value){ $super(value); var del = this._elements.get(value); if( del ) del.remove(); this._elements.unset(value); this._elm.fire('pswrap:optionremoved', {element: del, option: null, select: this}); this._elm.hide(); }, toggleOptionEventWrapper: function( e, option, value){ if( this._options.prevent_default ){ try{ e.stop(); }catch(ex){} } var selected = !this._select_options.get(value).option.selected; this.selectOption( option, value, selected); }, selectOptionEventWrapper: function(e, option, value, selected){ if ( this._options.prevent_default ) { try { e.stop(); } catch (ex) {} } this.selectOption(option, value, selected); }, selectOption: function($super, option, value, selected){ $super(value, selected); if( selected ){ option.addClassName(this._options.selected_class); this._elm.fire('pswrap:optionselected', {'element': option, 'option': this._select_options.get(value), 'select': this}); }else{ option.removeClassName(this._options.selected_class); this._elm.fire('pswrap:optiondeselected', {'element': option, 'option': this._select_options.get(value), 'select': this}); } } }); PSWrap.FormControls.DragDropMultiselects = { _items: new Hash(), add: function(elm){ elm = $(elm); if( elm ){ PSWrap.FormControls.DragDropMultiselects._items.set(elm.identify(), elm); } }, remove: function( id ){ if( Object.isElement(id) ){ id = id.identify(); } var r = PSWrap.FormControls.DragDropMultiselects._items.get(id); PSWrap.FormControls.DragDropMultiselects._items.set(id, null); return r; }, get: function(id){ return PSWrap.FormControls.DragDropMultiselects._items.get(id); } }; PSWrap.FormControls.DragDropMultiselect = Class.create({ initialize: function(elm, template, options){ this._selection_element = $(elm); if( this._selection_element && this._selection_element.tagName == 'SELECT'){ if( !(template instanceof Template) ){ throw "PSWrap.FormControls.DrapDropMultiselect initialize: template is not of type Template."; return null; }else{ this._template = template; } this._options = $A(); this._selected = $A(); this._options_by_value = $H(); this._currently_selected = 0; this._draggables = $H(); this._control_options = Object.extend({ remove_event: 'dblclick', add_event: 'dblclick', options_title: null, selected_title: null, onInit: Prototype.emptyFunction, onOptionChange: Prototype.EmptyFunction, onOptionAdd: Prototype.emptyFunction, onOptionRemove: Prototype.emptyFunction, onSelectedChange: Prototype.emptyFunction, onSelectedAdd: Prototype.emptyFunction, onSelectedRemove: Prototype.emptyFunction, max_selected: 0 }, options || {}); this._selection_element.hide(); this._id = this._selection_element.identify(); var container = new Element('div', {'class': 'drag_drop_multiselect'}); var otitle = this._control_options.options_title; if( otitle ){ otitle = new Element('div', {'class': 'options_title'}).update(otitle); } this._options_container = new Element('div', { 'id': this._id + '_options', 'class': 'options' }); var stitle = this._control_options.selected_title; if( stitle ){ stitle = new Element('div', {'class': 'selected_title'}).update(stitle); } this._selected_container = new Element('div', { 'id': this._id + '_selected', 'class': 'selected' }); if( stitle ){ container.insert(stitle); } container.insert(this._selected_container); if( otitle ){ container.insert(otitle); } container.insert(this._options_container); this._selection_element.insert({after: container}); $A(this._selection_element.options).each( function(o){ this._addItem( o.value, o.text, false); if( o.selected ){ this._addItem( o.value, o.text, true); } this._options_by_value.set( o.value, o); }.bind(this)); Droppables.add(this._selected_container, { containment: this._id + '_options', onDrop: this.select.bind(this) }); PSWrap.FormControls.DragDropMultiselects.add(this); this._fireCallback('onInit', this); }else{ throw "PSWrap.FormControls.DragDropMultiselect.initialize: Invalid select element"; } }, identify: function(){ return this._id; }, getID: function(){ return this._id; }, getSelectedContainer: function(){ return this._selected_container; }, getSelectedItems: function(){ return this._selected; }, getOptionsContainer: function(){ return this._options_container; }, getOptionsItems: function(){ return this._options; }, select: function(elm){ var val = elm.identify().split('_').last(); if( val ){ if(this._control_options['max_selected'] == 0 || this._currently_selected < this._control_options['max_selected']) { var o = this._options_by_value.get(val); try { if (!o.selected) { o.selected = true; var img = this._addItem(val, elm.src, true); this._currently_selected++; this._fireCallback('onSelectedAdd', img, val, this._selected_container); this._fireCallback('onSelectedChange', img, val, this._selected_container); } } catch (e) { } } } }, deselect: function(elm){ var val = elm.identify().split('_').last(); if( val ){ var o = this._options_by_value.get(val); try{ if( o.selected ){ o.selected = false; elm = this._removeItem( elm, true); this._currently_selected--; this._fireCallback('onSelectedRemove', elm, val, this._selected_container); this._fireCallback('onSelectedChange', elm, val, this._selected_container); } }catch(e){} } }, addElement: function( val, uri, selected){ if (!this._options_by_value.get(val)) { if( Object.isUndefined(selected)){ selected = false; } var o = this._addOption(val, uri, selected); var elm = this._addItem(val, uri, selected); this._options_by_value.set(o.value, o); //console.log('firing callbacks!'); this._fireCallback('onOptionAdd', elm, val, this._options_container); this._fireCallback('onOptionChange', elm, val, this._options_container); //console.log('fired callbacks'); } }, removeElement: function(elm){ elm = $(elm); var id = elm.identify().split('_').last(); this.removeElementById(id, elm); }, removeElementById: function(id, elm){ var selm = this._getSelectedItemFromValue(id); if( selm ){ this.deselect(selm); } if( !elm ){ elm = this._getOptionItemFromValue(id); } this._removeOption( elm, id); }, _getSelectedItemFromValue: function(v){ return this._getItemFromValue( v, this._selected); }, _getOptionItemFromValue: function(v){ return this._getItemFromValue( v, this._options); }, _getItemFromValue: function( v, arr){ var l = arr.length; for( var i=0; i