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