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 
339         socket.onerror = function(event) {
340             var clientIds = clientIdsByTokens[channelToken];
341             for (var i = clientIds.length - 1; i >= 0; i--){
342                 var socketClientId = clientIds[i];
343                 components[socketClientId]['onerror'](channel);
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     this.init = function(socketClientId, uri, channel, onopen, onmessage, onerror, onclose, behaviorScripts, autoconnect) {
371 
372         onclose = resolveFunction(onclose);
373 
374         if (!window.WebSocket) { // IE6-9.
375             onclose(-1, channel);
376             return;
377         }
378 
379         var channelToken = uri.substr(uri.indexOf('?')+1);
380 
381         if (!components[socketClientId]) {
382             components[socketClientId] = {
383                 'channelToken': channelToken,
384                 'onopen': resolveFunction(onopen),
385                 'onmessage' : resolveFunction(onmessage),
386                 'onerror' : resolveFunction(onerror),
387                 'onclose': onclose,
388                 'behaviors': behaviorScripts,
389                 'autoconnect': autoconnect};
390             if (!clientIdsByTokens[channelToken]) {
391                 clientIdsByTokens[channelToken] = [];
392             }
393             clientIdsByTokens[channelToken].push(socketClientId);
394             if (!sockets[channelToken]){
395                 sockets[channelToken] = new Socket(channelToken,
396                                     getBaseURL(uri), channel);
397             }
398         }
399 
400         if (autoconnect) {
401             this.open(socketClientId);
402         }
403     };
404 
405     /**
406      * Open the web socket on the given channel.
407      * @param {string} channel The name of the web socket channel.
408      * @throws {Error} When channel is unknown.
409      */
410     this.open = function(socketClientId) {
411         getSocket(components[socketClientId]['channelToken']).open();
412     };
413 
414     /**
415      * Close the web socket on the given channel.
416      * @param {string} channel The name of the web socket channel.
417      * @throws {Error} When channel is unknown.
418      */
419     this.close = function(socketClientId) {
420         getSocket(components[socketClientId]['channelToken']).close();
421     };
422 
423     // Private static functions ---------------------------------------------------------------------------------------
424 
425     /**
426      *
427      */
428     function getBaseURL(url) {
429         if (url.indexOf("://") < 0)
430         {
431             var base = window.location.hostname+":"+window.location.port
432             return URL_PROTOCOL + base + url;
433         }else
434         {
435             return url;
436         }
437     }
438 
439     /**
440      * Get socket associated with given channelToken.
441      * @param {string} channelToken The name of the web socket channelToken.
442      * @return {Socket} Socket associated with given channelToken.
443      * @throws {Error} When channelToken is unknown, you may need to initialize
444      *                 it first via <code>init()</code> function.
445      */
446     function getSocket(channelToken) {
447         var socket = sockets[channelToken];
448         if (socket) {
449             return socket;
450         } else {
451             throw new Error("Unknown channelToken: " + channelToken);
452         }
453     }
454 
455     function resolveFunction(fn) {
456         return (typeof fn !== "function") && (fn = window[fn] || function(){}), fn;
457     }
458     // Expose self to public ------------------------------------------------------------------------------------------
459 
460     //return self;
461     };
462 }
463 
464 
465 (!window.myfaces) ? window.myfaces = {} : null;
466 if (!myfaces.ab) {
467     /*
468      * Shortcut of the faces.ajax.request, to shorten the rendered JS.
469      */
470     myfaces.ab = function(source, event, eventName, execute, render, options) {
471         if (!options) {
472             options = {};
473         }
474 
475         if (eventName) {
476             options["jakarta.faces.behavior.event"] = eventName;
477         }
478         if (execute) {
479             options["execute"] = execute;
480         }
481         if (render) {
482             options["render"] = render;
483         }
484 
485         faces.ajax.request(source, event, options);
486     };
487 }