1/* 2 * Copyright 2005 Joe Walker 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/** 18 * The DWR object is also defined by dwr.util etc. 19 */ 20if (window['dojo']) dojo.provide('dwr.engine'); 21if (typeof window['dwr'] == 'undefined') { 22 dwr = { }; 23} 24 25(function() { 26 dwr.engine = { }; 27 28 /** 29 * Set an alternative error handler from the default alert box. 30 * @param {Function} handler The function to call when an error happens 31 * @see http://getahead.org/dwr/browser/engine/errors 32 */ 33 dwr.engine.setErrorHandler = function(handler) { 34 dwr.engine._errorHandler = handler; 35 }; 36 37 /** 38 * Set an alternative warning handler from the default alert box. 39 * @param {Function} handler The function to call when a warning happens 40 * @see http://getahead.org/dwr/browser/engine/errors 41 */ 42 dwr.engine.setWarningHandler = function(handler) { 43 dwr.engine._warningHandler = handler; 44 }; 45 46 /** 47 * Setter for the text/html handler - what happens if a DWR request gets an HTML 48 * reply rather than the expected Javascript. Often due to login timeout 49 * @param {Function} handler The function to call on an unexpected text/html content type 50 */ 51 dwr.engine.setTextHtmlHandler = function(handler) { 52 dwr.engine._textHtmlHandler = handler; 53 }; 54 55 /** 56 * Set a default timeout value for all calls. 0 (the default) turns timeouts off. 57 * @param {Function} handler The function to call when we get bored of waiting for a call 58 * @see getahead.org/dwr/browser/engine/errors 59 */ 60 dwr.engine.setTimeout = function(timeout) { 61 dwr.engine._timeout = timeout; 62 }; 63 64 /** 65 * The Pre-Hook is called before any DWR remoting is done. 66 * @param {Function} handler The function to call before any remote calls 67 * @see getahead.org/dwr/browser/engine/hooks 68 */ 69 dwr.engine.setPreHook = function(handler) { 70 dwr.engine._preHook = handler; 71 }; 72 73 /** 74 * The Post-Hook is called after any DWR remoting is done. 75 * @param {Function} handler The function to call after any remote calls 76 * @see getahead.org/dwr/browser/engine/hooks 77 */ 78 dwr.engine.setPostHook = function(handler) { 79 dwr.engine._postHook = handler; 80 }; 81 82 /** 83 * Custom headers for all DWR calls 84 * @param {Object} headers Object containing name/value pairs for extra headers 85 * @see getahead.org/dwr/???? 86 */ 87 dwr.engine.setHeaders = function(headers) { 88 dwr.engine._headers = headers; 89 }; 90 91 /** 92 * Custom parameters for all DWR calls 93 * @param {Object} parameters Object containing name/value pairs for extra request parameters 94 * @see getahead.org/dwr/???? 95 */ 96 dwr.engine.setParameters = function(parameters) { 97 dwr.engine._parameters = parameters; 98 }; 99 100 /** 101 * Ensure that remote calls happen in the order in which they were sent? (Default: false) 102 * @param {boolean} ordered true to enable ordered processing 103 * @see getahead.org/dwr/browser/engine/ordering 104 */ 105 dwr.engine.setOrdered = function(ordered) { 106 dwr.engine._ordered = ordered; 107 }; 108 109 /** 110 * Do we ask the XHR object to be asynchronous? (Default: true) 111 * Warning: it is <strong>highly</strong> advised to use the default ofasync 112 * processing, especially when dealing with Internet based requests. 113 * @param {boolean} async false to enable sync processing for XHR queries 114 * @see getahead.org/dwr/browser/engine/options 115 */ 116 dwr.engine.setAsync = function(async) { 117 dwr.engine._async = async; 118 }; 119 120 /** 121 * Does the client actively check the server for updates? (Default: false) 122 * @param {boolean} async true to enable low latency reverse ajax 123 * @see getahead.org/dwr/browser/engine/options 124 */ 125 dwr.engine.setActiveReverseAjax = function(activeReverseAjax) { 126 if (activeReverseAjax) { 127 // Bail if we are already started 128 if (dwr.engine._activeReverseAjax) return; 129 dwr.engine._activeReverseAjax = true; 130 dwr.engine._poll(); 131 } 132 else { 133 // Can we cancel an existing request? 134 if (dwr.engine._activeReverseAjax && dwr.engine._pollReq) { 135 dwr.engine._pollReq.abort(); 136 } 137 dwr.engine._activeReverseAjax = false;
138 } 139 // TODO: in iframe mode, if we start, stop, start then the second start may 140 // well kick off a second iframe while the first is still about to return 141 // we should cope with this but we don't 142 }; 143 144 /** 145 * Turn server notification of page unload on and off 146 * @param {boolean} notify true or false depending on if we want to turn unload on or off 147 * @see getahead.org/dwr/browser/engine/options 148 */ 149 dwr.engine.setNotifyServerOnPageUnload = function(notify) { 150 dwr.engine._isNotifyServerOnPageUnload = notify; 151 }; 152 153 /** 154 * The default message handler. 155 * @param {String} message The text of the error message 156 * @param {Object} ex An error object containing at least a name and message 157 * @see getahead.org/dwr/browser/engine/errors 158 */ 159 dwr.engine.defaultErrorHandler = function(message, ex) { 160 dwr.engine._debug("Error: " + ex.name + ", " + ex.message, true); 161 if (message == null || message == "") alert("A server error has occurred."); 162 // Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky 163 else if (message.indexOf("0x80040111") != -1) dwr.engine._debug(message); 164 else alert(message); 165 }; 166 167 /** 168 * The default warning handler. 169 * @param {String} message The text of the error message 170 * @param {Object} ex An error object containing at least a name and message 171 * @see getahead.org/dwr/browser/engine/errors 172 */ 173 dwr.engine.defaultWarningHandler = function(message, ex) { 174 dwr.engine._debug(message); 175 }; 176 177 /** 178 * For reduced latency you can group several remote calls together using a batch. 179 * @see getahead.org/dwr/browser/engine/batch 180 */ 181 dwr.engine.beginBatch = function() { 182 if (dwr.engine._batch) { 183 dwr.engine._handleError(null, { name:"dwr.engine.batchBegun", message:"Batch already begun" }); 184 return; 185 } 186 dwr.engine._batch = dwr.engine.batch.create(); 187 }; 188 189 /** 190 * Finished grouping a set of remote calls together. Go and execute them all. 191 * @param {Object} options A options object to customize processing 192 * @see getahead.org/dwr/browser/engine/batch 193 */ 194 dwr.engine.endBatch = function(options) { 195 var batch = dwr.engine._batch; 196 if (batch == null) { 197 dwr.engine._handleError(null, { name:"dwr.engine.batchNotBegun", message:"No batch in progress" }); 198 return; 199 } 200 dwr.engine._batch = null; 201 if (batch.map.callCount == 0) { 202 return; 203 } 204 205 // The hooks need to be merged carefully to preserve ordering 206 if (options) { 207 dwr.engine.batch.merge(batch, options); 208 } 209 210 // In ordered mode, we don't send unless the list of sent items is empty 211 if (dwr.engine._ordered && dwr.engine._batchesLength != 0) { 212 dwr.engine._batchQueue[dwr.engine._batchQueue.length] = batch; 213 } 214 else { 215 return dwr.engine.transport.send(batch); 216 } 217 }; 218 219 /** 220 * For use with file downloads. When a DWR function returns a binary download 221 * you can prompt the user to save it using this function 222 * @param {Object} data The binary data passed from DWR 223 */ 224 dwr.engine.openInDownload = function(data) { 225 var div = document.createElement("div"); 226 document.body.appendChild(div); 227 div.innerHTML = "<iframe width='0' height='0' scrolling='no' frameborder='0' src='" + data + "'></iframe>"; 228 }; 229 230 /** 231 * What is the current version DWR number 232 * DWR version numbers are of the form "Version 1.2.3.3128[.beta]", where: 233 * 1 is the major release number. Changes in major version number indicate 234 * significant enhancements in functionality 235 * 2 is the minor release number. Changes in minor version number indicate 236 * less significant changes in functionality 237 * 3 is the revision release number. Changes here typically indicate bug 238 * fixes only 239 * 3128 is the build number. This number increments for each build 240 * .beta is a release title that is generally only used for non production 241 * releases to indicate the purpose/quality of the release 242 * The label is these strings concatenated 243 */ 244 dwr.version = { 245 /** 246 * Changes in major version number indicate significant enhancements 247 */ 248 major:parseInt("3"), 249 250 /** 251 * Changes in minor version number indicate smaller enhancements 252 */ 253 minor:parseInt("0"), 254 255 /** 256 * Changes with the revision number typically indicate bug-fixes only 257 */ 258 revision:parseInt("0"), 259 260 /** 261 * The build number increments for each build 262 */ 263 build:parseInt("116"), 264 265 /** 266 * Only used for non production releases to indicate the purpose/quality of 267 * the release. Example titles include 'milestone1' or 'beta3'. 268 */ 269 title:"rc1", 270 271 /** 272 * The strings above concatenated 273 */ 274 label:"3.0.0.116.rc1" 275 }; 276 277 //============================================================================== 278 // Only private stuff below here 279 //============================================================================== 280 281 /** The session cookie name */ 282 dwr.engine._sessionCookieName = "JSESSIONID"; // JSESSIONID 283 284 /** Is GET enabled for the benefit of Safari? */ 285 dwr.engine._allowGetForSafariButMakeForgeryEasier = "false"; 286 287 /** The script prefix to strip in the case of scriptTagProtection. */ 288 dwr.engine._scriptTagProtection = "throw 'allowScriptTagRemoting is false.';"; 289 290 /** The default path to the DWR servlet */ 291 dwr.engine._pathToDwrServlet = "/dwr"; 292 293 /** Do we use XHR for reverse ajax because we are not streaming? */ 294 dwr.engine._pollWithXhr = "false"; 295 296 /** These URLs can be configured from the server */ 297 dwr.engine._ModePlainCall = "/call/plaincall/"; 298 dwr.engine._ModePlainPoll = "/call/plainpoll/";
299 dwr.engine._ModeHtmlCall = "/call/htmlcall/"; 300 dwr.engine._ModeHtmlPoll = "/call/htmlpoll/"; 301 302 /** Do we make the calls async? Default to 'true' */ 303 dwr.engine._async = Boolean("true"); 304 305 /** The page id */ 306 dwr.engine._scriptSessionId = null; 307 308 /** A function to be called before requests are marshalled. Can be null. */ 309 dwr.engine._preHook = null; 310 311 /** A function to be called after replies are received. Can be null. */ 312 dwr.engine._postHook = null; 313 314 /** A map of the batches that we have sent and are awaiting a reply on. */ 315 dwr.engine._batches = {}; 316 317 /** A count of the number of outstanding batches. Should be == to _batches.length unless prototype has messed things up */ 318 dwr.engine._batchesLength = 0; 319 320 /** In ordered mode, the array of batches waiting to be sent */ 321 dwr.engine._batchQueue = []; 322 323 /** Do we attempt to ensure that calls happen in the order in which they were 324 sent? This starts true until we have fetched the ids, when it is to false */ 325 dwr.engine._ordered = true; 326 327 /** The current batch (if we are in batch mode) */ 328 dwr.engine._batch = null; 329 330 /** The global timeout */ 331 dwr.engine._timeout = 0; 332 333 /** Are we doing comet or polling? */ 334 dwr.engine._activeReverseAjax = false; 335 336 /** The xhr object that we are using to poll */ 337 dwr.engine._pollReq = null; 338 339 /** How many milliseconds between internal comet polls */ 340 dwr.engine._pollCometInterval = 200; 341 342 /** How many times have we re-tried to poll? */ 343 dwr.engine._pollRetries = 0; 344 dwr.engine._maxPollRetries = 10; 345 346 /** The intervals between successive retries in seconds */ 347 dwr.engine._retryIntervals = [ 2, 5, 10, 60, 300 ]; 348 349 /** Do we do a document.reload if we get a text/html reply? */ 350 dwr.engine._textHtmlHandler = null; 351 352 /** If you wish to send custom headers with every request */ 353 dwr.engine._headers = null; 354 355 /** If you wish to send extra custom request parameters with each request */ 356 dwr.engine._parameters = null; 357 358 /** Batch ids allow us to know which batch the server is answering */ 359 dwr.engine._nextBatchId = 0; 360 361 /** A list of the properties that need merging from calls to a batch */ 362 dwr.engine._propnames = [ "async", "timeout", "errorHandler", "warningHandler", "textHtmlHandler" ]; 363 364 /** Do we stream, or can be hacked to do so? */ 365 dwr.engine._partialResponseNo = 0; 366 dwr.engine._partialResponseYes = 1; 367 dwr.engine._partialResponseFlush = 2; 368 369 /** Are we doing page unloading? */ 370 dwr.engine._isNotifyServerOnPageUnload = false; 371 372 /** 373 * A map of all mapped classes whose class declarations have been loaded 374 * (dwrClassName -> constructor function) 375 * This could have been pre-created by interface scripts, so we need to check. 376 */ 377 if (typeof dwr.engine['_mappedClasses'] == 'undefined') { 378 dwr.engine._mappedClasses = {}; 379 } 380 381 /** 382 * Find the HTTP session id sent by the web server 383 * @private 384 */ 385 dwr.engine._getHttpSessionId = function() { 386 // try to find the httpSessionId 387 var cookies = document.cookie.split(';'); 388 for (var i = 0; i < cookies.length; i++) { 389 var cookie = cookies[i]; 390 while (cookie.charAt(0) == ' ') cookie = cookie.substring(1, cookie.length); 391 if (cookie.indexOf(dwr.engine._sessionCookieName + "=") == 0) { 392 return cookie.substring(dwr.engine._sessionCookieName.length + 1, cookie.length); 393 } 394 } 395 return ""; 396 }; 397 398 /** A function to call if something fails. */ 399 dwr.engine._errorHandler = dwr.engine.defaultErrorHandler; 400 401 /** For debugging when something unexplained happens. */ 402 dwr.engine._warningHandler = dwr.engine.defaultWarningHandler; 403 404 /** Undocumented interceptors - do not use */ 405 dwr.engine._postSeperator = "\n"; 406 dwr.engine._defaultInterceptor = function(data) { return data; }; 407 dwr.engine._urlRewriteHandler = dwr.engine._defaultInterceptor; 408 dwr.engine._contentRewriteHandler = dwr.engine._defaultInterceptor; 409 dwr.engine._replyRewriteHandler = dwr.engine._defaultInterceptor; 410 411 /** Is this page in the process of unloading? */ 412 dwr.engine._unloading = false; 413 414 /** @private Abort any XHRs in progress at page unload (solves zombie socket problems in IE). */ 415 dwr.engine._unloader = function() { 416 dwr.engine._unloading = true; 417 418 // Empty queue of waiting ordered requests 419 dwr.engine._batchQueue.length = 0; 420 421 // Abort any ongoing XHRs and clear their batches 422 var batch; 423 for (var batchId in dwr.engine._batches) { 424 batch = dwr.engine._batches[batchId]; 425 // Only process objects that look like batches (avoid prototype additions!) 426 if (batch && batch.map) { 427 if (batch.req) { 428 batch.req.abort(); 429 } 430 } 431 } 432 433 // If we have used reverse ajax then we try to tell the server we are gone 434 if (dwr.engine._isNotifyServerOnPageUnload) { 435 dwr.engine._debug("calling unloader for: " + dwr.engine._scriptSessionId); 436 batch = { 437 map:{ 438 callCount:1, 439 'c0-scriptName':'__System', 440 'c0-methodName':'pageUnloaded', 441 'c0-id':0 442 }, 443 paramCount:0, isPoll:false, async:true, 444 headers:{}, preHooks:[], postHooks:[], 445 timeout:dwr.engine._timeout, 446 errorHandler:null, warningHandler:null, textHtmlHandler:null, 447 path:dwr.engine._pathToDwrServlet, 448 handlers:[{ exceptionHandler:null, callback:null }] 449 }; 450 dwr.engine.transport.send(batch); 451 dwr.engine._isNotifyServerOnPageUnload = false;
452 } 453 }; 454 455 // Now register the unload handler 456 if (!dwr.engine.isJaxerServer) { 457 if (window.addEventListener) window.addEventListener('unload', dwr.engine._unloader, false); 458 else if (window.attachEvent) window.attachEvent('onunload', dwr.engine._unloader); 459 } 460 461 /** 462 * Send a request. Called by the JavaScript interface stub 463 * @private 464 * @param path part of URL after the host and before the exec bit without leading or trailing /s 465 * @param scriptName The class to execute 466 * @param methodName The method on said class to execute 467 * @param func The callback function to which any returned data should be passed 468 * if this is null, any returned data will be ignored 469 * @param args The parameters to passed to the above method 470 */ 471 dwr.engine._execute = function(path, scriptName, methodName, args) { 472 var singleShot = false; 473 if (dwr.engine._batch == null) { 474 dwr.engine.beginBatch(); 475 singleShot = true; 476 } 477 478 var batch = dwr.engine._batch; 479 // All the paths MUST be to the same servlet 480 if (batch.path == null) { 481 batch.path = path; 482 } 483 else { 484 if (batch.path != path) { 485 dwr.engine._handleError(batch, { name:"dwr.engine.multipleServlets", message:"Can't batch requests to multiple DWR Servlets." }); 486 return; 487 } 488 } 489 490 dwr.engine.batch.addCall(batch, scriptName, methodName, args); 491 492 // Now we have finished remembering the call, we increment the call count 493 batch.map.callCount++; 494 if (singleShot) { 495 return dwr.engine.endBatch(); 496 } 497 }; 498 499 /** 500 * Poll the server to see if there is any data waiting 501 * @private 502 */ 503 dwr.engine._poll = function() { 504 if (!dwr.engine._activeReverseAjax) { 505 return; 506 } 507 var batch = dwr.engine.batch.createPoll(); 508 dwr.engine.transport.send(batch); 509 }; 510 511 /** 512 * Try to recover from polling errors 513 * @param {Object} msg 514 * @param {Object} ex 515 */ 516 dwr.engine._pollErrorHandler = function(msg, ex) { 517 if (dwr.engine._pollRetries > dwr.engine._maxPollRetries) { 518 dwr.engine._activeReverseAjax = false; 519 dwr.engine._debug("Reverse Ajax poll failed (retries=" + dwr.engine._pollRetries + "). Giving Up: " + ex.name + " : " + ex.message); 520 dwr.engine._debug("Giving up."); 521 return; 522 } 523 524 var index = dwr.engine._pollRetries; 525 if (index >= dwr.engine._retryIntervals.length) { 526 index = dwr.engine._retryIntervals.length - 1; 527 } 528 529 dwr.engine._debug("Reverse Ajax poll failed (retries=" + dwr.engine._pollRetries + "). Trying again in " + dwr.engine._retryIntervals[index] + "s: " + ex.name + " : " + ex.message); 530 setTimeout(dwr.engine._poll, 1000 * dwr.engine._retryIntervals[index]); 531 532 dwr.engine._pollRetries++; 533 }; 534 535 /** @private This is a hack to make the context be this window */ 536 dwr.engine._eval = function(script) { 537 if (script == null) { 538 return null; 539 } 540 if (script == "") { 541 dwr.engine._debug("Warning: blank script", true); 542 return null; 543 } 544 // dwr.engine._debug("Exec: [" + script + "]", true); 545 return eval(script); 546 }; 547 548 /** @private call all the post hooks for a batch */ 549 dwr.engine._callPostHooks = function(batch) { 550 if (batch.postHooks) { 551 for (var i = 0; i < batch.postHooks.length; i++) { 552 batch.postHooks[i](); 553 } 554 batch.postHooks = null; 555 } 556 }; 557 558 /** 559 * Generic error handling routing to save having null checks everywhere 560 * @private 561 * @param {Object} batch 562 * @param {Object} ex 563 */ 564 dwr.engine._handleError = function(batch, ex) { 565 if (typeof ex == "string") ex = { name:"unknown", message:ex }; 566 if (ex.message == null) ex.message = ""; 567 if (ex.name == null) ex.name = "unknown"; 568 if (batch && typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex); 569 else if (dwr.engine._errorHandler) dwr.engine._errorHandler(ex.message, ex); 570 if (batch) dwr.engine.batch.remove(batch); 571 }; 572 573 /** 574 * Generic error handling routing to save having null checks everywhere 575 * @private 576 * @param {Object} batch 577 * @param {Object} ex 578 */ 579 dwr.engine._handleWarning = function(batch, ex) { 580 if (typeof ex == "string") ex = { name:"unknown", message:ex }; 581 if (ex.message == null) ex.message = ""; 582 if (ex.name == null) ex.name = "unknown"; 583 if (batch && typeof batch.warningHandler == "function") batch.warningHandler(ex.message, ex); 584 else if (dwr.engine._warningHandler) dwr.engine._warningHandler(ex.message, ex); 585 if (batch) dwr.engine.batch.remove(batch); 586 }; 587 588 /** 589 * Used internally when some message needs to get to the programmer 590 * @private 591 * @param {String} message 592 * @param {Object} stacktrace 593 */ 594 dwr.engine._debug = function(message, stacktrace) { 595 var written = false;
596 try { 597 if (window.console) { 598 if (stacktrace && window.console.trace) window.console.trace(); 599 window.console.log(message); 600 written = true; 601 } 602 else if (window.opera && window.opera.postError) { 603 window.opera.postError(message); 604 written = true; 605 } 606 else if (window.Jaxer && Jaxer.isOnServer) { 607 Jaxer.Log.info(message); 608 written = true; 609 } 610 } 611 catch (ex) { /* ignore */ } 612 613 if (!written) { 614 var debug = document.getElementById("dwr-debug"); 615 if (debug) { 616 var contents = message + "<br/>" + debug.innerHTML; 617 if (contents.length > 2048) contents = contents.substring(0, 2048); 618 debug.innerHTML = contents; 619 } 620 } 621 }; 622 623 /** 624 * Functions called by the server 625 */ 626 dwr.engine.remote = { 627 /** 628 * Execute a callback 629 * @private 630 * @param {int} batchId The ID of the batch that we are replying to 631 * @param {int} callId The call ID that the script relates to 632 * @param {String} reply The script to execute 633 */ 634 handleCallback:function(batchId, callId, reply) { 635 var batch = dwr.engine._batches[batchId]; 636 if (batch == null) { 637 dwr.engine._debug("Warning: batch == null in remoteHandleCallback for batchId=" + batchId, true); 638 return; 639 } 640 641 // We store the reply in the batch so that in sync mode we can return the data 642 batch.reply = reply; 643 644 // Error handlers inside here indicate an error that is nothing to do 645 // with DWR so we handle them differently. 646 try { 647 var handlers = batch.handlers[callId]; 648 if (!handlers) { 649 dwr.engine._debug("Warning: Missing handlers. callId=" + callId, true); 650 } 651 else { 652 batch.handlers[callId].completed = true; 653 if (typeof handlers.callback == "function") { 654 handlers.callback.apply(handlers.callbackScope, [ reply, handlers.callbackArg ]); 655 } 656 } 657 } 658 catch (ex) { 659 dwr.engine._handleError(batch, ex); 660 } 661 }, 662 663 /** 664 * Called by the server when a JavascriptFunction is executed 665 * @param id The ID of the serialized function 666 * @param args The arguments to pass to the function 667 */ 668 handleFunctionCall:function(id, args) { 669 var func = dwr.engine.serialize.remoteFunctions[id]; 670 func.apply(window, args); 671 }, 672 673 /** 674 * Called by the server when a JavascriptFunction is executed 675 * @param id The ID of the serialized function 676 * @param args The arguments to pass to the function 677 */ 678 handleObjectCall:function(id, methodName, args) { 679 var obj = dwr.engine.serialize.remoteFunctions[id]; 680 obj[methodName].apply(obj, args); 681 }, 682 683 /** 684 * Called by the server when a JavascriptFunction is executed 685 * @param propertyName The ID of the serialized function 686 * @param data The arguments to pass to the function 687 */ 688 handleSetCall:function(id, propertyName, data) { 689 var obj = dwr.engine.serialize.remoteFunctions[id]; 690 obj[propertyName] = data; 691 }, 692 693 /** 694 * Called by the server when a JavascriptFunction is closed 695 * @param id The ID of the serialized function 696 */ 697 handleFunctionClose:function(id) { 698 delete dwr.engine.serialize.remoteFunctions[id]; 699 }, 700 701 /** 702 * Called by the server: Handle an exception for a call 703 * @private 704 * @param {int} batchId The ID of the batch that we are replying to 705 * @param {int} callId The call ID that the script relates to 706 * @param {String} reply The script to execute 707 */ 708 handleException:function(batchId, callId, ex) { 709 var batch = dwr.engine._batches[batchId]; 710 if (batch == null) { 711 dwr.engine._debug("Warning: null batch in remoteHandleException", true); 712 return; 713 } 714 715 var handlers = batch.handlers[callId]; 716 batch.handlers[callId].completed = true; 717 if (handlers == null) { 718 dwr.engine._debug("Warning: null handlers in remoteHandleException", true); 719 return; 720 } 721 722 if (ex.message == undefined) { 723 ex.message = ""; 724 } 725 726 if (typeof handlers.exceptionHandler == "function") { 727 handlers.exceptionHandler.call(handlers.exceptionScope, ex.message, ex, handlers.exceptionArg); 728 } 729 else if (typeof batch.errorHandler == "function") { 730 batch.errorHandler(ex.message, ex); 731 } 732 }, 733 734 /** 735 * Called by the server: The whole batch is broken 736 * @private 737 * @param {Object} ex The data about what broke 738 * @param {int} batchId The ID of the batch that we are replying to 739 */ 740 handleBatchException:function(ex, batchId) { 741 var searchBatch = (dwr.engine._receivedBatch == null && batchId != null); 742 if (searchBatch) { 743 dwr.engine._receivedBatch = dwr.engine._batches[batchId]; 744 } 745 if (ex.message == undefined) ex.message = ""; 746 dwr.engine._handleError(dwr.engine._receivedBatch, ex); 747 if (searchBatch) { 748 dwr.engine._receivedBatch = null; 749 dwr.engine.batch.remove(dwr.engine._batches[batchId]); 750 } 751 }, 752 753 /** 754 * Called by the server when we need to set a new script session id 755 * @param {Object} newSessionId The new script session id to be used from now 756 */ 757 handleNewScriptSession:function(newSessionId) { 758 if (dwr.engine._scriptSessionId != null) { 759 dwr.engine._debug("Server side script session id timed out. New session automatically created"); 760 } 761 dwr.engine._scriptSessionId = newSessionId; 762 }, 763 764 /** 765 * Called by the server when we need to set a new script session id 766 * @param {Object} newSessionId The new script session id to be used from now 767 */ 768 handleNewWindowName:function(windowName) { 769 dwr.engine._debug("Setting new window name: " + windowName); 770 if (window.name != null && window.name != "") { 771 dwr.engine._debug("- Warning: This will override existing name of: " + window.name); 772 } 773 window.name = windowName; 774 }, 775 776 /** 777 * Execute some script in a different window 778 * @param {Object} windowName The name of the window in which to eval the script 779 * @param {Object} script The script to eval elsewhere 780 */ 781 handleForeign:function(windowName, script) { 782 var foreign = window.open(null, windowName); 783 if (foreign != null) { 784 if (foreign.dwr != null) { 785 foreign.dwr.engine._eval(script); 786 } 787 else { 788 dwr.engine._debug("Found window, but DWR did not exist in it"); 789 } 790 } 791 else { 792 dwr.engine._debug("Could not find window"); 793 } 794 }, 795 796 /** 797 * Called by the server: Reverse ajax should not be used 798 * @private 799 * @param {Object} ex 800 * @param {int} batchId 801 */ 802 pollCometDisabled:function(ex, batchId){ 803 dwr.engine.setActiveReverseAjax(false); 804 var searchBatch = (dwr.engine._receivedBatch == null && batchId != null); 805 if (searchBatch) { 806 dwr.engine._receivedBatch = dwr.engine._batches[batchId]; 807 } 808 if (ex.message == undefined) { 809 ex.message = ""; 810 } 811 dwr.engine._handleError(dwr.engine._receivedBatch, ex); 812 if (searchBatch) { 813 dwr.engine._receivedBatch = null; 814 dwr.engine.batch.remove(dwr.engine._batches[batchId]); 815 } 816 }, 817 818 /** 819 * Called by the server: Create a new object of a mapped class 820 * @private 821 * @param {string} dwrClassName the name of the mapped class 822 * @param {Object} memberMap the object's data members 823 */ 824 newObject:function(dwrClassName, memberMap){
825 var classfunc = dwr.engine._mappedClasses[dwrClassName]; 826 if (classfunc && classfunc.createFromMap) { 827 return classfunc.createFromMap(memberMap); 828 } 829 else { 830 memberMap.$dwrClassName = dwrClassName; 831 return memberMap; 832 } 833 } 834 }; 835 836 /** 837 * Functions to serialize a data set into a list of parameters 838 */ 839 dwr.engine.serialize = { 840 /** 841 * ActiveX objects to use when we want to convert an xml string into a DOM object 842 */ 843 domDocument:[ 844 "Msxml2.DOMDocument.6.0", 845 "Msxml2.DOMDocument.5.0", 846 "Msxml2.DOMDocument.4.0", 847 "Msxml2.DOMDocument.3.0", 848 "MSXML2.DOMDocument", 849 "MSXML.DOMDocument", 850 "Microsoft.XMLDOM" 851 ], 852 853 /** 854 * A holder for functions that we have serialized for remote calling. 855 */ 856 remoteFunctions:{}, 857 858 /** 859 * The ID of the next function that we serialize 860 */ 861 funcId:0, 862 863 /** 864 * Convert a text representation of XML into a DOM element 865 * @param {String} xml An xml string 866 */ 867 toDomElement:function(xml) { 868 return dwr.engine.serialize.toDomDocument(xml).documentElement; 869 }, 870 871 /** 872 * Convert a text representation of XML into a DOM document 873 * @param {String} xml An xml string 874 */ 875 toDomDocument:function(xml) { 876 var dom; 877 if (window.DOMParser) { 878 var parser = new DOMParser(); 879 dom = parser.parseFromString(xml, "text/xml"); 880 if (!dom.documentElement || dom.documentElement.tagName == "parsererror") { 881 var message = dom.documentElement.firstChild.data; 882 message += "\n" + dom.documentElement.firstChild.nextSibling.firstChild.data; 883 throw message; 884 } 885 return dom; 886 } 887 else if (window.ActiveXObject) { 888 dom = dwr.engine.util.newActiveXObject(dwr.engine.serialize.domDocument); 889 dom.loadXML(xml); // What happens on parse fail with IE? 890 return dom; 891 } 892 else { 893 var div = document.createElement("div"); 894 div.innerHTML = xml; 895 return div; 896 } 897 }, 898 899 /** 900 * Marshall a data item 901 * @private 902 * @param batch A map of variables to how they have been marshalled 903 * @param referto An array of already marshalled variables to prevent recurrsion 904 * @param data The data to be marshalled 905 * @param name The name of the data being marshalled 906 */ 907 convert:function(batch, referto, data, name, depth) { 908 if (data == null) { 909 batch.map[name] = "null:null"; 910 return; 911 } 912 913 switch (typeof data) { 914 case "boolean": 915 batch.map[name] = "boolean:" + data; 916 break; 917 case "number": 918 batch.map[name] = "number:" + data; 919 break; 920 case "string": 921 batch.map[name] = "string:" + encodeURIComponent(data); 922 break; 923 case "object": 924 var ref = dwr.engine.serialize.lookup(referto, data, name); 925 var objstr = Object.prototype.toString.call(data); 926 if (data.$dwrByRef) batch.map[name] = dwr.engine.serialize.convertByReference(batch, referto, data, name, depth + 1); 927 else if (ref != null) batch.map[name] = ref; 928 else if (objstr == "[object String]") batch.map[name] = "string:" + encodeURIComponent(data); 929 else if (objstr == "[object Boolean]") batch.map[name] = "boolean:" + data; 930 else if (objstr == "[object Number]") batch.map[name] = "number:" + data; 931 else if (objstr == "[object Date]") batch.map[name] = "date:" + data.getTime(); 932 else if (objstr == "[object Array]") batch.map[name] = dwr.engine.serialize.convertArray(batch, referto, data, name, depth + 1); 933 else if (data && data.tagName && data.tagName.toLowerCase() == "input" && data.type && data.type.toLowerCase() == "file") { 934 batch.fileUpload = true; 935 batch.map[name] = data; 936 } 937 else { 938 // This check for an HTML is not complete, but is there a better way? 939 // Maybe we should add: data.hasChildNodes typeof "function" == true 940 if (data.nodeName && data.nodeType) { 941 batch.map[name] = dwr.engine.serialize.convertXml(batch, referto, data, name, depth + 1); 942 } 943 else { 944 batch.map[name] = dwr.engine.serialize.convertObject(batch, referto, data, name, depth + 1); 945 } 946 } 947 break; 948 case "function": 949 // Ignore functions unless they are directly passed in 950 if (depth == 0) { 951 batch.map[name] = dwr.engine.serialize.convertByReference(batch, referto, data, name, depth + 1); 952 } 953 break; 954 default: 955 dwr.engine._handleWarning(null, { name:"dwr.engine.unexpectedType", message:"Unexpected type: " + typeof data + ", attempting default converter." }); 956 batch.map[name] = "default:" + data; 957 break; 958 } 959 }, 960 961 /** 962 * Marshall an object by reference 963 * @private 964 * @see dwr.engine.serialize.convert() for parameter details 965 */ 966 convertByReference:function(batch, referto, data, name, depth) { 967 var funcId = "f" + dwr.engine.serialize.funcId; 968 dwr.engine.serialize.remoteFunctions[funcId] = data; 969 dwr.engine.serialize.funcId++; 970 return "byref:" + funcId; 971 }, 972 973 /** 974 * Marshall an array 975 * @private 976 * @see dwr.engine.serialize.convert() for parameter details 977 */ 978 convertArray:function(batch, referto, data, name, depth) { 979 var childName, i; 980 if (dwr.engine.isIE <= 7) { 981 // Use array joining on IE1-7 (fastest) 982 var buf = ["array:["]; 983 for (i = 0; i < data.length; i++) { 984 if (i != 0) buf.push(","); 985 batch.paramCount++; 986 childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount; 987 dwr.engine.serialize.convert(batch, referto, data[i], childName, depth + 1); 988 buf.push("reference:"); 989 buf.push(childName); 990 } 991 buf.push("]"); 992 reply = buf.join(""); 993 } 994 else { 995 // Use string concat on other browsers (fastest) 996 var reply = "array:["; 997 for (i = 0; i < data.length; i++) { 998 if (i != 0) reply += ","; 999 batch.paramCount++; 1000 childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount; 1001 dwr.engine.serialize.convert(batch, referto, data[i], childName, depth + 1); 1002 reply += "reference:"; 1003 reply += childName; 1004 } 1005 reply += "]"; 1006 } 1007 1008 return reply; 1009 }, 1010 1011 /** 1012 * Marshall an object by value 1013 * @private 1014 * @see dwr.engine.serialize.convert() for parameter details 1015 */ 1016 convertObject:function(batch, referto, data, name, depth) { 1017 // treat objects as an associative arrays 1018 var reply = "Object_" + dwr.engine.serialize.getObjectClassName(
1018data) + ":{"; 1019 var elementset = (data.constructor && data.constructor.$dwrClassMembers ? data.constructor.$dwrClassMembers : data); 1020 var element; 1021 for (element in elementset) { 1022 if (typeof data[element] != "function") { 1023 batch.paramCount++; 1024 var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount; 1025 dwr.engine.serialize.convert(batch, referto, data[element], childName, depth + 1); 1026 reply += encodeURIComponent(element) + ":reference:" + childName + ", "; 1027 } 1028 } 1029 1030 if (reply.substring(reply.length - 2) == ", ") { 1031 reply = reply.substring(0, reply.length - 2); 1032 } 1033 reply += "}"; 1034 1035 return reply; 1036 }, 1037 1038 /** 1039 * Marshall an object 1040 * @private 1041 * @see dwr.engine.serialize.convert() for parameter details 1042 */ 1043 convertXml:function(batch, referto, data, name, depth) { 1044 var output; 1045 if (window.XMLSerializer) output = new XMLSerializer().serializeToString(data); 1046 else if (data.toXml) output = data.toXml; 1047 else output = data.innerHTML; 1048 1049 return "xml:" + encodeURIComponent(output); 1050 }, 1051 1052 /** 1053 * Have we already converted this object? 1054 * @private 1055 * @see dwr.engine.serialize.convert() for parameter details 1056 */ 1057 lookup:function(referto, data, name) { 1058 var lookup; 1059 // Can't use a map: getahead.org/ajax/javascript-gotchas 1060 for (var i = 0; i < referto.length; i++) { 1061 if (referto[i].data == data) { 1062 lookup = referto[i]; 1063 break; 1064 } 1065 } 1066 if (lookup) { 1067 return "reference:" + lookup.name; 1068 } 1069 referto.push({ data:data, name:name }); 1070 return null; 1071 }, 1072 1073 /** 1074 * Returns the classname of supplied argument obj. Similar to typeof, but 1075 * which returns the name of the constructor that created the object rather 1076 * than 'object' 1077 * @private 1078 * @param {Object} obj The object to detect the type of 1079 * @return The name of the object 1080 */ 1081 getObjectClassName:function(obj) { 1082 // Different handling depending on if, and what type of, class-mapping is used 1083 if (obj.$dwrClassName) 1084 return obj.$dwrClassName; // Light class-mapping uses the classname from a property on the instance 1085 else if (obj.constructor && obj.constructor.$dwrClassName) 1086 return obj.constructor.$dwrClassName; // Full class-mapping uses the classname from a property on the constructor function 1087 else 1088 return "Object"; 1089 } 1090 }; 1091 1092 /** 1093 * Functions to handle the various remoting transport 1094 */ 1095 dwr.engine.transport = { 1096 /** 1097 * Actually send the block of data in the batch object. 1098 * @private 1099 * @param {Object} batch 1100 */ 1101 send:function(batch) { 1102 dwr.engine.batch.prepareToSend(batch); 1103 1104 // Work out if we are going cross domain 1105 var isCrossDomain = false; 1106 if (batch.path == null) { 1107 batch.path = dwr.engine._pathToDwrServlet; 1108 } 1109 if (batch.path.indexOf("http://") == 0 || batch.path.indexOf("https://") == 0) { 1110 var dwrShortPath = dwr.engine._pathToDwrServlet.split("/", 3).join("/"); 1111 var hrefShortPath = window.location.href.split("/", 3).join("/"); 1112 isCrossDomain = (dwrShortPath != hrefShortPath); 1113 } 1114 1115 if (batch.fileUpload) { 1116 if (isCrossDomain) { 1117 throw new Error("Cross domain file uploads are not possible with this release of DWR"); 1118 } 1119 batch.transport = dwr.engine.transport.iframe; 1120 } 1121 else if (isCrossDomain && !dwr.engine.isJaxerServer) { 1122 batch.transport = dwr.engine.transport.scriptTag; 1123 } 1124 // else if (batch.isPoll && dwr.engine.isIE) { 1125 // batch.transport = dwr.engine.transport.htmlfile; 1126 // } 1127 else { 1128 batch.transport = dwr.engine.transport.xhr; 1129 } 1130 1131 return batch.transport.send(batch); 1132 }, 1133 1134 /** 1135 * A generic function to remove all remoting artifacts 1136 * @param {Object} batch The batch that has completed 1137 */ 1138 remove:function(batch) { 1139 dwr.engine.transport.iframe.remove(batch); 1140 dwr.engine.transport.xhr.remove(batch); 1141 }, 1142 1143 /** 1144 * Called as a result of a request timeout 1145 * @private 1146 * @param {Object} batch The batch that is aborting 1147 */ 1148 abort:function(batch) {
1149 if (batch && !batch.completed) { 1150 dwr.engine.batch.remove(batch); 1151 1152 if (batch.req) { 1153 batch.req.abort(); 1154 } 1155 1156 dwr.engine.transport.remove(batch); 1157 dwr.engine._handleError(batch, { name:"dwr.engine.timeout", message:"Timeout" }); 1158 } 1159 }, 1160 1161 /** 1162 * Remoting through XHR 1163 */ 1164 xhr:{ 1165 /** 1166 * The default HTTP method to use 1167 */ 1168 httpMethod:"POST", 1169 1170 /** 1171 * The ActiveX objects to use when we want to do an XMLHttpRequest call. 1172 * TODO: We arrived at this by trial and error. Other toolkits use 1173 * different strings, maybe there is an officially correct version? 1174 */ 1175 XMLHTTP:["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"], 1176 1177 /** 1178 * Setup a batch for transfer through XHR 1179 * @param {Object} batch The batch to alter for XHR transmit 1180 */ 1181 send:function(batch) { 1182 if (batch.isPoll) { 1183 batch.map.partialResponse = dwr.engine._partialResponseYes; 1184 } 1185 1186 // Do proxies or IE force us to use early closing mode? 1187 if (batch.isPoll && dwr.engine._pollWithXhr == "true") { 1188 batch.map.partialResponse = dwr.engine._partialResponseNo; 1189 } 1190 if (batch.isPoll && dwr.engine.isIE) { 1191 batch.map.partialResponse = dwr.engine._partialResponseNo; 1192 } 1193 1194 if (window.XMLHttpRequest) { 1195 batch.req = new XMLHttpRequest(); 1196 } 1197 else if (window.ActiveXObject) { 1198 batch.req = dwr.engine.util.newActiveXObject(dwr.engine.transport.xhr.XMLHTTP); 1199 } 1200 1201 // Proceed using XMLHttpRequest 1202 if (batch.async == true) { 1203 batch.req.onreadystatechange = function() { 1204 if (typeof dwr != 'undefined') { 1205 dwr.engine.transport.xhr.stateChange(batch); 1206 } 1207 }; 1208 } 1209 1210 // If we're polling, record this for monitoring 1211 if (batch.isPoll) { 1212 dwr.engine._pollReq = batch.req; 1213 // In IE XHR is an ActiveX control so you can't augment it like this 1214 if (!dwr.engine.isIE) batch.req.batch = batch; 1215 } 1216 1217 httpMethod = dwr.engine.transport.xhr.httpMethod; 1218 1219 // Workaround for Safari 1.x POST bug 1220 var indexSafari = navigator.userAgent.indexOf("Safari/"); 1221 if (indexSafari >= 0) {
1222 var version = navigator.userAgent.substring(indexSafari + 7); 1223 if (parseInt(version, 10) < 400) { 1224 if (dwr.engine._allowGetForSafariButMakeForgeryEasier == "true") { 1225 httpMethod = "GET"; 1226 } 1227 else { 1228 dwr.engine._handleWarning(batch, { 1229 name: "dwr.engine.oldSafari", 1230 message: "Safari GET support disabled. See getahead.org/dwr/server/servlet and allowGetForSafariButMakeForgeryEasier." 1231 }); 1232 } 1233 } 1234 } 1235 1236 batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall; 1237 var request = dwr.engine.batch.constructRequest(batch, httpMethod); 1238 1239 try { 1240 batch.req.open(httpMethod, request.url, batch.async); 1241 try { 1242 for (var prop in batch.headers) { 1243 var value = batch.headers[prop]; 1244 if (typeof value == "string") { 1245 batch.req.setRequestHeader(prop, value); 1246 } 1247 } 1248 if (!batch.headers["Content-Type"]) { 1249 batch.req.setRequestHeader("Content-Type", "text/plain"); 1250 } 1251 } 1252 catch (ex) { 1253 dwr.engine._handleWarning(batch, ex); 1254 } 1255 batch.req.send(request.body); 1256 if (batch.async == false) { 1257 dwr.engine.transport.xhr.stateChange(batch); 1258 } 1259 } 1260 catch (ex) { 1261 dwr.engine._handleError(batch, ex); 1262 } 1263 1264 if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) { 1265 dwr.engine.transport.xhr.checkCometPoll(); 1266 } 1267 1268 // This is only of any use in sync mode to return the reply data 1269 return batch.reply; 1270 }, 1271 1272 /** 1273 * Called by XMLHttpRequest to indicate that something has happened 1274 * @private 1275 * @param {Object} batch The current remote operation 1276 */ 1277 stateChange:function(batch) { 1278 var toEval; 1279 1280 if (batch.completed) { 1281 dwr.engine._debug("Error: _stateChange() with batch.completed"); 1282 return; 1283 } 1284 1285 var req = batch.req; 1286 try { 1287 var readyState = req.readyState; 1288 var notReady = (req.readyState != 4); 1289 if (notReady) { 1290 return; 1291 } 1292 } 1293 catch (ex) { 1294 dwr.engine._handleWarning(batch, ex); 1295 // It's broken - clear up and forget this call 1296 dwr.engine.batch.remove(batch); 1297 return; 1298 } 1299 1300 if (dwr.engine._unloading && !dwr.engine.isJaxerServer) { 1301 dwr.engine._debug("Ignoring reply from server as page is unloading."); 1302 return; 1303 } 1304 1305 try { 1306 var reply = req.responseText; 1307 reply = dwr.engine._replyRewriteHandler(reply); 1308 var status = req.status; // causes Mozilla to except on page moves 1309 1310 if (reply == null || reply == "") { 1311 dwr.engine._handleWarning(batch, { name:"dwr.engine.missingData", message:"No data received from server" }); 1312 } 1313 else if (status != 200) { 1314 dwr.engine._handleError(batch, { name:"dwr.engine.http." + status, message:req.statusText }); 1315 } 1316 else { 1317 var contentType = req.getResponseHeader("Content-Type"); 1318 if (dwr.engine.isJaxerServer) { 1319 // HACK! Jaxer does something b0rken with Content-Type 1320 contentType = "text/javascript"; 1321 } 1322 if (!contentType.match(/^text\/plain/) && !contentType.match(/^text\/javascript/)) { 1323 if (contentType.match(/^text\/html/) && typeof batch.textHtmlHandler == "function") { 1324 batch.textHtmlHandler({ status:status, responseText:reply, contentType:contentType }); 1325 } 1326 else { 1327 dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidMimeType", message:"Invalid content type: '" + contentType + "'" }); 1328 } 1329 } 1330 else { 1331 // Comet replies might have already partially executed 1332 if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) { 1333 dwr.engine.transport.xhr.processCometResponse(reply, batch); 1334 } 1335 else { 1336 if (reply.search("//#DWR") == -1) { 1337 dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidReply", message:"Invalid reply from server" }); 1338 } 1339 else { 1340 toEval = reply; 1341 } 1342 } 1343 } 1344 } 1345 } 1346 catch (ex) { 1347 dwr.engine._handleWarning(batch, ex); 1348 } 1349 1350 dwr.engine._callPostHooks(batch); 1351 1352 // Outside of the try/catch so errors propagate normally: 1353 dwr.engine._receivedBatch = batch; 1354 if (toEval != null) toEval = toEval.replace(dwr.engine._scriptTagProtection, ""); 1355 dwr.engine._eval(toEval); 1356 dwr.engine._receivedBatch = null; 1357 dwr.engine.batch.validate(batch); 1358 if (!batch.completed) dwr.engine.batch.remove(batch); 1359 }, 1360 1361 /** 1362 * Check for reverse Ajax activity 1363 * @private 1364 */ 1365 checkCometPoll:function() { 1366 if (dwr.engine._pollReq) { 1367 var req = dwr.engine._pollReq; 1368 var text = req.responseText; 1369 if (text != null) { 1370 dwr.engine.transport.xhr.processCometResponse(text, req.batch); 1371 } 1372 } 1373 1374 // If the poll resources are still there, come back again 1375 if (dwr.engine._pollReq) { 1376 setTimeout(dwr.engine.transport.xhr.checkCometPoll, dwr.engine._pollCometInterval); 1377 } 1378 }, 1379 1380 /** 1381 * Some more text might have come in, test and execute the new stuff. 1382 * This method could also be called by the iframe transport 1383 * @private 1384 * @param {Object} response from xhr.responseText 1385 * @param {Object} batch The batch that the XHR object pertains to 1386 */ 1387 processCometResponse:function(response, batch) { 1388 if (batch.charsProcessed == response.length) return; 1389 if (response.length == 0) { 1390 batch.charsProcessed = 0; 1391 return; 1392 } 1393 1394 var firstStartTag = response.indexOf("//#DWR-START#", batch.charsProcessed); 1395 if (firstStartTag == -1) { 1396 // dwr.engine._debug("No start tag (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed) + "'"); 1397 batch.charsProcessed = response.length; 1398 return; 1399 } 1400 // if (firstStartTag > 0) { 1401 // dwr.engine._debug("Start tag not at start (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed, firstStartTag) + "'"); 1402 // } 1403 1404 var lastEndTag = response.lastIndexOf("//#DWR-END#"); 1405 if (lastEndTag == -1) { 1406 // dwr.engine._debug("No end tag. unchanged charsProcessed=" + batch.charsProcessed); 1407 return; 1408 } 1409 1410 // Skip the end tag too for next time, remembering CR and LF 1411 if (response.charCodeAt(lastEndTag + 11) == 13 && response.charCodeAt(lastEndTag + 12) == 10) { 1412 batch.charsProcessed = lastEndTag + 13; 1413 } 1414 else { 1415 batch.charsProcessed = lastEndTag + 11; 1416 } 1417 1418 var exec = response.substring(firstStartTag + 13, lastEndTag); 1419 1420 try { 1421 dwr.engine._receivedBatch = batch; 1422 dwr.engine._eval(exec); 1423 dwr.engine._receivedBatch = null; 1424 } 1425 catch (ex) { 1426 // This is one of these annoying points where we might be executing 1427 // while the window is being destroyed. If dwr == null, bail out. 1428 if (dwr != null) { 1429 dwr.engine._handleError(batch, ex); 1430 } 1431 } 1432 }, 1433 1434 /** 1435 * Tidy-up when an XHR call is done 1436 * @param {Object} batch 1437 */ 1438 remove:function(batch) { 1439 // XHR tidyup: avoid IE handles increase 1440 if (batch.req) { 1441 // If this is a poll frame then stop comet polling 1442 if (batch.req == dwr.engine._pollReq) dwr.engine._pollReq = null; 1443 delete batch.req; 1444 } 1445 } 1446 }, 1447 1448 /** 1449 * Functions for remoting through IFrame 1450 */ 1451 iframe:{ 1452 /** 1453 * Setup a batch for transfer through IFrame 1454 * @param {Object} batch The batch to alter for IFrame transmit 1455 */ 1456 send:function(batch) { 1457 if (batch.fileUpload) { 1458 batch.httpMethod = "POST"; 1459 batch.encType = "multipart/form-data"; 1460 } 1461 var idname = dwr.engine.transport.iframe.getId(batch); 1462 batch.div = document.createElement("div"); 1463 // Add the div to the document first, otherwise IE 6 will ignore onload handler. 1464 document.body.appendChild(batch.div); 1465 batch.div.innerHTML = "<iframe src='javascript:void(0)' frameborder='0' style='width:0px;height:0px;border:0;' id='" + idname + "' name='" + idname + "' onload='dwr.engine.transport.iframe.loadingComplete(" + batch.map.batchId + ");'></iframe>"; 1466 batch.document = document; 1467 dwr.engine.transport.iframe.beginLoader(batch, idname); 1468 }, 1469 1470 /** 1471 * Create a unique ID so multiple iframes can fire at the same time 1472 * @param {Object} batch A source of a unique number for the batch 1473 * @return {String} a name prefix for created elements 1474 */ 1475 getId:function(batch) { 1476 return batch.isPoll ? "dwr-if-poll-" + batch.map.batchId : "dwr-if-" + batch.map.batchId; 1477 }, 1478 1479 /** 1480 * Setup a form or construct a src attribute to use the iframe. 1481 * This is abstracted from send() because the same logic will do for htmlfile 1482 * @param {Object} batch 1483 */ 1484 beginLoader:function(batch, idname) { 1485 batch.iframe = batch.document.getElementById(idname); 1486 batch.iframe.batch = batch; 1487 batch.mode = batch.isPoll ? dwr.engine._ModeHtmlPoll : dwr.engine._ModeHtmlCall; 1488 if (batch.isPoll) dwr.engine._outstandingIFrames.push(batch.iframe); 1489 var request = dwr.engine.batch.constructRequest(batch, batch.httpMethod); 1490 if (batch.httpMethod == "GET") { 1491 batch.iframe.setAttribute("src", request.url); 1492 } 1493 else { 1494 // TODO: On firefox we can now get the values of file fields, maybe we should use this 1495 // See http://soakedandsoaped.com/articles/read/firefox-3-native-ajax-file-upload 1496 // setting enctype via the DOM does not work in IE, create the form using innerHTML instead 1497 var formHtml = "<form id='dwr-form' action='" + request.url + "' target='" + idname + "' style='display:none;' method='" + batch.httpMethod + "'"; 1498 if (batch.encType) formHtml += " enctype='" + batch.encType + "'"; 1499 formHtml += "></form>"; 1500 var div = batch.document.createElement("div"); 1501 div.innerHTML = formHtml; 1502 batch.form = div.firstChild; 1503 for (var prop in batch.map) { 1504 var value = batch.map[prop]; 1505 if (typeof value != "function") { 1506 if (value.tagName && value.tagName.toLowerCase() == "input" && value.type && value.type.toLowerCase() == "file") { 1507 // Since we can not set the value of a file object, we must post the actual file object 1508 // that the user clicked browse on. We will put a clone in it's place. 1509 var clone = value.cloneNode(true); 1510 value.removeAttribute("id", prop); 1511 value.setAttribute("name", prop); 1512 value.parentNode.insertBefore(clone, value); 1513 value.parentNode.removeChild(value); 1514 batch.form.appendChild(value); 1515 } else { 1516 var formInput = batch.document.createElement("input"); 1517 formInput.setAttribute("type", "hidden"); 1518 formInput.setAttribute("name", prop); 1519 formInput.setAttribute("value", value); 1520 batch.form.appendChild(formInput); 1521 } 1522 } 1523 } 1524 batch.document.body.appendChild(batch.form); 1525 batch.form.submit(); 1526 } 1527 }, 1528 1529 /** 1530 * Called from iframe onload, check batch using batch-id 1531 * @private 1532 * @param {int} batchId The id of the batch that has loaded 1533 */ 1534 loadingComplete:function(batchId) { 1535 var batch = dwr.engine._batches[batchId]; 1536 if (batch) dwr.engine.batch.validate(batch); 1537 }, 1538 1539 /** 1540 * Functions designed to be called by the server 1541 */ 1542 remote:{ 1543 /** 1544 * Called by the server: An IFrame reply is about to start 1545 * @private 1546 * @param {Object} iframe 1547 * @param {int} batchId 1548 */ 1549 beginIFrameResponse:function(iframe, batchId) { 1550 if (iframe != null) dwr.engine._receivedBatch = iframe.batch; 1551 dwr.engine._callPostHooks(dwr.engine._receivedBatch); 1552 }, 1553 1554 /** 1555 * Called by the server: An IFrame reply is just completing 1556 * @private 1557 * @param {int} batchId 1558 */ 1559 endIFrameResponse:function(batchId) { 1560 dwr.engine.batch.remove(dwr.engine._receivedBatch); 1561 dwr.engine._receivedBatch = null; 1562 } 1563 }, 1564 1565 remove:function(batch) { 1566 // TODO: make it so that we don't need these if statements 1567 if (batch.div) { 1568 batch.div.parentNode.removeChild(batch.div); 1569 } 1570 if (batch.iframe) { 1571 batch.iframe.parentNode.removeChild(batch.iframe); 1572 } 1573 if (batch.form) { 1574 batch.form.parentNode.removeChild(batch.form); 1575 } 1576 } 1577 1578 /* 1579 // If we have an iframe comet solution where we need to read data streamed 1580 // into an iframe then we need code like this to slurp the data out. 1581 // Compare this with xhr.checkCometPoll() 1582 outstandingIFrames:[], 1583 1584 checkCometPoll:function() { 1585 for (var i = 0; i < dwr.engine.transport.iframe.outstandingIFrames.length; i++) { 1586 var text = ""; 1587 var iframe = dwr.engine.transport.iframe.outstandingIFrames[i]; 1588 try { 1589 text = dwr.engine.transport.iframe.getTextFromCometIFrame(iframe); 1590 } 1591 catch (ex) { 1592 dwr.engine._handleWarning(iframe.batch, ex); 1593 } 1594 if (text != "") dwr.engine.transport.xhr.processCometResponse(text, iframe.batch); 1595 } 1596 1597 if (dwr.engine.transport.iframe.outstandingIFrames.length > 0) { 1598 setTimeout(dwr.engine.transport.iframe.checkCometPoll, dwr.engine._pollCometInterval); 1599 } 1600 } 1601 1602 // We probably also need to update dwr.engine.remote.beginIFrameResponse() 1603 // to call checkCometPoll. 1604 1605 // Extract the whole (executed and all) text from the current iframe 1606 getTextFromCometIFrame:function(frameEle) { 1607 var body = frameEle.contentWindow.document.body; 1608 if (body == null) return ""; 1609 var text = body.innerHTML; 1610 // We need to prevent IE from stripping line feeds 1611 if (text.indexOf("<PRE>") == 0 || text.indexOf("<pre>") == 0) { 1612 text = text.substring(5, text.length - 7); 1613 } 1614 return text; 1615 }; 1616 1617 // And an addition to iframe.remove(): 1618 { 1619 if (batch.iframe) { 1620 // If this is a poll frame then stop comet polling 1621 for (var i = 0; i < dwr.engine.transport.iframe.outstandingIFrames.length; i++) { 1622 if (dwr.engine.transport.iframe.outstandingIFrames[i] == batch.iframe) { 1623 dwr.engine.transport.iframe.outstandingIFrames.splice(i, 1); 1624 } 1625 } 1626 } 1627 } 1628 */ 1629 }, 1630 1631 /** 1632 * Functions for remoting through Script Tags 1633 */ 1634 scriptTag:{ 1635 /** 1636 * Setup a batch for transfer through a script tag 1637 * @param {Object} batch The batch to alter for script tag transmit 1638 */ 1639 send:function(batch) { 1640 batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall; 1641 var request = dwr.engine.batch.constructRequest(batch, "GET"); 1642 // The best option is DOM manipulation, but this only works after onload 1643 // has completed 1644 if (document.body) { 1645 batch.script = document.createElement("script"); 1646 batch.script.id = "dwr-st-" + batch.map.
1646batchId; 1647 batch.script.src = request.url; 1648 batch.script.type = "text/javascript"; 1649 document.body.appendChild(batch.script); 1650 } 1651 else { 1652 document.writeln("<scr" + "ipt type='text/javascript' id='dwr-st-" + batch.map["c0-id"] + "' src='" + request.url + "'> </scr" + "ipt>"); 1653 } 1654 } 1655 }, 1656 1657 /** 1658 * Remoting through IE's htmlfile ActiveX control 1659 */ 1660 htmlfile:{ 1661 /** 1662 * Setup a batch for transfer through htmlfile 1663 * @param {Object} batch The batch to alter for htmlfile transmit 1664 */ 1665 send:function(batch) { 1666 var idname = dwr.engine.transport.iframe.getId(batch); 1667 batch.htmlfile = new window.ActiveXObject("htmlfile"); 1668 batch.htmlfile.open(); 1669 batch.htmlfile.write("<" + "html>"); 1670 batch.htmlfile.write("<div><iframe className='wibble' src='javascript:void(0)' id='" + idname + "' name='" + idname + "' onload='dwr.engine.transport.iframe.loadingComplete(" + batch.map.batchId + ");'></iframe></div>"); 1671 batch.htmlfile.write("</" + "html>"); 1672 batch.htmlfile.close(); 1673 batch.htmlfile.parentWindow.dwr = dwr; 1674 batch.document = batch.htmlfile; 1675 1676 dwr.engine.transport.iframe.beginLoader(batch, idname); 1677 } 1678 } 1679 }; 1680 1681 /** 1682 * Functions to manipulate batches 1683 * @private 1684 */ 1685 dwr.engine.batch = { 1686 /** 1687 * Generate a new standard batch 1688 * @private 1689 */ 1690 create:function() { 1691 var batch = { 1692 async:dwr.engine._async, 1693 charsProcessed:0, 1694 handlers:[], 1695 isPoll:false, 1696 map:{ callCount:0, windowName:window.name }, 1697 paramCount:0, 1698 preHooks:[], 1699 postHooks:[], 1700 timeout:dwr.engine._timeout, 1701 errorHandler:dwr.engine._errorHandler, 1702 warningHandler:dwr.engine._warningHandler, 1703 textHtmlHandler:dwr.engine._textHtmlHandler 1704 }; 1705 1706 if (dwr.engine._preHook) { 1707 batch.preHooks.push(dwr.engine._preHook); 1708 } 1709 if (dwr.engine._postHook) { 1710 batch.postHooks.push(dwr.engine._postHook); 1711 } 1712 1713 dwr.engine.batch.populateHeadersAndParameters(batch); 1714 return batch; 1715 }, 1716 1717 /** 1718 * Generate a new batch for polling 1719 * @private 1720 * @see dwr.engine.batch.create() 1721 */ 1722 createPoll:function() { 1723 var batch = { 1724 async:true, 1725 charsProcessed:0, 1726 handlers:[{ 1727 callback:function(pause) { 1728 dwr.engine._pollRetries = 0; 1729 setTimeout(dwr.engine._poll, pause); 1730 } 1731 }], 1732 isPoll:true, 1733 map:{ windowName:window.name }, 1734 paramCount:0, 1735 path:dwr.engine._pathToDwrServlet, 1736 preHooks:[], 1737 postHooks:[], 1738 timeout:0, 1739 windowName:window.name, 1740 errorHandler:dwr.engine._pollErrorHandler, 1741 warningHandler:dwr.engine._pollErrorHandler, 1742 textHtmlHandler:dwr.engine._textHtmlHandler 1743 }; 1744 1745 dwr.engine.batch.populateHeadersAndParameters(batch); 1746 return batch; 1747 }, 1748 1749 /** 1750 * Copy the global headers and parameters into this batch object 1751 * @private 1752 * @param {Object} batch The destination 1753 */ 1754 populateHeadersAndParameters:function(batch) { 1755 var propname, data; 1756 batch.headers = {}; 1757 if (dwr.engine._headers) { 1758 for (propname in dwr.engine._headers) { 1759 data = dwr.engine._headers[propname]; 1760 if (typeof data != "function") batch.headers[propname] = data; 1761 } 1762 } 1763 batch.parameters = {}; 1764 if (dwr.engine._parameters) { 1765 for (propname in dwr.engine._parameters) { 1766 data = dwr.engine._parameters[propname]; 1767 if (typeof data != "function") batch.parameters[propname] = data; 1768 } 1769 } 1770 }, 1771 1772 /** 1773 * Augment this batch with a new call 1774 * @private 1775 */ 1776 addCall:function(batch, scriptName, methodName, args) { 1777 // From the other params, work out which is the function (or object with 1778 // call meta-data) and which is the call parameters 1779 var callData, stopAt; 1780 var lastArg = args[args.length - 1]; 1781 if (lastArg == null || typeof lastArg == "function") { 1782 callData = { callback:lastArg }; 1783 stopAt = args.length - 1; 1784 } 1785 else if (typeof lastArg == "object" && (typeof lastArg.callback == "function" 1786 || typeof lastArg.exceptionHandler == "function" || typeof lastArg.callbackHandler == "function" 1787 || typeof lastArg.errorHandler == "function" || typeof lastArg.warningHandler == "function" )) { 1788 callData = lastArg; 1789 stopAt = args.length - 1; 1790 } 1791 else { 1792 callData = {}; 1793 stopAt = args.length; 1794 } 1795 1796 // Merge from the callData into the batch 1797 dwr.engine.batch.merge(batch, callData); 1798 batch.handlers[batch.map.callCount] = { 1799 exceptionHandler:callData.exceptionHandler, 1800 exceptionArg:callData.exceptionArg || callData.arg || null, 1801 exceptionScope:callData.exceptionScope || callData.scope || window, 1802 callback:callData.callbackHandler || callData.callback, 1803 callbackArg:callData.callbackArg || callData.arg || null, 1804 callbackScope:callData.callbackScope || callData.scope || window 1805 }; 1806 1807 // Copy to the map the things that need serializing 1808 var prefix = "c" + batch.map.callCount + "-"; 1809 batch.map[prefix + "scriptName"] = scriptName; 1810 batch.map[prefix + "methodName"] = methodName; 1811 batch.map[prefix + "id"] = batch.map.callCount; 1812 var converted = []; 1813 for (var i = 0; i < stopAt; i++) { 1814 dwr.engine.serialize.convert(batch, converted, args[i], prefix + "param" + i, 0); 1815 } 1816 }, 1817 1818 /** 1819 * Take further options and merge them into a batch 1820 * @private 1821 * @param {Object} batch The batch that we are altering 1822 * @param {Object}
1822 overrides The object containing properties to copy into batch 1823 */ 1824 merge:function(batch, overrides) { 1825 var propname, data; 1826 for (var i = 0; i < dwr.engine._propnames.length; i++) { 1827 propname = dwr.engine._propnames[i]; 1828 if (overrides[propname] != null) batch[propname] = overrides[propname]; 1829 } 1830 if (overrides.preHook != null) batch.preHooks.unshift(overrides.preHook); 1831 if (overrides.postHook != null) batch.postHooks.push(overrides.postHook); 1832 if (overrides.headers) { 1833 for (propname in overrides.headers) { 1834 data = overrides.headers[propname]; 1835 if (typeof data != "function") batch.headers[propname] = data; 1836 } 1837 } 1838 if (overrides.parameters) { 1839 for (propname in overrides.parameters) { 1840 data = overrides.parameters[propname]; 1841 if (typeof data != "function") batch.map["p-" + propname] = "" + data; 1842 } 1843 } 1844 }, 1845 1846 /** 1847 * Executed just before a transport sends the batch 1848 * @private 1849 * @param {Object} batch The batch to prepare for sending 1850 */ 1851 prepareToSend:function(batch) { 1852 batch.map.batchId = dwr.engine._nextBatchId; 1853 dwr.engine._nextBatchId++; 1854 dwr.engine._batches[batch.map.batchId] = batch; 1855 dwr.engine._batchesLength++; 1856 batch.completed = false; 1857 1858 // security details are filled in late so previous batches have completed 1859 batch.map.page = encodeURIComponent(window.location.pathname + window.location.search); 1860 batch.map.httpSessionId = dwr.engine._getHttpSessionId(); 1861 batch.map.scriptSessionId = dwr.engine._scriptSessionId; 1862 batch.map.windowName = window.name; 1863 1864 for (var i = 0; i < batch.preHooks.length; i++) { 1865 batch.preHooks[i](); 1866 } 1867 batch.preHooks = null; 1868 // Set a timeout 1869 if (batch.timeout && batch.timeout != 0) { 1870 batch.timeoutId = setTimeout(function() { dwr.engine.transport.abort(batch); }, batch.timeout); 1871 } 1872 }, 1873 1874 /** 1875 * Work out what the URL should look like 1876 * @private 1877 * @param {Object} batch the data that we are sending 1878 * @param {Object} httpMethod Are we using GET/POST etc? 1879 */ 1880 constructRequest:function(batch, httpMethod) { 1881 // A quick string to help people that use web log analysers 1882 var urlBuffer = []; 1883 urlBuffer.push(batch.path); 1884 urlBuffer.push(batch.mode); 1885 if (batch.isPoll) { 1886 urlBuffer.push("ReverseAjax.dwr"); 1887 } 1888 else if (batch.map.callCount == 1) { 1889 urlBuffer.push(batch.map["c0-scriptName"]); 1890 urlBuffer.push("."); 1891 urlBuffer.push(batch.map["c0-methodName"]); 1892 urlBuffer.push(".dwr"); 1893 } 1894 else { 1895 urlBuffer.push("Multiple."); 1896 urlBuffer.push(batch.map.callCount); 1897 urlBuffer.push(".dwr"); 1898 } 1899 // Play nice with url re-writing 1900 var sessionMatch = location.href.match(/jsessionid=([^?]+)/); 1901 if (sessionMatch != null) { 1902 urlBuffer.push(";jsessionid="); 1903 urlBuffer.push(sessionMatch[1]); 1904 } 1905 1906 var request = {}; 1907 var prop; 1908 if (httpMethod == "GET") { 1909 // Some browsers (Opera/Safari2) seem to fail to convert the callCount value 1910 // to a string in the loop below so we do it manually here. 1911 batch.map.callCount = "" + batch.map.callCount; 1912 urlBuffer.push("?"); 1913 for (prop in batch.map) { 1914 if (typeof batch.map[prop] != "function") { 1915 urlBuffer.push(encodeURIComponent(prop)); 1916 urlBuffer.push("="); 1917 urlBuffer.push(encodeURIComponent(batch.map[prop])); 1918 urlBuffer.push("&"); 1919 } 1920 } 1921 urlBuffer.pop(); // remove the trailing & 1922 request.body = null; 1923 } 1924 else { 1925 // PERFORMANCE: for iframe mode this is thrown away. 1926 if (dwr.engine.isIE <= 7) { 1927 // Use array joining on IE1-7 (fastest) 1928 var buf = []; 1929 for (prop in batch.map) { 1930 if (typeof batch.map[prop] != "function") { 1931 buf.push(prop + "=" + batch.map[prop] + dwr.engine._postSeperator); 1932 } 1933 } 1934 request.body = buf.join(""); 1935 } 1936 else { 1937 // Use string concat on other browsers (fastest) 1938 for (prop in batch.map) { 1939 if (typeof batch.map[prop] != "function") { 1940 request.body += prop + "=" + batch.map[prop] + dwr.engine._postSeperator; 1941 } 1942 } 1943 } 1944 var bodyBuffer = []; 1945 for (prop in batch.map) { 1946 if (typeof batch.map[prop] != "function") { 1947 bodyBuffer.push(prop); 1948 bodyBuffer.push("="); 1949 bodyBuffer.push(batch.map[prop]); 1950 bodyBuffer.push(dwr.engine._postSeperator); 1951 } 1952 } 1953 request.body = dwr.engine._contentRewriteHandler(bodyBuffer.join("")); 1954 } 1955 request.url = dwr.engine._urlRewriteHandler(urlBuffer.join("")); 1956 return request; 1957 }, 1958 1959 /** 1960 * @private This function is invoked when a batch reply is received. 1961 * It checks that there is a response for every call in the batch. Otherwise, 1962 * an error will be signaled (a call without a response indicates that the 1963 * server failed to send complete batch response). 1964 */ 1965 validate:function(batch) { 1966 // If some call left unreplied, report an error. 1967 if (!batch.completed) { 1968 for (var i = 0; i < batch.map.callCount; i++) { 1969 if (batch.handlers[i].completed !== true) { 1970 dwr.engine._handleWarning(batch, { name:"dwr.engine.incompleteReply", message:"Incomplete reply from server" }); 1971 break; 1972 } 1973 } 1974 } 1975 }, 1976 1977 /** 1978 * A call has finished by whatever means and we need to shut it all down. 1979 * @private 1980 * @param {Object} batch The batch that we are altering 1981 */ 1982 remove:function(batch) { 1983 if (!batch) { 1984 dwr.engine._debug("Warning: null batch in dwr.engine.batch.remove()", true); 1985 return; 1986 } 1987
1988 if (batch.completed == "true") { 1989 dwr.engine._debug("Warning: Double complete", true); 1990 return; 1991 } 1992 batch.completed = true; 1993 1994 // Transport tidyup 1995 dwr.engine.transport.remove(batch); 1996 1997 // Timeout tidyup 1998 if (batch.timeoutId) { 1999 clearTimeout(batch.timeoutId); 2000 delete batch.timeoutId; 2001 } 2002 2003 // TODO: co-locate all the functions that work on a set of batches 2004 if (batch.map && (batch.map.batchId || batch.map.batchId == 0)) { 2005 delete dwr.engine._batches[batch.map.batchId]; 2006 dwr.engine._batchesLength--; 2007 } 2008 2009 // If there is anything on the queue waiting to go out, then send it. 2010 // We don't need to check for ordered mode, here because when ordered mode 2011 // gets turned off, we still process *waiting* batches in an ordered way. 2012 if (dwr.engine._batchQueue.length != 0) { 2013 var sendbatch = dwr.engine._batchQueue.shift(); 2014 dwr.engine.transport.send(sendbatch); 2015 } 2016 } 2017 }; 2018 2019 /** 2020 * Various utility functions 2021 * @private 2022 */ 2023 dwr.engine.util = { 2024 /** 2025 * Create one of a number of ActiveX strings 2026 * @param {Object} axarray An array of strings to attempt to create ActiveX objects from 2027 */ 2028 newActiveXObject:function(axarray) { 2029 var returnValue; 2030 for (var i = 0; i < axarray.length; i++) { 2031 try { 2032 returnValue = new ActiveXObject(axarray[i]); 2033 break; 2034 } 2035 catch (ex) { /* ignore */ } 2036 } 2037 return returnValue; 2038 } 2039 }; 2040 2041 /** 2042 * Work out what type of browser we are working on 2043 */ 2044 var userAgent = navigator.userAgent; 2045 var versionString = navigator.appVersion; 2046 var version = parseFloat(versionString); 2047 2048 dwr.engine.isOpera = (userAgent.indexOf("Opera") >= 0) ? version : 0; 2049 dwr.engine.isKhtml = (versionString.indexOf("Konqueror") >= 0) || (versionString.indexOf("Safari") >= 0) ? version : 0; 2050 dwr.engine.isSafari = (versionString.indexOf("Safari") >= 0) ? version : 0; 2051 dwr.engine.isJaxerServer = (window.Jaxer && Jaxer.isOnServer); 2052 2053 var geckoPos = userAgent.indexOf("Gecko"); 2054 dwr.engine.isMozilla = ((geckoPos >= 0) && (!dwr.engine.isKhtml)) ? version : 0; 2055 2056 dwr.engine.isFF = 0; 2057 dwr.engine.isIE = 0; 2058 try { 2059 if (dwr.engine.isMozilla) { 2060 dwr.engine.isFF = parseFloat(userAgent.split("Firefox/")[1].split(" ")[0]); 2061 } 2062 if ((document.all) && (!dwr.engine.isOpera)) { 2063 dwr.engine.isIE = parseFloat(versionString.split("MSIE ")[1].split(";")[0]); 2064 } 2065 } 2066 catch(ex) { } 2067 2068 // Fetch the scriptSessionId from the server 2069 eval("dwr.engine._execute(dwr.engine._pathToDwrServlet, '__System', 'pageLoaded', [ function() { dwr.engine._ordered = false; }]);"); 2070 2071 /** 2072 * Routines for the DWR pubsub hub 2073 */ 2074 dwr.hub = { 2075 /** 2076 * Publish some data to a given topic 2077 * @param {Object} topicName The topic to publish to 2078 * @param {Object} data The data to publish 2079 */ 2080 publish:function(topicName, data) { 2081 dwr.engine._execute(dwr.engine._pathToDwrServlet, '__System', 'publish', topicName, data, {}); 2082 }, 2083 2084 /** 2085 * Subscribe to get notifications of publish events to a given topic 2086 * @param {String} topicName The topic to subscribe to 2087 * @param {Function} callback The function to call when a publish happens 2088 * @param {Object} scope The 'this' object on which the callback executes (optional) 2089 * @param {Object} subscriberData Data that the subscriber wishes to remember (optional) 2090 * @return An opaque type for use with unsubscribe 2091 */ 2092 subscribe:function(topicName, callback, scope, subscriberData) { 2093 var subscription = "" + dwr.hub._subscriptionId; 2094 dwr.hub._subscriptionId++; 2095 dwr.hub._subscriptions[subscription] = { 2096 callback:callback, 2097 scope:scope, 2098 subscriberData:subscriberData 2099 }; 2100 dwr.engine._execute(dwr.engine._pathToDwrServlet, '__System', 'subscribe', topicName, subscription, {}); 2101 return subscription; 2102 }, 2103 2104 /** 2105 * Called by the server: A publish event has happened that we care about 2106 * @private 2107 * @param {Object} subscriptionId 2108 * @param {Object} publishData 2109 */ 2110 _remotePublish:function(subscriptionId, publishData) { 2111 var subscriptionData = dwr.hub._subscriptions[subscriptionId]; 2112 if (!subscriptionData) return; 2113 subscriptionData.callback.call(subscriptionData.scope, publishData, subscriptionData.subscriberData); 2114 }, 2115 2116 /** 2117 * Each time we subscribe to something, we use a unique number 2118 */ 2119 _subscriptionId:0, 2120 2121 /** 2122 * We need to remember what we are subscribed to so we can recall the callback 2123 */ 2124 _subscriptions:{} 2125 }; 2126})(); 2127 2128/** 2129 * High level data-sync API for use by Widget libraries like a Dojo-Data-Store. 2130 * For full documentation see org.directwebremoting.export.Data 2131 */ 2132dwr.data = { 2133 2134 /** 2135 * This is just documentation that defines how the listener parameter must act 2136 * in order to receive asynchronous updates 2137 */ 2138 StoreChangeListener:{ 2139 /** 2140 * Something has removed an item from the store 2141 * @param {StoreProvider} source The store from which it was moved 2142 * @param {string} itemId The ID of the item 2143 */ 2144 itemRemoved:function(source, itemId) { }, 2145 2146 /** 2147 * Something has added an item to the store 2148 * @param {StoreProvider} source The store from which it was moved 2149 * @param {Item} item The thing that has changed 2150 */ 2151 itemAdded:function(source, item) { }, 2152 2153 /** 2154 * Something has updated an item in the store 2155 * @param {StoreProvider} source The store from which it was moved 2156 * @param {Item} item The thing that has changed 2157 * @param {string[]} changedAttributes A list of changed attributes. If null then 2158 * you should assume that everything has changed 2159 */ 2160 itemChanged:function(source, item, changedAttributes) { } 2161 }, 2162 2163 /**
2164 * Create a cache object containing the functions to interact with a server 2165 * side StoreProvider 2166 * @param {string} storeId ID of server provided storage 2167 * @param {dwr.data.StoreChangeListener} listener See server documentation 2168 * This is likely to be true if dwr.engine.activeReverseAjax == true 2169 */ 2170 Cache:function(storeId, listener) { 2171 this.storeId = storeId; 2172 this.listener = listener; 2173 } 2174}; 2175 2176(function() { 2177 /** 2178 * Notes that there is a region of a page that wishes to subscribe to server 2179 * side data and registers a callback function to receive the data. 2180 * @param {Object} region filtering and sorting options. Includes: 2181 * - start: The beginning of the region of specific interest 2182 * - count: The number of items being viewed 2183 * - sort: The sort criteria 2184 * - query: The filter criteria 2185 * @param {function|object} callbackObj A standard DWR callback object 2186 * @return 2187 */ 2188 dwr.data.Cache.prototype.viewRegion = function(region, callbackObj) { 2189 if (!region) region = { }; 2190 if (!region.start) region.start = 0; 2191 if (!region.count) region.count = -1; 2192 if (!region.sort) region.sort = []; 2193 if (!region.query) region.query = {}; 2194 2195 return dwr.engine._execute(dwr.engine._pathToDwrServlet, '__Data', 'viewRegion', [ this.storeId, region, this.listener, callbackObj ]); 2196 }; 2197 2198 /** 2199 * As dwr.data.Cache.viewRegion() except that we only want to see a single item. 2200 * @param {string} itemId ID of object within the given store 2201 * @param {function|object} callbackObj A standard DWR callback object 2202 */ 2203 dwr.data.Cache.prototype.viewItem = function(itemId, callbackObj) { 2204 return dwr.engine._execute(dwr.engine._pathToDwrServlet, '__Data', 'viewItem', [ this.storeId, itemId, this.listener, callbackObj ]); 2205 }; 2206 2207 /** 2208 * Undo the action of dwr.data.view() 2209 * @param {function|object} callbackObj A standard DWR callback object 2210 */ 2211 dwr.data.Cache.prototype.unsubscribe = function(callbackObj) { 2212 if (this.listener) { 2213 return dwr.engine._execute(dwr.engine._pathToDwrServlet, '__Data', 'unsubscribe', [ this.storeId, this.listener, callbackObj ]); 2214 } 2215 }; 2216 2217 /** 2218 * Request an update to server side data 2219 * @param {Object} items An array of update descriptions 2220 * @param {function|object} callbackObj A standard DWR callback object 2221 */ 2222 dwr.data.Cache.prototype.update = function(items, callbackObj) { 2223 return dwr.engine._execute(dwr.engine._pathToDwrServlet, '__Data', 'update', [ this.storeId, items, callbackObj ]); 2224 }; 2225 2226})(); 2227
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.