/* * Web Forms 2.0 Cross-browser Implementation <http://code.google.com/p/webforms2/> * Version: 0.7 (2011-03-01) * Copyright: 2007, Weston Ruter <http://weston.ruter.net/> * with additions by Zoltan Hawryluk <http://www.useragentman.com> * Licenses (as of Feb 6, 2011) * - MIT License (http://www.opensource.org/licenses/mit-license.php) * - GPL (http://creativecommons.org/licenses/GPL/2.0/) * * The comments contained in this code are largely quotations from the * WebForms 2.0 specification: <http://whatwg.org/specs/web-forms/current-work/> * * Usage: <script type="text/javascript" src="webforms2_src.js"></script> * * Changelog: * version 0.5.4 - initial release by Weston Ruter * version 0.6 - refactored for use with HTML5Widgets by Zoltan Hawryluk (July 27th, 2010) * version 0.6.1 - updated to deal with WebKit's half-implemented WebForms 2 Implementation (Sept 10, 2010) * version 0.7 - abug fixes with nested repetition models by Zoltan Hawryluk. * version 0.7.1 - updated to dual MIT/GPL 2.0 license. * version 1.0 - Updated to mimic CSS validation pseudo-classes, support for newer browsers * native support (IE10, Firefox 4, Webkit, Opera 11.11). This version does rely on * the WebForms.js framework. * version 1.1 - fixed bug in Firefox (and possibly others) where this polyfill wasn't executing * if we instructed HTML5Forms.js tried to override it. */ if(!window.$wf2){ var $wf2 = {}; /* * do not make this polyfill active unless * * a) We can determine the browser *doesn't* support WebForms 2.0, or * b) html5Forms loaded this polyfill and has it's 'forceJSValidation' * property set to true. */ if(document.implementation && document.implementation.hasFeature && (!document.implementation.hasFeature('WebForms', '2.0') || window.html5Forms && html5Forms.forceJSValidation)){ $wf2 = { version : '0.5.4', isInitialized : false, libpath : '', globalEvent: null, hasElementExtensions : (window.HTMLElement && HTMLElement.prototype), hasGettersAndSetters : ($wf2.__defineGetter__ && $wf2.__defineSetter__), hasBadImplementation: navigator.userAgent.indexOf('WebKit') > 0, // WebKit less than 534 doesn't show validation UI - we need to check for this (from http://stackoverflow.com/questions/6030522/html5-form-validation-modernizr-safari) hasNativeBubbles: navigator.userAgent.indexOf('WebKit') < 0 || parseInt(navigator.userAgent.match(/AppleWebKit\/([^ ]*)/)[1].split('.')[0]) > 534, callBeforeValidation : new Array(), callAfterValidation : new Array(), callAfterDOMContentLoaded: new Array(), onDOMContentLoaded : function(){ if($wf2.isInitialized) return; $wf2.isInitialized = true; //Safari needs this here for some reason var i,j,k,node; //set global event for fireEvent method if (document.createEventObject){ // dispatch for IE $wf2.globalEvent = document.createEventObject(); } else if (document.createEvent) { $wf2.globalEvent = document.createEvent("HTMLEvents"); } //Include stylesheet var style = document.createElement('link'); style.setAttribute('type', 'text/css'); style.setAttribute('rel', 'stylesheet'); style.setAttribute('href', $wf2.libpath + 'webforms2.css'); var parent = document.getElementsByTagName('head')[0]; if(!parent) parent = document.getElementsByTagName('*')[0]; parent.insertBefore(style, parent.firstChild); //The zero point for datetime controls is 1970-01-01T00:00:00.0Z, for datetime-local is // 1970-01-01T00:00:00.0, for date controls is 1970-01-01, for month controls is 1970-01, for week // controls is 1970-W01 (the week starting 1969-12-29 and containing 1970-01-01), and for time controls // is 00:00. $wf2.zeroPoint = {}; $wf2.zeroPoint.datetime = $wf2.parseISO8601("1970-01-01T00:00:00.0Z"); $wf2.zeroPoint['datetime-local'] = $wf2.parseISO8601("1970-01-01T00:00:00.0"); $wf2.zeroPoint.date = $wf2.zeroPoint.datetime; //parseISO8601("1970-01-01"); //.zeroPointDatetime; //1970-01-01 (UTC) $wf2.zeroPoint.month = $wf2.zeroPoint.datetime; //parseISO8601("1970-01"); //1970-01 (UTC) $wf2.zeroPoint.week = $wf2.parseISO8601("1970-W01"); //(UTC) $wf2.zeroPoint.time = $wf2.zeroPoint.datetime; //parseISO8601("00:00"); //00:00 (UTC) //## Fetching data from external resources ################################## $wf2.xhr = null; if(window.XMLHttpRequest) $wf2.xhr = new XMLHttpRequest(); else if(window.ActiveXObject){ try { $wf2.xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e){ try { $wf2.xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e){} } } if($wf2.xhr){ $wf2.prefillSelectElements(); $wf2.prefillFormElements(); } //Initialize Repetition Behaviors **************************************** //Before load events are fired, but after the entire document has been parsed and after forms with data // attributes are prefilled (if necessary), UAs must iterate through every node in the document, depth // first, looking for templates so that their initial repetition blocks can be created. ... UAs should not // specifically wait for images and style sheets to be loaded before creating initial repetition blocks // as described above. if (window.$wf2Rep) { $wf2.initRepetitionBlocks(); $wf2.initRepetitionTemplates(); $wf2.initRepetitionButtons('add'); $wf2.initRepetitionButtons('remove'); $wf2.initRepetitionButtons('move-up'); $wf2.initRepetitionButtons('move-down'); $wf2.updateAddButtons(); $wf2.updateMoveButtons(); } // Initialize Non-Repetition Behaviors **************************************** if(document.addEventListener){ document.addEventListener('mousedown', $wf2.clearInvalidIndicators, false); document.addEventListener('keydown', $wf2.clearInvalidIndicators, false); } else if(document.attachEvent){ document.attachEvent('onmousedown', $wf2.clearInvalidIndicators); document.attachEvent('onkeydown', $wf2.clearInvalidIndicators); } $wf2.initNonRepetitionFunctionality(); for (var i=0; i<$wf2.callAfterDOMContentLoaded.length; i++) { $wf2.callAfterDOMContentLoaded[i](); } }, /*############################################################################################## # Section: Fetching data from external resources ##############################################################################################*/ prefillSelectElements : function(){ //If a select element or a datalist element being parsed has a data attribute, then as soon // as the element and all its children have been parsed and added to the document, the // prefilling process described here should start. var select, selects = $wf2.getElementsByTagNames.apply(document.documentElement, ['select', 'datalist']); //$wf2.getElementsByTagNamesAndAttribute.apply(document.documentElement, [['select', 'datalist']]); //, 'data' for(var i = 0; select = selects[i]; i++){ //If a select element or a datalist element has a data attribute, it must be a URI or // IRI that points to a well-formed XML file whose root element is a select element // in the http://www.w3.org/1999/xhtml namespace. The MIME type must be an XML MIME // type [RFC3023], preferably application/xml. It should not be application/xhtml+xml // since the root element is not html. //UAs must process this file if it has an XML MIME type [RFC3023], if it is a well-formed // XML file, and if the root element is the right root element in the right namespace. // If any of these conditions are not met, UAs must act as if the attribute was not // specified, although they may report the error to the user. UAs are expected to // correctly handle namespaces, so the file may use prefixes, etc. var xmlDoc = $wf2.loadDataURI(select); if(///\bxml\b/.test(xhr.getResponseHeader('Content-Type') && xmlDoc && xmlDoc.documentElement && /:?\bselect$/i.test(xmlDoc.documentElement.nodeName) && xmlDoc.documentElement.namespaceURI == 'http://www.w3.org/1999/xhtml' ) { var root = xmlDoc.documentElement; //1. Unless the root element of the file has a type attribute with the exact literal // string incremental, the children of the select or datalist element in the original // document must all be removed from the document. if(root.getAttribute('type') != 'incremental'){ while(select.lastChild) select.removeChild(select.lastChild); } //2. The entire contents of the select element in the referenced document are imported // into the original document and appended as children of the select or datalist // element. (Even if importing into a text/html document, the newly imported nodes // will still be namespaced.) //3. All nodes outside the select (such as style sheet processing instructions, whitespace // text nodes, and DOCTYPEs) are ignored, as are attributes (other than type) on the // select element. node = root.firstChild; while(node){ //select.appendChild(node.cloneNode(true)); //MSIE BUG: Throws "No such interface supported" exception select.appendChild($wf2.cloneNode(node)); node = node.nextSibling; } } } }, prefillFormElements : function(){ //-- Seeding a form with initial values ------------------------------- //Before load events are fired, but after the entire document has been parsed and after select // elements have been filled from external data sources (if necessary), forms with data attributes // are prefilled. var frm, frms = document.getElementsByTagName('form'); //$wf2.getElementsByTagNamesAndAttribute.apply(document.documentElement, [['form'], 'data']); for(var i = 0; frm = frms[i]; i++){ //If a form has a data attribute, it must be a URI or IRI that points to a well-formed XML file // whose root element is a formdata element in the http://n.whatwg.org/formdata namespace. The // MIME type must be an XML MIME type [RFC3023], preferably application/xml. //UAs must process this file if these conditions are met. If any of these conditions are not met, // UAs must act as if the attribute was not specified, although they may report the error to // the user. UAs are expected to correctly handle namespaces, so the file may use prefixes, etc. var xmlDoc = $wf2.loadDataURI(frm); if(///\bxml\b/.test(xhr.getResponseHeader('Content-Type') && xmlDoc && xmlDoc.documentElement && /:?\bformdata$/.test(xmlDoc.documentElement.nodeName) && xmlDoc.documentElement.namespaceURI == 'http://n.whatwg.org/formdata' ) { var rt; var root = xmlDoc.documentElement; //1. Unless the root element has a type attribute with the exact literal string incremental, // the form must be reset to its initial values as specified in the markup. if(root.getAttribute('type') != 'incremental') frm.reset(); //The algorithm must be processed in the order given above, meaning any clear elements are // handled before any repeat elements which are handled before the field elements, regardless // of the order in which the elements are given. (Note that this implies that this process // cannot be performed incrementally.) //clear elements in the http://n.whatwg.org/formdata namespace that are children of // the root element, have a non-empty template attribute, have no other non-namespaced // attributes (ignoring xmlns attributes), and have no content, must be processed...: //The template attribute should contain the ID of an element in the document. If the // template attribute specifies an element that is not a repetition template, then // the clear element is ignored. var clr, clrs = root.getElementsByTagName('clear'); //getElementsByTagNameNS('http://n.whatwg.org/formdata', 'clr') for(j = 0; clr = clrs[j]; j++){ if(clr.namespaceURI == 'http://n.whatwg.org/formdata' && clr.parentNode == root && !clr.firstChild && (rt = document.getElementById(clr.getAttribute('template'))) && rt.getAttribute('repeat') == 'template' /*Examining of non-namespaced attributes skipped*/ ) { //The user must make a note of the list of repetition blocks associated with that // template that are siblings of the template, and must then go through this list, // removing each repetition block in turn. //Note that we cannot use rt.repetitionBlocks since the repetition behavior has // not yet been initialized. var attr,node,next; node = rt.parentNode.firstChild; while(node){ if(node.nodeType == 1 && (attr = node.getAttributeNode('repeat')) && attr.value != 'template'){ next = node.nextSibling; node.parentNode.removeChild(node); node = next; } else node = node.nextSibling; } } } //repeat elements in the http://n.whatwg.org/formdata namespace that are children of // the root element, have a non-empty template attribute and an index attribute that // contains only one or more digits in the range 0-9 with an optional leading minus // sign (U+002D, "-"), have no other non-namespaced attributes (ignoring xmlns // attributes), and have no content, must be processed as follows: //The template attribute should contain the ID of an element in the document. If the // template attribute specifies an element that is not a repetition template, then // the repeat element is ignored. var index, rpt, rpts = root.getElementsByTagName('repeat'); for(j = 0; rpt = rpts[j]; j++){ if(rpt.namespaceURI == 'http://n.whatwg.org/formdata' && rpt.parentNode == root && !rpt.firstChild && (rt = document.getElementById(rpt.getAttribute('template'))) && rt.getAttribute('repeat') == 'template' && /^-?\d+$/.test(index = rpt.getAttribute('index')) /*Examining of non-namespaced attributes skipped*/ ) { //If the template attribute specifies a repetition template and that template // already has a repetition block with the index specified by the index attribute, // then the element is ignored. //for(j = 0; j < rt.repetitionBlocks.length; j++){ // if(rt.repetitionBlocks[j].repetititionIndex == index){ // hasIndex = true; // break; // } //} var hasIndex,attr,node,next; node = rt.parentNode.firstChild; while(node){ if(node.nodeType == 1 && (attr = node.getAttributeNode('repeat')) && attr.value == index){ hasIndex = true; break; } node = node.nextSibling; } if(!hasIndex){ //Otherwise, the specified template's addRepetitionBlockByIndex() method is // called, with a null first argument and the index specified by the repeat // element's index attribute as the second. $wf2.addRepetitionBlockByIndex.apply(rt, [null, index]); } } } //field elements in the http://n.whatwg.org/formdata namespace that are children of // the root element, have a non-empty name attribute, either an index attribute // that contains only one or more digits in the range 0-9 or no index attribute at // all, have no other non-namespaced attributes (ignoring xmlns attributes), and // have either nothing or only text and CDATA nodes as children, must be used to // initialize controls... var fld, flds = root.getElementsByTagName('field'); var formElements = $wf2.getFormElements.apply(frm); for(j = 0; fld = flds[j]; j++){ var indexAttr = fld.getAttributeNode('index'); var name = fld.getAttribute('name'); if(!name || (indexAttr && !/^\d+$/.test(indexAttr.value))) /*Examining of non-namespaced attributes skipped*/ /*Verification of the presence of text and CDATA nodes below*/ continue; //First, the form control that the field references must be identified. var value = ''; for(k = 0; node = fld.childNodes[k]; k++){ if(node.nodeType == 3 /*text*/ || node.nodeType == 4 /*CDATA*/) value += node.data; else break; //only text and CDATA nodes allowed } var ctrl, count = 0; for(k = 0; ctrl = formElements[k]; k++){ //console.info(ctrl.name + ' == ' + name) if(ctrl.type == 'image'){ //For image controls, instead of using the name given by the name attribute, // the field's name is checked against two names, the first being the value // of the name attribute with the string .x appended to it, and the second // being the same but with .y appended instead. If an image control's name // is the empty string (e.g. if its name attribute is omitted) then the // names x and y must be used instead. Thus image controls are handled as // if they were two controls. if(ctrl.name ? (ctrl.name + '.x' == name || ctrl.name + '.y' == name) : (name == 'x' || name == 'y') ){ if(!indexAttr || ++count-1 >= indexAttr.value) break; } } //This is done by walking the list of form controls associated with the form until // one is found that has a name exactly equal to the name given in the field // element's name attribute, skipping as many such matches as is specified in // the index attribute, or, if the index attribute was omitted, skipping over // any type="radio" and type="checkbox" controls that have the exact name given // but have a value that is not exactly the same as the contents of the field element. // SPECIFICATION DEFICIENCY: Note that this is not completely true. If the value of // a field element is empty, then it should not be skipped if it associated with // a radio button or checkbox. For example, the specification states four paragraphs // later, "The only values that would have an effect in this example are "", which // would uncheck the checkbox, and "green", which would check the checkbox." else if(ctrl.name == name){ if(indexAttr){ if(++count-1 < indexAttr.value) continue; } else if((ctrl.type == 'radio' || ctrl.type == 'checkbox') && (value && ctrl.value != value)) continue; break; } } //If the identified form control is a file upload control, a push button control, or // an image control, then the field element is now skipped. if(ctrl.type == 'file' || ctrl.type == 'button' || ctrl.type == 'image') continue; //Next, if the identified form control is not a multiple-valued control (a multiple- // valued control is one that can generate more than one value on submission, such // as a <select multiple="multiple">), or if it is a multiple-valued control but it // is the first time the control has been identified by a field element in this // data file that was not ignored, then it is set to the given value (the contents // of the field element), removing any previous values (even if these values were // the result of processing previous field elements in the same data file). if(!ctrl.getAttributeNode('multiple') || !ctrl.wf2Prefilled){ //If the element cannot be given the value specified, the field element is // ignored and the control's value is left unchanged. For example, if a // checkbox has its value attribute set to green and the field element // specifies that its value should be set to blue, it won't be changed from // its current value. (The only values that would have an effect in this // example are "", which would uncheck the checkbox, and "green", which would // check the checkbox.) if(ctrl.type == 'checkbox' || ctrl.type == 'radio'){ if(!value) ctrl.checked = false; else if(ctrl.value == value) ctrl.checked = true; else break; } else if(ctrl.nodeName.toLowerCase() == 'select'){ ctrl.selectedIndex = -1; for(var opt,k = 0; opt = ctrl.options[k]; k++){ if(opt.value ? opt.value == value : opt.text == value){ opt.selected = true; break; } } } //Another example would be a datetime control where the specified value is // outside the range allowed by the min and max attributes. The format // must match the allowed formats for that type for the value to be set. else { ctrl.value = value; $wf2.updateValidityState(ctrl); if(!ctrl.validity.valid){ ctrl.value = ctrl.defaultValue; $wf2.updateValidityState(ctrl); } } ctrl.wf2Prefilled = true; //TRACE } //Otherwise, this is a subsequent value for a multiple-valued control, and the // given value (the contents of the field element) should be added to the list of // values that the element has selected. //If the element is a multiple-valued control and the control already has the given // value selected, but it can be given the value again, then that occurs. else if(ctrl.getAttributeNode('multiple')){ for(var opt,k = 0; opt = ctrl.options[k]; k++){ if(!opt.selected && (opt.value ? opt.value == value : opt.text == value)){ opt.selected = true; break; } } } //if(ctrl){ // //} } //A formchange event is then fired on all the form controls of the form. var formElements = $wf2.getFormElements.apply(frm); for(j = 0; j < formElements.length; j++){ //onformchange(); //fireEvent() } } } }, /*############################################################################################# # Section: Extensions to the input element ##############################################################################################*/ initNonRepetitionFunctionality : function(parent){ parent = (parent || document.documentElement); var i,j, frm, frms = parent.getElementsByTagName('form'); for(i = 0; frm = frms[i]; i++){ // use the native validation if the browser has it, unless // the form has a data-webforms2-force-js-validation attribute // set to "true" *or* the browser has a bad implementation. if(frm.checkValidity && !$wf2.hasBadImplementation && $wf2.getAttributeValue(frm, 'data-webforms2-force-js-validation') != 'true') { console.log('sdsd') continue; } console.log('fff') frm.checkValidity = $wf2.formCheckValidity; if(frm.addEventListener) frm.addEventListener('submit', $wf2.onsubmitValidityHandler, false); else frm.attachEvent('onsubmit', $wf2.onsubmitValidityHandler); } var ctrl, ctrls = $wf2.getElementsByTagNames.apply(parent, ['input','select','textarea', 'button']);//parent.getElementsByTagName([i]); for(i = 0; ctrl = ctrls[i]; i++){ $wf2.applyValidityInterface(ctrl); $wf2.updateValidityState(ctrl); //ctrl._updateValidityState(); // add CSS focus-like support for IE7 and under /*@cc_on ctrl.attachEvent('onfocus', function (e) { $wf2.css.addClass(e.srcElement, 'wf2_focus'); }); ctrl.attachEvent('onblur', function (e) { $wf2.css.removeClass(e.srcElement, 'wf2_focus') }); @*/ } //Autofocus ********************************************************** //Authors must not set the autofocus attribute on multiple enabled elements in a document. // If multiple elements with the autofocus attribute set are inserted into a document, each one // will be processed as described above, as they are inserted. This means that during document // load, for example, the last focusable form control in document order with the attribute set // will end up with the focus. var els = $wf2.getElementsByTagNamesAndAttribute.apply(document.documentElement, [['*'], 'autofocus']); //ISSUE: Any form control (except hidden and output controls) can have an autofocus attribute specified. //var elName = els[i].nodeName.toLowerCase(); if(elName == 'output' || (elName == 'input' && els[i].type == 'hidden')) if(parent.getAttribute('autofocus')) els.unshift(parent); for(i = 0; i < els.length; i++) $wf2.initAutofocusElement(els[i]); // Maxlength for textareas ****************************************************** var textareas = $wf2.getElementsByTagNamesAndAttribute.apply(parent, [['textarea'], 'maxlength']); if(parent.nodeName.toLowerCase() == 'textarea') textareas.unshift(parent); for(i = 0; i < textareas.length; i++) textareas[i].maxLength = parseInt(textareas[i].getAttribute('maxlength')); //TODO: we must dynamically apply this behavior for new textareas (via repetition model or eventlistener) }, initAutofocusElement : function(el){ //skip if already initialized if(el.autofocus === false || el.autofocus === true) //(el.autofocus !== undefined) does not work due to MSIE's handling of attributes return; el.autofocus = true; //[autofocus if] the control is not disabled if(el.disabled) return; //[control] is of a type normally focusable in the user's operating environment //Don't focus on the control if it is not visible or nor displayed var node = el; while(node && node.nodeType == 1){ if($wf2.getElementStyle(node, 'visibility') == 'hidden' || $wf2.getElementStyle(node, 'display') == 'none') return; node = node.parentNode; } //Then the UA should focus the control, as if the control's focus() method was invoked. // UAs with a viewport should also scroll the document enough to make the control visible, // [[even if it is not of a type normally focusable.]] //WHAT DOES THIS MEAN? el.focus(); //BUG: in Gecko this does not work within DOMNodeInserted event handler, but the following does; setTimeout(function(){el.focus();}, 0); }, /*############################################################################################# # Section: Form Validation model ##############################################################################################*/ formCheckValidity : function(){ var i, el, valid = true; //When a form is submitted, user agents must act as if they used the following algorithm. // First, each element in that form's elements list is added to a temporary list (note that // the elements list is defined to be in document order). //An invalid event must be fired on each element that, when checked, is found to fail to // comply with its constraints (i.e. each element whose validity.valid DOM attribute is // false) and is still a member of the form after the event has been handled. //var _elements = []; var formElements = $wf2.getFormElements.apply(this); //for(i = 0; i < formElements.length; i++) // _elements.push(formElements[i]); for(i = 0; el = formElements[i]; i++){ var type = (el.getAttribute('type') ? el.getAttribute('type').toLowerCase() : el.type); try { el.willValidate = !(/(hidden|button|reset|add|remove|move-up|move-down)/.test(type) || !el.name || el.disabled) } catch (ex) { // do nothing. } //Then, each element in this list whose willValidate DOM attribute is true is checked for validity if(el.checkValidity && el.willValidate){ if(!el.checkValidity() && el.checkValidity() != undefined) { valid = false; /* var oninvalid = el.getAttribute('oninvalid'); if (oninvalid) { oninvalid(); } */ /* we change this to only show the first error. if (!$wf2.showAllErrors) { break; } */ } } } if (!valid) { $wf2.hiliteFirstError(); } return valid; }, hiliteFirstError: function () { if($wf2.invalidIndicators.length){ //second condition needed because modal in oninvalid handler may cause indicators to disappear before this is reached //$wf2.invalidIndicators[0].errorMsg.className += " wf2_firstErrorMsg"; $wf2.css.addClass($wf2.invalidIndicators[0].errorMsg, "wf2_firstErrorMsg"); //scroll to near the location where invalid control is el = $wf2.invalidIndicators[0].target; var doScroll = (el.form.getAttribute('data-wf2-no-scroll-on-error') != 'true') if(el.style.display == 'none' || !el.offsetParent){ while(el && (el.nodeType != 1 || (el.style.display == 'none' || !el.offsetParent))) el = el.previousSibling; var cur = el; var top = 0; if(cur && cur.offsetParent) { top = cur.offsetTop; while (cur = cur.offsetParent) top += cur.offsetTop; } if (doScroll) { scrollTo(0, top); } } //focus on the first invalid control and make sure error message is visible else { setTimeout( function() { el.focus(); $wf2.fireEvent(el, 'focus'); } , 10) var elPos = $wf2.css.getAbsoluteCoords(el); var maxBottom = $wf2.css.getScrollY() + $wf2.css.getWindowHeight(); var errorMsgHeight = $wf2.invalidIndicators[0].errorMsg.offsetHeight; //console.log(elPos.y + errorMsgHeight, maxBottom) //NOTE: We should only do this if the control's style.bottom == 0 if (doScroll && elPos.y + errorMsgHeight > maxBottom) { scrollBy(0, errorMsgHeight ); } } } }, controlCheckValidity : function(){ return $wf2.controlCheckValidityOfElement(this); }, controlCheckValidityOfElement: function (el) { $wf2.updateValidityState(el); if (el.validity.valid) { return true; } var canceled = false; var evt; try { if(document.createEvent) evt = document.createEvent('Events'); //document.createEvent("RepetitionEvent") else if(document.createEventObject) evt = document.createEventObject(); evt.initEvent('invalid', true /*canBubble*/, true /*cancelable*/); evt.srcElement = el; if(el.dispatchEvent) canceled = !el.dispatchEvent(evt); else if(el.fireEvent){ //console.warn("fireEvent('oninvalid') for MSIE is not yet working"); //el.fireEvent('oninvalid', invalidEvt); } } catch(err){ evt = new Object(); if(evt.initEvent) evt.initEvent('invalid', true /*canBubble*/, true /*cancelable*/); else { evt.type = 'invalid'; evt.cancelBubble = false; } evt.target = evt.srcElement = el; } var oninvalidAttr = el.getAttribute('oninvalid'); if(oninvalidAttr && (!el.oninvalid || typeof el.oninvalid != 'function')) //in MSIE, attribute == property el.oninvalid = new Function('event', oninvalidAttr); try { //Dispatch events for the old event model if(el.oninvalid){ //canceled = el.oninvalid(evt) === false || canceled; canceled = el.oninvalid.apply(el, [evt]) === false || canceled; //for some reason, exceptions cannot be caught if using the method above in MSIE } } catch(err){ var myErr = err; //throw exception within setTimeout so that the current execution will not be aborted setTimeout(function(){ throw myErr; }, 0); } //Determine if this radio/checkbox already has an invalid indicator var hasInvalidIndicator = false; if(el.type == 'radio' || el.type == 'checkbox'){ for(var i = 0; i < $wf2.invalidIndicators.length; i++){ if(el.form[el.name][0] == $wf2.invalidIndicators[i].target){ hasInvalidIndicator = true; break; } } } //Do default action if(!canceled && !hasInvalidIndicator) //(!(el.form && el.form[el.name]) || !el.form[el.name].wf2HasInvalidIndicator) $wf2.addInvalidIndicator(el); return false; }, //Frequently used regular expressions //W(?:0[1-9]|[1-4]\d|5[0-2])| //monthRegExp : /^\d\d\d\d-(0\d|1[0-2])$/, //weekRegExp : /^(\d\d\d\d)-W(0[1-9]|[1-4]\d|5[0-2])$/, //timeRegExp : /^(0\d|1\d|2[0-4]):([0-5]\d)(:[0-5]\d(.\d+)?)?$/, numberRegExp : /^-?\d+(.\d+)?(e-?\d+)?$/, //numberOrAnyRegExp : /^(any|-?\d+(.\d+)?(e-?\d+)?)$/i, urlRegExp : /^(\w+):(\/\/)?.+$/i, emailRegExp : /^\S+@\S+$/i, //Zero points for datetime-related types (set in onDOMContentLoaded function) // zeroPointDatetime : null, // zeroPointDatetimeLocal : null, // zeroPointDate : null, // zeroPointMonth : null, // zeroPointWeek : null, // zeroPointTime : null, copyOf: function(obj) { if (obj !== null && obj !== undefined) { var r = new Object(); for (i in obj) { try { r[i] = obj[i]; } catch (ex) { // do nothing; } } } else { r = null; } return r; }, getOriginalAttrNode: function (node, attrName) { var r; /* var dataSetItemName = attrName + 'AttrNode'; if ($wf2.getDatasetItem(node, dataSetItemName) == null) { r = $wf2.copyOf(node.getAttributeNode(attrName)); $wf2.setDatasetItem(node, dataSetItemName, r.value); } else { r = $wf2.getDatasetItem(node, dataSetItemName); if (r == 'null') { r = null; } } */ r = $wf2.copyOf(node.getAttributeNode(attrName)); return r; }, //This function is called "live" updateValidityState : function(node){ var customErrorMessage = $wf2.getAttributeValue(node, 'data-errormessage'); var isCustomErrorMessageSet = (customErrorMessage == node.validationMessage); if (isCustomErrorMessageSet) { node.validationMessage = ''; } //if(node.form && node.form[node.name] && node.form[node.name].wf2HasInvalidIndicator) // return; var type = (node.getAttribute('type') ? node.getAttribute('type').toLowerCase() : node.type); // for range, we need to make sure this doesn't run if (type == 'range' && node.type == 'range') { return; } var minAttrNode, maxAttrNode, valueAttrNode; minAttrNode = $wf2.getOriginalAttrNode(node, 'min'); maxAttrNode = $wf2.getOriginalAttrNode(node, 'max'); stepAttrNode = $wf2.getOriginalAttrNode(node, 'step'); valueAttrNode = $wf2.getOriginalAttrNode(node, 'value'); node.min = undefined; //wf2Min node.max = undefined; //wf2Max node.step = undefined; //wf2Step // only do this if node.validity is set, or Opera will complain. if (!node.validity) { node.validity = $wf2.createValidityState(); } node.validity.customError = !!node.validationMessage; //var type = node.type ? node.type.toLowerCase() : 'text'; //var type = (node.type ? node.getAttribute('type').toLowerCase() : // (node.nodeName.toLowerCase() == 'input' ? 'text' : '')); var isTimeRelated = (type == 'datetime' || type == 'datetime-local' || type == 'time'); //datetime, datetime-local, time var isDateRelated = (type == 'date' || type == 'month' || type == 'week'); //date, month, week var isNumberRelated = (type == 'number' || type == 'range'); //number, range var isFileInput = (type == 'file'); var doCheckPrecision = (isTimeRelated || isDateRelated || isNumberRelated); //datetime, datetime-local, time, date, month, week, number, range var doMaxLengthCheck = doCheckPrecision || node.nodeName.toLowerCase() == 'textarea'; //datetime, datetime-local, time, date, month, week, number, range, textarea var doCheckRange = (doCheckPrecision || isFileInput); //datetime, datetime-local, time, date, month, week, number, range, file var isRadioOrCheckbox = (type == 'radio' || type == 'checkbox'); var isSelect = (node.nodeName == 'SELECT'); var doRequiredCheck = (doMaxLengthCheck || //datetime, datetime-local, time, date, month, week, number, range, textarea isFileInput || isSelect || type == 'email' || type == 'url' || type == 'text' || type == 'password'|| type == 'tel' || isRadioOrCheckbox); //If a control has its type attribute changed to another type, then the user agent must reinterpret the min and // max attributes. If an attribute has an invalid value according to the new type, then the appropriate // default must be used (and not, e.g., the default appropriate for the previous type). Control values that // no longer match the range allowed for the control must be handled as described in the error handling section. //if(!node.wf2PreviousType) // node.wf2PreviousType == type; //else if(type != node.wf2PreviousType){ // throw Error("Currently unable to change the type of a control."); //TODO //} if(type == 'range'){ //For this type...min defaults to 0...and value defaults to the min value. node.min = (minAttrNode && $wf2.numberRegExp.test(minAttrNode.value)) ? Number(minAttrNode.value) : 0; node.max = (maxAttrNode && $wf2.numberRegExp.test(maxAttrNode.value)) ? Number(maxAttrNode.value) : 0; if((!valueAttrNode || !valueAttrNode.specified) && node.value === '' && !node.wf2ValueProvided){ //(!valueAttrNode || !valueAttrNode.specified) && node.setAttribute('value', node.min); node.value = node.min; node.wf2ValueProvided = true; } return; } if ($wf2.css.isMemberOfClass(node, 'html5-hasPlaceholderText')) { node.wf2Value = ''; } else { node.wf2Value = node.value; } //console.log("changing:", node.value, ", " , node.wf2Value) //valueMissing -- The control has the required attribute set but it has not been satisfied. //The required attribute applies to all form controls except controls with the type hidden, // image inputs, buttons (submit, move-up, etc), and select and output elements. For // disabled or readonly controls, the attribute has no effect. var type = (node.getAttribute('type') ? node.getAttribute('type').toLowerCase() : node.type); // do this only when node.willValidate doesn't exist or Opera will complain try { node.willValidate = !(/(hidden|button|reset|add|remove|move-up|move-down)/.test(type) || !node.name || node.disabled); } catch (ex) { // do nothing. } if(doRequiredCheck && node.willValidate){ //For checkboxes, the required attribute shall only be satisfied when one or more of // the checkboxes with that name in that form are checked. //For radio buttons, the required attribute shall only be satisfied when exactly one of // the radio buttons in that radio group is checked. if(isRadioOrCheckbox){ if(node.form && node.form[node.name]){ var isRequired = false; var hasChecked = false; var inputs = node.form[node.name]; /* * remember: the above expression may return an array * or a single value. Must check for this. */ if (inputs.length == undefined) { if (inputs.getAttributeNode('required')) isRequired = true; if (inputs.checked) hasChecked = true; } else { for (var i = 0; i < inputs.length; i++) { if (inputs[i].getAttributeNode('required')) isRequired = true; if (inputs[i].checked) hasChecked = true; } } node.validity.valueMissing = (isRequired && !hasChecked); } } //The required attribute applies to all form controls except controls with the type hidden, // image inputs, buttons (submit, move-up, etc), and select and output elements. For // disabled or readonly controls, the attribute has no effect. else if(node.getAttributeNode('required')){ //if(node.options) // node.validity.valueMissing = (node.selectedIndex == -1); //For other controls, any non-empty value shall satisfy the required condition, // including a simple whitespace character. //else node.validity.valueMissing = (node.wf2Value == ''); } //if(node.options ? node.selectedIndex == -1 : node.value === '') // node.validity.valueMissing = true; // } if(!node.validity.valueMissing && node.wf2Value){ //patternMismatch -- The value of the control with a pattern attribute doesn't match the pattern. // If the control is empty, this flag must not be set. //If the pattern attribute is present but empty, it doesn't match any value, and thus the // patternMismatch flag shall be set whenever the control's value isn't empty. var patternAttr = node.getAttributeNode('pattern'); if(patternAttr){ //the pattern attribute must match the entire value, not just any subset (somewhat as if // it implied a ^(?: at the start of the pattern and a )$ at the end). var rePattern = new RegExp("^(?:" + patternAttr.value + ")$"); //The pattern must be compiled with the global, ignoreCase, and multiline flags disabled rePattern.global = false; rePattern.ignoreCase = false; rePattern.multiline = false; //When the pattern is not a valid regular expression, it is ignored for the purposes of // validation, as if it wasn't specified. if(rePattern) node.validity.patternMismatch = !rePattern.test(node.wf2Value); } //typeMismatch -- The data entered does not match the type of the control. For example, if the UA // allows uninterpreted arbitrary text entry for month controls, and the user has entered SEP02, // then this flag would be set. This code is also used when the selected file in a file upload // control does not have an appropriate MIME type. If the control is empty, this flag must not be set. if(isDateRelated || isTimeRelated) { //node.validity.typeMismatch = ((node.value = $wf2.parseISO8601(node.wf2Value, type)) == null); //node.wf2Value = node.value; } else { switch(type){ case 'number': case 'range': node.validity.typeMismatch = !$wf2.numberRegExp.test(node.wf2Value); // if(!node.validity.typeMismatch && node.getAttribute("step") != 'any'){ // if(node.step == undefined) // node.step = 1; // var val = Number(node.value); // node.validity.stepMismatch = (val == parseInt(val) && node.step != parseInt(node.step)); // } break; case 'email': //An e-mail address, following the format of the addr-spec token defined in RFC 2822 section // 3.4.1 [RFC2822], but excluding the CFWS subtoken everywhere, and excluding the FWS // subtoken everywhere except in the quoted-string subtoken. UAs could, for example, offer // e-mail addresses from the user's address book. (See below for notes on IDN.) //http://www.ietf.org/rfc/rfc2822 node.validity.typeMismatch = !$wf2.emailRegExp.test(node.wf2Value); break; case 'url': //An IRI, as defined by [RFC3987] (the IRI token, defined in RFC 3987 section 2.2). UAs could, // for example, offer the user URIs from his bookmarks. (See below for notes on IDN.) The value // is called url (as opposed to iri or uri) for consistency with CSS syntax and because it is // generally felt authors are more familiar with the term "URL" than the other, more technically // correct terms. //http://www.ietf.org/rfc/rfc3987 node.validity.typeMismatch = !$wf2.urlRegExp.test(node.wf2Value); break; } } if(!node.validity.patternMismatch && !node.validity.typeMismatch){ //To limit the range of values allowed by some of the above types, two new attributes are introduced, which // apply to the date-related, time-related, numeric, and file upload types: min and max //rangeUnderflow -- The numeric, date, or time value of a control with a min attribute is lower than // the minimum, or a file upload control has fewer files selected than the minimum. If the control // is empty or if the typeMismatch flag is set, this flag must not be set. //rangeOverflow -- The numeric, date, or time value of a control with a max attribute is higher than // the maximum, or a file upload control has more files selected than the maximum. If the control // is empty or if the typeMismatch flag is set, this flag must not be set. if(doCheckRange){ if(isNumberRelated){ //For numeric types (number and range) the value must exactly match the number type (numberRegExp) if(type == 'range'){ //For this type...max defaults to 100 node.max = (maxAttrNode && $wf2.numberRegExp.test(maxAttrNode.value)) ? Number(maxAttrNode.value) : 100; //node.min is set at the beginning of this function so that the min value can be set as the default value } else { if(minAttrNode && $wf2.numberRegExp.test(minAttrNode.value)) node.min = Number(minAttrNode.value); if(maxAttrNode && $wf2.numberRegExp.test(maxAttrNode.value)) node.max = Number(maxAttrNode.value); if(stepAttrNode && $wf2.numberRegExp.test(stepAttrNode.value)) node.step = Number(stepAttrNode.value); } node.validity.rangeUnderflow = (node.min != undefined && Number(node.wf2Value) < node.min); node.validity.rangeOverflow = (node.max != undefined && Number(node.wf2Value) > node.max); node.validity.stepMismatch = !$wf2.isValidNumber(node); //console.log('first: ', node.validity.stepMismatch, ',', node.getAttribute('type')) } //For file types it must be a sequence of digits 0-9, treated as a base ten integer. else if(type == 'file'){ if(minAttrNode && /^\d+$/.test(minAttrNode.value)) node.min = Number(minAttrNode.value); //If absent, or if the minimum value is not in exactly the expected format, there // is no minimum restriction, except for the ... file types, where the default is zero. else node.min = 0; if(maxAttrNode && /^\d+$/.test(maxAttrNode.value)) node.max = Number(maxAttrNode.value); //If absent, or if the maximum value is not in exactly the expected format, there is no // maximum restriction (beyond those intrinsic to the type), except for ... the file // type, where the default is 1. else node.max = 1; //node.validity.rangeUnderflow = (node.min != undefined && Number(node.value) < node.min); //node.validity.rangeOverflow = (node.max != undefined && Number(node.value) > node.max); } //Date related else { //For date and time types it must match the relevant format mentioned for that type, all fields // having the right number of digits, with the right separating punctuation. if(minAttrNode){ node.min = $wf2.parseISO8601(minAttrNode.value, type); node.validity.rangeUnderflow = (node.min && node.wf2Value < node.min); } if(maxAttrNode){ node.max = $wf2.parseISO8601(maxAttrNode.value, type); node.validity.rangeOverflow = (node.max && node.wf2Value > node.max); } } } //The step attribute controls the precision allowed for the date-related, time-related, and numeric types. //Note: webforms2 as of March 6, 2012 does not support step for time related inputs. Will revisit later. if(!(isDateRelated || isTimeRelated) && doCheckPrecision && !node.validity.rangeUnderflow && !node.validity.rangeOverflow){ //stepMismatch -- The value is not one of the values allowed by the step attribute, and the UA will // not be rounding the value for submission. Empty values and values that caused the typeMismatch // flag to be set must not cause this flag to be set. var stepAttrNode = $wf2.getOriginalAttrNode(node, 'step'); //node.getAttributeNode('step'); if(!stepAttrNode){ //The step attribute [for types datetime, datetime-local, and time] ... defaulting to 60 (one minute). //For time controls, the value of the step attribute is in seconds, although it may be a fractional // number as well to allow fractional times. The default value of the step // attribute for datetime, datetime-local and time controls is 60 (one minute). //The step [for type date] attribute specifies the precision in days, defaulting to 1. //The step [for type month] attribute specifies the precision in months, defaulting to 1. //The step [for type week] attribute specifies the precision in weeks, defaulting to 1. //For date controls, the value of the step attribute is in days, weeks, or months, for the date, // week, and month types respectively. The format is a non-negative integer; one or more digits // 0-9 interpreted as base ten. If the step is zero, it is interpreted as the default. The default // for the step attribute for these control types is 1. //The step [for types number and range] attribute specifies the precision, defaulting to 1. node.step = isTimeRelated ? 60 : 1; } //The literal value 'any' may be used as the value of the step attribute. This keyword indicates that // any value may be used (within the bounds of other restrictions placed on the control). else if(stepAttrNode.value == 'any') node.step = 'any'; //isStepAny = true; //The format of the step attribute is the number format described above, except that // the value must be greater than zero. else if($wf2.numberRegExp.test(stepAttrNode.value) && stepAttrNode.value > 0) node.step = Number(stepAttrNode.value); else node.step = isTimeRelated ? 60 : 1; if(node.step != 'any'){ node.wf2StepDatum = null; if(minAttrNode) node.wf2StepDatum = node.min; else if(maxAttrNode) node.wf2StepDatum = node.max; else node.wf2StepDatum = $wf2.zeroPoint[type] ? $wf2.zeroPoint[type] : 0; //The zero point for datetime controls is 1970-01-01T00:00:00.0Z, for datetime-local is // 1970-01-01T00:00:00.0, for date controls is 1970-01-01, for month controls is 1970-01, // for week controls is 1970-W01 (the week starting 1969-12-29 and containing 1970-01-01), // and for time controls is 00:00. var _step = node.step; if(type == 'month' && node.wf2StepDatum && node.wf2StepDatum.getUTCFullYear){ //var month1 = node.wf2StepDatum.getUTCFullYear()*12 + node.wf2StepDatum.getUTCMonth(); //var month2 = node.wf2Value.getUTCFullYear()*12 + node.wf2Value.getUTCMonth(); //node.validity.stepMismatch = (month2 - month1)%_step != 0; } else { switch(type){ case 'datetime': case 'datetime-local': case 'time': _step = parseInt(_step * 1000); //for millisecond comparisons break; case 'date': _step = parseInt(_step * 24*60*60*1000); break; case 'week': _step = parseInt(_step * 7*24*60*60*1000); break; } //For the control to be valid, the control's value must be an integral number of steps from the min value, // or, if there is no min attribute, the max value, or if there is neither attribute, from the zero point. //allow decimal places to the 1,000th place node.validity.stepMismatch = (Math.round((node.wf2Value - node.wf2StepDatum)*1000) % Math.round(_step*1000)) != 0; } } } } //[TEXTAREA] tooLong -- The value of a control with a maxlength attribute is longer than the attribute allows, // and the value of the control doesn't exactly match the control's default value. //[The maxlength] attribute must not affect the initial value (the DOM defaultValue attribute). It must only // affect what the user may enter and whether a validity error is flagged during validation. if(doMaxLengthCheck && node.maxLength >= 0 && node.wf2Value != node.defaultValue){ //A newline in a textarea's value must count as two code points for maxlength processing (because // newlines in textareas are submitted as U+000D U+000A). [[NOT IMPLEMENTED: This includes the // implied newlines that are added for submission when the wrap attribute has the value hard.]] //var matches = node.value.match(/((?<!\x0D|^)\x0A|\x0D(?!^\x0A|$))/g); //no negative lookbehind var shortNewlines = 0; var v = node.wf2Value; node.wf2ValueLength = v.length; for(var i = 1; i < v.length; i++){ if(v[i] === "\x0A" && v[i-1] !== "\x0D" || v[i] == "\x0D" && (v[i+1] && v[i+1] !== "\x0A")) node.wf2ValueLength++; } //The tooLong flag is used when this attribute is specified on a ... textarea control and the control // has more than the specified number of code points and the value doesn't match the control's default value. node.validity.tooLong = node.wf2ValueLength > node.maxLength; } } else { if(minAttrNode && $wf2.numberRegExp.test(minAttrNode.value)) node.min = Number(minAttrNode.value); if(maxAttrNode && $wf2.numberRegExp.test(maxAttrNode.value)) node.max = Number(maxAttrNode.value); if(stepAttrNode && $wf2.numberRegExp.test(stepAttrNode.value)) node.step = Number(stepAttrNode.value); } //customError -- The control was marked invalid from script. See the definition of the setCustomValiditiy() method. //console.log('second: ', node.validity.stepMismatch) node.validity.valid = !$wf2.hasInvalidState(node.validity); var nextSib = (node.nextSibling); if (nextSib && nextSib.nodeName != 'SPAN' && !$wf2.css.isMemberOfClass(nextSib, 'wf2-validityIndicator')) { nextSib = null; } if (node.validity.valid) { $wf2.css.removeClass(node, 'wf2_invalid'); $wf2.css.addClass(node, 'wf2_valid'); /* * If we have an .wf2-validityIndicator span after the input, mark these as well */ if (nextSib) { $wf2.css.removeClass(nextSib, 'wf2_invalid'); $wf2.css.addClass(nextSib, 'wf2_valid'); //console.log('valid: ', nextSib.className); } //node.className = node.className.replace(/\s?wf2_invalid/g, "") + ' wf2_valid'; } else { $wf2.css.removeClass(node, 'wf2_valid'); $wf2.css.addClass(node, 'wf2_invalid'); /* * If we have an .wf2-validityIndicator span after the input, mark these as well */ if (nextSib) { $wf2.css.removeClass(nextSib, 'wf2_valid'); $wf2.css.addClass(nextSib, 'wf2_invalid'); //node.className = node.className.replace(/\s?wf2_valid/g, "") + ' wf2_invalid'; //console.log('not valid: ', nextSib.className, 'value:', node.value, 'wtfValue:', node.wf2Value) } } if (isCustomErrorMessageSet) { node.validationMessage = customErrorMessage; } //This is now done onmousedown or onkeydown, just as Opera does //if(node.validity.valid){ // node.className = node.className.replace(/\s*\binvalid\b\s*/g, " "); //substitute for :invalid pseudo class // //if(node.wf2_errorMsg){ // // node.wf2_errorMsg.parentNode.removeChild(node.wf2_errorMsg); // // node.wf2_errorMsg = null; // //} // var errMsg = document.getElementById((node.id || node.name) + '_wf2_errorMsg'); // if(errMsg) // errMsg.parentNode.removeChild(errMsg); //} }, // checks to see if a number node has a value that is valid. isValidNumber: function (node) { //console.log(node.wf2Value + ", " + node.min + ", " + node.max + ", " + node.step); var n = (parseFloat(node.wf2Value) - parseFloat(node.min))/parseFloat(node.step); //console.log('!' , n == parseInt(n)) return (n == parseInt(n)); }, applyValidityInterface : function(node){ //console.log('applying ' + node.name) /* Webkit browsers need this */ if (!$wf2.hasNativeBubbles || (node.form && $wf2.getAttributeValue(node.form, 'data-webforms2-force-js-validation') == 'true')) { //console.log(node.name) if (node.type == 'submit' || node.type == 'button') { node.formNoValidate=true; } } /* Made a change here to ensure Google's unfinished native implementation is not used. */ else if((node.validity && node.validity.typeMismatch !== undefined)) {//MSIE needs the second test for some reason // console.log('bad implementation!! ' + node.id); return node; } try { node.validationMessage = ""; //ValidityState interface node.validity = $wf2.createValidityState(); node.willValidate = true; } catch (ex) { // do nothing. } var nodeName = node.nodeName.toLowerCase(); if(nodeName == 'button' || nodeName == 'fieldset'){ node.setCustomValidity = function(error){ throw $wf2.DOMException(9); //NOT_SUPPORTED_ERR }; node.checkValidity = function(){ return true; }; return node; } //node._updateValidityState = $wf2._updateValidityState; if (!node.setCustomValidity) { node.setCustomValidity = $wf2.controlSetCustomValidity; } node.checkValidity = $wf2.controlCheckValidity; //var type = (node.type ? node.type.toLowerCase() : (nodeName == 'input' ? 'text' : '')); var type = (node.getAttribute('type') ? node.getAttribute('type').toLowerCase() : node.type); if(/(hidden|button|reset|add|remove|move-up|move-down)/.test(type) || !node.name || node.disabled) { try { node.willValidate = false; } catch (ex) { // do nothing. } } else if(window.RepetitionElement) { var parent = node; while(parent = parent.parentNode){ if(parent.repetitionType == RepetitionElement.REPETITION_TEMPLATE){ try { node.willValidate = false; } catch (ex) { // do nothing. } break; } } } var handler = function(event){ return $wf2.updateValidityState(event.currentTarget || event.srcElement); }; //attempt to check validity live if(document.addEventListener){ node.addEventListener('change', handler, false); node.addEventListener('blur', handler, false); node.addEventListener('keyup', handler, false); } else if(window.attachEvent){ node.attachEvent('onchange', handler); node.attachEvent('onblur', handler); node.attachEvent('onkeyup', handler); } else { } return node; }, /* updateValidityState: function (node) { if (node.checkValidity()){ node.className = node.className.replace(/\s?wf2_invalid/, ""); } else { node.className += ' wf2_invalid'; } }, */ onsubmitValidityHandler : function(event){ var frm = event.currentTarget || event.srcElement; var r; // call routines other libraries have set to be run before // validation. for (var i=0; i<$wf2.callBeforeValidation.length; i++) { $wf2.callBeforeValidation[i](event); } if(!frm.checkValidity()){ if(event.preventDefault) event.preventDefault(); event.returnValue = false; r = false; } else { event.returnValue = true; r = true; } // call routines other libraries have set to be run before // validation. for (var i=0; i<$wf2.callAfterValidation.length; i++) { $wf2.callAfterValidation[i](event, r); } // Finally .. set a class on the form to indicate that a form // submit was attempted $wf2.css.addClass(frm, 'wf2_submitAttempted'); return r; }, controlSetCustomValidity : function(error){ if(error){ this.validationMessage = String(error); this.validity.customError = true; } else { this.validationMessage = ""; this.validity.customError = false; } this.validity.valid = !$wf2.hasInvalidState(this.validity); }, hasInvalidState : function(validity){ return validity.typeMismatch || validity.rangeUnderflow || validity.rangeOverflow || validity.stepMismatch || validity.tooLong || validity.patternMismatch || validity.valueMissing || validity.customError; }, createValidityState : function(){ return { typeMismatch : false, rangeUnderflow : false, rangeOverflow : false, stepMismatch : false, tooLong : false, patternMismatch : false, valueMissing : false, customError : false, valid : true }; }, //## Default action functions for invalid events ################################################## invalidIndicators : [], indicatorTimeoutId : null, indicatorIntervalId : null, stepUnits : { 'datetime' : 'second', 'datetime-local': 'second', 'time': 'second', 'date': 'day', 'week': 'week', 'month': 'month' }, invalidMessages : { valueMissing : 'Please fill out this field.', typeMismatch : 'Please enter a valid %s.', rangeUnderflow : 'The value must be equal to or greater than %s.', rangeOverflow : 'The value must be equal to or less than %s.', stepMismatch : 'The value has a step mismatch; it must be a certain number multiples of %s from %s.', tooLong : 'The value is too long. The field may have a maximum of %s characters but you supplied %s. Note that each line-break counts as two characters.', patternMismatch: 'Please match the requested format.' }, valueToWF2Type : function(value, type){ switch(String(type).toLowerCase()){ case 'datetime': case 'datetime-local': case 'date': case 'month': case 'week': case 'time': return $wf2.dateToISO8601(value, type); default: return value; } }, addInvalidIndicator : function(target){ //show contextual help message var msg = document.createElement('div'); msg.className = 'wf2_errorMsg wf2_fromForm_' + (target.form.id || target.form.name); //Let's put in a container in here so IE9- can put a gradient filter in here. /* var msgContainer = document.createElement('div'); msgContainer.className = 'wf2_errorMsgContainer'; msg.appendChild(msgContainer); */ var IEmsgContainer = document.createElement('div'); IEmsgContainer.className = 'wf2_IEerrorMsgContainer'; msg.appendChild(IEmsgContainer); var msgContainer = document.createElement('div'); msgContainer.className = 'wf2_errorMsgContainer'; IEmsgContainer.appendChild(msgContainer); //msg.title = "Close"; msg.id = (target.id || target.name) + '_wf2_errorMsg'; //QUESTION: does this work for MSIE? msg.onmousedown = function(){ this.parentNode.removeChild(this); }; //var type = String(target.getAttribute('type')).toLowerCase(); //var type = (target.type ? target.type.toLowerCase() : (target.nodeName.toLowerCase() == 'input' ? 'text' : '')); var type = (target.getAttribute('type') ? target.getAttribute('type').toLowerCase() : target.type); var isDateTimeRelated = (type == 'datetime' || type == 'datetime-local' || type == 'time' || type == 'date' || type == 'month' || type == 'week'); var ol = document.createElement('ol'); if(target.validity.customError) { ol.appendChild($wf2.createLI(target.validationMessage)); } else { if(target.validity.valueMissing) ol.appendChild($wf2.createLI($wf2.invalidMessages.valueMissing)); if(target.validity.typeMismatch) ol.appendChild($wf2.createLI($wf2.invalidMessages.typeMismatch.replace(/%s/, type))); if(target.validity.rangeUnderflow) ol.appendChild($wf2.createLI($wf2.invalidMessages.rangeUnderflow.replace(/%s/, $wf2.valueToWF2Type(target.min, type)))); if(target.validity.rangeOverflow) ol.appendChild($wf2.createLI($wf2.invalidMessages.rangeOverflow.replace(/%s/, $wf2.valueToWF2Type(target.max, type)))); if(target.validity.stepMismatch) ol.appendChild($wf2.createLI($wf2.invalidMessages.stepMismatch.replace(/%s/, target.step + ($wf2.stepUnits[type] ? ' ' + $wf2.stepUnits[type] + '(s)' : '')).replace(/%s/, $wf2.valueToWF2Type(target.wf2StepDatum, type)))); if(target.validity.tooLong) ol.appendChild($wf2.createLI($wf2.invalidMessages.tooLong.replace(/%s/, target.maxLength).replace(/%s/, target.wf2ValueLength ? target.wf2ValueLength : target.value.length))); if(target.validity.patternMismatch) ol.appendChild($wf2.createLI($wf2.invalidMessages.patternMismatch.replace(/%s/, target.title ? target.title : ' "' + target.getAttribute('pattern') + '"'))); } if(ol.childNodes.length == 1) ol.className = 'single'; msgContainer.appendChild(ol); ////remove existing error message //if(document.getElementById(msg.id)) // document.documentElement.removeChild(document.getElementById(msg.id)); //target.parentNode.insertBefore(msg, target); //Inserting error message next to element in question causes problems when the element has a positioned containing block var parent = document.body ? document.body : document.documentElement; if($wf2.invalidIndicators.length) //insert before other error messages so that it appears on top parent.insertBefore(msg, $wf2.invalidIndicators[$wf2.invalidIndicators.length-1].errorMsg); else //insert at the end of the document parent.insertBefore(msg, null); //target.wf2_errorMsg = msg; //if(target.style.display == 'none' || !target.offsetParent){ // var prevEl = target.previousSibling; // var nextEl = target.nextSibling; // var prevCount = 0, nextCount = 0; // while(prevEl && (prevEl.nodeType != 1 || (prevEl.style.display == 'none' || !prevEl.offsetParent)) && ++prevCount) // prevEl = prevEl.previousSibling; // while(nextEl && (nextEl.nodeType != 1 || (nextEl.style.display == 'none' || !nextEl.offsetParent)) && ++nextCount) // nextEl = nextEl.nextSibling; // // if(prevEl && prevCount > nextCount) // //} // this allows a border radius on the msg to play well with IE9 and lower's Gradient filter on the msgContainer. if (msg.currentStyle && msg.currentStyle.borderRadius && msgContainer.currentStyle.filter.indexOf('Gradient') > -1) { IEmsgContainer.style.borderRadius = msg.currentStyle.borderRadius; IEmsgContainer.style.overflow = 'hidden'; } var el = target; while(el && (el.nodeType != 1 || (el.style.display == 'none' || el.style.visibility == 'hidden' || !el.offsetParent))) el = el.parentNode; var top = left = 0; var cur = el; var s = $wf2.css.getAbsoluteCoords(el); top = s.y + el.offsetHeight; left = s.x; msg.style.top = top + 'px'; msg.style.left = left + 'px'; $wf2.invalidIndicators.push({ target : target, errorMsg : msg }); //if(target.form && target.form[target.name]){ // target.form[target.name].wf2HasInvalidIndicator = true; // console.info('set') //} /* if(!target.className.match(/\bwf2_invalid\b/)) target.className += " wf2_invalid"; */ $wf2.css.addClass(target, 'wf2_invalid'); // WR: We removed the commented stuff below because it is not needed for the new validation routines. /* if($wf2.indicatorIntervalId == null){ //var i = $wf2.invalidIndicators.length - 1; $wf2.indicatorIntervalId = setInterval(function(){ var invalidIndicator; for(var i = 0; invalidIndicator = $wf2.invalidIndicators[i]; i++){ if (!$wf2.css.isMemberOfClass(invalidIndicator.target, 'wf2_invalid')) { $wf2.css.addClass(invalidIndicator.target, 'wf2_invalid') } else { $wf2.css.removeClass(invalidIndicator.target, 'wf2_invalid'); } } }, 500); return; // call routines other libraries have set to be run before // validation. setTimeout(function() { for (var i=0; i<$wf2.callAfterValidation.length; i++) { $wf2.callAfterValidation[i](null, false); } }, 1); $wf2.indicatorTimeoutId = setTimeout($wf2.clearInvalidIndicators, 4000); }*/ }, clearInvalidIndicators : function(e){ if (e.type == 'mousedown') { var coords = $wf2.css.getMouseCoords(e), browserWidth = $wf2.css.getWindowWidth(), offset = (window.innerWidth )?16:0; if (coords.x >= browserWidth - offset) { return; } } clearTimeout($wf2.indicatorTimeoutId); $wf2.indicatorTimeoutId = null; clearInterval($wf2.indicatorIntervalId); $wf2.indicatorIntervalId = null; var invalidIndicator; while(invalidIndicator = $wf2.invalidIndicators[0]){ if(invalidIndicator.errorMsg && invalidIndicator.errorMsg.parentNode) invalidIndicator.errorMsg.parentNode.removeChild(invalidIndicator.errorMsg); //clearInterval(insts[0].intervalId); var target = invalidIndicator.target; // Removed // target.className = target.className.replace(/\s?wf2_invalid/, ""); //([^\b]\s)? $wf2.invalidIndicators.shift(); } }, /*############################################################################################## # Other helper functions (not made into methods) ##############################################################################################*/ cloneNode_customAttrs : { //FOR MSIE BUG: it cannot perceive the attributes that were actually specified 'type':1,'template':1,'repeat':1,'repeat-template':1,'repeat-min':1, 'repeat-max':1,'repeat-start':1,'value':1,'class':1,'required':1, 'pattern':1,'form':1,'autocomplete':1,'autofocus':1,'inputmode':1, 'max':1,'min':1,'step':1, onmoved:1,onadded:1,onremoved:1, onadd:1,onremove:1,onmove:1 //deprecated }, cloneNode_skippedAttrs : { 'name':1, //due to MSIE bug, set via $wf2.createElement 'class':1, //due to MSIE bug, set below (see http://www.alistapart.com/articles/jslogging) 'for':1, //due to preceived MSIE bug, set below 'style':1, //inline styles require special handling 'checked':1, //set by $wf2.createElement due to MSIE bug creating INPUT@type=radio //for MSIE, properties (or methods) == attributes addRepetitionBlock:1,addRepetitionBlockByIndex:1,moveRepetitionBlock:1, removeRepetitionBlock:1, repetitionBlocks:1, setCustomValidity:1,checkValidity:1,validity:1,validationMessage:1,willValidate:1, wf2StepDatum:1,wf2Value:1,wf2Initialized:1,wf2ValueLength:1 }, cloneNode_rtEventHandlerAttrs : { onmoved:1,onadded:1,onremoved:1, //don't copy Repetition old model event attributes not methods onadd:1,onremove:1,onmove:1 //deprecated //QUESTION: is this right??? }, //The following cloneNode algorithm was designed to handle the attribute processing that the repetition // model specifies. Gecko starts to have irratic behavior with a cloned input's value attribute and value // property when using DOM cloneNode; furthermore, various MSIE bugs prevent its use of DOM cloneNode cloneNode : function (node, processAttr, rtNestedDepth){ if(!rtNestedDepth) rtNestedDepth = 0; var clone, i, attr; switch(node.nodeType){ case 1: /*Node.ELEMENT_NODE*/ //if(node.nodeType == 1 /*Node.ELEMENT_NODE*/){ var isTemplate = node.getAttribute('repeat') == 'template'; if(isTemplate) rtNestedDepth++; //BROWSER BUGS: MSIE does not allow the setting of the node.name, except when creating the new node // MSIE neither permits the standard DOM creation of radio buttons var attrs = []; if(node.name) attrs.name = processAttr ? processAttr(node.name) : node.name; if(node.type == 'radio') attrs.type = node.type; if(node.checked) attrs.checked = 'checked'; clone = $wf2.createElement(node.nodeName, attrs); //clone = node.name ? // $wf2.createElement(node.nodeName, attrs) // : document.createElement(node.nodeName); for(i = 0; attr = node.attributes[i]; i++){ //MSIE ISSUE: Custom attributes specified do not have .specified property set to true? //ISSUE: VALUE IS REPEATED IN MSIE WHEN VALUE ATTRIBUTE SET? //if(attr.specified || node.getAttribute(attr.nodeName)) //$wf2.cloneNode_customAttrs[attr.nodeName] || // if(window.console && console.info) console.info(node.nodeName + "@" + attr.nodeName + " -- " + attr.specified + " <font color=red>" + node.getAttribute(attr.nodeName) + "</font>(" + typeof node.getAttribute(attr.nodeName) + ")<br>"); //MSIE needs $wf2.cloneNode_customAttrs[attr.name] test since attr.specified does not work with custom attributes //If the node is a template, the repetition event handlers should only be copied // if the template is nested and is being cloned by a parent repetition template. if((attr.specified || $wf2.cloneNode_customAttrs[attr.name]) && !$wf2.cloneNode_skippedAttrs[attr.name] && ( (!isTemplate || (rtNestedDepth > 1 || !$wf2.cloneNode_rtEventHandlerAttrs[attr.name])) // && )) { //MSIE BUG: when button[type=add|remove|move-up|move-down], then (attr.nodeValue and attr.value == 'button') but node.getAttribute(attr.nodeName) == 'add|remove|move-up|move-down' (as desired) //clone and process an event handler property (attribute); // keep event handler attributes as plain text if nested repetition template if(rtNestedDepth < 2 && (attr.name.indexOf('on') === 0) && (typeof node[attr.name] == 'function')){ var funcBody = processAttr(node[attr.name].toString().match(/{((?:.|\n)+)}/)[1]); funcBody = processAttr(funcBody); clone[attr.name] = new Function('event', funcBody); } //clone and process other attributes else { var attrValue = node.getAttribute(attr.name); attrValue = (processAttr ? processAttr(attrValue) : attrValue); clone.setAttribute(attr.name, attrValue); } //console.log(StringHelpers.sprintf("AFTER attr: %s val: %s (%s)", attr.name, attr.value, processAttr(attrValue))) } } //MSIE BUG: setAttribute('class') creates duplicate value attribute in MSIE; //QUESTION: will setting className on this clonedNode still cause this error later on for users? will addClassName croak? Should it be improved? //see: http://www.alistapart.com/articles/jslogging if(node.className){ var _className = (processAttr ? processAttr(node.className) : node.className); if(clone.getAttributeNode('class')){ for(i = 0; i < clone.attributes.length; i++) { if(clone.attributes[i].name == 'class') clone.attributes[i].value = _className; } } else clone.setAttribute('class', _className); } //Restore the template's elements to the originally coded disabled state (indicated by 'disabled' class name) // All elements within the repetition template are disabled to prevent them from being successful. if(!/\bdisabled\b/.test(node.className)) clone.disabled = false; //Process the inline style if(node.style && node.style.cssText){ //clone.setAttribute('style', processAttr(node.style.cssText)); clone.style.cssText = (processAttr ? processAttr(node.style.cssText) : node.style.cssText); } //label's 'for' attribute, set here due to MSIE bug if(node.nodeName && node.nodeName.toLowerCase() == 'label' && node.htmlFor) clone.htmlFor = (processAttr ? processAttr(node.htmlFor) : node.htmlFor); if(clone.nodeName.toLowerCase() == 'option'){ //MSIE clone element bug requires this clone.selected = node.selected; clone.defaultSelected = node.defaultSelected; } for(i = 0; i < node.childNodes.length; i++){ clone.appendChild($wf2.cloneNode(node.childNodes[i], processAttr, rtNestedDepth)); } break; //MSIE BUG: The following three cases are for MSIE because when cloning nodes from XML // files loaded via SELECT@data attribute, MSIE fails when performing appendChild. case 3: /*Node.TEXT_NODE*/ case 4: /*Node.CDATA_SECTION_NODE*/ clone = document.createTextNode(node.data); break; case 8: /*Node.COMMENT_NODE*/ clone = document.createComment(node.data); break; default: clone = node.cloneNode(true) } //else clone = node.cloneNode(true); return clone; }, getFormElements : function(){ var elements = []; var allElements = $wf2.getElementsByTagNames.apply(this, ['input','output','select','textarea','button']); //fieldset for(var i = 0; i < allElements.length; i++){ var node = allElements[i].parentNode; while(node && node.nodeType == 1 && node.getAttribute('repeat') != 'template') node = node.parentNode; if(!node || node.nodeType != 1) elements.push(allElements[i]); } return elements; }, loadDataURI : function(el){ var uri = el.data || el.getAttribute('data'); if(!uri) return null; var doc = null, matches; try { if(matches = uri.match(/^data:[^,]*xml[^,]*,((?:.|\n)+)/)){ var xml = decodeURI(matches[1].replace(/%3D/ig, '=').replace(/%3A/ig, ':').replace(/%2F/ig, '/')); if(window.DOMParser){ var parser = new DOMParser(); doc = parser.parseFromString(xml, 'text/xml'); } else if(window.ActiveXObject){ doc = new ActiveXObject("Microsoft.XMLDOM"); doc.async = 'false'; doc.loadXML(xml); } } else { $wf2.xhr.open('GET', uri, false); $wf2.xhr.send(null); //Note: if in Firefox and null not provided, and if Firebug is disabled, an error occurs doc = $wf2.xhr.responseXML; } } catch(e){ return null; } return doc; }, getAttributeByName: function (obj, attrName) { var i; var attributes = obj.attributes; for (i=0; i<attributes.length; i++) { var attr = attributes[i] if (attr.nodeName == attrName && attr.specified) { return attr; } } return null; }, getAttributeValue: function (obj, attrName) { var attr = $wf2.getAttributeByName(obj, attrName); if (attr != null) { return attr.nodeValue; } else { return null; } }, setAttributeValue: function (obj, attrName, attrValue) { var attr = $wf2.getAttributeByName(obj, attrName); if (attr != null) { attr.nodeValue = attrValue; } else { return; } }, getDatasetItem: function (obj, name) { var r = $wf2.getAttributeValue(obj, 'data-' + name); if (!r) { r = $wf2.getAttributeValue(obj, 'data-' + name.toLowerCase()) } if (!r) { r = obj['data-' + name.toLowerCase()] } return r; }, setDatasetItem: function (obj, name, value) { var attrName = 'data-' + name.toLowerCase(); var val = $wf2.setAttributeValue(obj, attrName, value); if ($wf2.getAttributeValue(obj, attrName) == null) { obj[attrName] = value; } }, getElementsByTagNames : function(/* ... */){ var els,i,results = []; if(document.evaluate){ var _tagNames = []; for(i = 0; i < arguments.length; i++) _tagNames.push(".//" + arguments[i]); els = document.evaluate(_tagNames.join('|'), this, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(i = 0; i < els.snapshotLength; i++) results.push(els.snapshotItem(i)); } else { for(i = 0; i < arguments.length; i++){ els = this.getElementsByTagName(arguments[i]); for(var j = 0; j < els.length; j++){ results.push(els[j]); } } if($wf2.sortNodes) results.sort($wf2.sortNodes); } return results; }, getElementsByTagNamesAndAttribute : function(elNames, attrName, attrValue, isNotEqual){ var els,el,i,j,results = []; //QUESTION!!! Can we exclude all nodes that are not decendents of the repetition template? if(document.evaluate){ var attrExpr = ''; if(attrName) attrExpr = '[@' + attrName + (attrValue ? (isNotEqual ? '!=' : '=') + '"' + attrValue + '"' : "") + "]"; var xPaths = []; for(i = 0; i < elNames.length; i++) xPaths.push('.//' + elNames[i] + attrExpr); els = document.evaluate(xPaths.join('|'), this, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(i = 0; i < els.snapshotLength; i++) results.push(els.snapshotItem(i)); } else { for(i = 0; i < elNames.length; i++){ els = this.getElementsByTagName(elNames[i]); for(j = 0; el = els[j]; j++){ var thisAttrNode = el.getAttributeNode(attrName); var thisAttrValue = el.getAttribute(attrName); //MSIE needs getAttribute here for custom button types to be read if(!attrName || (thisAttrNode && (attrValue === undefined || (isNotEqual ? thisAttrValue != attrValue : thisAttrValue == attrValue) ))){ results.push(el); } } } if($wf2.sortNodes) results.sort($wf2.sortNodes); } return results; }, arrayHasItem : function(arr, item){ for(var i = 0; i < arr.length; i++){ if(arr[i] == item) return true; } return false; }, //Note: this function has been deemed harmful getElementStyle : function(el, property) { //adapted from Danny Goodman <http://www.oreillynet.com/pub/a/javascript/excerpt/JSDHTMLCkbk_chap5/index5.html> if(el.currentStyle) return el.currentStyle[property]; else if(window.getComputedStyle) return getComputedStyle(el, '').getPropertyValue(property); else if(el.style) return el.style[property]; else return ''; }, //createElement code based on Anthony Lieuallen's work <http://www.easy-reader.net/archives/2005/09/02/death-to-bad-dom-implementations/#comment-444> // The following function enables MSIE to create elements with the name attribute set, per MSDN: // The NAME attribute cannot be set at run time on elements dynamically created with the // createElement method. To create an element with a name attribute, include the attribute // and value when using the createElement method. // The same goes for creating radio buttons and creating defaultly checked checkboxes, // per <http://channel9.msdn.com/wiki/default.aspx/Channel9.InternetExplorerProgrammingBugs> createElement : (function(){ try { var el = document.createElement('<div name="foo">'); //MSIE memory leak according to Drip if(el.tagName.toLowerCase() != 'div' || el.name != 'foo') throw 'create element error'; return function(tag, attrs){ var html = '<' + tag; for(var name in attrs) html += ' ' + name + '="' + attrs[name] + '"'; html += '>'; if(tag.toLowerCase() != 'input') html += '</'+tag+'>'; return document.createElement(html); //'<'+tag+' name="'+name+'"></'+tag+'>' }; } catch(err){ return function(tag, attrs){ var el = document.createElement(tag); for(var name in attrs) el.setAttribute(name, attrs[name]); return el; }; } })(), //Sort elements in document order (from ppk) sortNodes : (function(){ var n = document.documentElement.firstChild; if(n.sourceIndex){ return function (a,b){ return a.sourceIndex - b.sourceIndex; }; } else if(n.compareDocumentPosition){ return function (a,b){ return 3 - (a.compareDocumentPosition(b) & 6); }; } })(), //Shortcut to create new list items; used by default invalid event handler in listing the errors createLI : function(text){ var li = document.createElement('li'); li.appendChild(document.createTextNode(text)); return li; }, //Initially inspired by Paul Sowden <http://delete.me.uk/2005/03/iso8601.html> ISO8601RegExp : /^(?:(\d\d\d\d)-(W(0[1-9]|[1-4]\d|5[0-2])|(0\d|1[0-2])(-(0\d|[1-2]\d|3[0-1])(T(0\d|1\d|2[0-4]):([0-5]\d)(:([0-5]\d)(\.(\d+))?)?(Z)?)?)?)|(0\d|1\d|2[0-4]):([0-5]\d)(:([0-5]\d)(\.(\d+))?)?)$/, parseISO8601 : function (str, type) { var d = $wf2.validateDateTimeType(str, type); if(!d) return null; var date = new Date(0); var _timePos = 8; if(d[15]){ //Time if(type && type != 'time') // a time date return null; _timePos = 15; } else { date.setUTCFullYear(d[1]); //ISO8601 Week if(d[3]){ if(type && type != 'week') return null; date.setUTCDate(date.getUTCDate() + ((8 - date.getUTCDay()) % 7) + (d[3]-1)*7); //set week day and week return date; } //Other date-related types else { date.setUTCMonth(d[4] - 1); //Month must be supplied for WF2 if(d[6]) date.setUTCDate(d[6]); } } //Set time-related fields if(d[_timePos+0]) date.setUTCHours(d[_timePos+0]); if(d[_timePos+1]) date.setUTCMinutes(d[_timePos+1]); if(d[_timePos+2]) date.setUTCSeconds(d[_timePos+3]); if(d[_timePos+4]) date.setUTCMilliseconds(Math.round(Number(d[_timePos+4]) * 1000)); //Set to local time if date given, hours present and no 'Z' provided if(d[4] && d[_timePos+0] && !d[_timePos+6]) date.setUTCMinutes(date.getUTCMinutes()+date.getTimezoneOffset()); return date; }, validateDateTimeType : function(value, type){ //returns RegExp matches var isValid = false; var d = $wf2.ISO8601RegExp.exec(value); //var d = string.match(new RegExp(regexp)); if(!d || !type) return d; type = type.toLowerCase(); if(type == 'week') // a week date isValid = (d[2].toString().indexOf('W') === 0); //valid if W present else if(type == 'time') // a time date isValid = !!d[15]; else if(type == 'month') isValid = !d[5]; else { //a date related value //Verify that the number of days in the month are valid if(d[6]){ var date = new Date(d[1], d[4]-1, d[6]); if(date.getMonth() != d[4]-1) isValid = false; else switch(type){ case 'date': isValid = (d[4] && !d[7]); //valid if day of month supplied and time field not present break; case 'datetime': isValid = !!d[14]; //valid if Z present break; case 'datetime-local': isValid = (d[7] && !d[14]); //valid if time present and Z not provided break; } } } return isValid ? d : null; }, zeroPad : function(num, pad){ if(!pad) pad = 2; var str = num.toString(); while(str.length < pad) str = '0' + str; return str; }, dateToISO8601 : function(date, type){ type = String(type).toLowerCase(); var ms = ''; if(date.getUTCMilliseconds()) ms = '.' + $wf2.zeroPad(date.getUTCMilliseconds(), 3).replace(/0+$/,''); switch(type){ case 'date': return date.getUTCFullYear() + '-' + $wf2.zeroPad(date.getUTCMonth()+1) + '-' + $wf2.zeroPad(date.getUTCDate()); case 'datetime-local': return date.getFullYear() + '-' + $wf2.zeroPad(date.getMonth()+1) + '-' + $wf2.zeroPad(date.getDate()) + 'T' + $wf2.zeroPad(date.getHours()) + ':' + $wf2.zeroPad(date.getMinutes()) + ':' + $wf2.zeroPad(date.getMinutes()) + ms + 'Z'; case 'month': return date.getUTCFullYear() + '-' + $wf2.zeroPad(date.getUTCMonth()+1); case 'week': var week1 = $wf2.parseISO8601(date.getUTCFullYear() + '-W01'); return date.getUTCFullYear() + '-W' + $wf2.zeroPad(((date.valueOf() - week1.valueOf()) / (7*24*60*60*1000)) + 1); case 'time': return $wf2.zeroPad(date.getUTCHours()) + ':' + $wf2.zeroPad(date.getUTCMinutes()) + ':' + $wf2.zeroPad(date.getUTCMinutes()) + ms; case 'datetime': default: return date.getUTCFullYear() + '-' + $wf2.zeroPad(date.getUTCMonth()+1) + '-' + $wf2.zeroPad(date.getUTCDate()) + 'T' + $wf2.zeroPad(date.getUTCHours()) + ':' + $wf2.zeroPad(date.getUTCMinutes()) + ':' + $wf2.zeroPad(date.getUTCMinutes()) + ms + 'Z'; } }, /* * Fires an event manually. * @author Scott Andrew - http://www.scottandrew.com/weblog/articles/cbs-events * @author John Resig - http://ejohn.org/projects/flexible-javascript-events/ * @param {Object} obj - a javascript object. * @param {String} evType - an event attached to the object. * @param {Function} fn - the function that is called when the event fires. * */ fireEvent: function (element, event, options){ if(!element) { return; } if (document.createEventObject){ return element.fireEvent('on' + event, $wf2.globalEvent); } else{ // dispatch for firefox + others $wf2.globalEvent.initEvent(event, true, true); // event type,bubbling,cancelable return !element.dispatchEvent($wf2.globalEvent); } }, //Emulation of DOMException DOMException : function(code){ var message = 'DOMException: '; switch(code){ case 1: message += 'INDEX_SIZE_ERR'; break; case 9: message += 'NOT_SUPPORTED_ERR'; break; case 11: message += 'INVALID_STATE_ERR'; break; case 12: message += 'SYNTAX_ERR'; break; case 13: message += 'INVALID_MODIFICATION_ERR'; break; } var err = new Error(message); err.code = code; err.name = 'DOMException'; //Provide error codes and messages for the exception types that are raised by WF2 err.INDEX_SIZE_ERR = 1; err.NOT_SUPPORTED_ERR = 9; err.INVALID_STATE_ERR = 11; err.SYNTAX_ERR = 12; err.INVALID_MODIFICATION_ERR = 13; //with($wf2.DOMException.prototype){ // INDEX_SIZE_ERR = 1; // DOMSTRING_SIZE_ERR = 2; // HIERARCHY_REQUEST_ERR = 3; // WRONG_DOCUMENT_ERR = 4; // INVALID_CHARACTER_ERR = 5; // NO_DATA_ALLOWED_ERR = 6; // NO_MODIFICATION_ALLOWED_ERR = 7; // NOT_FOUND_ERR = 8; // NOT_SUPPORTED_ERR = 9; // INUSE_ATTRIBUTE_ERR = 10; // INVALID_STATE_ERR = 11; // SYNTAX_ERR = 12; // INVALID_MODIFICATION_ERR = 13; // NAMESPACE_ERR = 14; // INVALID_ACCESS_ERR = 15; //}; return err; }, css: new function () { var me = this; var blankRe = new RegExp('\\s'); /** * Generates a regular expression string that can be used to detect a class name * in a tag's class attribute. It is used by a few methods, so I * centralized it. * * @param {String} className - a name of a CSS class. */ function getClassReString(className) { return '\\s'+className+'\\s|^' + className + '\\s|\\s' + className + '$|' + '^' + className +'$'; } function getClassPrefixReString(className) { return '\\s'+className+'-[0-9a-zA-Z_]+\\s|^' + className + '[0-9a-zA-Z_]+\\s|\\s' + className + '[0-9a-zA-Z_]+$|' + '^' + className +'[0-9a-zA-Z_]+$'; } /** * Make an HTML object be a member of a certain class. * * @param {Object} obj - an HTML object * @param {String} className - a CSS class name. */ me.addClass = function (obj, className) { if (blankRe.test(className)) { return; } // only add class if the object is not a member of it yet. if (!me.isMemberOfClass(obj, className)) { obj.className += " " + className; } } /** * Make an HTML object *not* be a member of a certain class. * * @param {Object} obj - an HTML object * @param {Object} className - a CSS class name. */ me.removeClass = function (obj, className) { if (blankRe.test(className)) { return; } var re = new RegExp(getClassReString(className) , "g"); var oldClassName = obj.className; if (obj.className) { obj.className = oldClassName.replace(re, ' '); } } /** * Determines if an HTML object is a member of a specific class. * @param {Object} obj - an HTML object. * @param {Object} className - the CSS class name. */ me.isMemberOfClass = function (obj, className) { if (blankRe.test(className)) return false; var re = new RegExp(getClassReString(className) , "g"); return (re.test(obj.className)); } me.getAbsoluteCoords = function(obj) { var curleft = obj.offsetLeft; var curtop = obj.offsetTop; /* * IE and Gecko */ if (obj.getBoundingClientRect) { var temp = obj.getBoundingClientRect(); curleft = temp.left + me.getScrollX(); curtop = temp.top + me.getScrollY(); } else { /* Everything else must do the quirkmode.org way */ if (obj.offsetParent) { while (obj = obj.offsetParent) { curleft += obj.offsetLeft - obj.scrollLeft; curtop += obj.offsetTop - obj.scrollTop; } } } return { x: curleft, y: curtop }; } /** * Get the the amount of pixels the window has been scrolled from the top. If there is no * vertical scrollbar, this function return 0. * * @return {int} - the amount of pixels the window has been scrolled to the right, in pixels. */ me.getScrollX = function (myWindow) { var myDocument; if (myWindow) { myDocument = myWindow.document; } else { myWindow = window; myDocument = document; } // All except that I know of except IE if (myWindow.pageXOffset != null) { return myWindow.pageXOffset; // IE 6.x strict } else if (myDocument.documentElement != null && myDocument.documentElement.scrollLeft !="0px" && myDocument.documentElement.scrollLeft !=0) { return myDocument.documentElement.scrollLeft; // all other IE } else if (myDocument.body != null && myDocument.body.scrollLeft != null) { return myDocument.body.scrollLeft; // if for some reason none of the above work, this should. } else if (myWindow.scrollX != null) { return myWindow.scrollX; } else { return null; } } /** * Get the the amount of pixels the window has been scrolled to the right. If there is no * horizontal scrollbar, this function return 0. * * @return {int} - the amount of pixels the window has been scrolled to the right, in pixels. */ me.getScrollY = function(myWindow) { var myDocument; if (myWindow) { myDocument = myWindow.document; } else { myWindow = window; myDocument = document; } // All except that I know of except IE if (myWindow.pageYOffset != null) { return myWindow.pageYOffset; // IE 6.x strict } else if (myDocument.documentElement != null && myDocument.documentElement.scrollTop !="0px" && myDocument.documentElement.scrollTop !=0) { return myDocument.documentElement.scrollTop; // all other IE } else if (myDocument.body && myDocument.body.scrollTop != null) { return myDocument.body.scrollTop; // if for some reason none of the above work, this should. } else if (myWindow.scrollY != null) { return myWindow.scrollY; } else { return null; } } /** * gets the current window's width. * * @author Peter-Paul Koch - http://www.quirksmode.org * @license see http://www.quirksmode.org/about/copyright.html * @return {int} - the window's width, in pixels. */ me.getWindowWidth = function (theWindow) { if (!theWindow) { theWindow = window; } var theDocument = theWindow.document; // all except IE if (theWindow.innerWidth != null) { return theWindow.innerWidth; // IE6 Strict mode } else if (theDocument.documentElement && theDocument.documentElement.clientWidth ) { return theDocument.documentElement.clientWidth; // IE strictly less than 6 } else if (theDocument.body != null) { return theDocument.body.clientWidth; } else { return null; } } /** * gets the current window's height. * * @author Peter-Paul Koch - http://www.quirksmode.org * @license see http://www.quirksmode.org/about/copyright.html * @return {int} - the window's height in pixels. */ me.getWindowHeight = function (theWindow) { if (!theWindow) { theWindow = window; } var theDocument = theWindow.document; // all except IE if (theWindow.innerHeight != null) { return theWindow.innerHeight; // IE6 Strict mode } else if (theDocument.documentElement && theDocument.documentElement.clientHeight ) { return theDocument.documentElement.clientHeight; // IE strictly less than 6 } else if (theDocument.body != null) { return theDocument.body.clientHeight; } else { return null; } } me.getMouseCoords = function (e) { if (!e) { return; } // IE if (e.clientX != null) { return { x: e.clientX, y: e.clientY } } // NS4 else if (e.pageX != null) { return { x: e.pageX, y: e.pageY } // W3C } else if (window.event != null && window.event.clientX != null && document.body != null && document.body.scrollLeft != null) { return { x: window.event.clientX + document.body.scrollLeft, y: window.event.clientY + document.body.scrollTop } } else { return null; } } } }; //End $wf2 = { /*############################################################################################## # Change the prototypes of HTML elements ##############################################################################################*/ /*############################################################################################## # Set mutation event handlers to automatically add WF2 behaviors ##############################################################################################*/ //When a form control is inserted into a document, the UA must check to see if it has [the autofocus] // attribute set. If it does, and the control is not disabled, and it is of a type normally // focusable in the user's operating environment, then the UA should focus the control, as if // the control's focus() method was invoked. UAs with a viewport should also scroll the document // enough to make the control visible, even if it is not of a type normally focusable. //REVISE: there should be one handler for all attr events on the page. if(document.addEventListener){ document.addEventListener('DOMNodeInsertedIntoDocument', function(evt){ //DOMNodeInserted? DOMNodeInsertedIntoDocument if(evt.target.nodeType == 1 && evt.target.hasAttribute('autofocus')){ $wf2.initAutofocusElement(evt.target); } //[[UAs may ignore this attribute if the user has indicated (for example, by starting to type in a // form control) that he does not wish focus to be changed.]] }, false); //NOT CURRENTLY IMPLEMENTABLE: // Setting the DOM attribute to true must set the content attribute to the value autofocus. // Setting the DOM attribute to false must remove the content attribute. document.addEventListener('DOMAttrModified', function(evt){ //The autofocus DOM attribute must return true when the content attribute is present (regardless // of its value, even if it is the empty string), and false when it is absent. if(evt.attrName == 'autofocus'){ if(evt.attrChange == evt.ADDITION) //evt.relatedNode.autofocus = true; $wf2.initAutofocusElement(evt.target); else if(evt.attrChange == evt.REMOVAL) evt.target.autofocus = false; } }, false); } (function(){ //Get the path to the library base directory var match; //For some reason, if not using documentElement, scriptaculous fails to load if reference to // webforms2 script placed beforehand in Firefox var scripts = document.documentElement.getElementsByTagName('script'); for(var i = 0; i < scripts.length; i++){ if(match = scripts[i].src.match(/^(.*)webforms2[^\/]+$/)) $wf2.libpath = match[1]; } EventHelpers.addPageLoadEvent('$wf2.onDOMContentLoaded', true) })(); } //End If(!document.implementation... } //end if(!window.$wf2) //if(!window.ValidityState){ //var ValidityState = { // //}; //if(HTMLElement.prototype) //ValidityState interface //node.validity = { // typeMismatch : false, // rangeUnderflow : false, // rangeOverflow : false, // stepMismatch : false, // tooLong : false, // patternMismatch : false, // valueMissing : false, // customError : false, // valid : true //}; //} //if(!window.HTMLOutputElement){ //}