1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 1997-2016 Sun Microsystems, Inc. All rights reserved. 5 * 6 * The contents of this file are subject to the terms of either the GNU 7 * General Public License Version 2 only ("GPL") or the Common Development 8 * and Distribution License("CDDL") (collectively, the "License"). You 9 * may not use this file except in compliance with the License. You can obtain 10 * a copy of the License at https://glassfish.java.net/public/CDDL+GPL.html 11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific 12 * language governing permissions and limitations under the License. 13 * 14 * When distributing the software, include this License Header Notice in each 15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt. 16 * Sun designates this particular file as subject to the "Classpath" exception 17 * as provided by Sun in the GPL Version 2 section of the License file that 18 * accompanied this code. If applicable, add the following below the License 19 * Header, with the fields enclosed by brackets [] replaced by your own 20 * identifying information: "Portions Copyrighted [year] 21 * [name of copyright owner]" 22 * 23 * Contributor(s): 24 * 25 * If you wish your version of this file to be governed by only the CDDL or 26 * only the GPL Version 2, indicate your decision by adding "[Contributor] 27 * elects to include this software in this distribution under the [CDDL or GPL 28 * Version 2] license." If you don't indicate a single choice of license, a 29 * recipient has the option to distribute your version of this file under 30 * either the CDDL, the GPL Version 2 or to extend the choice of license to 31 * its licensees as provided above. However, if you add GPL Version 2 code 32 * and therefore, elected the GPL Version 2 license, then the option applies 33 * only if the new code is made subject to such option by the copyright 34 * holder. 35 * 36 * 37 * This file incorporates work covered by the following copyright and 38 * permission notices: 39 * 40 * Copyright 2004 The Apache Software Foundation 41 * Copyright 2004-2008 Emmanouil Batsis, mailto: mbatsis at users full stop sourceforge full stop net 42 * 43 * Licensed under the Apache License, Version 2.0 (the "License"); 44 * you may not use this file except in compliance with the License. 45 * You may obtain a copy of the License at 46 * 47 * http://www.apache.org/licenses/LICENSE-2.0 48 * 49 * Unless required by applicable law or agreed to in writing, software 50 * distributed under the License is distributed on an "AS IS" BASIS, 51 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 52 * See the License for the specific language governing permissions and 53 * limitations under the License. 54 */ 55 56 /** 57 @project JSF JavaScript Library 58 @version 2.2 59 @description This is the standard implementation of the JSF JavaScript Library. 60 */ 61 62 // Detect if this is already loaded, and if loaded, if it's a higher version 63 if (!((jsf && jsf.specversion && jsf.specversion >= 23000 ) && 64 (jsf.implversion && jsf.implversion >= 3))) { 65 66 /** 67 * <span class="changed_modified_2_2">The top level global namespace 68 * for JavaServer Faces functionality.</span> 69 70 * @name jsf 71 * @namespace 72 */ 73 var jsf = {}; 74 75 /** 76 77 * <span class="changed_modified_2_2 changed_modified_2_3">The namespace for Ajax 78 * functionality.</span> 79 80 * @name jsf.ajax 81 * @namespace 82 * @exec 83 */ 84 jsf.ajax = function() { 85 86 var eventListeners = []; 87 var errorListeners = []; 88 89 var delayHandler = null; 90 /** 91 * Determine if the current browser is part of Microsoft's failed attempt at 92 * standards modification. 93 * @ignore 94 */ 95 var isIE = function isIE() { 96 if (typeof isIECache !== "undefined") { 97 return isIECache; 98 } 99 isIECache = 100 document.all && window.ActiveXObject && 101 navigator.userAgent.toLowerCase().indexOf("msie") > -1 && 102 navigator.userAgent.toLowerCase().indexOf("opera") == -1; 103 return isIECache; 104 }; 105 var isIECache; 106 107 /** 108 * Determine the version of IE. 109 * @ignore 110 */ 111 var getIEVersion = function getIEVersion() { 112 if (typeof IEVersionCache !== "undefined") { 113 return IEVersionCache; 114 } 115 if (/MSIE ([0-9]+)/.test(navigator.userAgent)) { 116 IEVersionCache = parseInt(RegExp.$1); 117 } else { 118 IEVersionCache = -1; 119 } 120 return IEVersionCache; 121 } 122 var IEVersionCache; 123 124 /** 125 * Determine if loading scripts into the page executes the script. 126 * This is instead of doing a complicated browser detection algorithm. Some do, some don't. 127 * @returns {boolean} does including a script in the dom execute it? 128 * @ignore 129 */ 130 var isAutoExec = function isAutoExec() { 131 try { 132 if (typeof isAutoExecCache !== "undefined") { 133 return isAutoExecCache; 134 } 135 var autoExecTestString = "<script>var mojarra = mojarra || {};mojarra.autoExecTest = true;</script>"; 136 var tempElement = document.createElement('span'); 137 tempElement.innerHTML = autoExecTestString; 138 var body = document.getElementsByTagName('body')[0]; 139 var tempNode = body.appendChild(tempElement); 140 if (mojarra && mojarra.autoExecTest) { 141 isAutoExecCache = true; 142 delete mojarra.autoExecTest; 143 } else { 144 isAutoExecCache = false; 145 } 146 deleteNode(tempNode); 147 return isAutoExecCache; 148 } catch (ex) { 149 // OK, that didn't work, we'll have to make an assumption 150 if (typeof isAutoExecCache === "undefined") { 151 isAutoExecCache = false; 152 } 153 return isAutoExecCache; 154 } 155 }; 156 var isAutoExecCache; 157 158 /** 159 * @ignore 160 */ 161 var getTransport = function getTransport(context) { 162 var returnVal; 163 // Here we check for encoding type for file upload(s). 164 // This is where we would also include a check for the existence of 165 // input file control for the current form (see hasInputFileControl 166 // function) but IE9 (at least) seems to render controls outside of 167 // form. 168 if (typeof context !== 'undefined' && context !== null && 169 context.includesInputFile && 170 context.form.enctype === "multipart/form-data") { 171 returnVal = new FrameTransport(context); 172 return returnVal; 173 } 174 var methods = [ 175 function() { 176 return new XMLHttpRequest(); 177 }, 178 function() { 179 return new ActiveXObject('Msxml2.XMLHTTP'); 180 }, 181 function() { 182 return new ActiveXObject('Microsoft.XMLHTTP'); 183 } 184 ]; 185 186 for (var i = 0, len = methods.length; i < len; i++) { 187 try { 188 returnVal = methods[i](); 189 } catch(e) { 190 continue; 191 } 192 return returnVal; 193 } 194 throw new Error('Could not create an XHR object.'); 195 }; 196 197 /** 198 * Used for iframe based communication (instead of XHR). 199 * @ignore 200 */ 201 var FrameTransport = function FrameTransport(context) { 202 this.context = context; 203 this.frame = null; 204 this.FRAME_ID = "JSFFrameId"; 205 this.FRAME_PARTIAL_ID = "Faces-Request"; 206 this.partial = null; 207 this.aborted = false; 208 this.responseText = null; 209 this.responseXML = null; 210 this.readyState = 0; 211 this.requestHeader = {}; 212 this.status = null; 213 this.method = null; 214 this.url = null; 215 this.requestParams = null; 216 }; 217 218 /** 219 * Extends FrameTransport an adds method functionality. 220 * @ignore 221 */ 222 FrameTransport.prototype = { 223 224 /** 225 *@ignore 226 */ 227 setRequestHeader:function(key, value) { 228 if (typeof(value) !== "undefined") { 229 this.requestHeader[key] = value; 230 } 231 }, 232 233 /** 234 * Creates the hidden iframe and sets readystate. 235 * @ignore 236 */ 237 open:function(method, url, async) { 238 this.method = method; 239 this.url = url; 240 this.async = async; 241 this.frame = document.getElementById(this.FRAME_ID); 242 if (this.frame) { 243 this.frame.parentNode.removeChild(this.frame); 244 this.frame = null; 245 } 246 if (!this.frame) { 247 if ((!isIE() && !isIE9Plus())) { 248 this.frame = document.createElement('iframe'); 249 this.frame.src = "about:blank"; 250 this.frame.id = this.FRAME_ID; 251 this.frame.name = this.FRAME_ID; 252 this.frame.type = "content"; 253 this.frame.collapsed = "true"; 254 this.frame.style = "visibility:hidden"; 255 this.frame.width = "0"; 256 this.frame.height = "0"; 257 this.frame.style = "border:0"; 258 this.frame.frameBorder = 0; 259 document.body.appendChild(this.frame); 260 this.frame.onload = bind(this, this.callback); 261 } else { 262 var div = document.createElement("div"); 263 div.id = "frameDiv"; 264 div.innerHTML = "<iframe id='" + this.FRAME_ID + "' name='" + this.FRAME_ID + "' style='display:none;' src='about:blank' type='content' onload='this.onload_cb();' ></iframe>"; 265 document.body.appendChild(div); 266 this.frame = document.getElementById(this.FRAME_ID); 267 this.frame.onload_cb = bind(this, this.callback); 268 } 269 } 270 // Create to send "Faces-Request" param with value "partial/ajax" 271 // For iframe approach we are sending as request parameter 272 // For non-iframe (xhr ajax) it is sent in the request header 273 this.partial = document.createElement("input"); 274 this.partial.setAttribute("type", "hidden"); 275 this.partial.setAttribute("id", this.FRAME_PARTIAL_ID); 276 this.partial.setAttribute("name", this.FRAME_PARTIAL_ID); 277 this.partial.setAttribute("value", "partial/ajax"); 278 this.context.form.appendChild(this.partial); 279 280 this.readyState = 1; 281 }, 282 283 /** 284 * Sets the form target to iframe, sets up request parameters 285 * and submits the form. 286 * @ignore 287 */ 288 send: function(data) { 289 var evt = {}; 290 this.context.form.target = this.frame.name; 291 this.context.form.method = this.method; 292 if (this.url) { 293 this.context.form.action = this.url; 294 } 295 296 this.readyState = 3; 297 298 this.onreadystatechange(evt); 299 300 var ddata = decodeURIComponent(data); 301 var dataArray = ddata.split("&"); 302 var input; 303 this.requestParams = new Array(); 304 for (var i=0; i<dataArray.length; i++) { 305 var nameValue = dataArray[i].split("="); 306 if (nameValue[0] === this.context.namingContainerPrefix + "javax.faces.source" || 307 nameValue[0] === this.context.namingContainerPrefix + "javax.faces.partial.event" || 308 nameValue[0] === this.context.namingContainerPrefix + "javax.faces.partial.execute" || 309 nameValue[0] === this.context.namingContainerPrefix + "javax.faces.partial.render" || 310 nameValue[0] === this.context.namingContainerPrefix + "javax.faces.partial.ajax" || 311 nameValue[0] === this.context.namingContainerPrefix + "javax.faces.behavior.event") { 312 input = document.createElement("input"); 313 input.setAttribute("type", "hidden"); 314 input.setAttribute("id", nameValue[0]); 315 input.setAttribute("name", nameValue[0]); 316 input.setAttribute("value", nameValue[1]); 317 this.context.form.appendChild(input); 318 this.requestParams.push(nameValue[0]); 319 } 320 } 321 this.requestParams.push(this.FRAME_PARTIAL_ID); 322 this.context.form.submit(); 323 }, 324 325 /** 326 *@ignore 327 */ 328 abort:function() { 329 this.aborted = true; 330 }, 331 332 /** 333 *@ignore 334 */ 335 onreadystatechange:function(evt) { 336 337 }, 338 339 /** 340 * Extracts response from iframe document, sets readystate. 341 * @ignore 342 */ 343 callback: function() { 344 if (this.aborted) { 345 return; 346 } 347 var iFrameDoc; 348 var docBody; 349 try { 350 var evt = {}; 351 iFrameDoc = this.frame.contentWindow.document || 352 this.frame.contentDocument || this.frame.document; 353 docBody = iFrameDoc.body || iFrameDoc.documentElement; 354 this.responseText = docBody.innerHTML; 355 this.responseXML = iFrameDoc.XMLDocument || iFrameDoc; 356 this.status = 201; 357 this.readyState = 4; 358 359 this.onreadystatechange(evt); 360 } finally { 361 this.cleanupReqParams(); 362 } 363 }, 364 365 /** 366 *@ignore 367 */ 368 cleanupReqParams: function() { 369 var clone = this.frame.cloneNode(); 370 while (this.frame.firstChild) { 371 clone.appendChild(this.frame.lastChild); 372 } 373 374 this.frame.parentNode.replaceChild(clone, this.frame); 375 this.context.form.removeAttribute("target"); 376 for (var i=0; i<this.requestParams.length; i++) { 377 var elements = this.context.form.childNodes; 378 for (var j=0; j<elements.length; j++) { 379 if (!elements[j].type === "hidden") { 380 continue; 381 } 382 if (elements[j].name === this.requestParams[i]) { 383 var node = this.context.form.removeChild(elements[j]); 384 node = null; 385 break; 386 } 387 } 388 } 389 } 390 }; 391 392 393 /** 394 *Utility function that binds function to scope. 395 *@ignore 396 */ 397 var bind = function(scope, fn) { 398 return function () { 399 fn.apply(scope, arguments); 400 }; 401 }; 402 403 /** 404 * Utility function that determines if a file control exists 405 * for the form. 406 * @ignore 407 */ 408 var hasInputFileControl = function(form) { 409 var returnVal = false; 410 var inputs = form.getElementsByTagName("input"); 411 if (inputs !== null && typeof inputs !=="undefined") { 412 for (var i=0; i<inputs.length; i++) { 413 if (inputs[i].type === "file") { 414 returnVal = true; 415 break; 416 } 417 } 418 } 419 return returnVal; 420 }; 421 422 /** 423 * Find instance of passed String via getElementById 424 * @ignore 425 */ 426 var $ = function $() { 427 var results = [], element; 428 for (var i = 0; i < arguments.length; i++) { 429 element = arguments[i]; 430 if (typeof element == 'string') { 431 element = document.getElementById(element); 432 } 433 results.push(element); 434 } 435 return results.length > 1 ? results : results[0]; 436 }; 437 438 /** 439 * Get the form element which encloses the supplied element. 440 * @param element - element to act against in search 441 * @returns form element representing enclosing form, or first form if none found. 442 * @ignore 443 */ 444 var getForm = function getForm(element) { 445 if (element) { 446 var form = $(element); 447 while (form) { 448 449 if (form.nodeName && (form.nodeName.toLowerCase() == 'form')) { 450 return form; 451 } 452 if (form.form) { 453 return form.form; 454 } 455 if (form.parentNode) { 456 form = form.parentNode; 457 } else { 458 form = null; 459 } 460 } 461 return document.forms[0]; 462 } 463 return null; 464 }; 465 466 /** 467 * Get an array of all JSF form elements which need their view state to be updated. 468 * This covers at least the form that submitted the request and any form that is covered in the render target list. 469 * 470 * @param context An object containing the request context, including the following properties: 471 * the source element, per call onerror callback function, per call onevent callback function, the render 472 * instructions, the submitting form ID, the naming container ID and naming container prefix. 473 * @ignore 474 */ 475 var getFormsToUpdate = function getFormsToUpdate(context) { 476 var formsToUpdate = []; 477 478 var add = function(element) { 479 if (element) { 480 if (element.nodeName 481 && element.nodeName.toLowerCase() == "form" 482 && element.method == "post" 483 && element.id 484 && element.elements 485 && element.id.indexOf(context.namingContainerPrefix) == 0) 486 { 487 formsToUpdate.push(element); 488 } 489 else { 490 var forms = element.getElementsByTagName("form"); 491 492 for (var i = 0; i < forms.length; i++) { 493 add(forms[i]); 494 } 495 } 496 } 497 } 498 499 if (context.formId) { 500 add(document.getElementById(context.formId)); 501 } 502 503 if (context.render) { 504 if (context.render.indexOf("@all") >= 0) { 505 add(document); 506 } 507 else { 508 var clientIds = context.render.split(" "); 509 510 for (var i = 0; i < clientIds.length; i++) { 511 if (clientIds.hasOwnProperty(i)) { 512 add(document.getElementById(clientIds[i])); 513 } 514 } 515 } 516 } 517 518 return formsToUpdate; 519 } 520 521 /** 522 * <p>Namespace given space separated parameters if necessary (only 523 * call this if there is a namingContainerPrefix!). This 524 * function is here for backwards compatibility with manual 525 * jsf.ajax.request() calls written before Spec790 changes.</p> 526 527 * @param parameters Spaceseparated string of parameters as 528 * usually specified in f:ajax execute and render attributes. 529 530 * @param sourceClientId The client ID of the f:ajax 531 * source. This is to be used for prefixing relative target 532 * client IDs. 533 534 * It's expected that this already starts with 535 * namingContainerPrefix. 536 537 * @param namingContainerPrefix The naming container prefix (the 538 * view root ID suffixed with separator character). 539 540 * This is to be used for prefixing absolute target client IDs. 541 * @ignore 542 */ 543 var namespaceParametersIfNecessary = function namespaceParametersIfNecessary(parameters, sourceClientId, namingContainerPrefix) { 544 if (sourceClientId.indexOf(namingContainerPrefix) != 0) { 545 return parameters; // Unexpected source client ID; let's silently do nothing. 546 } 547 548 var targetClientIds = parameters.replace(/^\s+|\s+$/g, '').split(/\s+/g); 549 550 for (var i = 0; i < targetClientIds.length; i++) { 551 var targetClientId = targetClientIds[i]; 552 553 if (targetClientId.indexOf(jsf.separatorchar) == 0) { 554 targetClientId = targetClientId.substring(1); 555 556 if (targetClientId.indexOf(namingContainerPrefix) != 0) { 557 targetClientId = namingContainerPrefix + targetClientId; 558 } 559 } 560 else if (targetClientId.indexOf(namingContainerPrefix) != 0) { 561 var parentClientId = sourceClientId.substring(0, sourceClientId.lastIndexOf(jsf.separatorchar)); 562 563 if (namingContainerPrefix + targetClientId == parentClientId) { 564 targetClientId = parentClientId; 565 } 566 else { 567 targetClientId = parentClientId + jsf.separatorchar + targetClientId; 568 } 569 } 570 571 targetClientIds[i] = targetClientId; 572 } 573 574 return targetClientIds.join(' '); 575 }; 576 577 /** 578 * Check if a value exists in an array 579 * @ignore 580 */ 581 var isInArray = function isInArray(array, value) { 582 for (var i = 0; i < array.length; i++) { 583 if (array[i] === value) { 584 return true; 585 } 586 } 587 return false; 588 }; 589 590 591 /** 592 * Evaluate JavaScript code in a global context. 593 * @param src JavaScript code to evaluate 594 * @ignore 595 */ 596 var globalEval = function globalEval(src) { 597 if (window.execScript) { 598 window.execScript(src); 599 return; 600 } 601 // We have to wrap the call in an anon function because of a firefox bug, where this is incorrectly set 602 // We need to explicitly call window.eval because of a Chrome peculiarity 603 /** 604 * @ignore 605 */ 606 var fn = function() { 607 window.eval.call(window,src); 608 }; 609 fn(); 610 }; 611 612 /** 613 * Get all scripts from supplied string, return them as an array for later processing. 614 * @param str 615 * @returns {array} of script text 616 * @ignore 617 */ 618 var getScripts = function getScripts(str) { 619 // Regex to find all scripts in a string 620 var findscripts = /<script[^>]*>([\S\s]*?)<\/script>/igm; 621 // Regex to find one script, to isolate it's content [2] and attributes [1] 622 var findscript = /<script([^>]*)>([\S\s]*?)<\/script>/im; 623 // Regex to find type attribute 624 var findtype = /type="([\S]*?)"/im; 625 var initialnodes = []; 626 var scripts = []; 627 initialnodes = str.match(findscripts); 628 while (!!initialnodes && initialnodes.length > 0) { 629 var scriptStr = []; 630 scriptStr = initialnodes.shift().match(findscript); 631 // check the type - skip if it not javascript type 632 var type = []; 633 type = scriptStr[1].match(findtype); 634 if ( !!type && type[1]) { 635 if (type[1] !== "text/javascript") { 636 continue; 637 } 638 } 639 scripts.push(scriptStr); 640 } 641 return scripts; 642 }; 643 644 var removeScripts = function removeScripts(str) { 645 return str.replace(/<script[^>]*type="text\/javascript"[^>]*>([\S\s]*?)<\/script>/igm,""); 646 }; 647 648 /** 649 * Run an array of script nodes, 650 * @param scripts Array of script nodes. 651 * @ignore 652 */ 653 var runScripts = function runScripts(scripts) { 654 if (!scripts || scripts.length === 0) { 655 return; 656 } 657 658 var loadedScripts = document.getElementsByTagName("script"); 659 var loadedScriptUrls = []; 660 661 for (var i = 0; i < loadedScripts.length; i++) { 662 var scriptNode = loadedScripts[i]; 663 var url = scriptNode.getAttribute("src"); 664 665 if (url) { 666 loadedScriptUrls.push(url); 667 } 668 } 669 670 var head = document.head || document.getElementsByTagName('head')[0] || document.documentElement; 671 runScript(head, loadedScriptUrls, scripts, 0); 672 }; 673 674 /** 675 * Run script at given index. 676 * @param head Document's head. 677 * @param loadedScriptUrls URLs of scripts which are already loaded. 678 * @param scripts Array of script nodes. 679 * @param index Index of script to be loaded. 680 * @ignore 681 */ 682 var runScript = function runScript(head, loadedScriptUrls, scripts, index) { 683 if (index >= scripts.length) { 684 return; 685 } 686 687 // Regex to find src attribute 688 var findsrc = /src="([\S]*?)"/im; 689 // Regex to remove leading cruft 690 var stripStart = /^\s*(<!--)*\s*(\/\/)*\s*(\/\*)*\s*\n*\**\n*\s*\*.*\n*\s*\*\/(<!\[CDATA\[)*/; 691 692 var scriptStr = scripts[index]; 693 var src = scriptStr[1].match(findsrc); 694 var scriptLoadedViaUrl = false; 695 696 if (!!src && src[1]) { 697 // if this is a file, load it 698 var url = unescapeHTML(src[1]); 699 // if this is already loaded, don't load it 700 // it's never necessary, and can make debugging difficult 701 if (loadedScriptUrls.indexOf(url) < 0) { 702 // create script node 703 var scriptNode = document.createElement('script'); 704 var parserElement = document.createElement('div'); 705 parserElement.innerHTML = scriptStr[0]; 706 cloneAttributes(scriptNode, parserElement.firstChild); 707 deleteNode(parserElement); 708 scriptNode.type = 'text/javascript'; 709 scriptNode.src = url; // add the src to the script node 710 scriptNode.onload = scriptNode.onreadystatechange = function(_, abort) { 711 if (abort || !scriptNode.readyState || /loaded|complete/.test(scriptNode.readyState)) { 712 scriptNode.onload = scriptNode.onreadystatechange = null; // IE memory leak fix. 713 scriptNode = null; 714 runScript(head, loadedScriptUrls, scripts, index + 1); // Run next script. 715 } 716 }; 717 head.insertBefore(scriptNode, null); // add it to end of the head (and don't remove it) 718 scriptLoadedViaUrl = true; 719 } 720 } else if (!!scriptStr && scriptStr[2]) { 721 // else get content of tag, without leading CDATA and such 722 var script = scriptStr[2].replace(stripStart,""); 723 724 if (!!script) { 725 // create script node 726 var scriptNode = document.createElement('script'); 727 scriptNode.type = 'text/javascript'; 728 scriptNode.text = script; // add the code to the script node 729 head.appendChild(scriptNode); // add it to the head 730 head.removeChild(scriptNode); // then remove it 731 } 732 } 733 734 if (!scriptLoadedViaUrl) { 735 runScript(head, loadedScriptUrls, scripts, index + 1); // Run next script. 736 } 737 }; 738 739 /** 740 * Get all stylesheets from supplied string and run them all. 741 * @param str 742 * @ignore 743 */ 744 var runStylesheets = function runStylesheets(str) { 745 // Regex to find all links in a string 746 var findlinks = /<link[^>]*\/>/igm; 747 // Regex to find one link, to isolate its attributes [1] 748 var findlink = /<link([^>]*)\/>/im; 749 // Regex to find type attribute 750 var findtype = /type="([\S]*?)"/im; 751 var findhref = /href="([\S]*?)"/im; 752 753 var stylesheets = []; 754 var loadedStylesheetUrls = null; 755 var head = document.head || document.getElementsByTagName('head')[0] || document.documentElement; 756 var parserElement = null; 757 758 var initialnodes = str.match(findlinks); 759 while (!!initialnodes && initialnodes.length > 0) { 760 var linkStr = initialnodes.shift().match(findlink); 761 // check the type - skip if it not css type 762 var type = linkStr[1].match(findtype); 763 if (!type || type[1] !== "text/css") { 764 continue; 765 } 766 var href = linkStr[1].match(findhref); 767 if (!!href && href[1]) { 768 if (loadedStylesheetUrls === null) { 769 var loadedLinks = document.getElementsByTagName("link"); 770 loadedStylesheetUrls = []; 771 772 for (var i = 0; i < loadedLinks.length; i++) { 773 var linkNode = loadedLinks[i]; 774 775 if (linkNode.getAttribute("type") === "text/css") { 776 var url = linkNode.getAttribute("href"); 777 778 if (url) { 779 loadedStylesheetUrls.push(url); 780 } 781 } 782 } 783 } 784 785 var url = unescapeHTML(href[1]); 786 787 if (loadedStylesheetUrls.indexOf(url) < 0) { 788 // create stylesheet node 789 parserElement = parserElement !== null ? parserElement : document.createElement('div'); 790 parserElement.innerHTML = linkStr[0]; 791 var linkNode = parserElement.firstChild; 792 linkNode.type = 'text/css'; 793 linkNode.rel = 'stylesheet'; 794 linkNode.href = url; 795 head.insertBefore(linkNode, null); // add it to end of the head (and don't remove it) 796 } 797 } 798 } 799 800 deleteNode(parserElement); 801 }; 802 803 /** 804 * Replace DOM element with a new tagname and supplied innerHTML 805 * @param element element to replace 806 * @param tempTagName new tag name to replace with 807 * @param src string new content for element 808 * @ignore 809 */ 810 var elementReplaceStr = function elementReplaceStr(element, tempTagName, src) { 811 812 var temp = document.createElement(tempTagName); 813 if (element.id) { 814 temp.id = element.id; 815 } 816 817 // Creating a head element isn't allowed in IE, and faulty in most browsers, 818 // so it is not allowed 819 if (element.nodeName.toLowerCase() === "head") { 820 throw new Error("Attempted to replace a head element - this is not allowed."); 821 } else { 822 var scripts = []; 823 if (isAutoExec()) { 824 temp.innerHTML = src; 825 } else { 826 // Get scripts from text 827 scripts = getScripts(src); 828 // Remove scripts from text 829 src = removeScripts(src); 830 temp.innerHTML = src; 831 } 832 } 833 834 replaceNode(temp, element); 835 cloneAttributes(temp, element); 836 runScripts(scripts); 837 838 }; 839 840 /** 841 * Get a string with the concatenated values of all string nodes under the given node 842 * @param oNode the given DOM node 843 * @param deep boolean - whether to recursively scan the children nodes of the given node for text as well. Default is <code>false</code> 844 * @ignore 845 * Note: This code originally from Sarissa: http://dev.abiss.gr/sarissa 846 * It has been modified to fit into the overall codebase 847 */ 848 var getText = function getText(oNode, deep) { 849 var Node = {ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, 850 ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, 851 COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, 852 DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12}; 853 854 var s = ""; 855 var nodes = oNode.childNodes; 856 for (var i = 0; i < nodes.length; i++) { 857 var node = nodes[i]; 858 var nodeType = node.nodeType; 859 if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) { 860 s += node.data; 861 } else if (deep === true && (nodeType == Node.ELEMENT_NODE || 862 nodeType == Node.DOCUMENT_NODE || 863 nodeType == Node.DOCUMENT_FRAGMENT_NODE)) { 864 s += getText(node, true); 865 } 866 } 867 return s; 868 }; 869 870 var PARSED_OK = "Document contains no parsing errors"; 871 var PARSED_EMPTY = "Document is empty"; 872 var PARSED_UNKNOWN_ERROR = "Not well-formed or other error"; 873 var getParseErrorText; 874 if (isIE()) { 875 /** 876 * Note: This code orginally from Sarissa: http://dev.abiss.gr/sarissa 877 * @ignore 878 */ 879 getParseErrorText = function (oDoc) { 880 var parseErrorText = PARSED_OK; 881 if (oDoc && oDoc.parseError && oDoc.parseError.errorCode && oDoc.parseError.errorCode !== 0) { 882 parseErrorText = "XML Parsing Error: " + oDoc.parseError.reason + 883 "\nLocation: " + oDoc.parseError.url + 884 "\nLine Number " + oDoc.parseError.line + ", Column " + 885 oDoc.parseError.linepos + 886 ":\n" + oDoc.parseError.srcText + 887 "\n"; 888 for (var i = 0; i < oDoc.parseError.linepos; i++) { 889 parseErrorText += "-"; 890 } 891 parseErrorText += "^\n"; 892 } 893 else if (oDoc.documentElement === null) { 894 parseErrorText = PARSED_EMPTY; 895 } 896 return parseErrorText; 897 }; 898 } else { // (non-IE) 899 900 /** 901 * <p>Returns a human readable description of the parsing error. Useful 902 * for debugging. Tip: append the returned error string in a <pre> 903 * element if you want to render it.</p> 904 * @param oDoc The target DOM document 905 * @returns {String} The parsing error description of the target Document in 906 * human readable form (preformated text) 907 * @ignore 908 * Note: This code orginally from Sarissa: http://dev.abiss.gr/sarissa 909 */ 910 getParseErrorText = function (oDoc) { 911 var parseErrorText = PARSED_OK; 912 if ((!oDoc) || (!oDoc.documentElement)) { 913 parseErrorText = PARSED_EMPTY; 914 } else if (oDoc.documentElement.tagName == "parsererror") { 915 parseErrorText = oDoc.documentElement.firstChild.data; 916 parseErrorText += "\n" + oDoc.documentElement.firstChild.nextSibling.firstChild.data; 917 } else if (oDoc.getElementsByTagName("parsererror").length > 0) { 918 var parsererror = oDoc.getElementsByTagName("parsererror")[0]; 919 parseErrorText = getText(parsererror, true) + "\n"; 920 } else if (oDoc.parseError && oDoc.parseError.errorCode !== 0) { 921 parseErrorText = PARSED_UNKNOWN_ERROR; 922 } 923 return parseErrorText; 924 }; 925 } 926 927 if ((typeof(document.importNode) == "undefined") && isIE()) { 928 try { 929 /** 930 * Implementation of importNode for the context window document in IE. 931 * If <code>oNode</code> is a TextNode, <code>bChildren</code> is ignored. 932 * @param oNode the Node to import 933 * @param bChildren whether to include the children of oNode 934 * @returns the imported node for further use 935 * @ignore 936 * Note: This code orginally from Sarissa: http://dev.abiss.gr/sarissa 937 */ 938 document.importNode = function(oNode, bChildren) { 939 var tmp; 940 if (oNode.nodeName == '#text') { 941 return document.createTextNode(oNode.data); 942 } 943 else { 944 if (oNode.nodeName == "tbody" || oNode.nodeName == "tr") { 945 tmp = document.createElement("table"); 946 } 947 else if (oNode.nodeName == "td") { 948 tmp = document.createElement("tr"); 949 } 950 else if (oNode.nodeName == "option") { 951 tmp = document.createElement("select"); 952 } 953 else { 954 tmp = document.createElement("div"); 955 } 956 if (bChildren) { 957 tmp.innerHTML = oNode.xml ? oNode.xml : oNode.outerHTML; 958 } else { 959 tmp.innerHTML = oNode.xml ? oNode.cloneNode(false).xml : oNode.cloneNode(false).outerHTML; 960 } 961 return tmp.getElementsByTagName("*")[0]; 962 } 963 }; 964 } catch(e) { 965 } 966 } 967 // Setup Node type constants for those browsers that don't have them (IE) 968 var Node = {ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, 969 ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, 970 COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, 971 DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12}; 972 973 // PENDING - add support for removing handlers added via DOM 2 methods 974 /** 975 * Delete all events attached to a node 976 * @param node 977 * @ignore 978 */ 979 var clearEvents = function clearEvents(node) { 980 if (!node) { 981 return; 982 } 983 984 // don't do anything for text and comment nodes - unnecessary 985 if (node.nodeType == Node.TEXT_NODE || node.nodeType == Node.COMMENT_NODE) { 986 return; 987 } 988 989 var events = ['abort', 'blur', 'change', 'error', 'focus', 'load', 'reset', 'resize', 'scroll', 'select', 'submit', 'unload', 990 'keydown', 'keypress', 'keyup', 'click', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'dblclick' ]; 991 try { 992 for (var e in events) { 993 if (events.hasOwnProperty(e)) { 994 node[e] = null; 995 } 996 } 997 } catch (ex) { 998 // it's OK if it fails, at least we tried 999 } 1000 }; 1001 1002 /** 1003 * Determine if this current browser is IE9 or greater 1004 * @param node 1005 * @ignore 1006 */ 1007 var isIE9Plus = function isIE9Plus() { 1008 var iev = getIEVersion(); 1009 if (iev >= 9) { 1010 return true; 1011 } else { 1012 return false; 1013 } 1014 } 1015 1016 1017 /** 1018 * Deletes node 1019 * @param node 1020 * @ignore 1021 */ 1022 var deleteNode = function deleteNode(node) { 1023 if (!node) { 1024 return; 1025 } 1026 if (!node.parentNode) { 1027 // if there's no parent, there's nothing to do 1028 return; 1029 } 1030 if (!isIE() || (isIE() && isIE9Plus())) { 1031 // nothing special required 1032 node.parentNode.removeChild(node); 1033 return; 1034 } 1035 // The rest of this code is specialcasing for IE 1036 if (node.nodeName.toLowerCase() === "body") { 1037 // special case for removing body under IE. 1038 deleteChildren(node); 1039 try { 1040 node.outerHTML = ''; 1041 } catch (ex) { 1042 // fails under some circumstances, but not in RI 1043 // supplied responses. If we've gotten here, it's 1044 // fairly safe to leave a lingering body tag rather than 1045 // fail outright 1046 } 1047 return; 1048 } 1049 var temp = node.ownerDocument.createElement('div'); 1050 var parent = node.parentNode; 1051 temp.appendChild(parent.removeChild(node)); 1052 // Now clean up the temporary element 1053 try { 1054 temp.outerHTML = ''; //prevent leak in IE 1055 } catch (ex) { 1056 // at least we tried. Fails in some circumstances, 1057 // but not in RI supplied responses. Better to leave a lingering 1058 // temporary div than to fail outright. 1059 } 1060 }; 1061 1062 /** 1063 * Deletes all children of a node 1064 * @param node 1065 * @ignore 1066 */ 1067 var deleteChildren = function deleteChildren(node) { 1068 if (!node) { 1069 return; 1070 } 1071 for (var x = node.childNodes.length - 1; x >= 0; x--) { //delete all of node's children 1072 var childNode = node.childNodes[x]; 1073 deleteNode(childNode); 1074 } 1075 }; 1076 1077 /** 1078 * <p> Copies the childNodes of nodeFrom to nodeTo</p> 1079 * 1080 * @param nodeFrom the Node to copy the childNodes from 1081 * @param nodeTo the Node to copy the childNodes to 1082 * @ignore 1083 * Note: This code originally from Sarissa: http://dev.abiss.gr/sarissa 1084 * It has been modified to fit into the overall codebase 1085 */ 1086 var copyChildNodes = function copyChildNodes(nodeFrom, nodeTo) { 1087 1088 if ((!nodeFrom) || (!nodeTo)) { 1089 throw "Both source and destination nodes must be provided"; 1090 } 1091 1092 deleteChildren(nodeTo); 1093 var nodes = nodeFrom.childNodes; 1094 // if within the same doc, just move, else copy and delete 1095 if (nodeFrom.ownerDocument == nodeTo.ownerDocument) { 1096 while (nodeFrom.firstChild) { 1097 nodeTo.appendChild(nodeFrom.firstChild); 1098 } 1099 } else { 1100 var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument; 1101 var i; 1102 if (typeof(ownerDoc.importNode) != "undefined") { 1103 for (i = 0; i < nodes.length; i++) { 1104 nodeTo.appendChild(ownerDoc.importNode(nodes[i], true)); 1105 } 1106 } else { 1107 for (i = 0; i < nodes.length; i++) { 1108 nodeTo.appendChild(nodes[i].cloneNode(true)); 1109 } 1110 } 1111 } 1112 }; 1113 1114 1115 /** 1116 * Replace one node with another. Necessary for handling IE memory leak. 1117 * @param node 1118 * @param newNode 1119 * @ignore 1120 */ 1121 var replaceNode = function replaceNode(newNode, node) { 1122 if(isIE()){ 1123 node.parentNode.insertBefore(newNode, node); 1124 deleteNode(node); 1125 } else { 1126 node.parentNode.replaceChild(newNode, node); 1127 } 1128 }; 1129 1130 /** 1131 * @ignore 1132 */ 1133 var propertyToAttribute = function propertyToAttribute(name) { 1134 if (name === 'className') { 1135 return 'class'; 1136 } else if (name === 'xmllang') { 1137 return 'xml:lang'; 1138 } else { 1139 return name.toLowerCase(); 1140 } 1141 }; 1142 1143 /** 1144 * @ignore 1145 */ 1146 var isFunctionNative = function isFunctionNative(func) { 1147 return /^\s*function[^{]+{\s*\[native code\]\s*}\s*$/.test(String(func)); 1148 }; 1149 1150 /** 1151 * @ignore 1152 */ 1153 var detectAttributes = function detectAttributes(element) { 1154 //test if 'hasAttribute' method is present and its native code is intact 1155 //for example, Prototype can add its own implementation if missing 1156 if (element.hasAttribute && isFunctionNative(element.hasAttribute)) { 1157 return function(name) { 1158 return element.hasAttribute(name); 1159 } 1160 } else { 1161 try { 1162 //when accessing .getAttribute method without arguments does not throw an error then the method is not available 1163 element.getAttribute; 1164 1165 var html = element.outerHTML; 1166 var startTag = html.match(/^<[^>]*>/)[0]; 1167 return function(name) { 1168 return startTag.indexOf(name + '=') > -1; 1169 } 1170 } catch (ex) { 1171 return function(name) { 1172 return element.getAttribute(name); 1173 } 1174 } 1175 } 1176 }; 1177 1178 /** 1179 * copy all attributes from one element to another - except id 1180 * @param target element to copy attributes to 1181 * @param source element to copy attributes from 1182 * @ignore 1183 */ 1184 var cloneAttributes = function cloneAttributes(target, source) { 1185 1186 // enumerate core element attributes - without 'dir' as special case 1187 var coreElementProperties = ['className', 'title', 'lang', 'xmllang']; 1188 // enumerate additional input element attributes 1189 var inputElementProperties = [ 1190 'name', 'value', 'size', 'maxLength', 'src', 'alt', 'useMap', 'tabIndex', 'accessKey', 'accept', 'type' 1191 ]; 1192 // enumerate additional boolean input attributes 1193 var inputElementBooleanProperties = [ 1194 'checked', 'disabled', 'readOnly' 1195 ]; 1196 1197 // Enumerate all the names of the event listeners 1198 var listenerNames = 1199 [ 'onclick', 'ondblclick', 'onmousedown', 'onmousemove', 'onmouseout', 1200 'onmouseover', 'onmouseup', 'onkeydown', 'onkeypress', 'onkeyup', 1201 'onhelp', 'onblur', 'onfocus', 'onchange', 'onload', 'onunload', 'onabort', 1202 'onreset', 'onselect', 'onsubmit' 1203 ]; 1204 1205 var sourceAttributeDetector = detectAttributes(source); 1206 var targetAttributeDetector = detectAttributes(target); 1207 1208 var isInputElement = target.nodeName.toLowerCase() === 'input'; 1209 var propertyNames = isInputElement ? coreElementProperties.concat(inputElementProperties) : coreElementProperties; 1210 var isXML = !source.ownerDocument.contentType || source.ownerDocument.contentType == 'text/xml'; 1211 for (var iIndex = 0, iLength = propertyNames.length; iIndex < iLength; iIndex++) { 1212 var propertyName = propertyNames[iIndex]; 1213 var attributeName = propertyToAttribute(propertyName); 1214 if (sourceAttributeDetector(attributeName)) { 1215 1216 //With IE 7 (quirks or standard mode) and IE 8/9 (quirks mode only), 1217 //you cannot get the attribute using 'class'. You must use 'className' 1218 //which is the same value you use to get the indexed property. The only 1219 //reliable way to detect this (without trying to evaluate the browser 1220 //mode and version) is to compare the two return values using 'className' 1221 //to see if they exactly the same. If they are, then use the property 1222 //name when using getAttribute. 1223 if( attributeName == 'class'){ 1224 if( isIE() && (source.getAttribute(propertyName) === source[propertyName]) ){ 1225 attributeName = propertyName; 1226 } 1227 } 1228 1229 var newValue = isXML ? source.getAttribute(attributeName) : source[propertyName]; 1230 var oldValue = target[propertyName]; 1231 if (oldValue != newValue) { 1232 target[propertyName] = newValue; 1233 } 1234 } else { 1235 //setting property to '' seems to be the only cross-browser method for removing an attribute 1236 //avoid setting 'value' property to '' for checkbox and radio input elements because then the 1237 //'value' is used instead of the 'checked' property when the form is serialized by the browser 1238 if (attributeName == "value" && (target.type != 'checkbox' && target.type != 'radio')) { 1239 target[propertyName] = ''; 1240 } 1241 target.removeAttribute(attributeName); 1242 } 1243 } 1244 1245 var booleanPropertyNames = isInputElement ? inputElementBooleanProperties : []; 1246 for (var jIndex = 0, jLength = booleanPropertyNames.length; jIndex < jLength; jIndex++) { 1247 var booleanPropertyName = booleanPropertyNames[jIndex]; 1248 var newBooleanValue = source[booleanPropertyName]; 1249 var oldBooleanValue = target[booleanPropertyName]; 1250 if (oldBooleanValue != newBooleanValue) { 1251 target[booleanPropertyName] = newBooleanValue; 1252 } 1253 } 1254 1255 //'style' attribute special case 1256 if (sourceAttributeDetector('style')) { 1257 var newStyle; 1258 var oldStyle; 1259 if (isIE()) { 1260 newStyle = source.style.cssText; 1261 oldStyle = target.style.cssText; 1262 if (newStyle != oldStyle) { 1263 target.style.cssText = newStyle; 1264 } 1265 } else { 1266 newStyle = source.getAttribute('style'); 1267 oldStyle = target.getAttribute('style'); 1268 if (newStyle != oldStyle) { 1269 target.setAttribute('style', newStyle); 1270 } 1271 } 1272 } else if (targetAttributeDetector('style')){ 1273 target.removeAttribute('style'); 1274 } 1275 1276 // Special case for 'dir' attribute 1277 if (!isIE() && source.dir != target.dir) { 1278 if (sourceAttributeDetector('dir')) { 1279 target.dir = source.dir; 1280 } else if (targetAttributeDetector('dir')) { 1281 target.dir = ''; 1282 } 1283 } 1284 1285 for (var lIndex = 0, lLength = listenerNames.length; lIndex < lLength; lIndex++) { 1286 var name = listenerNames[lIndex]; 1287 target[name] = source[name] ? source[name] : null; 1288 if (source[name]) { 1289 source[name] = null; 1290 } 1291 } 1292 1293 //clone HTML5 data-* attributes 1294 try{ 1295 var targetDataset = target.dataset; 1296 var sourceDataset = source.dataset; 1297 if (targetDataset || sourceDataset) { 1298 //cleanup the dataset 1299 for (var tp in targetDataset) { 1300 delete targetDataset[tp]; 1301 } 1302 //copy dataset's properties 1303 for (var sp in sourceDataset) { 1304 targetDataset[sp] = sourceDataset[sp]; 1305 } 1306 } 1307 } catch (ex) { 1308 //most probably dataset properties are not supported 1309 } 1310 }; 1311 1312 /** 1313 * Replace an element from one document into another 1314 * @param newElement new element to put in document 1315 * @param origElement original element to replace 1316 * @ignore 1317 */ 1318 var elementReplace = function elementReplace(newElement, origElement) { 1319 copyChildNodes(newElement, origElement); 1320 // sadly, we have to reparse all over again 1321 // to reregister the event handlers and styles 1322 // PENDING do some performance tests on large pages 1323 origElement.innerHTML = origElement.innerHTML; 1324 1325 try { 1326 cloneAttributes(origElement, newElement); 1327 } catch (ex) { 1328 // if in dev mode, report an error, else try to limp onward 1329 if (jsf.getProjectStage() == "Development") { 1330 throw new Error("Error updating attributes"); 1331 } 1332 } 1333 deleteNode(newElement); 1334 1335 }; 1336 1337 /** 1338 * Create a new document, then select the body element within it 1339 * @param docStr Stringified version of document to create 1340 * @return element the body element 1341 * @ignore 1342 */ 1343 var getBodyElement = function getBodyElement(docStr) { 1344 1345 var doc; // intermediate document we'll create 1346 var body; // Body element to return 1347 1348 if (typeof DOMParser !== "undefined") { // FF, S, Chrome 1349 doc = (new DOMParser()).parseFromString(docStr, "text/xml"); 1350 } else if (typeof ActiveXObject !== "undefined") { // IE 1351 doc = new ActiveXObject("MSXML2.DOMDocument"); 1352 doc.loadXML(docStr); 1353 } else { 1354 throw new Error("You don't seem to be running a supported browser"); 1355 } 1356 1357 if (getParseErrorText(doc) !== PARSED_OK) { 1358 throw new Error(getParseErrorText(doc)); 1359 } 1360 1361 body = doc.getElementsByTagName("body")[0]; 1362 1363 if (!body) { 1364 throw new Error("Can't find body tag in returned document."); 1365 } 1366 1367 return body; 1368 }; 1369 1370 /** 1371 * Find encoded url field for a given form. 1372 * @param form 1373 * @ignore 1374 */ 1375 var getEncodedUrlElement = function getEncodedUrlElement(form) { 1376 var encodedUrlElement = form['javax.faces.encodedURL']; 1377 1378 if (encodedUrlElement) { 1379 return encodedUrlElement; 1380 } else { 1381 var formElements = form.elements; 1382 for (var i = 0, length = formElements.length; i < length; i++) { 1383 var formElement = formElements[i]; 1384 if (formElement.name && (formElement.name.indexOf('javax.faces.encodedURL') >= 0)) { 1385 return formElement; 1386 } 1387 } 1388 } 1389 1390 return undefined; 1391 }; 1392 1393 /** 1394 * Update hidden state fields from the server into the DOM for any JSF forms which need to be updated. 1395 * This covers at least the form that submitted the request and any form that is covered in the render target list. 1396 * 1397 * @param updateElement The update element of partial response holding the state value. 1398 * @param context An object containing the request context, including the following properties: 1399 * the source element, per call onerror callback function, per call onevent callback function, the render 1400 * instructions, the submitting form ID, the naming container ID and naming container prefix. 1401 * @param hiddenStateFieldName The hidden state field name, e.g. javax.faces.ViewState or javax.faces.ClientWindow 1402 * @ignore 1403 */ 1404 var updateHiddenStateFields = function updateHiddenStateFields(updateElement, context, hiddenStateFieldName) { 1405 var firstChild = updateElement.firstChild; 1406 var state = (typeof firstChild.wholeText !== 'undefined') ? firstChild.wholeText : firstChild.nodeValue; 1407 var formsToUpdate = getFormsToUpdate(context); 1408 1409 for (var i = 0; i < formsToUpdate.length; i++) { 1410 var formToUpdate = formsToUpdate[i]; 1411 var field = getHiddenStateField(formToUpdate, hiddenStateFieldName, context.namingContainerPrefix); 1412 if (typeof field == "undefined") { 1413 field = document.createElement("input"); 1414 field.type = "hidden"; 1415 field.name = context.namingContainerPrefix + hiddenStateFieldName; 1416 formToUpdate.appendChild(field); 1417 } 1418 field.value = state; 1419 } 1420 } 1421 1422 /** 1423 * Find hidden state field for a given form. 1424 * @param form The form to find hidden state field in. 1425 * @param hiddenStateFieldName The hidden state field name, e.g. javax.faces.ViewState or javax.faces.ClientWindow 1426 * @param namingContainerPrefix The naming container prefix, if any (the view root ID suffixed with separator character). 1427 * @ignore 1428 */ 1429 var getHiddenStateField = function getHiddenStateField(form, hiddenStateFieldName, namingContainerPrefix) { 1430 namingContainerPrefix = namingContainerPrefix || ""; 1431 var field = form[namingContainerPrefix + hiddenStateFieldName]; 1432 1433 if (field) { 1434 return field; 1435 } 1436 else { 1437 var formElements = form.elements; 1438 1439 for (var i = 0, length = formElements.length; i < length; i++) { 1440 var formElement = formElements[i]; 1441 1442 if (formElement.name && (formElement.name.indexOf(hiddenStateFieldName) >= 0)) { 1443 return formElement; 1444 } 1445 } 1446 } 1447 1448 return undefined; 1449 }; 1450 1451 /** 1452 * Do update. 1453 * @param updateElement The update element of partial response. 1454 * @param context An object containing the request context, including the following properties: 1455 * the source element, per call onerror callback function, per call onevent callback function, the render 1456 * instructions, the submitting form ID, the naming container ID and naming container prefix. 1457 * @ignore 1458 */ 1459 var doUpdate = function doUpdate(updateElement, context) { 1460 var id, content, markup; 1461 var scripts = []; // temp holding value for array of script nodes 1462 1463 id = updateElement.getAttribute('id'); 1464 var viewStateRegex = new RegExp(context.namingContainerPrefix + "javax.faces.ViewState" + jsf.separatorchar + ".+$"); 1465 var windowIdRegex = new RegExp(context.namingContainerPrefix + "javax.faces.ClientWindow" + jsf.separatorchar + ".+$"); 1466 1467 if (id.match(viewStateRegex)) { 1468 updateHiddenStateFields(updateElement, context, "javax.faces.ViewState"); 1469 return; 1470 } else if (id.match(windowIdRegex)) { 1471 updateHiddenStateFields(updateElement, context, "javax.faces.ClientWindow"); 1472 return; 1473 } 1474 1475 // join the CDATA sections in the markup 1476 markup = ''; 1477 for (var j = 0; j < updateElement.childNodes.length; j++) { 1478 content = updateElement.childNodes[j]; 1479 markup += content.nodeValue; 1480 } 1481 1482 var src = markup; 1483 1484 // If our special render all markup is present.. 1485 if (id === "javax.faces.ViewRoot" || id === "javax.faces.ViewBody") { 1486 1487 // spec790: If UIViewRoot is currently being updated, 1488 // then it means that ajax navigation has taken place. 1489 // So, ensure that context.render has correct value for this condition, 1490 // because this is not necessarily correclty specified during the request. 1491 context.render = "@all"; 1492 1493 var bodyStartEx = new RegExp("< *body[^>]*>", "gi"); 1494 var bodyEndEx = new RegExp("< */ *body[^>]*>", "gi"); 1495 var newsrc; 1496 1497 var docBody = document.getElementsByTagName("body")[0]; 1498 var bodyStart = bodyStartEx.exec(src); 1499 1500 if (bodyStart !== null) { // replace body tag 1501 // First, try with XML manipulation 1502 try { 1503 runStylesheets(src); 1504 // Get scripts from text 1505 scripts = getScripts(src); 1506 // Remove scripts from text 1507 newsrc = removeScripts(src); 1508 elementReplace(getBodyElement(newsrc), docBody); 1509 runScripts(scripts); 1510 } catch (e) { 1511 // OK, replacing the body didn't work with XML - fall back to quirks mode insert 1512 var srcBody, bodyEnd; 1513 // if src contains </body> 1514 bodyEnd = bodyEndEx.exec(src); 1515 if (bodyEnd !== null) { 1516 srcBody = src.substring(bodyStartEx.lastIndex, bodyEnd.index); 1517 } else { // can't find the </body> tag, punt 1518 srcBody = src.substring(bodyStartEx.lastIndex); 1519 } 1520 // replace body contents with innerHTML - note, script handling happens within function 1521 elementReplaceStr(docBody, "body", srcBody); 1522 1523 } 1524 1525 } else { // replace body contents with innerHTML - note, script handling happens within function 1526 elementReplaceStr(docBody, "body", src); 1527 } 1528 } else if (id === "javax.faces.ViewHead") { 1529 throw new Error("javax.faces.ViewHead not supported - browsers cannot reliably replace the head's contents"); 1530 } else if (id === "javax.faces.Resource") { 1531 runStylesheets(src); 1532 scripts = getScripts(src); 1533 runScripts(scripts); 1534 } else { 1535 var element = $(id); 1536 if (!element) { 1537 throw new Error("During update: " + id + " not found"); 1538 } 1539 1540 if (context.namingContainerId && id == context.namingContainerId) { 1541 // spec790: If UIViewRoot is a NamingContainer and this is currently being updated, 1542 // then it means that ajax navigation has taken place. 1543 // So, ensure that context.render has correct value for this condition, 1544 // because this is not necessarily correclty specified during the request. 1545 context.render = context.namingContainerId; 1546 } 1547 1548 var parent = element.parentNode; 1549 // Trim space padding before assigning to innerHTML 1550 var html = src.replace(/^\s+/g, '').replace(/\s+$/g, ''); 1551 var parserElement = document.createElement('div'); 1552 var tag = element.nodeName.toLowerCase(); 1553 var tableElements = ['td', 'th', 'tr', 'tbody', 'thead', 'tfoot']; 1554 var isInTable = false; 1555 for (var tei = 0, tel = tableElements.length; tei < tel; tei++) { 1556 if (tableElements[tei] == tag) { 1557 isInTable = true; 1558 break; 1559 } 1560 } 1561 if (isInTable) { 1562 1563 if (isAutoExec()) { 1564 // Create html 1565 parserElement.innerHTML = '<table>' + html + '</table>'; 1566 } else { 1567 // Get the scripts from the text 1568 scripts = getScripts(html); 1569 // Remove scripts from text 1570 html = removeScripts(html); 1571 parserElement.innerHTML = '<table>' + html + '</table>'; 1572 } 1573 var newElement = parserElement.firstChild; 1574 //some browsers will also create intermediary elements such as table>tbody>tr>td 1575 while ((null !== newElement) && (id !== newElement.id)) { 1576 newElement = newElement.firstChild; 1577 } 1578 parent.replaceChild(newElement, element); 1579 runScripts(scripts); 1580 } else if (element.nodeName.toLowerCase() === 'input') { 1581 // special case handling for 'input' elements 1582 // in order to not lose focus when updating, 1583 // input elements need to be added in place. 1584 parserElement = document.createElement('div'); 1585 parserElement.innerHTML = html; 1586 newElement = parserElement.firstChild; 1587 1588 cloneAttributes(element, newElement); 1589 deleteNode(parserElement); 1590 } else if (html.length > 0) { 1591 if (isAutoExec()) { 1592 // Create html 1593 parserElement.innerHTML = html; 1594 } else { 1595 // Get the scripts from the text 1596 scripts = getScripts(html); 1597 // Remove scripts from text 1598 html = removeScripts(html); 1599 parserElement.innerHTML = html; 1600 } 1601 replaceNode(parserElement.firstChild, element); 1602 deleteNode(parserElement); 1603 runScripts(scripts); 1604 } 1605 } 1606 }; 1607 1608 /** 1609 * Delete a node specified by the element. 1610 * @param element 1611 * @ignore 1612 */ 1613 var doDelete = function doDelete(element) { 1614 var id = element.getAttribute('id'); 1615 var target = $(id); 1616 deleteNode(target); 1617 }; 1618 1619 /** 1620 * Insert a node specified by the element. 1621 * @param element 1622 * @ignore 1623 */ 1624 var doInsert = function doInsert(element) { 1625 var tablePattern = new RegExp("<\\s*(td|th|tr|tbody|thead|tfoot)", "i"); 1626 var scripts = []; 1627 var target = $(element.firstChild.getAttribute('id')); 1628 var parent = target.parentNode; 1629 var html = element.firstChild.firstChild.nodeValue; 1630 var isInTable = tablePattern.test(html); 1631 1632 if (!isAutoExec()) { 1633 // Get the scripts from the text 1634 scripts = getScripts(html); 1635 // Remove scripts from text 1636 html = removeScripts(html); 1637 } 1638 var tempElement = document.createElement('div'); 1639 var newElement = null; 1640 if (isInTable) { 1641 tempElement.innerHTML = '<table>' + html + '</table>'; 1642 newElement = tempElement.firstChild; 1643 //some browsers will also create intermediary elements such as table>tbody>tr>td 1644 //test for presence of id on the new element since we do not have it directly 1645 while ((null !== newElement) && ("" == newElement.id)) { 1646 newElement = newElement.firstChild; 1647 } 1648 } else { 1649 tempElement.innerHTML = html; 1650 newElement = tempElement.firstChild; 1651 } 1652 1653 if (element.firstChild.nodeName === 'after') { 1654 // Get the next in the list, to insert before 1655 target = target.nextSibling; 1656 } // otherwise, this is a 'before' element 1657 if (!!tempElement.innerHTML) { // check if only scripts were inserted - if so, do nothing here 1658 parent.insertBefore(newElement, target); 1659 } 1660 runScripts(scripts); 1661 deleteNode(tempElement); 1662 }; 1663 1664 /** 1665 * Modify attributes of given element id. 1666 * @param element 1667 * @ignore 1668 */ 1669 var doAttributes = function doAttributes(element) { 1670 1671 // Get id of element we'll act against 1672 var id = element.getAttribute('id'); 1673 1674 var target = $(id); 1675 1676 if (!target) { 1677 throw new Error("The specified id: " + id + " was not found in the page."); 1678 } 1679 1680 // There can be multiple attributes modified. Loop through the list. 1681 var nodes = element.childNodes; 1682 for (var i = 0; i < nodes.length; i++) { 1683 var name = nodes[i].getAttribute('name'); 1684 var value = nodes[i].getAttribute('value'); 1685 1686 //boolean attribute handling code for all browsers 1687 if (name === 'disabled') { 1688 target.disabled = value === 'disabled' || value === 'true'; 1689 return; 1690 } else if (name === 'checked') { 1691 target.checked = value === 'checked' || value === 'on' || value === 'true'; 1692 return; 1693 } else if (name == 'readonly') { 1694 target.readOnly = value === 'readonly' || value === 'true'; 1695 return; 1696 } 1697 1698 if (!isIE()) { 1699 if (name === 'value') { 1700 target.value = value; 1701 } else { 1702 target.setAttribute(name, value); 1703 } 1704 } else { // if it's IE, then quite a bit more work is required 1705 if (name === 'class') { 1706 target.className = value; 1707 } else if (name === "for") { 1708 name = 'htmlFor'; 1709 target.setAttribute(name, value, 0); 1710 } else if (name === 'style') { 1711 target.style.setAttribute('cssText', value, 0); 1712 } else if (name.substring(0, 2) === 'on') { 1713 var c = document.body.appendChild(document.createElement('span')); 1714 try { 1715 c.innerHTML = '<span ' + name + '="' + value + '"/>'; 1716 target[name] = c.firstChild[name]; 1717 } finally { 1718 document.body.removeChild(c); 1719 } 1720 } else if (name === 'dir') { 1721 if (jsf.getProjectStage() == 'Development') { 1722 throw new Error("Cannot set 'dir' attribute in IE"); 1723 } 1724 } else { 1725 target.setAttribute(name, value, 0); 1726 } 1727 } 1728 } 1729 }; 1730 1731 /** 1732 * Eval the CDATA of the element. 1733 * @param element to eval 1734 * @ignore 1735 */ 1736 var doEval = function doEval(element) { 1737 var evalText = ''; 1738 var childNodes = element.childNodes; 1739 for (var i = 0; i < childNodes.length; i++) { 1740 evalText += childNodes[i].nodeValue; 1741 } 1742 globalEval(evalText); 1743 }; 1744 1745 /** 1746 * Ajax Request Queue 1747 * @ignore 1748 */ 1749 var Queue = new function Queue() { 1750 1751 // Create the internal queue 1752 var queue = []; 1753 1754 1755 // the amount of space at the front of the queue, initialised to zero 1756 var queueSpace = 0; 1757 1758 /** Returns the size of this Queue. The size of a Queue is equal to the number 1759 * of elements that have been enqueued minus the number of elements that have 1760 * been dequeued. 1761 * @ignore 1762 */ 1763 this.getSize = function getSize() { 1764 return queue.length - queueSpace; 1765 }; 1766 1767 /** Returns true if this Queue is empty, and false otherwise. A Queue is empty 1768 * if the number of elements that have been enqueued equals the number of 1769 * elements that have been dequeued. 1770 * @ignore 1771 */ 1772 this.isEmpty = function isEmpty() { 1773 return (queue.length === 0); 1774 }; 1775 1776 /** Enqueues the specified element in this Queue. 1777 * 1778 * @param element - the element to enqueue 1779 * @ignore 1780 */ 1781 this.enqueue = function enqueue(element) { 1782 // Queue the request 1783 queue.push(element); 1784 }; 1785 1786 1787 /** Dequeues an element from this Queue. The oldest element in this Queue is 1788 * removed and returned. If this Queue is empty then undefined is returned. 1789 * 1790 * @returns Object The element that was removed from the queue. 1791 * @ignore 1792 */ 1793 this.dequeue = function dequeue() { 1794 // initialise the element to return to be undefined 1795 var element = undefined; 1796 1797 // check whether the queue is empty 1798 if (queue.length) { 1799 // fetch the oldest element in the queue 1800 element = queue[queueSpace]; 1801 1802 // update the amount of space and check whether a shift should occur 1803 if (++queueSpace * 2 >= queue.length) { 1804 // set the queue equal to the non-empty portion of the queue 1805 queue = queue.slice(queueSpace); 1806 // reset the amount of space at the front of the queue 1807 queueSpace = 0; 1808 } 1809 } 1810 // return the removed element 1811 try { 1812 return element; 1813 } finally { 1814 element = null; // IE 6 leak prevention 1815 } 1816 }; 1817 1818 /** Returns the oldest element in this Queue. If this Queue is empty then 1819 * undefined is returned. This function returns the same value as the dequeue 1820 * function, but does not remove the returned element from this Queue. 1821 * @ignore 1822 */ 1823 this.getOldestElement = function getOldestElement() { 1824 // initialise the element to return to be undefined 1825 var element = undefined; 1826 1827 // if the queue is not element then fetch the oldest element in the queue 1828 if (queue.length) { 1829 element = queue[queueSpace]; 1830 } 1831 // return the oldest element 1832 try { 1833 return element; 1834 } finally { 1835 element = null; //IE 6 leak prevention 1836 } 1837 }; 1838 }(); 1839 1840 1841 /** 1842 * AjaxEngine handles Ajax implementation details. 1843 * @ignore 1844 */ 1845 var AjaxEngine = function AjaxEngine(context) { 1846 1847 var req = {}; // Request Object 1848 req.url = null; // Request URL 1849 req.context = context; // Context of request and response 1850 req.context.sourceid = null; // Source of this request 1851 req.context.onerror = null; // Error handler for request 1852 req.context.onevent = null; // Event handler for request 1853 req.context.namingContainerId = null; // If UIViewRoot is an instance of NamingContainer this represents its ID. 1854 req.context.namingContainerPrefix = null; // If UIViewRoot is an instance of NamingContainer this represents its ID suffixed with separator character, else an empty string. 1855 req.xmlReq = null; // XMLHttpRequest Object 1856 req.async = true; // Default - Asynchronous 1857 req.parameters = {}; // Parameters For GET or POST 1858 req.queryString = null; // Encoded Data For GET or POST 1859 req.method = null; // GET or POST 1860 req.status = null; // Response Status Code From Server 1861 req.fromQueue = false; // Indicates if the request was taken off the queue before being sent. This prevents the request from entering the queue redundantly. 1862 1863 req.que = Queue; 1864 1865 // Get a transport Handle 1866 // The transport will be an iframe transport if the form 1867 // has multipart encoding type. This is where we could 1868 // handle XMLHttpRequest Level2 as well (perhaps 1869 // something like: if ('upload' in req.xmlReq)' 1870 req.xmlReq = getTransport(context); 1871 1872 if (req.xmlReq === null) { 1873 return null; 1874 } 1875 1876 /** 1877 * @ignore 1878 */ 1879 function noop() {} 1880 1881 // Set up request/response state callbacks 1882 /** 1883 * @ignore 1884 */ 1885 req.xmlReq.onreadystatechange = function() { 1886 if (req.xmlReq.readyState === 4) { 1887 req.onComplete(); 1888 // next two lines prevent closure/ciruclar reference leaks 1889 // of XHR instances in IE 1890 req.xmlReq.onreadystatechange = noop; 1891 req.xmlReq = null; 1892 } 1893 }; 1894 1895 /** 1896 * This function is called when the request/response interaction 1897 * is complete. If the return status code is successfull, 1898 * dequeue all requests from the queue that have completed. If a 1899 * request has been found on the queue that has not been sent, 1900 * send the request. 1901 * @ignore 1902 */ 1903 req.onComplete = function onComplete() { 1904 if (req.xmlReq.status && (req.xmlReq.status >= 200 && req.xmlReq.status < 300)) { 1905 sendEvent(req.xmlReq, req.context, "complete"); 1906 jsf.ajax.response(req.xmlReq, req.context); 1907 } else { 1908 sendEvent(req.xmlReq, req.context, "complete"); 1909 sendError(req.xmlReq, req.context, "httpError"); 1910 } 1911 1912 // Regardless of whether the request completed successfully (or not), 1913 // dequeue requests that have been completed (readyState 4) and send 1914 // requests that ready to be sent (readyState 0). 1915 1916 var nextReq = req.que.getOldestElement(); 1917 if (nextReq === null || typeof nextReq === 'undefined') { 1918 return; 1919 } 1920 while ((typeof nextReq.xmlReq !== 'undefined' && nextReq.xmlReq !== null) && 1921 nextReq.xmlReq.readyState === 4) { 1922 req.que.dequeue(); 1923 nextReq = req.que.getOldestElement(); 1924 if (nextReq === null || typeof nextReq === 'undefined') { 1925 break; 1926 } 1927 } 1928 if (nextReq === null || typeof nextReq === 'undefined') { 1929 return; 1930 } 1931 if ((typeof nextReq.xmlReq !== 'undefined' && nextReq.xmlReq !== null) && 1932 nextReq.xmlReq.readyState === 0) { 1933 nextReq.fromQueue = true; 1934 nextReq.sendRequest(); 1935 } 1936 }; 1937 1938 /** 1939 * Utility method that accepts additional arguments for the AjaxEngine. 1940 * If an argument is passed in that matches an AjaxEngine property, the 1941 * argument value becomes the value of the AjaxEngine property. 1942 * Arguments that don't match AjaxEngine properties are added as 1943 * request parameters. 1944 * @ignore 1945 */ 1946 req.setupArguments = function(args) { 1947 for (var i in args) { 1948 if (args.hasOwnProperty(i)) { 1949 if (typeof req[i] === 'undefined') { 1950 req.parameters[i] = args[i]; 1951 } else { 1952 req[i] = args[i]; 1953 } 1954 } 1955 } 1956 }; 1957 1958 /** 1959 * This function does final encoding of parameters, determines the request method 1960 * (GET or POST) and sends the request using the specified url. 1961 * @ignore 1962 */ 1963 req.sendRequest = function() { 1964 if (req.xmlReq !== null) { 1965 // if there is already a request on the queue waiting to be processed.. 1966 // just queue this request 1967 if (!req.que.isEmpty()) { 1968 if (!req.fromQueue) { 1969 req.que.enqueue(req); 1970 return; 1971 } 1972 } 1973 // If the queue is empty, queue up this request and send 1974 if (!req.fromQueue) { 1975 req.que.enqueue(req); 1976 } 1977 // Some logic to get the real request URL 1978 if (req.generateUniqueUrl && req.method == "GET") { 1979 req.parameters["AjaxRequestUniqueId"] = new Date().getTime() + "" + req.requestIndex; 1980 } 1981 var content = null; // For POST requests, to hold query string 1982 for (var i in req.parameters) { 1983 if (req.parameters.hasOwnProperty(i)) { 1984 if (req.queryString.length > 0) { 1985 req.queryString += "&"; 1986 } 1987 req.queryString += encodeURIComponent(i) + "=" + encodeURIComponent(req.parameters[i]); 1988 } 1989 } 1990 if (req.method === "GET") { 1991 if (req.queryString.length > 0) { 1992 req.url += ((req.url.indexOf("?") > -1) ? "&" : "?") + req.queryString; 1993 } 1994 } 1995 req.xmlReq.open(req.method, req.url, req.async); 1996 // note that we are including the charset=UTF-8 as part of the content type (even 1997 // if encodeURIComponent encodes as UTF-8), because with some 1998 // browsers it will not be set in the request. Some server implementations need to 1999 // determine the character encoding from the request header content type. 2000 if (req.method === "POST") { 2001 if (typeof req.xmlReq.setRequestHeader !== 'undefined') { 2002 req.xmlReq.setRequestHeader('Faces-Request', 'partial/ajax'); 2003 req.xmlReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); 2004 } 2005 content = req.queryString; 2006 } 2007 // note that async == false is not a supported feature. We may change it in ways 2008 // that break existing programs at any time, with no warning. 2009 if(!req.async) { 2010 req.xmlReq.onreadystatechange = null; // no need for readystate change listening 2011 } 2012 sendEvent(req.xmlReq, req.context, "begin"); 2013 req.xmlReq.send(content); 2014 if(!req.async){ 2015 req.onComplete(); 2016 } 2017 } 2018 }; 2019 2020 return req; 2021 }; 2022 2023 /** 2024 * Error handling callback. 2025 * Assumes that the request has completed. 2026 * @ignore 2027 */ 2028 var sendError = function sendError(request, context, status, description, serverErrorName, serverErrorMessage) { 2029 2030 // Possible errornames: 2031 // httpError 2032 // emptyResponse 2033 // serverError 2034 // malformedXML 2035 2036 var sent = false; 2037 var data = {}; // data payload for function 2038 data.type = "error"; 2039 data.status = status; 2040 data.source = context.sourceid; 2041 data.responseCode = request.status; 2042 data.responseXML = request.responseXML; 2043 data.responseText = request.responseText; 2044 2045 // ensure data source is the dom element and not the ID 2046 // per 14.4.1 of the 2.0 specification. 2047 if (typeof data.source === 'string') { 2048 data.source = document.getElementById(data.source); 2049 } 2050 2051 if (description) { 2052 data.description = description; 2053 } else if (status == "httpError") { 2054 if (data.responseCode === 0) { 2055 data.description = "The Http Transport returned a 0 status code. This is usually the result of mixing ajax and full requests. This is usually undesired, for both performance and data integrity reasons."; 2056 } else { 2057 data.description = "There was an error communicating with the server, status: " + data.responseCode; 2058 } 2059 } else if (status == "serverError") { 2060 data.description = serverErrorMessage; 2061 } else if (status == "emptyResponse") { 2062 data.description = "An empty response was received from the server. Check server error logs."; 2063 } else if (status == "malformedXML") { 2064 if (getParseErrorText(data.responseXML) !== PARSED_OK) { 2065 data.description = getParseErrorText(data.responseXML); 2066 } else { 2067 data.description = "An invalid XML response was received from the server."; 2068 } 2069 } 2070 2071 if (status == "serverError") { 2072 data.errorName = serverErrorName; 2073 data.errorMessage = serverErrorMessage; 2074 } 2075 2076 // If we have a registered callback, send the error to it. 2077 if (context.onerror) { 2078 context.onerror.call(null, data); 2079 sent = true; 2080 } 2081 2082 for (var i in errorListeners) { 2083 if (errorListeners.hasOwnProperty(i)) { 2084 errorListeners[i].call(null, data); 2085 sent = true; 2086 } 2087 } 2088 2089 if (!sent && jsf.getProjectStage() === "Development") { 2090 if (status == "serverError") { 2091 alert("serverError: " + serverErrorName + " " + serverErrorMessage); 2092 } else { 2093 alert(status + ": " + data.description); 2094 } 2095 } 2096 }; 2097 2098 /** 2099 * Event handling callback. 2100 * Request is assumed to have completed, except in the case of event = 'begin'. 2101 * @ignore 2102 */ 2103 var sendEvent = function sendEvent(request, context, status) { 2104 2105 var data = {}; 2106 data.type = "event"; 2107 data.status = status; 2108 data.source = context.sourceid; 2109 // ensure data source is the dom element and not the ID 2110 // per 14.4.1 of the 2.0 specification. 2111 if (typeof data.source === 'string') { 2112 data.source = document.getElementById(data.source); 2113 } 2114 if (status !== 'begin') { 2115 data.responseCode = request.status; 2116 data.responseXML = request.responseXML; 2117 data.responseText = request.responseText; 2118 } 2119 2120 if (context.onevent) { 2121 context.onevent.call(null, data); 2122 } 2123 2124 for (var i in eventListeners) { 2125 if (eventListeners.hasOwnProperty(i)) { 2126 eventListeners[i].call(null, data); 2127 } 2128 } 2129 }; 2130 2131 var unescapeHTML = function unescapeHTML(escapedHTML) { 2132 return escapedHTML 2133 .replace(/'/g, "'") 2134 .replace(/"/g, '"') 2135 .replace(/>/g, '>') 2136 .replace(/</g, '<') 2137 .replace(/&/g, '&'); 2138 }; 2139 2140 // Use module pattern to return the functions we actually expose 2141 return { 2142 /** 2143 * Register a callback for error handling. 2144 * <p><b>Usage:</b></p> 2145 * <pre><code> 2146 * jsf.ajax.addOnError(handleError); 2147 * ... 2148 * var handleError = function handleError(data) { 2149 * ... 2150 * } 2151 * </pre></code> 2152 * <p><b>Implementation Requirements:</b></p> 2153 * This function must accept a reference to an existing JavaScript function. 2154 * The JavaScript function reference must be added to a list of callbacks, making it possible 2155 * to register more than one callback by invoking <code>jsf.ajax.addOnError</code> 2156 * more than once. This function must throw an error if the <code>callback</code> 2157 * argument is not a function. 2158 * 2159 * @member jsf.ajax 2160 * @param callback a reference to a function to call on an error 2161 */ 2162 addOnError: function addOnError(callback) { 2163 if (typeof callback === 'function') { 2164 errorListeners[errorListeners.length] = callback; 2165 } else { 2166 throw new Error("jsf.ajax.addOnError: Added a callback that was not a function."); 2167 } 2168 }, 2169 /** 2170 * Register a callback for event handling. 2171 * <p><b>Usage:</b></p> 2172 * <pre><code> 2173 * jsf.ajax.addOnEvent(statusUpdate); 2174 * ... 2175 * var statusUpdate = function statusUpdate(data) { 2176 * ... 2177 * } 2178 * </pre></code> 2179 * <p><b>Implementation Requirements:</b></p> 2180 * This function must accept a reference to an existing JavaScript function. 2181 * The JavaScript function reference must be added to a list of callbacks, making it possible 2182 * to register more than one callback by invoking <code>jsf.ajax.addOnEvent</code> 2183 * more than once. This function must throw an error if the <code>callback</code> 2184 * argument is not a function. 2185 * 2186 * @member jsf.ajax 2187 * @param callback a reference to a function to call on an event 2188 */ 2189 addOnEvent: function addOnEvent(callback) { 2190 if (typeof callback === 'function') { 2191 eventListeners[eventListeners.length] = callback; 2192 } else { 2193 throw new Error("jsf.ajax.addOnEvent: Added a callback that was not a function"); 2194 } 2195 }, 2196 /** 2197 2198 * <p><span class="changed_modified_2_2">Send</span> an 2199 * asynchronous Ajax req uest to the server. 2200 2201 * <p><b>Usage:</b></p> 2202 * <pre><code> 2203 * Example showing all optional arguments: 2204 * 2205 * <commandButton id="button1" value="submit" 2206 * onclick="jsf.ajax.request(this,event, 2207 * {execute:'button1',render:'status',onevent: handleEvent,onerror: handleError});return false;"/> 2208 * </commandButton/> 2209 * </pre></code> 2210 * <p><b>Implementation Requirements:</b></p> 2211 * This function must: 2212 * <ul> 2213 * <li>Be used within the context of a <code>form</code><span class="changed_added_2_3">, 2214 * else throw an error</span>.</li> 2215 * <li>Capture the element that triggered this Ajax request 2216 * (from the <code>source</code> argument, also known as the 2217 * <code>source</code> element.</li> 2218 * <li>If the <code>source</code> element is <code>null</code> or 2219 * <code>undefined</code> throw an error.</li> 2220 * <li>If the <code>source</code> argument is not a <code>string</code> or 2221 * DOM element object, throw an error.</li> 2222 * <li>If the <code>source</code> argument is a <code>string</code>, find the 2223 * DOM element for that <code>string</code> identifier. 2224 * <li>If the DOM element could not be determined, throw an error.</li> 2225 * <li class="changed_added_2_3">If the <code>javax.faces.ViewState</code> 2226 * element could not be found, throw an error.</li> 2227 * <li class="changed_added_2_3">If the ID of the <code>javax.faces.ViewState</code> 2228 * element has a <code><VIEW_ROOT_CONTAINER_CLIENT_ID><SEP></code> 2229 * prefix, where <SEP> is the currently configured 2230 * <code>UINamingContainer.getSeparatorChar()</code> and 2231 * <VIEW_ROOT_CONTAINER_CLIENT_ID> is the return from 2232 * <code>UIViewRoot.getContainerClientId()</code> on the 2233 * view from whence this state originated, then remember it as <i>namespace prefix</i>. 2234 * This is needed during encoding of the set of post data arguments.</li> 2235 * <li>If the <code>onerror</code> and <code>onevent</code> arguments are set, 2236 * they must be functions, or throw an error. 2237 * <li>Determine the <code>source</code> element's <code>form</code> 2238 * element.</li> 2239 * <li>Get the <code>form</code> view state by calling 2240 * {@link jsf.getViewState} passing the 2241 * <code>form</code> element as the argument.</li> 2242 * <li>Collect post data arguments for the Ajax request. 2243 * <ul> 2244 * <li>The following name/value pairs are required post data arguments: 2245 * <table border="1"> 2246 * <tr> 2247 * <th>name</th> 2248 * <th>value</th> 2249 * </tr> 2250 * <tr> 2251 * <td><code>javax.faces.ViewState</code></td> 2252 * <td><code>Contents of javax.faces.ViewState hidden field. This is included when 2253 * {@link jsf.getViewState} is used.</code></td> 2254 * </tr> 2255 * <tr> 2256 * <td><code>javax.faces.partial.ajax</code></td> 2257 * <td><code>true</code></td> 2258 * </tr> 2259 * <tr> 2260 * <td><code>javax.faces.source</code></td> 2261 * <td><code>The identifier of the element that triggered this request.</code></td> 2262 * </tr> 2263 * <tr class="changed_added_2_2"> 2264 * <td><code>javax.faces.ClientWindow</code></td> 2265 2266 * <td><code>Call jsf.getClientWindow(), passing the current 2267 * form. If the return is non-null, it must be set as the 2268 * value of this name/value pair, otherwise, a name/value 2269 * pair for client window must not be sent.</code></td> 2270 2271 * </tr> 2272 * </table> 2273 * </li> 2274 * </ul> 2275 * </li> 2276 * <li>Collect optional post data arguments for the Ajax request. 2277 * <ul> 2278 * <li>Determine additional arguments (if any) from the <code>options</code> 2279 * argument. If <code>options.execute</code> exists: 2280 * <ul> 2281 * <li>If the keyword <code>@none</code> is present, do not create and send 2282 * the post data argument <code>javax.faces.partial.execute</code>.</li> 2283 * <li>If the keyword <code>@all</code> is present, create the post data argument with 2284 * the name <code>javax.faces.partial.execute</code> and the value <code>@all</code>.</li> 2285 * <li>Otherwise, there are specific identifiers that need to be sent. Create the post 2286 * data argument with the name <code>javax.faces.partial.execute</code> and the value as a 2287 * space delimited <code>string</code> of client identifiers.</li> 2288 * </ul> 2289 * </li> 2290 * <li>If <code>options.execute</code> does not exist, create the post data argument with the 2291 * name <code>javax.faces.partial.execute</code> and the value as the identifier of the 2292 * element that caused this request.</li> 2293 * <li>If <code>options.render</code> exists: 2294 * <ul> 2295 * <li>If the keyword <code>@none</code> is present, do not create and send 2296 * the post data argument <code>javax.faces.partial.render</code>.</li> 2297 * <li>If the keyword <code>@all</code> is present, create the post data argument with 2298 * the name <code>javax.faces.partial.render</code> and the value <code>@all</code>.</li> 2299 * <li>Otherwise, there are specific identifiers that need to be sent. Create the post 2300 * data argument with the name <code>javax.faces.partial.render</code> and the value as a 2301 * space delimited <code>string</code> of client identifiers.</li> 2302 * </ul> 2303 * <li>If <code>options.render</code> does not exist do not create and send the 2304 * post data argument <code>javax.faces.partial.render</code>.</li> 2305 2306 * <li class="changed_added_2_2">If 2307 * <code>options.delay</code> exists let it be the value 2308 * <em>delay</em>, for this discussion. If 2309 * <code>options.delay</code> does not exist, or is the 2310 * literal string <code>'none'</code>, without the quotes, 2311 * no delay is used. If less than <em>delay</em> 2312 * milliseconds elapses between calls to <em>request()</em> 2313 * only the most recent one is sent and all other requests 2314 * are discarded.</li> 2315 2316 2317 * <li class="changed_added_2_2">If 2318 * <code>options.resetValues</code> exists and its value is 2319 * <code>true</code>, ensure a post data argument with the 2320 * name <code>javax.faces.partial.resetValues</code> and the 2321 * value <code>true</code> is sent in addition to the other 2322 * post data arguments. This will cause 2323 * <code>UIViewRoot.resetValues()</code> to be called, 2324 * passing the value of the "render" attribute. Note: do 2325 * not use any of the <code>@</code> keywords such as 2326 * <code>@form</code> or <code>@this</code> with this option 2327 * because <code>UIViewRoot.resetValues()</code> does not 2328 * descend into the children of the listed components.</li> 2329 2330 2331 * <li>Determine additional arguments (if any) from the <code>event</code> 2332 * argument. The following name/value pairs may be used from the 2333 * <code>event</code> object: 2334 * <ul> 2335 * <li><code>target</code> - the ID of the element that triggered the event.</li> 2336 * <li><code>captured</code> - the ID of the element that captured the event.</li> 2337 * <li><code>type</code> - the type of event (ex: onkeypress)</li> 2338 * <li><code>alt</code> - <code>true</code> if ALT key was pressed.</li> 2339 * <li><code>ctrl</code> - <code>true</code> if CTRL key was pressed.</li> 2340 * <li><code>shift</code> - <code>true</code> if SHIFT key was pressed. </li> 2341 * <li><code>meta</code> - <code>true</code> if META key was pressed. </li> 2342 * <li><code>right</code> - <code>true</code> if right mouse button 2343 * was pressed. </li> 2344 * <li><code>left</code> - <code>true</code> if left mouse button 2345 * was pressed. </li> 2346 * <li><code>keycode</code> - the key code. 2347 * </ul> 2348 * </li> 2349 * </ul> 2350 * </li> 2351 * <li>Encode the set of post data arguments. <span class="changed_added_2_3"> 2352 * If the <code>javax.faces.ViewState</code> element has a namespace prefix, then 2353 * make sure that all post data arguments are prefixed with this namespace prefix. 2354 * </span></li> 2355 * <li>Join the encoded view state with the encoded set of post data arguments 2356 * to form the <code>query string</code> that will be sent to the server.</li> 2357 * <li>Create a request <code>context</code> object and set the properties: 2358 * <ul><li><code>source</code> (the source DOM element for this request)</li> 2359 * <li><code>onerror</code> (the error handler for this request)</li> 2360 * <li><code>onevent</code> (the event handler for this request)</li></ul> 2361 * The request context will be used during error/event handling.</li> 2362 * <li>Send a <code>begin</code> event following the procedure as outlined 2363 * in the Chapter 13 "Sending Events" section of the spec prose document <a 2364 * href="../../javadocs/overview-summary.html#prose_document">linked in the 2365 * overview summary</a></li> 2366 * <li>Set the request header with the name: <code>Faces-Request</code> and the 2367 * value: <code>partial/ajax</code>.</li> 2368 * <li>Determine the <code>posting URL</code> as follows: If the hidden field 2369 * <code>javax.faces.encodedURL</code> is present in the submitting form, use its 2370 * value as the <code>posting URL</code>. Otherwise, use the <code>action</code> 2371 * property of the <code>form</code> element as the <code>URL</code>.</li> 2372 2373 * <li> 2374 2375 * <p><span class="changed_modified_2_2">Determine whether 2376 * or not the submitting form is using 2377 * <code>multipart/form-data</code> as its 2378 * <code>enctype</code> attribute. If not, send the request 2379 * as an <code>asynchronous POST</code> using the 2380 * <code>posting URL</code> that was determined in the 2381 * previous step.</span> <span 2382 * class="changed_added_2_2">Otherwise, send the request 2383 * using a multi-part capable transport layer, such as a 2384 * hidden inline frame. Note that using a hidden inline 2385 * frame does <strong>not</strong> use 2386 * <code>XMLHttpRequest</code>, but the request must be sent 2387 * with all the parameters that a JSF 2388 * <code>XMLHttpRequest</code> would have been sent with. 2389 * In this way, the server side processing of the request 2390 * will be identical whether or the request is multipart or 2391 * not.</span></p> 2392 2393 * <div class="changed_added_2_2"> 2394 2395 * <p>The <code>begin</code>, <code>complete</code>, and 2396 * <code>success</code> events must be emulated when using 2397 * the multipart transport. This allows any listeners to 2398 * behave uniformly regardless of the multipart or 2399 * <code>XMLHttpRequest</code> nature of the transport.</p> 2400 2401 * </div></li> 2402 * </ul> 2403 * Form serialization should occur just before the request is sent to minimize 2404 * the amount of time between the creation of the serialized form data and the 2405 * sending of the serialized form data (in the case of long requests in the queue). 2406 * Before the request is sent it must be put into a queue to ensure requests 2407 * are sent in the same order as when they were initiated. The request callback function 2408 * must examine the queue and determine the next request to be sent. The behavior of the 2409 * request callback function must be as follows: 2410 * <ul> 2411 * <li>If the request completed successfully invoke {@link jsf.ajax.response} 2412 * passing the <code>request</code> object.</li> 2413 * <li>If the request did not complete successfully, notify the client.</li> 2414 * <li>Regardless of the outcome of the request (success or error) every request in the 2415 * queue must be handled. Examine the status of each request in the queue starting from 2416 * the request that has been in the queue the longest. If the status of the request is 2417 * <code>complete</code> (readyState 4), dequeue the request (remove it from the queue). 2418 * If the request has not been sent (readyState 0), send the request. Requests that are 2419 * taken off the queue and sent should not be put back on the queue.</li> 2420 * </ul> 2421 * 2422 * </p> 2423 * 2424 * @param source The DOM element that triggered this Ajax request, or an id string of the 2425 * element to use as the triggering element. 2426 * @param event The DOM event that triggered this Ajax request. The 2427 * <code>event</code> argument is optional. 2428 * @param options The set of available options that can be sent as 2429 * request parameters to control client and/or server side 2430 * request processing. Acceptable name/value pair options are: 2431 * <table border="1"> 2432 * <tr> 2433 * <th>name</th> 2434 * <th>value</th> 2435 * </tr> 2436 * <tr> 2437 * <td><code>execute</code></td> 2438 * <td><code>space seperated list of client identifiers</code></td> 2439 * </tr> 2440 * <tr> 2441 * <td><code>render</code></td> 2442 * <td><code>space seperated list of client identifiers</code></td> 2443 * </tr> 2444 * <tr> 2445 * <td><code>onevent</code></td> 2446 * <td><code>function to callback for event</code></td> 2447 * </tr> 2448 * <tr> 2449 * <td><code>onerror</code></td> 2450 * <td><code>function to callback for error</code></td> 2451 * </tr> 2452 * <tr> 2453 * <td><code>params</code></td> 2454 * <td><code>object containing parameters to include in the request</code></td> 2455 * </tr> 2456 2457 * <tr class="changed_added_2_2"> 2458 2459 * <td><code>delay</code></td> 2460 2461 * <td>If less than <em>delay</em> milliseconds elapses 2462 * between calls to <em>request()</em> only the most recent 2463 * one is sent and all other requests are discarded. If the 2464 * value of <em>delay</em> is the literal string 2465 * <code>'none'</code> without the quotes, or no delay is 2466 * specified, no delay is used. </td> 2467 2468 * </tr> 2469 2470 * <tr class="changed_added_2_2"> 2471 2472 * <td><code>resetValues</code></td> 2473 2474 * <td>If true, ensure a post data argument with the name 2475 * javax.faces.partial.resetValues and the value true is 2476 * sent in addition to the other post data arguments. This 2477 * will cause UIViewRoot.resetValues() to be called, passing 2478 * the value of the "render" attribute. Note: do not use any 2479 * of the @ keywords such as @form or @this with this option 2480 * because UIViewRoot.resetValues() does not descend into 2481 * the children of the listed components.</td> 2482 2483 * </tr> 2484 2485 2486 * </table> 2487 * The <code>options</code> argument is optional. 2488 * @member jsf.ajax 2489 * @function jsf.ajax.request 2490 2491 * @throws Error if first required argument 2492 * <code>element</code> is not specified, or if one or more 2493 * of the components in the <code>options.execute</code> 2494 * list is a file upload component, but the form's enctype 2495 * is not set to <code>multipart/form-data</code> 2496 */ 2497 2498 request: function request(source, event, options) { 2499 2500 var element, form, viewStateElement; // Element variables 2501 var all, none; 2502 2503 var context = {}; 2504 2505 if (typeof source === 'undefined' || source === null) { 2506 throw new Error("jsf.ajax.request: source not set"); 2507 } 2508 if(delayHandler) { 2509 clearTimeout(delayHandler); 2510 delayHandler = null; 2511 } 2512 2513 // set up the element based on source 2514 if (typeof source === 'string') { 2515 element = document.getElementById(source); 2516 } else if (typeof source === 'object') { 2517 element = source; 2518 } else { 2519 throw new Error("jsf.ajax.request: source must be object or string"); 2520 } 2521 // attempt to handle case of name unset 2522 // this might be true in a badly written composite component 2523 if (!element.name) { 2524 element.name = element.id; 2525 } 2526 2527 context.element = element; 2528 2529 if (typeof(options) === 'undefined' || options === null) { 2530 options = {}; 2531 } 2532 2533 // Error handler for this request 2534 var onerror = false; 2535 2536 if (options.onerror && typeof options.onerror === 'function') { 2537 onerror = options.onerror; 2538 } else if (options.onerror && typeof options.onerror !== 'function') { 2539 throw new Error("jsf.ajax.request: Added an onerror callback that was not a function"); 2540 } 2541 2542 // Event handler for this request 2543 var onevent = false; 2544 2545 if (options.onevent && typeof options.onevent === 'function') { 2546 onevent = options.onevent; 2547 } else if (options.onevent && typeof options.onevent !== 'function') { 2548 throw new Error("jsf.ajax.request: Added an onevent callback that was not a function"); 2549 } 2550 2551 form = getForm(element); 2552 if (!form) { 2553 throw new Error("jsf.ajax.request: Method must be called within a form"); 2554 } 2555 2556 viewStateElement = getHiddenStateField(form, "javax.faces.ViewState"); 2557 if (!viewStateElement) { 2558 throw new Error("jsf.ajax.request: Form has no view state element"); 2559 } 2560 2561 context.form = form; 2562 context.formId = form.id; 2563 2564 var viewState = jsf.getViewState(form); 2565 2566 // Set up additional arguments to be used in the request.. 2567 // Make sure "javax.faces.source" is set up. 2568 // If there were "execute" ids specified, make sure we 2569 // include the identifier of the source element in the 2570 // "execute" list. If there were no "execute" ids 2571 // specified, determine the default. 2572 2573 var args = {}; 2574 2575 var namingContainerPrefix = viewStateElement.name.substring(0, viewStateElement.name.indexOf("javax.faces.ViewState")); 2576 2577 args[namingContainerPrefix + "javax.faces.source"] = element.id; 2578 2579 if (event && !!event.type) { 2580 args[namingContainerPrefix + "javax.faces.partial.event"] = event.type; 2581 } 2582 2583 if ("resetValues" in options) { 2584 args[namingContainerPrefix + "javax.faces.partial.resetValues"] = options.resetValues; 2585 } 2586 2587 // If we have 'execute' identifiers: 2588 // Handle any keywords that may be present. 2589 // If @none present anywhere, do not send the 2590 // "javax.faces.partial.execute" parameter. 2591 // The 'execute' and 'render' lists must be space 2592 // delimited. 2593 2594 if (options.execute) { 2595 none = options.execute.indexOf("@none"); 2596 if (none < 0) { 2597 all = options.execute.indexOf("@all"); 2598 if (all < 0) { 2599 options.execute = options.execute.replace("@this", element.id); 2600 options.execute = options.execute.replace("@form", form.id); 2601 var temp = options.execute.split(' '); 2602 if (!isInArray(temp, element.name)) { 2603 options.execute = element.name + " " + options.execute; 2604 } 2605 if (namingContainerPrefix) { 2606 options.execute = namespaceParametersIfNecessary(options.execute, element.name, namingContainerPrefix); 2607 } 2608 } else { 2609 options.execute = "@all"; 2610 } 2611 args[namingContainerPrefix + "javax.faces.partial.execute"] = options.execute; 2612 } 2613 } else { 2614 options.execute = element.name + " " + element.id; 2615 args[namingContainerPrefix + "javax.faces.partial.execute"] = options.execute; 2616 } 2617 2618 if (options.render) { 2619 none = options.render.indexOf("@none"); 2620 if (none < 0) { 2621 all = options.render.indexOf("@all"); 2622 if (all < 0) { 2623 options.render = options.render.replace("@this", element.id); 2624 options.render = options.render.replace("@form", form.id); 2625 if (namingContainerPrefix) { 2626 options.render = namespaceParametersIfNecessary(options.render, element.name, namingContainerPrefix); 2627 } 2628 } else { 2629 options.render = "@all"; 2630 } 2631 args[namingContainerPrefix + "javax.faces.partial.render"] = options.render; 2632 } 2633 } 2634 var explicitlyDoNotDelay = ((typeof options.delay == 'undefined') || (typeof options.delay == 'string') && 2635 (options.delay.toLowerCase() == 'none')); 2636 var delayValue; 2637 if (typeof options.delay == 'number') { 2638 delayValue = options.delay; 2639 } else { 2640 var converted = parseInt(options.delay); 2641 2642 if (!explicitlyDoNotDelay && isNaN(converted)) { 2643 throw new Error('invalid value for delay option: ' + options.delay); 2644 } 2645 delayValue = converted; 2646 } 2647 2648 var checkForTypeFile 2649 2650 // check the execute ids to see if any include an input of type "file" 2651 context.includesInputFile = false; 2652 var ids = options.execute.split(" "); 2653 if (ids == "@all") { ids = [ form.id ]; } 2654 if (ids) { 2655 for (i = 0; i < ids.length; i++) { 2656 var elem = document.getElementById(ids[i]); 2657 if (elem) { 2658 var nodeType = elem.nodeType; 2659 if (nodeType == Node.ELEMENT_NODE) { 2660 var elemAttributeDetector = detectAttributes(elem); 2661 if (elemAttributeDetector("type")) { 2662 if (elem.getAttribute("type") === "file") { 2663 context.includesInputFile = true; 2664 break; 2665 } 2666 } else { 2667 if (hasInputFileControl(elem)) { 2668 context.includesInputFile = true; 2669 break; 2670 } 2671 } 2672 } 2673 } 2674 } 2675 } 2676 2677 // copy all params to args 2678 var params = options.params || {}; 2679 for (var property in params) { 2680 if (params.hasOwnProperty(property)) { 2681 args[namingContainerPrefix + property] = params[property]; 2682 } 2683 } 2684 2685 // remove non-passthrough options 2686 delete options.execute; 2687 delete options.render; 2688 delete options.onerror; 2689 delete options.onevent; 2690 delete options.delay; 2691 delete options.resetValues; 2692 delete options.params; 2693 2694 // copy all other options to args (for backwards compatibility on issue 4115) 2695 for (var property in options) { 2696 if (options.hasOwnProperty(property)) { 2697 args[namingContainerPrefix + property] = options[property]; 2698 } 2699 } 2700 2701 args[namingContainerPrefix + "javax.faces.partial.ajax"] = "true"; 2702 args["method"] = "POST"; 2703 2704 // Determine the posting url 2705 2706 var encodedUrlField = getEncodedUrlElement(form); 2707 if (typeof encodedUrlField == 'undefined') { 2708 args["url"] = form.action; 2709 } else { 2710 args["url"] = encodedUrlField.value; 2711 } 2712 var sendRequest = function() { 2713 var ajaxEngine = new AjaxEngine(context); 2714 ajaxEngine.setupArguments(args); 2715 ajaxEngine.queryString = viewState; 2716 ajaxEngine.context.onevent = onevent; 2717 ajaxEngine.context.onerror = onerror; 2718 ajaxEngine.context.sourceid = element.id; 2719 ajaxEngine.context.render = args[namingContainerPrefix + "javax.faces.partial.render"] || ""; 2720 ajaxEngine.context.namingContainerPrefix = namingContainerPrefix; 2721 ajaxEngine.sendRequest(); 2722 2723 // null out element variables to protect against IE memory leak 2724 element = null; 2725 form = null; 2726 sendRequest = null; 2727 context = null; 2728 }; 2729 2730 if (explicitlyDoNotDelay) { 2731 sendRequest(); 2732 } else { 2733 delayHandler = setTimeout(sendRequest, delayValue); 2734 } 2735 2736 }, 2737 /** 2738 * <p><span class="changed_modified_2_2">Receive</span> an Ajax response 2739 * from the server. 2740 * <p><b>Usage:</b></p> 2741 * <pre><code> 2742 * jsf.ajax.response(request, context); 2743 * </pre></code> 2744 * <p><b>Implementation Requirements:</b></p> 2745 * This function must evaluate the markup returned in the 2746 * <code>request.responseXML</code> object and perform the following action: 2747 * <ul> 2748 * <p>If there is no XML response returned, signal an <code>emptyResponse</code> 2749 * error. If the XML response does not follow the format as outlined 2750 * in Appendix A of the spec prose document <a 2751 * href="../../javadocs/overview-summary.html#prose_document">linked in the 2752 * overview summary</a> signal a <code>malformedError</code> error. Refer to 2753 * section "Signaling Errors" in Chapter 13 of the spec prose document <a 2754 * href="../../javadocs/overview-summary.html#prose_document">linked in the 2755 * overview summary</a>.</p> 2756 * <p>If the response was successfully processed, send a <code>success</code> 2757 * event as outlined in Chapter 13 "Sending Events" section of the spec prose 2758 * document <a 2759 * href="../../javadocs/overview-summary.html#prose_document">linked in the 2760 * overview summary</a>.</p> 2761 * <p><i>Update Element Processing</i></p> 2762 * The <code>update</code> element is used to update a single DOM element. The 2763 * "id" attribute of the <code>update</code> element refers to the DOM element that 2764 * will be updated. The contents of the <code>CDATA</code> section is the data that 2765 * will be used when updating the contents of the DOM element as specified by the 2766 * <code><update></code> element identifier. 2767 * <li>If an <code><update></code> element is found in the response 2768 * with the identifier <code>javax.faces.ViewRoot</code>: 2769 * <pre><code><update id="javax.faces.ViewRoot"> 2770 * <![CDATA[...]]> 2771 * </update></code></pre> 2772 * Update the entire DOM replacing the appropriate <code>head</code> and/or 2773 * <code>body</code> sections with the content from the response.</li> 2774 2775 * <li class="changed_modified_2_2">If an 2776 * <code><update></code> element is found in the 2777 * response with an identifier containing 2778 * <code>javax.faces.ViewState</code>: 2779 2780 * <pre><code><update id="<VIEW_ROOT_CONTAINER_CLIENT_ID><SEP>javax.faces.ViewState<SEP><UNIQUE_PER_VIEW_NUMBER>"> 2781 * <![CDATA[...]]> 2782 * </update></code></pre> 2783 2784 * locate and update the submitting form's 2785 * <code>javax.faces.ViewState</code> value with the 2786 * <code>CDATA</code> contents from the response. 2787 * <SEP> is the currently configured 2788 * <code>UINamingContainer.getSeparatorChar()</code>. 2789 * <VIEW_ROOT_CONTAINER_CLIENT_ID> is the return from 2790 * <code>UIViewRoot.getContainerClientId()</code> on the 2791 * view from whence this state originated. 2792 * <UNIQUE_PER_VIEW_NUMBER> is a number that must be 2793 * unique within this view, but must not be included in the 2794 * view state. This requirement is simply to satisfy XML 2795 * correctness in parity with what is done in the 2796 * corresponding non-partial JSF view. Locate and update 2797 * the <code>javax.faces.ViewState</code> value for all 2798 * JSF forms covered in the <code>render</code> target 2799 * list whose ID starts with the same 2800 * <VIEW_ROOT_CONTAINER_CLIENT_ID> value.</li> 2801 2802 * <li class="changed_added_2_2">If an 2803 * <code>update</code> element is found in the response with 2804 * an identifier containing 2805 * <code>javax.faces.ClientWindow</code>: 2806 2807 * <pre><code><update id="<VIEW_ROOT_CONTAINER_CLIENT_ID><SEP>javax.faces.ClientWindow<SEP><UNIQUE_PER_VIEW_NUMBER>"> 2808 * <![CDATA[...]]> 2809 * </update></code></pre> 2810 2811 * locate and update the submitting form's 2812 * <code>javax.faces.ClientWindow</code> value with the 2813 * <code>CDATA</code> contents from the response. 2814 * <SEP> is the currently configured 2815 * <code>UINamingContainer.getSeparatorChar()</code>. 2816 * <VIEW_ROOT_CONTAINER_CLIENT_ID> is the return from 2817 * <code>UIViewRoot.getContainerClientId()</code> on the 2818 * view from whence this state originated. 2819 * <UNIQUE_PER_VIEW_NUMBER> is a number that must be 2820 * unique within this view, but must not be included in the 2821 * view state. This requirement is simply to satisfy XML 2822 * correctness in parity with what is done in the 2823 * corresponding non-partial JSF view. Locate and update 2824 * the <code>javax.faces.ClientWindow</code> value for all 2825 * JSF forms covered in the <code>render</code> target 2826 * list whose ID starts with the same 2827 * <VIEW_ROOT_CONTAINER_CLIENT_ID> value.</li> 2828 2829 * <li class="changed_added_2_3">If an <code>update</code> element is found in the response with the 2830 * identifier <code>javax.faces.Resource</code>: 2831 * <pre><code><update id="javax.faces.Resource"> 2832 * <![CDATA[...]]> 2833 * </update></code></pre> 2834 * append any element found in the <code>CDATA</code> contents which is absent in the document to the 2835 * document's <code>head</code> section. 2836 * </li> 2837 2838 * <li>If an <code>update</code> element is found in the response with the identifier 2839 * <code>javax.faces.ViewHead</code>: 2840 * <pre><code><update id="javax.faces.ViewHead"> 2841 * <![CDATA[...]]> 2842 * </update></code></pre> 2843 * update the document's <code>head</code> section with the <code>CDATA</code> 2844 * contents from the response.</li> 2845 * <li>If an <code>update</code> element is found in the response with the identifier 2846 * <code>javax.faces.ViewBody</code>: 2847 * <pre><code><update id="javax.faces.ViewBody"> 2848 * <![CDATA[...]]> 2849 * </update></code></pre> 2850 * update the document's <code>body</code> section with the <code>CDATA</code> 2851 * contents from the response.</li> 2852 * <li>For any other <code><update></code> element: 2853 * <pre><code><update id="update id"> 2854 * <![CDATA[...]]> 2855 * </update></code></pre> 2856 * Find the DOM element with the identifier that matches the 2857 * <code><update></code> element identifier, and replace its contents with 2858 * the <code><update></code> element's <code>CDATA</code> contents.</li> 2859 * </li> 2860 * <p><i>Insert Element Processing</i></p> 2861 2862 * <li>If an <code><insert></code> element is found in 2863 * the response with a nested <code><before></code> 2864 * element: 2865 2866 * <pre><code><insert> 2867 * <before id="before id"> 2868 * <![CDATA[...]]> 2869 * </before> 2870 * </insert></code></pre> 2871 * 2872 * <ul> 2873 * <li>Extract this <code><before></code> element's <code>CDATA</code> contents 2874 * from the response.</li> 2875 * <li>Find the DOM element whose identifier matches <code>before id</code> and insert 2876 * the <code><before></code> element's <code>CDATA</code> content before 2877 * the DOM element in the document.</li> 2878 * </ul> 2879 * </li> 2880 * 2881 * <li>If an <code><insert></code> element is found in 2882 * the response with a nested <code><after></code> 2883 * element: 2884 * 2885 * <pre><code><insert> 2886 * <after id="after id"> 2887 * <![CDATA[...]]> 2888 * </after> 2889 * </insert></code></pre> 2890 * 2891 * <ul> 2892 * <li>Extract this <code><after></code> element's <code>CDATA</code> contents 2893 * from the response.</li> 2894 * <li>Find the DOM element whose identifier matches <code>after id</code> and insert 2895 * the <code><after></code> element's <code>CDATA</code> content after 2896 * the DOM element in the document.</li> 2897 * </ul> 2898 * </li> 2899 * <p><i>Delete Element Processing</i></p> 2900 * <li>If a <code><delete></code> element is found in the response: 2901 * <pre><code><delete id="delete id"/></code></pre> 2902 * Find the DOM element whose identifier matches <code>delete id</code> and remove it 2903 * from the DOM.</li> 2904 * <p><i>Element Attribute Update Processing</i></p> 2905 * <li>If an <code><attributes></code> element is found in the response: 2906 * <pre><code><attributes id="id of element with attribute"> 2907 * <attribute name="attribute name" value="attribute value"> 2908 * ... 2909 * </attributes></code></pre> 2910 * <ul> 2911 * <li>Find the DOM element that matches the <code><attributes></code> identifier.</li> 2912 * <li>For each nested <code><attribute></code> element in <code><attribute></code>, 2913 * update the DOM element attribute value (whose name matches <code>attribute name</code>), 2914 * with <code>attribute value</code>.</li> 2915 * </ul> 2916 * </li> 2917 * <p><i>JavaScript Processing</i></p> 2918 * <li>If an <code><eval></code> element is found in the response: 2919 * <pre><code><eval> 2920 * <![CDATA[...JavaScript...]]> 2921 * </eval></code></pre> 2922 * <ul> 2923 * <li>Extract this <code><eval></code> element's <code>CDATA</code> contents 2924 * from the response and execute it as if it were JavaScript code.</li> 2925 * </ul> 2926 * </li> 2927 * <p><i>Redirect Processing</i></p> 2928 * <li>If a <code><redirect></code> element is found in the response: 2929 * <pre><code><redirect url="redirect url"/></code></pre> 2930 * Cause a redirect to the url <code>redirect url</code>.</li> 2931 * <p><i>Error Processing</i></p> 2932 * <li>If an <code><error></code> element is found in the response: 2933 * <pre><code><error> 2934 * <error-name>..fully qualified class name string...<error-name> 2935 * <error-message><![CDATA[...]]><error-message> 2936 * </error></code></pre> 2937 * Extract this <code><error></code> element's <code>error-name</code> contents 2938 * and the <code>error-message</code> contents. Signal a <code>serverError</code> passing 2939 * the <code>errorName</code> and <code>errorMessage</code>. Refer to 2940 * section "Signaling Errors" in Chapter 13 of the spec prose document <a 2941 * href="../../javadocs/overview-summary.html#prose_document">linked in the 2942 * overview summary</a>.</li> 2943 * <p><i>Extensions</i></p> 2944 * <li>The <code><extensions></code> element provides a way for framework 2945 * implementations to provide their own information.</li> 2946 * <p><li>The implementation must check if <script> elements in the response can 2947 * be automatically run, as some browsers support this feature and some do not. 2948 * If they can not be run, then scripts should be extracted from the response and 2949 * run separately.</li></p> 2950 * </ul> 2951 * 2952 * </p> 2953 * 2954 * @param request The <code>XMLHttpRequest</code> instance that 2955 * contains the status code and response message from the server. 2956 * 2957 * @param context An object containing the request context, including the following properties: 2958 * the source element, per call onerror callback function, and per call onevent callback function. 2959 * 2960 * @throws Error if request contains no data 2961 * 2962 * @function jsf.ajax.response 2963 */ 2964 response: function response(request, context) { 2965 if (!request) { 2966 throw new Error("jsf.ajax.response: Request parameter is unset"); 2967 } 2968 2969 // ensure context source is the dom element and not the ID 2970 // per 14.4.1 of the 2.0 specification. We're doing it here 2971 // *before* any errors or events are propagated becasue the 2972 // DOM element may be removed after the update has been processed. 2973 if (typeof context.sourceid === 'string') { 2974 context.sourceid = document.getElementById(context.sourceid); 2975 } 2976 2977 var xml = request.responseXML; 2978 if (xml === null) { 2979 sendError(request, context, "emptyResponse"); 2980 return; 2981 } 2982 2983 if (getParseErrorText(xml) !== PARSED_OK) { 2984 sendError(request, context, "malformedXML"); 2985 return; 2986 } 2987 2988 var partialResponse = xml.getElementsByTagName("partial-response")[0]; 2989 var namingContainerId = partialResponse.getAttribute("id"); 2990 var namingContainerPrefix = namingContainerId ? (namingContainerId + jsf.separatorchar) : ""; 2991 var responseType = partialResponse.firstChild; 2992 2993 context.namingContainerId = namingContainerId; 2994 context.namingContainerPrefix = namingContainerPrefix; 2995 2996 for (var i = 0; i < partialResponse.childNodes.length; i++) { 2997 if (partialResponse.childNodes[i].nodeName === "error") { 2998 responseType = partialResponse.childNodes[i]; 2999 break; 3000 } 3001 } 3002 3003 if (responseType.nodeName === "error") { // it's an error 3004 var errorName = ""; 3005 var errorMessage = ""; 3006 3007 var element = responseType.firstChild; 3008 if (element.nodeName === "error-name") { 3009 if (null != element.firstChild) { 3010 errorName = element.firstChild.nodeValue; 3011 } 3012 } 3013 3014 element = responseType.firstChild.nextSibling; 3015 if (element.nodeName === "error-message") { 3016 if (null != element.firstChild) { 3017 errorMessage = element.firstChild.nodeValue; 3018 } 3019 } 3020 sendError(request, context, "serverError", null, errorName, errorMessage); 3021 sendEvent(request, context, "success"); 3022 return; 3023 } 3024 3025 3026 if (responseType.nodeName === "redirect") { 3027 window.location = responseType.getAttribute("url"); 3028 return; 3029 } 3030 3031 3032 if (responseType.nodeName !== "changes") { 3033 sendError(request, context, "malformedXML", "Top level node must be one of: changes, redirect, error, received: " + responseType.nodeName + " instead."); 3034 return; 3035 } 3036 3037 3038 var changes = responseType.childNodes; 3039 3040 try { 3041 for (var i = 0; i < changes.length; i++) { 3042 switch (changes[i].nodeName) { 3043 case "update": 3044 doUpdate(changes[i], context); 3045 break; 3046 case "delete": 3047 doDelete(changes[i]); 3048 break; 3049 case "insert": 3050 doInsert(changes[i]); 3051 break; 3052 case "attributes": 3053 doAttributes(changes[i]); 3054 break; 3055 case "eval": 3056 doEval(changes[i]); 3057 break; 3058 case "extension": 3059 // no action 3060 break; 3061 default: 3062 sendError(request, context, "malformedXML", "Changes allowed are: update, delete, insert, attributes, eval, extension. Received " + changes[i].nodeName + " instead."); 3063 return; 3064 } 3065 } 3066 } catch (ex) { 3067 sendError(request, context, "malformedXML", ex.message); 3068 return; 3069 } 3070 sendEvent(request, context, "success"); 3071 3072 } 3073 }; 3074 }(); 3075 3076 /** 3077 * 3078 * <p>Return the value of <code>Application.getProjectStage()</code> for 3079 * the currently running application instance. Calling this method must 3080 * not cause any network transaction to happen to the server.</p> 3081 * <p><b>Usage:</b></p> 3082 * <pre><code> 3083 * var stage = jsf.getProjectStage(); 3084 * if (stage === ProjectStage.Development) { 3085 * ... 3086 * } else if stage === ProjectStage.Production) { 3087 * ... 3088 * } 3089 * </code></pre> 3090 * 3091 * @returns String <code>String</code> representing the current state of the 3092 * running application in a typical product development lifecycle. Refer 3093 * to <code>javax.faces.application.Application.getProjectStage</code> and 3094 * <code>javax.faces.application.ProjectStage</code>. 3095 * @function jsf.getProjectStage 3096 */ 3097 jsf.getProjectStage = function() { 3098 // First, return cached value if available 3099 if (typeof mojarra !== 'undefined' && typeof mojarra.projectStageCache !== 'undefined') { 3100 return mojarra.projectStageCache; 3101 } 3102 var scripts = document.getElementsByTagName("script"); // nodelist of scripts 3103 var script; // jsf.js script 3104 var s = 0; // incremental variable for for loop 3105 var stage; // temp value for stage 3106 var match; // temp value for match 3107 while (s < scripts.length) { 3108 if (typeof scripts[s].src === 'string' && scripts[s].src.match('\/javax\.faces\.resource\/jsf\.js\?.*ln=javax\.faces')) { 3109 script = scripts[s].src; 3110 break; 3111 } 3112 s++; 3113 } 3114 if (typeof script == "string") { 3115 match = script.match("stage=(.*)"); 3116 if (match) { 3117 stage = match[1]; 3118 } 3119 } 3120 if (typeof stage === 'undefined' || !stage) { 3121 stage = "Production"; 3122 } 3123 3124 mojarra = mojarra || {}; 3125 mojarra.projectStageCache = stage; 3126 3127 return mojarra.projectStageCache; 3128 }; 3129 3130 3131 /** 3132 * <p>Collect and encode state for input controls associated 3133 * with the specified <code>form</code> element. This will include 3134 * all input controls of type <code>hidden</code>.</p> 3135 * <p><b>Usage:</b></p> 3136 * <pre><code> 3137 * var state = jsf.getViewState(form); 3138 * </pre></code> 3139 * 3140 * @param form The <code>form</code> element whose contained 3141 * <code>input</code> controls will be collected and encoded. 3142 * Only successful controls will be collected and encoded in 3143 * accordance with: <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2"> 3144 * Section 17.13.2 of the HTML Specification</a>. 3145 * 3146 * @returns String The encoded state for the specified form's input controls. 3147 * @function jsf.getViewState 3148 */ 3149 jsf.getViewState = function(form) { 3150 if (!form) { 3151 throw new Error("jsf.getViewState: form must be set"); 3152 } 3153 var els = form.elements; 3154 var len = els.length; 3155 // create an array which we'll use to hold all the intermediate strings 3156 // this bypasses a problem in IE when repeatedly concatenating very 3157 // large strings - we'll perform the concatenation once at the end 3158 var qString = []; 3159 var addField = function(name, value) { 3160 var tmpStr = ""; 3161 if (qString.length > 0) { 3162 tmpStr = "&"; 3163 } 3164 tmpStr += encodeURIComponent(name) + "=" + encodeURIComponent(value); 3165 qString.push(tmpStr); 3166 }; 3167 for (var i = 0; i < len; i++) { 3168 var el = els[i]; 3169 if (el.name === "") { 3170 continue; 3171 } 3172 if (!el.disabled) { 3173 switch (el.type) { 3174 case 'submit': 3175 case 'reset': 3176 case 'image': 3177 case 'file': 3178 break; 3179 case 'select-one': 3180 if (el.selectedIndex >= 0) { 3181 addField(el.name, el.options[el.selectedIndex].value); 3182 } 3183 break; 3184 case 'select-multiple': 3185 for (var j = 0; j < el.options.length; j++) { 3186 if (el.options[j].selected) { 3187 addField(el.name, el.options[j].value); 3188 } 3189 } 3190 break; 3191 case 'checkbox': 3192 case 'radio': 3193 if (el.checked) { 3194 addField(el.name, el.value || 'on'); 3195 } 3196 break; 3197 default: 3198 // this is for any input incl. text', 'password', 'hidden', 'textarea' 3199 var nodeName = el.nodeName.toLowerCase(); 3200 if (nodeName === "input" || nodeName === "select" || 3201 nodeName === "button" || nodeName === "object" || 3202 nodeName === "textarea") { 3203 addField(el.name, el.value); 3204 } 3205 break; 3206 } 3207 } 3208 } 3209 // concatenate the array 3210 return qString.join(""); 3211 }; 3212 3213 /** 3214 * <p class="changed_added_2_2">Return the windowId of the window 3215 * in which the argument form is rendered.</p> 3216 3217 * @param {optional String|DomNode} node. Determine the nature of 3218 * the argument. If not present, search for the windowId within 3219 * <code>document.forms</code>. If present and the value is a 3220 * string, assume the string is a DOM id and get the element with 3221 * that id and start the search from there. If present and the 3222 * value is a DOM element, start the search from there. 3223 3224 * @returns String The windowId of the current window, or null 3225 * if the windowId cannot be determined. 3226 3227 * @throws an error if more than one unique WindowId is found. 3228 3229 * @function jsf.getClientWindow 3230 */ 3231 jsf.getClientWindow = function(node) { 3232 var FORM = "form"; 3233 var WIN_ID = "javax.faces.ClientWindow"; 3234 3235 /** 3236 * Find javax.faces.ClientWindow field for a given form. 3237 * @param form 3238 * @ignore 3239 */ 3240 var getWindowIdElement = function getWindowIdElement(form) { 3241 var windowIdElement = form[WIN_ID]; 3242 3243 if (windowIdElement) { 3244 return windowIdElement; 3245 } else { 3246 var formElements = form.elements; 3247 for (var i = 0, length = formElements.length; i < length; i++) { 3248 var formElement = formElements[i]; 3249 if (formElement.name && (formElement.name.indexOf(WIN_ID) >= 0)) { 3250 return formElement; 3251 } 3252 } 3253 } 3254 3255 return undefined; 3256 }; 3257 3258 var fetchWindowIdFromForms = function (forms) { 3259 var result_idx = {}; 3260 var result; 3261 var foundCnt = 0; 3262 for (var cnt = forms.length - 1; cnt >= 0; cnt--) { 3263 var UDEF = 'undefined'; 3264 var currentForm = forms[cnt]; 3265 var windowIdElement = getWindowIdElement(currentForm); 3266 var windowId = windowIdElement && windowIdElement.value; 3267 if (UDEF != typeof windowId) { 3268 if (foundCnt > 0 && UDEF == typeof result_idx[windowId]) throw Error("Multiple different windowIds found in document"); 3269 result = windowId; 3270 result_idx[windowId] = true; 3271 foundCnt++; 3272 } 3273 } 3274 return result; 3275 } 3276 3277 /** 3278 * @ignore 3279 */ 3280 var getChildForms = function (currentElement) { 3281 //Special condition no element we return document forms 3282 //as search parameter, ideal would be to 3283 //have the viewroot here but the frameworks 3284 //can deal with that themselves by using 3285 //the viewroot as currentElement 3286 if (!currentElement) { 3287 return document.forms; 3288 } 3289 3290 var targetArr = []; 3291 if (!currentElement.tagName) return []; 3292 else if (currentElement.tagName.toLowerCase() == FORM) { 3293 targetArr.push(currentElement); 3294 return targetArr; 3295 } 3296 3297 //if query selectors are supported we can take 3298 //a non recursive shortcut 3299 if (currentElement.querySelectorAll) { 3300 return currentElement.querySelectorAll(FORM); 3301 } 3302 3303 //old recursive way, due to flakeyness of querySelectorAll 3304 for (var cnt = currentElement.childNodes.length - 1; cnt >= 0; cnt--) { 3305 var currentChild = currentElement.childNodes[cnt]; 3306 targetArr = targetArr.concat(getChildForms(currentChild, FORM)); 3307 } 3308 return targetArr; 3309 } 3310 3311 /** 3312 * @ignore 3313 */ 3314 var fetchWindowIdFromURL = function () { 3315 var href = window.location.href; 3316 var windowId = "windowId"; 3317 var regex = new RegExp("[\\?&]" + windowId + "=([^\\;]*)"); 3318 var results = regex.exec(href); 3319 //initial trial over the url and a regexp 3320 if (results != null) return results[1]; 3321 return null; 3322 } 3323 3324 //byId ($) 3325 var finalNode = (node && (typeof node == "string" || node instanceof String)) ? 3326 document.getElementById(node) : (node || null); 3327 3328 var forms = getChildForms(finalNode); 3329 var result = fetchWindowIdFromForms(forms); 3330 return (null != result) ? result : fetchWindowIdFromURL(); 3331 3332 3333 }; 3334 3335 /** 3336 * <p class="changed_added_2_3"> 3337 * The Push functionality. 3338 * </p> 3339 * @name jsf.push 3340 * @namespace 3341 * @exec 3342 */ 3343 jsf.push = function() { 3344 3345 // "Constant" fields ---------------------------------------------------------------------------------------------- 3346 3347 var RECONNECT_INTERVAL = 500; 3348 var MAX_RECONNECT_ATTEMPTS = 25; 3349 var REASON_EXPIRED = "Expired"; 3350 3351 // Private static fields ------------------------------------------------------------------------------------------ 3352 3353 var sockets = {}; 3354 3355 // Private constructor functions ---------------------------------------------------------------------------------- 3356 3357 /** 3358 * Creates a reconnecting websocket. When the websocket successfully connects on first attempt, then it will 3359 * automatically reconnect on timeout with cumulative intervals of 500ms with a maximum of 25 attempts (~3 minutes). 3360 * The <code>onclose</code> function will be called with the error code of the last attempt. 3361 * @constructor 3362 * @param {string} url The URL of the websocket. 3363 * @param {string} channel The channel name of the websocket. 3364 * @param {function} onopen The function to be invoked when the websocket is opened. 3365 * @param {function} onmessage The function to be invoked when a message is received. 3366 * @param {function} onclose The function to be invoked when the websocket is closed. 3367 * @param {Object} behaviors Client behavior functions to be invoked when specific message is received. 3368 * @ignore 3369 */ 3370 function ReconnectingWebsocket(url, channel, onopen, onmessage, onclose, behaviors) { 3371 3372 // Private fields ----------------------------------------------------------------------------------------- 3373 3374 var socket; 3375 var reconnectAttempts; 3376 var self = this; 3377 3378 // Public functions --------------------------------------------------------------------------------------- 3379 3380 /** 3381 * Opens the reconnecting websocket. 3382 */ 3383 self.open = function() { 3384 if (socket && socket.readyState == 1) { 3385 return; 3386 } 3387 3388 socket = new WebSocket(url); 3389 3390 socket.onopen = function(event) { 3391 if (reconnectAttempts == null) { 3392 onopen(channel); 3393 } 3394 3395 reconnectAttempts = 0; 3396 } 3397 3398 socket.onmessage = function(event) { 3399 var message = JSON.parse(event.data).data; 3400 onmessage(message, channel, event); 3401 var functions = behaviors[message]; 3402 3403 if (functions && functions.length) { 3404 for (var i = 0; i < functions.length; i++) { 3405 functions[i](); 3406 } 3407 } 3408 } 3409 3410 socket.onclose = function(event) { 3411 if (!socket 3412 || (event.code == 1000 && event.reason == REASON_EXPIRED) 3413 || (event.code == 1008) 3414 || (reconnectAttempts == null) 3415 || (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS)) 3416 { 3417 onclose(event.code, channel, event); 3418 } 3419 else { 3420 setTimeout(self.open, RECONNECT_INTERVAL * reconnectAttempts++); 3421 } 3422 } 3423 } 3424 3425 /** 3426 * Closes the reconnecting websocket. 3427 */ 3428 self.close = function() { 3429 if (socket) { 3430 var s = socket; 3431 socket = null; 3432 reconnectAttempts == null; 3433 s.close(); 3434 } 3435 } 3436 3437 } 3438 3439 3440 // Private static functions --------------------------------------------------------------------------------------- 3441 3442 /** 3443 * If given function is actually not a function, then try to interpret it as name of a global function. 3444 * If it still doesn't resolve to anything, then return a NOOP function. 3445 * @param {Object} fn Can be function, or string representing function name, or undefined. 3446 * @return {function} The intented function, or a NOOP function when undefined. 3447 * @ignore 3448 */ 3449 function resolveFunction(fn) { 3450 return (typeof fn !== "function") && (fn = window[fn] || function(){}), fn; 3451 } 3452 3453 /** 3454 * Get socket associated with given client identifier. 3455 * @param {string} clientId The client identifier of the websocket. 3456 * @return {Socket} Socket associated with given client identifier. 3457 * @throws {Error} When client identifier is unknown. You may need to initialize it first via <code>init()</code> function. 3458 * @ignore 3459 */ 3460 function getSocket(clientId) { 3461 var socket = sockets[clientId]; 3462 3463 if (socket) { 3464 return socket; 3465 } 3466 else { 3467 throw new Error("Unknown clientId: " + clientId); 3468 } 3469 } 3470 3471 3472 // Public static functions ---------------------------------------------------------------------------------------- 3473 3474 return { 3475 3476 /** 3477 * Initialize a websocket on the given client identifier. When connected, it will stay open and reconnect as 3478 * long as URL is valid and <code>jsf.push.close()</code> hasn't explicitly been called on the same client 3479 * identifier. 3480 * @param {string} clientId The client identifier of the websocket. 3481 * @param {string} url The URL of the websocket. All open websockets on the same URL will receive the 3482 * same push notification from the server. 3483 * @param {string} channel The channel name of the websocket. 3484 * @param {function} onopen The JavaScript event handler function that is invoked when the websocket is opened. 3485 * The function will be invoked with one argument: the client identifier. 3486 * @param {function} onmessage The JavaScript event handler function that is invoked when a message is received from 3487 * the server. The function will be invoked with three arguments: the push message, the client identifier and 3488 * the raw <code>MessageEvent</code> itself. 3489 * @param {function} onclose The JavaScript event handler function that is invoked when the websocket is closed. 3490 * The function will be invoked with three arguments: the close reason code, the client identifier and the raw 3491 * <code>CloseEvent</code> itself. Note that this will also be invoked on errors and that you can inspect the 3492 * close reason code if an error occurred and which one (i.e. when the code is not 1000). See also 3493 * <a href="http://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455 section 7.4.1</a> and 3494 * <a href="http://docs.oracle.com/javaee/7/api/javax/websocket/CloseReason.CloseCodes.html">CloseCodes</a> API 3495 * for an elaborate list. 3496 * @param {Object} behaviors Client behavior functions to be invoked when specific message is received. 3497 * @param {boolean} autoconnect Whether or not to automatically connect the socket. Defaults to <code>false</code>. 3498 * @member jsf.push 3499 * @function jsf.push.init 3500 */ 3501 init: function(clientId, url, channel, onopen, onmessage, onclose, behaviors, autoconnect) { 3502 onclose = resolveFunction(onclose); 3503 3504 if (!window.WebSocket) { // IE6-9. 3505 onclose(-1, clientId); 3506 return; 3507 } 3508 3509 if (!sockets[clientId]) { 3510 sockets[clientId] = new ReconnectingWebsocket(url, channel, resolveFunction(onopen), resolveFunction(onmessage), onclose, behaviors); 3511 } 3512 3513 if (autoconnect) { 3514 getSocket(clientId).open(); 3515 } 3516 }, 3517 3518 /** 3519 * Open the websocket on the given client identifier. 3520 * @param {string} clientId The client identifier of the websocket. 3521 * @throws {Error} When client identifier is unknown. You may need to initialize it first via <code>init()</code> function. 3522 * @member jsf.push 3523 * @function jsf.push.open 3524 */ 3525 open: function(clientId) { 3526 getSocket(clientId).open(); 3527 }, 3528 3529 /** 3530 * Close the websocket on the given client identifier. 3531 * @param {string} clientId The client identifier of the websocket. 3532 * @throws {Error} When client identifier is unknown. You may need to initialize it first via <code>init()</code> function. 3533 * @member jsf.push 3534 * @function jsf.push.close 3535 */ 3536 close: function(clientId) { 3537 getSocket(clientId).close(); 3538 } 3539 } 3540 }(); 3541 3542 3543 /** 3544 * The namespace for JavaServer Faces JavaScript utilities. 3545 * @name jsf.util 3546 * @namespace 3547 */ 3548 jsf.util = {}; 3549 3550 /** 3551 * <p>A varargs function that invokes an arbitrary number of scripts. 3552 * If any script in the chain returns false, the chain is short-circuited 3553 * and subsequent scripts are not invoked. Any number of scripts may 3554 * specified after the <code>event</code> argument.</p> 3555 * 3556 * @param source The DOM element that triggered this Ajax request, or an 3557 * id string of the element to use as the triggering element. 3558 * @param event The DOM event that triggered this Ajax request. The 3559 * <code>event</code> argument is optional. 3560 * 3561 * @returns boolean <code>false</code> if any scripts in the chain return <code>false</code>, 3562 * otherwise returns <code>true</code> 3563 * 3564 * @function jsf.util.chain 3565 */ 3566 jsf.util.chain = function(source, event) { 3567 3568 if (arguments.length < 3) { 3569 return true; 3570 } 3571 3572 // RELEASE_PENDING rogerk - shouldn't this be getElementById instead of null 3573 var thisArg = (typeof source === 'object') ? source : null; 3574 3575 // Call back any scripts that were passed in 3576 for (var i = 2; i < arguments.length; i++) { 3577 3578 var f = new Function("event", arguments[i]); 3579 var returnValue = f.call(thisArg, event); 3580 3581 if (returnValue === false) { 3582 return false; 3583 } 3584 } 3585 return true; 3586 3587 }; 3588 3589 /** 3590 * <p class="changed_added_2_2">The result of calling 3591 * <code>UINamingContainer.getNamingContainerSeparatorChar().</code></p> 3592 */ 3593 jsf.separatorchar = '#{facesContext.namingContainerSeparatorChar}'; 3594 3595 /** 3596 * <p>An integer specifying the specification version that this file implements. 3597 * Its format is: rightmost two digits, bug release number, next two digits, 3598 * minor release number, leftmost digits, major release number. 3599 * This number may only be incremented by a new release of the specification.</p> 3600 */ 3601 jsf.specversion = 23000; 3602 3603 /** 3604 * <p>An integer specifying the implementation version that this file implements. 3605 * It's a monotonically increasing number, reset with every increment of 3606 * <code>jsf.specversion</code> 3607 * This number is implementation dependent.</p> 3608 */ 3609 jsf.implversion = 3; 3610 3611 3612 } //end if version detection block 3613