[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
simpeg
/
zapatec
/
zptabs
/
utils
/
[
Home
]
File: wizard.js
/** * @fileoverview Zapatec Wizard. * * <pre> * Copyright (c) 2004-2006 by Zapatec, Inc. * http://www.zapatec.com * 1700 MLK Way, Berkeley, California, * 94709, U.S.A. * All rights reserved. * </pre> */ // $Id: wizard.js 4969 2006-10-31 22:10:37Z alex $ /** * Zapatec.Wizard constructor. * * <pre> * A Wizard object provides common functionality that seems to be required by * any wizard: * * - tabbed display * - keyboard navigation through tabs * - tab navigation bar (<button>-s) * - standard/advanced mode (ability to hide some elements in standard mode) * - data validation code * * Call the constructor like this: * * var args = { * tabsID : "tabs", * tabBarID : "tab-bar" * }; * var wizard = new Zapatec.Wizard(args); * * The args object contains named arguments. As of now, the following two are * required: * * - tabsID -- must be the ID of an element that contains one DIV for each tab. * - tabBarID -- must be the ID of an element where the tab bar should be inserted. * </pre> * * @param {object} args Contains the arguments to be used, as described above * @return A new Zapatec.Wizard object * @type object */ Zapatec.Wizard = function(args) { this.args = args; this.validators = Zapatec.Wizard.defaultValidators; this._tabsEl = document.getElementById(args.tabsID); }; /** * Static variable that implements certain common validators to be available * by default in any wizard. */ Zapatec.Wizard.defaultValidators = { "numeric.int" : function(value, range) { var isnum = /^-?[0-9]+$/.test(value); if (isnum) { isnum = parseInt(value, 10); range[0] = parseInt(range[0], 10); range[1] = parseInt(range[1], 10); if (isnum < range[0] || isnum > range[1]) return "Must be in [" + range[0] + ", " + range[1] + "]"; return true; } else return "Must be numeric, integer."; }, "numeric.float" : function(value, range) { var isfloat = strlen(value) > 0 && /^-?([0-9]*)\.?([0-9]*)$/.test(value); if (isfloat) { isfloat = parseFloat(value, 10); range[0] = parseFloat(range[0], 10); range[1] = parseFloat(range[1], 10); if (isfloat < range[0] || isfloat > range[1]) return "Must be in [" + range[0] + ", " + range[1] + "]"; return true; } else return "Must be numeric, float."; }, "email" : function(value) { return /^([\w.-_]+)@([\w.-_]+)\.(\w+)$/.test(value) ? true : "Must be an email address."; }, "url" : function(value) { return /^(https?|ftps?):\/\/([^\s\x22\x27(){},]+)$/i.test(value) ? true : "Must be an URL."; } }; /** * Initializes the wizard. Developers should call this function after creating * a wizard and assigning any event handlers and/or creating the standard * toolbar (with Zapatec.Wizard.setupNav()). */ Zapatec.Wizard.prototype.init = function() { this._setupTabs(); this.onInit(); }; /** * Display a new tab. This function also takes care of (re)setting the * visibility of buttons in the navigation bar, and of calling the appropriate * event hooks. If "onBeforeTabChange()" returns false, the operation is * cancelled. * * @param {string} newTab ID of the new tab */ Zapatec.Wizard.prototype.changeTab = function(newTab) { var currentTab = this.getCurrentTab(), tab; if (currentTab != newTab) { if (!currentTab || this.onBeforeTabChange(currentTab, newTab)) { if (currentTab) { tab = this.tabs[currentTab]; tab.cont_el.style.display = "none"; Zapatec.Utils.removeClass(tab.tab_el, "active"); } tab = this.tabs[newTab]; tab.cont_el.style.display = "block"; Zapatec.Utils.addClass(tab.tab_el, "active"); this.tabsArray.current = tab.index; window.location = tab.tab_el.href; this.onTabChange(currentTab, newTab); if (this.btnHome) this.btnHome.style.visibility = this.isFirstTab() ? "hidden" : "visible"; if (this.btnPrev) this.btnPrev.style.visibility = this.isFirstTab() ? "hidden" : "visible"; if (this.btnNext) this.btnNext.style.visibility = this.isLastTab() ? "hidden" : "visible"; if (this.btnEnd) this.btnEnd.style.visibility = this.isLastTab() ? "hidden" : "visible"; if (this.btnAdvanced) this._updateAdvancedButton(); if (tab.tab_el.__msh_onclick_action) { var func = tab.tab_el.__msh_onclick_action; if (typeof func == "string") eval(func); else if (typeof func == "function") func(); } } } return this; }; /** * Move to the next tab. */ Zapatec.Wizard.prototype.nextTab = function() { if (this.tabsArray.current < this.tabsArray.length - 1) this.changeTab(this.tabsArray[this.tabsArray.current + 1].id); return this; }; /** * Move to the previous tab. */ Zapatec.Wizard.prototype.prevTab = function() { if (this.tabsArray.current > 0) this.changeTab(this.tabsArray[this.tabsArray.current - 1].id); return this; }; /** * Move to the first tab. */ Zapatec.Wizard.prototype.firstTab = function() { this.changeTab(this.tabsArray[0].id); }; /** * Move to the last tab. Usually needed by buttons like "finish wizard". */ Zapatec.Wizard.prototype.lastTab = function() { this.changeTab(this.tabsArray[this.tabsArray.length - 1].id); }; /** * @return ID of the currently displayed tab * @type string */ Zapatec.Wizard.prototype.getCurrentTab = function() { var tab = this.tabsArray[this.tabsArray.current]; return tab ? tab.id : null; }; /** * @return true if we are at the first tab, false otherwise * @type boolean */ Zapatec.Wizard.prototype.isFirstTab = function() { return this.tabsArray.current == 0; }; /** * @return true if we are at the last tab, false otherwise * @type boolean */ Zapatec.Wizard.prototype.isLastTab = function() { return this.tabsArray.current == this.tabsArray.length - 1; }; /** * Toggles "advanced mode" for the currently displayed tab. This operation * involves displaying or hiding any fields that have the class "advanced". * This is actually achieved by simply adding or removing the class to those * elements, accordingly. */ Zapatec.Wizard.prototype.toggleAdvanced = function() { var tab = this.tabs[this.getCurrentTab()], a = tab.advanced_els, el, i = 0, visible = (tab.advanced =! tab.advanced); while (el = a[i++]) { Zapatec.Utils.removeClass(el, "wizard-advanced"); if (!visible) Zapatec.Utils.addClass(el, "wizard-advanced"); } this._updateAdvancedButton(); }; /** * Creates a default navigation bar for the wizard and appends it into the * given parent. The default buttons are: * * <pre> * - "advanced mode" (only visible if the current tab has advanced elements) * - "Begin" (moves to first tab) * - "Prev." (moves to previous tab) * - "Next" (moves to next tab) * - "End" (moves to last tab) * * Any of these buttons is assigned a certain class name, which helps * customizing the look through external CSS. The classes are (in the same * order as above): "btn-advanced", "btn-begin", "btn-prev", "btn-next", * "btn-finish". * </pre> * * @param {object} parent the parent of the tab navigation bar * @return Reference to the DIV containing the navigation bar * @type object */ Zapatec.Wizard.prototype.setupNav = function(parent) { var div = Zapatec.Utils.createElement("div", parent || this._tabsEl.parentNode); div.className = "navigation"; var self = this, btn; btn = Zapatec.Utils.createElement("button", div); btn.innerHTML = "Show advanced options"; btn.className = "btn-advanced"; btn.onclick = function() { self.toggleAdvanced(); }; this.btnAdvanced = btn; btn = Zapatec.Utils.createElement("button", div); btn.innerHTML = "Begin"; btn.className = "btn-begin"; btn.onclick = function() { self.firstTab(); }; this.btnHome = btn; btn = Zapatec.Utils.createElement("button", div); btn.innerHTML = "« <u>P</u>rev."; btn.accessKey = "p"; btn.className = "btn-prev"; btn.onclick = function() { self.prevTab(); }; this.btnPrev = btn; btn = Zapatec.Utils.createElement("button", div); btn.innerHTML = "<u>N</u>ext »"; btn.accessKey = "n"; btn.className = "btn-next"; btn.onclick = function() { self.nextTab(); }; this.btnNext = btn; btn = Zapatec.Utils.createElement("button", div); btn.innerHTML = "Finish"; btn.className = "btn-finish"; btn.onclick = function() { self.lastTab(); }; this.btnEnd = btn; return div; }; /** * Call this function given the value and the validator to match it against. * WARNING, this function throws an exception if the validator is not defined. * You do not normally need to call this function manually, as all the * validation checks are being done automatically when the field requiring * validation loses focus. * * @param {string} value the string to test * @param {string} validator the validator name * @return true if it validates, false otherwise * @type boolean */ Zapatec.Wizard.prototype.validate = function(value, validator, args) { var f = this.validators[validator]; if (f) return f(value, args, validator); else throw "Validator “" + validator + "” is NOT defined."; }; /** * Create a custom validator. You need to specify the ID of the validator, and * a function that does the validation checks. The function specification is * simple: * * <pre> * function validate(value, args, validator); * * The 3 arguments are: * * - value -- the value that we should check validation against; usually the * \em value attribute of the input field. * - args -- any arguments that might be passed to the validator in the class * name. Note that this might be null. * - validator -- the ID of the validator, useful if you wish to use the same * handler function for multiple validators. * </pre> */ Zapatec.Wizard.prototype.addCustomValidator = function(name, func) { if (!/^[a-z0-9.]+$/i.test(name)) { throw "Illegal validator ID: '" + name + "'. Accepted values can only contain letters, digits and the dot sign."; } else this.validators[name] = func; return this; }; /** * Add a simple validator. Pass the regexp that should match and the error * message that should be displayed if it doesn't. */ Zapatec.Wizard.prototype.addValidator = function(name, regexp, error) { this.addCustomValidator(name, function(value) { return regexp.test(value) ? true : error; }); }; /** * Initializes the wizard tabs and internal data. * @private */ Zapatec.Wizard.prototype._setupTabs = function() { var self = this; var tabs = this._tabsEl; Zapatec.Utils.addClass(tabs, "tabs"); var bar = document.getElementById(this.args.tabBarID); Zapatec.Utils.addClass(bar, "tab-bar"); this.tabs = {}; // maintain by ID this.tabsArray = []; // maintain by index for (var i = tabs.firstChild; i; i = i.nextSibling) { if (i.nodeType != 1) continue; var tab = { tab_el : Zapatec.Utils.createElement("a", bar), cont_el : i, id : i.id, index : this.tabsArray.length, advanced : false, advanced_els : [] }; tab.tab_el.href = "#" + i.id; var tmp = Zapatec.Utils.getFirstChild(i, "label"); if (tmp) while (tmp.firstChild) tab.tab_el.appendChild(tmp.firstChild); if (tmp.accessKey) { tab.tab_el.accessKey = tmp.accessKey; tmp.accessKey = ""; } tab.tab_el.title = tmp.title; tab.tab_el.__msh_onclick_action = tmp.onclick; tmp.parentNode.removeChild(tmp); tab.tab_el.__msh_info = tab; tab.tab_el.onclick = function() { self.changeTab(this.__msh_info.id); if (typeof this.blur == "function") this.blur(); return false; }; if (Zapatec.is_ie) tab.tab_el.onfocus = tab.tab_el.onclick; this.tabsArray[this.tabsArray.length] = tab; this.tabs[tab.id] = tab; i.style.display = "none"; this._populateLists(tab); } this.tabsArray.current = -1; var currentTab = this.tabsArray[0].id; if (/#([^\/]+)$/.test(document.URL) && this.tabs[RegExp.$1]) currentTab = RegExp.$1; this.changeTab(currentTab); }; /** * Populates some internal arrays in the given tab object, analyzing * all elements in the tab. At this time, this function initializes: * * <pre> * - the list of advanced elements (those that should be visible only if * the tab is in "show advanced options" mode). * - the list of fields that require validation. Also, all these fields get * installed an "onblur" handler that checks validation. * </pre> * * @private * @param {object} tab reference to the internally defined tab object */ Zapatec.Wizard.prototype._populateLists = function(tab) { var a = tab.cont_el.getElementsByTagName("*"), i = 0, el, c, self = this; while (el = a[i++]) { var c = el.className; if (/(^|\s)wizard-advanced(\s|$)/i.test(c)) tab.advanced_els[tab.advanced_els.length] = el; if (/(^|\s)validate-([^\s-]+)(-[^\s]+)?/i.test(c)) { el.__msh_validator = RegExp.$2; el.__msh_validator_args = RegExp.$3; el.onblur = function(ev) { ev || (ev = window.event); return self._validateField( this, this.__msh_validator, this.__msh_validator_args, ev); }; } } }; /** * Called by the "onblur" handler for any fields that might required * validation, this function parses arguments, calls the appropriate validating * code and outputs the error message if it's the case. * * @private * @param {object} field a reference to a HTMLInputElement (usually) to be validated. * @param {string} validator the ID of a validator. * @param {string} args the validator arguments, as specified in the class name. * @param {object} event the Event object, useful if we want to stop propagation. */ Zapatec.Wizard.prototype._validateField = function(field, validator, args, event) { var value, tag = field.tagName.toLowerCase(), div, message; if (typeof args != "undefined") { args = args.replace(/^-/, ''); args = args.split(/-/); } else args = null; try { if (tag == "input" || tag == "select" || tag == "textarea") { message = this.validate(field.value, validator, args); if (typeof message == "boolean" && !message) // No message provided, let's think of one.. message = "This field must validate by “" + validator + "”"; if (typeof message == "string") { div = field.__msh_message; if (!div) { // create the message area div = field.__msh_message = Zapatec.Utils.createElement("div"); div.className = "validation-error"; field.parentNode.insertBefore(div, field.nextSibling); } div.innerHTML = message; // FIXME: this doesn't work, for some reason // field.focus(); // field.select(); Zapatec.Utils.addClass(field, "field-error"); Zapatec.Utils.stopEvent(event); return false; } else { div = field.__msh_message; if (div) { div.parentNode.removeChild(div); field.__msh_message = null; } Zapatec.Utils.removeClass(field, "field-error"); } } else // FIXME: what should we do here? throw "I don't know how to validate <" + tag + "> elements."; } catch(e) { alert("Error: " + e); // FIXME: what should we do here? } }; /** * Updates the state of the "advanced" button. * * @private * @param {object} tab Optional. Reference to the tab to check the state * against. If not passed, the current tab is assumed. */ Zapatec.Wizard.prototype._updateAdvancedButton = function(tab) { if (this.btnAdvanced) { if (!tab) tab = this.tabs[this.getCurrentTab()]; this.btnAdvanced.innerHTML = tab.advanced ? "Hide advanced options" : "Show advanced options"; this.btnAdvanced.style.visibility = tab.advanced_els.length == 0 ? "hidden" : "visible"; } }; //@} /* Customizable event hooks * * Event hooks provide a way for the developer to insert special code that is * executed when wizard events occur. For instance, one could write the * following: * * function myOnInit() { * this.changeTab("tab-3"); * alert("Look, we changed to the third tab! >8-]"); * }; * wizard.onInit = myOnInit; */ /** * A "do nothing" handler used for default event hooks. */ Zapatec.Wizard._doNothing = function() { return true; }; /** * Called when the wizard is created. Users can perform * problem-specific initializations at this stage. No * arguments. */ Zapatec.Wizard.prototype.onInit = Zapatec.Wizard._doNothing; /** * Called _after_ the tab was changed. * * @param {string} oldTab ID of the old tab * @param {string} newTab ID of the new tab */ Zapatec.Wizard.prototype.onTabChange = Zapatec.Wizard._doNothing; /** * Called when the tab is about to be changed, just _before_. * * @param {string} oldTab ID of the old (current) tab * @param {string} newTab ID of the new tab * @return false if the tab should not change * @type boolean */ Zapatec.Wizard.prototype.onBeforeTabChange = Zapatec.Wizard._doNothing;