Implemented page, menu and scripts loading via ajax

This commit is contained in:
Rouslan Placella 2012-06-18 23:40:16 +01:00
parent 100ca317f8
commit 5486bdb312
5 changed files with 308 additions and 6 deletions

210
js/ajax.js Normal file
View File

@ -0,0 +1,210 @@
/**
* This object handles ajax requests for pages. It also
* handles the reloading of the main menu and scripts (TODO: navigation).
*/
var AJAX = {
/**
* @var bool active Whether we are busy
*/
active: false,
/**
* @var bool _debug Makes noise in your Firebug console
*/
_debug: false,
/**
* Given the filename of a script, returns a string to be
* used to refer to all the events registered for the file
*
* @param string key The filename for which to get the event name
*
* @return string
*/
getEventName: function (key){
/* http://burtleburtle.net/bob/hash/doobs.html#one */
key += "";
var len = key.length, hash=0, i=0;
for (; i<len; ++i) {
hash += key.charCodeAt(i);
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return "onload_" + Math.abs(hash);
},
/**
* Registers an onload event for a file
*
* @param string file The filename for which to register the event
* @param function func The function to execute when the page is ready
*
* @return void
*/
registerOnload: function (file, func) {
eventName = AJAX.getEventName(file);
$(document).bind(eventName, func);
this._debug && console.log("Registered event " + eventName + " for file " + file); // no need to translate
},
/**
* Called when a page has finished loading, once for every
* file that registered to the onload event of that file.
*
* @param string file The filename for which to fire the event
*
* @return void
*/
fireOnload: function (file) {
eventName = AJAX.getEventName(file);
$(document).trigger(eventName);
this._debug && console.log("Fired event " + eventName + " for file " + file); // no need to translate
},
/**
* Event handler for clicks on links and form submissions
*
* @param eventData e
*
* @return void
*/
requestHandler: function (e) {
if ($(this).attr('target')) {
return true;
} else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) {
return true;
} else if ($(this).attr('href') && $(this).attr('href').match(/^#/)) {
return true;
} else if ($(this).attr('href') && $(this).attr('href').match(/^mailto/)) {
return true;
} else {
e.preventDefault();
e.stopImmediatePropagation();
if (AJAX.active == true) {
return false;
} else {
AJAX.active = true;
}
}
var url = $(this).attr('href') || $(this).attr('action') + '?' + $(this).serialize();
this._debug && console.log("Loading: " + url); // no need to translate
var $msgbox = PMA_ajaxShowMessage();
$('html, body').animate({scrollTop: 0}, 'fast');
$.get(url, {'ajax_request': true, 'ajax_page_request': true}, function (data) {
if (data.success) {
PMA_ajaxRemoveMessage($msgbox);
if (data._menu) {
$('#floating_menubar').html(data._menu)
.children().first().remove(); // Remove duplicate wrapper (TODO: don't send it in the response)
menuPrepare();
menuResize();
}
$('*').unbind().die();
$('body').children().not('#floating_menubar').not('#page_content').not('#selflink').remove();
$('#page_content').replaceWith("<div id='page_content'>" + data.message + "</div>");
if (data._scripts) {
AJAX.scriptHandler.load(data._scripts);
}
} else {
PMA_ajaxShowMessage(data.error, false);
AJAX.active = false;
}
});
},
/**
* This object is in charge of downloading scripts,
* keeping track of what's downloaded and firing
* the onload event for them when the page is ready.
*/
scriptHandler: {
/**
* @var array _scripts The list of files already downloaded
*/
_scripts: [],
/**
* @var array _scriptsToBeLoaded The list of files that
* need to be downloaded
*/
_scriptsToBeLoaded: [],
/**
* @var array _scriptsToBeFired The list of files for which
* to fire the onload event
*/
_scriptsToBeFired: [],
/**
* Records that a file has been downloaded
*
* @param string file The filename
*
* @return self For chaining
*/
add: function (file) {
this._scripts.push(file);
return this;
},
/**
* Queues up an array of files to be downloaded
*
* @param array files An array of filenames and flags
*
* @return void
*/
load: function (files) {
this._scriptsToBeLoaded = [];
this._scriptsToBeFired = [];
for (var i in files) {
this._scriptsToBeLoaded.push(files[i].name);
if (files[i].fire) {
this._scriptsToBeFired.push(files[i].name);
}
}
this.callback();
},
/**
* Called whenever a file is loaded.
* Will queue up another file, or call done();
*
* @return void
*/
callback: function () {
var scripts = this._scriptsToBeLoaded;
if (scripts.length > 0) {
var script = scripts.shift();
if (this._scripts.indexOf(script) === -1) {
this.add(script);
var self = this;
$.getScript('js/' + script, function () {
self.callback();
});
} else {
this.callback();
}
} else {
this.done();
}
},
/**
* Called whenever all files are loaded
*
* @return void
*/
done: function () {
for (var i in this._scriptsToBeFired) {
AJAX.fireOnload(this._scriptsToBeFired[i]);
}
AJAX.active = false;
}
}
};
/**
* Attach a generic event handler to clicks
* on pages and submissions of forms
*/
$('a').live('click', AJAX.requestHandler);
$('form').live('submit', AJAX.requestHandler);

View File

@ -2824,7 +2824,8 @@ function menuResize()
}
}
$(function() {
function menuPrepare()
{
var topmenu = $('#topmenu');
if (topmenu.length == 0) {
return;
@ -2853,7 +2854,10 @@ $(function() {
}
});
topmenu.append(submenu);
}
$(function() {
menuPrepare();
// populate submenu and register resize event
menuResize();
$(window).resize(menuResize);

View File

@ -142,6 +142,7 @@ class PMA_Header
private function _addDefaultScripts()
{
$this->_scripts->addFile('jquery/jquery-1.6.2.js');
$this->_scripts->addFile('ajax.js');
$this->_scripts->addFile('jquery/jquery-ui-1.8.16.custom.js');
$this->_scripts->addFile('jquery/jquery.sprintf.js');
$this->_scripts->addFile('update-location.js');
@ -206,6 +207,16 @@ class PMA_Header
return $this->_scripts;
}
/**
* Returns the PMA_Menu object
*
* @return PMA_Menu object
*/
public function getMenu()
{
return $this->_menu;
}
/**
* Setter for the ID attribute in the BODY tag
*

View File

@ -66,6 +66,14 @@ class PMA_Response
* @var bool
*/
private $_isAjax;
/**
* Whether we are servicing an ajax request for a page
* that was fired using the generic page handler in JS.
*
* @access private
* @var bool
*/
private $_isAjaxPage;
/**
* Whether there were any errors druing the processing of the request
* Only used for ajax responses
@ -96,11 +104,15 @@ class PMA_Response
$this->_JSON = array();
$this->_footer = new PMA_Footer();
$this->_isSuccess = true;
$this->_isAjax = false;
$this->_isSuccess = true;
$this->_isAjax = false;
$this->_isAjaxPage = false;
if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
$this->_isAjax = true;
}
if (isset($_REQUEST['ajax_page_request']) && $_REQUEST['ajax_page_request'] == true) {
$this->_isAjaxPage = true;
}
$this->_header->setAjax($this->_isAjax);
$this->_footer->setAjax($this->_isAjax);
$this->_CWD = getcwd();
@ -278,6 +290,11 @@ class PMA_Response
unset($this->_JSON['message']);
}
if ($this->_isAjaxPage) {
$this->addJSON('_menu', $this->getHeader()->getMenu()->getDisplay());
$this->addJSON('_scripts', $this->getHeader()->getScripts()->getFiles());
}
// Set the Content-Type header to JSON so that jQuery parses the
// response correctly.
if (! defined('TESTSUITE')) {

View File

@ -96,14 +96,15 @@ class PMA_Scripts
*/
public function addFile($filename, $conditional_ie = false)
{
$filename = 'js/' . $filename;
$hash = md5($filename);
if (empty($this->_files[$hash])) {
$has_onload = $this->eventBlacklist($filename);
$timestamp = null;
if (strpos($filename, '?') === false) {
$timestamp = filemtime($filename);
$timestamp = filemtime('js/' . $filename);
}
$this->_files[$hash] = array(
'has_onload' => $has_onload,
'filename' => $filename,
'timestamp' => $timestamp,
'conditional_ie' => $conditional_ie
@ -111,6 +112,29 @@ class PMA_Scripts
}
}
/**
* Determines whether to fire up an onload event for a file
*
* @param string $filename
*
* @return int 1 to fire up the event, 0 not to
*/
private function eventBlacklist($filename)
{
if ( strpos($filename, 'jquery') !== false
|| strpos($filename, 'codemirror') !== false
|| strpos($filename, 'messages.php') !== false
|| strpos($filename, 'ajax.js') !== false
|| strpos($filename, 'update-location.js') !== false
|| strpos($filename, 'get_image.js.php') !== false
|| strpos($filename, 'cross_framing_protection.js') !== false
) {
return 0;
} else {
return 1;
}
}
/**
* Adds a new code snippet to the code to be executed
*
@ -141,6 +165,26 @@ class PMA_Scripts
);
}
/**
* Returns a list with filenames and a flag to indicate
* whether to register onload events for this file
*
* @return array
*/
public function getFiles()
{
$retval = array();
foreach ($this->_files as $file) {
if (! $file['conditional_ie'] || PMA_USR_BROWSER_AGENT == 'IE') {
$retval[] = array(
'name' => $file['filename'],
'fire' => $file['has_onload']
);
}
}
return $retval;
}
/**
* Renders all the JavaScript file inclusions, code and events
*
@ -152,11 +196,27 @@ class PMA_Scripts
foreach ($this->_files as $file) {
$retval .= $this->_includeFile(
$file['filename'],
'js/' . $file['filename'],
$file['timestamp'],
$file['conditional_ie']
);
}
$code = 'AJAX.scriptHandler';
foreach ($this->_files as $file) {
$code .= '.add("' . PMA_escapeJsString($file['filename']) . '")';
}
$code .= ';';
$this->addCode($code);
$code = '$(function() {';
foreach ($this->_files as $file) {
if ($file['has_onload']) {
$code .= 'AJAX.fireOnload("' . PMA_escapeJsString($file['filename']) . '");';
}
}
$code .= '});';
$this->addCode($code);
$retval .= '<script type="text/javascript">';
$retval .= "// <![CDATA[\n";
$retval .= $this->_code;