[ Index ] |
|
Code source de Seagull 0.6.1 |
1 /** 2 Copyright (c) 2005, Brad Neuberg, bkn3@columbia.edu 3 http://codinginparadise.org 4 5 Permission is hereby granted, free of charge, to any person obtaining 6 a copy of this software and associated documentation files (the "Software"), 7 to deal in the Software without restriction, including without limitation 8 the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 and/or sell copies of the Software, and to permit persons to whom the 10 Software is furnished to do so, subject to the following conditions: 11 12 The above copyright notice and this permission notice shall be 13 included in all copies or substantial portions of the Software. 14 15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 20 OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 21 THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 */ 23 24 /** An object that provides DHTML history, history data, and bookmarking 25 for AJAX applications. */ 26 window.dhtmlHistory = { 27 /** Initializes our DHTML history. You should 28 call this after the page is finished loading. */ 29 /** public */ initialize: function() { 30 // only Internet Explorer needs to be explicitly initialized; 31 // other browsers don't have its particular behaviors. 32 // Basicly, IE doesn't autofill form data until the page 33 // is finished loading, which means historyStorage won't 34 // work until onload has been fired. 35 if (this.isInternetExplorer() == false) { 36 return; 37 } 38 39 // if this is the first time this page has loaded... 40 if (historyStorage.hasKey("DhtmlHistory_pageLoaded") == false) { 41 this.fireOnNewListener = false; 42 this.firstLoad = true; 43 historyStorage.put("DhtmlHistory_pageLoaded", true); 44 } 45 // else if this is a fake onload event 46 else { 47 this.fireOnNewListener = true; 48 this.firstLoad = false; 49 } 50 }, 51 52 /** Adds a history change listener. Note that 53 only one listener is supported at this 54 time. */ 55 /** public */ addListener: function(callback) { 56 this.listener = callback; 57 58 // if the page was just loaded and we 59 // should not ignore it, fire an event 60 // to our new listener now 61 if (this.fireOnNewListener == true) { 62 this.fireHistoryEvent(this.currentLocation); 63 this.fireOnNewListener = false; 64 } 65 }, 66 67 /** public */ add: function(newLocation, historyData) { 68 // most browsers require that we wait a certain amount of time before changing the 69 // location, such as 200 milliseconds; rather than forcing external callers to use 70 // window.setTimeout to account for this to prevent bugs, we internally handle this 71 // detail by using a 'currentWaitTime' variable and have requests wait in line 72 var self = this; 73 var addImpl = function() { 74 // indicate that the current wait time is now less 75 if (self.currentWaitTime > 0) 76 self.currentWaitTime = self.currentWaitTime - self.WAIT_TIME; 77 78 // remove any leading hash symbols on newLocation 79 newLocation = self.removeHash(newLocation); 80 81 // IE has a strange bug; if the newLocation 82 // is the same as _any_ preexisting id in the 83 // document, then the history action gets recorded 84 // twice; throw a programmer exception if there is 85 // an element with this ID 86 var idCheck = document.getElementById(newLocation); 87 if (idCheck != undefined || idCheck != null) { 88 var message = 89 "Exception: History locations can not have " 90 + "the same value as _any_ id's " 91 + "that might be in the document, " 92 + "due to a bug in Internet " 93 + "Explorer; please ask the " 94 + "developer to choose a history " 95 + "location that does not match " 96 + "any HTML id's in this " 97 + "document. The following ID " 98 + "is already taken and can not " 99 + "be a location: " 100 + newLocation; 101 102 throw message; 103 } 104 105 // store the history data into history storage 106 historyStorage.put(newLocation, historyData); 107 108 // indicate to the browser to ignore this upcomming 109 // location change 110 self.ignoreLocationChange = true; 111 112 // indicate to IE that this is an atomic location change 113 // block 114 this.ieAtomicLocationChange = true; 115 116 // save this as our current location 117 self.currentLocation = newLocation; 118 119 // change the browser location 120 window.location.hash = newLocation; 121 122 // change the hidden iframe's location if on IE 123 if (self.isInternetExplorer()) 124 self.iframe.src = "blank.html?" + newLocation; 125 126 // end of atomic location change block 127 // for IE 128 this.ieAtomicLocationChange = false; 129 }; 130 131 // now execute this add request after waiting a certain amount of time, so as to 132 // queue up requests 133 window.setTimeout(addImpl, this.currentWaitTime); 134 135 // indicate that the next request will have to wait for awhile 136 this.currentWaitTime = this.currentWaitTime + this.WAIT_TIME; 137 }, 138 139 /** public */ isFirstLoad: function() { 140 if (this.firstLoad == true) { 141 return true; 142 } 143 else { 144 return false; 145 } 146 }, 147 148 /** public */ isInternational: function() { 149 return false; 150 }, 151 152 /** public */ getVersion: function() { 153 return "0.05"; 154 }, 155 156 /** Gets the current hash value that is in the browser's 157 location bar, removing leading # symbols if they are present. */ 158 /** public */ getCurrentLocation: function() { 159 var currentLocation = this.removeHash(window.location.hash); 160 161 return currentLocation; 162 }, 163 164 165 166 167 168 /** Our current hash location, without the "#" symbol. */ 169 /** private */ currentLocation: null, 170 171 /** Our history change listener. */ 172 /** private */ listener: null, 173 174 /** A hidden IFrame we use in Internet Explorer to detect history 175 changes. */ 176 /** private */ iframe: null, 177 178 /** Indicates to the browser whether to ignore location changes. */ 179 /** private */ ignoreLocationChange: null, 180 181 /** The amount of time in milliseconds that we should wait between add requests. 182 Firefox is okay with 200 ms, but Internet Explorer needs 400. */ 183 /** private */ WAIT_TIME: 200, 184 185 /** The amount of time in milliseconds an add request has to wait in line before being 186 run on a window.setTimeout. */ 187 /** private */ currentWaitTime: 0, 188 189 /** A flag that indicates that we should fire a history change event 190 when we are ready, i.e. after we are initialized and 191 we have a history change listener. This is needed due to 192 an edge case in browsers other than Internet Explorer; if 193 you leave a page entirely then return, we must fire this 194 as a history change event. Unfortunately, we have lost 195 all references to listeners from earlier, because JavaScript 196 clears out. */ 197 /** private */ fireOnNewListener: null, 198 199 /** A variable that indicates whether this is the first time 200 this page has been loaded. If you go to a web page, leave 201 it for another one, and then return, the page's onload 202 listener fires again. We need a way to differentiate 203 between the first page load and subsequent ones. 204 This variable works hand in hand with the pageLoaded 205 variable we store into historyStorage.*/ 206 /** private */ firstLoad: null, 207 208 /** A variable to handle an important edge case in Internet 209 Explorer. In IE, if a user manually types an address into 210 their browser's location bar, we must intercept this by 211 continiously checking the location bar with an timer 212 interval. However, if we manually change the location 213 bar ourselves programmatically, when using our hidden 214 iframe, we need to ignore these changes. Unfortunately, 215 these changes are not atomic, so we surround them with 216 the variable 'ieAtomicLocationChange', that if true, 217 means we are programmatically setting the location and 218 should ignore this atomic chunked change. */ 219 /** private */ ieAtomicLocationChange: null, 220 221 /** Creates the DHTML history infrastructure. */ 222 /** private */ create: function() { 223 // get our initial location 224 var initialHash = this.getCurrentLocation(); 225 226 // save this as our current location 227 this.currentLocation = initialHash; 228 229 // write out a hidden iframe for IE and 230 // set the amount of time to wait between add() requests 231 if (this.isInternetExplorer()) { 232 document.write("<iframe style='border: 0px; width: 1px; " 233 + "height: 1px; position: absolute; bottom: 0px; " 234 + "right: 0px; visibility: visible;' " 235 + "name='DhtmlHistoryFrame' id='DhtmlHistoryFrame' " 236 + "src='blank.html?" + initialHash + "'>" 237 + "</iframe>"); 238 // wait 400 milliseconds between history 239 // updates on IE, versus 200 on Firefox 240 this.WAIT_TIME = 400; 241 } 242 243 // add an unload listener for the page; this is 244 // needed for Firefox 1.5+ because this browser caches all 245 // dynamic updates to the page, which can break some of our 246 // logic related to testing whether this is the first instance 247 // a page has loaded or whether it is being pulled from the cache 248 var self = this; 249 window.onunload = function() { 250 self.firstLoad = null; 251 }; 252 253 // determine if this is our first page load; 254 // for Internet Explorer, we do this in 255 // this.iframeLoaded(), which is fired on 256 // page load. We do it there because 257 // we have no historyStorage at this point 258 // in IE, which only exists after the page 259 // is finished loading for that browser 260 if (this.isInternetExplorer() == false) { 261 if (historyStorage.hasKey("DhtmlHistory_pageLoaded") == false) { 262 this.ignoreLocationChange = true; 263 this.firstLoad = true; 264 historyStorage.put("DhtmlHistory_pageLoaded", true); 265 } 266 else { 267 // indicate that we want to pay attention 268 // to this location change 269 this.ignoreLocationChange = false; 270 // For browser's other than IE, fire 271 // a history change event; on IE, 272 // the event will be thrown automatically 273 // when it's hidden iframe reloads 274 // on page load. 275 // Unfortunately, we don't have any 276 // listeners yet; indicate that we want 277 // to fire an event when a listener 278 // is added. 279 this.fireOnNewListener = true; 280 } 281 } 282 else { // Internet Explorer 283 // the iframe will get loaded on page 284 // load, and we want to ignore this fact 285 this.ignoreLocationChange = true; 286 } 287 288 if (this.isInternetExplorer()) { 289 this.iframe = document.getElementById("DhtmlHistoryFrame"); 290 } 291 292 // other browsers can use a location handler that checks 293 // at regular intervals as their primary mechanism; 294 // we use it for Internet Explorer as well to handle 295 // an important edge case; see checkLocation() for 296 // details 297 var self = this; 298 var locationHandler = function() { 299 self.checkLocation(); 300 }; 301 setInterval(locationHandler, 100); 302 }, 303 304 /** Notify the listener of new history changes. */ 305 /** private */ fireHistoryEvent: function(newHash) { 306 // extract the value from our history storage for 307 // this hash 308 var historyData = historyStorage.get(newHash); 309 310 // call our listener 311 this.listener.call(null, newHash, historyData); 312 }, 313 314 /** Sees if the browsers has changed location. This is the primary history mechanism 315 for Firefox. For Internet Explorer, we use this to handle an important edge case: 316 if a user manually types in a new hash value into their Internet Explorer location 317 bar and press enter, we want to intercept this and notify any history listener. */ 318 /** private */ checkLocation: function() { 319 // ignore any location changes that we made ourselves 320 // for browsers other than Internet Explorer 321 if (this.isInternetExplorer() == false 322 && this.ignoreLocationChange == true) { 323 this.ignoreLocationChange = false; 324 return; 325 } 326 327 // if we are dealing with Internet Explorer 328 // and we are in the middle of making a location 329 // change from an iframe, ignore it 330 if (this.isInternetExplorer() == false 331 && this.ieAtomicLocationChange == true) { 332 return; 333 } 334 335 // get hash location 336 var hash = this.getCurrentLocation(); 337 338 // see if there has been a change 339 if (hash == this.currentLocation) 340 return; 341 342 // on Internet Explorer, we need to intercept users manually 343 // entering locations into the browser; we do this by comparing 344 // the browsers location against the iframes location; if they 345 // differ, we are dealing with a manual event and need to 346 // place it inside our history, otherwise we can return 347 this.ieAtomicLocationChange = true; 348 349 if (this.isInternetExplorer() 350 && this.getIFrameHash() != hash) { 351 this.iframe.src = "blank.html?" + hash; 352 } 353 else if (this.isInternetExplorer()) { 354 // the iframe is unchanged 355 return; 356 } 357 358 // save this new location 359 this.currentLocation = hash; 360 361 this.ieAtomicLocationChange = false; 362 363 // notify listeners of the change 364 this.fireHistoryEvent(hash); 365 }, 366 367 /** Gets the current location of the hidden IFrames 368 that is stored as history. For Internet Explorer. */ 369 /** private */ getIFrameHash: function() { 370 // get the new location 371 var historyFrame = document.getElementById("DhtmlHistoryFrame"); 372 var doc = historyFrame.contentWindow.document; 373 var hash = new String(doc.location.search); 374 375 if (hash.length == 1 && hash.charAt(0) == "?") 376 hash = ""; 377 else if (hash.length >= 2 && hash.charAt(0) == "?") 378 hash = hash.substring(1); 379 380 381 return hash; 382 }, 383 384 /** Removes any leading hash that might be on a location. */ 385 /** private */ removeHash: function(hashValue) { 386 if (hashValue == null || hashValue == undefined) 387 return null; 388 else if (hashValue == "") 389 return ""; 390 else if (hashValue.length == 1 && hashValue.charAt(0) == "#") 391 return ""; 392 else if (hashValue.length > 1 && hashValue.charAt(0) == "#") 393 return hashValue.substring(1); 394 else 395 return hashValue; 396 }, 397 398 /** For IE, says when the hidden iframe has finished loading. */ 399 /** private */ iframeLoaded: function(newLocation) { 400 // ignore any location changes that we made ourselves 401 if (this.ignoreLocationChange == true) { 402 this.ignoreLocationChange = false; 403 return; 404 } 405 406 // get the new location 407 var hash = new String(newLocation.search); 408 if (hash.length == 1 && hash.charAt(0) == "?") 409 hash = ""; 410 else if (hash.length >= 2 && hash.charAt(0) == "?") 411 hash = hash.substring(1); 412 413 // move to this location in the browser location bar 414 // if we are not dealing with a page load event 415 if (this.pageLoadEvent != true) { 416 window.location.hash = hash; 417 } 418 419 // notify listeners of the change 420 this.fireHistoryEvent(hash); 421 }, 422 423 /** Determines if this is Internet Explorer. */ 424 /** private */ isInternetExplorer: function() { 425 var userAgent = navigator.userAgent.toLowerCase(); 426 if (document.all && userAgent.indexOf('msie')!=-1) { 427 return true; 428 } 429 else { 430 return false; 431 } 432 } 433 }; 434 435 436 437 438 439 440 441 442 443 444 445 446 /** An object that uses a hidden form to store history state 447 across page loads. The chief mechanism for doing so is using 448 the fact that browser's save the text in form data for the 449 life of the browser and cache, which means the text is still 450 there when the user navigates back to the page. See 451 http://codinginparadise.org/weblog/2005/08/ajax-tutorial-saving-session-across.html 452 for full details. */ 453 window.historyStorage = { 454 /** If true, we are debugging and show the storage textfield. */ 455 /** public */ debugging: false, 456 457 /** Our hash of key name/values. */ 458 /** private */ storageHash: new Object(), 459 460 /** If true, we have loaded our hash table out of the storage form. */ 461 /** private */ hashLoaded: false, 462 463 /** public */ put: function(key, value) { 464 this.assertValidKey(key); 465 466 // if we already have a value for this, 467 // remove the value before adding the 468 // new one 469 if (this.hasKey(key)) { 470 this.remove(key); 471 } 472 473 // store this new key 474 this.storageHash[key] = value; 475 476 // save and serialize the hashtable into the form 477 this.saveHashTable(); 478 }, 479 480 /** public */ get: function(key) { 481 this.assertValidKey(key); 482 483 // make sure the hash table has been loaded 484 // from the form 485 this.loadHashTable(); 486 487 var value = this.storageHash[key]; 488 489 if (value == undefined) 490 return null; 491 else 492 return value; 493 }, 494 495 /** public */ remove: function(key) { 496 this.assertValidKey(key); 497 498 // make sure the hash table has been loaded 499 // from the form 500 this.loadHashTable(); 501 502 // delete the value 503 delete this.storageHash[key]; 504 505 // serialize and save the hash table into the 506 // form 507 this.saveHashTable(); 508 }, 509 510 /** Clears out all saved data. */ 511 /** public */ reset: function() { 512 this.storageField.value = ""; 513 this.storageHash = new Object(); 514 }, 515 516 /** public */ hasKey: function(key) { 517 this.assertValidKey(key); 518 519 // make sure the hash table has been loaded 520 // from the form 521 this.loadHashTable(); 522 523 if (typeof this.storageHash[key] == "undefined") 524 return false; 525 else 526 return true; 527 }, 528 529 /** Determines whether the key given is valid; 530 keys can only have letters, numbers, the dash, 531 underscore, spaces, or one of the 532 following characters: 533 !@#$%^&*()+=:;,./?|\~{}[] */ 534 /** public */ isValidKey: function(key) { 535 // allow all strings, since we don't use XML serialization 536 // format anymore 537 return (typeof key == "string"); 538 539 /* 540 if (typeof key != "string") 541 key = key.toString(); 542 543 544 var matcher = 545 /^[a-zA-Z0-9_ \!\@\#\$\%\^\&\*\(\)\+\=\:\;\,\.\/\?\|\\\~\{\}\[\]]*$/; 546 547 return matcher.test(key);*/ 548 }, 549 550 551 552 553 /** A reference to our textarea field. */ 554 /** private */ storageField: null, 555 556 /** private */ init: function() { 557 // write a hidden form into the page 558 var styleValue = "position: absolute; top: -1000px; left: -1000px;"; 559 if (this.debugging == true) { 560 styleValue = "width: 30em; height: 30em;"; 561 } 562 563 var newContent = 564 "<form id='historyStorageForm' " 565 + "method='GET' " 566 + "style='" + styleValue + "'>" 567 + "<textarea id='historyStorageField' " 568 + "style='" + styleValue + "'" 569 + "left: -1000px;' " 570 + "name='historyStorageField'></textarea>" 571 + "</form>"; 572 document.write(newContent); 573 574 this.storageField = document.getElementById("historyStorageField"); 575 }, 576 577 /** Asserts that a key is valid, throwing 578 an exception if it is not. */ 579 /** private */ assertValidKey: function(key) { 580 if (this.isValidKey(key) == false) { 581 throw "Please provide a valid key for " 582 + "window.historyStorage, key= " 583 + key; 584 } 585 }, 586 587 /** Loads the hash table up from the form. */ 588 /** private */ loadHashTable: function() { 589 if (this.hashLoaded == false) { 590 // get the hash table as a serialized 591 // string 592 var serializedHashTable = this.storageField.value; 593 594 if (serializedHashTable != "" && 595 serializedHashTable != null) { 596 // destringify the content back into a 597 // real JavaScript object 598 this.storageHash = eval('(' + serializedHashTable + ')'); 599 } 600 601 this.hashLoaded = true; 602 } 603 }, 604 605 /** Saves the hash table into the form. */ 606 /** private */ saveHashTable: function() { 607 this.loadHashTable(); 608 609 // serialized the hash table 610 var serializedHashTable = HTML_AJAX_JSON.stringify(this.storageHash); 611 612 // save this value 613 this.storageField.value = serializedHashTable; 614 } 615 }; 616 617 /** Initialize all of our objects now. */ 618 window.historyStorage.init(); 619 window.dhtmlHistory.create();
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Fri Mar 30 01:27:52 2007 | par Balluche grâce à PHPXref 0.7 |