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