1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to you under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /** 19 * MyFaces core javascripting libraries 20 * 21 * Those are the central public API functions in the Faces2 22 * Ajax API! They handle the entire form submit and ajax send 23 * and resolve cycle! 24 */ 25 26 /** 27 * @ignore 28 */ 29 if (!window.faces) { 30 /** 31 * @namespace faces 32 */ 33 var faces = new function() { 34 /* 35 * Version of the implementation for the faces.js. 36 * <p /> 37 * as specified within the faces specifications: 38 * <ul> 39 * <li>left two digits major release number</li> 40 * <li>middle two digits minor spec release number</li> 41 * <li>right two digits bug release number</li> 42 * </ul> 43 * @constant 44 */ 45 this.specversion = 220000; 46 /** 47 * Implementation version as specified within the faces specification. 48 * <p /> 49 * A number increased with every implementation version 50 * and reset by moving to a new spec release number 51 * 52 * @constant 53 */ 54 this.implversion = 0; 55 56 /** 57 * SeparatorChar as defined by UINamingContainer.getNamingContainerSeparatorChar() 58 * @type {Char} 59 */ 60 this.separatorchar = getSeparatorChar(); 61 62 /** 63 * This method is responsible for the return of a given project stage as defined 64 * by the faces specification. 65 * <p/> 66 * Valid return values are: 67 * <ul> 68 * <li>"Production"</li> 69 * <li>"Development"</li> 70 * <li>"SystemTest"</li> 71 * <li>"UnitTest"</li> 72 * </li> 73 * 74 * @return {String} the current project state emitted by the server side method: 75 * <i>jakarta.faces.application.Application.getProjectStage()</i> 76 */ 77 this.getProjectStage = function() { 78 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 79 return impl.getProjectStage(); 80 }; 81 82 /** 83 * collect and encode data for a given form element (must be of type form) 84 * find the jakarta.faces.ViewState element and encode its value as well! 85 * return a concatenated string of the encoded values! 86 * 87 * @throws an exception in case of the given element not being of type form! 88 * https://issues.apache.org/jira/browse/MYFACES-2110 89 */ 90 this.getViewState = function(formElement) { 91 /*we are not allowed to add the impl on a global scope so we have to inline the code*/ 92 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 93 return impl.getViewState(formElement); 94 }; 95 96 /** 97 * returns the window identifier for the given node / window 98 * @param {optional String | DomNode} the node for which the client identifier has to be determined 99 * @return the window identifier or null if none is found 100 */ 101 this.getClientWindow = function() { 102 /*we are not allowed to add the impl on a global scope so we have to inline the code*/ 103 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 104 return (arguments.length)? impl.getClientWindow(arguments[0]) : impl.getClientWindow(); 105 } 106 107 //private helper functions 108 function getSeparatorChar() { 109 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 110 return impl.getSeparatorChar(); 111 } 112 113 }; 114 115 //jsdoc helper to avoid warnings, we map later 116 window.faces = faces; 117 } 118 119 /** 120 * just to make sure no questions arise, I simply prefer here a weak 121 * typeless comparison just in case some frameworks try to interfere 122 * by overriding null or fiddeling around with undefined or typeof in some ways 123 * it is safer in this case than the standard way of doing a strong comparison 124 **/ 125 if (!faces.ajax) { 126 /** 127 * @namespace faces.ajax 128 */ 129 faces.ajax = new function() { 130 131 132 /** 133 * this function has to send the ajax requests 134 * 135 * following request conditions must be met: 136 * <ul> 137 * <li> the request must be sent asynchronously! </li> 138 * <li> the request must be a POST!!! request </li> 139 * <li> the request url must be the form action attribute </li> 140 * <li> all requests must be queued with a client side request queue to ensure the request ordering!</li> 141 * </ul> 142 * 143 * @param {String|Node} element: any dom element no matter being it html or faces, from which the event is emitted 144 * @param {EVENT} event: any javascript event supported by that object 145 * @param {Map} options : map of options being pushed into the ajax cycle 146 */ 147 this.request = function(element, event, options) { 148 if (!options) { 149 options = {}; 150 } 151 /*we are not allowed to add the impl on a global scope so we have to inline the code*/ 152 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 153 return impl.request(element, event, options); 154 }; 155 156 /** 157 * Adds an error handler to our global error queue. 158 * the error handler must be of the format <i>function errorListener(<errorData>)</i> 159 * with errorData being of following format: 160 * <ul> 161 * <li> errorData.type : "error"</li> 162 * <li> errorData.status : the error status message</li> 163 * <li> errorData.errorName : the server error name in case of a server error</li> 164 * <li> errorData.errorMessage : the server error message in case of a server error</li> 165 * <li> errorData.source : the issuing source element which triggered the request </li> 166 * <li> eventData.responseCode: the response code (aka http request response code, 401 etc...) </li> 167 * <li> eventData.responseText: the request response text </li> 168 * <li> eventData.responseXML: the request response xml </li> 169 * </ul> 170 * 171 * @param {function} errorListener error handler must be of the format <i>function errorListener(<errorData>)</i> 172 */ 173 this.addOnError = function(/*function*/errorListener) { 174 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 175 return impl.addOnError(errorListener); 176 }; 177 178 /** 179 * Adds a global event listener to the ajax event queue. The event listener must be a function 180 * of following format: <i>function eventListener(<eventData>)</i> 181 * 182 * @param {function} eventListener event must be of the format <i>function eventListener(<eventData>)</i> 183 */ 184 this.addOnEvent = function(/*function*/eventListener) { 185 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 186 return impl.addOnEvent(eventListener); 187 }; 188 189 /** 190 * processes the ajax response if the ajax request completes successfully 191 * @param request the ajax request! 192 * @param context the ajax context! 193 */ 194 this.response = function(/*xhr request object*/request, context) { 195 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 196 return impl.response(request, context); 197 }; 198 }; 199 } 200 201 if (!faces.util) { 202 /** 203 * @namespace faces.util 204 */ 205 faces.util = new function() { 206 207 /** 208 * varargs function which executes a chain of code (functions or any other code) 209 * 210 * if any of the code returns false, the execution 211 * is terminated prematurely skipping the rest of the code! 212 * 213 * @param {DomNode} source, the callee object 214 * @param {Event} event, the event object of the callee event triggering this function 215 * @param {optional} functions to be chained, if any of those return false the chain is broken 216 */ 217 this.chain = function(source, event) { 218 var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl); 219 return impl.chain.apply(impl, arguments); 220 }; 221 }; 222 } 223 224 if (!faces.push) { 225 226 /** 227 * @namespace faces.push 228 */ 229 faces.push = new function() { 230 231 // "Constant" fields ---------------------------------------------------------------------------------------------- 232 var URL_PROTOCOL = window.location.protocol.replace("http", "ws") + "//"; 233 var RECONNECT_INTERVAL = 500; 234 var MAX_RECONNECT_ATTEMPTS = 25; 235 var REASON_EXPIRED = "Expired"; 236 237 // Private static fields ------------------------------------------------------------------------------------------ 238 239 /* socket map by token */ 240 var sockets = {}; 241 /* component attributes by clientId */ 242 var components = {}; 243 /* client ids by token (share websocket connection) */ 244 var clientIdsByTokens = {}; 245 var self = {}; 246 247 // Private constructor functions ---------------------------------------------------------------------------------- 248 /** 249 * Creates a reconnecting web socket. When the web socket successfully connects on first attempt, then it will 250 * automatically reconnect on timeout with cumulative intervals of 500ms with a maximum of 25 attempts (~3 minutes). 251 * The <code>onclose</code> function will be called with the error code of the last attempt. 252 * @constructor 253 * @param {string} channelToken the channel token associated with this websocket connection 254 * @param {string} url The URL of the web socket 255 * @param {string} channel The name of the web socket channel. 256 */ 257 function Socket(channelToken, url, channel) { 258 259 // Private fields ----------------------------------------------------------------------------------------- 260 261 var socket; 262 var reconnectAttempts = 0; 263 var self = this; 264 265 // Public functions --------------------------------------------------------------------------------------- 266 267 /** 268 * Opens the reconnecting web socket. 269 */ 270 self.open = function() { 271 if (socket && socket.readyState == 1) { 272 return; 273 } 274 275 socket = new WebSocket(url); 276 277 socket.onopen = function(event) { 278 if (!reconnectAttempts) { 279 var clientIds = clientIdsByTokens[channelToken]; 280 for (var i = clientIds.length - 1; i >= 0; i--){ 281 var socketClientId = clientIds[i]; 282 components[socketClientId]['onopen'](channel); 283 } 284 } 285 reconnectAttempts = 0; 286 }; 287 288 socket.onmessage = function(event) { 289 var message = JSON.parse(event.data); 290 for (var i = clientIdsByTokens[channelToken].length - 1; i >= 0; i--){ 291 var socketClientId = clientIdsByTokens[channelToken][i]; 292 if(document.getElementById(socketClientId)) { 293 try{ 294 components[socketClientId]['onmessage'](message, channel, event); 295 }catch(e){ 296 //Ignore 297 } 298 var behaviors = components[socketClientId]['behaviors']; 299 var functions = behaviors[message]; 300 if (functions && functions.length) { 301 for (var j = 0; j < functions.length; j++) { 302 try{ 303 functions[j](null); 304 }catch(e){ 305 //Ignore 306 } 307 } 308 } 309 } else { 310 clientIdsByTokens[channelToken].splice(i,1); 311 } 312 } 313 if (clientIdsByTokens[channelToken].length == 0){ 314 //tag dissapeared 315 self.close(); 316 } 317 318 }; 319 320 socket.onclose = function(event) { 321 if (!socket 322 || (event.code == 1000 && event.reason == REASON_EXPIRED) 323 || (event.code == 1008) 324 || (!reconnectAttempts) 325 || (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS)) 326 { 327 var clientIds = clientIdsByTokens[channelToken]; 328 for (var i = clientIds.length - 1; i >= 0; i--){ 329 var socketClientId = clientIds[i]; 330 components[socketClientId]['onclose'](event.code, channel, event); 331 } 332 } 333 else { 334 setTimeout(self.open, RECONNECT_INTERVAL * reconnectAttempts++); 335 } 336 }; 337 338 socket.onerror = function(event) { 339 var clientIds = clientIdsByTokens[channelToken]; 340 for (var i = clientIds.length - 1; i >= 0; i--){ 341 var socketClientId = clientIds[i]; 342 components[socketClientId]['onerror'](channel); 343 } 344 }; 345 }; 346 347 /** 348 * Closes the reconnecting web socket. 349 */ 350 self.close = function() { 351 if (socket) { 352 var s = socket; 353 socket = null; 354 s.close(); 355 } 356 }; 357 358 } 359 360 // Public static functions ---------------------------------------------------------------------------------------- 361 362 /** 363 * 364 * @param {function} onopen The function to be invoked when the web socket is opened. 365 * @param {function} onmessage The function to be invoked when a message is received. 366 * @param {function} onerror The function to be invoked when the web socket throws a error. 367 * @param {function} onclose The function to be invoked when the web socket is closed. 368 * @param {boolean} autoconnect Whether or not to immediately open the socket. Defaults to <code>false</code>. 369 */ 370 var _t = this; 371 this.init = function(socketClientId, uri, channel, onopen, onmessage, onerror, onclose, behaviorScripts, autoconnect) { 372 373 onclose = resolveFunction(onclose); 374 375 if (!window.WebSocket) { // IE6-9. 376 onclose(-1, channel); 377 return; 378 } 379 380 var channelToken = uri.substr(uri.indexOf('?')+1); 381 382 if (!components[socketClientId]) { 383 components[socketClientId] = { 384 'channelToken': channelToken, 385 'onopen': resolveFunction(onopen), 386 'onmessage' : resolveFunction(onmessage), 387 'onerror' : resolveFunction(onerror), 388 'onclose': onclose, 389 'behaviors': behaviorScripts, 390 'autoconnect': autoconnect}; 391 if (!clientIdsByTokens[channelToken]) { 392 clientIdsByTokens[channelToken] = []; 393 } 394 clientIdsByTokens[channelToken].push(socketClientId); 395 if (!sockets[channelToken]){ 396 sockets[channelToken] = new Socket(channelToken, 397 getBaseURL(uri), channel); 398 } 399 } 400 401 if (autoconnect) { 402 _t.open(socketClientId); 403 } 404 }; 405 406 /** 407 * Open the web socket on the given channel. 408 * @param {string} channel The name of the web socket channel. 409 * @throws {Error} When channel is unknown. 410 */ 411 this.open = function(socketClientId) { 412 getSocket(components[socketClientId]['channelToken']).open(); 413 }; 414 415 /** 416 * Close the web socket on the given channel. 417 * @param {string} channel The name of the web socket channel. 418 * @throws {Error} When channel is unknown. 419 */ 420 this.close = function(socketClientId) { 421 getSocket(components[socketClientId]['channelToken']).close(); 422 }; 423 424 // Private static functions --------------------------------------------------------------------------------------- 425 426 /** 427 * 428 */ 429 function getBaseURL(url) { 430 if (url.indexOf("://") < 0) 431 { 432 var base = window.location.hostname+":"+window.location.port 433 return URL_PROTOCOL + base + url; 434 }else 435 { 436 return url; 437 } 438 } 439 440 /** 441 * Get socket associated with given channelToken. 442 * @param {string} channelToken The name of the web socket channelToken. 443 * @return {Socket} Socket associated with given channelToken. 444 * @throws {Error} When channelToken is unknown, you may need to initialize 445 * it first via <code>init()</code> function. 446 */ 447 function getSocket(channelToken) { 448 var socket = sockets[channelToken]; 449 if (socket) { 450 return socket; 451 } else { 452 throw new Error("Unknown channelToken: " + channelToken); 453 } 454 } 455 456 function resolveFunction(fn) { 457 return (typeof fn !== "function") && (fn = window[fn] || function(){}), fn; 458 } 459 // Expose self to public ------------------------------------------------------------------------------------------ 460 461 //return self; 462 }; 463 } 464 465 466 (!window.myfaces) ? window.myfaces = {} : null; 467 if (!myfaces.ab) { 468 /* 469 * Shortcut of the faces.ajax.request, to shorten the rendered JS. 470 */ 471 myfaces.ab = function(source, event, eventName, execute, render, options) { 472 if (!options) { 473 options = {}; 474 } 475 476 if (eventName) { 477 options["params"] = options.params || {}; 478 options.params["jakarta.faces.behavior.event"] = eventName; 479 } 480 if (execute) { 481 options["execute"] = execute; 482 } 483 if (render) { 484 options["render"] = render; 485 } 486 487 faces.ajax.request(source, event, options); 488 }; 489 }