ajax.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. /* vim: set expandtab sw=4 ts=4 sts=4: */
  2. /**
  3. * This object handles ajax requests for pages. It also
  4. * handles the reloading of the main menu and scripts.
  5. */
  6. var AJAX = {
  7. /**
  8. * @var bool active Whether we are busy
  9. */
  10. active: false,
  11. /**
  12. * @var object source The object whose event initialized the request
  13. */
  14. source: null,
  15. /**
  16. * @var object xhr A reference to the ajax request that is currently running
  17. */
  18. xhr: null,
  19. /**
  20. * @var function Callback to execute after a successful request
  21. * Used by PMA_commonFunctions from common.js
  22. */
  23. _callback: function () {},
  24. /**
  25. * @var bool _debug Makes noise in your Firebug console
  26. */
  27. _debug: false,
  28. /**
  29. * @var object $msgbox A reference to a jQuery object that links to a message
  30. * box that is generated by PMA_ajaxShowMessage()
  31. */
  32. $msgbox: null,
  33. /**
  34. * Given the filename of a script, returns a hash to be
  35. * used to refer to all the events registered for the file
  36. *
  37. * @param string key The filename for which to get the event name
  38. *
  39. * @return int
  40. */
  41. hash: function (key) {
  42. /* http://burtleburtle.net/bob/hash/doobs.html#one */
  43. key += "";
  44. var len = key.length, hash = 0, i = 0;
  45. for (; i < len; ++i) {
  46. hash += key.charCodeAt(i);
  47. hash += (hash << 10);
  48. hash ^= (hash >> 6);
  49. }
  50. hash += (hash << 3);
  51. hash ^= (hash >> 11);
  52. hash += (hash << 15);
  53. return Math.abs(hash);
  54. },
  55. /**
  56. * Registers an onload event for a file
  57. *
  58. * @param string file The filename for which to register the event
  59. * @param function func The function to execute when the page is ready
  60. *
  61. * @return self For chaining
  62. */
  63. registerOnload: function (file, func) {
  64. var eventName = 'onload_' + AJAX.hash(file);
  65. $(document).bind(eventName, func);
  66. if (this._debug) {
  67. console.log(
  68. // no need to translate
  69. "Registered event " + eventName + " for file " + file
  70. );
  71. }
  72. return this;
  73. },
  74. /**
  75. * Registers a teardown event for a file. This is useful to execute functions
  76. * that unbind events for page elements that are about to be removed.
  77. *
  78. * @param string file The filename for which to register the event
  79. * @param function func The function to execute when
  80. * the page is about to be torn down
  81. *
  82. * @return self For chaining
  83. */
  84. registerTeardown: function (file, func) {
  85. var eventName = 'teardown_' + AJAX.hash(file);
  86. $(document).bind(eventName, func);
  87. if (this._debug) {
  88. console.log(
  89. // no need to translate
  90. "Registered event " + eventName + " for file " + file
  91. );
  92. }
  93. return this;
  94. },
  95. /**
  96. * Called when a page has finished loading, once for every
  97. * file that registered to the onload event of that file.
  98. *
  99. * @param string file The filename for which to fire the event
  100. *
  101. * @return void
  102. */
  103. fireOnload: function (file) {
  104. var eventName = 'onload_' + AJAX.hash(file);
  105. $(document).trigger(eventName);
  106. if (this._debug) {
  107. console.log(
  108. // no need to translate
  109. "Fired event " + eventName + " for file " + file
  110. );
  111. }
  112. },
  113. /**
  114. * Called just before a page is torn down, once for every
  115. * file that registered to the teardown event of that file.
  116. *
  117. * @param string file The filename for which to fire the event
  118. *
  119. * @return void
  120. */
  121. fireTeardown: function (file) {
  122. var eventName = 'teardown_' + AJAX.hash(file);
  123. $(document).triggerHandler(eventName);
  124. if (this._debug) {
  125. console.log(
  126. // no need to translate
  127. "Fired event " + eventName + " for file " + file
  128. );
  129. }
  130. },
  131. /**
  132. * Event handler for clicks on links and form submissions
  133. *
  134. * @param object e Event data
  135. *
  136. * @return void
  137. */
  138. requestHandler: function (event) {
  139. // In some cases we don't want to handle the request here and either
  140. // leave the browser deal with it natively (e.g: file download)
  141. // or leave an existing ajax event handler present elsewhere deal with it
  142. var href = $(this).attr('href');
  143. if (typeof event != 'undefined' && (event.shiftKey || event.ctrlKey)) {
  144. return true;
  145. } else if ($(this).attr('target')) {
  146. return true;
  147. } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) {
  148. return true;
  149. } else if (href && href.match(/^#/)) {
  150. return true;
  151. } else if (href && href.match(/^mailto/)) {
  152. return true;
  153. } else if ($(this).hasClass('ui-datepicker-next') ||
  154. $(this).hasClass('ui-datepicker-prev')
  155. ) {
  156. return true;
  157. }
  158. if (typeof event != 'undefined') {
  159. event.preventDefault();
  160. event.stopImmediatePropagation();
  161. }
  162. if (AJAX.active === true) {
  163. // Cancel the old request if abortable, when the user requests
  164. // something else. Otherwise silently bail out, as there is already
  165. // a request well in progress.
  166. if (AJAX.xhr) {
  167. //In case of a link request, attempt aborting
  168. AJAX.xhr.abort();
  169. if(AJAX.xhr.status === 0 && AJAX.xhr.statusText === 'abort') {
  170. //If aborted
  171. AJAX.$msgbox = PMA_ajaxShowMessage(PMA_messages.strAbortedRequest);
  172. AJAX.active = false;
  173. AJAX.xhr = null;
  174. } else {
  175. //If can't abort
  176. return false;
  177. }
  178. } else {
  179. //In case submitting a form, don't attempt aborting
  180. return false;
  181. }
  182. }
  183. AJAX.source = $(this);
  184. $('html, body').animate({scrollTop: 0}, 'fast');
  185. var isLink = !! href || false;
  186. var url = isLink ? href : $(this).attr('action');
  187. var params = 'ajax_request=true&ajax_page_request=true';
  188. if (! isLink) {
  189. params += '&' + $(this).serialize();
  190. }
  191. // Add a list of menu hashes that we have in the cache to the request
  192. params += AJAX.cache.menus.getRequestParam();
  193. if (AJAX._debug) {
  194. console.log("Loading: " + url); // no need to translate
  195. }
  196. if (isLink) {
  197. AJAX.active = true;
  198. AJAX.$msgbox = PMA_ajaxShowMessage();
  199. //Save reference for the new link request
  200. AJAX.xhr = $.get(url, params, AJAX.responseHandler);
  201. } else {
  202. /**
  203. * Manually fire the onsubmit event for the form, if any.
  204. * The event was saved in the jQuery data object by an onload
  205. * handler defined below. Workaround for bug #3583316
  206. */
  207. var onsubmit = $(this).data('onsubmit');
  208. // Submit the request if there is no onsubmit handler
  209. // or if it returns a value that evaluates to true
  210. if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) {
  211. AJAX.active = true;
  212. AJAX.$msgbox = PMA_ajaxShowMessage();
  213. $.post(url, params, AJAX.responseHandler);
  214. }
  215. }
  216. },
  217. /**
  218. * Called after the request that was initiated by this.requestHandler()
  219. * has completed successfully or with a caught error. For completely
  220. * failed requests or requests with uncaught errors, see the .ajaxError
  221. * handler at the bottom of this file.
  222. *
  223. * To refer to self use 'AJAX', instead of 'this' as this function
  224. * is called in the jQuery context.
  225. *
  226. * @param object e Event data
  227. *
  228. * @return void
  229. */
  230. responseHandler: function (data) {
  231. if (data.success) {
  232. $table_clone = false;
  233. PMA_ajaxRemoveMessage(AJAX.$msgbox);
  234. if (data._redirect) {
  235. PMA_ajaxShowMessage(data._redirect, false);
  236. AJAX.active = false;
  237. AJAX.xhr = null;
  238. return;
  239. }
  240. AJAX.scriptHandler.reset(function () {
  241. if (data._reloadNavigation) {
  242. PMA_reloadNavigation();
  243. }
  244. if (data._reloadQuerywindow) {
  245. var params = data._reloadQuerywindow;
  246. PMA_querywindow.reload(
  247. params.db,
  248. params.table,
  249. params.sql_query
  250. );
  251. }
  252. if (data._focusQuerywindow) {
  253. PMA_querywindow.focus(
  254. data._focusQuerywindow
  255. );
  256. }
  257. if (data._title) {
  258. $('title').replaceWith(data._title);
  259. }
  260. if (data._menu) {
  261. AJAX.cache.menus.replace(data._menu);
  262. AJAX.cache.menus.add(data._menuHash, data._menu);
  263. } else if (data._menuHash) {
  264. AJAX.cache.menus.replace(AJAX.cache.menus.get(data._menuHash));
  265. }
  266. // Remove all containers that may have
  267. // been added outside of #page_content
  268. $('body').children()
  269. .not('#pma_navigation')
  270. .not('#floating_menubar')
  271. .not('#goto_pagetop')
  272. .not('#page_content')
  273. .not('#selflink')
  274. .not('#session_debug')
  275. .not('#pma_header')
  276. .not('#pma_footer')
  277. .not('#pma_demo')
  278. .remove();
  279. // Replace #page_content with new content
  280. if (data.message && data.message.length > 0) {
  281. $('#page_content').replaceWith(
  282. "<div id='page_content'>" + data.message + "</div>"
  283. );
  284. PMA_highlightSQL($('#page_content'));
  285. checkNumberOfFields();
  286. }
  287. if (data._selflink) {
  288. var source = data._selflink.split('?')[0];
  289. //Check for faulty links
  290. if (source == "import.php") {
  291. var replacement = "tbl_sql.php";
  292. data._selflink = data._selflink.replace(source,replacement);
  293. }
  294. $('#selflink > a').attr('href', data._selflink);
  295. }
  296. if (data._scripts) {
  297. AJAX.scriptHandler.load(data._scripts, data._params.token);
  298. }
  299. if (data._selflink && data._scripts && data._menuHash && data._params) {
  300. AJAX.cache.add(
  301. data._selflink,
  302. data._scripts,
  303. data._menuHash,
  304. data._params,
  305. AJAX.source.attr('rel')
  306. );
  307. }
  308. if (data._params) {
  309. PMA_commonParams.setAll(data._params);
  310. }
  311. if (data._displayMessage) {
  312. $('#page_content').prepend(data._displayMessage);
  313. PMA_highlightSQL($('#page_content'));
  314. }
  315. $('#pma_errors').remove();
  316. if (data._errors) {
  317. $('<div/>', {id : 'pma_errors'})
  318. .insertAfter('#selflink')
  319. .append(data._errors);
  320. }
  321. if (typeof AJAX._callback === 'function') {
  322. AJAX._callback.call();
  323. }
  324. AJAX._callback = function () {};
  325. });
  326. } else {
  327. PMA_ajaxShowMessage(data.error, false);
  328. AJAX.active = false;
  329. AJAX.xhr = null;
  330. if (parseInt(data.redirect_flag) == 1) {
  331. // add one more GET param to display session expiry msg
  332. window.location.href += '&session_expired=1';
  333. window.location.reload();
  334. }
  335. if (data.fieldWithError) {
  336. $(':input.error').removeClass("error");
  337. $('#'+data.fieldWithError).addClass("error");
  338. }
  339. }
  340. },
  341. /**
  342. * This object is in charge of downloading scripts,
  343. * keeping track of what's downloaded and firing
  344. * the onload event for them when the page is ready.
  345. */
  346. scriptHandler: {
  347. /**
  348. * @var array _scripts The list of files already downloaded
  349. */
  350. _scripts: [],
  351. /**
  352. * @var array _scriptsToBeLoaded The list of files that
  353. * need to be downloaded
  354. */
  355. _scriptsToBeLoaded: [],
  356. /**
  357. * @var array _scriptsToBeFired The list of files for which
  358. * to fire the onload event
  359. */
  360. _scriptsToBeFired: [],
  361. /**
  362. * Records that a file has been downloaded
  363. *
  364. * @param string file The filename
  365. * @param string fire Whether this file will be registering
  366. * onload/teardown events
  367. *
  368. * @return self For chaining
  369. */
  370. add: function (file, fire) {
  371. this._scripts.push(file);
  372. if (fire) {
  373. // Record whether to fire any events for the file
  374. // This is necessary to correctly tear down the initial page
  375. this._scriptsToBeFired.push(file);
  376. }
  377. return this;
  378. },
  379. /**
  380. * Download a list of js files in one request
  381. *
  382. * @param array files An array of filenames and flags
  383. *
  384. * @return void
  385. */
  386. load: function (files, token) {
  387. var self = this;
  388. self._scriptsToBeLoaded = [];
  389. self._scriptsToBeFired = [];
  390. for (var i in files) {
  391. self._scriptsToBeLoaded.push(files[i].name);
  392. if (files[i].fire) {
  393. self._scriptsToBeFired.push(files[i].name);
  394. }
  395. }
  396. // Generate a request string
  397. var request = [];
  398. var needRequest = false;
  399. for (var index in self._scriptsToBeLoaded) {
  400. var script = self._scriptsToBeLoaded[index];
  401. // Only for scripts that we don't already have
  402. if ($.inArray(script, self._scripts) == -1) {
  403. needRequest = true;
  404. this.add(script);
  405. request.push("scripts[]=" + script);
  406. }
  407. }
  408. request.push("token=" + token);
  409. request.push("call_done=1");
  410. // Download the composite js file, if necessary
  411. if (needRequest) {
  412. this.appendScript("js/get_scripts.js.php?" + request.join("&"));
  413. } else {
  414. self.done();
  415. }
  416. },
  417. /**
  418. * Called whenever all files are loaded
  419. *
  420. * @return void
  421. */
  422. done: function () {
  423. if (typeof ErrorReport !== 'undefined') {
  424. ErrorReport.wrap_global_functions();
  425. }
  426. for (var i in this._scriptsToBeFired) {
  427. AJAX.fireOnload(this._scriptsToBeFired[i]);
  428. }
  429. AJAX.active = false;
  430. },
  431. /**
  432. * Appends a script element to the head to load the scripts
  433. *
  434. * @return void
  435. */
  436. appendScript: function (url) {
  437. var head = document.head || document.getElementsByTagName('head')[0];
  438. var script = document.createElement('script');
  439. script.type = 'text/javascript';
  440. script.src = url;
  441. head.appendChild(script);
  442. },
  443. /**
  444. * Fires all the teardown event handlers for the current page
  445. * and rebinds all forms and links to the request handler
  446. *
  447. * @param function callback The callback to call after resetting
  448. *
  449. * @return void
  450. */
  451. reset: function (callback) {
  452. for (var i in this._scriptsToBeFired) {
  453. AJAX.fireTeardown(this._scriptsToBeFired[i]);
  454. }
  455. this._scriptsToBeFired = [];
  456. /**
  457. * Re-attach a generic event handler to clicks
  458. * on pages and submissions of forms
  459. */
  460. $('a').die('click').live('click', AJAX.requestHandler);
  461. $('form').die('submit').live('submit', AJAX.requestHandler);
  462. AJAX.cache.update();
  463. callback();
  464. }
  465. }
  466. };
  467. /**
  468. * Here we register a function that will remove the onsubmit event from all
  469. * forms that will be handled by the generic page loader. We then save this
  470. * event handler in the "jQuery data", so that we can fire it up later in
  471. * AJAX.requestHandler().
  472. *
  473. * See bug #3583316
  474. */
  475. AJAX.registerOnload('functions.js', function () {
  476. // Registering the onload event for functions.js
  477. // ensures that it will be fired for all pages
  478. $('form').not('.ajax').not('.disableAjax').each(function () {
  479. if ($(this).attr('onsubmit')) {
  480. $(this).data('onsubmit', this.onsubmit).attr('onsubmit', '');
  481. }
  482. });
  483. });
  484. /**
  485. * An implementation of a client-side page cache.
  486. * This object also uses the cache to provide a simple microhistory,
  487. * that is the ability to use the back and forward buttons in the browser
  488. */
  489. AJAX.cache = {
  490. /**
  491. * @var int The maximum number of pages to keep in the cache
  492. */
  493. MAX: 6,
  494. /**
  495. * @var object A hash used to prime the cache with data about the initially
  496. * loaded page. This is set in the footer, and then loaded
  497. * by a double-queued event further down this file.
  498. */
  499. primer: {},
  500. /**
  501. * @var array Stores the content of the cached pages
  502. */
  503. pages: [],
  504. /**
  505. * @var int The index of the currently loaded page
  506. * This is used to know at which point in the history we are
  507. */
  508. current: 0,
  509. /**
  510. * Saves a new page in the cache
  511. *
  512. * @param string hash The hash part of the url that is being loaded
  513. * @param array scripts A list of scripts that is requured for the page
  514. * @param string menu A hash that links to a menu stored
  515. * in a dedicated menu cache
  516. * @param array params A list of parameters used by PMA_commonParams()
  517. * @param string rel A relationship to the current page:
  518. * 'samepage': Forces the response to be treated as
  519. * the same page as the current one
  520. * 'newpage': Forces the response to be treated as
  521. * a new page
  522. * undefined: Default behaviour, 'samepage' if the
  523. * selflinks of the two pages are the same.
  524. * 'newpage' otherwise
  525. *
  526. * @return void
  527. */
  528. add: function (hash, scripts, menu, params, rel) {
  529. if (this.pages.length > AJAX.cache.MAX) {
  530. // Trim the cache, to the maximum number of allowed entries
  531. // This way we will have a cached menu for every page
  532. for (var i = 0; i < this.pages.length - this.MAX; i++) {
  533. delete this.pages[i];
  534. }
  535. }
  536. while (this.current < this.pages.length) {
  537. // trim the cache if we went back in the history
  538. // and are now going forward again
  539. this.pages.pop();
  540. }
  541. if (rel === 'newpage' ||
  542. (
  543. typeof rel === 'undefined' && (
  544. typeof this.pages[this.current - 1] === 'undefined' ||
  545. this.pages[this.current - 1].hash !== hash
  546. )
  547. )
  548. ) {
  549. this.pages.push({
  550. hash: hash,
  551. content: $('#page_content').html(),
  552. scripts: scripts,
  553. selflink: $('#selflink').html(),
  554. menu: menu,
  555. params: params
  556. });
  557. AJAX.setUrlHash(this.current, hash);
  558. this.current++;
  559. }
  560. },
  561. /**
  562. * Restores a page from the cache. This is called when the hash
  563. * part of the url changes and it's structure appears to be valid
  564. *
  565. * @param string index Which page from the history to load
  566. *
  567. * @return void
  568. */
  569. navigate: function (index) {
  570. if (typeof this.pages[index] === 'undefined'
  571. || typeof this.pages[index].content === 'undefined'
  572. || typeof this.pages[index].menu === 'undefined'
  573. || ! AJAX.cache.menus.get(this.pages[index].menu)
  574. ) {
  575. PMA_ajaxShowMessage(
  576. '<div class="error">' + PMA_messages.strInvalidPage + '</div>',
  577. false
  578. );
  579. } else {
  580. AJAX.active = true;
  581. var record = this.pages[index];
  582. AJAX.scriptHandler.reset(function () {
  583. $('#page_content').html(record.content);
  584. $('#selflink').html(record.selflink);
  585. AJAX.cache.menus.replace(AJAX.cache.menus.get(record.menu));
  586. PMA_commonParams.setAll(record.params);
  587. AJAX.scriptHandler.load(record.scripts, record.params ? record.params.token : PMA_commonParams.get('token'));
  588. AJAX.cache.current = ++index;
  589. });
  590. }
  591. },
  592. /**
  593. * Resaves the content of the current page in the cache.
  594. * Necessary in order not to show the user some outdated version of the page
  595. *
  596. * @return void
  597. */
  598. update: function () {
  599. var page = this.pages[this.current - 1];
  600. if (page) {
  601. page.content = $('#page_content').html();
  602. }
  603. },
  604. /**
  605. * @var object Dedicated menu cache
  606. */
  607. menus: {
  608. /**
  609. * Returns the number of items in an associative array
  610. *
  611. * @return int
  612. */
  613. size: function (obj) {
  614. var size = 0, key;
  615. for (key in obj) {
  616. if (obj.hasOwnProperty(key)) {
  617. size++;
  618. }
  619. }
  620. return size;
  621. },
  622. /**
  623. * @var hash Stores the content of the cached menus
  624. */
  625. data: {},
  626. /**
  627. * Saves a new menu in the cache
  628. *
  629. * @param string hash The hash (trimmed md5) of the menu to be saved
  630. * @param string content The HTML code of the menu to be saved
  631. *
  632. * @return void
  633. */
  634. add: function (hash, content) {
  635. if (this.size(this.data) > AJAX.cache.MAX) {
  636. // when the cache grows, we remove the oldest entry
  637. var oldest, key, init = 0;
  638. for (var i in this.data) {
  639. if (this.data[i]) {
  640. if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) {
  641. oldest = this.data[i].timestamp;
  642. key = i;
  643. init = 1;
  644. }
  645. }
  646. }
  647. delete this.data[key];
  648. }
  649. this.data[hash] = {
  650. content: content,
  651. timestamp: new Date()
  652. };
  653. },
  654. /**
  655. * Retrieves a menu given its hash
  656. *
  657. * @param string hash The hash of the menu to be retrieved
  658. *
  659. * @return string
  660. */
  661. get: function (hash) {
  662. if (this.data[hash]) {
  663. return this.data[hash].content;
  664. } else {
  665. // This should never happen as long as the number of stored menus
  666. // is larger or equal to the number of pages in the page cache
  667. return '';
  668. }
  669. },
  670. /**
  671. * Prepares part of the parameter string used during page requests,
  672. * this is necessary to tell the server which menus we have in the cache
  673. *
  674. * @return string
  675. */
  676. getRequestParam: function () {
  677. var param = '';
  678. var menuHashes = [];
  679. for (var i in this.data) {
  680. menuHashes.push(i);
  681. }
  682. var menuHashesParam = menuHashes.join('-');
  683. if (menuHashesParam) {
  684. param = '&menuHashes=' + menuHashesParam;
  685. }
  686. return param;
  687. },
  688. /**
  689. * Replaces the menu with new content
  690. *
  691. * @return void
  692. */
  693. replace: function (content) {
  694. $('#floating_menubar').html(content)
  695. // Remove duplicate wrapper
  696. // TODO: don't send it in the response
  697. .children().first().remove();
  698. $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
  699. }
  700. }
  701. };
  702. /**
  703. * URL hash management module.
  704. * Allows direct bookmarking and microhistory.
  705. */
  706. AJAX.setUrlHash = (function (jQuery, window) {
  707. "use strict";
  708. /**
  709. * Indictaes whether we have already completed
  710. * the initialisation of the hash
  711. *
  712. * @access private
  713. */
  714. var ready = false;
  715. /**
  716. * Stores a hash that needed to be set when we were not ready
  717. *
  718. * @access private
  719. */
  720. var savedHash = "";
  721. /**
  722. * Flag to indicate if the change of hash was triggered
  723. * by a user pressing the back/forward button or if
  724. * the change was triggered internally
  725. *
  726. * @access private
  727. */
  728. var userChange = true;
  729. // Fix favicon disappearing in Firefox when setting location.hash
  730. function resetFavicon() {
  731. if (jQuery.browser.mozilla) {
  732. // Move the link tags for the favicon to the bottom
  733. // of the head element to force a reload of the favicon
  734. $('head > link[href=favicon\\.ico]').appendTo('head');
  735. }
  736. }
  737. /**
  738. * Sets the hash part of the URL
  739. *
  740. * @access public
  741. */
  742. function setUrlHash(index, hash) {
  743. /*
  744. * Known problem:
  745. * Setting hash leads to reload in webkit:
  746. * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html
  747. *
  748. * so we expect that users are not running an ancient Safari version
  749. */
  750. userChange = false;
  751. if (ready) {
  752. window.location.hash = "PMAURL-" + index + ":" + hash;
  753. resetFavicon();
  754. } else {
  755. savedHash = "PMAURL-" + index + ":" + hash;
  756. }
  757. }
  758. /**
  759. * Start initialisation
  760. */
  761. if (window.location.hash.substring(0, 8) == '#PMAURL-') {
  762. // We have a valid hash, let's redirect the user
  763. // to the page that it's pointing to
  764. window.location = window.location.hash.substring(
  765. window.location.hash.indexOf(':') + 1
  766. );
  767. } else {
  768. // We don't have a valid hash, so we'll set it up
  769. // when the page finishes loading
  770. jQuery(function () {
  771. /* Check if we should set URL */
  772. if (savedHash !== "") {
  773. window.location.hash = savedHash;
  774. savedHash = "";
  775. resetFavicon();
  776. }
  777. // Indicate that we're done initialising
  778. ready = true;
  779. });
  780. }
  781. /**
  782. * Register an event handler for when the url hash changes
  783. */
  784. jQuery(function () {
  785. jQuery(window).hashchange(function () {
  786. if (userChange === false) {
  787. // Ignore internally triggered hash changes
  788. userChange = true;
  789. } else if (/^#PMAURL-\d+:/.test(window.location.hash)) {
  790. // Change page if the hash changed was triggered by a user action
  791. var index = window.location.hash.substring(
  792. 8, window.location.hash.indexOf(':')
  793. );
  794. AJAX.cache.navigate(index);
  795. }
  796. });
  797. });
  798. /**
  799. * Publicly exposes a reference to the otherwise private setUrlHash function
  800. */
  801. return setUrlHash;
  802. })(jQuery, window);
  803. /**
  804. * Page load event handler
  805. */
  806. $(function () {
  807. // Add the menu from the initial page into the cache
  808. // The cache primer is set by the footer class
  809. if (AJAX.cache.primer.url) {
  810. AJAX.cache.menus.add(
  811. AJAX.cache.primer.menuHash,
  812. $('<div></div>')
  813. .append('<div></div>')
  814. .append($('#serverinfo').clone())
  815. .append($('#topmenucontainer').clone())
  816. .html()
  817. );
  818. }
  819. $(function () {
  820. // Queue up this event twice to make sure that we get a copy
  821. // of the page after all other onload events have been fired
  822. if (AJAX.cache.primer.url) {
  823. AJAX.cache.add(
  824. AJAX.cache.primer.url,
  825. AJAX.cache.primer.scripts,
  826. AJAX.cache.primer.menuHash
  827. );
  828. }
  829. });
  830. });
  831. /**
  832. * Attach a generic event handler to clicks
  833. * on pages and submissions of forms
  834. */
  835. $('a').live('click', AJAX.requestHandler);
  836. $('form').live('submit', AJAX.requestHandler);
  837. /**
  838. * Gracefully handle fatal server errors
  839. * (e.g: 500 - Internal server error)
  840. */
  841. $(document).ajaxError(function (event, request, settings) {
  842. if (request.status !== 0) { // Don't handle aborted requests
  843. var errorCode = $.sprintf(PMA_messages.strErrorCode, request.status);
  844. var errorText = $.sprintf(PMA_messages.strErrorText, request.statusText);
  845. PMA_ajaxShowMessage(
  846. '<div class="error">' +
  847. PMA_messages.strErrorProcessingRequest +
  848. '<div>' + errorCode + '</div>' +
  849. '<div>' + errorText + '</div>' +
  850. '</div>',
  851. false
  852. );
  853. AJAX.active = false;
  854. }
  855. });