1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 1997-2008 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.dev.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 notice: 39 * 40 * Copyright 2004 The Apache Software Foundation 41 * 42 * Licensed under the Apache License, Version 2.0 (the "License"); 43 * you may not use this file except in compliance with the License. 44 * You may obtain a copy of the License at 45 * 46 * http://www.apache.org/licenses/LICENSE-2.0 47 * 48 * Unless required by applicable law or agreed to in writing, software 49 * distributed under the License is distributed on an "AS IS" BASIS, 50 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 51 * See the License for the specific language governing permissions and 52 * limitations under the License. 53 */ 54 55 /** 56 @project JSF JavaScript Library 57 @version 2.0 58 @description This is the standard implementation of the JSF JavaScript Library. 59 */ 60 61 /** 62 * Register with OpenAjax 63 */ 64 if (typeof OpenAjax !== "undefined" && 65 typeof OpenAjax.hub.registerLibrary !== "undefined") { 66 OpenAjax.hub.registerLibrary("jsf", "www.sun.com", "2.0", null); 67 } 68 69 // Detect if this is already loaded, and if loaded, if it's a higher version 70 if (!((jsf && jsf.specversion && jsf.specversion > 20000 ) && 71 (jsf.implversion && jsf.implversion > 1))) { 72 73 /** 74 * The top level global namespace for JavaServer Faces functionality. 75 * @name jsf 76 * @namespace 77 */ 78 var jsf = {}; 79 80 /** 81 * The namespace for Ajax functionality. 82 * @name jsf.ajax 83 * @namespace 84 * @exec 85 */ 86 87 jsf.ajax = function() { 88 89 var eventListeners = []; 90 var errorListeners = []; 91 92 /** 93 * @ignore 94 */ 95 var getTransport = function getTransport() { 96 var methods = [ 97 function() { 98 return new XMLHttpRequest(); 99 }, 100 function() { 101 return new ActiveXObject('Msxml2.XMLHTTP'); 102 }, 103 function() { 104 return new ActiveXObject('Microsoft.XMLHTTP'); 105 } 106 ]; 107 108 var returnVal; 109 for (var i = 0, len = methods.length; i < len; i++) { 110 try { 111 returnVal = methods[i](); 112 } catch(e) { 113 continue; 114 } 115 return returnVal; 116 } 117 throw new Error('Could not create an XHR object.'); 118 }; 119 120 /** 121 * @ignore 122 */ 123 var $ = function $() { 124 var results = [], element; 125 for (var i = 0; i < arguments.length; i++) { 126 element = arguments[i]; 127 if (typeof element == 'string') { 128 element = document.getElementById(element); 129 } 130 results.push(element); 131 } 132 return results.length > 1 ? results : results[0]; 133 }; 134 135 /** 136 * @ignore 137 */ 138 var getForm = function getForm(element) { 139 if (element) { 140 var form = $(element); 141 while (form && form.nodeName && form.nodeName.toLowerCase() !== 'form') { 142 if (form.form) { 143 return form.form; 144 } 145 if (form.parentNode) { 146 form = form.parentNode; 147 } else { 148 form = null; 149 } 150 if (form) { 151 return form; 152 } 153 } 154 return document.forms[0]; 155 } 156 return null; 157 }; 158 159 /** 160 * Remove trailing and leading whitespace 161 * @ignore 162 */ 163 var trim = function trim(str) { 164 return str.replace(/^\s+/g, "").replace(/\s+$/g, ""); 165 }; 166 167 /** 168 * Split a delimited string into an array, trimming whitespace 169 * param s String to split 170 * param e delimiter character - cannot be a space 171 * @ignore 172 */ 173 var toArray = function toArray(s, e) { 174 var sarray; 175 if (typeof s === 'string') { 176 sarray = s.split((e) ? e : ' '); 177 for (var i = 0; i < sarray.length; i++) { 178 sarray[i] = trim(sarray[i]); 179 } 180 } 181 return sarray; 182 }; 183 184 /** 185 * Check if a value exists in an array 186 * @ignore 187 */ 188 var isInArray = function isInArray(array, value) { 189 for (var i = 0; i < array.length; i++) { 190 if (array[i] === value) { 191 return true; 192 } 193 } 194 return false; 195 }; 196 197 198 /** 199 * Replace DOM element 200 * @ignore 201 */ 202 var elementReplace = function elementReplace(d, tempTagName, src) { 203 var parent = d.parentNode; 204 var temp = document.createElement(tempTagName); 205 var result = null; 206 temp.id = d.id; 207 208 // Creating a head element isn't allowed in IE, so we'll disallow it 209 if (d.nodeName.toLowerCase() === "head") { 210 throw new Error("Attempted to replace a head element"); 211 } else { 212 temp.innerHTML = src; 213 } 214 215 result = temp; 216 parent.replaceChild(temp, d); 217 return result; 218 }; 219 220 221 /** 222 * Do update. 223 * @ignore 224 */ 225 var doUpdate = function doUpdate(element) { 226 var id, content, markup, str, state; 227 var stateElem; 228 229 id = element.getAttribute('id'); 230 if (id === "javax.faces.ViewState") { 231 state = element.firstChild; 232 233 // Now set the view state from the server into the DOM 234 // If there are multiple forms, make sure they all have a 235 // viewState hidden field. 236 237 stateElem = $("javax.faces.ViewState"); 238 if (stateElem) { 239 stateElem.value = state.text || state.data; 240 } 241 var field; 242 for (var k = 0; k < document.forms.length; k++) { 243 field = document.forms[k].elements["javax.faces.ViewState"]; 244 if (typeof field == 'undefined') { 245 field = document.createElement("input"); 246 field.type = "hidden"; 247 field.name = "javax.faces.ViewState"; 248 document.forms[k].appendChild(field); 249 } 250 field.value = state.text || state.data; 251 } 252 return; 253 } 254 255 // join the CDATA sections in the markup 256 // RELEASE_PENDING are there allowed to be more than one CDATA? 257 markup = ''; 258 for (var j = 0; j < element.childNodes.length; j++) { 259 content = element.childNodes[j]; 260 markup += content.text || content.data; 261 } 262 263 // RELEASE_PENDING - doc what this does 264 str = markup.replace(new RegExp('(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', 'img'), ''); 265 266 var src = str; 267 268 // If our special render all markup is present.. 269 if (id === "javax.faces.ViewRoot") { 270 // if src contains <html>, trim the <html> and </html>, if present. 271 // if src contains <head> 272 // extract the contents of <head> and replace current document's 273 // <head> with the contents. 274 // if src contains <body> 275 // extract the contents of <body> and replace the current 276 // document's <body> with the contents. 277 // if src does not contain <body> 278 // replace the current document's <body> with the contents. 279 var 280 htmlStartEx = new RegExp("< *html.*>", "gi"), 281 htmlEndEx = new RegExp("< */ *html.*>", "gi"), 282 headStartEx = new RegExp("< *head.*>", "gi"), 283 headEndEx = new RegExp("< */ *head.*>", "gi"), 284 bodyStartEx = new RegExp("< *body.*>", "gi"), 285 bodyEndEx = new RegExp("< */ *body.*>", "gi"), 286 htmlStart, htmlEnd, headStart, headEnd, bodyStart, bodyEnd; 287 var srcHead = null, srcBody = null; 288 // find the current document's "body" element 289 var docBody = document.getElementsByTagName("body")[0]; 290 // if src contains <html> 291 if (null !== (htmlStart = htmlStartEx.exec(src))) { 292 // if src contains </html> 293 if (null !== (htmlEnd = htmlEndEx.exec(src))) { 294 src = src.substring(htmlStartEx.lastIndex, htmlEnd.index); 295 } else { 296 src = src.substring(htmlStartEx.lastIndex); 297 } 298 } 299 // if src contains <head> 300 if (null !== (headStart = headStartEx.exec(src))) { 301 // if src contains </head> 302 if (null !== (headEnd = headEndEx.exec(src))) { 303 srcHead = src.substring(headStartEx.lastIndex, 304 headEnd.index); 305 } else { 306 srcHead = src.substring(headStartEx.lastIndex); 307 } 308 // find the "head" element 309 var docHead = document.getElementsByTagName("head")[0]; 310 if (docHead) { 311 elementReplace(docHead, "head", srcHead); 312 } 313 } 314 // if src contains <body> 315 if (null !== (bodyStart = bodyStartEx.exec(src))) { 316 // if src contains </body> 317 if (null !== (bodyEnd = bodyEndEx.exec(src))) { 318 srcBody = src.substring(bodyStartEx.lastIndex, 319 bodyEnd.index); 320 } else { 321 srcBody = src.substring(bodyStartEx.lastIndex); 322 } 323 elementReplace(docBody, "body", srcBody); 324 } 325 if (!srcBody) { 326 elementReplace(docBody, "body", src); 327 } 328 329 } else { 330 var d = $(id); 331 if (!d) { 332 throw new Error("jsf.ajax.response: " + id + " not found"); 333 } 334 var parent = d.parentNode; 335 var temp = document.createElement('div'); 336 temp.id = d.id; 337 temp.innerHTML = trim(str); 338 339 parent.replaceChild(temp.firstChild, d); 340 } 341 }; 342 343 /** 344 * Delete a node specified by the element. 345 * @param element 346 * @ignore 347 */ 348 var doDelete = function doDelete(element) { 349 var id = element.getAttribute('id'); 350 var target = $(id); 351 var parent = target.parentNode; 352 parent.removeChild(target); 353 }; 354 355 /** 356 * Insert a node specified by the element. 357 * @param element 358 * @ignore 359 */ 360 var doInsert = function doInsert(element) { 361 // RELEASE_PENDING there may be an insert issue in IE tables. Needs testing. 362 var target = $(element.firstChild.getAttribute('id')); 363 var parent = target.parentNode; 364 var tempElement = document.createElement('span'); 365 tempElement.innerHTML = element.firstChild.firstChild.nodeValue; 366 if (element.firstChild.nodeName === 'after') { 367 // Get the next in the list, to insert before 368 target = target.nextSibling; 369 } // otherwise, this is a 'before' element 370 parent.insertBefore(tempElement.firstChild, target); 371 document.removeChild(tempElement); 372 }; 373 374 /** 375 * Modify attributes of given element id. 376 * @param element 377 * @ignore 378 */ 379 var doAttributes = function doAttributes(element) { 380 381 // RELEASE_PENDING IE 5-7 does not allow setting styles with setAttribute 382 // RELEASE_PENDING IE 5-7 will clear event attributes if you try to set them 383 384 // Get id of element we'll act against 385 var id = element.getAttribute('id'); 386 387 var target = $(id); 388 389 // There can be multiple attributes modified. Loop through the list. 390 var nodes = element.childNodes; 391 for (var i = 0; i < nodes.length; i++) { 392 var name = nodes[i].firstChild.firstChild.nodeValue; 393 var value = nodes[i].firstChild.nextSibling.firstChild.nodeValue; 394 target.setAttribute(name,value); 395 } 396 }; 397 398 /** 399 * Eval the CDATA of the element. 400 * @param element to eval 401 * @ignore 402 */ 403 var doEval = function doEval(element) { 404 var evalText = element.firstChild.nodeValue; 405 eval(evalText); 406 } 407 408 /** 409 * Ajax Request Queue 410 * @ignore 411 */ 412 var Queue = new function Queue() { 413 414 // Create the internal queue 415 var queue = []; 416 417 418 // the amount of space at the front of the queue, initialised to zero 419 var queueSpace = 0; 420 421 /** Returns the size of this Queue. The size of a Queue is equal to the number 422 * of elements that have been enqueued minus the number of elements that have 423 * been dequeued. 424 * @ignore 425 */ 426 this.getSize = function getSize() { 427 return queue.length - queueSpace; 428 }; 429 430 /** Returns true if this Queue is empty, and false otherwise. A Queue is empty 431 * if the number of elements that have been enqueued equals the number of 432 * elements that have been dequeued. 433 * @ignore 434 */ 435 this.isEmpty = function isEmpty() { 436 return (queue.length === 0); 437 }; 438 439 /** Enqueues the specified element in this Queue. 440 * 441 * @param element - the element to enqueue 442 * @ignore 443 */ 444 this.enqueue = function enqueue(element) { 445 // Queue the request 446 queue.push(element); 447 }; 448 449 450 /** Dequeues an element from this Queue. The oldest element in this Queue is 451 * removed and returned. If this Queue is empty then undefined is returned. 452 * 453 * @returns Object The element that was removed from the queue. 454 * @ignore 455 */ 456 this.dequeue = function dequeue() { 457 // initialise the element to return to be undefined 458 var element = undefined; 459 460 // check whether the queue is empty 461 if (queue.length) { 462 // fetch the oldest element in the queue 463 element = queue[queueSpace]; 464 465 // update the amount of space and check whether a shift should occur 466 if (++queueSpace * 2 >= queue.length) { 467 // set the queue equal to the non-empty portion of the queue 468 queue = queue.slice(queueSpace); 469 // reset the amount of space at the front of the queue 470 queueSpace = 0; 471 } 472 } 473 // return the removed element 474 return element; 475 }; 476 477 /** Returns the oldest element in this Queue. If this Queue is empty then 478 * undefined is returned. This function returns the same value as the dequeue 479 * function, but does not remove the returned element from this Queue. 480 * @ignore 481 */ 482 this.getOldestElement = function getOldestElement() { 483 // initialise the element to return to be undefined 484 var element = undefined; 485 486 // if the queue is not element then fetch the oldest element in the queue 487 if (queue.length) { 488 element = queue[queueSpace]; 489 } 490 // return the oldest element 491 return element; 492 }; 493 }(); 494 495 496 /** 497 * AjaxEngine handles Ajax implementation details. 498 * @ignore 499 */ 500 var AjaxEngine = function AjaxEngine() { 501 502 var req = {}; // Request Object 503 req.url = null; // Request URL 504 req.context = {}; // Context of request and response 505 req.context.source = null; // Source of this request 506 req.context.onerror = null; // Error handler for request 507 req.context.onevent = null; // Event handler for request 508 req.xmlReq = null; // XMLHttpRequest Object 509 req.async = true; // Default - Asynchronous 510 req.parameters = {}; // Parameters For GET or POST 511 req.queryString = null; // Encoded Data For GET or POST 512 req.method = null; // GET or POST 513 req.status = null; // Response Status Code From Server 514 req.fromQueue = false; // Indicates if the request was taken off the queue 515 // before being sent. This prevents the request from 516 // entering the queue redundantly. 517 518 req.que = Queue; 519 520 // Get an XMLHttpRequest Handle 521 req.xmlReq = getTransport(); 522 if (req.xmlReq === null) { 523 return null; 524 } 525 526 // Set up request/response state callbacks 527 /** 528 * @ignore 529 */ 530 req.xmlReq.onreadystatechange = function() { 531 if (req.xmlReq.readyState === 4) { 532 req.onComplete(); 533 } 534 }; 535 536 /** 537 * This function is called when the request/response interaction 538 * is complete. If the return status code is successfull, 539 * dequeue all requests from the queue that have completed. If a 540 * request has been found on the queue that has not been sent, 541 * send the request. 542 * @ignore 543 */ 544 req.onComplete = function onComplete() { 545 req.status = req.xmlReq.status; 546 if ((req.status !== null && typeof req.status !== 'undefined' && 547 req.status !== 0) && (req.status >= 200 && req.status < 300)) { 548 sendEvent(req.xmlReq, req.context, "complete"); 549 jsf.ajax.response(req.xmlReq, req.context); 550 } else { 551 sendEvent(req.xmlReq, req.context, "complete"); 552 sendError(req.xmlReq, req.context, "httpError"); 553 } 554 555 // Regardless of whether the request completed successfully (or not), 556 // dequeue requests that have been completed (readyState 4) and send 557 // requests that ready to be sent (readyState 0). 558 559 var nextReq = req.que.getOldestElement(); 560 if (nextReq === null || typeof nextReq === 'undefined') { 561 return; 562 } 563 while ((typeof nextReq.xmlReq !== 'undefined' && nextReq.xmlReq !== null) && 564 nextReq.xmlReq.readyState === 4) { 565 req.que.dequeue(); 566 nextReq = req.que.getOldestElement(); 567 if (nextReq === null || typeof nextReq === 'undefined') { 568 break; 569 } 570 } 571 if (nextReq === null || typeof nextReq === 'undefined') { 572 return; 573 } 574 if ((typeof nextReq.xmlReq !== 'undefined' && nextReq.xmlReq !== null) && 575 nextReq.xmlReq.readyState === 0) { 576 nextReq.fromQueue = true; 577 nextReq.sendRequest(); 578 } 579 }; 580 581 /** 582 * Utility method that accepts additional arguments for the AjaxEngine. 583 * If an argument is passed in that matches an AjaxEngine property, the 584 * argument value becomes the value of the AjaxEngine property. 585 * Arguments that don't match AjaxEngine properties are added as 586 * request parameters. 587 * @ignore 588 */ 589 req.setupArguments = function(args) { 590 for (var i in args) { 591 if (args.hasOwnProperty(i)) { 592 if (typeof req[i] === 'undefined') { 593 req.parameters[i] = args[i]; 594 } else { 595 req[i] = args[i]; 596 } 597 } 598 } 599 }; 600 601 /** 602 * This function does final encoding of parameters, determines the request method 603 * (GET or POST) and sends the request using the specified url. 604 * @ignore 605 */ 606 req.sendRequest = function() { 607 if (req.xmlReq !== null) { 608 // if there is already a request on the queue waiting to be processed.. 609 // just queue this request 610 if (!req.que.isEmpty()) { 611 if (!req.fromQueue) { 612 req.que.enqueue(req); 613 return; 614 } 615 } 616 // If the queue is empty, queue up this request and send 617 if (!req.fromQueue) { 618 req.que.enqueue(req); 619 } 620 // Some logic to get the real request URL 621 if (req.generateUniqueUrl && req.method == "GET") { 622 req.parameters["AjaxRequestUniqueId"] = new Date().getTime() + "" + req.requestIndex; 623 } 624 var content = null; // For POST requests, to hold query string 625 for (var i in req.parameters) { 626 if (req.parameters.hasOwnProperty(i)) { 627 if (req.queryString.length > 0) { 628 req.queryString += "&"; 629 } 630 req.queryString += encodeURIComponent(i) + "=" + encodeURIComponent(req.parameters[i]); 631 } 632 } 633 if (req.method === "GET") { 634 if (req.queryString.length > 0) { 635 req.url += ((req.url.indexOf("?") > -1) ? "&" : "?") + req.queryString; 636 } 637 } 638 req.xmlReq.open(req.method, req.url, req.async); 639 if (req.method === "POST") { 640 if (typeof req.xmlReq.setRequestHeader !== 'undefined') { 641 req.xmlReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 642 } 643 content = req.queryString; 644 } 645 sendEvent(req.xmlReq, req.context, "begin"); 646 req.xmlReq.send(content); 647 } 648 }; 649 650 return req; 651 }; 652 653 /** 654 * Error handling callback. 655 * Assumes that the request has completed. 656 * @ignore 657 */ 658 var sendError = function sendError(request, context, name, serverErrorName, serverErrorMessage) { 659 660 // Possible errornames: 661 // httpError 662 // emptyResponse 663 // serverError RELEASE_PENDING or facesError? 664 // malformedXML 665 666 var sent = false; 667 var data = {}; // data payload for function 668 data.type = "error"; 669 data.name = name; 670 // RELEASE_PENDING won't work in response 671 data.source = context.source; 672 data.responseCode = request.status; 673 // RELEASE_PENDING pass a copy, not a reference 674 data.responseXML = request.responseXML; 675 // RELEASE_PENDING won't work in response 676 data.responseText = request.responseText; 677 678 if (name == "serverError") { 679 data.errorName = serverErrorName; 680 data.errorMessage = serverErrorMessage; 681 } 682 683 // If we have a registered callback, send the error to it. 684 if (context.onerror) { 685 context.onerror.call(null, data); 686 sent = true; 687 } 688 689 for (var i in errorListeners) { 690 if (errorListeners.hasOwnProperty(i)) { 691 errorListeners[i].call(null, data); 692 sent = true; 693 } 694 } 695 696 if (!sent && jsf.getProjectStage() === "Development") { 697 switch (name) { 698 case "httpError": 699 alert("httpError " + request.status); 700 break; 701 case "serverError": 702 alert("serverError: " + serverErrorName + " " + serverErrorMessage); 703 break; 704 default: 705 alert("Error " + name); 706 break; 707 } 708 } 709 }; 710 711 /** 712 * Event handling callback. 713 * Request is assumed to have completed, except in the case of event = 'begin'. 714 * @ignore 715 */ 716 var sendEvent = function sendEvent(request, context, name) { 717 718 var data = {}; 719 data.type = "event"; 720 data.name = name; 721 data.source = context.source; 722 if (name !== 'begin') { 723 data.responseCode = request.status; 724 // RELEASE_PENDING pass a copy, not a reference 725 data.responseXML = request.responseXML; 726 data.responseText = request.responseText; 727 } 728 729 if (context.onevent) { 730 context.onevent.call(null, data); 731 } 732 733 for (var i in eventListeners) { 734 if (eventListeners.hasOwnProperty(i)) { 735 eventListeners[i].call(null, data); 736 } 737 } 738 }; 739 740 // Use module pattern to return the functions we actually expose 741 return { 742 /** 743 * Register a callback for error handling. 744 * <p><b>Usage:</b></p> 745 * <pre><code> 746 * jsf.ajax.addOnError(handleError); 747 * ... 748 * var handleError = function handleError(data) { 749 * ... 750 * } 751 * </pre></code> 752 * <p><b>Implementation Requirements:</b></p> 753 * This function must accept a reference to an existing JavaScript function. 754 * The JavaScript function reference must be added to a list of callbacks, making it possible 755 * to register more than one callback by invoking <code>jsf.ajax.addOnError</code> 756 * more than once. This function must throw an error if the <code>callback</code> 757 * argument is not a function. 758 * 759 * @member jsf.ajax 760 * @param callback a reference to a function to call on an error 761 */ 762 addOnError: function addOnError(callback) { 763 if (typeof callback === 'function') { 764 errorListeners[errorListeners.length] = callback; 765 } else { 766 throw new Error("jsf.ajax.addOnError: Added a callback that was not a function."); 767 } 768 }, 769 /** 770 * Register a callback for event handling. 771 * <p><b>Usage:</b></p> 772 * <pre><code> 773 * jsf.ajax.addOnEvent(statusUpdate); 774 * ... 775 * var statusUpdate = function statusUpdate(data) { 776 * ... 777 * } 778 * </pre></code> 779 * <p><b>Implementation Requirements:</b></p> 780 * This function must accept a reference to an existing JavaScript function. 781 * The JavaScript function reference must be added to a list of callbacks, making it possible 782 * to register more than one callback by invoking <code>jsf.ajax.addOnEvent</code> 783 * more than once. This function must throw an error if the <code>callback</code> 784 * argument is not a function. 785 * 786 * @member jsf.ajax 787 * @param callback a reference to a function to call on an event 788 */ 789 addOnEvent: function addOnEvent(callback) { 790 if (typeof callback === 'function') { 791 eventListeners[eventListeners.length] = callback; 792 } else { 793 throw new Error("jsf.ajax.addOnEvent: Added a callback that was not a function"); 794 } 795 }, 796 /** 797 * <p>Send an asynchronous Ajax request to the server. 798 * <p><b>Usage:</b></p> 799 * <pre><code> 800 * Example showing all optional arguments: 801 * 802 * <commandButton id="button1" value="submit" 803 * onclick="jsf.ajax.request(this,event, 804 * {execute:'button1',render:'status',onevent: handleEvent,onerror: handleError});return false;"/> 805 * </commandButton/> 806 * </pre></code> 807 * <p><b>Implementation Requirements:</b></p> 808 * This function must: 809 * <ul> 810 * <li>Capture the element that triggered this Ajax request 811 * (from the <code>source</code> argument, also known as the 812 * <code>source</code> element.</li> 813 * <li>If the <code>source</code> element is <code>null</code> or 814 * <code>undefined</code> throw an error.</li> 815 * <li>If the <code>source</code> argument is not a <code>string</code> or 816 * DOM element object, throw an error.</li> 817 * <li>If the <code>source</code> argument is a <code>string</code>, find the 818 * DOM element for that <code>string</code> identifier. 819 * <li>If the DOM element could not be determined, throw an error.</li> 820 * <li>If the <code>onerror</code> and <code>onevent</code> arguments are set, 821 * they must be functions, or throw an error. 822 * <li>Determine the <code>source</code> element's <code>form</code> 823 * element.</li> 824 * <li>Get the <code>form</code> view state by calling 825 * {@link jsf.viewState} passing the 826 * <code>form</code> element as the argument.</li> 827 * <li>Collect post data arguments for the Ajax request. 828 * <ul> 829 * <li>The following name/value pairs are required post data arguments: 830 * <ul> 831 * <li><code>javax.faces.partial.source</code> with the value as the 832 * source element identifier.</li> 833 * <li>The name and value of the <code>source</code> element that 834 * triggered this request;</li> 835 * <li><code>javax.faces.partial.ajax</code> with the value 836 * <code>true</code></li> 837 * </ul> 838 * </li> 839 * </ul> 840 * </li> 841 * <li>Collect optional post data arguments for the Ajax request. 842 * <ul> 843 * <li>Determine additional arguments (if any) from the <code>options</code> 844 * argument. If <code>options.execute</code> exists, create the post data argument 845 * with the name <code>javax.faces.partial.execute</code> and the value as a 846 * space delimited <code>string</code> of client identifiers. If 847 * <code>options.render</code> exists, create the post data argument with the name 848 * <code>javax.faces.partial.render</code> and the value as a space delimited 849 * <code>string</code> of client identifiers.</li> 850 * <li>Determine additional arguments (if any) from the <code>event</code> 851 * argument. The following name/value pairs may be used from the 852 * <code>event</code> object: 853 * <ul> 854 * <li><code>target</code> - the ID of the element that triggered the event.</li> 855 * <li><code>captured</code> - the ID of the element that captured the event.</li> 856 * <li><code>type</code> - the type of event (ex: onkeypress)</li> 857 * <li><code>alt</code> - <code>true</code> if ALT key was pressed.</li> 858 * <li><code>ctrl</code> - <code>true</code> if CTRL key was pressed.</li> 859 * <li><code>shift</code> - <code>true</code> if SHIFT key was pressed. </li> 860 * <li><code>meta</code> - <code>true</code> if META key was pressed. </li> 861 * <li><code>right</code> - <code>true</code> if right mouse button 862 * was pressed. </li> 863 * <li><code>left</code> - <code>true</code> if left mouse button 864 * was pressed. </li> 865 * <li><code>keycode</code> - the key code. 866 * </ul> 867 * </li> 868 * </ul> 869 * </li> 870 * <li>Encode the set of post data arguments.</li> 871 * <li>Join the encoded view state with the encoded set of post data arguments 872 * to form the <code>query string</code> that will be sent to the server.</li> 873 * <li>Create a request <code>context</code> object and set the properties: 874 * <ul><li><code>source</code> (the source DOM element for this request)</li> 875 * <li><code>onerror</code> (the error handler for this request)</li> 876 * <li><code>onevent</code> (the event handler for this request)</li></ul> 877 * The request context will be used during error/event handling.</li> 878 * <li>Send a <code>begin</code> event following the procedure as outlined 879 * in the Chapter 13 "Sending Events" section of the spec prose document <a 880 * href="../../javadocs/overview-summary.html#prose_document">linked in the 881 * overview summary</a></li> 882 * <li>Send the request as an <code>asynchronous POST</code> using the 883 * <code>action</code> property of the <code>form</code> element as the 884 * <code>url</code>.</li> 885 * </ul> 886 * Before the request is sent it must be put into a queue to ensure requests 887 * are sent in the same order as when they were initiated. The request callback function 888 * must examine the queue and determine the next request to be sent. The behavior of the 889 * request callback function must be as follows: 890 * <ul> 891 * <li>If the request completed successfully invoke {@link jsf.ajax.response} 892 * passing the <code>request</code> object.</li> 893 * <li>If the request did not complete successfully, notify the client.</li> 894 * <li>Regardless of the outcome of the request (success or error) every request in the 895 * queue must be handled. Examine the status of each request in the queue starting from 896 * the request that has been in the queue the longest. If the status of the request is 897 * <code>complete</code> (readyState 4), dequeue the request (remove it from the queue). 898 * If the request has not been sent (readyState 0), send the request. Requests that are 899 * taken off the queue and sent should not be put back on the queue.</li> 900 * </ul> 901 * 902 * </p> 903 * 904 * @param source The DOM element that triggered this Ajax request, or an id string of the 905 * element to use as the triggering element. 906 * @param event The DOM event that triggered this Ajax request. The 907 * <code>event</code> argument is optional. 908 * @param options The set of available options that can be sent as 909 * request parameters to control client and/or server side 910 * request processing. Acceptable name/value pair options are: 911 * <table border="1"> 912 * <tr> 913 * <th>name</th> 914 * <th>value</th> 915 * </tr> 916 * <tr> 917 * <td><code>execute</code></td> 918 * <td><code>space seperated list of client identifiers</code></td> 919 * </tr> 920 * <tr> 921 * <td><code>render</code></td> 922 * <td><code>space seperated list of client identifiers</code></td> 923 * </tr> 924 * <tr> 925 * <td><code>onevent</code></td> 926 * <td><code>function to callback for event</code></td> 927 * </tr> 928 * <tr> 929 * <td><code>onerror</code></td> 930 * <td><code>function to callback for error</code></td> 931 * </tr> 932 * </table> 933 * The <code>options</code> argument is optional. 934 * @member jsf.ajax 935 * @function jsf.ajax.request 936 * @throws ArgNotSet Error if first required argument <code>element</code> is not specified 937 */ 938 request: function request(source, event, options) { 939 940 var element; 941 942 if (typeof source === 'undefined' || source === null) { 943 throw new Error("jsf.ajax.request: source not set"); 944 } 945 if (typeof source === 'string') { 946 element = document.getElementById(source); 947 } else if (typeof source === 'object') { 948 element = source; 949 } else { 950 throw new Error("jsf.request: source must be object or string"); 951 } 952 953 if (typeof(options) === 'undefined' || options === null) { 954 options = {}; 955 } 956 957 // Error handler for this request 958 var onerror = false; 959 960 if (options.onerror && typeof options.onerror === 'function') { 961 onerror = options.onerror; 962 } else if (options.onerror && typeof options.onerror !== 'function') { 963 throw new Error("jsf.ajax.request: Added an onerror callback that was not a function"); 964 } 965 966 // Event handler for this request 967 var onevent = false; 968 969 if (options.onevent && typeof options.onevent === 'function') { 970 onevent = options.onevent; 971 } else if (options.onevent && typeof options.onevent !== 'function') { 972 throw new Error("jsf.ajax.request: Added an onevent callback that was not a function"); 973 } 974 975 var form = getForm(element); 976 var viewState = jsf.getViewState(form); 977 978 // Set up additional arguments to be used in the request.. 979 // Make sure "javax.faces.partial.source" is set up. 980 // If there were "execute" ids specified, make sure we 981 // include the identifier of the source element in the 982 // "execute" list. If there were no "execute" ids 983 // specified, determine the default. 984 985 var args = {}; 986 987 args["javax.faces.partial.source"] = element.id; 988 989 // RELEASE_PENDING Get rid of commas. It's supposed to be spaces. 990 if (options.execute) { 991 var temp = toArray(options.execute, ','); 992 // RELEASE_PENDING remove isInArray function 993 if (!isInArray(temp, element.name)) { 994 options.execute = element.name + "," + options.execute; 995 } 996 } else { 997 options.execute = element.id; 998 } 999 1000 args["javax.faces.partial.execute"] = toArray(options.execute, ',').join(','); 1001 if (options.render) { 1002 args["javax.faces.partial.render"] = toArray(options.render, ',').join(','); 1003 } 1004 1005 // remove non-passthrough options 1006 delete options.execute; 1007 delete options.render; 1008 delete options.onerror; 1009 delete options.onevent; 1010 // copy all other options to args 1011 for (var property in options) { 1012 if (options.hasOwnProperty(property)) { 1013 args[property] = options[property]; 1014 } 1015 } 1016 1017 args["javax.faces.partial.ajax"] = "true"; 1018 args["method"] = "POST"; 1019 args["url"] = form.action; 1020 // add source 1021 var action = $(element); 1022 if (action && action.form) { 1023 args[action.name] = action.value || 'x'; 1024 } else { 1025 args[element] = element; 1026 } 1027 1028 var ajaxEngine = new AjaxEngine(); 1029 ajaxEngine.setupArguments(args); 1030 ajaxEngine.queryString = viewState; 1031 ajaxEngine.context.onevent = onevent; 1032 ajaxEngine.context.onerror = onerror; 1033 ajaxEngine.context.source = element; 1034 ajaxEngine.sendRequest(); 1035 }, 1036 /** 1037 * <p>Receive an Ajax response from the server. 1038 * <p><b>Usage:</b></p> 1039 * <pre><code> 1040 * jsf.ajax.response(request, context); 1041 * </pre></code> 1042 * <p><b>Implementation Requirements:</b></p> 1043 * This function must evaluate the markup returned in the 1044 * <code>request.responseXML</code> object and perform the following action: 1045 * <ul> 1046 * <p>If there is no XML response returned, signal an <code>emptyResponse</code> 1047 * error. If the XML response does not follow the format as outlined 1048 * in Appendix A of the spec prose document <a 1049 * href="../../javadocs/overview-summary.html#prose_document">linked in the 1050 * overview summary</a> signal a <code>malformedError</code> error. Refer to 1051 * section "Signaling Errors" in Chapter 13 of the spec prose document <a 1052 * href="../../javadocs/overview-summary.html#prose_document">linked in the 1053 * overview summary</a>.</p> 1054 * <p>If the response was successfully processed, send a <code>success</code> 1055 * event as outlined in Chapter 13 "Sending Events" section of the spec prose 1056 * document <a 1057 * href="../../javadocs/overview-summary.html#prose_document">linked in the 1058 * overview summary</a>.</p> 1059 * <p><i>Update Element Processing</i></p> 1060 * <li>If an <code>update</code> element is found in the response 1061 * with the identifier <code>javax.faces.ViewRoot</code>: 1062 * <pre><code><update id="javax.faces.ViewRoot"> 1063 * <![CDATA[...]]> 1064 * </update></code></pre> 1065 * Update the entire DOM as follows: 1066 * <ul> 1067 * <li>Extract the <code>CDATA</code> content and trim the <html> 1068 * and </html> from the <code>CDATA</code> content if it is present.</li> 1069 * <li>If the <code>CDATA</code> content contains a <head> element, 1070 * and the document has a <code><head></code> section, extract the 1071 * contents of the <head> element from the <code><update></code> 1072 * element's <code>CDATA</code> content and replace the document's <head> 1073 * section with this contents.</li> 1074 * <li>If the <code>CDATA</code> content contains a <body> element, 1075 * and the document has a <code><body></code> section, extract the contents 1076 * of the <body> element from the <code><update></code> 1077 * element's <code>CDATA</code> content and replace the document's <body> 1078 * section with this contents.</li> 1079 * <li>If the <code>CDATA</code> content does not contain a <body> element, 1080 * replace the document's <body> section with the <code>CDATA</code> 1081 * contents.</li> 1082 * </ul> 1083 * <li>If an <code>update</code> element is found in the response with the identifier 1084 * <code>javax.faces.ViewState</code>: 1085 * <pre><code><update id="javax.faces.ViewState"> 1086 * <![CDATA[...]]> 1087 * </update></code></pre> 1088 * Include this <code>state</code> in the document as follows: 1089 * <ul> 1090 * <li>Extract this <code><update></code> element's <code>CDATA</code> contents 1091 * from the response.</li> 1092 * <li>If the document contains an element with the identifier 1093 * <code>javax.faces.ViewState</code> replace its contents with the 1094 * <code>CDATA</code> contents.</li> 1095 * <li>For each <code><form></code> element in the document: 1096 * <ul> 1097 * <li>If the <code><form></code> element contains an <code><input></code> 1098 * element with the identifier <code>javax.faces.ViewState</code>, replace the 1099 * <code><input></code> element contents with the <code><update></code> 1100 * element's <code>CDATA</code> contents.</li> 1101 * <li>If the <code><form></code> element does not contain an element with 1102 * the identifier <code>javax.faces.ViewState</code>, create an 1103 * <code><input></code> element of the type <code>hidden</code>, 1104 * with the identifier <code>javax.faces.ViewState</code>, set its contents 1105 * to the <code><update></code> element's <code>CDATA</code> contents, and 1106 * add the <code><input></code> element as a child to the 1107 * <code><form></code> element.</li> 1108 * </ul> 1109 * </li> 1110 * </ul> 1111 * </li> 1112 * <li>For any other <code><update></code> element: 1113 * <pre><code><update id="update id"> 1114 * <![CDATA[...]]> 1115 * </update></code></pre> 1116 * Find the DOM element with the identifier that matches the 1117 * <code><update></code> element identifier, and replace its contents with 1118 * the <code><update></code> element's <code>CDATA</code> contents.</li> 1119 * </li> 1120 * <p><i>Insert Element Processing</i></p> 1121 * <li>If an <code><insert></code> element is found in the response with the 1122 * attribute <code>before</code>: 1123 * <pre><code><insert id="insert id" before="before id"> 1124 * <![CDATA[...]]> 1125 * </insert></code></pre> 1126 * <ul> 1127 * <li>Extract this <code><insert></code> element's <code>CDATA</code> contents 1128 * from the response.</li> 1129 * <li>Find the DOM element whose identifier matches <code>before id</code> and insert 1130 * the <code><insert></code> element's <code>CDATA</code> content before 1131 * the DOM element in the document.</li> 1132 * </ul> 1133 * </li> 1134 * <li>If an <code><insert></code> element is found in the response with the 1135 * attribute <code>after</code>: 1136 * <pre><code><insert id="insert id" after="after id"> 1137 * <![CDATA[...]]> 1138 * </insert></code></pre> 1139 * <ul> 1140 * <li>Extract this <code><insert></code> element's <code>CDATA</code> contents 1141 * from the response.</li> 1142 * <li>Find the DOM element whose identifier matches <code>after id</code> and insert 1143 * the <code><insert></code> element's <code>CDATA</code> content after 1144 * the DOM element in the document.</li> 1145 * </ul> 1146 * </li> 1147 * <p><i>Delete Element Processing</i></p> 1148 * <li>If a <code><delete></code> element is found in the response: 1149 * <pre><code><delete id="delete id"/></code></pre> 1150 * Find the DOM element whose identifier matches <code>delete id</code> and remove it 1151 * from the DOM.</li> 1152 * <p><i>Element Attribute Update Processing</i></p> 1153 * <li>If an <code><attributes></code> element is found in the response: 1154 * <pre><code><attributes id="id of element with attribute"> 1155 * <attribute name="attribute name" value="attribute value"> 1156 * ... 1157 * </attributes></code></pre> 1158 * <ul> 1159 * <li>Find the DOM element that matches the <code><attributes></code> identifier.</li> 1160 * <li>For each nested <code><attribute></code> element in <code><attribute></code>, 1161 * update the DOM element attribute value (whose name matches <code>attribute name</code>), 1162 * with <code>attribute value</code>.</li> 1163 * </ul> 1164 * </li> 1165 * <p><i>JavaScript Processing</i></p> 1166 * <li>If an <code><eval></code> element is found in the response: 1167 * <pre><code><eval> 1168 * <![CDATA[...JavaScript...]]> 1169 * </eval></code></pre> 1170 * <ul> 1171 * <li>Extract this <code><eval></code> element's <code>CDATA</code> contents 1172 * from the response and execute it as if it were JavaScript code.</li> 1173 * </ul> 1174 * </li> 1175 * <p><i>Redirect Processing</i></p> 1176 * <li>If a <code><redirect></code> element is found in the response: 1177 * <pre><code><redirect url="redirect url"/></code></pre> 1178 * Cause a redirect to the url <code>redirect url</code>.</li> 1179 * <p><i>Error Processing</i></p> 1180 * <li>If an <code><error></code> element is found in the response: 1181 * <pre><code><error> 1182 * <error-name>..fully qualified class name string...<error-name> 1183 * <error-message><![CDATA[...]]><error-message> 1184 * </error></code></pre> 1185 * Extract this <code><error></code> element's <code>error-name</code> contents 1186 * and the <code>error-message</code> contents. Signal a <code>serverError</code> passing 1187 * the <code>errorName</code> and <code>errorMessage</code>. Refer to 1188 * section "Signaling Errors" in Chapter 13 of the spec prose document <a 1189 * href="../../javadocs/overview-summary.html#prose_document">linked in the 1190 * overview summary</a>.</li> 1191 * <p><i>Extensions</i></p> 1192 * <li>The <code><extensions></code> element provides a way for framework 1193 * implementations to provide their own information.</li> 1194 * </ul> 1195 * 1196 * </p> 1197 * 1198 * @param request The <code>XMLHttpRequest</code> instance that 1199 * contains the status code and response message from the server. 1200 * 1201 * @param context An object containing the request context, including the following properties: 1202 * the source element, per call onerror callback function, and per call onevent callback function. 1203 * 1204 * @throws EmptyResponse error if request contains no data 1205 * 1206 * @function jsf.ajax.response 1207 */ 1208 response: function response(request, context) { 1209 if (!request) { 1210 throw new Error("jsf.ajax.response: Request parameter is unset"); 1211 } 1212 1213 var xmlReq = request; 1214 1215 var xml = xmlReq.responseXML; 1216 if (xml === null) { 1217 sendError(request, context, "emptyResponse"); 1218 } 1219 1220 1221 var responseType = xml.getElementsByTagName("partial-response")[0].firstChild; 1222 1223 if (responseType.nodeName === "error") { // it's an error 1224 var errorName = responseType.firstChild.firstChild.nodeValue; 1225 var errorMessage = responseType.firstChild.nextSibling.firstChild.nodeValue; 1226 sendError(request, context, "serverError", errorName, errorMessage); 1227 return; 1228 } 1229 1230 1231 if (responseType.nodeName === "redirect") { 1232 window.location = responseType.getAttribute("url"); 1233 return; 1234 } 1235 1236 1237 if (responseType.nodeName !== "changes") { 1238 sendError(request, context, "malformedXML"); 1239 return; 1240 } 1241 1242 1243 var changes = responseType.childNodes; 1244 1245 1246 for (var i = 0; i < changes.length; i++) { 1247 switch (changes[i].nodeName) { 1248 case "update": 1249 doUpdate(changes[i]); 1250 break; 1251 case "delete": 1252 doDelete(changes[i]); 1253 break; 1254 case "insert": 1255 doInsert(changes[i]); 1256 break; 1257 case "attributes": 1258 doAttributes(changes[i]); 1259 break; 1260 case "eval": 1261 doEval(changes[i]); 1262 break; 1263 case "extension": 1264 // RELEASE_PENDING no action? 1265 break; 1266 default: 1267 sendError(request, context, "malformedXML"); 1268 return; 1269 } 1270 } 1271 sendEvent(request, context, "success"); 1272 1273 ////////////////////// 1274 // Check for updates.. 1275 ////////////////////// 1276 1277 ////////////////////// 1278 // Check For Inserts. 1279 ////////////////////// 1280 1281 ////////////////////// 1282 // Check For Deletes. 1283 ////////////////////// 1284 1285 ////////////////////// 1286 // Update Attributes. 1287 ////////////////////// 1288 1289 ////////////////////// 1290 // JavaScript Eval. 1291 ////////////////////// 1292 1293 } 1294 }; 1295 }(); 1296 1297 /** 1298 * 1299 * <p>Return the value of <code>Application.getProjectStage()</code> for 1300 * the currently running application instance. Calling this method must 1301 * not cause any network transaction to happen to the server.</p> 1302 * <p><b>Usage:</b></p> 1303 * <pre><code> 1304 * var stage = jsf.getProjectStage(); 1305 * if (stage === ProjectStage.Development) { 1306 * ... 1307 * } else if stage === ProjectStage.Production) { 1308 * ... 1309 * } 1310 * </code></pre> 1311 * 1312 * @returns String <code>String</code> representing the current state of the 1313 * running application in a typical product development lifecycle. Refer 1314 * to <code>javax.faces.application.Application.getProjectStage</code> and 1315 * <code>javax.faces.application.ProjectStage</code>. 1316 * @function jsf.getProjectStage 1317 */ 1318 jsf.getProjectStage = function() { 1319 return "#{facesContext.application.projectStage}"; 1320 }; 1321 1322 1323 /** 1324 * <p>Collect and encode state for input controls associated 1325 * with the specified <code>form</code> element.</p> 1326 * <p><b>Usage:</b></p> 1327 * <pre><code> 1328 * var state = jsf.getViewState(form); 1329 * </pre></code> 1330 * 1331 * @param form The <code>form</code> element whose contained 1332 * <code>input</code> controls will be collected and encoded. 1333 * Only successful controls will be collected and encoded in 1334 * accordance with: <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2"> 1335 * Section 17.13.2 of the HTML Specification</a>. 1336 * 1337 * @returns String The encoded state for the specified form's input controls. 1338 * @function jsf.getViewState 1339 */ 1340 jsf.getViewState = function(form) { 1341 var els = form.elements; 1342 var len = els.length; 1343 var qString = ""; 1344 var addField = function(name, value) { 1345 if (qString.length > 0) { 1346 qString += "&"; 1347 } 1348 qString += encodeURIComponent(name) + "=" + encodeURIComponent(value); 1349 }; 1350 for (var i = 0; i < len; i++) { 1351 var el = els[i]; 1352 if (!el.disabled) { 1353 switch (el.type) { 1354 case 'text': 1355 case 'password': 1356 case 'hidden': 1357 case 'textarea': 1358 addField(el.name, el.value); 1359 break; 1360 case 'select-one': 1361 if (el.selectedIndex >= 0) { 1362 addField(el.name, el.options[el.selectedIndex].value); 1363 } 1364 break; 1365 case 'select-multiple': 1366 for (var j = 0; j < el.options.length; j++) { 1367 if (el.options[j].selected) { 1368 addField(el.name, el.options[j].value); 1369 } 1370 } 1371 break; 1372 case 'checkbox': 1373 case 'radio': 1374 addField(el.name, el.checked + ""); 1375 break; 1376 } 1377 } 1378 } 1379 return qString; 1380 }; 1381 1382 /** 1383 * An integer specifying the specification version that this file implements. 1384 * It's format is: rightmost two digits, bug release number, next two digits, 1385 * minor release number, leftmost digits, major release number. 1386 * This number may only be incremented by a new release of the specification. 1387 * @ignore 1388 */ 1389 jsf.specversion = 20000; 1390 1391 /** 1392 * An integer specifying the implementation version that this file implements. 1393 * It's a monotonically increasing number, reset with every increment of 1394 * <code>jsf.specversion</code> 1395 * This number is implementation dependent. 1396 * @ignore 1397 */ 1398 jsf.implversion = 1; 1399 1400 1401 } //end if version detection block 1402