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 }(RichFaces.jQuery, RichFaces)); 768