      1 /*
      2  * JBoss, Home of Professional Open Source
      3  * Copyright 2013, Red Hat, Inc. and individual contributors
      4  * by the @authors tag. See the copyright.txt in the distribution for a
      5  * full listing of individual contributors.
      6  *
      7  * This is free software; you can redistribute it and/or modify it
      8  * under the terms of the GNU Lesser General Public License as
      9  * published by the Free Software Foundation; either version 2.1 of
     10  * the License, or (at your option) any later version.
     11  *
     12  * This software is distributed in the hope that it will be useful,
     13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     15  * Lesser General Public License for more details.
     16  *
     17  * You should have received a copy of the GNU Lesser General Public
     18  * License along with this software; if not, write to the Free
     19  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
     20  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
     21  */
     22
     23 window.RichFaces = window.RichFaces || {};
     24 RichFaces.jQuery = RichFaces.jQuery || window.jQuery;
     25
     26 (function($, rf) {
     27
     28     rf.RICH_CONTAINER = "rf";
     29
     30     /**
     31      * All input elements which can hold value, which are enabled and visible.
     32      */
     33     rf.EDITABLE_INPUT_SELECTOR = ":not(:submit):not(:button):not(:image):input:visible:enabled";
     34
     35     //keys codes
     36     rf.KEYS = {
     37         BACKSPACE: 8,
     38         TAB: 9,
     39         RETURN: 13,
     40         ESC: 27,
     41         PAGEUP: 33,
     42         PAGEDOWN: 34,
     43         END: 35,
     44         HOME: 36,
     45         LEFT: 37,
     46         UP: 38,
     47         RIGHT: 39,
     48         DOWN: 40,
     49         DEL: 46
     50     };
     51
     52     if (window.jsf) {
     53         var jsfAjaxRequest = jsf.ajax.request;
     54         var jsfAjaxResponse = jsf.ajax.response;
     55     }
     56
     57     // get DOM element by id or DOM element or jQuery object
     58     rf.getDomElement = function (source) {
     59         var type = typeof source;
     60         var element;
     61         if (source == null) {
     62             element = null;
     63         } else if (type == "string") {
     64             // id
     65             element = document.getElementById(source);
     66         } else if (type == "object") {
     67             if (source.nodeType) {
     68                 // DOM element
     69                 element = source;
     70             } else
     71             if (source instanceof $) {
     72                 // jQuery object
     73                 element = source.get(0);
     74             }
     75         }
     76         return element;
     77     };
     78
     79     // get RichFaces component object by component id or DOM element or jQuery object
     80     rf.component = function (source) {
     81         var element = rf.getDomElement(source);
     82
     83         if (element) {
     84             return $(element).data("rf.widget") || (element[rf.RICH_CONTAINER] || {})["component"];
     85         }
     86     };
     87
     88     /**
     89      * jQuery selector ":editable" which selects only input elements which can be edited, are visible and enabled
     90      */
     91     $.extend($.expr[':'], {
     92         editable : function(element) {
     93             return $(element).is(rf.EDITABLE_INPUT_SELECTOR);
     94         }
     95     });
     96
     97     rf.$$ = function(componentName, element) {
     98         while (element.parentNode) {
     99             var e = element[rf.RICH_CONTAINER];
    100             if (e && e.component && e.component.name == componentName)
    101                 return e.component;
    102             else
    103                 element = element.parentNode;
    104         }
    105     };
    106     rf.findNonVisualComponents = function (source) {
    107         var element = rf.getDomElement(source);
    108
    109         if (element) {
    110             return (element[rf.RICH_CONTAINER] || {})["attachedComponents"];
    111         }
    112     };
    113
    114     // find component and call his method
    115     rf.invokeMethod = function(source, method) {
    116         var c = rf.component(source);
    117         var f;
    118         if (c && typeof (f = c[method]) == "function") {
    119             return f.apply(c, Array.prototype.slice.call(arguments, 2));
    120         }
    121     };
    122
    123     //dom cleaner
    124     rf.cleanComponent = function (source) {
    125         var component = rf.component(source);
    126         if (component && !$(source).data('rf.bridge')) {
    127             //TODO fire destroy event
    128             component.destroy();
    129             component.detach(source);
    130         }
    131         var attachedComponents = rf.findNonVisualComponents(source);
    132         if (attachedComponents) {
    133             for (var i in attachedComponents) {
    134                 if (attachedComponents[i]) {
    135                     attachedComponents[i].destroy();
    136                 }
    137             }
    138         }
    139     };
    140
    141     rf.cleanDom = function(source) {
    142         var e = (typeof source == "string") ? document.getElementById(source) : $('body').get(0);
    143         if (source == "javax.faces.ViewRoot") {
    144             e = $('body').get(0);
    145         }
    146         if (e) {
    147             // Fire a DOM cleanup event
    148             $(e).trigger("beforeDomClean.RICH");
    149             var elements = e.getElementsByTagName("*");
    150             if (elements.length) {
    151                 $.each(elements, function(index) {
    152                     rf.cleanComponent(this);
    153                 });
    154                 $.cleanData(elements);
    155             }
    156             // clean RF 4 BaseComponent components
    157             rf.cleanComponent(e);
    158             // cleans data and jQuery UI Widget Factory components
    159             $.cleanData([e]);
    160             $(e).trigger("afterDomClean.RICH");
    161         }
    162     };
    163
    164     //form.js
    165     rf.submitForm = function(form, parameters, target) {
    166         if (typeof form === "string") {
    167             form = $(form)
    168         }
    169         var initialTarget = form.attr("target");
    170         var parameterInputs = new Array();
    171         try {
    172             form.attr("target", target);
    173
    174             if (parameters) {
    175                 for (var parameterName in parameters) {
    176                     var parameterValue = parameters[parameterName];
    177
    178                     var input = $("input[name='" + parameterName + "']", form);
    179                     if (input.length == 0) {
    180                         var newInput = $("<input />").attr({type: 'hidden', name: parameterName, value: parameterValue});
    181                         if (parameterName === 'javax.faces.portletbridge.STATE_ID' /* fix for fileUpload in portlets */) {
    182                             input = newInput.prependTo(form);
    183                         } else {
    184                             input = newInput.appendTo(form);
    185                         }
    186                     } else {
    187                         input.val(parameterValue);
    188                     }
    189
    190                     input.each(function() {
    191                         parameterInputs.push(this)
    192                     });
    193                 }
    194             }
    195
    196             //TODO: inline onsubmit handler is not triggered - http://dev.jquery.com/ticket/4930
    197             form.trigger("submit");
    198         } finally {
    199             if (initialTarget === undefined) {
    200                 form.removeAttr("target");
    201             } else {
    202                 form.attr("target", initialTarget);
    203             }
    204             $(parameterInputs).remove();
    205         }
    206     };
    207
    208
    209
    210     //utils.js
    211     $.fn.toXML = function () {
    212         var out = '';
    213
    214         if (this.length > 0) {
    215             if (typeof XMLSerializer == 'function' ||
    216                 typeof XMLSerializer == 'object') {
    217
    218                 var xs = new XMLSerializer();
    219                 this.each(function() {
    220                     out += xs.serializeToString(this);
    221                 });
    222             } else if (this[0].xml !== undefined) {
    223                 this.each(function() {
    224                     out += this.xml;
    225                 });
    226             } else {
    227                 this.each(function() {
    228                     out += this;
    229                 });
    230             }
    231         }
    232
    233         return out;
    234     };
    235
    236     //there is the same pattern in server-side code:
    237     //org.richfaces.javascript.ScriptUtils.escapeCSSMetachars(String)
    238     var CSS_METACHARS_PATTERN = /([#;&,.+*~':"!^$\[\]()=>|\/])/g;
    239
    240     /**
    241      * Escapes CSS meta-characters in string according to
    242      *  <a href="http://api.jquery.com/category/selectors/">jQuery selectors</a> document.
    243      *
    244      * @param s - string to escape meta-characters in
    245      * @return string with meta-characters escaped
    246      */
    247     rf.escapeCSSMetachars = function(s) {
    248         //TODO nick - cache results
    249
    250         return s.replace(CSS_METACHARS_PATTERN, "\\$1");
    251     };
    252
    253     var logImpl;
    254
    255     rf.setLog = function(newLogImpl) {
    256         logImpl = newLogImpl;
    257     };
    258
    259     rf.log = {
    260         debug: function(text) {
    261             if (logImpl) {
    262                 logImpl.debug(text);
    263             }
    264         },
    265
    266         info: function(text) {
    267             if (logImpl) {
    268                 logImpl.info(text);
    269             }
    270         },
    271
    272         warn: function(text) {
    273             if (logImpl) {
    274                 logImpl.warn(text);
    275             }
    276         },
    277
    278         error: function(text) {
    279             if (logImpl) {
    280                 logImpl.error(text);
    281             }
    282         },
    283
    284         setLevel: function(level) {
    285             if (logImpl) {
    286                 logImpl.setLevel(level);
    287             }
    288         },
    289
    290         getLevel: function() {
    291             if (logImpl) {
    292                 return logImpl.getLevel();
    293             }
    294             return 'info';
    295         },
    296
    297         clear: function() {
    298             if (logImpl) {
    299                 logImpl.clear();
    300             }
    301         }
    302     };
    303
    304     /**
    305      * Evaluates chained properties for the "base" object.
    306      * For example, window.document.location is equivalent to
    307      * "propertyNamesString" = "document.location" and "base" = window
    308      * Evaluation is safe, so it stops on the first null or undefined object
    309      *
    310      * @param propertyNamesArray - array of strings that contains names of the properties to evaluate
    311      * @param base - base object to evaluate properties on
    312      * @return returns result of evaluation or empty string
    313      */
    314     rf.getValue = function(propertyNamesArray, base) {
    315         var result = base;
    316         var c = 0;
    317         do {
    318             result = result[propertyNamesArray[c++]];
    319         } while (result && c != propertyNamesArray.length);
    320
    321         return result;
    322     };
    323
    324     var VARIABLE_NAME_PATTERN_STRING = "[_A-Z,a-z]\\w*";
    325     var VARIABLES_CHAIN = new RegExp("^\\s*" + VARIABLE_NAME_PATTERN_STRING + "(?:\\s*\\.\\s*" + VARIABLE_NAME_PATTERN_STRING + ")*\\s*$");
    326     var DOT_SEPARATOR = /\s*\.\s*/;
    327
    328     rf.evalMacro = function(macro, base) {
    329         var value = "";
    330         // variable evaluation
    331         if (VARIABLES_CHAIN.test(macro)) {
    332             // object's variable evaluation
    333             var propertyNamesArray = $.trim(macro).split(DOT_SEPARATOR);
    334             value = rf.getValue(propertyNamesArray, base);
    335             if (!value) {
    336                 value = rf.getValue(propertyNamesArray, window);
    337             }
    338         } else {
    339             //js string evaluation
    340             try {
    341                 if (base.eval) {
    342                     value = base.eval(macro);
    343                 } else with (base) {
    344                     value = eval(macro);
    345                 }
    346             } catch (e) {
    347                 rf.log.warn("Exception: " + e.message + "\n[" + macro + "]");
    348             }
    349         }
    350
    351         if (typeof value == 'function') {
    352             value = value(base);
    353         }
    354         //TODO 0 and false are also treated as null values
    355         return value || "";
    356     };
    357
    358     var ALPHA_NUMERIC_MULTI_CHAR_REGEXP = /^\w+$/;
    359
    360     rf.interpolate = function (placeholders, context) {
    361         var contextVarsArray = new Array();
    362         for (var contextVar in context) {
    363             if (ALPHA_NUMERIC_MULTI_CHAR_REGEXP.test(contextVar)) {
    364                 //guarantees that no escaping for the below RegExp is necessary
    365                 contextVarsArray.push(contextVar);
    366             }
    367         }
    368
    369         var regexp = new RegExp("\\{(" + contextVarsArray.join("|") + ")\\}", "g");
    370         return placeholders.replace(regexp, function(str, contextVar) {
    371             return context[contextVar];
    372         });
    373     };
    374
    375     rf.clonePosition = function(element, baseElement, positioning, offset) {
    376
    377     };
    378     //
    379
    380     var jsfEventsAdapterEventNames = {
    381         event: {
    382             'begin': ['begin'],
    383             'complete': ['beforedomupdate'],
    384             'success': ['success', 'complete']
    385         },
    386         error: ['error', 'complete']
    387     };
    388
    389     var getExtensionResponseElement = function(responseXML) {
    390         return $("partial-response extension#org\\.richfaces\\.extension", responseXML);
    391     };
    392
    393     var JSON_STRING_START = /^\s*(\[|\{)/;
    394
    395     rf.parseJSON = function(dataString) {
    396         try {
    397             if (dataString) {
    398                 if (JSON_STRING_START.test(dataString)) {
    399                     return $.parseJSON(dataString);
    400                 } else {
    401                     var parsedData = $.parseJSON("{\"root\": " + dataString + "}");
    402                     return parsedData.root;
    403                 }
    404             }
    405         } catch (e) {
    406             rf.log.warn("Error evaluating JSON data from element <" + elementName + ">: " + e.message);
    407         }
    408
    409         return null;
    410     }
    411
    412     var getJSONData = function(extensionElement, elementName) {
    413         var dataString = $.trim(extensionElement.children(elementName).text());
    414         return rf.parseJSON(dataString);
    415     };
    416
    417     rf.createJSFEventsAdapter = function(handlers) {
    418         //hash of handlers
    419         //supported are:
    420         // - begin
    421         // - beforedomupdate
    422         // - success
    423         // - error
    424         // - complete
    425         var handlers = handlers || {};
    426         var ignoreSuccess;
    427
    428         return function(eventData) {
    429             var source = eventData.source;
    430             //that's request status, not status control data
    431             var status = eventData.status;
    432             var type = eventData.type;
    433
    434             if (type == 'event' && status == 'begin') {
    435                 ignoreSuccess = false;
    436             } else if (type == 'error') {
    437                 ignoreSuccess = true;
    438             } else if (ignoreSuccess) {
    439                 return;
    440             } else if (status == 'complete' && rf.ajaxContainer && rf.ajaxContainer.isIgnoreResponse && rf.ajaxContainer.isIgnoreResponse()) {
    441                 return;
    442             }
    443
    444             var typeHandlers = jsfEventsAdapterEventNames[type];
    445             var handlerNames = (typeHandlers || {})[status] || typeHandlers;
    446
    447             if (handlerNames) {
    448                 for (var i = 0; i < handlerNames.length; i++) {
    449                     var eventType = handlerNames[i];
    450                     var handler = handlers[eventType];
    451                     if (handler) {
    452                         var event = {};
    453                         $.extend(event, eventData);
    454                         event.type = eventType;
    455                         if (type != 'error') {
    456                             delete event.status;
    457
    458                             if (event.responseXML) {
    459                                 var xml = getExtensionResponseElement(event.responseXML);
    460                                 var data = getJSONData(xml, "data");
    461                                 var componentData = getJSONData(xml, "componentData");
    462
    463                                 event.data = data;
    464                                 event.componentData = componentData || {};
    465                             }
    466                         }
    467                         handler.call(source, event);
    468                     }
    469                 }
    470             }
    471         };
    472     };
    473
    474     rf.setGlobalStatusNameVariable = function(statusName) {
    475         //TODO: parallel requests
    476         if (statusName) {
    477             rf['statusName'] = statusName;
    478         } else {
    479             delete rf['statusName'];
    480         }
    481     }
    482
    483     rf.setZeroRequestDelay = function(options) {
    484         if (typeof options.requestDelay == "undefined") {
    485             options.requestDelay = 0;
    486         }
    487     };
    488
    489     var chain = function() {
    490         var functions = arguments;
    491         if (functions.length == 1) {
    492             return functions[0];
    493         } else {
    494             return function() {
    495                 var callResult;
    496                 for (var i = 0; i < functions.length; i++) {
    497                     var f = functions[i];
    498                     if (f) {
    499                         callResult = f.apply(this, arguments);
    500                     }
    501                 }
    502
    503                 return callResult;
    504             };
    505         }
    506     };
    507
    508     var createEventHandler = function(handlerCode) {
    509         if (handlerCode) {
    510             // ensure safe execution, errors would cause rf.queue to hang up (RF-12132)
    511             var safeHandlerCode = "try {" +
    512                     handlerCode +
    513                 "} catch (e) {" +
    514                     "window.RichFaces.log.error('Error in method execution: ' + e.message)" +
    515                 "}";
    516             return new Function("event", safeHandlerCode);
    517         }
    518
    519         return null;
    520     };
    521
    522     //TODO take events just from .java code using EL-expression
    523     var AJAX_EVENTS = (function() {
    524         var serverEventHandler = function(clientHandler, event) {
    525             var xml = getExtensionResponseElement(event.responseXML);
    526
    527             var serverHandler = createEventHandler(xml.children(event.type).text());
    528
    529             if (clientHandler) {
    530                 clientHandler.call(this, event);
    531             }
    532
    533             if (serverHandler) {
    534                 serverHandler.call(this, event);
    535             }
    536         };
    537
    538         return {
    539             'error': null,
    540             'begin': null,
    541             'complete': serverEventHandler,
    542             'beforedomupdate': serverEventHandler
    543         }
    544     }());
    545
    546     rf.ajax = function(source, event, options) {
    547         var options = options || {};
    548
    549         var sourceId = getSourceId(source, options);
    550         var sourceElement = getSourceElement(source);
    551
    552         // event source re-targeting finds a RichFaces component root
    553         // to setup javax.faces.source correctly - RF-12616)
    554         if (sourceElement) {
    555             source = searchForComponentRootOrReturn(sourceElement);
    556         }
    557
    558         parameters = options.parameters || {}; // TODO: change "parameters" to "richfaces.ajax.params"
    559         parameters.execute = "@component";
    560         parameters.render = "@component";
    561
    562         if (options.clientParameters) {
    563             $.extend(parameters, options.clientParameters);
    564         }
    565
    566         if (!parameters["org.richfaces.ajax.component"]) {
    567             parameters["org.richfaces.ajax.component"] = sourceId;
    568         }
    569
    570         if (options.incId) {
    571             parameters[sourceId] = sourceId;
    572         }
    573
    574         if (rf.queue) {
    575             parameters.queueId = options.queueId;
    576         }
    577
    578         // propagates some options to process it in jsf.ajax.request
    579         parameters.rfExt = {};
    580         parameters.rfExt.status = options.status;
    581         for (var eventName in AJAX_EVENTS) {
    582             parameters.rfExt[eventName] = options[eventName];
    583         }
    584
    585         jsf.ajax.request(source, event, parameters);
    586     };
    587
    588     if (window.jsf) {
    589         jsf.ajax.request = function request(source, event, options) {
    590
    591             // build parameters, taking options.rfExt into consideration
    592             var parameters = $.extend({}, options);
    593             parameters.rfExt = null;
    594
    595             var eventHandlers;
    596
    597             var sourceElement = getSourceElement(source);
    598             var form = getFormElement(sourceElement);
    599
    600             for (var eventName in AJAX_EVENTS) {
    601                 var handlerCode, handler;
    602
    603                 if (options.rfExt) {
    604                     handlerCode = options.rfExt[eventName];
    605                     handler = typeof handlerCode == "function" ? handlerCode : createEventHandler(handlerCode);
    606                 }
    607
    608                 var serverHandler = AJAX_EVENTS[eventName];
    609                 if (serverHandler) {
    610                     handler = $.proxy(function(clientHandler, event) {
    611                       return serverHandler.call(this, clientHandler, event);
    612                     }, sourceElement, handler);
    613                 }
    614
    615                 if (handler) {
    616                     eventHandlers = eventHandlers || {};
    617                     eventHandlers[eventName] = handler;
    618                 }
    619             }
    620
    621             if (options.rfExt && options.rfExt.status) {
    622                 var namedStatusEventHandler = function() {
    623                     rf.setGlobalStatusNameVariable(options.rfExt.status);
    624                 };
    625
    626                 //TODO add support for options.submit
    627                 eventHandlers = eventHandlers || {};
    628                 if (eventHandlers.begin) {
    629                     eventHandlers.begin = chain(namedStatusEventHandler, eventHandlers.begin);
    630                 } else {
    631                     eventHandlers.begin = namedStatusEventHandler;
    632                 }
    633             }
    634
    635             // register handlers for form events: ajaxbegin and ajaxbeforedomupdate
    636             if (form) {
    637                 eventHandlers.begin = chain(eventHandlers.begin, function() { $(form).trigger('ajaxbegin'); });
    638                 eventHandlers.beforedomupdate = chain(eventHandlers.beforedomupdate, function() { $(form).trigger('ajaxbeforedomupdate'); });
    639                 eventHandlers.complete = chain(eventHandlers.complete, function() { $(form).trigger('ajaxcomplete'); });
    640             }
    641
    642             if (eventHandlers) {
    643                 var eventsAdapter = rf.createJSFEventsAdapter(eventHandlers);
    644                 parameters['onevent'] = chain(options.onevent, eventsAdapter);
    645                 parameters['onerror'] = chain(options.onerror, eventsAdapter);
    646             }
    647
    648             // trigger handler for form event: ajaxsubmit
    649             if (form) {
    650                 $(form).trigger('ajaxsubmit');
    651             }
    652
    653             return jsfAjaxRequest(source, event, parameters);
    654         }
    655
    656         jsf.ajax.response = function(request, context) {
    657             // for all RichFaces.ajax requests
    658             if (context.render == '@component') {
    659                 // get list of IDs updated on the server - replaces @render option which is normally available on client
    660                 context.render = $("extension[id='org.richfaces.extension'] render", request.responseXML).text();
    661             }
    662
    663             return jsfAjaxResponse(request, context);
    664         }
    665     }
    666
    667     /**
    668      * Returns RichFaces component root for given element in the list of ancestors of sourceElement.
    669      * Otherwise returns sourceElement if RichFaces component root can't be located.
    670      */
    671     var searchForComponentRootOrReturn = function(sourceElement) {
    672         if (sourceElement.id && !isRichFacesComponent(sourceElement)) {
    673             var parentElement = false;
    674             $(sourceElement).parents().each(function() {
    675                 if (this.id && sourceElement.id.indexOf(this.id) == 0) { // otherwise parent element is definitely not JSF component
    676                     var suffix = sourceElement.id.substring(this.id.length); // extract suffix
    677                     if (suffix.match(/^[a-zA-Z]*$/) && isRichFacesComponent(this)) {
    678                         parentElement = this;
    679                         return false;
    680                     }
    681                 }
    682             });
    683             if (parentElement !== false) {
    684                 return parentElement;
    685             }
    686         }
    687         return sourceElement;
    688     };
    689
    690
    691     /**
    692      * Detects whether the element has bound RichFaces component.
    693      *
    694      * Supports detection of RichFaces 5 (bridge-base.js) and RichFaces 4 (richfaces-base-component.js) components.
    695      */
    696     var isRichFacesComponent = function(element) {
    697       return $(element).data('rf.bridge') || rf.component(element);
    698     };
    699
    700     var getSourceElement = function(source) {
    701         if (typeof source === 'string') {
    702             return document.getElementById(source);
    703         } else if (typeof source === 'object') {
    704             return source;
    705         } else {
    706             throw new Error("jsf.request: source must be object or string");
    707         }
    708     };
    709
    710     var getFormElement = function(sourceElement) {
    711         if ($(sourceElement).is('form')) {
    712             return sourceElement;
    713         } else {
    714             return $('form').has(sourceElement).get(0);
    715         }
    716     };
    717
    718     var getSourceId = function(source, options) {
    719         if (options.sourceId) {
    720             return options.sourceId;
    721         } else {
    722             return (typeof source == 'object' && (source.id || source.name)) ? (source.id ? source.id : source.name) : source;
    723         }
    724     };
    725
    726     var ajaxOnComplete = function (data) {
    727         var type = data.type;
    728         var responseXML = data.responseXML;
    729
    730         if (data.type == 'event' && data.status == 'complete' && responseXML) {
    731             var partialResponse = $(responseXML).children("partial-response");
    732             if (partialResponse && partialResponse.length) {
    733                 var elements = partialResponse.children('changes').children('update, delete');
    734                 $.each(elements, function () {
    735                     rf.cleanDom($(this).attr('id'));
    736                 });
    737             }
    738         }
    739     };
    740
    741     rf.javascriptServiceComplete = function(event) {
    742         $(function() {
    743             $(document).trigger("javascriptServiceComplete");
    744         });
    745     };
    746
    747     var attachAjaxDOMCleaner = function() {
    748         // move this code to somewhere
    749         if (typeof jsf != 'undefined' && jsf.ajax) {
    750             jsf.ajax.addOnEvent(ajaxOnComplete);
    751
    752             return true;
    753         }
    754
    755         return false;
    756     };
    757
    758     if (!attachAjaxDOMCleaner()) {
    759         $(document).ready(attachAjaxDOMCleaner);
    760     }
    761
    762     if (window.addEventListener) {
    763         window.addEventListener("unload", rf.cleanDom, false);
    764     } else {
    765         window.attachEvent("onunload", rf.cleanDom);
    766     }
    767
    768     // browser detection, taken jQuery Migrate plugin (https://github.com/jquery/jquery-migrate)
    769     //     and atmosphere.js
    770     rf.browser = {};
    771     var ua = navigator.userAgent.toLowerCase(),
    772         match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
    773             /(webkit)[ \/]([\w.]+)/.exec(ua) ||
    774             /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
    775             /(msie) ([\w.]+)/.exec(ua) ||
    776             /(trident)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
    777             ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
    778             [];
    779
    780     rf.browser[match[1] || ""] = true;
    781     rf.browser.version = match[2] || "0";
    782
    783     // Chrome is Webkit, but Webkit is also Safari.
    784 	if ( rf.browser.chrome ) {
    785 		rf.browser.webkit = true;
    786 	} else if ( rf.browser.webkit ) {
    787 		rf.browser.safari = true;
    788 	}
    789
    790     // Trident is the layout engine of the Internet Explorer
    791     // IE 11 has no "MSIE: 11.0" token
    792     if (rf.browser.trident) {
    793         rf.browser.msie = true;
    794     }
    795 }(RichFaces.jQuery, RichFaces));
    796