1 2 /** 3 * Global object container for RichFaces API. 4 * All classes should be defined here. 5 * @class 6 * @name RichFaces 7 * @static 8 * 9 * */ 10 window.RichFaces = {}; 11 12 (function(jQuery, richfaces) { 13 14 // get DOM element by id or DOM element or jQuery object 15 richfaces.getDomElement = function (source) { 16 var type = typeof source; 17 var element; 18 if (type == "string") { 19 // id 20 element = document.getElementById(source); 21 } else if (type == "object") { 22 if (source.nodeType) { 23 // DOM element 24 element = source; 25 } else 26 if (source instanceof jQuery) { 27 // jQuery object 28 element = source.get(0); 29 } 30 } 31 return element; 32 } 33 34 // get RichFaces component object by component id or DOM element or jQuery object 35 richfaces.$ = function (source) { 36 var element = richfaces.getDomElement(source); 37 38 if (element) { 39 return (element["richfaces"] || {})["component"]; 40 } 41 } 42 43 // find component and call his method 44 richfaces.invokeMethod = function(source, method) { 45 var c = richfaces.$(source); 46 if (c) { 47 var f = c[method]; 48 if (typeof f == "function") { 49 return f.apply(c, Array.prototype.slice.call(arguments, 2)); 50 } 51 } 52 } 53 54 //dom cleaner 55 richfaces.cleanDom = function(source) { 56 var e = (typeof source == "string") ? document.getElementById(source) : jQuery('body').get(0); 57 if (e) { 58 var elements = e.getElementsByTagName("*"); 59 if (elements.length) { 60 jQuery.cleanData(elements); 61 jQuery.cleanData([e]); 62 jQuery.each(elements, function(index) { 63 richfaces.invokeMethod(this, "destroy"); 64 }); 65 richfaces.invokeMethod(e, "destroy"); 66 } 67 } 68 } 69 70 //form.js 71 richfaces.submitForm = function(form, parameters, target) { 72 if (typeof form === "string") { form = jQuery(form) }; 73 var initialTarget = form.attr("target"); 74 var parameterInputs = new Array(); 75 try { 76 form.attr("target", target); 77 78 if (parameters) { 79 for (var parameterName in parameters) { 80 var parameterValue = parameters[parameterName]; 81 82 var input = jQuery("input[name='" + parameterName + "']", form); 83 if (input.length == 0) { 84 var newInput = jQuery("<input />").attr({type: 'hidden', name: parameterName, value: parameterValue}); 85 if (parameterName === 'javax.faces.portletbridge.STATE_ID' /* fix for fileUpload in portlets */) { 86 input = newInput.prependTo(form); 87 } else { 88 input = newInput.appendTo(form); 89 } 90 } else { 91 input.val(parameterValue); 92 } 93 94 input.each(function() {parameterInputs.push(this)}); 95 } 96 } 97 98 //TODO: inline onsubmit handler is not triggered - http://dev.jquery.com/ticket/4930 99 form.trigger("submit"); 100 } finally { 101 form.attr("target", initialTarget); 102 jQuery(parameterInputs).remove(); 103 } 104 }; 105 // 106 107 //utils.js 108 jQuery.fn.toXML = function () { 109 var out = ''; 110 111 if (this.length > 0) { 112 if (typeof XMLSerializer == 'function' || 113 typeof XMLSerializer == 'object') { 114 115 var xs = new XMLSerializer(); 116 this.each(function() { out += xs.serializeToString(this); }); 117 } else if (this[0].xml !== undefined) { 118 this.each(function() { out += this.xml; }); 119 } else { 120 this.each( function() { out += this; } ); 121 } 122 } 123 124 return out; 125 }; 126 127 //there is the same pattern in server-side code: 128 //org.ajax4jsf.javascript.ScriptUtils.escapeCSSMetachars(String) 129 var CSS_METACHARS_PATTERN = /([#;&,.+*~':"!^$[\]()=>|\/])/g; 130 131 /** 132 * Escapes CSS meta-characters in string according to 133 * <a href="http://api.jquery.com/category/selectors/">jQuery selectors</a> document. 134 * 135 * @param s - string to escape meta-characters in 136 * @return string with meta-characters escaped 137 */ 138 richfaces.escapeCSSMetachars = function(s) { 139 //TODO nick - cache results 140 141 return s.replace(CSS_METACHARS_PATTERN, "\\$1"); 142 }; 143 144 richfaces.log = (function(jQuery) { 145 var LOG_LEVELS = {'debug': 1, 'info': 2, 'warn': 3, 'error': 4}; 146 var LOG_LEVEL_COLORS = {'debug': 'darkblue', 'info': 'blue', 'warn': 'gold', 'error': 'red'}; 147 var DEFAULT_LOG_LEVEL = LOG_LEVELS['info']; 148 var currentLogLevel = DEFAULT_LOG_LEVEL; 149 150 var consoleInitialized = false; 151 152 var setLevel = function(level) { 153 currentLogLevel = LOG_LEVELS[level] || DEFAULT_LOG_LEVEL; 154 if (consoleInitialized) { 155 getConsole().find("select.rich-log-element").val(currentLogLevel); 156 } 157 clear(); 158 }; 159 160 var setLevelFromSelect = function(iLevel) { 161 currentLogLevel = iLevel || DEFAULT_LOG_LEVEL; 162 }; 163 164 var clear = function() { 165 if (window.console && useBrowserConsole) { 166 window.console.clear(); 167 } else { 168 var console = getConsole(); 169 console.children(".rich-log-contents").children().remove(); 170 } 171 }; 172 173 var getConsole = function() { 174 var console = jQuery('#richfaces\\.log'); 175 if (console.length != 0) { 176 if (!consoleInitialized) { 177 consoleInitialized = true; 178 179 var clearBtn = console.children("button.rich-log-element"); 180 if (clearBtn.length == 0) { 181 clearBtn = jQuery("<button type='button' class='rich-log-element'>Clear</button>").appendTo(console); 182 } 183 clearBtn.click(clear); 184 185 var levelSelect = console.children("select.rich-log-element"); 186 if (levelSelect.length == 0) { 187 levelSelect = jQuery("<select class='rich-log-element' />").appendTo(console); 188 } 189 190 if (levelSelect.children().length == 0) { 191 for (var level in LOG_LEVELS) { 192 jQuery("<option value='" + LOG_LEVELS[level]+ "'>" + level + "</option>").appendTo(levelSelect); 193 } 194 } 195 196 levelSelect.val(currentLogLevel); 197 levelSelect.change(function(event) { clear(); setLevelFromSelect(parseInt(jQuery(this).val(), 10)); return false;}); 198 199 var consoleEntries = console.children(".rich-log-contents"); 200 if (consoleEntries.length == 0) { 201 consoleEntries = jQuery("<div class='rich-log-contents'></div>").appendTo(console); 202 } 203 } 204 } 205 206 return console; 207 }; 208 209 var useBrowserConsole = false; 210 211 var AM_PM = /(\s*(?:a|p)m)$/i; 212 213 var getMessagePrefix = function(level) { 214 var date = new Date(); 215 var formattedDate = date.toLocaleTimeString(); 216 217 var ms = (date.getMilliseconds() + 1000).toString().substr(1); 218 if (AM_PM.test(formattedDate)) { 219 formattedDate = formattedDate.replace(AM_PM, "." + ms + "$1"); 220 } else { 221 formattedDate += "." + ms; 222 } 223 224 return level + '[' + formattedDate.replace() + ']: '; 225 }; 226 227 var appendConsoleEntry = function(level, messagePrefix, messageText, console) { 228 //TODO - cache jQuery("<element>")? 229 var newEntry = jQuery(document.createElement("div")).appendTo(console.children(".rich-log-contents")); 230 jQuery("<span style='color: " + LOG_LEVEL_COLORS[level] + "'></span>").appendTo(newEntry).text(messagePrefix); 231 232 var entrySpan = jQuery(document.createElement("span")).appendTo(newEntry); 233 if (typeof messageText != 'object' || !messageText.appendTo) { 234 entrySpan.text(messageText); 235 } else { 236 messageText.appendTo(entrySpan); 237 } 238 }; 239 240 var appendBrowserConsoleEntry = function(level, text) { 241 window.console[level](); 242 }; 243 244 var appendMessage = function(level, message) { 245 if (window.console && useBrowserConsole) { 246 var text = getMessagePrefix(level) + message; 247 appendBrowserConsoleEntry(level, text); 248 } else { 249 var console = getConsole(); 250 if (LOG_LEVELS[level] >= currentLogLevel && console.length != 0) { 251 var messagePrefix = getMessagePrefix(level); 252 appendConsoleEntry(level, messagePrefix, message, console); 253 } 254 } 255 }; 256 257 var methods = {setLevel: setLevel, clear: clear}; 258 for (var logLevel in LOG_LEVELS) { 259 var f = function(text) { 260 appendMessage(arguments.callee.logLevel, text); 261 }; 262 f.logLevel = logLevel; 263 methods[logLevel] = f; 264 } 265 return methods; 266 }(jQuery)); 267 268 /** 269 * Evaluates chained properties for the "base" object. 270 * For example, window.document.location is equivalent to 271 * "propertyNamesString" = "document.location" and "base" = window 272 * Evaluation is safe, so it stops on the first null or undefined object 273 * 274 * @param propertyNamesArray - array of strings that contains names of the properties to evaluate 275 * @param base - base object to evaluate properties on 276 * @return returns result of evaluation or empty string 277 */ 278 richfaces.getValue = function(propertyNamesArray, base) { 279 var result = base; 280 var c = 0; 281 do { 282 result = result[propertyNamesArray[c++]]; 283 } while (result && c != propertyNamesArray.length); 284 285 return result; 286 }; 287 288 var VARIABLE_NAME_PATTERN_STRING = "[_A-Z,a-z]\\w*"; 289 var VARIABLES_CHAIN = new RegExp("^\\s*"+VARIABLE_NAME_PATTERN_STRING+"(?:\\s*\\.\\s*"+VARIABLE_NAME_PATTERN_STRING+")*\\s*$"); 290 var DOT_SEPARATOR = /\s*\.\s*/; 291 292 richfaces.evalMacro = function(macro, base) { 293 var value = ""; 294 // variable evaluation 295 if (VARIABLES_CHAIN.test(macro)) { 296 // object's variable evaluation 297 var propertyNamesArray = jQuery.trim(macro).split(DOT_SEPARATOR); 298 value = richfaces.getValue(propertyNamesArray, base); 299 if (!value) { 300 value = richfaces.getValue(propertyNamesArray, window); 301 } 302 } else { 303 //js string evaluation 304 try { 305 if (base.eval) { 306 value = base.eval(macro); 307 } else with (base) { 308 value = eval(macro) ; 309 } 310 } catch (e) { 311 richfaces.log.warn("Exception: " + e.message + "\n[" + macro + "]"); 312 } 313 } 314 315 if (typeof value == 'function') { 316 value = value(base); 317 } 318 //TODO 0 and false are also treated as null values 319 return value || ""; 320 }; 321 322 var ALPHA_NUMERIC_MULTI_CHAR_REGEXP = /^\w+$/; 323 324 richfaces.interpolate = function (placeholders, context) { 325 var contextVarsArray = new Array(); 326 for (var contextVar in context) { 327 if (ALPHA_NUMERIC_MULTI_CHAR_REGEXP.test(contextVar)) { 328 //guarantees that no escaping for the below RegExp is necessary 329 contextVarsArray.push(contextVar); 330 } 331 } 332 333 var regexp = new RegExp("\\{(" + contextVarsArray.join("|") + ")\\}", "g"); 334 return placeholders.replace(regexp, function(str, contextVar) {return context[contextVar];}); 335 }; 336 337 richfaces.clonePosition = function(element, baseElement, positioning, offset) { 338 339 }; 340 // 341 342 var pollTracker = {}; 343 richfaces.startPoll = function(options) { 344 var pollId = options.pollId; 345 var interval = options.pollinterval; 346 var ontimer = options.ontimer; 347 richfaces.stopPoll(pollId); 348 349 richfaces.setZeroRequestDelay(options); 350 351 pollTracker[pollId] = window.setTimeout(function(){ 352 var pollElement = document.getElementById(pollId); 353 try { 354 ontimer.call(pollElement || window); 355 richfaces.startPoll(options); 356 } catch (e) { 357 // TODO: handle exception 358 } 359 },interval); 360 }; 361 362 richfaces.stopPoll = function(id) { 363 if(pollTracker[id]){ 364 window.clearTimeout(pollTracker[id]); 365 delete pollTracker[id]; 366 } 367 }; 368 369 var pushTracker = {}; 370 371 richfaces.startPush = function(options) { 372 var clientId = options.clientId; 373 var pushResourceUrl = options.pushResourceUrl; 374 var pushId = options.pushId; 375 var interval = options.interval; 376 var ondataavailable = options.ondataavailable; 377 richfaces.setZeroRequestDelay(options); 378 379 richfaces.stopPush(pushId); 380 381 pushTracker[pushId] = setTimeout(function() { // TODO: define this function in richfaces object to avoid definition every time when call startPush 382 var ajaxOptions = { 383 type: "HEAD", 384 //TODO - encodeURIComponent; URL sessionId handling check 385 //TODO - add pushUri supports 386 url: pushResourceUrl + "?id=" + pushId, 387 dataType: "text", 388 complete: function(xhr) { 389 var isPushActive = !!pushTracker[pushId]; 390 391 //TODO may someone wish to stop push from dataavailable handler? 392 delete pushTracker[pushId]; 393 394 if (xhr.status == 200 && xhr.getResponseHeader("Ajax-Push-Status") == "READY") { 395 var pushElement = document.getElementById(clientId); 396 try { 397 ondataavailable.call(pushElement || window); 398 } catch (e) { 399 // TODO: handle exception 400 } 401 } 402 403 if (isPushActive) { 404 richfaces.startPush(options); 405 } 406 } 407 }; 408 409 if (options.timeout) { 410 ajaxOptions.timeout = options.timeout; 411 } 412 413 jQuery.ajax(ajaxOptions); 414 }, interval); 415 }; 416 417 richfaces.stopPush = function(id) { 418 if (pushTracker[id]){ 419 window.clearTimeout(pushTracker[id]); 420 delete pushTracker[id]; 421 } 422 }; 423 424 var jsfEventsAdapterEventNames = { 425 event: { 426 'begin': ['begin'], 427 'complete': ['beforedomupdate'], 428 'success': ['success', 'complete'] 429 }, 430 error: ['error', 'complete'] 431 }; 432 433 richfaces.createJSFEventsAdapter = function(handlers) { 434 //hash of handlers 435 //supported are: 436 // - begin 437 // - beforedomupdate 438 // - success 439 // - error 440 // - complete 441 handlers = handlers || {}; 442 443 return function(eventData) { 444 var source = eventData.source; 445 //that's request status, not status control data 446 var status = eventData.status; 447 var type = eventData.type; 448 449 var typeHandlers = jsfEventsAdapterEventNames[type]; 450 var handlerNames = (typeHandlers || {})[status] || typeHandlers; 451 452 if (handlerNames) { 453 for (var i = 0; i < handlerNames.length; i++) { 454 var eventType = handlerNames[i]; 455 var handler = handlers[eventType]; 456 if (handler) { 457 var event = {}; 458 jQuery.extend(event, eventData); 459 event.type = eventType; 460 if (type != 'error') { 461 delete event.status; 462 } 463 464 handler.call(source, event); 465 } 466 } 467 } 468 }; 469 }; 470 471 var setGlobalStatusNameVariable = function(statusName) { 472 //TODO: parallel requests 473 if (statusName) { 474 richfaces['statusName'] = statusName; 475 } else { 476 delete richfaces['statusName']; 477 } 478 } 479 480 richfaces.setZeroRequestDelay = function(options) { 481 if (typeof options.requestDelay == "undefined") { 482 options.requestDelay = 0; 483 } 484 }; 485 486 var getGlobalStatusNameVariable = function() { 487 return richfaces.statusName; 488 } 489 490 var chain = function() { 491 var functions = arguments; 492 if (functions.length == 1) { 493 return functions[0]; 494 } else { 495 return function() { 496 var callResult; 497 for (var i = 0; i < functions.length; i++) { 498 var f = functions[i]; 499 callResult = f.apply(this, arguments); 500 } 501 502 return callResult; 503 }; 504 } 505 }; 506 507 /** 508 * curry (g, a) (b) -> g(a, b) 509 */ 510 var curry = function(g, a) { 511 var _g = g; 512 var _a = a; 513 514 return function(b) { 515 _g(_a, b); 516 }; 517 }; 518 519 var createEventHandler = function(handlerCode) { 520 if (handlerCode) { 521 return new Function("event", "data", handlerCode); 522 } 523 524 return null; 525 }; 526 527 //TODO take events just from .java code using EL-expression 528 var AJAX_EVENTS = (function() { 529 var serverEventHandler = function(clientHandler, event) { 530 var xml = jQuery("partial-response > extension#org\\.richfaces\\.extension", event.responseXML); 531 532 var serverHandler = createEventHandler(xml.children(event.type).text()); 533 xml.end(); 534 535 var dataString = xml.children("data").text(); 536 xml.end(); 537 538 var data = null; 539 if (dataString) { 540 try { 541 data = window["eval"]("(" + dataString + ")"); 542 } catch (e) { 543 richfaces.log.warn("Error evaluating custom response data: " + e.message); 544 } 545 } 546 547 if (serverHandler) { 548 serverHandler.call(window, event, data); 549 } else if (clientHandler) { 550 clientHandler.call(window, event, data); 551 } 552 }; 553 554 return { 555 'begin': null, 556 'complete': serverEventHandler, 557 'beforedomupdate': serverEventHandler 558 } 559 }()); 560 561 richfaces.ajax = function(source, event, options) { 562 var sourceId = (typeof source == 'object' && source.id) ? source.id : source; 563 var sourceElt = (typeof source == 'object') ? source : document.getElementById(sourceId); 564 565 options = options || {}; 566 567 parameters = options.parameters || {}; // TODO: change "parameters" to "richfaces.ajax.params" 568 parameters.execute = "@component"; 569 parameters.render = "@component"; 570 571 if (!parameters["org.richfaces.ajax.component"]) { 572 parameters["org.richfaces.ajax.component"] = sourceId; 573 } 574 575 var eventHandlers; 576 577 for (var eventName in AJAX_EVENTS) { 578 var handler = createEventHandler(options[eventName]); 579 580 var serverHandler = AJAX_EVENTS[eventName]; 581 if (serverHandler) { 582 handler = curry(serverHandler, handler); 583 } 584 585 if (handler) { 586 eventHandlers = eventHandlers || {}; 587 eventHandlers[eventName] = handler; 588 } 589 } 590 591 if (options.status) { 592 var namedStatusEventHandler = function() { setGlobalStatusNameVariable(options.status); }; 593 594 //TODO add support for options.submit 595 eventHandlers = eventHandlers || {}; 596 if (eventHandlers.begin) { 597 eventHandlers.begin = chain(namedStatusEventHandler, eventHandlers.begin); 598 } else { 599 eventHandlers.begin = namedStatusEventHandler; 600 } 601 } 602 603 if (!jQuery(sourceElt).is(":input:not(:submit, :button, :image, :reset)")) { 604 parameters[sourceId] = sourceId; 605 } 606 607 if (eventHandlers) { 608 var eventsAdapter = richfaces.createJSFEventsAdapter(eventHandlers); 609 parameters['onevent'] = eventsAdapter; 610 parameters['onerror'] = eventsAdapter; 611 } 612 613 if (richfaces.queue) { 614 parameters.queueId = options.queueId; 615 } 616 617 jsf.ajax.request(source, event, parameters); 618 }; 619 620 var RICHFACES_AJAX_STATUS = "richfaces:ajaxStatus"; 621 622 var getStatusDataAttributeName = function(statusName) { 623 return statusName ? (RICHFACES_AJAX_STATUS + "@" + statusName) : RICHFACES_AJAX_STATUS; 624 }; 625 626 var statusAjaxEventHandler = function(data, methodName) { 627 if (methodName) { 628 //global status name 629 var statusName = getGlobalStatusNameVariable(); 630 var source = data.source; 631 632 var statusApplied = false; 633 var statusDataAttribute = getStatusDataAttributeName(statusName); 634 635 var statusContainers; 636 if (statusName) { 637 statusContainers = [jQuery(document)]; 638 } else { 639 statusContainers = [jQuery(source).parents('form'), jQuery(document)]; 640 } 641 642 for (var containerIdx = 0; containerIdx < statusContainers.length && !statusApplied; 643 containerIdx++) { 644 645 var statusContainer = statusContainers[containerIdx]; 646 var statuses = statusContainer.data(statusDataAttribute); 647 if (statuses) { 648 for (var statusId in statuses) { 649 var status = statuses[statusId]; 650 var result = status[methodName].apply(status, arguments); 651 if (result) { 652 statusApplied = true; 653 } else { 654 delete statuses[statusId]; 655 } 656 } 657 658 if (!statusApplied) { 659 statusContainer.removeData(statusDataAttribute); 660 } 661 } 662 } 663 } 664 }; 665 666 var initializeStatuses = function() { 667 var thisFunction = arguments.callee; 668 if (!thisFunction.initialized) { 669 thisFunction.initialized = true; 670 671 var jsfEventsListener = richfaces.createJSFEventsAdapter({ 672 begin: function(event) { statusAjaxEventHandler(event, 'start'); }, 673 error: function(event) { statusAjaxEventHandler(event, 'error'); }, 674 success: function(event) { statusAjaxEventHandler(event, 'success'); }, 675 complete: function() { setGlobalStatusNameVariable(null); } 676 }); 677 678 jsf.ajax.addOnEvent(jsfEventsListener); 679 //TODO blocks default alert error handler 680 jsf.ajax.addOnError(jsfEventsListener); 681 } 682 }; 683 684 richfaces.status = function(statusId, options) { 685 this.statusId = statusId; 686 this.options = options || {}; 687 this.register(); 688 }; 689 690 jQuery.extend(richfaces.status.prototype, (function() { 691 //TODO - support for parallel requests 692 693 var getElement = function() { 694 var elt = document.getElementById(this.statusId); 695 return elt ? jQuery(elt) : null; 696 }; 697 698 var showHide = function(selector) { 699 var element = getElement.call(this); 700 if (element) { 701 var statusElts = element.children(); 702 statusElts.each(function() { 703 var t = jQuery(this); 704 t.css('display', t.is(selector) ? '': 'none'); 705 }); 706 707 return true; 708 } 709 710 return false; 711 }; 712 713 return { 714 register: function() { 715 initializeStatuses(); 716 717 var statusName = this.options.statusName; 718 var dataStatusAttribute = getStatusDataAttributeName(statusName); 719 720 var container; 721 if (statusName) { 722 container = jQuery(document); 723 } else { 724 container = getElement.call(this).parents('form'); 725 if (container.length == 0) { 726 container = jQuery(document); 727 }; 728 } 729 730 var statuses = container.data(dataStatusAttribute); 731 if (!statuses) { 732 statuses = {}; 733 container.data(dataStatusAttribute, statuses); 734 } 735 736 statuses[this.statusId] = this; 737 }, 738 739 start: function() { 740 if (this.options.onstart) { 741 this.options.onstart.apply(this, arguments); 742 } 743 744 return showHide.call(this, '.rich-status-start'); 745 }, 746 747 stop: function() { 748 if (this.options.onstop) { 749 this.options.onstop.apply(this, arguments); 750 } 751 }, 752 753 success: function() { 754 if (this.options.onsuccess) { 755 this.options.onsuccess.apply(this, arguments); 756 } 757 this.stop(); 758 759 return showHide.call(this, '.rich-status-stop'); 760 }, 761 762 error: function() { 763 if (this.options.onerror) { 764 this.options.onerror.apply(this, arguments); 765 } 766 this.stop(); 767 768 return showHide.call(this, ':not(.rich-status-error) + .rich-status-stop, .rich-status-error'); 769 } 770 }; 771 }())); 772 773 var ajaxOnComplete = function (data) { 774 var type = data.type; 775 var responseXML = data.responseXML; 776 777 if (data.type == 'event' && data.status == 'complete' && responseXML) { 778 var partialResponse = jQuery(responseXML).children("partial-response"); 779 if (partialResponse && partialResponse.length) { 780 var elements = partialResponse.children('changes').children('update, delete'); 781 jQuery.each(elements, function () { 782 richfaces.cleanDom(jQuery(this).attr('id')); 783 }); 784 } 785 } 786 }; 787 // move this code to somewhere 788 if (typeof jsf != 'undefined') { 789 jsf.ajax.addOnEvent(ajaxOnComplete); 790 } 791 if (window.addEventListener) { 792 window.addEventListener("unload", richfaces.cleanDom, false); 793 } else { 794 window.attachEvent("onunload", richfaces.cleanDom); 795 } 796 }(jQuery, RichFaces)); 797 798