/* * Web Forms 2.0 Cross-browser Implementation * Version: 0.7 (2011-03-01) * Copyright: 2007, Weston Ruter * with additions by Zoltan Hawryluk * 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: * * Usage: * * 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