You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ta...@apache.org on 2021/09/14 09:34:18 UTC

[myfaces] branch master updated: MYFACES-4415 - Faces 4.0: add

This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces.git


The following commit(s) were added to refs/heads/master by this push:
     new 712b6c9  MYFACES-4415 - Faces 4.0: add <f:websocket onerror>
712b6c9 is described below

commit 712b6c9ddddd3425ad2b58ae3aeaf0277755ecc5
Author: Thomas Andraschko <ta...@apache.org>
AuthorDate: Tue Sep 14 11:34:01 2021 +0200

    MYFACES-4415 - Faces 4.0: add <f:websocket onerror>
---
 .../java/jakarta/faces/component/_UIWebsocket.java |   3 +
 .../META-INF/resources/myfaces/api/faces.js        | 962 +++++++++++----------
 .../myfaces/push/WebsocketComponentRenderer.java   | 544 ++++++------
 3 files changed, 762 insertions(+), 747 deletions(-)

diff --git a/api/src/main/java/jakarta/faces/component/_UIWebsocket.java b/api/src/main/java/jakarta/faces/component/_UIWebsocket.java
index 1b84590..6178923 100644
--- a/api/src/main/java/jakarta/faces/component/_UIWebsocket.java
+++ b/api/src/main/java/jakarta/faces/component/_UIWebsocket.java
@@ -62,6 +62,9 @@ abstract class _UIWebsocket extends UIComponentBase
     
     @JSFProperty
     public abstract String getOnclose();
+    
+    @JSFProperty
+    public abstract String getOnerror();
 
     @JSFProperty(defaultValue = "true")
     public abstract boolean isConnected();
diff --git a/api/src/main/javascript/META-INF/resources/myfaces/api/faces.js b/api/src/main/javascript/META-INF/resources/myfaces/api/faces.js
index bc78478..658d1f1 100644
--- a/api/src/main/javascript/META-INF/resources/myfaces/api/faces.js
+++ b/api/src/main/javascript/META-INF/resources/myfaces/api/faces.js
@@ -1,477 +1,487 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-*/
-
-/**
- *  MyFaces core javascripting libraries
- *
- *  Those are the central public API functions in the Faces2
- *  Ajax API! They handle the entire form submit and ajax send
- *  and resolve cycle!
- */
-
-/**
-* @ignore
-*/
-if (!window.faces) {
-    /**
-    * @namespace faces
-    */
-    var faces = new function() {
-        /*
-         * Version of the implementation for the faces.js.
-         * <p />
-         * as specified within the faces specifications:
-         * <ul>
-         * <li>left two digits major release number</li>
-         * <li>middle two digits minor spec release number</li>
-         * <li>right two digits bug release number</li>
-         * </ul>
-         * @constant
-         */
-        this.specversion = 220000;
-        /**
-         * Implementation version as specified within the faces specification.
-         * <p />
-         * A number increased with every implementation version
-         * and reset by moving to a new spec release number
-         *
-		 * @constant
-         */
-        this.implversion = 0;
-
-        /**
-         * SeparatorChar as defined by UINamingContainer.getNamingContainerSeparatorChar()
-         * @type {Char}
-         */
-        this.separatorchar = getSeparatorChar();
-
-        /**
-         * This method is responsible for the return of a given project stage as defined
-         * by the faces specification.
-         * <p/>
-         * Valid return values are:
-         * <ul>
-         *     <li>&quot;Production&quot;</li>
-         *     <li>&quot;Development&quot;</li>
-         *     <li>&quot;SystemTest&quot;</li>
-         *     <li>&quot;UnitTest&quot;</li>
-         * </li>
-         *
-         * @return {String} the current project state emitted by the server side method:
-         * <i>jakarta.faces.application.Application.getProjectStage()</i>
-         */
-        this.getProjectStage = function() {
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.getProjectStage();
-        };
-
-        /**
-         * collect and encode data for a given form element (must be of type form)
-         * find the jakarta.faces.ViewState element and encode its value as well!
-         * return a concatenated string of the encoded values!
-         *
-         * @throws an exception in case of the given element not being of type form!
-         * https://issues.apache.org/jira/browse/MYFACES-2110
-         */
-        this.getViewState = function(formElement) {
-            /*we are not allowed to add the impl on a global scope so we have to inline the code*/
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.getViewState(formElement);
-        };
-
-        /**
-         * returns the window identifier for the given node / window
-         * @param {optional String | DomNode}  the node for which the client identifier has to be determined
-         * @return the window identifier or null if none is found
-         */
-        this.getClientWindow = function() {
-            /*we are not allowed to add the impl on a global scope so we have to inline the code*/
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return (arguments.length)? impl.getClientWindow(arguments[0]) : impl.getClientWindow();
-        }
-
-        //private helper functions
-        function getSeparatorChar() {
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.getSeparatorChar();
-        }
-
-    };
-
-    //jsdoc helper to avoid warnings, we map later
-    window.faces = faces;
-}
-
-/**
- * just to make sure no questions arise, I simply prefer here a weak
- * typeless comparison just in case some frameworks try to interfere
- * by overriding null or fiddeling around with undefined or typeof in some ways
- * it is safer in this case than the standard way of doing a strong comparison
- **/
-if (!faces.ajax) {
-    /**
-    * @namespace faces.ajax
-    */
-    faces.ajax = new function() {
-
-
-        /**
-         * this function has to send the ajax requests
-         *
-         * following request conditions must be met:
-         * <ul>
-         *  <li> the request must be sent asynchronously! </li>
-         *  <li> the request must be a POST!!! request </li>
-         *  <li> the request url must be the form action attribute </li>
-         *  <li> all requests must be queued with a client side request queue to ensure the request ordering!</li>
-         * </ul>
-         *
-         * @param {String|Node} element: any dom element no matter being it html or faces, from which the event is emitted
-         * @param {EVENT} event: any javascript event supported by that object
-         * @param {Map} options : map of options being pushed into the ajax cycle
-         */
-        this.request = function(element, event, options) {
-            if (!options) {
-                options = {};
-            }
-            /*we are not allowed to add the impl on a global scope so we have to inline the code*/
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.request(element, event, options);
-        };
-
-        /**
-         * Adds an error handler to our global error queue.
-         * the error handler must be of the format <i>function errorListener(&lt;errorData&gt;)</i>
-         * with errorData being of following format:
-         * <ul>
-         *     <li> errorData.type : &quot;error&quot;</li>
-         *     <li> errorData.status : the error status message</li>
-         *     <li> errorData.errorName : the server error name in case of a server error</li>
-         *     <li> errorData.errorMessage : the server error message in case of a server error</li>
-         *     <li> errorData.source  : the issuing source element which triggered the request </li>
-         *     <li> eventData.responseCode: the response code (aka http request response code, 401 etc...) </li>
-         *     <li> eventData.responseText: the request response text </li>
-         *     <li> eventData.responseXML: the request response xml </li>
-        * </ul>
-         *
-         * @param {function} errorListener error handler must be of the format <i>function errorListener(&lt;errorData&gt;)</i>
-		*/
-        this.addOnError = function(/*function*/errorListener) {
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.addOnError(errorListener);
-        };
-
-        /**
-         * Adds a global event listener to the ajax event queue. The event listener must be a function
-         * of following format: <i>function eventListener(&lt;eventData&gt;)</i>
-         *
-         * @param {function} eventListener event must be of the format <i>function eventListener(&lt;eventData&gt;)</i>
-         */
-        this.addOnEvent = function(/*function*/eventListener) {
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.addOnEvent(eventListener);
-        };
-
-        /**
-         * processes the ajax response if the ajax request completes successfully
-         * @param request the ajax request!
-         * @param context the ajax context!
-         */
-        this.response = function(/*xhr request object*/request, context) {
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.response(request, context);
-        };
-    };
-}
-
-if (!faces.util) {
-    /**
-    * @namespace faces.util
-    */
-    faces.util = new function() {
-
-        /**
-         * varargs function which executes a chain of code (functions or any other code)
-         *
-         * if any of the code returns false, the execution
-         * is terminated prematurely skipping the rest of the code!
-         *
-         * @param {DomNode} source, the callee object
-         * @param {Event} event, the event object of the callee event triggering this function
-         * @param {optional} functions to be chained, if any of those return false the chain is broken
-         */
-        this.chain = function(source, event) {
-            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
-            return impl.chain.apply(impl, arguments);
-        };
-    };
-}
-
-if (!faces.push) {
-
-  /**
-   * @namespace faces.push
-   */
-  faces.push = new function() {
-
-    // "Constant" fields ----------------------------------------------------------------------------------------------
-    var URL_PROTOCOL = window.location.protocol.replace("http", "ws") + "//";
-    var RECONNECT_INTERVAL = 500;
-    var MAX_RECONNECT_ATTEMPTS = 25;
-    var REASON_EXPIRED = "Expired";
-
-    // Private static fields ------------------------------------------------------------------------------------------
-
-    /* socket map by token */
-    var sockets = {};
-    /* component attributes by clientId */
-    var components = {};
-    /* client ids by token (share websocket connection) */
-    var clientIdsByTokens = {};
-    var self = {};
-
-    // Private constructor functions ----------------------------------------------------------------------------------
-    /**
-     * Creates a reconnecting web socket. When the web socket successfully connects on first attempt, then it will
-     * automatically reconnect on timeout with cumulative intervals of 500ms with a maximum of 25 attempts (~3 minutes).
-     * The <code>onclose</code> function will be called with the error code of the last attempt.
-     * @constructor
-     * @param {string} channelToken the channel token associated with this websocket connection
-     * @param {string} url The URL of the web socket
-     * @param {string} channel The name of the web socket channel.
-     */
-    function Socket(channelToken, url, channel) {
-
-        // Private fields -----------------------------------------------------------------------------------------
-
-        var socket;
-        var reconnectAttempts = 0;
-        var self = this;
-
-        // Public functions ---------------------------------------------------------------------------------------
-
-        /**
-         * Opens the reconnecting web socket.
-         */
-        self.open = function() {
-            if (socket && socket.readyState == 1) {
-                return;
-            }
-
-            socket = new WebSocket(url);
-
-            socket.onopen = function(event) {
-                if (!reconnectAttempts) {
-                    var clientIds = clientIdsByTokens[channelToken];
-                    for (var i = clientIds.length - 1; i >= 0; i--){
-                        var socketClientId = clientIds[i];
-                        components[socketClientId]['onopen'](channel);
-                    }
-                }
-                reconnectAttempts = 0;
-            };
-
-            socket.onmessage = function(event) {
-                var message = JSON.parse(event.data);
-                for (var i = clientIdsByTokens[channelToken].length - 1; i >= 0; i--){
-                    var socketClientId = clientIdsByTokens[channelToken][i];
-                    if(document.getElementById(socketClientId)) {
-                        try{
-                            components[socketClientId]['onmessage'](message, channel, event);
-                        }catch(e){
-                            //Ignore
-                        }
-                        var behaviors = components[socketClientId]['behaviors'];
-                        var functions = behaviors[message];
-                        if (functions && functions.length) {
-                            for (var j = 0; j < functions.length; j++) {
-                                try{
-                                    functions[j](null);
-                                }catch(e){
-                                    //Ignore
-                                }
-                            }
-                        }
-                    } else {
-                        clientIdsByTokens[channelToken].splice(i,1);
-                    }
-                }
-                if (clientIdsByTokens[channelToken].length == 0){
-                    //tag dissapeared
-                    self.close();
-                }
-
-            };
-
-            socket.onclose = function(event) {
-                if (!socket
-                    || (event.code == 1000 && event.reason == REASON_EXPIRED)
-                    || (event.code == 1008)
-                    || (!reconnectAttempts)
-                    || (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS))
-                {
-                    var clientIds = clientIdsByTokens[channelToken];
-                    for (var i = clientIds.length - 1; i >= 0; i--){
-                        var socketClientId = clientIds[i];
-                        components[socketClientId]['onclose'](event.code, channel, event);
-                    }
-                }
-                else {
-                    setTimeout(self.open, RECONNECT_INTERVAL * reconnectAttempts++);
-                }
-            };
-        };
-
-        /**
-         * Closes the reconnecting web socket.
-         */
-        self.close = function() {
-            if (socket) {
-                var s = socket;
-                socket = null;
-                s.close();
-            }
-        };
-
-    }
-
-    // Public static functions ----------------------------------------------------------------------------------------
-
-    /**
-     *
-     * @param {function} onopen The function to be invoked when the web socket is opened.
-     * @param {function} onmessage The function to be invoked when a message is received.
-     * @param {function} onclose The function to be invoked when the web socket is closed.
-     * @param {boolean} autoconnect Whether or not to immediately open the socket. Defaults to <code>false</code>.
-     */
-    this.init = function(socketClientId, uri, channel, onopen, onmessage, onclose, behaviorScripts, autoconnect) {
-
-        onclose = resolveFunction(onclose);
-
-        if (!window.WebSocket) { // IE6-9.
-            onclose(-1, channel);
-            return;
-        }
-
-        var channelToken = uri.substr(uri.indexOf('?')+1);
-
-        if (!components[socketClientId]) {
-            components[socketClientId] = {
-                'channelToken': channelToken,
-                'onopen': resolveFunction(onopen),
-                'onmessage' : resolveFunction(onmessage),
-                'onclose': onclose,
-                'behaviors': behaviorScripts,
-                'autoconnect': autoconnect};
-            if (!clientIdsByTokens[channelToken]) {
-                clientIdsByTokens[channelToken] = [];
-            }
-            clientIdsByTokens[channelToken].push(socketClientId);
-            if (!sockets[channelToken]){
-                sockets[channelToken] = new Socket(channelToken,
-                                    getBaseURL(uri), channel);
-            }
-        }
-
-        if (autoconnect) {
-            this.open(socketClientId);
-        }
-    };
-
-    /**
-     * Open the web socket on the given channel.
-     * @param {string} channel The name of the web socket channel.
-     * @throws {Error} When channel is unknown.
-     */
-    this.open = function(socketClientId) {
-        getSocket(components[socketClientId]['channelToken']).open();
-    };
-
-    /**
-     * Close the web socket on the given channel.
-     * @param {string} channel The name of the web socket channel.
-     * @throws {Error} When channel is unknown.
-     */
-    this.close = function(socketClientId) {
-        getSocket(components[socketClientId]['channelToken']).close();
-    };
-
-    // Private static functions ---------------------------------------------------------------------------------------
-
-    /**
-     *
-     */
-    function getBaseURL(url) {
-        if (url.indexOf("://") < 0)
-        {
-            var base = window.location.hostname+":"+window.location.port
-            return URL_PROTOCOL + base + url;
-        }else
-        {
-            return url;
-        }
-    }
-
-    /**
-     * Get socket associated with given channelToken.
-     * @param {string} channelToken The name of the web socket channelToken.
-     * @return {Socket} Socket associated with given channelToken.
-     * @throws {Error} When channelToken is unknown, you may need to initialize
-     *                 it first via <code>init()</code> function.
-     */
-    function getSocket(channelToken) {
-        var socket = sockets[channelToken];
-        if (socket) {
-            return socket;
-        } else {
-            throw new Error("Unknown channelToken: " + channelToken);
-        }
-    }
-
-    function resolveFunction(fn) {
-        return (typeof fn !== "function") && (fn = window[fn] || function(){}), fn;
-    }
-    // Expose self to public ------------------------------------------------------------------------------------------
-
-    //return self;
-    };
-}
-
-
-(!window.myfaces) ? window.myfaces = {} : null;
-if (!myfaces.ab) {
-    /*
-     * Shortcut of the faces.ajax.request, to shorten the rendered JS.
-     */
-    myfaces.ab = function(source, event, eventName, execute, render, options) {
-        if (!options) {
-            options = {};
-        }
-
-        if (eventName) {
-            options["jakarta.faces.behavior.event"] = eventName;
-        }
-        if (execute) {
-            options["execute"] = execute;
-        }
-        if (render) {
-            options["render"] = render;
-        }
-
-        faces.ajax.request(source, event, options);
-    };
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+/**
+ *  MyFaces core javascripting libraries
+ *
+ *  Those are the central public API functions in the Faces2
+ *  Ajax API! They handle the entire form submit and ajax send
+ *  and resolve cycle!
+ */
+
+/**
+* @ignore
+*/
+if (!window.faces) {
+    /**
+    * @namespace faces
+    */
+    var faces = new function() {
+        /*
+         * Version of the implementation for the faces.js.
+         * <p />
+         * as specified within the faces specifications:
+         * <ul>
+         * <li>left two digits major release number</li>
+         * <li>middle two digits minor spec release number</li>
+         * <li>right two digits bug release number</li>
+         * </ul>
+         * @constant
+         */
+        this.specversion = 220000;
+        /**
+         * Implementation version as specified within the faces specification.
+         * <p />
+         * A number increased with every implementation version
+         * and reset by moving to a new spec release number
+         *
+		 * @constant
+         */
+        this.implversion = 0;
+
+        /**
+         * SeparatorChar as defined by UINamingContainer.getNamingContainerSeparatorChar()
+         * @type {Char}
+         */
+        this.separatorchar = getSeparatorChar();
+
+        /**
+         * This method is responsible for the return of a given project stage as defined
+         * by the faces specification.
+         * <p/>
+         * Valid return values are:
+         * <ul>
+         *     <li>&quot;Production&quot;</li>
+         *     <li>&quot;Development&quot;</li>
+         *     <li>&quot;SystemTest&quot;</li>
+         *     <li>&quot;UnitTest&quot;</li>
+         * </li>
+         *
+         * @return {String} the current project state emitted by the server side method:
+         * <i>jakarta.faces.application.Application.getProjectStage()</i>
+         */
+        this.getProjectStage = function() {
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.getProjectStage();
+        };
+
+        /**
+         * collect and encode data for a given form element (must be of type form)
+         * find the jakarta.faces.ViewState element and encode its value as well!
+         * return a concatenated string of the encoded values!
+         *
+         * @throws an exception in case of the given element not being of type form!
+         * https://issues.apache.org/jira/browse/MYFACES-2110
+         */
+        this.getViewState = function(formElement) {
+            /*we are not allowed to add the impl on a global scope so we have to inline the code*/
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.getViewState(formElement);
+        };
+
+        /**
+         * returns the window identifier for the given node / window
+         * @param {optional String | DomNode}  the node for which the client identifier has to be determined
+         * @return the window identifier or null if none is found
+         */
+        this.getClientWindow = function() {
+            /*we are not allowed to add the impl on a global scope so we have to inline the code*/
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return (arguments.length)? impl.getClientWindow(arguments[0]) : impl.getClientWindow();
+        }
+
+        //private helper functions
+        function getSeparatorChar() {
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.getSeparatorChar();
+        }
+
+    };
+
+    //jsdoc helper to avoid warnings, we map later
+    window.faces = faces;
+}
+
+/**
+ * just to make sure no questions arise, I simply prefer here a weak
+ * typeless comparison just in case some frameworks try to interfere
+ * by overriding null or fiddeling around with undefined or typeof in some ways
+ * it is safer in this case than the standard way of doing a strong comparison
+ **/
+if (!faces.ajax) {
+    /**
+    * @namespace faces.ajax
+    */
+    faces.ajax = new function() {
+
+
+        /**
+         * this function has to send the ajax requests
+         *
+         * following request conditions must be met:
+         * <ul>
+         *  <li> the request must be sent asynchronously! </li>
+         *  <li> the request must be a POST!!! request </li>
+         *  <li> the request url must be the form action attribute </li>
+         *  <li> all requests must be queued with a client side request queue to ensure the request ordering!</li>
+         * </ul>
+         *
+         * @param {String|Node} element: any dom element no matter being it html or faces, from which the event is emitted
+         * @param {EVENT} event: any javascript event supported by that object
+         * @param {Map} options : map of options being pushed into the ajax cycle
+         */
+        this.request = function(element, event, options) {
+            if (!options) {
+                options = {};
+            }
+            /*we are not allowed to add the impl on a global scope so we have to inline the code*/
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.request(element, event, options);
+        };
+
+        /**
+         * Adds an error handler to our global error queue.
+         * the error handler must be of the format <i>function errorListener(&lt;errorData&gt;)</i>
+         * with errorData being of following format:
+         * <ul>
+         *     <li> errorData.type : &quot;error&quot;</li>
+         *     <li> errorData.status : the error status message</li>
+         *     <li> errorData.errorName : the server error name in case of a server error</li>
+         *     <li> errorData.errorMessage : the server error message in case of a server error</li>
+         *     <li> errorData.source  : the issuing source element which triggered the request </li>
+         *     <li> eventData.responseCode: the response code (aka http request response code, 401 etc...) </li>
+         *     <li> eventData.responseText: the request response text </li>
+         *     <li> eventData.responseXML: the request response xml </li>
+        * </ul>
+         *
+         * @param {function} errorListener error handler must be of the format <i>function errorListener(&lt;errorData&gt;)</i>
+		*/
+        this.addOnError = function(/*function*/errorListener) {
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.addOnError(errorListener);
+        };
+
+        /**
+         * Adds a global event listener to the ajax event queue. The event listener must be a function
+         * of following format: <i>function eventListener(&lt;eventData&gt;)</i>
+         *
+         * @param {function} eventListener event must be of the format <i>function eventListener(&lt;eventData&gt;)</i>
+         */
+        this.addOnEvent = function(/*function*/eventListener) {
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.addOnEvent(eventListener);
+        };
+
+        /**
+         * processes the ajax response if the ajax request completes successfully
+         * @param request the ajax request!
+         * @param context the ajax context!
+         */
+        this.response = function(/*xhr request object*/request, context) {
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.response(request, context);
+        };
+    };
+}
+
+if (!faces.util) {
+    /**
+    * @namespace faces.util
+    */
+    faces.util = new function() {
+
+        /**
+         * varargs function which executes a chain of code (functions or any other code)
+         *
+         * if any of the code returns false, the execution
+         * is terminated prematurely skipping the rest of the code!
+         *
+         * @param {DomNode} source, the callee object
+         * @param {Event} event, the event object of the callee event triggering this function
+         * @param {optional} functions to be chained, if any of those return false the chain is broken
+         */
+        this.chain = function(source, event) {
+            var impl = myfaces._impl.core._Runtime.getGlobalConfig("facesAjaxImpl", myfaces._impl.core.Impl);
+            return impl.chain.apply(impl, arguments);
+        };
+    };
+}
+
+if (!faces.push) {
+
+  /**
+   * @namespace faces.push
+   */
+  faces.push = new function() {
+
+    // "Constant" fields ----------------------------------------------------------------------------------------------
+    var URL_PROTOCOL = window.location.protocol.replace("http", "ws") + "//";
+    var RECONNECT_INTERVAL = 500;
+    var MAX_RECONNECT_ATTEMPTS = 25;
+    var REASON_EXPIRED = "Expired";
+
+    // Private static fields ------------------------------------------------------------------------------------------
+
+    /* socket map by token */
+    var sockets = {};
+    /* component attributes by clientId */
+    var components = {};
+    /* client ids by token (share websocket connection) */
+    var clientIdsByTokens = {};
+    var self = {};
+
+    // Private constructor functions ----------------------------------------------------------------------------------
+    /**
+     * Creates a reconnecting web socket. When the web socket successfully connects on first attempt, then it will
+     * automatically reconnect on timeout with cumulative intervals of 500ms with a maximum of 25 attempts (~3 minutes).
+     * The <code>onclose</code> function will be called with the error code of the last attempt.
+     * @constructor
+     * @param {string} channelToken the channel token associated with this websocket connection
+     * @param {string} url The URL of the web socket
+     * @param {string} channel The name of the web socket channel.
+     */
+    function Socket(channelToken, url, channel) {
+
+        // Private fields -----------------------------------------------------------------------------------------
+
+        var socket;
+        var reconnectAttempts = 0;
+        var self = this;
+
+        // Public functions ---------------------------------------------------------------------------------------
+
+        /**
+         * Opens the reconnecting web socket.
+         */
+        self.open = function() {
+            if (socket && socket.readyState == 1) {
+                return;
+            }
+
+            socket = new WebSocket(url);
+
+            socket.onopen = function(event) {
+                if (!reconnectAttempts) {
+                    var clientIds = clientIdsByTokens[channelToken];
+                    for (var i = clientIds.length - 1; i >= 0; i--){
+                        var socketClientId = clientIds[i];
+                        components[socketClientId]['onopen'](channel);
+                    }
+                }
+                reconnectAttempts = 0;
+            };
+
+            socket.onmessage = function(event) {
+                var message = JSON.parse(event.data);
+                for (var i = clientIdsByTokens[channelToken].length - 1; i >= 0; i--){
+                    var socketClientId = clientIdsByTokens[channelToken][i];
+                    if(document.getElementById(socketClientId)) {
+                        try{
+                            components[socketClientId]['onmessage'](message, channel, event);
+                        }catch(e){
+                            //Ignore
+                        }
+                        var behaviors = components[socketClientId]['behaviors'];
+                        var functions = behaviors[message];
+                        if (functions && functions.length) {
+                            for (var j = 0; j < functions.length; j++) {
+                                try{
+                                    functions[j](null);
+                                }catch(e){
+                                    //Ignore
+                                }
+                            }
+                        }
+                    } else {
+                        clientIdsByTokens[channelToken].splice(i,1);
+                    }
+                }
+                if (clientIdsByTokens[channelToken].length == 0){
+                    //tag dissapeared
+                    self.close();
+                }
+
+            };
+
+            socket.onclose = function(event) {
+                if (!socket
+                    || (event.code == 1000 && event.reason == REASON_EXPIRED)
+                    || (event.code == 1008)
+                    || (!reconnectAttempts)
+                    || (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS))
+                {
+                    var clientIds = clientIdsByTokens[channelToken];
+                    for (var i = clientIds.length - 1; i >= 0; i--){
+                        var socketClientId = clientIds[i];
+                        components[socketClientId]['onclose'](event.code, channel, event);
+                    }
+                }
+                else {
+                    setTimeout(self.open, RECONNECT_INTERVAL * reconnectAttempts++);
+                }
+            };
+        };
+
+        socket.onerror = function(event) {
+            var clientIds = clientIdsByTokens[channelToken];
+            for (var i = clientIds.length - 1; i >= 0; i--){
+                var socketClientId = clientIds[i];
+                components[socketClientId]['onerror'](channel);
+            }
+        };
+
+        /**
+         * Closes the reconnecting web socket.
+         */
+        self.close = function() {
+            if (socket) {
+                var s = socket;
+                socket = null;
+                s.close();
+            }
+        };
+
+    }
+
+    // Public static functions ----------------------------------------------------------------------------------------
+
+    /**
+     *
+     * @param {function} onopen The function to be invoked when the web socket is opened.
+     * @param {function} onmessage The function to be invoked when a message is received.
+     * @param {function} onerror The function to be invoked when the web socket throws a error.
+     * @param {function} onclose The function to be invoked when the web socket is closed.
+     * @param {boolean} autoconnect Whether or not to immediately open the socket. Defaults to <code>false</code>.
+     */
+    this.init = function(socketClientId, uri, channel, onopen, onmessage, onerror, onclose, behaviorScripts, autoconnect) {
+
+        onclose = resolveFunction(onclose);
+
+        if (!window.WebSocket) { // IE6-9.
+            onclose(-1, channel);
+            return;
+        }
+
+        var channelToken = uri.substr(uri.indexOf('?')+1);
+
+        if (!components[socketClientId]) {
+            components[socketClientId] = {
+                'channelToken': channelToken,
+                'onopen': resolveFunction(onopen),
+                'onmessage' : resolveFunction(onmessage),
+                'onerror' : resolveFunction(onerror),
+                'onclose': onclose,
+                'behaviors': behaviorScripts,
+                'autoconnect': autoconnect};
+            if (!clientIdsByTokens[channelToken]) {
+                clientIdsByTokens[channelToken] = [];
+            }
+            clientIdsByTokens[channelToken].push(socketClientId);
+            if (!sockets[channelToken]){
+                sockets[channelToken] = new Socket(channelToken,
+                                    getBaseURL(uri), channel);
+            }
+        }
+
+        if (autoconnect) {
+            this.open(socketClientId);
+        }
+    };
+
+    /**
+     * Open the web socket on the given channel.
+     * @param {string} channel The name of the web socket channel.
+     * @throws {Error} When channel is unknown.
+     */
+    this.open = function(socketClientId) {
+        getSocket(components[socketClientId]['channelToken']).open();
+    };
+
+    /**
+     * Close the web socket on the given channel.
+     * @param {string} channel The name of the web socket channel.
+     * @throws {Error} When channel is unknown.
+     */
+    this.close = function(socketClientId) {
+        getSocket(components[socketClientId]['channelToken']).close();
+    };
+
+    // Private static functions ---------------------------------------------------------------------------------------
+
+    /**
+     *
+     */
+    function getBaseURL(url) {
+        if (url.indexOf("://") < 0)
+        {
+            var base = window.location.hostname+":"+window.location.port
+            return URL_PROTOCOL + base + url;
+        }else
+        {
+            return url;
+        }
+    }
+
+    /**
+     * Get socket associated with given channelToken.
+     * @param {string} channelToken The name of the web socket channelToken.
+     * @return {Socket} Socket associated with given channelToken.
+     * @throws {Error} When channelToken is unknown, you may need to initialize
+     *                 it first via <code>init()</code> function.
+     */
+    function getSocket(channelToken) {
+        var socket = sockets[channelToken];
+        if (socket) {
+            return socket;
+        } else {
+            throw new Error("Unknown channelToken: " + channelToken);
+        }
+    }
+
+    function resolveFunction(fn) {
+        return (typeof fn !== "function") && (fn = window[fn] || function(){}), fn;
+    }
+    // Expose self to public ------------------------------------------------------------------------------------------
+
+    //return self;
+    };
+}
+
+
+(!window.myfaces) ? window.myfaces = {} : null;
+if (!myfaces.ab) {
+    /*
+     * Shortcut of the faces.ajax.request, to shorten the rendered JS.
+     */
+    myfaces.ab = function(source, event, eventName, execute, render, options) {
+        if (!options) {
+            options = {};
+        }
+
+        if (eventName) {
+            options["jakarta.faces.behavior.event"] = eventName;
+        }
+        if (execute) {
+            options["execute"] = execute;
+        }
+        if (render) {
+            options["render"] = render;
+        }
+
+        faces.ajax.request(source, event, options);
+    };
 }
\ No newline at end of file
diff --git a/impl/src/main/java/org/apache/myfaces/push/WebsocketComponentRenderer.java b/impl/src/main/java/org/apache/myfaces/push/WebsocketComponentRenderer.java
index 70a4d08..4bf300c 100644
--- a/impl/src/main/java/org/apache/myfaces/push/WebsocketComponentRenderer.java
+++ b/impl/src/main/java/org/apache/myfaces/push/WebsocketComponentRenderer.java
@@ -1,271 +1,273 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.myfaces.push;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import jakarta.enterprise.inject.spi.BeanManager;
-import jakarta.faces.FacesWrapper;
-import jakarta.faces.component.UIComponent;
-import jakarta.faces.component.UIWebsocket;
-import jakarta.faces.component.behavior.ClientBehavior;
-import jakarta.faces.component.behavior.ClientBehaviorContext;
-import jakarta.faces.context.FacesContext;
-import jakarta.faces.context.ResponseWriter;
-import jakarta.faces.event.ComponentSystemEvent;
-import jakarta.faces.event.ComponentSystemEventListener;
-import jakarta.faces.event.ListenerFor;
-import jakarta.faces.event.PostAddToViewEvent;
-import jakarta.faces.render.Renderer;
-import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFRenderer;
-import org.apache.myfaces.cdi.util.CDIUtils;
-import org.apache.myfaces.push.cdi.WebsocketApplicationBean;
-import org.apache.myfaces.push.cdi.WebsocketChannelMetadata;
-import org.apache.myfaces.push.cdi.WebsocketChannelTokenBuilderBean;
-import org.apache.myfaces.push.cdi.WebsocketSessionBean;
-import org.apache.myfaces.push.cdi.WebsocketViewBean;
-import org.apache.myfaces.renderkit.html.util.ClientBehaviorRendererUtils;
-import org.apache.myfaces.renderkit.html.util.HTML;
-import org.apache.myfaces.renderkit.html.util.HtmlRendererUtils;
-import org.apache.myfaces.renderkit.html.util.ResourceUtils;
-
-@JSFRenderer(renderKitId = "HTML_BASIC",
-        family = "jakarta.faces.Script",
-        type = "jakarta.faces.Websocket")
-@ListenerFor(systemEventClass = PostAddToViewEvent.class)
-public class WebsocketComponentRenderer extends Renderer implements ComponentSystemEventListener
-{
-
-    @Override
-    public void processEvent(ComponentSystemEvent event)
-    {
-        if (event instanceof PostAddToViewEvent)
-        {
-            FacesContext facesContext = FacesContext.getCurrentInstance();
-            UIWebsocket component = (UIWebsocket) event.getComponent();
-            WebsocketInit initComponent = (WebsocketInit) facesContext.getViewRoot().findComponent(
-                    (String) component.getAttributes().get(_WebsocketInit.ATTRIBUTE_COMPONENT_ID));
-            if (initComponent == null)
-            {
-                initComponent = (WebsocketInit) facesContext.getApplication().createComponent(facesContext,
-                        WebsocketInit.COMPONENT_TYPE, WebsocketInit.COMPONENT_TYPE);
-                initComponent.setId((String) component.getAttributes().get(_WebsocketInit.ATTRIBUTE_COMPONENT_ID));
-                facesContext.getViewRoot().addComponentResource(facesContext,
-                        initComponent, "body");
-            }
-        }
-    }
-
-    private HtmlBufferResponseWriterWrapper getResponseWriter(FacesContext context)
-    {
-        return HtmlBufferResponseWriterWrapper.getInstance(context.getResponseWriter());
-    }
-
-    @Override
-    public void decode(FacesContext facesContext, UIComponent component)
-    {
-        ClientBehaviorRendererUtils.decodeClientBehaviors(facesContext, component);
-    }
-
-    @Override
-    public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException
-    {
-        ResponseWriter writer = facesContext.getResponseWriter();
-
-        ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, writer);
-        
-        // Render the tag that will be embedded into the DOM tree that helps to detect if the message
-        // must be processed or not and if the connection must be closed.
-        writer.startElement(HTML.DIV_ELEM, component);
-        writer.writeAttribute(HTML.ID_ATTR, component.getClientId() ,null);
-        writer.writeAttribute(HTML.STYLE_ATTR, "display:none", null);
-        writer.endElement(HTML.DIV_ELEM);
-        
-        if (!facesContext.getPartialViewContext().isAjaxRequest())
-        {
-            facesContext.setResponseWriter(getResponseWriter(facesContext));
-        }
-    }
-
-    @Override
-    public void encodeEnd(FacesContext facesContext, UIComponent c) throws IOException
-    {
-        super.encodeEnd(facesContext, c); //check for NP
-
-        UIWebsocket component = (UIWebsocket) c;
-
-        WebsocketInit init = (WebsocketInit) facesContext.getViewRoot().findComponent(
-                (String) component.getAttributes().get(_WebsocketInit.ATTRIBUTE_COMPONENT_ID));
-
-        ResponseWriter writer = facesContext.getResponseWriter();
-
-        String channel = component.getChannel();
-
-        // TODO: use a single bean and entry point for this algorithm.
-        BeanManager beanManager = CDIUtils.getBeanManager(facesContext);
-
-        WebsocketChannelTokenBuilderBean channelTokenBean = CDIUtils.get(
-                beanManager,
-                WebsocketChannelTokenBuilderBean.class);
-
-        // This bean is required because you always need to register the token, so it can be properly destroyed
-        WebsocketViewBean viewTokenBean = CDIUtils.get(
-                beanManager,
-                WebsocketViewBean.class);
-        WebsocketSessionBean sessionTokenBean = CDIUtils.get(
-                beanManager, WebsocketSessionBean.class);
-
-        // Create channel token 
-        // TODO: Use ResponseStateManager to create the token
-        String scope = component.getScope() == null ? "application" : component.getScope();
-        WebsocketChannelMetadata metadata = new WebsocketChannelMetadata(
-                channel, scope, component.getUser(), component.isConnected());
-
-        String channelToken = null;
-        // Force a new channelToken if "connected" property is set to false, because in that case websocket
-        // creation 
-        if (!component.isConnected())
-        {
-            channelToken = viewTokenBean.getChannelToken(metadata);
-        }
-        if (channelToken == null)
-        {
-            // No channel token found for that combination, create a new token for this view
-            channelToken = channelTokenBean.createChannelToken(facesContext, channel);
-            
-            // Register channel in view scope to chain discard view algorithm using @PreDestroy
-            viewTokenBean.registerToken(channelToken, metadata);
-            
-            // Register channel in session scope to allow validation on handshake ( WebsocketConfigurator )
-            sessionTokenBean.registerToken(channelToken, metadata);
-        }
-
-        // Ask these two scopes 
-        WebsocketApplicationBean appTokenBean = CDIUtils.get(
-                beanManager, WebsocketApplicationBean.class, false);
-
-        // Register token and metadata in the proper bean
-        if (scope.equals("view"))
-        {
-            viewTokenBean.registerWebsocketSession(channelToken, metadata);
-        }
-        else if (scope.equals("session"))
-        {
-            sessionTokenBean = (sessionTokenBean != null) ? sessionTokenBean : CDIUtils.get(
-                    CDIUtils.getBeanManager(facesContext),
-                    WebsocketSessionBean.class);
-
-            sessionTokenBean.registerWebsocketSession(channelToken, metadata);
-        }
-        else
-        {
-            //Default application
-            appTokenBean = (appTokenBean != null) ? appTokenBean : CDIUtils.get(
-                    CDIUtils.getBeanManager(facesContext),
-                    WebsocketApplicationBean.class);
-
-            appTokenBean.registerWebsocketSession(channelToken, metadata);
-        }
-        writer.startElement(HTML.SCRIPT_ELEM, component);
-        HtmlRendererUtils.renderScriptType(facesContext, writer);
-
-        StringBuilder sb = new StringBuilder(50);
-        sb.append("faces.push.init(");
-        sb.append('\'');
-        sb.append(component.getClientId());
-        sb.append('\'');
-        sb.append(',');
-        sb.append('\'');
-        sb.append(facesContext.getExternalContext().encodeWebsocketURL(
-                facesContext.getApplication().getViewHandler().getWebsocketURL(
-                        facesContext, component.getChannel()+ '?' +channelToken)));
-        sb.append('\'');
-        sb.append(',');
-        sb.append('\'');
-        sb.append(component.getChannel());
-        sb.append('\'');
-        sb.append(',');
-        sb.append(component.getOnopen());
-        sb.append(',');
-        sb.append(component.getOnmessage());
-        sb.append(',');
-        sb.append(component.getOnclose());
-        sb.append(',');
-        sb.append(getBehaviorScripts(facesContext, component));
-        sb.append(',');
-        sb.append(component.isConnected());
-        sb.append(");");
-
-        writer.write(sb.toString());
-
-        writer.endElement(HTML.SCRIPT_ELEM);
-        
-        if (!facesContext.getPartialViewContext().isAjaxRequest())
-        {
-            ResponseWriter responseWriter = facesContext.getResponseWriter();
-            while (!(responseWriter instanceof HtmlBufferResponseWriterWrapper)
-                    && responseWriter instanceof FacesWrapper)
-            {
-                responseWriter = (ResponseWriter) ((FacesWrapper) responseWriter).getWrapped();
-            }
-            
-            HtmlBufferResponseWriterWrapper htmlBufferResponseWritter =
-                    (HtmlBufferResponseWriterWrapper) responseWriter;
-            init.getUIWebsocketMarkupList().add(htmlBufferResponseWritter.toString());
-
-            facesContext.setResponseWriter(htmlBufferResponseWritter.getInitialWriter());
-        }
-    }
-
-    private String getBehaviorScripts(FacesContext facesContext, UIWebsocket component)
-    {
-        Map<String, List<ClientBehavior>> clientBehaviorsByEvent = component.getClientBehaviors();
-
-        if (clientBehaviorsByEvent.isEmpty())
-        {
-            return "{}";
-        }
-
-        String clientId = component.getClientId(facesContext);
-        StringBuilder scripts = new StringBuilder("{");
-
-        for (Entry<String, List<ClientBehavior>> entry : clientBehaviorsByEvent.entrySet())
-        {
-            String event = entry.getKey();
-            List<ClientBehavior> clientBehaviors = entry.getValue();
-            scripts.append(scripts.length() > 1 ? ',' : "").append(event).append(":[");
-
-            for (int i = 0; i < clientBehaviors.size(); i++)
-            {
-                scripts.append(i > 0 ? ',' : "").append("function(event){");
-                scripts.append(clientBehaviors.get(i).getScript(
-                        ClientBehaviorContext.createClientBehaviorContext(
-                                facesContext, component, event, clientId, null)));
-                scripts.append('}');
-            }
-
-            scripts.append(']');
-        }
-
-        return scripts.append('}').toString();
-    }
-        
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.push;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.faces.FacesWrapper;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIWebsocket;
+import jakarta.faces.component.behavior.ClientBehavior;
+import jakarta.faces.component.behavior.ClientBehaviorContext;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.context.ResponseWriter;
+import jakarta.faces.event.ComponentSystemEvent;
+import jakarta.faces.event.ComponentSystemEventListener;
+import jakarta.faces.event.ListenerFor;
+import jakarta.faces.event.PostAddToViewEvent;
+import jakarta.faces.render.Renderer;
+import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFRenderer;
+import org.apache.myfaces.cdi.util.CDIUtils;
+import org.apache.myfaces.push.cdi.WebsocketApplicationBean;
+import org.apache.myfaces.push.cdi.WebsocketChannelMetadata;
+import org.apache.myfaces.push.cdi.WebsocketChannelTokenBuilderBean;
+import org.apache.myfaces.push.cdi.WebsocketSessionBean;
+import org.apache.myfaces.push.cdi.WebsocketViewBean;
+import org.apache.myfaces.renderkit.html.util.ClientBehaviorRendererUtils;
+import org.apache.myfaces.renderkit.html.util.HTML;
+import org.apache.myfaces.renderkit.html.util.HtmlRendererUtils;
+import org.apache.myfaces.renderkit.html.util.ResourceUtils;
+
+@JSFRenderer(renderKitId = "HTML_BASIC",
+        family = "jakarta.faces.Script",
+        type = "jakarta.faces.Websocket")
+@ListenerFor(systemEventClass = PostAddToViewEvent.class)
+public class WebsocketComponentRenderer extends Renderer implements ComponentSystemEventListener
+{
+
+    @Override
+    public void processEvent(ComponentSystemEvent event)
+    {
+        if (event instanceof PostAddToViewEvent)
+        {
+            FacesContext facesContext = FacesContext.getCurrentInstance();
+            UIWebsocket component = (UIWebsocket) event.getComponent();
+            WebsocketInit initComponent = (WebsocketInit) facesContext.getViewRoot().findComponent(
+                    (String) component.getAttributes().get(_WebsocketInit.ATTRIBUTE_COMPONENT_ID));
+            if (initComponent == null)
+            {
+                initComponent = (WebsocketInit) facesContext.getApplication().createComponent(facesContext,
+                        WebsocketInit.COMPONENT_TYPE, WebsocketInit.COMPONENT_TYPE);
+                initComponent.setId((String) component.getAttributes().get(_WebsocketInit.ATTRIBUTE_COMPONENT_ID));
+                facesContext.getViewRoot().addComponentResource(facesContext,
+                        initComponent, "body");
+            }
+        }
+    }
+
+    private HtmlBufferResponseWriterWrapper getResponseWriter(FacesContext context)
+    {
+        return HtmlBufferResponseWriterWrapper.getInstance(context.getResponseWriter());
+    }
+
+    @Override
+    public void decode(FacesContext facesContext, UIComponent component)
+    {
+        ClientBehaviorRendererUtils.decodeClientBehaviors(facesContext, component);
+    }
+
+    @Override
+    public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException
+    {
+        ResponseWriter writer = facesContext.getResponseWriter();
+
+        ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, writer);
+        
+        // Render the tag that will be embedded into the DOM tree that helps to detect if the message
+        // must be processed or not and if the connection must be closed.
+        writer.startElement(HTML.DIV_ELEM, component);
+        writer.writeAttribute(HTML.ID_ATTR, component.getClientId() ,null);
+        writer.writeAttribute(HTML.STYLE_ATTR, "display:none", null);
+        writer.endElement(HTML.DIV_ELEM);
+        
+        if (!facesContext.getPartialViewContext().isAjaxRequest())
+        {
+            facesContext.setResponseWriter(getResponseWriter(facesContext));
+        }
+    }
+
+    @Override
+    public void encodeEnd(FacesContext facesContext, UIComponent c) throws IOException
+    {
+        super.encodeEnd(facesContext, c); //check for NP
+
+        UIWebsocket component = (UIWebsocket) c;
+
+        WebsocketInit init = (WebsocketInit) facesContext.getViewRoot().findComponent(
+                (String) component.getAttributes().get(_WebsocketInit.ATTRIBUTE_COMPONENT_ID));
+
+        ResponseWriter writer = facesContext.getResponseWriter();
+
+        String channel = component.getChannel();
+
+        // TODO: use a single bean and entry point for this algorithm.
+        BeanManager beanManager = CDIUtils.getBeanManager(facesContext);
+
+        WebsocketChannelTokenBuilderBean channelTokenBean = CDIUtils.get(
+                beanManager,
+                WebsocketChannelTokenBuilderBean.class);
+
+        // This bean is required because you always need to register the token, so it can be properly destroyed
+        WebsocketViewBean viewTokenBean = CDIUtils.get(
+                beanManager,
+                WebsocketViewBean.class);
+        WebsocketSessionBean sessionTokenBean = CDIUtils.get(
+                beanManager, WebsocketSessionBean.class);
+
+        // Create channel token 
+        // TODO: Use ResponseStateManager to create the token
+        String scope = component.getScope() == null ? "application" : component.getScope();
+        WebsocketChannelMetadata metadata = new WebsocketChannelMetadata(
+                channel, scope, component.getUser(), component.isConnected());
+
+        String channelToken = null;
+        // Force a new channelToken if "connected" property is set to false, because in that case websocket
+        // creation 
+        if (!component.isConnected())
+        {
+            channelToken = viewTokenBean.getChannelToken(metadata);
+        }
+        if (channelToken == null)
+        {
+            // No channel token found for that combination, create a new token for this view
+            channelToken = channelTokenBean.createChannelToken(facesContext, channel);
+            
+            // Register channel in view scope to chain discard view algorithm using @PreDestroy
+            viewTokenBean.registerToken(channelToken, metadata);
+            
+            // Register channel in session scope to allow validation on handshake ( WebsocketConfigurator )
+            sessionTokenBean.registerToken(channelToken, metadata);
+        }
+
+        // Ask these two scopes 
+        WebsocketApplicationBean appTokenBean = CDIUtils.get(
+                beanManager, WebsocketApplicationBean.class, false);
+
+        // Register token and metadata in the proper bean
+        if (scope.equals("view"))
+        {
+            viewTokenBean.registerWebsocketSession(channelToken, metadata);
+        }
+        else if (scope.equals("session"))
+        {
+            sessionTokenBean = (sessionTokenBean != null) ? sessionTokenBean : CDIUtils.get(
+                    CDIUtils.getBeanManager(facesContext),
+                    WebsocketSessionBean.class);
+
+            sessionTokenBean.registerWebsocketSession(channelToken, metadata);
+        }
+        else
+        {
+            //Default application
+            appTokenBean = (appTokenBean != null) ? appTokenBean : CDIUtils.get(
+                    CDIUtils.getBeanManager(facesContext),
+                    WebsocketApplicationBean.class);
+
+            appTokenBean.registerWebsocketSession(channelToken, metadata);
+        }
+        writer.startElement(HTML.SCRIPT_ELEM, component);
+        HtmlRendererUtils.renderScriptType(facesContext, writer);
+
+        StringBuilder sb = new StringBuilder(50);
+        sb.append("faces.push.init(");
+        sb.append('\'');
+        sb.append(component.getClientId());
+        sb.append('\'');
+        sb.append(',');
+        sb.append('\'');
+        sb.append(facesContext.getExternalContext().encodeWebsocketURL(
+                facesContext.getApplication().getViewHandler().getWebsocketURL(
+                        facesContext, component.getChannel()+ '?' +channelToken)));
+        sb.append('\'');
+        sb.append(',');
+        sb.append('\'');
+        sb.append(component.getChannel());
+        sb.append('\'');
+        sb.append(',');
+        sb.append(component.getOnopen());
+        sb.append(',');
+        sb.append(component.getOnmessage());
+        sb.append(',');
+        sb.append(component.getOnerror());
+        sb.append(',');
+        sb.append(component.getOnclose());
+        sb.append(',');
+        sb.append(getBehaviorScripts(facesContext, component));
+        sb.append(',');
+        sb.append(component.isConnected());
+        sb.append(");");
+
+        writer.write(sb.toString());
+
+        writer.endElement(HTML.SCRIPT_ELEM);
+        
+        if (!facesContext.getPartialViewContext().isAjaxRequest())
+        {
+            ResponseWriter responseWriter = facesContext.getResponseWriter();
+            while (!(responseWriter instanceof HtmlBufferResponseWriterWrapper)
+                    && responseWriter instanceof FacesWrapper)
+            {
+                responseWriter = (ResponseWriter) ((FacesWrapper) responseWriter).getWrapped();
+            }
+            
+            HtmlBufferResponseWriterWrapper htmlBufferResponseWritter =
+                    (HtmlBufferResponseWriterWrapper) responseWriter;
+            init.getUIWebsocketMarkupList().add(htmlBufferResponseWritter.toString());
+
+            facesContext.setResponseWriter(htmlBufferResponseWritter.getInitialWriter());
+        }
+    }
+
+    private String getBehaviorScripts(FacesContext facesContext, UIWebsocket component)
+    {
+        Map<String, List<ClientBehavior>> clientBehaviorsByEvent = component.getClientBehaviors();
+
+        if (clientBehaviorsByEvent.isEmpty())
+        {
+            return "{}";
+        }
+
+        String clientId = component.getClientId(facesContext);
+        StringBuilder scripts = new StringBuilder("{");
+
+        for (Entry<String, List<ClientBehavior>> entry : clientBehaviorsByEvent.entrySet())
+        {
+            String event = entry.getKey();
+            List<ClientBehavior> clientBehaviors = entry.getValue();
+            scripts.append(scripts.length() > 1 ? ',' : "").append(event).append(":[");
+
+            for (int i = 0; i < clientBehaviors.size(); i++)
+            {
+                scripts.append(i > 0 ? ',' : "").append("function(event){");
+                scripts.append(clientBehaviors.get(i).getScript(
+                        ClientBehaviorContext.createClientBehaviorContext(
+                                facesContext, component, event, clientId, null)));
+                scripts.append('}');
+            }
+
+            scripts.append(']');
+        }
+
+        return scripts.append('}').toString();
+    }
+        
+}