1 2 3/* ==== INCLUDE: /js/weblets/responsive/menu.js ==== */ 4 5jQuery( function($) { 6 7 function calcHorizontalLeaning( $menuitem ) { 8 9 var left = $menuitem.offset().left; 10 var width = $menuitem.outerWidth(); 11 var screen = $(document).width(); 12 13 var rightEdge = screen - ( left + width ); 14 var tolerance = screen / 4; // quarter of screen 15 console.log( "rightedge", rightEdge, "tolerance", tolerance ); 16 if( rightEdge < tolerance ) { 17 return "left"; 18 } 19 else { 20 return ""; 21 } 22 } 23 24 $( document ).on ( "click", ".mrp-menu-resp-wrapper .top-handle", function( e ) { 25 e.stopImmediatePropagation(); 26 $(this).toggleClass( "on" ); 27 $("html").toggleClass( "mrp-menu-on" ); 28 return false; 29 }); 30 31 $( document ).on( "click", ".mrp-menu-resp-wrapper li", function(e) { 32 //if( e.target && e.target.nodeName && e.target.nodeName.toLowerCase() == "a" ) { 33 if( e.target && e.target.nodeName && $(e.target).closest("a").length > 0 ) { 34 var $target = $(e.target).closest("a"); 35 if( $target.attr( "href" ) && $target.attr( "href" ) != "javascript:;" ) { 36 return; 37 } 38 } 39 var $on = $( this ).closest( "li" ); 40 var $menu = $(this).closest( ".mrp-menu-resp-wrapper" ); 41 if( $on.hasClass( "on" ) ) { 42 $on.parent().removeClass( "on" ); 43 $on.removeClass( "on" ); 44 $on.find( ".on" ).removeClass( "on left" ); 45 return false; 46 } 47 $menu.find( "li.on" ).filter( function() { 48 return !( $.contains( this, $on[0] ) ); // this not $(this)!! it's correct 49 }).removeClass( "on left" ).parent().removeClass( "on" ); 50 51 $on.addClass( "on" ); 52 $on.addClass( calcHorizontalLeaning( $on ) ); 53 $on.parent().addClass( "on" ); 54 55 return false; 56 }); 57 58 $( document ).on( "click.mrp-menu-close", function() { 59 $( ".mrp-menu-resp-wrapper .on" ).removeClass( "on left" ); 60 $("html").removeClass( "mrp-menu-on" ); 61 }); 62}); 63 64/* ==== INCLUDE: /js/weblets/responsive/image-banner.js ==== */ 65 66function mrp_image_banner_rotate( id, index ) { 67 68 var $weblet = jQuery( "#" + id ); 69 70 if( index >= 0 ) { 71 var interval = $weblet.data( "animation-interval" ); 72 if( !isNaN( interval ) ) { 73 clearInterval( interval ); 74 } 75 } 76 77 var $active = $weblet.find( "ul.images li.mrp-resp-banner-show" ); 78 if( isNaN( index ) || index < 0 ) {
79 index = parseInt( $active.attr( "data-idx" ) ); 80 if( isNaN( index ) ) { 81 return; 82 } 83 index++; 84 } 85 86 var $next = $weblet.find( "ul.images li[data-idx=" + index + "]" ); 87 if( $next.length == 0 ) { 88 // first 89 index = 0; 90 } 91 $next = $weblet.find( "ul.images li[data-idx=" + index + "]" ); 92 93 if( $next.length > 0 ) { 94 $weblet.find( "ul.images li" ).removeClass( "mrp-resp-banner-show" ); 95 $next.addClass( "mrp-resp-banner-show" ); 96 97 $weblet.find( ".bullet-nav span.active" ).removeClass( "active" ); 98 $weblet.find( ".bullet-nav span[data-idx=" + index + "]" ).addClass( "active" ); 99 } 100} 101 102function mrp_banner_nav_next( id ) { 103 104 var $weblet = jQuery( "#" + id ); 105 var $active = $weblet.find( "ul.images li.mrp-resp-banner-show" ); 106 var idx = parseInt( $active.attr( "data-idx" ) ) || 0; 107 idx = idx + 1; 108 if( idx > $weblet.find( ".images > li img" ).length - 1 ) { 109 idx = 0; 110 } 111 mrp_image_banner_rotate( id, idx ); 112 113 try { 114 var interval = $weblet.data( "animation-interval" ); 115 if( !isNaN( interval ) ) { 116 clearInterval( interval ); 117 } 118 } 119 catch( e ) {} 120} 121 122function mrp_banner_nav_prev( id ) { 123 124 var $weblet = jQuery( "#" + id ); 125 var $active = $weblet.find( "ul.images li.mrp-resp-banner-show" );
126 var idx = parseInt( $active.attr( "data-idx" ) ) || 0; 127 idx = idx - 1; 128 if( idx < 0 ) { 129 idx = $weblet.find( ".images > li img" ).length - 1; 130 } 131 mrp_image_banner_rotate( id, idx ); 132 133 134 try { 135 var interval = $weblet.data( "animation-interval" ); 136 if( !isNaN( interval ) ) { 137 clearInterval( interval ); 138 } 139 } 140 catch( e ) {} 141} 142 143/* ==== INCLUDE: /w/w=realtorchad.ca/dwr/engine.js ==== */ 144 145/* 146 * Copyright 2005 Joe Walker 147 * 148 * Licensed under the Apache License, Version 2.0 (the "License"); 149 * you may not use this file except in compliance with the License. 150 * You may obtain a copy of the License at 151 * 152 * http://www.apache.org/licenses/LICENSE-2.0 153 * 154 * Unless required by applicable law or agreed to in writing, software 155 * distributed under the License is distributed on an "AS IS" BASIS, 156 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 157 * See the License for the specific language governing permissions and 158 * limitations under the License. 159 */ 160 161/** 162 * The DWR object is also defined by dwr.util etc. 163 */ 164if (typeof dwr == 'undefined') dwr = {}; 165 166(function() { 167 if (!dwr.engine) dwr.engine = {}; 168 169 /** 170 * Set an alternative error handler from the default alert box. 171 * @param {Function} handler The function to call when an error happens 172 */ 173 dwr.engine.setErrorHandler = function(handler) { 174 dwr.engine._errorHandler = handler; 175 }; 176 177 /** 178 * Set an alternative warning handler from the default alert box. 179 * @param {Function} handler The function to call when a warning happens 180 */ 181 dwr.engine.setWarningHandler = function(handler) { 182 dwr.engine._warningHandler = handler; 183 }; 184 185 /** 186 * @deprecated - In favor of the errorHandler being called with an exception name:"dwr.engine.textHtmlReply" 187 * containing a responseText property if available. 188 * 189 * Setter for the text/html handler - what happens if a DWR request gets an HTML 190 * reply rather than the expected Javascript. Often due to login timeout 191 * @param {Function} handler The function to call on an unexpected text/html content type 192 */ 193 dwr.engine.setTextHtmlHandler = function(handler) { 194 dwr.engine._textHtmlHandler = handler; 195 }; 196 197 dwr.engine.setPollStatusHandler = function(handler) { 198 dwr.engine._pollStatusHandler = handler; 199 }; 200 201 /** 202 * Set a default timeout value for all calls. 0 (the default) turns timeouts off. 203 * @param {Function} handler The function to call when we get bored of waiting for a call 204 */ 205 dwr.engine.setTimeout = function(timeout) { 206 dwr.engine._timeout = timeout; 207 }; 208 209 /** 210 * Set a timeout value for Reverse Ajax polls. 0 (the default) turns timeouts off. 211 */ 212 dwr.engine.setPollTimeout = function(timeout) { 213 dwr.engine._pollTimeout = timeout; 214 }; 215 216 /** 217 * The Pre-Hook is called before any DWR remoting is done. 218 * @param {Function} handler The function to call before any remote calls 219 */ 220 dwr.engine.setPreHook = function(handler) { 221 dwr.engine._preHook = handler; 222 }; 223 224 /** 225 * The Post-Hook is called after any DWR remoting is done. 226 * @param {Function} handler The function to call after any remote calls 227 */ 228 dwr.engine.setPostHook = function(handler) { 229 dwr.engine._postHook = handler; 230 }; 231 232 /** 233 * Set a custom path to the DWR servlet (may be a full URL for cross-domain usage) 234 * @param {string} path path or URL 235 */ 236 dwr.engine.setOverridePath = function(path) { 237 dwr.engine._overridePath = path; 238 }; 239 240 /** 241 * Set a custom contextPath (typically used when rewriting paths through a web server) 242 * @param {string} path path 243 */ 244 dwr.engine.setOverrideContextPath = function(path) { 245 dwr.engine._overrideContextPath = path; 246 }; 247 248 /** 249 * Extra attributes to append to the DWRSESSIONID cookie (domain, secure, etc) 250 * @param {string} attributeString attribute string according to cookie syntax 251 */ 252 dwr.engine.setCookieAttributes = function(attributeString) { 253 dwr.engine._cookieAttributes = attributeString; 254 }; 255 256 /** 257 * Custom headers for all DWR calls 258 * @param {Object} headers Object containing name/value pairs for extra headers 259 */ 260 dwr.engine.setHeaders = function(headers) { 261 dwr.engine._headers = headers; 262 }; 263 264 /** 265 * Custom attributes transferred to server for all DWR calls 266 * @param {Object} attributes Object containing name/value pairs for attributes 267 */ 268 dwr.engine.setAttributes = function(attributes) { 269 dwr.engine._attributes = attributes; 270 }; 271 272 /** 273 * Ensure that remote calls happen in the order in which they were sent? (Default: false) 274 * @param {boolean} ordered true to enable ordered processing 275 */ 276 dwr.engine.setOrdered = function(ordered) { 277 dwr.engine._ordered = ordered; 278 }; 279 280 /** 281 * Do we ask the XHR object to be asynchronous? (Default: true) 282 * Warning: it is <strong>highly</strong> advised to use the default of async 283 * processing, especially when dealing with Internet based requests. 284 * @param {boolean} async false to enable sync processing for XHR queries 285 */ 286 dwr.engine.setAsync = function(async) { 287 dwr.engine._async = async; 288 }; 289 290 /** 291 * Does the client actively check the server for updates? (Default: false) 292 * @param {boolean} async true to enable low latency reverse ajax 293 */ 294 dwr.engine.setActiveReverseAjax = function(activeReverseAjax) { 295 if (activeReverseAjax) { 296 // Bail if we are already started 297 if (dwr.engine._activeReverseAjax) return; 298 // We always want a retry policy when reverse AJAX is enabled. 299 if (!dwr.engine._retryIntervals || dwr.engine._retryIntervals.length === 0) { dwr.engine._retryIntervals = dwr.engine._defaultRetryIntervals; } 300 dwr.engine._activeReverseAjax = true; 301 if (dwr.engine._initializing) return; // We'll start after init is done 302 dwr.engine._poll(); 303 } 304 else { 305 // Can we cancel an existing request? 306 if (dwr.engine._activeReverseAjax && dwr.engine._pollBatch) { 307 dwr.engine.transport.abort(dwr.engine._pollBatch); 308 } 309 dwr.engine._activeReverseAjax = false;
310 } 311 // TODO: in iframe mode, if we start, stop, start then the second start may 312 // well kick off a second iframe while the first is still about to return 313 // we should cope with this but we don't 314 }; 315 316 /** 317 * Turn server notification of page unload on and off 318 * @param {boolean} notify true or false depending on if we want to turn unload notification on or off 319 */ 320 dwr.engine.setNotifyServerOnPageUnload = function(notify, asyncUnload) { 321 dwr.engine._asyncUnload = (asyncUnload !== undefined) ? asyncUnload : false; 322 dwr.engine._isNotifyServerOnPageUnload = notify; 323 }; 324 325 /** 326 * Turn server notification of page load on and off 327 * @param {boolean} notify true or false depending on if we want to turn load notification on or off 328 */ 329 dwr.engine.setNotifyServerOnPageLoad = function(notify) { 330 dwr.engine._isNotifyServerOnPageLoad = notify; 331 if (notify && !dwr.engine._initializing && !dwr.engine._isNotifyServerOnPageLoadSent) { 332 eval("dwr.engine._execute(dwr.engine._pathToDwrServlet, '__System', 'pageLoaded', [ function() { dwr.engine._ordered = false; }]);"); 333 dwr.engine._isNotifyServerOnPageLoadSent = true; 334 } 335 }; 336 337 /* 338 * The maximum number of retries, when reached we will no longer attempt to check for server status. 339 * @param - maxRetries 340 */ 341 dwr.engine.setMaxRetries = function(maxRetries) { 342 dwr.engine._maxRetries = maxRetries; 343 }; 344 345 /* 346 * The intervals between successive retries in seconds 347 * @param - array of integers representing the retry interval in seconds. 348 */ 349 dwr.engine.setRetryIntervals = function(intervalsArray) { 350 dwr.engine._retryIntervals = intervalsArray; 351 }; 352 353 /** 354 * The default message handler. 355 * @param {String} message The text of the error message 356 * @param {Object} ex An error object containing at least a name and message 357 */ 358 dwr.engine.defaultErrorHandler = function(message, ex) { 359 dwr.engine._debug("Error: " + ex.name + ", " + ex.message, true); 360 if (!message) message = "A server error has occurred."; 361 // Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky 362 if (message.indexOf("0x80040111") != -1) return; 363 if ("true" == "true") alert(message); 364 }; 365 366 /** 367 * The default warning handler. 368 * @param {String} message The text of the error message 369 * @param {Object} ex An error object containing at least a name and message 370 */ 371 dwr.engine.defaultWarningHandler = function(message, ex) { 372 dwr.engine._debug(message); 373 }; 374 375 /** 376 * The default poll status handler. 377 * @param {boolean} newStatus - true = online, false = offline 378 * @param {object} ex - The exception if one exists (offline). 379 */ 380 dwr.engine.defaultPollStatusHandler = function(newStatus, ex) { 381 dwr.engine.util.logHandlerEx(function() { 382 if (newStatus === false && dwr.engine._errorHandler) dwr.engine._errorHandler(ex.message, ex); 383 }); 384 }; 385 386 /** 387 * For reduced latency you can group several remote calls together using a batch. 388 */ 389 dwr.engine.beginBatch = function() { 390 if (dwr.engine._batch) { 391 dwr.engine._handleError(null, { name:"dwr.engine.batchBegun", message:"Batch already begun" }); 392 return; 393 } 394 dwr.engine._batch = dwr.engine.batch.create(); 395 }; 396 397 /** 398 * Finished grouping a set of remote calls together. Go and execute them all. 399 * @param {Object} options A options object to customize processing 400 */ 401 dwr.engine.endBatch = function(options) { 402 var batch = dwr.engine._batch; 403 if (batch == null) { 404 dwr.engine._handleError(null, { name:"dwr.engine.batchNotBegun", message:"No batch in progress" }); 405 return; 406 } 407 dwr.engine._batch = null; 408 if (batch.map.callCount === 0) { 409 return; 410 } 411 412 // The hooks need to be merged carefully to preserve ordering 413 if (options) { 414 dwr.engine.batch.merge(batch, options); 415 } 416 417 // In ordered mode, we don't send unless the list of sent items is empty 418 // (queuing is not possible for sync calls so let them slip through) 419 if (batch.async && (dwr.engine._ordered || dwr.engine._internalOrdered) && dwr.engine._batchesLength !== 0) { 420 dwr.engine._batchQueue[dwr.engine._batchQueue.length] = batch; 421 } 422 else { 423 return dwr.engine.transport.send(batch); 424 } 425 }; 426 427 /** 428 * For use with file downloads. When a DWR function returns a binary download 429 * you can prompt the user to save it using this function 430 * @param {Object} data The binary data passed from DWR 431 */ 432 dwr.engine.openInDownload = function(data) { 433 var div = document.createElement("div"); 434 document.body.appendChild(div); 435 div.innerHTML = "<iframe width='0' height='0' scrolling='no' frameborder='0' src='" + data + "'></iframe>"; 436 }; 437 438 /** 439 * What is the current version DWR number
440 * DWR version numbers are of the form "Version 1.2.3.3128[.beta]", where: 441 * 1 is the major release number. Changes in major version number indicate 442 * significant enhancements in functionality 443 * 2 is the minor release number. Changes in minor version number indicate 444 * less significant changes in functionality 445 * 3 is the revision release number. Changes here typically indicate bug 446 * fixes only 447 * 3128 is the build number. This number increments for each build 448 * .beta is a release title that is generally only used for non production 449 * releases to indicate the purpose/quality of the release 450 * The label is these strings concatenated 451 */ 452 dwr.version = { 453 /** 454 * Changes in major version number indicate significant enhancements 455 */ 456 major:parseInt("3", 10), 457 458 /** 459 * Changes in minor version number indicate smaller enhancements 460 */ 461 minor:parseInt("0", 10), 462 463 /** 464 * Changes with the revision number typically indicate bug-fixes only 465 */ 466 revision:parseInt("3", 10), 467 468 /** 469 * The build number increments for each build 470 */ 471 build:parseInt("-1", 10), 472 473 /** 474 * Only used for non production releases to indicate the purpose/quality of 475 * the release. Example titles include 'milestone1' or 'beta3'. 476 */ 477 title:"dev", 478 479 /** 480 * The strings above concatenated 481 */ 482 label:"3.0.3-dev" 483 }; 484 485 //============================================================================== 486 // Only private stuff below here 487 //============================================================================== 488 489 /** Keep track of page load initialization */ 490 dwr.engine._initializing = true; 491 492 /** Is GET enabled? */ 493 dwr.engine._allowGetButMakeForgeryEasier = "true"; 494 495 /** The script prefix to strip in the case of scriptTagProtection. */ 496 dwr.engine._scriptTagProtection = "throw 'allowScriptTagRemoting is false.';"; 497 498 /** The default path to the DWR servlet */ 499 dwr.engine._pathToDwrServlet = "/w/w=realtorchad.ca/dwr"; 500 501 /** Custom path to the DWR servlet */ 502 dwr.engine._overridePath = ""; 503 if (typeof pathToDwrServlet != "undefined") { 504 dwr.engine._overridePath = pathToDwrServlet; 505 } 506 507 dwr.engine._effectivePath = function() { 508 return dwr.engine._overridePath || dwr.engine._pathToDwrServlet; 509 }; 510 511 /** The webapp's contextPath */ 512 dwr.engine._contextPath = ""; 513 514 /** Custom contextPath */ 515 dwr.engine._overrideContextPath = ""; 516 517 dwr.engine._effectiveContextPath = function() { 518 return dwr.engine._overrideContextPath || dwr.engine._contextPath; 519 }; 520 521 /** Extra attributes to append to the DWRSESSIONID cookie (domain, secure, etc) */ 522 dwr.engine._cookieAttributes = ""; 523 524 /** Do we use XHR for reverse ajax because we are not streaming? */ 525 dwr.engine._useStreamingPoll = "true"; 526 527 dwr.engine._pollOnline = true; 528 529 /** These URLs can be configured from the server */ 530 dwr.engine._ModePlainCall = "/call/plaincall/"; 531 dwr.engine._ModePlainPoll = "/call/plainpoll/"; 532 dwr.engine._ModeHtmlCall = "/call/htmlcall/"; 533 dwr.engine._ModeHtmlPoll = "/call/htmlpoll/"; 534 535 /** Do we make the calls async? Default to 'true' */ 536 dwr.engine._async = Boolean("true"); 537 538 /** The local page id */ 539 dwr.engine._pageId = null; 540 541 /** The browser instance id */ 542 dwr.engine._dwrSessionId = null; 543 544 /** The global page id (browser instance + local page id) */ 545 dwr.engine._scriptSessionId = ""; // we must use "" and not null 546 547 /** A function to be called before requests are marshalled. Can be null. */ 548 dwr.engine._preHook = null; 549 550 /** A function to be called after replies are received. Can be null. */ 551 dwr.engine._postHook = null; 552 553 /** A map of the batches that we have sent and are awaiting a reply on. */ 554 dwr.engine._batches = {}; 555 556 /** A count of the number of outstanding batches. Should be == to _batches.length unless prototype has messed things up */ 557 dwr.engine._batchesLength = 0; 558 559 /** In ordered mode, the array of batches waiting to be sent */ 560 dwr.engine._batchQueue = []; 561 562 /** User setting for saying that calls should execute one by one in the order in which they were sent */ 563 dwr.engine._ordered = false;
564 565 /** Internal state forcing calls to execute one by one in the order in which they were sent */ 566 dwr.engine._internalOrdered = false; 567 568 /** The current batch (if we are in batch mode) */ 569 dwr.engine._batch = null; 570 571 /** The global timeout */ 572 dwr.engine._timeout = 0; 573 574 /** Are we doing comet or polling? */ 575 dwr.engine._activeReverseAjax = false; 576 577 /** All reverse ajax scripts before this index have been received and executed */ 578 dwr.engine._nextReverseAjaxIndex = 0; 579 580 /** Queue of reverse ajax scripts (functions) that have been received but not yet executed due to receiving in wrong order */ 581 dwr.engine._reverseAjaxQueue = {}; 582 583 /** The batch that we are using to poll */ 584 dwr.engine._pollBatch = null; 585 586 /** Maximum time we wait for a poll to return (0 = no timeout) */ 587 dwr.engine._pollTimeout = 0; 588 589 /** How many milliseconds between internal comet polls */ 590 dwr.engine._pollCometInterval = 200; 591 592 /** How many times have we re-tried a call? */ 593 dwr.engine._retries = 1; 594 dwr.engine._maxRetries = -1; 595 596 /** The intervals between successive retries in seconds */ 597 dwr.engine._retryIntervals = []; 598 599 /** Used as the default for reverse ajax/polling 600 * Retry immediately twice first with a one second interval, next with a 3 second interval, then go offline. 601 * Therafter, retry every 3 seconds. 602 */ 603 dwr.engine._defaultRetryIntervals = [ 1, 3, 3 ]; 604 605 dwr.engine._textHtmlHandler = null; 606 607 /** If you wish to send custom headers with every request */ 608 dwr.engine._headers = null; 609 610 /** If you wish to send extra custom request attributes with each request */ 611 if (typeof attributes != "undefined") { 612 dwr.engine._attributes = attributes; 613 } 614 else { 615 dwr.engine._attributes = null; 616 } 617 618 /** Batch ids allow us to know which batch the server is answering */ 619 dwr.engine._nextBatchId = 0; 620 621 /** The instance id helps us distinguish multiple engine.js loaded in one page */ 622 dwr.engine._instanceId = -1; 623 624 /** A list of the properties that need merging from calls to a batch */ 625 dwr.engine._propnames = [ "async", "timeout", "errorHandler", "warningHandler", "textHtmlHandler" ]; 626 627 /** Do we stream, or can be hacked to do so? */ 628 dwr.engine._partialResponseNo = 0; 629 dwr.engine._partialResponseYes = 1; 630 631 /** Are we notifying the server on page unload? */ 632 dwr.engine._isNotifyServerOnPageUnload = false; 633 634 /** Are we notifying the server on page load? */ 635 dwr.engine._isNotifyServerOnPageLoad = false; 636 dwr.engine._isNotifyServerOnPageLoadSent = false; 637 638 /** Should the unload call be asynchronous? If true it may not be called by the browser. */ 639 dwr.engine._asyncUnload = false; 640 641 /** 642 * A map of all mapped classes whose class declarations have been loaded 643 * (dwrClassName -> constructor function) 644 */ 645 dwr.engine._mappedClasses = {}; 646 647 /** A function to call if something fails. */ 648 dwr.engine._errorHandler = dwr.engine.defaultErrorHandler; 649 650 /** For debugging when something unexplained happens. */ 651 dwr.engine._warningHandler = dwr.engine.defaultWarningHandler; 652 653 dwr.engine._pollStatusHandler = dwr.engine.defaultPollStatusHandler; 654 655 /** Undocumented interceptors - do not use */ 656 dwr.engine._postSeperator = "\n"; 657 dwr.engine._defaultInterceptor = function(data) { return data; }; 658 dwr.engine._urlRewriteHandler = dwr.engine._defaultInterceptor; 659 dwr.engine._contentRewriteHandler = dwr.engine._defaultInterceptor; 660 dwr.engine._replyRewriteHandler = dwr.engine._defaultInterceptor; 661 662 /** Object attributes to ignore when serializing */ 663 dwr.engine._excludeObjectAttributes = { 664 "$dwrClassName": true, 665 "$dwrByRef": true, 666 "$_dwrConversionRef": true 667 }; 668 669 dwr.engine._ieConditions = {}; 670 671 /** Is this page doing beforeunload processing? */ 672 dwr.engine._beforeUnloading = false; 673 674 /** A place to temporarily store exception before reporting */ 675 dwr.engine._queuedBatchException = null; 676 677 /** @private Keep beforeunload flag set during beforeunload processing (including potential 678 * "Are you sure?" popups). */ 679 dwr.engine._beforeUnloader = function() { 680 dwr.engine._beforeUnloading = true; 681 // We want to reset the flag if unloading is aborted by "Are you sure?" popup. We detect
682 // return to interactive mode with a timeout that normally will execute directly after 683 // unload is aborted through popup. 684 setTimeout(function() { 685 // Some browsers (f ex Firefox) will execute the timeout even if unload is not aborted. 686 // To keep a continuous unloading state we therefore prolong the beforeunload state for 687 // a second so the unload flag gets time to trigger. 688 setTimeout(function() { 689 dwr.engine._beforeUnloading = false; 690 }, 1000); 691 }, 1); 692 }; 693 694 /** Is this page in the process of unloading? */ 695 dwr.engine._unloading = false; 696 697 /** @private Abort any XHRs in progress at page unload (solves zombie socket problems in IE). */ 698 dwr.engine._unloader = function() { 699 dwr.engine._unloading = true; 700 701 // Empty queue of waiting ordered requests 702 dwr.engine._batchQueue.length = 0; 703 704 // Abort any ongoing requests and clear their batches 705 var batch; 706 for (var batchId in dwr.engine._batches) { 707 batch = dwr.engine._batches[batchId]; 708 if (batch.transport && batch.transport.abort) { 709 batch.transport.abort(batch); 710 } 711 } 712 713 // If we have used reverse ajax then we try to tell the server we are gone 714 if (dwr.engine._isNotifyServerOnPageUnload) { 715 dwr.engine._debug("calling unloader for: " + dwr.engine._scriptSessionId); 716 batch = { 717 map:{ 718 callCount:1, 719 'c0-scriptName':'__System', 720 'c0-methodName':'pageUnloaded', 721 'c0-id':0 722 }, 723 paramCount:0, isPoll:false, async:dwr.engine._asyncUnload, 724 headers:{}, preHooks:[], postHooks:[], 725 timeout:dwr.engine._timeout, 726 errorHandler:null, globalErrorHandler:dwr.engine._errorHandler, warningHandler:null, textHtmlHandler:null, globalTextHtmlHandler:null, 727 handlers:[{ exceptionHandler:null, callback:null }] 728 }; 729 dwr.engine.transport.send(batch); 730 dwr.engine._isNotifyServerOnPageUnload = false; 731 } 732 }; 733 734 function ignoreIfUnloading(batch, f) { 735 // Do nothing if we are unloading 736 if (dwr.engine._unloading) return; 737 // Retry later if beforeunload is in progress and this is not a sync call 738 if (dwr.engine._beforeUnloading && (batch == null || batch.async)) { 739 setTimeout(function() { 740 ignoreIfUnloading(batch, f); 741 }, 100); 742 } 743 // Otherwise trigger the target func 744 else { 745 return f(); 746 } 747 } 748 749 dwr.engine._initializer = { 750 /* 751 * Load-time initializations 752 */ 753 preInit: function() { 754 // Make random local page id 755 dwr.engine._pageId = dwr.engine.util.tokenify(new Date().getTime()) + "-" + dwr.engine.util.tokenify(Math.random() * 1E16); 756 757 // Reuse any existing dwr session 758 dwr.engine.transport.updateDwrSessionFromCookie(); 759 760 // Register unload handlers 761 dwr.engine.util.addEventListener(window, 'beforeunload', dwr.engine._beforeUnloader); 762 dwr.engine.util.addEventListener(window, 'unload', dwr.engine._unloader); 763 764 // Register this engine instance globally 765 var g = dwr.engine._global; 766 if (!g.dwr) g.dwr = {}; 767 if (!g.dwr._) g.dwr._ = []; 768 dwr.engine._instanceId = g.dwr._.length; 769 g.dwr._[dwr.engine._instanceId] = dwr; 770 }, 771 772 loadDwrConfig: function() { 773 if (typeof dwrConfig != "undefined") { 774 for(p in dwrConfig) { 775 var methodName = "set" + p.charAt(0).toUpperCase() + p.substring(1); 776 var setter = dwr.engine[methodName]; 777 if (setter) setter(dwrConfig[p]); 778 } 779 } 780 }, 781 782 init: function() { 783 dwr.engine._initializer.preInit(); 784 dwr.engine._initializer.loadDwrConfig(); 785 dwr.engine._initializing = false; 786 // Now we can start sending requests 787 if (dwr.engine._isNotifyServerOnPageLoad) { 788 eval("dwr.engine._execute(dwr.engine._pathToDwrServlet, '__System', 'pageLoaded', [ function() { dwr.engine._ordered = false; }]);"); 789 dwr.engine._isNotifyServerOnPageLoadSent = true; 790 } 791 if (dwr.engine._activeReverseAjax) { 792 dwr.engine._poll(); 793 } 794 } 795 }; 796 797 /** 798 * Send a request. Called by the JavaScript interface stub 799 * @private 800 * @param path part of URL after the host and before the exec bit without leading or trailing /s 801 * @param scriptName The class to execute 802 * @param methodName The method on said class to execute 803 * @param func The callback function to which any returned data should be passed 804 * if this is null, any returned data will be ignored 805 * @param args The parameters to passed to the above method 806 */ 807 dwr.engine._execute = function(overridePath, scriptName, methodName, args) { 808 var path = overridePath || dwr.engine._effectivePath(); 809 dwr.engine._singleShot = false;
810 if (dwr.engine._batch == null) { 811 dwr.engine.beginBatch(); 812 dwr.engine._singleShot = true; 813 } 814 815 var batch = dwr.engine._batch; 816 817 // All the paths MUST be to the same servlet 818 if (batch.path == null) { 819 batch.path = path; 820 } 821 else { 822 if (batch.path != path) { 823 dwr.engine._handleError(batch, { name:"dwr.engine.multipleServlets", message:"Can't batch requests to multiple DWR Servlets." }); 824 return; 825 } 826 } 827 828 dwr.engine.batch.addCall(batch, scriptName, methodName, args); 829 830 if (dwr.engine._isHeartbeatBatch(batch)) { 831 // Heartbeats should fail fast. 832 batch.timeout = 750; 833 } 834 835 // Now we have finished remembering the call, we increment the call count 836 batch.map.callCount++; 837 if (dwr.engine._singleShot) { 838 return dwr.engine.endBatch(); 839 } 840 }; 841 842 /** 843 * Poll the server to see if there is any data waiting 844 * @private 845 */ 846 dwr.engine._poll = function() { 847 if (!dwr.engine._activeReverseAjax) { 848 return; 849 } 850 dwr.engine._pollBatch = dwr.engine.batch.createPoll(); 851 dwr.engine.transport.send(dwr.engine._pollBatch); 852 }; 853 854 /** @private Utility to execute incoming scripts */ 855 dwr.engine._executeScript = function(script) { 856 if (script == null) { 857 return null; 858 } 859 if (script === "") { 860 dwr.engine._debug("Warning: blank script", true); 861 return null; 862 } 863 // Using Function instead of eval as the latter has memory problems on IE9 864 (new Function("dwr", script))(dwr); 865 }; 866 867 /** @private call all the post hooks for a batch */ 868 dwr.engine._callPostHooks = function(batch) { 869 if (batch.postHooks) { 870 for (var i = 0; i < batch.postHooks.length; i++) { 871 batch.postHooks[i](); 872 } 873 batch.postHooks = null; 874 } 875 }; 876 877 /** 878 * Is this a batch that is checking for a heartbeat? 879 * If so we want to ignore errors/warnings, etc. 880 * @private 881 * @param {Object} batch 882 */ 883 dwr.engine._isHeartbeatBatch = function(batch) { 884 return (batch.map && batch.map['c0-methodName'] === 'checkHeartbeat' && batch.map['c0-scriptName'] === '__System'); 885 }; 886 887 /** 888 * Generic error handling routing to save having null checks everywhere 889 * @private 890 * @param {Object} batch 891 * @param {Object} ex 892 */ 893 dwr.engine._handleError = function(batch, ex) { 894 if (batch && dwr.engine._isHeartbeatBatch(batch)) { // Ignore errors that happen while checking for a heartbeat. 895 return; 896 } 897 var errorHandlers = []; 898 var anyCallWithoutErrorHandler = false; 899 if (batch && batch.isPoll) { // No error reporting and only retry for polls 900 dwr.engine._handlePollRetry(batch, ex); 901 } else { 902 // Perform error cleanup synchronously 903 if (batch) { 904 for (var i = 0; i < batch.map.callCount; i++) { 905 var handlers = batch.handlers[i]; 906 if (!handlers.completed) { 907 if (typeof handlers.errorHandler == "function") 908 errorHandlers.push(handlers.errorHandler); 909 else 910 anyCallWithoutErrorHandler = true; 911 handlers.completed = true; 912 } 913 } 914 } 915 // Perform error reporting asynchronously (possibly) 916 ignoreIfUnloading(batch, function() { 917 if (batch) { 918 var textHtmlHandler = batch.textHtmlHandler || batch.globalTextHtmlHandler; 919 if (ex.name === "dwr.engine.textHtmlReply" && textHtmlHandler) { 920 dwr.engine.util.logHandlerEx(function() { 921 textHtmlHandler(ex); 922 }); 923 return; 924 } 925 } 926 dwr.engine._prepareException(ex); 927 var errorHandler; 928 while(errorHandlers.length > 0) { 929 errorHandler = errorHandlers.shift(); 930 dwr.engine.util.logHandlerEx(function() { 931 errorHandler(ex.message, ex); 932 }); 933 } 934 if (batch) { 935 dwr.engine.util.logHandlerEx(function() { 936 if (typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex); 937 else if (anyCallWithoutErrorHandler) { 938 if (typeof batch.globalErrorHandler == "function") batch.globalErrorHandler(ex.message, ex); 939 } 940 }); 941 dwr.engine.batch.remove(batch); 942 } 943 }); 944 } 945 }; 946 947 /** 948 * Handle retries for polling as well as online/offline status. 949 * @private 950 * @param {Object} batch 951 * @param {Object} ex 952 */ 953 dwr.engine._handlePollRetry = function(batch, ex) { 954 var retryInterval = dwr.engine._getRetryInterval(); 955 // If number of retries is equal to the retryIntervals array length, call the supplied pollStatusHandler and go offline. 956 if (dwr.engine._retries === dwr.engine._retryIntervals.length - 1) { 957 dwr.engine._debug("poll retry - going offline: " + retryInterval/1000 + " seconds"); 958 dwr.engine._handlePollStatusChange(false, ex, batch); 959 // We are offline, continue to check for a heartbeat until _retries is > _maxRetries. 960 var heartbeatInterval = setInterval(function() { 961 if (dwr.engine._maxRetries === -1 || dwr.engine._retries < dwr.engine._maxRetries) { 962 dwr.engine._execute(null, '__System', 'checkHeartbeat', [ function() { 963 // We found a heartbeat, we are back online! 964 clearInterval(heartbeatInterval); 965 dwr.engine._poll(); 966 }]); 967 dwr.engine._retries++; 968 dwr.engine._debug("DWR Offline - poll retry - interval: " + retryInterval/1000 + " seconds"); 969 } else { 970 // maxRetries has been reached, stop the heartbeat check. 971 clearInterval(heartbeatInterval); 972 dwr.engine._debug("max retries reached, stop polling for server status."); 973 dwr.engine._handlePollStatusChange(false, ex, batch); 974 } 975 }, retryInterval); 976 } else { // We are still online, poll again. 977 dwr.engine._retries++; 978 dwr.engine.batch.remove(batch); 979 dwr.engine._debug("DWR Online - poll retry - interval: " + retryInterval/1000 + " seconds"); 980 setTimeout(dwr.engine._poll, retryInterval); 981 } 982 }; 983 984 dwr.engine._getRetryInterval = function() { 985 var retryInterval; 986 if (dwr.engine._retries < dwr.engine._retryIntervals.length) { 987 retryInterval = dwr.engine._retryIntervals[dwr.engine._retries] * 1000; 988 } else { 989 // The last interval in retryIntervals is the number that will be used to poll when offline. 990 retryInterval = dwr.engine._retryIntervals[dwr.engine._retryIntervals.length - 1] * 1000; 991 } 992 return retryInterval; 993 }; 994 995 /** 996 * Handles polling status changes - online or offline. 997 * @param {boolean} newStatus - true = online, false = offline 998 * @param {object} ex - The exception if one exists (offline). 999 */ 1000 dwr.engine._handlePollStatusChange = function(newStatus, ex, batch) { 1001 if (batch.isPoll || dwr.engine._isHeartbeatBatch(batch)) { 1002 var changed = dwr.engine._pollOnline !== newStatus; 1003 var maxRetriesReached = dwr.engine._maxRetries === dwr.engine._retries; 1004 dwr.engine._pollOnline = newStatus; 1005 if ((changed || maxRetriesReached) && typeof dwr.engine._pollStatusHandler === "function") 1006 dwr.engine.util.logHandlerEx(function() { 1007 dwr.engine._pollStatusHandler(newStatus, ex, maxRetriesReached); 1008 }); 1009 if (newStatus) { 1010 dwr.engine._retries = 1; 1011 } 1012 } 1013 }; 1014 1015 /** 1016 * Generic error handling routing to save having null checks everywhere. 1017 * @private 1018 * @param {Object} batch 1019 * @param {Object} ex 1020 */ 1021 dwr.engine._handleWarning = function(batch, ex) { 1022 if (batch && dwr.engine._isHeartbeatBatch(batch)) { // Ignore warnings that happen while checking for a heartbeat. 1023 return; 1024 } 1025 ignoreIfUnloading(batch, function() { 1026 // If this is a poll, we should retry! 1027 dwr.engine._prepareException(ex); 1028 dwr.engine.util.logHandlerEx(function() { 1029 if (batch && typeof batch.warningHandler == "function") batch.warningHandler(ex.message, ex); 1030 else if (dwr.engine._warningHandler) dwr.engine._warningHandler(ex.message, ex); 1031 }); 1032 if (batch) dwr.engine.batch.remove(batch); 1033 }); 1034 }; 1035 1036 /** 1037 * Prepares an exception for an error/warning handler. 1038 * @private 1039 * @param {Object} ex 1040 */ 1041 dwr.engine._prepareException = function(ex) { 1042 if (typeof ex == "string") ex = { name:"unknown", message:ex }; 1043 if (ex.message == null) ex.message = ""; 1044 if (ex.name == null) ex.name = "unknown"; 1045 }; 1046 1047 /** 1048 * Create a new object that delegates to obj 1049 * @param obj 1050 */ 1051 dwr.engine._delegate = (function() { 1052 function F(){} 1053 return (function(obj){ 1054 F.prototype = obj; 1055 return new F(); 1056 }); 1057 })(); 1058 1059 /** 1060 * Create a new object that delegates to obj 1061 * @param obj 1062 */ 1063 dwr.engine._createFromMap = function(map) { 1064 var obj = new this(); // this should be set to a constructor function! 1065 for(prop in map) if (map.hasOwnProperty(prop)) obj[prop] = map[prop]; 1066 return obj; 1067 }; 1068 1069 /** 1070 * A reference to the global context (window when in a browser) 1071 */ 1072 dwr.engine._global = (function(){return this;}).call(null); 1073 1074 /** 1075 * Navigates properties from the global scope and down to fetch a property 1076 * value. 1077 * @param prop hierarchical property name 1078 * @return property value, or undefined if doesn't exist 1079 */ 1080 dwr.engine._getObject = function(prop) { 1081 var parts = prop.split("."); 1082 var value = undefined; 1083 var scope = dwr.engine._global; 1084 while(parts.length > 0) { 1085 var currprop = parts.shift(); 1086 if (!scope) return undefined; 1087 value = scope[currprop]; 1088 if (value && value.tagName && document.getElementById(currprop) == value) return undefined; 1089 scope = value; 1090 } 1091 return value; 1092 }; 1093 1094 /** 1095 * Navigates properties from the global scope and down to set a value. 1096 * @param prop hierarchical property name 1097 * @param obj property value to set 1098 */ 1099 dwr.engine._setObject = function(prop, obj) { 1100 var parts = prop.split("."); 1101 var level; 1102 var scope = dwr.engine._global; 1103 while(parts.length > 0) { 1104 var currprop = parts.shift(); 1105 if (parts.length === 0) { 1106 scope[currprop] = obj; 1107 } 1108 else { 1109 level = scope[currprop]; 1110 if (level == null || level.tagName && document.getElementById(currprop) == level) { 1111 scope[currprop] = level = {}; 1112 } 1113 scope = level; 1114 } 1115 } 1116 }; 1117 1118 /** 1119 * Used internally when some message needs to get to the programmer 1120 * @private 1121 * @param {String} message 1122 * @param {Object} stacktrace 1123 */ 1124 dwr.engine._debug = function(message, stacktrace) { 1125 var written = false;
1126 try { 1127 if (window.console) { 1128 if (stacktrace && window.console.trace) window.console.trace(); 1129 if (window.console.log) window.console.log(message); 1130 written = true; 1131 } 1132 else if (window.opera && window.opera.postError) { 1133 window.opera.postError(message); 1134 written = true; 1135 } 1136 } 1137 catch (ex) { /* ignore */ } 1138 1139 if (!written) { 1140 var debug = document.getElementById("dwr-debug"); 1141 if (debug) { 1142 var contents = message + "<br/>" + debug.innerHTML; 1143 if (contents.length > 2048) contents = contents.substring(0, 2048); 1144 debug.innerHTML = contents; 1145 } 1146 } 1147 }; 1148 1149 /** 1150 * Functions called by the server 1151 */ 1152 dwr.engine.remote = { 1153 /** 1154 * Execute a callback 1155 * @private 1156 * @param {int} batchId The ID of the batch that we are replying to 1157 * @param {int} callId The call ID that the script relates to 1158 * @param {String} reply The script to execute 1159 */ 1160 handleCallback:function(batchId, callId, reply) { 1161 var batch = dwr.engine._batches[batchId]; 1162 if (!batch) return; 1163 1164 // We store the reply in the batch so we can return the data when in sync mode 1165 batch.reply = reply; 1166 1167 // Error handlers inside here indicate an error that is nothing to do 1168 // with DWR so we handle them differently. 1169 try { 1170 var handlers = batch.handlers[callId]; 1171 if (!handlers) { 1172 dwr.engine._debug("Warning: Missing handlers. callId=" + callId, true); 1173 } 1174 else { 1175 batch.handlers[callId].completed = true; 1176 if (typeof handlers.callback == "function") { 1177 dwr.engine.util.logHandlerEx(function() { 1178 if(handlers.callbackArg !== undefined) 1179 handlers.callback.call(handlers.callbackScope, reply, handlers.callbackArg); 1180 else 1181 handlers.callback.call(handlers.callbackScope, reply); 1182 }); 1183 } 1184 } 1185 } 1186 catch (ex) { 1187 dwr.engine._handleError(batch, ex); 1188 } 1189 }, 1190 1191 /** 1192 * Called by the server when a JavascriptFunction is executed 1193 * @param id The ID of the serialized function 1194 * @param args The arguments to pass to the function 1195 */ 1196 handleFunctionCall:function(id, args) { 1197 var func = dwr.engine.serialize.remoteFunctions[id]; 1198 func.apply(window, args); 1199 }, 1200 1201 /** 1202 * Called by the server when a JavascriptFunction is executed 1203 * @param id The ID of the serialized function 1204 * @param args The arguments to pass to the function 1205 */ 1206 handleObjectCall:function(id, methodName, args) { 1207 var obj = dwr.engine.serialize.remoteFunctions[id]; 1208 obj[methodName].apply(obj, args); 1209 }, 1210 1211 /** 1212 * Called by the server when a JavascriptFunction is executed 1213 * @param propertyName The ID of the serialized function 1214 * @param data The arguments to pass to the function 1215 */ 1216 handleSetCall:function(id, propertyName, data) { 1217 var obj = dwr.engine.serialize.remoteFunctions[id]; 1218 obj[propertyName] = data; 1219 }, 1220 1221 /** 1222 * Called by the server when a JavascriptFunction is closed 1223 * @param id The ID of the serialized function 1224 */ 1225 handleFunctionClose:function(id) { 1226 delete dwr.engine.serialize.remoteFunctions[id]; 1227 }, 1228 1229 /** 1230 * Called by the server: Handle an exception for a call 1231 * @private 1232 * @param {int} batchId The ID of the batch that we are replying to 1233 * @param {int} callId The call ID that the script relates to 1234 * @param {String} reply The script to execute 1235 */ 1236 handleException:function(batchId, callId, ex) { 1237 var batch = dwr.engine._batches[batchId]; 1238 if (!batch) return; 1239 1240 var handlers = batch.handlers[callId]; 1241 batch.handlers[callId].completed = true; 1242 if (handlers == null) { 1243 dwr.engine._debug("Warning: null handlers in remoteHandleException", true); 1244 return; 1245 } 1246 1247 if (ex.message === undefined) { 1248 ex.message = ""; 1249 } 1250 1251 dwr.engine.util.logHandlerEx(function() { 1252 if (typeof handlers.exceptionHandler == "function") { 1253 if (handlers.exceptionArg !== undefined) 1254 handlers.exceptionHandler.call(handlers.exceptionScope, ex.message, ex, handlers.exceptionArg); 1255 else 1256 handlers.exceptionHandler.call(handlers.exceptionScope, ex.message, ex); 1257 } 1258 else if (typeof handlers.errorHandler == "function") { 1259 handlers.errorHandler(ex.message, ex); 1260 } 1261 else if (typeof batch.errorHandler == "function") { 1262 batch.errorHandler(ex.message, ex); 1263 } 1264 else if (typeof batch.globalErrorHandler == "function") { 1265 batch.globalErrorHandler(ex.message, ex); 1266 } 1267 }); 1268 }, 1269 1270 /** 1271 * Called by the server when a new Reverse Ajax script arrives 1272 */ 1273 handleReverseAjax:function(reverseAjaxIndex, reverseAjaxFunc) {
1274 if (reverseAjaxIndex < dwr.engine._nextReverseAjaxIndex) return; 1275 dwr.engine._reverseAjaxQueue[reverseAjaxIndex] = reverseAjaxFunc; 1276 while(true) { 1277 var nextFunc = dwr.engine._reverseAjaxQueue[dwr.engine._nextReverseAjaxIndex]; 1278 if (!nextFunc) return; 1279 dwr.engine.util.logHandlerEx(function() { 1280 nextFunc(); 1281 }); 1282 delete dwr.engine._reverseAjaxQueue[dwr.engine._nextReverseAjaxIndex]; 1283 dwr.engine._nextReverseAjaxIndex++; 1284 } 1285 }, 1286 1287 /** 1288 * Called by the server: The whole batch is broken 1289 * @private 1290 * @param {Object} ex The data about what broke 1291 * @param {int} batchId The ID of the batch that we are replying to 1292 */ 1293 handleBatchException:function(ex, batchId) { 1294 var batch = dwr.engine._batches[batchId]; 1295 if (ex.message === undefined) ex.message = ""; 1296 if (batch) { 1297 dwr.engine._handleError(batch, ex); 1298 } else { 1299 dwr.engine._queuedBatchException = ex; 1300 } 1301 }, 1302 1303 /** 1304 * Called by the server: Reverse ajax should not be used 1305 * @private 1306 * @param {Object} ex 1307 * @param {int} batchId 1308 */ 1309 pollCometDisabled:function(ex, batchId){ 1310 dwr.engine.setActiveReverseAjax(false); 1311 var batch = dwr.engine._batches[batchId]; 1312 if (ex.message === undefined) { 1313 ex.message = ""; 1314 } 1315 dwr.engine._handleError(batch, ex); 1316 }, 1317 1318 /** 1319 * Called by the server: Create a new object of a mapped class 1320 * @private 1321 * @param {string} dwrClassName the name of the mapped class 1322 * @param {Object} memberMap the object's data members 1323 */ 1324 newObject:function(dwrClassName, memberMap){ 1325 var classfunc = dwr.engine._mappedClasses[dwrClassName]; 1326 if (classfunc && classfunc.createFromMap) { 1327 return classfunc.createFromMap(memberMap); 1328 } 1329 else { 1330 memberMap.$dwrClassName = dwrClassName; 1331 return memberMap; 1332 } 1333 } 1334 }; 1335 1336 /** 1337 * Functions to serialize a data set into a list of parameters 1338 */ 1339 dwr.engine.serialize = { 1340 /** 1341 * ActiveX objects to use when we want to convert an xml string into a DOM object 1342 */ 1343 domDocument:[ 1344 "Msxml2.DOMDocument.6.0", 1345 "Msxml2.DOMDocument.5.0", 1346 "Msxml2.DOMDocument.4.0", 1347 "Msxml2.DOMDocument.3.0", 1348 "MSXML2.DOMDocument", 1349 "MSXML.DOMDocument", 1350 "Microsoft.XMLDOM" 1351 ], 1352 1353 /** 1354 * A holder for functions that we have serialized for remote calling. 1355 */ 1356 remoteFunctions:{}, 1357 1358 /** 1359 * The ID of the next function that we serialize 1360 */ 1361 funcId:0, 1362 1363 /** 1364 * Convert a text representation of XML into a DOM element 1365 * @param {String} xml An xml string 1366 */ 1367 toDomElement:function(xml) { 1368 return dwr.engine.serialize.toDomDocument(xml).documentElement; 1369 }, 1370 1371 /** 1372 * Convert a text representation of XML into a DOM document 1373 * @param {String} xml An xml string 1374 */ 1375 toDomDocument:function(xml) { 1376 var dom; 1377 if (window.DOMParser) { 1378 var parser = new DOMParser(); 1379 dom = parser.parseFromString(xml, "text/xml"); 1380 if (!dom.documentElement || dom.documentElement.tagName == "parsererror") { 1381 var message = dom.documentElement.firstChild.data; 1382 message += "\n" + dom.documentElement.firstChild.nextSibling.firstChild.data; 1383 throw message; 1384 } 1385 return dom; 1386 } 1387 else if (window.ActiveXObject) { 1388 dom = dwr.engine.util.newActiveXObject(dwr.engine.serialize.domDocument); 1389 dom.loadXML(xml); // What happens on parse fail with IE? 1390 return dom; 1391 } 1392 else { 1393 var div = document.createElement("div"); 1394 div.innerHTML = xml; 1395 return div; 1396 } 1397 }, 1398 1399 /** 1400 * Marshall a data item 1401 * @private 1402 * @param batch A map of variables to how they have been marshalled 1403 * @param directrefmap A map of already marshalled variables to prevent recurrsion (these have direct links from their data object) 1404 * @param otherrefmap A map of already marshalled variables to prevent recursion (these have no links from data objects) 1405 * @param data The data to be marshalled 1406 * @param name The name of the data being marshalled 1407 */ 1408 convert:function(batch, directrefmap, otherrefmap, data, name, depth) { 1409 if (data == null) { 1410 batch.map[name] = "null:null"; 1411 return; 1412 } 1413 1414 switch (typeof data) { 1415 case "boolean": 1416 batch.map[name] = "boolean:" + data; 1417 break; 1418 case "number": 1419 batch.map[name] = "number:" + data; 1420 break; 1421 case "string": 1422 batch.map[name] = "string:" + encodeURIComponent(data); 1423 break; 1424 case "object": 1425 var ref = dwr.engine.serialize.lookup(directrefmap, otherrefmap, data, name); 1426 var objstr = Object.prototype.toString.call(data); 1427 if (data.$dwrByRef) batch.map[name] = dwr.engine.serialize.convertByReference(batch, directrefmap, otherrefmap, data, name, depth + 1); 1428 else if (ref != null) batch.map[name] = ref; 1429 else if (objstr == "[object String]") batch.map[name] = "string:" + encodeURIComponent(data); 1430 else if (objstr == "[object Boolean]") batch.map[name] = "boolean:" + data; 1431 else if (objstr == "[object Number]") batch.map[name] = "number:" + data; 1432 else if (objstr == "[object Date]") batch.map[name] = "date:" + data.getTime(); 1433 else if (objstr == "[object Array]") batch.map[name] = dwr.engine.serialize.convertArray(batch, directrefmap, otherrefmap, data, name, depth + 1); 1434 else if (data && data.tagName && data.tagName.toLowerCase() == "input" && data.type && data.type.toLowerCase() == "file") { 1435 batch.fileUpload = true; 1436 batch.map[name] = data; 1437 } 1438 else { 1439 // This check for an HTML is not complete, but is there a better way? 1440 // Maybe we should add: data.hasChildNodes typeof "function" == true 1441 if (data.nodeName && data.nodeType) { 1442 batch.map[name] = dwr.engine.serialize.convertDom(data); 1443 } 1444 else { 1445 batch.map[name] = dwr.engine.serialize.convertObject(batch, directrefmap, otherrefmap, data, name, depth + 1); 1446 } 1447 } 1448 break; 1449 case "function": 1450 // Ignore functions unless they are directly passed in 1451 if (depth === 0) { 1452 batch.map[name] = dwr.engine.serialize.convertByReference(batch, directrefmap, otherrefmap, data, name, depth + 1); 1453 } 1454 break; 1455 default: 1456 dwr.engine._handleWarning(null, { name:"dwr.engine.unexpectedType", message:"Unexpected type: " + typeof data + ", attempting default converter." }); 1457 batch.map[name] = "default:" + data; 1458 break; 1459 } 1460 }, 1461 1462 /** 1463 * Marshall an object by reference 1464 * @private 1465 * @see dwr.engine.serialize.convert() for parameter details 1466 */ 1467 convertByReference:function(batch, directrefmap, otherrefmap, data, name, depth) { 1468 var funcId = "f" + dwr.engine.serialize.funcId; 1469 dwr.engine.serialize.remoteFunctions[funcId] = data; 1470 dwr.engine.serialize.funcId++; 1471 return "byref:" + funcId; 1472 }, 1473 1474 /** 1475 * Marshall an array 1476 * @private 1477 * @see dwr.engine.serialize.convert() for parameter details 1478 */ 1479 convertArray:function(batch, directrefmap, otherrefmap, data, name, depth) { 1480 var childName, i; 1481 if (dwr.engine.util.ieCondition("if lte IE 7")) { 1482 // Use array joining on IE1-7 (fastest) 1483 var buf = ["array:["]; 1484 for (i = 0; i < data.length; i++) { 1485 if (i !== 0) buf.push(","); 1486 batch.paramCount++; 1487 childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount; 1488 dwr.engine.serialize.convert(batch, directrefmap, otherrefmap, data[i], childName, depth + 1); 1489 buf.push("reference:"); 1490 buf.push(childName); 1491 } 1492 buf.push("]"); 1493 reply = buf.join(""); 1494 } 1495 else { 1496 // Use string concat on other browsers (fastest) 1497 var reply = "array:["; 1498 for (i = 0; i < data.length; i++) { 1499 if (i !== 0) reply += ","; 1500 batch.paramCount++; 1501 childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount; 1502 dwr.engine.serialize.convert(batch, directrefmap, otherrefmap, data[i], childName, depth + 1); 1503 reply += "reference:"; 1504 reply += childName; 1505 } 1506 reply += "]"; 1507 } 1508 1509 return reply; 1510 }, 1511 1512 /** 1513 * Marshall an object by value 1514 * @private 1515 * @see dwr.engine.serialize.convert() for parameter details 1516 */ 1517 convertObject:function(batch, directrefmap, otherrefmap, data, name, depth) { 1518 // treat objects as an associative arrays 1519 var reply = "Object_" + dwr.engine.serialize.getObjectClassName(
1519data).replace(/:/g, "?") + ":{"; 1520 var elementset = (data.constructor && data.constructor.$dwrClassMembers ? data.constructor.$dwrClassMembers : data); 1521 var element; 1522 for (element in elementset) { 1523 if (typeof data[element] != "function" && !dwr.engine._excludeObjectAttributes[element]) { 1524 batch.paramCount++; 1525 var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount; 1526 dwr.engine.serialize.convert(batch, directrefmap, otherrefmap, data[element], childName, depth + 1); 1527 reply += encodeURIComponent(element) + ":reference:" + childName + ", "; 1528 } 1529 } 1530 1531 if (reply.substring(reply.length - 2) == ", ") { 1532 reply = reply.substring(0, reply.length - 2); 1533 } 1534 reply += "}"; 1535 1536 return reply; 1537 }, 1538 1539 /** 1540 * Marshall an object 1541 * @private 1542 * @see dwr.engine.serialize.convert() for parameter details 1543 */ 1544 convertDom:function(data) { 1545 return "xml:" + encodeURIComponent(dwr.engine.serialize.serializeDom(data)); 1546 }, 1547 serializeDom:function(data) { 1548 var output; 1549 if (window.XMLSerializer) output = new XMLSerializer().serializeToString(data); 1550 else if (data.xml) output = data.xml; 1551 else throw new Error("The browser doesn't support XML serialization of: " + data); 1552 return output; 1553 }, 1554 1555 /** 1556 * Have we already converted this object? 1557 * @private 1558 * @see dwr.engine.serialize.convert() for parameter details 1559 */ 1560 lookup:function(directrefmap, otherrefmap, data, name) { 1561 // Look for reference to previous conversion 1562 var ref; 1563 if ("$_dwrConversionRef" in data) { 1564 ref = data.$_dwrConversionRef; 1565 if (ref && directrefmap[ref] != data) ref = null; 1566 } 1567 if (ref == null) { 1568 for(r in otherrefmap) { 1569 if (otherrefmap[r] == data) { 1570 ref = r; 1571 break; 1572 } 1573 } 1574 } 1575 if (ref != null) return "reference:" + ref; 1576 // This was a new conversion - save the reference! 1577 try { 1578 data.$_dwrConversionRef = name; 1579 directrefmap[name] = data; 1580 } 1581 catch(err) { 1582 otherrefmap[name] = data; 1583 } 1584 return null; 1585 }, 1586 1587 /** 1588 * Clean up our conversion markers from user data 1589 * @private 1590 */ 1591 cleanup:function(directrefmap) { 1592 for(var refName in directrefmap) { 1593 var data = directrefmap[refName]; 1594 try { 1595 delete data.$_dwrConversionRef; 1596 } 1597 catch(err) { 1598 data.$_dwrConversionRef = undefined; 1599 } 1600 } 1601 }, 1602 1603 /** 1604 * Returns the classname of supplied argument obj. Similar to typeof, but 1605 * which returns the name of the constructor that created the object rather 1606 * than 'object' 1607 * @private 1608 * @param {Object} obj The object to detect the type of 1609 * @return The name of the object 1610 */ 1611 getObjectClassName:function(obj) { 1612 // Different handling depending on if, and what type of, class-mapping is used 1613 if (obj.$dwrClassName) 1614 return obj.$dwrClassName; // Light class-mapping uses the classname from a property on the instance 1615 else if (obj.constructor && obj.constructor.$dwrClassName) 1616 return obj.constructor.$dwrClassName; // Full class-mapping uses the classname from a property on the constructor function 1617 else 1618 return "Object"; 1619 } 1620 }; 1621 1622 /** 1623 * Functions to handle the various remoting transport 1624 */ 1625 dwr.engine.transport = { 1626 /** 1627 * Manage the DWR session and then send. 1628 * @private 1629 * @param {Object} batch 1630 */ 1631 send:function(batch) { 1632 if (batch.path == null) { 1633 batch.path = dwr.engine._effectivePath(); 1634 } 1635 dwr.engine.transport.updateDwrSessionFromCookie(); 1636 if (!dwr.engine._dwrSessionId) { 1637 dwr.engine._internalOrdered = true; 1638 var retval; 1639 var idbatch = { 1640 map:{ 1641 callCount:1, 1642 'c0-scriptName':'__System', 1643 'c0-methodName':'generateId', 1644 'c0-id':0 1645 }, 1646 paramCount:0, isPoll:false, async:batch.async, 1647 headers:{}, preHooks:[], 1648 postHooks:[function() { 1649 dwr.engine._internalOrdered = false;
1650 }], 1651 timeout:dwr.engine._timeout, 1652 errorHandler:batch.errorHandler, globalErrorHandler:batch.globalErrorHandler, warningHandler:batch.warningHandler, textHtmlHandler:batch.textHtmlHandler, globalTextHtmlHandler:batch.globalTextHtmlHandler, 1653 path:batch.path, 1654 handlers:[{ 1655 exceptionHandler:null, 1656 callback:function(id) { 1657 dwr.engine.transport.updateDwrSessionFromCookie(); 1658 if (!dwr.engine._dwrSessionId) { 1659 dwr.engine.transport.setDwrSession(id); 1660 } 1661 retval = dwr.engine.transport.send2(batch); 1662 } 1663 }] 1664 }; 1665 dwr.engine.transport.send2(idbatch); 1666 return retval; 1667 } 1668 else { 1669 return dwr.engine.transport.send2(batch); 1670 } 1671 }, 1672 1673 /** 1674 * Actually send the block of data in the batch object. 1675 * @private 1676 * @param {Object} batch 1677 */ 1678 send2:function(batch) { 1679 dwr.engine.batch.prepareToSend(batch); 1680 1681 // Work out if we are going cross domain 1682 var isCrossDomain = false; 1683 if (batch.path.indexOf("http://") === 0 || batch.path.indexOf("https://") === 0) { 1684 var dwrShortPath = batch.path.split("/", 3).join("/"); 1685 var hrefShortPath = window.location.href.split("/", 3).join("/"); 1686 isCrossDomain = (dwrShortPath != hrefShortPath); 1687 } 1688 1689 if (batch.fileUpload) { 1690 if (isCrossDomain) { 1691 throw new Error("Cross domain file uploads are not possible with this release of DWR"); 1692 } 1693 batch.transport = dwr.engine.transport.iframe; 1694 } 1695 else if (isCrossDomain) { 1696 batch.transport = dwr.engine.transport.scriptTag; 1697 } 1698 else if (batch.isPoll && dwr.engine._useStreamingPoll == "true" && dwr.engine.util.ieCondition("if (IE 8)|(IE 9)")) { 1699 batch.transport = dwr.engine.transport.iframe; 1700 } 1701 else { 1702 batch.transport = dwr.engine.transport.xhr; 1703 } 1704 1705 return batch.transport.send(batch); 1706 }, 1707 1708 /** 1709 * Called to signal that the batch response has been delivered 1710 */ 1711 complete:function(batch) { 1712 dwr.engine.batch.validate(batch); 1713 dwr.engine.transport.remove(batch); 1714 }, 1715 1716 /** 1717 * Called as a result of a request timeout 1718 * @private 1719 * @param {Object} batch The batch that is aborting 1720 */ 1721 abort:function(batch) { 1722 var transport = batch.transport; 1723 if (transport.abort) { 1724 transport.abort(batch); 1725 } 1726 dwr.engine.transport.remove(batch); 1727 }, 1728 1729 /** 1730 * Remove all remoting artifacts 1731 * @param {Object} batch The batch that has completed 1732 */ 1733 remove:function(batch) { 1734 if (batch.transport) { 1735 dwr.engine._callPostHooks(batch); 1736 batch.transport.remove(batch); 1737 batch.transport = null; 1738 } 1739 dwr.engine.batch.remove(batch); 1740 }, 1741 1742 setDwrSession:function(dwrsess) { 1743 dwr.engine._dwrSessionId = dwrsess; 1744 var attrs = ""; 1745 if (!dwr.engine._cookieAttributes.match(/^path=/i)) { 1746 attrs = "; path=" + (dwr.engine._effectiveContextPath() || "/"); 1747 } 1748 if (dwr.engine._cookieAttributes) { 1749 attrs += "; " + dwr.engine._cookieAttributes; 1750 } 1751 document.cookie = "DWRSESSIONID=" + dwrsess + attrs; 1752 dwr.engine._scriptSessionId = dwrsess + "/" + dwr.engine._pageId; 1753 }, 1754 1755 updateDwrSessionFromCookie:function() { 1756 if (!dwr.engine._dwrSessionId) { 1757 var match = document.cookie.match(/(?:^|; )DWRSESSIONID=([^;]+)/); 1758 if (match) { 1759 dwr.engine.transport.setDwrSession(match[1]); 1760 } 1761 } 1762 }, 1763 1764 /** 1765 * Remoting through XHR 1766 */ 1767 xhr:{ 1768 /** 1769 * The default HTTP method to use 1770 */ 1771 httpMethod:"POST", 1772 1773 /** 1774 * The ActiveX objects to use when we want to do an XMLHttpRequest call. 1775 * TODO: We arrived at this by trial and error. Other toolkits use 1776 * different strings, maybe there is an officially correct version? 1777 */ 1778 XMLHTTP:["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"], 1779 1780 /** 1781 * Setup a batch for transfer through XHR 1782 * @param {Object} batch The batch to alter for XHR transmit 1783 */ 1784 send:function(batch) { 1785 if (batch.isPoll) { 1786 batch.map.partialResponse = dwr.engine._partialResponseYes; 1787 } 1788 1789 // Are we told to use early closing mode? 1790 if (batch.isPoll && dwr.engine._useStreamingPoll == "false") { 1791 batch.map.partialResponse = dwr.engine._partialResponseNo; 1792 } 1793 if (batch.isPoll && dwr.engine.util.ieCondition("if lte IE 9")) { 1794 batch.map.partialResponse = dwr.engine._partialResponseNo; 1795 } 1796 1797 if (window.XMLHttpRequest) { 1798 batch.req = new XMLHttpRequest(); 1799 } 1800 else if (window.ActiveXObject) {
1801 batch.req = dwr.engine.util.newActiveXObject(dwr.engine.transport.xhr.XMLHTTP); 1802 } 1803 1804 // Proceed using XMLHttpRequest 1805 if (batch.async === true) { 1806 batch.req.onreadystatechange = function() { 1807 if (typeof dwr != 'undefined') { 1808 dwr.engine.transport.xhr.stateChange(batch); 1809 } 1810 }; 1811 } 1812 1813 httpMethod = dwr.engine.transport.xhr.httpMethod; 1814 1815 batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall; 1816 var request = dwr.engine.batch.constructRequest(batch, httpMethod); 1817 1818 try { 1819 batch.req.open(httpMethod, request.url, batch.async); 1820 try { 1821 for (var prop in batch.headers) { 1822 var value = batch.headers[prop]; 1823 if (typeof value == "string") { 1824 batch.req.setRequestHeader(prop, value); 1825 } 1826 } 1827 if (!batch.headers["Content-Type"]) { 1828 batch.req.setRequestHeader("Content-Type", "text/plain"); 1829 } 1830 } 1831 catch (ex) { 1832 dwr.engine._handleWarning(batch, ex); 1833 } 1834 1835 batch.req.send(request.body); 1836 if (batch.async === false) { 1837 dwr.engine.transport.xhr.stateChange(batch); 1838 } 1839 } 1840 catch (ex2) { 1841 dwr.engine._handleError(batch, ex2); 1842 } 1843 1844 if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) { 1845 dwr.engine.transport.xhr.checkCometPoll(); 1846 } 1847 1848 // This is only of any use in sync mode to return the reply data 1849 return batch.reply; 1850 }, 1851 1852 /** 1853 * Called by XMLHttpRequest to indicate that something has happened 1854 * @private 1855 * @param {Object} batch The current remote operation 1856 */ 1857 stateChange:function(batch) { 1858 var toEval; 1859 1860 if (batch.aborted) return; 1861 if (batch.completed) { 1862 if (batch.transport) dwr.engine._debug("Error: _stateChange() with batch.completed"); 1863 return; 1864 } 1865 1866 // Try to get the response HTTP status if applicable 1867 var req = batch.req; 1868 var status = 0; 1869 try { 1870 if (req.readyState >= 2) { 1871 status = req.status; // causes Mozilla to except on page moves 1872 } 1873 } 1874 catch(ignore) {} 1875 1876 // If we couldn't get the status we bail out, unless the request is 1877 // complete, which means error (handled further below) 1878 if (status === 0 && req.readyState < 4) { 1879 return; 1880 } 1881 1882 // If the status is 200, we are now online. 1883 // Future improvement per Mike W. - A solution where we only use the callbacks/handlers of the poll call to trigger 1884 // the retry handling would be ideal. We would need something like a new internal callback that reports 1885 // progress back to the caller, and the design should be compatible with getting it to work with iframes as well. 1886 if (status === 200) { 1887 dwr.engine._handlePollStatusChange(true, null, batch); 1888 } 1889 1890 // The rest of this function only deals with request completion 1891 if (req.readyState != 4) { 1892 return; 1893 } 1894 1895 if (dwr.engine._unloading) { 1896 dwr.engine._debug("Ignoring reply from server as page is unloading."); 1897 return; 1898 } 1899 1900 try { 1901 var reply = req.responseText; 1902 reply = dwr.engine._replyRewriteHandler(reply); 1903 var contentType = req.getResponseHeader("Content-Type"); 1904 if (status >= 200 && status < 300) { 1905 if (contentType.indexOf("text/plain") < 0 && contentType.indexOf("text/javascript") < 0) { 1906 if (contentType.indexOf("text/html") == 0) { 1907 dwr.engine._handleError(batch, { name:"dwr.engine.textHtmlReply", message:"HTML reply from the server.", responseText:reply||"" }); 1908 } else { 1909 dwr.engine._handleError(batch, { name:"dwr.engine.invalidMimeType", message:"Invalid content type: '" + contentType + "'" }); 1910 } 1911 } else if (reply == null || reply === "") { 1912 dwr.engine._handleError(batch, { name:"dwr.engine.missingData", message:"No data received from server" }); 1913 } else { 1914 if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) { 1915 dwr.engine.transport.xhr.processCometResponse(reply, batch); 1916 } else { 1917 if (reply.search("//#DWR") === -1) { 1918 dwr.engine._handleError(batch, { name:"dwr.engine.invalidReply", message:"Invalid reply from server" }); 1919 } else { 1920 toEval = reply; 1921 } 1922 } 1923 } 1924 } else if (status >= 400) { 1925 var statusText = "statusText could not be read."; 1926 try {
1927 statusText = req.statusText; 1928 } catch (ex) { 1929 // Eat this, if the server just went down an exception can occur reading statusText. 1930 } 1931 dwr.engine._handleError(batch, { name:"dwr.engine.http." + status, message:statusText }); 1932 } 1933 1934 if (toEval != null) toEval = toEval.replace(dwr.engine._scriptTagProtection, ""); 1935 dwr.engine._executeScript(toEval); 1936 dwr.engine.transport.complete(batch); 1937 1938 } catch (ex2) { 1939 dwr.engine._handleError(batch, ex2); 1940 } 1941 }, 1942 1943 /** 1944 * Check for reverse Ajax activity 1945 * @private 1946 */ 1947 checkCometPoll:function() { 1948 var req = dwr.engine._pollBatch && dwr.engine._pollBatch.req; 1949 if (req) { 1950 var text = req.responseText; 1951 if (text != null) { 1952 dwr.engine.transport.xhr.processCometResponse(text, dwr.engine._pollBatch); 1953 } 1954 } 1955 // If the poll resources are still there, come back again 1956 if (dwr.engine._pollBatch) { 1957 setTimeout(dwr.engine.transport.xhr.checkCometPoll, dwr.engine._pollCometInterval); 1958 } 1959 }, 1960 1961 /** 1962 * Some more text might have come in, test and execute the new stuff. 1963 * This method could also be called by the iframe transport 1964 * @private 1965 * @param {Object} response from xhr.responseText 1966 * @param {Object} batch The batch that the XHR object pertains to 1967 */ 1968 processCometResponse:function(response, batch) { 1969 if (batch.charsProcessed == response.length) return; 1970 if (response.length === 0) { 1971 batch.charsProcessed = 0; 1972 return; 1973 } 1974 1975 var firstStartTag = response.indexOf("//#DWR-START#", batch.charsProcessed); 1976 if (firstStartTag == -1) { 1977 // dwr.engine._debug("No start tag (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed) + "'"); 1978 return; 1979 } 1980 // if (firstStartTag > 0) { 1981 // dwr.engine._debug("Start tag not at start (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed, firstStartTag) + "'"); 1982 // } 1983 1984 var lastEndTag = response.lastIndexOf("//#DWR-END#"); 1985 if (lastEndTag == -1) { 1986 // dwr.engine._debug("No end tag. unchanged charsProcessed=" + batch.charsProcessed); 1987 return; 1988 } 1989 1990 // Skip the end tag too for next time, remembering CR and LF 1991 if (response.charCodeAt(lastEndTag + 11) == 13 && response.charCodeAt(lastEndTag + 12) == 10) { 1992 batch.charsProcessed = lastEndTag + 13; 1993 } 1994 else { 1995 batch.charsProcessed = lastEndTag + 11; 1996 } 1997 1998 var exec = response.substring(firstStartTag + 13, lastEndTag); 1999 2000 try { 2001 dwr.engine._executeScript(exec); 2002 } 2003 catch (ex) { 2004 // This is one of these annoying points where we might be executing 2005 // while the window is being destroyed. If dwr == null, bail out. 2006 if (dwr != null) { 2007 dwr.engine._handleError(batch, ex); 2008 } 2009 } 2010 }, 2011 2012 /** 2013 * Aborts ongoing request (for timeouts etc) 2014 */ 2015 abort:function(batch) { 2016 if (batch.req) { 2017 batch.aborted = true; 2018 batch.req.abort(); 2019 } 2020 }, 2021 2022 /** 2023 * Tidy-up when an XHR call is done 2024 * @param {Object} batch 2025 */ 2026 remove:function(batch) { 2027 // XHR tidyup: avoid IE handles increase 2028 if (batch.req) { 2029 delete batch.req; 2030 } 2031 } 2032 }, 2033 2034 /** 2035 * Functions for remoting through IFrame 2036 */ 2037 iframe:{ 2038 /** 2039 * The default HTTP method to use 2040 */ 2041 httpMethod:"POST", 2042 2043 /** 2044 * Setup a batch for transfer through IFrame 2045 * @param {Object} batch The batch to alter for IFrame transmit 2046 */ 2047 send:function(batch) { 2048 if (document.body == null) { 2049 setTimeout(function(){dwr.engine.transport.iframe.send(batch);}, 100); 2050 return; 2051 } 2052 batch.httpMethod = dwr.engine.transport.iframe.httpMethod; 2053 if (batch.fileUpload) { 2054 batch.httpMethod = "POST"; 2055 batch.encType = "multipart/form-data"; 2056 } 2057 batch.loadingStarted = false;
2058 if (window.location.hostname != document.domain) batch.map.documentDomain = document.domain; 2059 var idname = dwr.engine.transport.iframe.getId(batch); 2060 batch.div1 = document.createElement("div"); 2061 batch.div1.innerHTML = "<iframe frameborder='0' style='width:0px;height:0px;border:0;display:none;' id='" + idname + "' name='" + idname + "'></iframe>"; 2062 batch.iframe = batch.div1.firstChild; 2063 batch.document = document; 2064 batch.iframe.batch = batch; 2065 batch.fileInputs = []; 2066 dwr.engine.util.addEventListener(batch.iframe, "load", function(ev) { 2067 // Bail out if this is a premature load event (IE) 2068 if (!batch.loadingStarted) return; 2069 // Put back file inputs 2070 while(batch.fileInputs.length > 0) { 2071 var entry = batch.fileInputs.pop(); 2072 entry.original.setAttribute("id", entry.clone.getAttribute("id")); 2073 entry.original.setAttribute("name", entry.clone.getAttribute("name")); 2074 entry.original.style.display = entry.clone.style.display; 2075 entry.clone.parentNode.replaceChild(entry.original, entry.clone); 2076 } 2077 // Bail out if the batch was completed the normal way 2078 if (batch.completed) return; 2079 // Check for a queued batch exception 2080 if (dwr.engine._queuedBatchException) { 2081 dwr.engine._handleError(batch, dwr.engine._queuedBatchException); 2082 dwr.engine._queuedBatchException = null; 2083 return; 2084 } 2085 // The batch hasn't been completed the normal way, trigger a textHtmlReply error 2086 try { 2087 var contentDoc = batch.iframe.contentDocument || batch.iframe.contentWindow.document; 2088 } catch(ex) { 2089 var contentEx = ex; // may be caused by a HTML error page reply when document.domain is used 2090 } 2091 if (typeof dwr != "undefined") { 2092 var htmlResponse = contentDoc && contentDoc.documentElement ? contentDoc.documentElement.outerHTML : "(Could not extract HTML response: " + contentEx.message + ")"; 2093 dwr.engine._handleError(batch, {name:"dwr.engine.textHtmlReply", message:"HTML reply from the server.", responseText:htmlResponse}); 2094 } 2095 }); 2096 // This needs to be after the event listener is added, for IE. 2097 // http://stackoverflow.com/questions/18414964/load-event-for-iframe-does-not-fire-in-ie 2098 document.body.appendChild(batch.div1); 2099 dwr.engine.transport.iframe.beginLoader(batch, idname); 2100 }, 2101 2102 /** 2103 * Create a unique ID so multiple iframes can fire at the same time 2104 * @param {Object} batch A source of a unique number for the batch 2105 * @return {String} a name prefix for created elements 2106 */ 2107 getId:function(batch) { 2108 return batch.isPoll ? "dwr-if-poll-" + dwr.engine._instanceId + "-" + batch.map.batchId : "dwr-if-" + dwr.engine._instanceId + "-" + batch.map.batchId; 2109 }, 2110 2111 /** 2112 * Setup a form or construct a src attribute to use the iframe. 2113 * @param {Object} batch 2114 */ 2115 beginLoader:function(batch, idname) { 2116 if (batch.isPoll) { 2117 batch.map.partialResponse = dwr.engine._partialResponseYes; 2118 } 2119 batch.mode = batch.isPoll ? dwr.engine._ModeHtmlPoll : dwr.engine._ModeHtmlCall; 2120 var request = dwr.engine.batch.constructRequest(batch, batch.httpMethod); 2121 if (batch.httpMethod === "GET") { 2122 batch.iframe.setAttribute("src", request.url); 2123 } 2124 else { 2125 // TODO: On firefox we can now get the values of file fields, maybe we should use this 2126 // See http://soakedandsoaped.com/articles/read/firefox-3-native-ajax-file-upload 2127 // setting enctype via the DOM does not work in IE, create the form using innerHTML instead 2128 batch.div2 = document.createElement("div"); 2129 document.body.appendChild(batch.div2); 2130 batch.div2.innerHTML = "<form" + (batch.encType ? " encType='" + batch.encType + "' encoding='" + batch.encType + "'" : "") + "></form>"; 2131 batch.form = batch.div2.firstChild; 2132 batch.form.setAttribute("action", request.url); 2133 batch.form.setAttribute("target", idname); 2134 batch.form.setAttribute("style", "display:none"); 2135 batch.form.setAttribute("method", batch.httpMethod); 2136 for (var prop in batch.map) { 2137 var value = batch.map[prop]; 2138 if (typeof value != "function") { 2139 if (value && value.tagName && value.tagName.toLowerCase() == "input" && value.type && value.type.toLowerCase() == "file") { 2140 // Since we can not set the value of a file object, we must post the actual file object 2141 // that the user clicked browse on. We will put a clone in it's place that we will restore later. 2142 // (note that the clone doesn't retain the selected file in most browsers) 2143 if (value.parentNode) { 2144 var clone = value.cloneNode(true); 2145 value.parentNode.replaceChild(clone, value); 2146 batch.fileInputs.push({original:value, clone:clone}); 2147 } 2148 value.removeAttribute("id"); 2149 value.setAttribute("name", prop); 2150 value.style.display = "none"; 2151 batch.form.appendChild(value); 2152 } else { 2153 var formInput = batch.document.createElement("input"); 2154 formInput.setAttribute("type", "hidden"); 2155 formInput.setAttribute("name", prop); 2156 formInput.setAttribute("value", value); 2157 batch.form.appendChild(formInput); 2158 } 2159 } 2160 } 2161 batch.loadingStarted = true; 2162 batch.form.submit(); 2163 } 2164 }, 2165 2166 /** 2167 * Functions designed to be called by the server 2168 */ 2169 remote:{ 2170 /** 2171 * Called by the server: An IFrame reply is about to start 2172 * @private 2173 * @param {Object} iframe 2174 * @param {int} batchId 2175 */ 2176 beginIFrameResponse:function(iframe, batchId) { 2177 }, 2178 2179 /** 2180 * Called by the server: An IFrame script tag has completed 2181 * @private 2182 * @param {Object} iframe 2183 */ 2184 endChunk:function(iframeWindow) { 2185 setTimeout(function() { 2186 // Delete the first script tag in the iframe 2187 var scriptTags = iframeWindow.document.body.getElementsByTagName("script"); 2188 if (scriptTags.length > 0) { 2189 var s = scriptTags[0]; 2190 s.parentNode.removeChild(s); 2191 } 2192 }, 0); 2193 }, 2194 2195 /** 2196 * Called by the server: An IFrame reply is just completing 2197 * @private 2198 * @param {int} batchId 2199 */ 2200 endIFrameResponse:function(batchId) { 2201 var batch = dwr.engine._batches[batchId]; 2202 if (batch) dwr.engine.transport.complete(batch); 2203 } 2204 }, 2205 2206 remove:function(batch) { 2207 // Safari 3 and Chrome 1 will show endless loading spinner if removing 2208 // iframe during execution of iframe script, so we delay it a bit 2209 setTimeout(function(){ 2210 if (batch.iframe && batch.iframe.parentNode) { 2211 batch.iframe.parentNode.removeChild(batch.iframe); 2212 batch.iframe = null; 2213 } 2214 if (batch.div1 && batch.div1.parentNode) { 2215 batch.div1.parentNode.removeChild(batch.div1); 2216 batch.div1 = null; 2217 } 2218 if (batch.form && batch.form.parentNode) { 2219 batch.form.parentNode.removeChild(batch.form); 2220 batch.form = null; 2221 } 2222 if (batch.div2 && batch.div2.parentNode) { 2223 batch.div2.parentNode.removeChild(batch.div2); 2224 batch.div2 = null; 2225 } 2226 }, 100); 2227 } 2228 }, 2229 2230 /** 2231 * Functions for remoting through Script Tags 2232 */ 2233 scriptTag:{ 2234 /** 2235 * Setup a batch for transfer through a script tag 2236 * @param {Object} batch The batch to alter for script tag transmit 2237 */ 2238 send:function(batch) { 2239 if (batch.isPoll) { 2240 batch.map.partialResponse = dwr.engine._partialResponseNo; 2241 } 2242 batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall; 2243 var request = dwr.engine.batch.constructRequest(batch, "GET"); 2244 // The best option is DOM manipulation 2245 batch.script = document.createElement("script"); 2246 batch.script.id = "dwr-st-" + batch.map.batchId; 2247 batch.script.src = request.url; 2248 batch.script.type = "text/javascript"; 2249 batch.script.async = true; 2250 dwr.engine.util.addEventListener(batch.script, "load", function(ev) { 2251 if (typeof dwr != "undefined") dwr.engine.transport.scriptTag.complete(batch); 2252 }); 2253 dwr.engine.util.addEventListener(batch.script, "error", function(ev) { 2254 if (typeof dwr != "undefined") dwr.engine.transport.scriptTag.complete(batch); 2255 }); 2256 dwr.engine.util.addEventListener(batch.script, "readystatechange", function(ev) { 2257 if (typeof dwr != "undefined") {
2258 if (batch.completed) return; 2259 if (batch.script.readyState == "complete" || batch.script.readyState == "loaded") { 2260 dwr.engine.transport.scriptTag.complete(batch); 2261 } 2262 } 2263 }); 2264 document.getElementsByTagName("head")[0].appendChild(batch.script); 2265 }, 2266 2267 /** 2268 * Notified when the script tag has been loaded and executed 2269 */ 2270 complete:function(batch) { 2271 dwr.engine.transport.complete(batch); 2272 }, 2273 2274 /** 2275 * Cleanup script tag 2276 */ 2277 remove:function(batch) { 2278 // Bail out if we were already called 2279 if (!batch.script) return; 2280 2281 // Cleanup script tag 2282 batch.script.parentNode.removeChild(batch.script); 2283 batch.script = null; 2284 } 2285 } 2286 }; 2287 2288 /** 2289 * Functions to manipulate batches 2290 * @private 2291 */ 2292 dwr.engine.batch = { 2293 /** 2294 * Generate a new standard batch 2295 * @private 2296 */ 2297 create:function() { 2298 var batch = { 2299 async:dwr.engine._async, 2300 charsProcessed:0, 2301 handlers:[], 2302 isPoll:false, 2303 map:{ callCount:0 }, 2304 paramCount:0, 2305 preHooks:[], 2306 postHooks:[], 2307 timeout:dwr.engine._timeout, 2308 errorHandler:null, 2309 globalErrorHandler:dwr.engine._errorHandler, 2310 warningHandler:dwr.engine._warningHandler, 2311 textHtmlHandler:null, 2312 globalTextHtmlHandler:dwr.engine._textHtmlHandler 2313 }; 2314 if (!dwr.engine._activeReverseAjax) batch.map.nextReverseAjaxIndex = dwr.engine._nextReverseAjaxIndex; 2315 2316 if (dwr.engine._preHook) { 2317 batch.preHooks.push(dwr.engine._preHook); 2318 } 2319 if (dwr.engine._postHook) { 2320 batch.postHooks.push(dwr.engine._postHook); 2321 } 2322 2323 dwr.engine.batch.populateHeadersAndAttributes(batch); 2324 return batch; 2325 }, 2326 2327 /** 2328 * Generate a new batch for polling 2329 * @private 2330 * @see dwr.engine.batch.create() 2331 */ 2332 createPoll:function() { 2333 var batch = { 2334 async:true, 2335 charsProcessed:0, 2336 handlers:[{ 2337 callback:function(pause) { 2338 dwr.engine._pollBatch = null; 2339 setTimeout(dwr.engine._poll, pause); 2340 } 2341 }], 2342 isPoll:true, 2343 map:{ callCount:1, nextReverseAjaxIndex:dwr.engine._nextReverseAjaxIndex}, 2344 paramCount:0, 2345 preHooks:[], 2346 postHooks:[], 2347 timeout:dwr.engine._pollTimeout 2348 }; 2349 dwr.engine.batch.populateHeadersAndAttributes(batch); 2350 return batch; 2351 }, 2352 2353 /** 2354 * Copy the global headers and parameters into this batch object 2355 * @private 2356 * @param {Object} batch The destination 2357 */ 2358 populateHeadersAndAttributes:function(batch) { 2359 var propname, data; 2360 batch.headers = {}; 2361 if (dwr.engine._headers) { 2362 for (propname in dwr.engine._headers) { 2363 data = dwr.engine._headers[propname]; 2364 if (typeof data != "function") batch.headers[propname] = data; 2365 } 2366 } 2367 batch.attributes = {}; // props to add as request attributes 2368 if (dwr.engine._attributes) { 2369 for (propname in dwr.engine._attributes) { 2370 data = dwr.engine._attributes[propname]; 2371 if (typeof data != "function") batch.attributes[propname] = data; 2372 } 2373 } 2374 }, 2375 2376 /** 2377 * Augment this batch with a new call 2378 * @private 2379 */ 2380 addCall:function(batch, scriptName, methodName, args) { 2381 // From the other params, work out which is the function (or object with 2382 // call meta-data) and which is the call parameters 2383 var callData, stopAt; 2384 var lastArg = args[args.length - 1]; 2385 if (lastArg == null || typeof lastArg == "function") { 2386 callData = { callback:lastArg }; 2387 stopAt = args.length - 1; 2388 } 2389 else if (dwr.engine.util.isCallOptionArgument(lastArg)) { 2390 callData = lastArg; 2391 stopAt = args.length - 1; 2392 } 2393 else { 2394 callData = {}; 2395 stopAt = args.length; 2396 } 2397 2398 // Merge from the callData into the batch 2399 if (dwr.engine._singleShot) dwr.engine.batch.merge(batch, callData); 2400 batch.handlers[batch.map.callCount] = { 2401 callback:callData.callbackHandler || callData.callback, 2402 callbackArg:callData.callbackArg || callData.arg, 2403 callbackScope:callData.callbackScope || callData.scope || window, 2404 exceptionHandler:callData.exceptionHandler, 2405 exceptionArg:callData.exceptionArg || callData.arg, 2406 exceptionScope:callData.exceptionScope || callData.scope || window, 2407 errorHandler:callData.errorHandler 2408 }; 2409 2410 // Copy to the map the things that need serializing 2411 var prefix = "c" + batch.map.callCount + "-"; 2412 batch.map[prefix + "scriptName"] = scriptName; 2413 batch.map[prefix + "methodName"] = methodName; 2414 batch.map[prefix + "id"] = batch.map.callCount; 2415 var directrefmap = {}, otherrefmap = {}; 2416 for (var i = 0; i < stopAt; i++) { 2417 dwr.engine.serialize.convert(batch, directrefmap, otherrefmap, args[i], prefix + "param" + i, 0); 2418 } 2419 dwr.engine.serialize.cleanup(directrefmap); 2420 }, 2421 2422 /** 2423 * Take further options and merge them into a batch 2424 * @private 2425 * @param {Object} batch The batch that we are altering 2426 * @param {Object}
2426 overrides The object containing properties to copy into batch 2427 */ 2428 merge:function(batch, overrides) { 2429 var propname, data; 2430 for (var i = 0; i < dwr.engine._propnames.length; i++) { 2431 propname = dwr.engine._propnames[i]; 2432 if (dwr.engine._singleShot && propname == "errorHandler") continue; 2433 if (overrides[propname] != null) batch[propname] = overrides[propname]; 2434 } 2435 if (overrides.preHook != null) batch.preHooks.unshift(overrides.preHook); 2436 if (overrides.postHook != null) batch.postHooks.push(overrides.postHook); 2437 if (overrides.headers) { 2438 for (propname in overrides.headers) { 2439 data = overrides.headers[propname]; 2440 if (typeof data != "function") batch.headers[propname] = data; 2441 } 2442 } 2443 var attrs = null; 2444 if (overrides.attributes) attrs = overrides.attributes; 2445 if (attrs) { 2446 for (propname in attrs) { 2447 data = attrs[propname]; 2448 if (typeof data != "function") batch.attributes[propname] = data; 2449 } 2450 } 2451 }, 2452 2453 /** 2454 * Executed just before a transport sends the batch 2455 * @private 2456 * @param {Object} batch The batch to prepare for sending 2457 */ 2458 prepareToSend:function(batch) { 2459 batch.map.batchId = dwr.engine._nextBatchId; 2460 dwr.engine._nextBatchId++; 2461 dwr.engine._batches[batch.map.batchId] = batch; 2462 dwr.engine._batchesLength++; 2463 batch.completed = false; 2464 batch.map.instanceId = dwr.engine._instanceId; 2465 2466 // security details are filled in late so previous batches have completed 2467 batch.map.page = encodeURIComponent(window.location.pathname + window.location.search); 2468 batch.map.scriptSessionId = dwr.engine._scriptSessionId; 2469 2470 for (var i = 0; i < batch.preHooks.length; i++) { 2471 batch.preHooks[i](); 2472 } 2473 batch.preHooks = null; 2474 // Set a timeout 2475 if (batch.timeout && batch.timeout !== 0) { 2476 batch.timeoutId = setTimeout(function() { 2477 dwr.engine.transport.abort(batch); 2478 dwr.engine._handleError(batch, { name:"dwr.engine.timeout", message:"Timeout" }); 2479 }, batch.timeout); 2480 } 2481 }, 2482 2483 /** 2484 * Work out what the URL should look like 2485 * @private 2486 * @param {Object} batch the data that we are sending 2487 * @param {Object} httpMethod Are we using GET/POST etc? 2488 */ 2489 constructRequest:function(batch, httpMethod) { 2490 // A quick string to help people that use web log analysers 2491 var urlBuffer = []; 2492 urlBuffer.push(batch.path); 2493 urlBuffer.push(batch.mode); 2494 if (batch.isPoll) { 2495 urlBuffer.push("ReverseAjax.dwr"); 2496 } 2497 else if (batch.map.callCount == 1) { 2498 urlBuffer.push(batch.map["c0-scriptName"]); 2499 urlBuffer.push("."); 2500 urlBuffer.push(batch.map["c0-methodName"]); 2501 urlBuffer.push(".dwr"); 2502 } 2503 else { 2504 urlBuffer.push("Multiple."); 2505 urlBuffer.push(batch.map.callCount); 2506 urlBuffer.push(".dwr"); 2507 } 2508 // Play nice with url re-writing 2509 var sessionMatchExpr = new RegExp( 2510 "^" + // start of string 2511 "[^;\\?#]+" + // protocol, host, and path (up to first of ; ? or #) 2512 "(;[^\\?#]+)"); // group 1: ; and sessionid (up to first of ? or #) 2513 var sessionMatch = location.href.match(sessionMatchExpr); 2514 if (sessionMatch != null) { 2515 urlBuffer.push(sessionMatch[1]); 2516 } 2517 2518 // Add attributes to the output map 2519 if (batch.attributes) { 2520 for (var attrname in batch.attributes) { 2521 var data = batch.attributes[attrname]; 2522 if (typeof data != "function") batch.map["a-" + attrname] = "" + data; 2523 } 2524 } 2525 2526 var request = {}; 2527 var prop; 2528 if (httpMethod == "GET") { 2529 // Some browsers (Opera/Safari2) seem to fail to convert the callCount value 2530 // to a string in the loop below so we do it manually here. 2531 batch.map.callCount = "" + batch.map.callCount; 2532 urlBuffer.push("?"); 2533 for (prop in batch.map) { 2534 if (typeof batch.map[prop] != "function") { 2535 urlBuffer.push(encodeURIComponent(prop)); 2536 urlBuffer.push("="); 2537 urlBuffer.push(encodeURIComponent(batch.map[prop])); 2538 urlBuffer.push("&"); 2539 } 2540 } 2541 urlBuffer.pop(); // remove the trailing & 2542 request.body = null; 2543 } 2544 else { 2545 // PERFORMANCE: for iframe mode this is thrown away. 2546 request.body = ""; 2547 if (dwr.engine.util.ieCondition("if lte IE 7")) { 2548 // Use array joining on IE1-7 (fastest) 2549 var buf = []; 2550 for (prop in batch.map) { 2551 if (typeof batch.map[prop] != "function") { 2552 buf.push(prop + "=" + batch.map[prop] + dwr.engine._postSeperator); 2553 } 2554 } 2555 request.body = buf.join(""); 2556 } 2557 else { 2558 // Use string concat on other browsers (fastest) 2559 for (prop in batch.map) { 2560 if (typeof batch.map[prop] != "function") { 2561 request.body += prop + "=" + batch.map[prop] + dwr.engine._postSeperator; 2562 } 2563 } 2564 } 2565 request.body = dwr.engine._contentRewriteHandler(request.body); 2566 } 2567 request.url = dwr.engine._urlRewriteHandler(urlBuffer.join("")); 2568 return request; 2569 }, 2570 2571 /** 2572 * @private This function is invoked when a batch reply is received. 2573 * It checks that there is a response for every call in the batch. Otherwise, 2574 * an error will be signaled (a call without a response indicates that the 2575 * server failed to send complete batch response). 2576 */ 2577 validate:function(batch) { 2578 if (!batch.completed) { 2579 var repliesReceived = 0; 2580 for (var i = 0; i < batch.map.callCount; i++) { 2581 if (batch.handlers[i].completed === true) { 2582 repliesReceived++; 2583 } 2584 } 2585 if (repliesReceived == 0 && dwr.engine._queuedBatchException) { 2586 dwr.engine._handleError(batch, dwr.engine._queuedBatchException); 2587 dwr.engine._queuedBatchException = null; 2588 } else if (repliesReceived < batch.map.callCount) { 2589 dwr.engine._handleError(batch, { name:"dwr.engine.incompleteReply", message:"Incomplete reply from server" }); 2590 } 2591 } 2592 }, 2593 2594 /** 2595 * A call has finished by whatever means and we need to shut it all down. 2596 * @private 2597 * @param {Object} batch The batch that we are altering 2598 */ 2599 remove:function(batch) { 2600 if (!batch) { 2601 dwr.engine._debug("Warning: null batch in dwr.engine.batch.remove()", true); 2602 return; 2603 } 2604
2605 if (batch.completed) { 2606 return; 2607 } 2608 batch.completed = true; 2609 2610 // Transport tidyup 2611 dwr.engine.transport.remove(batch); 2612 2613 // Timeout tidyup 2614 if (batch.timeoutId != null) { 2615 clearTimeout(batch.timeoutId); 2616 delete batch.timeoutId; 2617 } 2618 2619 // TODO: co-locate all the functions that work on a set of batches 2620 if (batch.map && (batch.map.batchId != null)) { 2621 delete dwr.engine._batches[batch.map.batchId]; 2622 dwr.engine._batchesLength--; 2623 } 2624 if (batch == dwr.engine._batch) dwr.engine._batch = null; 2625 if (batch == dwr.engine._pollBatch) dwr.engine._pollBatch = null; 2626 2627 // If there is anything on the queue waiting to go out, then send it. 2628 // We don't need to check for ordered mode, here because when ordered mode 2629 // gets turned off, we still process *waiting* batches in an ordered way. 2630 if (dwr.engine._batchQueue.length !== 0) { 2631 var sendbatch = dwr.engine._batchQueue.shift(); 2632 dwr.engine.transport.send(sendbatch); 2633 } 2634 } 2635 }; 2636 2637 /** 2638 * Various utility functions 2639 * @private 2640 */ 2641 dwr.engine.util = { 2642 /** 2643 * Create one of a number of ActiveX strings 2644 * @param {Object} axarray An array of strings to attempt to create ActiveX objects from 2645 */ 2646 newActiveXObject:function(axarray) { 2647 var returnValue; 2648 for (var i = 0; i < axarray.length; i++) { 2649 try { 2650 returnValue = new ActiveXObject(axarray[i]); 2651 break; 2652 } 2653 catch (ex) { /* ignore */ } 2654 } 2655 return returnValue; 2656 }, 2657 2658 ieCondition:function(cond) { 2659 if (!(cond in dwr.engine._ieConditions)) { 2660 var div = document.createElement("div"); 2661 div.innerHTML = "<!--[" + cond + "]><p><![endif]-->"; 2662 dwr.engine._ieConditions[cond] = !!(div.getElementsByTagName("p").length); 2663 } 2664 return dwr.engine._ieConditions[cond]; 2665 }, 2666 2667 /** 2668 * Transform a number into a token string suitable for ids 2669 */ 2670 tokenify: function(number) { 2671 var tokenbuf = []; 2672 var charmap = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*$"; 2673 var remainder = number; 2674 while (remainder > 0) 2675 { 2676 tokenbuf.push(charmap.charAt(remainder & 0x3F)); 2677 remainder = Math.floor(remainder / 64); // Can't use shift operator due to 32-bit limit in JS 2678 } 2679 return tokenbuf.join(""); 2680 }, 2681 2682 addEventListener: function(elem, name, func) { 2683 if (elem.addEventListener) 2684 elem.addEventListener(name, func, false); 2685 else 2686 elem.attachEvent("on" + name, func); 2687 }, 2688 2689 isCallOptionArgument: function(lastArg) { 2690 return (typeof lastArg === "object" && (typeof lastArg.callback === "function" || 2691 typeof lastArg.exceptionHandler === "function" || typeof lastArg.callbackHandler === "function" || 2692 typeof lastArg.errorHandler === "function" || typeof lastArg.warningHandler === "function" || lastArg.hasOwnProperty("async"))); 2693 }, 2694 2695 logHandlerEx: function(func) { 2696 try { 2697 func(); 2698 } catch(ex) { 2699 dwr.engine._debug("Exception occured in user-specified handler:"); 2700 dwr.engine._debug(ex); 2701 } 2702 } 2703 }; 2704 2705 // Initialize DWR. 2706 dwr.engine._initializer.init(); 2707 2708 /** 2709 * Routines for the DWR pubsub hub 2710 */ 2711 dwr.hub = { 2712 /** 2713 * Publish some data to a given topic 2714 * @param {Object} topicName The topic to publish to 2715 * @param {Object} data The data to publish 2716 */ 2717 publish:function(topicName, data) { 2718 dwr.engine._execute(null, '__System', 'publish', topicName, data, {}); 2719 }, 2720 2721 /** 2722 * Subscribe to get notifications of publish events to a given topic 2723 * @param {String} topicName The topic to subscribe to 2724 * @param {Function} callback The function to call when a publish happens 2725 * @param {Object} scope The 'this' object on which the callback executes (optional) 2726 * @param {Object} subscriberData Data that the subscriber wishes to remember (optional) 2727 * @return An opaque type for use with unsubscribe 2728 */ 2729 subscribe:function(topicName, callback, scope, subscriberData) { 2730 var subscription = "" + dwr.hub._subscriptionId; 2731 dwr.hub._subscriptionId++; 2732 dwr.hub._subscriptions[subscription] = { 2733 callback:callback, 2734 scope:scope, 2735 subscriberData:subscriberData 2736 }; 2737 dwr.engine._execute(null, '__System', 'subscribe', topicName, subscription, {}); 2738 return subscription; 2739 }, 2740 2741 /** 2742 * Called by the server: A publish event has happened that we care about 2743 * @private 2744 * @param {Object} subscriptionId 2745 * @param {Object} publishData 2746 */ 2747 _remotePublish:function(subscriptionId, publishData) { 2748 var subscriptionData = dwr.hub._subscriptions[subscriptionId]; 2749 if (!subscriptionData) return; 2750 subscriptionData.callback.call(subscriptionData.scope, publishData, subscriptionData.subscriberData); 2751 }, 2752 2753 /** 2754 * Each time we subscribe to something, we use a unique number 2755 */ 2756 _subscriptionId:0, 2757 2758 /** 2759 * We need to remember what we are subscribed to so we can recall the callback 2760 */ 2761 _subscriptions:{} 2762 }; 2763 2764/** 2765 * High level data-sync API for use by Widget libraries like a Dojo-Data-Store. 2766 * For full documentation see org.directwebremoting.export.Data 2767 */ 2768dwr.data = { 2769 2770 /** 2771 * This is just documentation that defines how the listener parameter must act 2772 * in order to receive asynchronous updates 2773 */ 2774 StoreChangeListener:{ 2775 /** 2776 * Something has removed an item from the store 2777 * @param {StoreProvider} source The store from which it was moved 2778 * @param {string} itemId The ID of the item 2779 */ 2780 itemRemoved:function(source, itemId) { }, 2781 2782 /** 2783 * Something has added an item to the store 2784 * @param {StoreProvider} source The store from which it was moved 2785 * @param {Item} item The thing that has changed 2786 */ 2787 itemAdded:function(source, item) { }, 2788 2789 /** 2790 * Something has updated an item in the store 2791 * @param {StoreProvider} source The store from which it was moved 2792 * @param {Item} item The thing that has changed 2793 * @param {string[]} changedAttributes A list of changed attributes. If null then 2794 * you should assume that everything has changed 2795 */ 2796 itemChanged:function(source, item, changedAttributes) { } 2797 }, 2798 2799 /**
2800 * Create a cache object containing the functions to interact with a server 2801 * side StoreProvider 2802 * @param {string} storeId ID of server provided storage 2803 * @param {dwr.data.StoreChangeListener} listener See server documentation 2804 * This is likely to be true if dwr.engine.activeReverseAjax == true 2805 */ 2806 Cache:function(storeId, listener) { 2807 this.storeId = storeId; 2808 this.listener = listener; 2809 } 2810}; 2811 2812 /** 2813 * Notes that there is a region of a page that wishes to subscribe to server 2814 * side data and registers a callback function to receive the data. 2815 * @param {Object} region filtering and sorting options. Includes: 2816 * - start: The beginning of the region of specific interest 2817 * - count: The number of items being viewed 2818 * - sort: The sort criteria 2819 * - query: The filter criteria 2820 * @param {function|object} callbackObj A standard DWR callback object 2821 * @return 2822 */ 2823 dwr.data.Cache.prototype.viewRegion = function(region, callbackObj) { 2824 if (!region) region = { }; 2825 if (!region.start) region.start = 0; 2826 if (!region.count) region.count = -1; 2827 if (!region.sort) region.sort = []; 2828 else { 2829 for (var index = 0; index < region.sort.length; index++) { 2830 if (typeof region.sort[index].descending == "undefined") { 2831 region.sort[index].descending = false; 2832 } 2833 } 2834 } 2835 if (!region.query) region.query = {}; 2836 2837 return dwr.engine._execute(null, '__Data', 'viewRegion', [ this.storeId, region, this.listener, callbackObj ]); 2838 }; 2839 2840 /** 2841 * As dwr.data.Cache.viewRegion() except that we only want to see a single item. 2842 * @param {string} itemId ID of object within the given store 2843 * @param {function|object} callbackObj A standard DWR callback object 2844 */ 2845 dwr.data.Cache.prototype.viewItem = function(itemId, callbackObj) { 2846 return dwr.engine._execute(null, '__Data', 'viewItem', [ this.storeId, itemId, this.listener, callbackObj ]); 2847 }; 2848 2849 /** 2850 * Undo the action of dwr.data.view() 2851 * @param {function|object} callbackObj A standard DWR callback object 2852 */ 2853 dwr.data.Cache.prototype.unsubscribe = function(callbackObj) { 2854 if (this.listener) { 2855 return dwr.engine._execute(null, '__Data', 'unsubscribe', [ this.storeId, this.listener, callbackObj ]); 2856 } 2857 }; 2858 2859 /** 2860 * Request an update to server side data 2861 * @param {Object} items An array of update descriptions 2862 * @param {function|object} callbackObj A standard DWR callback object 2863 */ 2864 dwr.data.Cache.prototype.update = function(items, callbackObj) { 2865 return dwr.engine._execute(null, '__Data', 'update', [ this.storeId, items, callbackObj ]); 2866 }; 2867 2868})(); 2869 2870 2871 2872/* ==== INCLUDE: /js/weblets/FeaturedListingsWebletV2.js ==== */ 2873 2874document.featuredListingsRefreshInterval = 5000; 2875document.featuredListingsPhotoRefreshInterval = 5000; 2876 2877try 2878{ 2879 dwr.engine._allowGetForSafariButMakeForgeryEasier = true; 2880} 2881catch( e ) 2882{ 2883} 2884 2885// Effect copied from rico 2886/** 2887 * 2888 * Copyright 2005 Sabre Airline Solutions 2889 * 2890 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 2891 * file except in compliance with the License. You may obtain a copy of the License at 2892 * 2893 * http://www.apache.org/licenses/LICENSE-2.0 2894 * 2895 * Unless required by applicable law or agreed to in writing, software distributed under the 2896 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 2897 * either express or implied. See the License for the specific language governing permissions 2898 * and limitations under the License. 2899 **/ 2900if ( window.Effect == undefined ) 2901 Effect = {}; 2902 2903Effect.FadeTo = function() { 2904 this.initialize.apply(this, arguments); 2905}; 2906 2907Effect.FadeTo.prototype = { 2908 2909 initialize: function( element, opacity, duration, steps, options) { 2910 if( typeof element == "string" ) { 2911 this.element = document.getElementById( element ); 2912 } 2913 else { 2914 this.element = element; 2915 } 2916 2917 this.opacity = opacity; 2918 this.duration = duration; 2919 this.steps = steps; 2920 this.options = arguments[4] || {}; 2921 this.fadeTo(); 2922 }, 2923 2924 fadeTo: function() { 2925 if (this.isFinished()) { 2926 this.element.ricoOpacity = null; 2927 if(this.options.complete) this.options.complete(this); 2928 return; 2929 } 2930 if (this.timer) 2931 clearTimeout(this.timer); 2932 2933 var stepDuration = Math.round(this.duration/this.steps) ; 2934 var currentOpacity = this.getElementOpacity(); 2935 var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0; 2936 2937 this.changeOpacityBy(delta); 2938 this.duration -= stepDuration; 2939 this.steps--; 2940 2941 this.timer = setTimeout(this.fadeTo.bind(this), stepDuration); 2942 }, 2943 2944 changeOpacityBy: function(v) { 2945 var currentOpacity = this.getElementOpacity();
2946 var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1)); 2947 this.element.ricoOpacity = newOpacity; 2948 // bill: bug note: in IE changing opacity appears to whack seemingly random elements 2949 // on page, disabling for IE, assining imgs[i].id == ... also seems to contribute 2950 // not sure what this is!!!, see another bug not below 2951 if( !browser.isIE ) { 2952 //this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")"; 2953 this.element.style.opacity = newOpacity; /*//*/; 2954 } 2955 }, 2956 2957 isFinished: function() { 2958 return this.steps <= 0; 2959 }, 2960 2961 getElementOpacity: function() { 2962 if ( this.element.ricoOpacity == undefined ) { 2963 var opacity; 2964 if ( this.element.currentStyle ) { 2965 opacity = this.element.currentStyle.opacity; 2966 } 2967 else if ( document.defaultView.getComputedStyle != undefined ) { 2968 opacity = document.defaultView.getComputedStyle(this.element, null).getPropertyValue('opacity'); 2969 } 2970 2971 this.element.ricoOpacity = opacity != undefined ? opacity : 1.0; 2972 } 2973 2974 return parseFloat(this.element.ricoOpacity); 2975 } 2976} 2977 2978 2979function rotateFeaturedListingsPhotos( id, interval ) 2980{ 2981 if( document.hidden ) { 2982 return; 2983 } 2984 if( !interval ) { 2985 interval = document.featuredListingsPhotoRefreshInterval; 2986 } 2987 for( var listingId in document.featuredListingsHash[id].images ) { 2988 var images = document.featuredListingsHash[id].images[listingId]; 2989 if( images.length > 1 ) { 2990 setTimeout( "showFeaturedListingPhoto1('" + id + "', '" + listingId + 2991 "', 1, " + interval + " )", interval ); 2992 } 2993 } 2994} 2995 2996function showFeaturedListingPhoto1( id, listingId, index, interval ) 2997{ 2998 var w = document.getElementById(id); 2999 var imgId = id + "-" + listingId + "-image"; 3000 var imgs = w.getElementsByTagName( "img" ); 3001 var img = null; 3002 if( imgs && imgs.length > 0 ) { 3003 for( var i=0; i<imgs.length; ++i ) { 3004 3005 3006 if( imgs[i].src && !imgs[i].classList.contains( "leadin_overlay" ) && 3007 //imgs[i].src.indexOf( "/" + listingId + "/" ) != -1 3008 imgs[i].classList.contains("mrp-auto-image") && 3009 imgs[i].closest('.mrp-leadin').querySelector('a').getAttribute('href').indexOf( listingId ) !== -1 && 3010 imgs[i].getAttribute("id")==null ) { 3011 3012 // if( imgs[i] && imgs[i].parentNode && imgs[i].parentNode.getAttribute( "data-auto-image" ) != "ready" ) { 3013 // setTimeout( function() { 3014 // showFeaturedListingPhoto1( id, listingId, index, interval ); 3015 // }, 1000 ); 3016 // return; 3017 // } 3018 3019 // bill: bug note: instead of assining id, we are assigning to "rel" 3020 // connected to disappearing elements in IE bug, see above 3021 imgs[i].rel = imgId; 3022 img = imgs[i]; 3023 break; 3024 } 3025 } 3026 } 3027 if( !img ) { 3028 return; 3029 } 3030 // img.removeAttribute('srcset'); 3031 // img.removeAttribute('sizes'); 3032 var bg = document.getElementById(id + "-" + listingId + "-shadow"); 3033 if (!bg) { 3034 bg = new Image(); 3035 bg.setAttribute("id", id + "-" + listingId + "-shadow"); 3036 bg.style.position = "absolute"; 3037 bg.style.display = "block"; 3038 bg.style.visibility = "visible"; 3039 bg.style.margin = img.margin; 3040 bg.style.border = img.border; 3041 bg.className = "mrpl-image"; 3042 img.parentNode.insertBefore( bg, img ); 3043 } 3044 bg.style.left = img.style.left; 3045 bg.style.top = img.style.top; 3046 bg.style.width = img.style.width; 3047 bg.style.height = img.style.height; 3048 bg.style.opacity = "0"; // bill; this effectively negates teh bg trick, but it's due to flicker 3049 bg.src = img.src; 3050 // console.log( 'bg src', bg.src ); 3051 new Effect.FadeTo( img, 0, 1, 1, { 3052 complete: function() { showFeaturedListingPhoto2( id, listingId, index, interval ); } 3053 } ); 3054} 3055 3056function showFeaturedListingPhoto2( id, listingId, index, interval ) 3057{ 3058 if( !interval ) { 3059 interval = document.featuredListingsPhotoRefreshInterval; 3060 } 3061 3062 var imgId = id + "-" + listingId + "-image"; 3063 3064 var images = document.featuredListingsHash[id].images[listingId]; 3065 if( !images ) { 3066 return; 3067 } 3068 if( images.length <= index ) { 3069 index = 0; 3070 } 3071 3072 var img = null; 3073 var imgId = id + "-" + listingId + "-image"; 3074 var w = document.getElementById(id); 3075 var imgs = w.getElementsByTagName( "img" ); 3076 if( imgs && imgs.length > 0 ) { 3077 for( var i=0; i<imgs.length; ++i ) { 3078 if( imgs[i].rel == imgId ) { 3079 img = imgs[i]; 3080 break; 3081 } 3082 } 3083 } 3084 3085 //var img = $(id + "-" + listingId + "-image"); 3086 if( !img ) { 3087 return; 3088 } 3089 img.onload = function() { 3090 new Effect.FadeTo( img, 1, 500, 50 ); 3091 3092 setTimeout( "showFeaturedListingPhoto1('" + id + "', '" + listingId + "', " + index + ", " + interval + " )", interval ); 3093 }; 3094 index++; 3095 // console.log( 'img src before', img.src ); 3096 setTimeout(function() { 3097 if( img.src != images[index]) { 3098 img.src = images[index]; 3099 } 3100 }, 100 ); 3101 // console.log( 'img src after', img.src ); 3102 3103 3104} 3105 3106var prefetchedListingsNG = {}; 3107 3108function rotateFeaturedListingsNG( id, listingIds, interval ) 3109{ 3110 if( document.hidden ) { 3111 return; 3112 } 3113 var w = document.getElementById(id); 3114 webletGateway.renderWeblet( document.pageName, id, 3115 { "ListingIds": listingIds, "ajaxGetAll" : "true" }, { 3116 callback: function( result ) { 3117 if( result.content ) { 3118 3119 // strip the wrapper 3120 var content = result.content; 3121 var idx = content.indexOf( '>' ); 3122 if( idx != -1 ) { 3123 content = content.substring( idx + 1 ); 3124 idx = content.indexOf( '>' ); 3125 if( idx != -1 ) { 3126 content = content.substring( idx + 1 ); 3127 } 3128 } 3129 idx = content.lastIndexOf( "</div></div>" ); 3130 if( idx != -1 ) { 3131 content = content.substring( 0, idx ); 3132 } 3133 3134 var tks = content.split( "\=\_\=\_\=\_\=\_\=" ); 3135 prefetchedListingsNG[id] = tks; 3136 3137 var pos = 1; 3138 if( tks.length == 1 ) { 3139 pos = 0; 3140 } 3141 3142 new Effect.FadeTo( id, 0.1, 100, 10, { 3143 complete: function() { rotateFeaturedListingsAfterFadeNG( id, pos, interval ); } 3144 }); 3145 } 3146 }, 3147 errorHandler: function( error ) { 3148 return; 3149 } 3150 }); 3151} 3152 3153function getMatches(string, regex, index) { 3154 index || (index = 1); // default to the first capturing group 3155 var matches = []; 3156 var match; 3157 while (match = regex.exec(string)) { 3158 matches.push(match[index]); 3159 } 3160 return matches; 3161} 3162 3163function rotateFeaturedListingsAfterFadeNG( id, pos, interval ) 3164{ 3165 if( !prefetchedListingsNG[id] ) { 3166 return; 3167 } 3168 if( pos >= prefetchedListingsNG[id].length ) { 3169 return; // bail: something is wrong 3170 } 3171 var content = prefetchedListingsNG[id][pos]; 3172 var scripts = getMatches(content, /<script[^>]*>(.*?)<\/script>/gmi).join('\n'); 3173 content = content.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gmi, "") 3174 /*var idx = content.indexOf( "<script>" ); 3175 if( idx != -1 ) { 3176 content = content.substring( 0, idx ); 3177 }*/ 3178 3179 var elem = document.getElementById(id); 3180 3181 elem = elem.children[0]; // inner-weblet 3182 3183 var h2 = null; 3184 var viewAll = null; 3185 3186 var h2s = elem.getElementsByTagName( "h2" ); 3187 if( h2s && h2s.length > 0 && h2s[0] && h2s[0].className === "weblet_featured-listings-title" ) { 3188 h2 = h2s[0]; 3189 } 3190 3191 if( elem.children[elem.children.length-1] && 3192 elem.children[elem.children.length-1].className === "weblet_featured-listings-view-all" ) { 3193 viewAll = elem.children[elem.children.length-1]; 3194 } 3195 3196 if( !elem.existingRRectId ) { 3197 var existingRRectId = null; 3198 var divs = elem.getElementsByTagName("div"); 3199 for( var i=0; i<divs.length; ++i ) { 3200 if( divs[i].className && 3201 divs[i].className.indexOf( "leadin_style" ) != -1 && 3202 divs[i].id ) { 3203 existingRRectId = divs[i].id; 3204 break; 3205 } 3206 } 3207 elem.existingRRectId = existingRRectId; 3208 } 3209 3210 elem.innerHTML = content; 3211 if( h2 ) { 3212 elem.insertBefore( h2, elem.children[0] ); 3213 } 3214 3215 if( viewAll ) { 3216 // no need to re-append what is already there 3217 //elem.appendChild( viewAll ); 3218 } 3219 eval(scripts); 3220 3221 if( elem.existingRRectId ) { 3222 setTimeout( function() { 3223 var fx = allFx[elem.existingRRectId]; 3224 if( fx && fx.applyOnResizeFx ) { 3225 var divs = elem.getElementsByTagName("div"); 3226 for( var i=0; i<divs.length; ++i ) { 3227 if( divs[i].className && 3228 divs[i].className.indexOf( "leadin_style" ) != -1 && 3229 divs[i].id ) { 3230 fx.applyOnLoadFx( divs[i].id ); 3231 } 3232 } 3233 } 3234 }, 102 ); 3235 } 3236 3237 new Effect.FadeTo( id, 1, 100, 10 ); 3238 var newPos = pos+1; 3239 if( newPos >= prefetchedListingsNG[id].length ) { 3240 newPos = 0; 3241 } 3242 3243 if( !interval ) { 3244 interval = document.featuredListingsRefreshInterval; 3245 } 3246 3247 if( window.location.href.indexOf( 'm=__manage__' ) != -1 ) { 3248 return; 3249 } 3250 3251 setTimeout( function() { 3252 new Effect.FadeTo( id, 0.1, 100, 10, { 3253 complete: function() { rotateFeaturedListingsAfterFadeNG( id, newPos, interval ); } 3254 }); 3255 }, interval ); 3256} 3257 3258function rotateFeaturedListings( id, listingIds, interval ) 3259{ 3260 if( window.location.href.indexOf( 'm=__manage__' ) != -1 ) { 3261 return; 3262 } 3263 3264 if( true ) { 3265 rotateFeaturedListingsNG( id, listingIds, interval ); 3266 return; 3267 } 3268} 3269 3270/* ==== INCLUDE: /js/weblets/responsive/leadins.js ==== */ 3271
3272/* leadins js */ 3273jQuery( function() { 3274 jQuery( document ).on( "click", ".mrp-leadin *", function(e) { 3275 var $p = jQuery( this ).closest( ".mrp-leadin" ); 3276 3277 if( $(this).hasClass( 'ed3-editable') && $("body").hasClass( "ed3-inline-edit-on" ) ) { 3278 return false; 3279 } 3280 var $target = jQuery( e.target ); 3281 if( $target && $target.length > 0 && "A" == $target.prop("tagName" ) || $target.closest( "a" ).length > 0 ) { 3282 return true; 3283 } 3284 var url = $p.attr( "data-link" ); 3285 if( url ) { 3286 e.stopImmediatePropagation(); 3287 if( $p.attr( "data-target" ) ) { 3288 window.open( url ); 3289 } 3290 else { 3291 window.location = url; 3292 } 3293 console.log( url ); 3294 return false; 3295 } 3296 return true; 3297 }); 3298});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.