blob: efa761eccaf859f17e7af61b13921004e97c03bf [file] [log] [blame]
/*
* 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 JSF2
* Ajax API! They handle the entire form submit and ajax send
* and resolve cycle!
*/
/**
* reserve the root namespace
*/
if ('undefined' != typeof OpenAjax && ('undefined' == typeof jsf || null == typeof jsf)) {
OpenAjax.hub.registerLibrary("jsf", "www.sun.com", "1.0", null);
}
//just in case openajax has failed (testing environment)
/**
* @ignore
*/
if (!window.jsf) {
/**
* @namespace jsf
*/
var jsf = new function() {
/*
* Version of the implementation for the jsf.js.
* <p />
* as specified within the jsf specifications jsf.html:
* <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 jsf 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 jsf 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>javax.faces.application.Application.getProjectStage()</i>
*/
this.getProjectStage = function() {
var impl = myfaces._impl.core._Runtime.getGlobalConfig("jsfAjaxImpl", myfaces._impl.core.Impl);
return impl.getProjectStage();
};
/**
* collect and encode data for a given form element (must be of type form)
* find the javax.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("jsfAjaxImpl", 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("jsfAjaxImpl", 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("jsfAjaxImpl", myfaces._impl.core.Impl);
return impl.getSeparatorChar();
}
};
//jsdoc helper to avoid warnings, we map later
window.jsf = jsf;
}
/**
* 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 (!jsf.ajax) {
/**
* @namespace jsf.ajax
*/
jsf.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 jsf, 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("jsfAjaxImpl", 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("jsfAjaxImpl", 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("jsfAjaxImpl", 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("jsfAjaxImpl", myfaces._impl.core.Impl);
return impl.response(request, context);
};
}
}
if (!jsf.util) {
/**
* @namespace jsf.util
*/
jsf.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("jsfAjaxImpl", myfaces._impl.core.Impl);
return impl.chain.apply(impl, arguments);
};
}
}
if (!jsf.push) {
/**
* @namespace jsf.push
*/
jsf.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 jsf.ajax.request, to shorten the rendered JS.
*/
myfaces.ab = function(source, event, eventName, execute, render, options) {
if (!options) {
options = {};
}
if (eventName) {
options["javax.faces.behavior.event"] = eventName;
}
if (execute) {
options["execute"] = execute;
}
if (render) {
options["render"] = render;
}
jsf.ajax.request(source, event, options);
};
}