/* * Mabtabulous! Simple tabs using Mootools * Michael 'Iggy' Rudolph * http://www.sensomedia.de/ * http://creativecommons.org/licenses/by-sa/2.5/ * * originally based on: * Fabtabulous! Simple tabs using Prototype * http://tetlaw.id.au/view/blog/fabtabulous-simple-tabs-using-prototype/ * Andrew Tetlaw * version 1.1 2006-05-06 * http://creativecommons.org/licenses/by-sa/2.5/ */ var Mootabs = new Class({ initialize: function(element, options) { this.options = Object.extend({ changeTransition: Fx.Transitions.bounceOut }, options || {}); this.el = $(element); this.id = element; this.tabs = $$('#' + this.id + ' li a'); if(this.tabs.length) { this.tabs.each(function(tab) { var linkName = tab.getProperty('href').split('#')[1]; tab.setProperty('rel', linkName ); tab.href = 'javascript:void(0);'; tab.addEvent('click', function() { this.activate(tab); }.bind(this)); }.bind(this)); this.activate(this.getInitialTab()); } }, activate: function(tab) { var linkName = tab.getProperty('rel'); this.tabs.each(function(tab) { tab.getParent('li').removeClass('active-tab'); $(tab.getProperty('rel')).removeClass('active-tab-body'); }); tab.getParent('li').addClass('active-tab'); $(linkName).addClass('active-tab-body'); }, getInitialTab : function() { if(document.location.href.match(/#(\w.+)/)) { var loc = RegExp.$1; var elm = this.tabs.find(function(value) { return value.href.match(/#(\w.+)/)[1] == loc; }); return elm || this.tabs[0]; } else { return this.tabs[0]; } } }); window.addEvent('load', function() {var Tabs = new Mootabs('tabs' );}); /* * Validation, the mootools way! * adopted to powermail by: * Michael 'Iggy' Rudolph * http://www.sensomedia.de/ * * originally based on: * Clientcide libraries * License: * http://www.clientcide.com/wiki/cnet-libraries#license */ /* Script: Element.Measure.js Extends the Element native object to include methods useful in measuring dimensions. License: http://www.clientcide.com/wiki/cnet-libraries#license */ Element.implement({ // Daniel Steigerwald - MIT licence measure: function(fn) { var restore = this.expose(); var result = fn.apply(this); restore(); return result; }, expose: function(){ if (this.getStyle('display') != 'none') return $empty; var before = {}; var styles = { visibility: 'hidden', display: 'block', position:'absolute' }; //use this method instead of getStyles $each(styles, function(value, style){ before[style] = this.style[style]||''; }, this); //this.getStyles('visibility', 'display', 'position'); this.setStyles(styles); return (function(){ this.setStyles(before); }).bind(this); }, getDimensions: function(options) { options = $merge({computeSize: false},options); var dim = {}; function getSize(el, options){ return (options.computeSize)?el.getComputedSize(options):el.getSize(); }; if(this.getStyle('display') == 'none'){ var restore = this.expose(); dim = getSize(this, options); //works now, because the display isn't none restore(); //put it back where it was } else { try { //safari sometimes crashes here, so catch it dim = getSize(this, options); }catch(e){} } return $chk(dim.x)?$extend(dim, {width: dim.x, height: dim.y}):$extend(dim, {x: dim.width, y: dim.height}); }, getComputedSize: function(options){ options = $merge({ styles: ['padding','border'], plains: {height: ['top','bottom'], width: ['left','right']}, mode: 'both' }, options); var size = {width: 0,height: 0}; switch (options.mode){ case 'vertical': delete size.width; delete options.plains.width; break; case 'horizontal': delete size.height; delete options.plains.height; break; }; var getStyles = []; //this function might be useful in other places; perhaps it should be outside this function? $each(options.plains, function(plain, key){ plain.each(function(edge){ options.styles.each(function(style){ getStyles.push((style=="border")?style+'-'+edge+'-'+'width':style+'-'+edge); }); }); }); var styles = this.getStyles.apply(this, getStyles); var subtracted = []; $each(options.plains, function(plain, key){ //keys: width, height, plains: ['left','right'], ['top','bottom'] size['total'+key.capitalize()] = 0; size['computed'+key.capitalize()] = 0; plain.each(function(edge){ //top, left, right, bottom size['computed'+edge.capitalize()] = 0; getStyles.each(function(style,i){ //padding, border, etc. //'padding-left'.test('left') size['totalWidth'] = size['width']+[padding-left] if(style.test(edge)) { styles[style] = styles[style].toInt(); //styles['padding-left'] = 5; if(isNaN(styles[style]))styles[style]=0; size['total'+key.capitalize()] = size['total'+key.capitalize()]+styles[style]; size['computed'+edge.capitalize()] = size['computed'+edge.capitalize()]+styles[style]; } //if width != width (so, padding-left, for instance), then subtract that from the total if(style.test(edge) && key!=style && (style.test('border') || style.test('padding')) && !subtracted.contains(style)) { subtracted.push(style); size['computed'+key.capitalize()] = size['computed'+key.capitalize()]-styles[style]; } }); }); }); if($chk(size.width)) { size.width = size.width+this.offsetWidth+size.computedWidth; size.totalWidth = size.width + size.totalWidth; delete size.computedWidth; } if($chk(size.height)) { size.height = size.height+this.offsetHeight+size.computedHeight; size.totalHeight = size.height + size.totalHeight; delete size.computedHeight; } return $extend(styles, size); } }); /* Script: Element.Shortcuts.js Extends the Element native object to include some shortcut methods. License: http://www.clientcide.com/wiki/cnet-libraries#license */ Element.implement({ isVisible: function() { return this.getStyle('display') != 'none'; }, toggle: function() { return this[this.isVisible() ? 'hide' : 'show'](); }, hide: function() { var d; try { //IE fails here if the element is not in the dom if ('none' != this.getStyle('display')) d = this.getStyle('display'); } catch(e){} this.store('originalDisplay', d||'block'); this.setStyle('display','none'); return this; }, show: function(display) { original = this.retrieve('originalDisplay')?this.retrieve('originalDisplay'):this.get('originalDisplay'); this.setStyle('display',(display || original || 'block')); return this; }, swapClass: function(remove, add) { return this.removeClass(remove).addClass(add); }, //TODO //DO NOT USE THIS METHOD //it is temporary, as Mootools 1.1 will negate its requirement fxOpacityOk: function(){ return !Browser.Engine.trident4; } }); /* Script: Fx.Reveal.js Defines Fx.Reveal, a class that shows and hides elements with a transition. License: http://www.clientcide.com/wiki/cnet-libraries#license */ Fx.Reveal = new Class({ Extends: Fx.Morph, options: { styles: ['padding','border','margin'], transitionOpacity: true, mode:'vertical', heightOverride: null, widthOverride: null }, dissolve: function(){ try { if(!this.hiding && !this.showing) { if(this.element.getStyle('display') != 'none'){ this.hiding = true; this.showing = false; this.hidden = true; var startStyles = this.element.getComputedSize({ styles: this.options.styles, mode: this.options.mode }); var setToAuto = this.element.style.height === ""||this.element.style.height=="auto"; this.element.setStyle('display', 'block'); if (this.element.fxOpacityOk() && this.options.transitionOpacity) startStyles.opacity = 1; var zero = {}; $each(startStyles, function(style, name){ zero[name] = [style, 0]; }, this); var overflowBefore = this.element.getStyle('overflow'); this.element.setStyle('overflow', 'hidden'); //put the final fx method at the front of the chain this.$chain = this.$chain || []; this.$chain.unshift(function(){ if(this.hidden) { this.hiding = false; $each(startStyles, function(style, name) { startStyles[name] = style; }, this); this.element.setStyles($merge({display: 'none', overflow: overflowBefore}, startStyles)); if (setToAuto) this.element.setStyle('height', 'auto'); } this.fireEvent('onHide', this.element); this.callChain(); }.bind(this)); this.start(zero); } else { this.callChain.delay(10, this); this.fireEvent('onComplete', this.element); this.fireEvent('onHide', this.element); } } else if (this.options.link == "chain") { this.chain(this.dissolve.bind(this)); } else if (this.options.link == "cancel" && !this.hiding) { this.cancel(); this.dissolve(); } } catch(e) { this.hiding = false; this.element.hide(); this.callChain.delay(10, this); this.fireEvent('onComplete', this.element); this.fireEvent('onHide', this.element); } return this; }, reveal: function(){ try { if(!this.showing && !this.hiding) { if(this.element.getStyle('display') == "none" || this.element.getStyle('visiblity') == "hidden" || this.element.getStyle('opacity')==0){ this.showing = true; this.hiding = false; this.hidden = false; //toggle display, but hide it var before = this.element.getStyles('visibility', 'display', 'position'); this.element.setStyles({ visibility: 'hidden', display: 'block', position:'absolute' }); var setToAuto = this.element.style.height === ""||this.element.style.height=="auto"; //enable opacity effects if(this.element.fxOpacityOk() && this.options.transitionOpacity) this.element.setStyle('opacity',0); //create the styles for the opened/visible state var startStyles = this.element.getComputedSize({ styles: this.options.styles, mode: this.options.mode }); //reset the styles back to hidden now this.element.setStyles(before); $each(startStyles, function(style, name) { startStyles[name] = style; }, this); //if we're overridding height/width if($chk(this.options.heightOverride)) startStyles['height'] = this.options.heightOverride.toInt(); if($chk(this.options.widthOverride)) startStyles['width'] = this.options.widthOverride.toInt(); if(this.element.fxOpacityOk() && this.options.transitionOpacity) startStyles.opacity = 1; //create the zero state for the beginning of the transition var zero = { height: 0, display: 'block' }; $each(startStyles, function(style, name){ zero[name] = 0 }, this); var overflowBefore = this.element.getStyle('overflow'); //set to zero this.element.setStyles($merge(zero, {overflow: 'hidden'})); //start the effect this.start(startStyles); if (!this.$chain) this.$chain = []; this.$chain.unshift(function(){ this.element.setStyle('overflow', overflowBefore); if (!this.options.heightOverride && setToAuto) { if (["vertical", "both"].contains(this.options.mode)) this.element.setStyle('height', 'auto'); if (["width", "both"].contains(this.options.mode)) this.element.setStyle('width', 'auto'); } if(!this.hidden) this.showing = false; this.callChain(); this.fireEvent('onShow', this.element); }.bind(this)); } else { this.callChain(); this.fireEvent('onComplete', this.element); this.fireEvent('onShow', this.element); } } else if (this.options.link == "chain") { this.chain(this.reveal.bind(this)); } else if (this.options.link == "cancel" && !this.showing) { this.cancel(); this.reveal(); } } catch(e) { this.element.setStyles({ display: 'block', visiblity: 'visible', opacity: 1 }); this.showing = false; this.callChain.delay(10, this); this.fireEvent('onComplete', this.element); this.fireEvent('onShow', this.element); } return this; }, toggle: function(){ try { if(this.element.getStyle('display') == "none" || this.element.getStyle('visiblity') == "hidden" || this.element.getStyle('opacity')==0){ this.reveal(); } else { this.dissolve(); } } catch(e) { this.show(); } return this; } }); Element.Properties.reveal = { set: function(options){ var reveal = this.retrieve('reveal'); if (reveal) reveal.cancel(); return this.eliminate('reveal').store('reveal:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('reveal')){ if (options || !this.retrieve('reveal:options')) this.set('reveal', options); this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options'))); } return this.retrieve('reveal'); } }; Element.Properties.dissolve = Element.Properties.reveal; Element.implement({ reveal: function(options){ this.get('reveal', options).reveal(); return this; }, dissolve: function(options){ this.get('reveal', options).dissolve(); return this; }, nix: function() { var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type}); this.get('reveal', params.options).dissolve().chain(function(){ this[params.destroy?'destroy':'dispose'](); }.bind(this)); return this; } }); /* Script: FormValidator.js A css-class based form validation system. License: http://www.clientcide.com/wiki/cnet-libraries#license */ var InputValidator = new Class({ Implements: [Options], options: { errorMsg: 'Validation failed.', test: function(field){return true;} }, initialize: function(className, options){ this.setOptions(options); this.className = className; }, test: function(field, props){ if($(field)) return this.options.test($(field), props||this.getProps(field)); else return false; }, getError: function(field, props){ var err = this.options.errorMsg; if($type(err) == "function") err = err($(field), props||this.getProps(field)); return err; }, getProps: function(field){ if (!$(field)) return {}; return field.get('validatorProps'); } }); Element.Properties.validatorProps = { set: function(props){ return this.eliminate('validatorProps').store('validatorProps', props); }, get: function(props){ if (props) this.set(props); if (this.retrieve('validatorProps')) return this.retrieve('validatorProps'); if(this.getProperty('validatorProps')){ try { this.store('validatorProps', JSON.decode(this.getProperty('validatorProps'))); }catch(e){ return {}} } else { var vals = this.get('class').split(' ').filter(function(cls) { //return cls.match(/[a-z].*\[\'.*\'\]/ig); return cls.test(':'); }); if (!vals.length) { this.store('validatorProps', {}); } else { props = {}; vals.each(function(cls){ var split = cls.indexOf(':'); props[cls.substring(0, split)] = JSON.decode(cls.substring(split+1, cls.length)) }); this.store('validatorProps', props); } } return this.retrieve('validatorProps'); } }; var Validation = new Class({ Implements:[Options, Events], options: { fieldSelectors:"input, select, textarea", ignoreHidden: true, useTitles:false, evaluateOnSubmit:true, evaluateFieldsOnBlur: true, evaluateFieldsOnChange: true, serial: true, stopOnFailure: true, scrollToErrorsOnSubmit: true }, initialize: function(form, options){ this.setOptions(options); this.form = $(form); this.form.store('validator', this); if(this.options.evaluateOnSubmit) this.form.addEvent('submit', this.onSubmit.bind(this)); if(this.options.evaluateFieldsOnBlur) this.watchFields(); }, toElement: function(){ return this.form; }, getFields: function(){ return this.fields = this.form.getElements(this.options.fieldSelectors); }, watchFields: function(){ this.getFields().each(function(el){ el.addEvent('blur', this.validateField.pass([el, false], this)); if(this.options.evaluateFieldsOnChange) el.addEvent('change', this.validateField.pass([el, true], this)); }, this); }, onSubmit: function(event){ if(!this.validate(event) && event) event.preventDefault(); else this.reset(); }, reset: function() { this.getFields().each(this.resetField, this); return this; }, validate: function(event) { var result = this.getFields().map(function(field) { return this.validateField(field, true); }, this).every(function(v){ return v;}); this.fireEvent('onFormValidate', [result, this.form, event]); if (this.options.stopOnFailure && !result && event) event.preventDefault(); if (this.options.scrollToErrorsOnSubmit && !result) { var par = this.form.getParent(); var failed = this.form.getElement('.validation-failed'); } return result; }, validateField: function(field, force){ if (this.paused) return true; field = $(field); var passed = !field.hasClass('validation-failed'); var failed; if (this.options.serial && !force) { failed = this.form.getElement('.validation-failed'); } if(field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){ var validators = field.className.split(" ").some(function(cn){ return this.getValidator(cn); }, this); var validatorsFailed = []; field.className.split(" ").each(function(className){ if (!this.test(className,field)) validatorsFailed.include(className); }, this); passed = validatorsFailed.length === 0; if (validators){ if(passed) { field.addClass('validation-passed').removeClass('validation-failed'); this.fireEvent('onElementPass', field); } else { field.addClass('validation-failed').removeClass('validation-passed'); this.fireEvent('onElementFail', [field, failed]); } } } return passed; }, getPropName: function(className){ return 'advice'+className; }, test: function(className, field){ field = $(field); if(field.hasClass('ignoreValidation')) return true; var isValid = true; var validator = this.getValidator(className); if(validator && this.isVisible(field)) { isValid = validator.test(field); if(!isValid && validator.getError(field)){ var advice = this.makeAdvice(className, field, validator.getError(field)); this.insertAdvice(advice, field); this.showAdvice(className, field); } else this.hideAdvice(className, field); this.fireEvent('onElementValidate', [isValid, field, className]); } return isValid; }, getAllAdviceMessages: function(field, force) { var advice = []; if (field.hasClass('ignoreValidation') && !force) return advice; var validators = field.className.split(" ").some(function(cn){ var validator = this.getValidator(cn); if (!validator) return; advice.push({ message: validator.getError(field), passed: validator.test(), validator: validator }); }, this); return advice; }, showAdvice: function(className, field){ var advice = this.getAdvice(className, field); if(advice && !field.retrieve(this.getPropName(className)) && (advice.getStyle('display') == "none" || advice.getStyle('visiblity') == "hidden" || advice.getStyle('opacity')==0)){ field.store(this.getPropName(className), true); if(advice.reveal) {advice.reveal( {duration:'short'} );} else advice.setStyle('display','block'); } }, hideAdvice: function(className, field){ var advice = this.getAdvice(className, field); if(advice && field.retrieve(this.getPropName(className))) { field.store(this.getPropName(className), false); //if Fx.Reveal.js is present, transition the advice out if(advice.dissolve) advice.dissolve( {duration:'short'} ); else advice.setStyle('display','none'); } }, isVisible : function(field) { if (!this.options.ignoreHidden) return true; while(field != document.body) { if($(field).getStyle('display') == "none") return false; field = field.getParent(); } return true; }, getAdvice: function(className, field) { return field.retrieve('advice-'+className); }, makeAdvice: function(className, field, error){ var errorMsg = (this.options.useTitles) ? field.title || error:error; var advice = this.getAdvice(className, field); if(!advice){ advice = new Element('div', { text: errorMsg, styles: { display: 'none' }, id: 'advice-'+className+'-'+this.getFieldId(field) }).addClass('powermail_mandatory_js'); } else{ advice.set('html', errorMsg); } field.store('advice-'+className, advice); return advice; }, insertAdvice: function(advice, field){ //Check for error position prop var props = field.get('validatorProps'); //Build advice if (!props.msgPos || !$(props.msgPos)) { switch (field.type.toLowerCase()) { case 'radio': var p = field.getParent().adopt(advice); break; default: advice.inject($(field), 'after'); }; } else { $(props.msgPos).grab(advice); } }, getFieldId : function(field) { return field.id ? field.id : field.id = "input_"+field.name; }, resetField: function(field) { field = $(field); if(field) { var cn = field.className.split(" "); cn.each(function(className) { var prop = this.getPropName(className); if(field.retrieve(prop)) this.hideAdvice(className, field); field.removeClass('validation-failed'); field.removeClass('validation-passed'); }, this); } return this; }, stop: function(){ this.paused = true; return this; }, start: function(){ this.paused = false; return this; }, ignoreField: function(field){ field = $(field); if(field){ this.enforceField(field); field.addClass('ignoreValidation'); } return this; }, enforceField: function(field){ field = $(field); if(field) field.removeClass('ignoreValidation'); return this; } }); Validation.adders = { validators:{}, add : function(className, options) { this.validators[className] = new InputValidator(className, options); //if this is a class //extend these validators into it if(!this.initialize){ this.implement({ validators: this.validators }); } }, addAllThese : function(validators) { $A(validators).each(function(validator) { this.add(validator[0], validator[1]); }, this); }, getValidator: function(className){ return this.validators[className.split(":")[0]]; } }; $extend(Validation, Validation.adders); Validation.implement(Validation.adders); Validation.add('IsEmpty', { errorMsg: false, test: function(element) { if(element.type == "select-one"||element.type == "select") return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != ""); else return ((element.get('value') == null) || (element.get('value').length == 0)); } }); Validation.addAllThese([ ['required', { errorMsg: 'This is a required field', test: function(element) { return !Validation.getValidator('IsEmpty').test(element); } }], ['validate-number', { errorMsg: 'Please enter a valid number in this field', test: function(element) { return Validation.getValidator('IsEmpty').test(element) || (!isNaN(element.get('value')) && !/^\s+$/.test(element.get('value'))); } }], ['validate-digits', { errorMsg: 'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas', test: function(element) { return Validation.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value'))); } }], ['validate-alpha', { errorMsg: 'Please use letters only (a-z) in this field', test: function (element) { return Validation.getValidator('IsEmpty').test(element) || /^[a-zA-Z]+$/.test(element.get('value')) } }], ['validate-alphanum', { errorMsg: 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed', test: function(element) { return Validation.getValidator('IsEmpty').test(element) || !/\W/.test(element.get('value')) } }], ['validate-date', { errorMsg: 'Please enter a valid date', test: function(element, props) { if(Validation.getValidator('IsEmpty').test(element)) return true; if (Date.parse) { var format = props.dateFormat || "%x"; var d = Date.parse(element.get('value')); var formatted = d.format(format); if (formatted != "invalid date") element.set('value', formatted); return !isNaN(d); } else { var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/; if(!regex.test(element.get('value'))) return false; var d = new Date(element.get('value').replace(regex, '$1/$2/$3')); return (parseInt(RegExp.$1, 10) == (1+d.getMonth())) && (parseInt(RegExp.$2, 10) == d.getDate()) && (parseInt(RegExp.$3, 10) == d.getFullYear() ); } } }], ['validate-email', { errorMsg: 'Please enter a valid email address (test@test.com)', test: function (element) { return Validation.getValidator('IsEmpty').test(element) || /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(element.get('value')); } }], ['validate-url', { errorMsg: 'Please enter a valid URL (http://www.test.com)', test: function (element) { return Validation.getValidator('IsEmpty').test(element) || /^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(element.get('value')); } }], ['validate-date-au', { errorMsg: 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006', test: function(element) { if(Validation.getValidator('IsEmpty').test(element)) return true; var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/; if(!regex.test(element.get('value'))) return false; var d = new Date(element.get('value').replace(regex, '$2/$1/$3')); return ( parseInt(RegExp.$2, 10) == (1+d.getMonth()) ) && (parseInt(RegExp.$1, 10) == d.getDate()) && (parseInt(RegExp.$3, 10) == d.getFullYear() ); } }], ['validate-currency-dollar', { errorMsg: 'Please enter a valid $ amount. For example $100.00', test: function(element) { // [$]1[##][,###]+[.##] // [$]1###+[.##] // [$]0.## // [$].## return Validation.getValidator('IsEmpty').test(element) || /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(element.get('value')); } }], ['validate-one-required', { errorMsg: 'Please make a selection', test: function (element) { var p = element.parentNode; p = p.parentNode; // enable parent DIV with parent DIV - Powermail Fix #2263 return p.getElements('input').some(function(el) { if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked'); return el.get('value'); }); } }], ['validate-selection', { errorMsg: 'Please make a selection', test: function(element){ return element.options ? element.selectedIndex > 0 : !Validation.get('IsEmpty').test(element.get('value')); } }] ]); Element.Properties.validator = { set: function(options){ var validator = this.retrieve('validator'); if (validator) validator.setOptions(options); return this.store('validator:options'); }, get: function(options){ if (options || !this.retrieve('validator')){ if (options || !this.retrieve('validator:options')) this.set('validator', options); this.store('validator', new Validation(this, this.retrieve('validator:options'))); } return this.retrieve('validator'); } }; Element.implement({ validate: function(options){ this.set('validator', options); return this.get('validator', options).validate(); } });