From 8c986d8308ad964cea6ce31bce25837a959e84c7 Mon Sep 17 00:00:00 2001 From: Piyush Vijay Date: Mon, 14 May 2018 23:48:40 +0530 Subject: [PATCH 01/16] Weekly progress. 1). Microhistory added in classes for modular code 2). Code is structured and camel casing of varables is done for removing linter warnings. 3). JQuery plugins are added in some files. 4). jQplot is working for server_status_queries and server_status_monitor. 5). Work started for Dabatase, SQl and Table files. Some changes are stll left for server_status_monitor and sql.js Signed-Off-By: Piyush Vijay --- db_search.php | 4 +- js/src/ajax.js | 4 +- js/src/classes/Chart.js | 13 +- js/src/classes/CommonActions.js | 59 + js/src/classes/Console/PMA_ConsoleResizer.js | 5 +- .../classes/Console/PMA_consoleBookmarks.js | 14 +- js/src/classes/Console/PMA_consoleDebug.js | 31 +- js/src/classes/Console/PMA_consoleInput.js | 26 +- js/src/classes/Console/PMA_consoleMessages.js | 13 +- js/src/classes/ErrorReport.js | 3 +- js/src/classes/MicroHistory.js | 228 ++ js/src/classes/Server/ProcessList.js | 8 +- js/src/classes/SetUrlHash.js | 117 + js/src/config.js | 41 +- js/src/console.js | 195 +- js/src/consts/files.js | 6 +- js/src/db_search.js | 253 ++ js/src/functions.js | 111 + js/src/functions/Common.js | 46 + js/src/functions/Grid/Cell.js | 16 + js/src/functions/Grid/GetFieldName.js | 41 + js/src/functions/Grid/StickyColumns.js | 77 + .../functions/Server/ServerStatusMonitor.js | 133 + js/src/functions/Server/SeverStatusSorter.js | 42 + js/src/functions/Sql.js | 82 + js/src/functions/UpdateCode.js | 50 + js/src/functions/chart.js | 6 +- js/src/functions/config.js | 7 +- js/src/index.js | 10 +- js/src/server_databases.js | 8 +- js/src/server_privileges.js | 4 + js/src/server_status_monitor.js | 382 +-- js/src/server_status_queries.js | 4 +- js/src/server_status_sorter.js | 34 +- js/src/server_user_groups.js | 11 +- js/src/sql.js | 893 +++++++ js/src/utils/DateTime.js | 147 ++ js/src/utils/Sanitise.js | 27 +- js/src/utils/makegrid.js | 2255 +++++++++++++++++ js/src/utils/menu_resizer.js | 62 +- js/src/utils/show_ajax_messages.js | 10 +- js/src/variables/import_variables.js | 4 +- libraries/classes/Header.php | 12 +- libraries/classes/Scripts.php | 27 + server_sql.php | 6 +- server_status_monitor.php | 26 +- server_status_queries.php | 12 +- test/classes/ScriptsTest.php | 26 +- webpack.config.babel.js | 5 +- 49 files changed, 5100 insertions(+), 496 deletions(-) create mode 100644 js/src/classes/CommonActions.js create mode 100644 js/src/classes/MicroHistory.js create mode 100644 js/src/classes/SetUrlHash.js create mode 100644 js/src/db_search.js create mode 100644 js/src/functions.js create mode 100644 js/src/functions/Common.js create mode 100644 js/src/functions/Grid/Cell.js create mode 100644 js/src/functions/Grid/GetFieldName.js create mode 100644 js/src/functions/Grid/StickyColumns.js create mode 100644 js/src/functions/Server/ServerStatusMonitor.js create mode 100644 js/src/functions/Server/SeverStatusSorter.js create mode 100644 js/src/functions/Sql.js create mode 100644 js/src/functions/UpdateCode.js create mode 100644 js/src/sql.js create mode 100644 js/src/utils/DateTime.js create mode 100644 js/src/utils/makegrid.js diff --git a/db_search.php b/db_search.php index 56777e7a6b..abc293a061 100644 --- a/db_search.php +++ b/db_search.php @@ -21,9 +21,9 @@ require_once 'libraries/common.inc.php'; $response = Response::getInstance(); $header = $response->getHeader(); $scripts = $header->getScripts(); -$scripts->addFile('db_search.js'); +$scripts->addFile('db_search'); $scripts->addFile('sql.js'); -$scripts->addFile('makegrid.js'); +// $scripts->addFile('makegrid.js'); require 'libraries/db_common.inc.php'; diff --git a/js/src/ajax.js b/js/src/ajax.js index a07932eebf..d733b04587 100644 --- a/js/src/ajax.js +++ b/js/src/ajax.js @@ -15,6 +15,7 @@ import { PMA_ensureNaviSettings, PMA_disableNaviSettings } from './functions/navigation'; import { isStorageSupported } from './functions/config'; +import PMA_MicroHistory from './classes/MicroHistory'; /** * This object handles ajax requests for pages. It also * handles the reloading of the main menu and scripts. @@ -367,7 +368,6 @@ export let AJAX = { if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) { AJAX.active = true; AJAX.$msgbox = PMA_ajaxShowMessage(); - $.post(url, params, AJAX.responseHandler); } } @@ -599,7 +599,7 @@ export let AJAX = { var fileImports = ['server_privileges', 'server_databases', 'error_report', 'navigation', 'server_status_advisor', 'server_status_processes', 'server_status_variables', 'server_plugins', 'server_status_sorter', 'server_status_queries', 'server_status_monitor', 'server_variables', 'server_user_groups', 'replication', 'export', 'import', 'config', - 'page_settings', 'shortcuts_handler' + 'page_settings', 'shortcuts_handler', 'db_search', 'sql', 'functions' ]; if ($.inArray(file, fileImports) !== -1) { // Dynamic import to load the files dynamically diff --git a/js/src/classes/Chart.js b/js/src/classes/Chart.js index ddbeb75842..725068a0c4 100644 --- a/js/src/classes/Chart.js +++ b/js/src/classes/Chart.js @@ -1,7 +1,14 @@ +import { $ } from '../utils/extend_jquery'; +import 'updated-jqplot'; +import '../../../node_modules/updated-jqplot/dist/plugins/jqplot.pieRenderer'; +import '../../../node_modules/updated-jqplot/dist/plugins/jqplot.highlighter'; +import '../../../node_modules/updated-jqplot/dist/plugins/jqplot.enhancedPieLegendRenderer'; +window.test2 = $; + /** * Chart type enumerations */ -var ChartType = { +export var ChartType = { LINE : 'line', SPLINE : 'spline', AREA : 'area', @@ -15,7 +22,7 @@ var ChartType = { /** * Column type enumeration */ -var ColumnType = { +export var ColumnType = { STRING : 'string', NUMBER : 'number', BOOLEAN : 'boolean', @@ -153,7 +160,7 @@ ScatterChart.prototype.validateColumns = function (dataTable) { /** * The data table contains column information and data for the chart. */ -var DataTable = function () { +export var DataTable = function () { var columns = []; var data = null; diff --git a/js/src/classes/CommonActions.js b/js/src/classes/CommonActions.js new file mode 100644 index 0000000000..35963b424d --- /dev/null +++ b/js/src/classes/CommonActions.js @@ -0,0 +1,59 @@ +import CommonParams from '../variables/common_params'; +import { AJAX } from '../ajax'; + +/** + * Holds common parameters such as server, db, table, etc + * + * The content for this is normally loaded from Header.php or + * Response.php and executed by ajax.js + */ +export var PMA_commonActions = { + /** + * Saves the database name when it's changed + * and reloads the query window, if necessary + * + * @param {string} newDb newDb The name of the new database + * + * @return {void} + */ + setDb: function (newDb) { + if (newDb !== CommonParams.get('db')) { + CommonParams.setAll({ 'db': newDb, 'table': '' }); + } + }, + /** + * Opens a database in the main part of the page + * + * @param {string} newDb The name of the new database + * + * @return void + */ + openDb: function (newDb) { + CommonParams + .set('db', newDb) + .set('table', ''); + this.refreshMain( + CommonParams.get('opendb_url') + ); + }, + /** + * Refreshes the main frame + * + * @param mixed url Undefined to refresh to the same page + * String to go to a different page, e.g: 'index.php' + * + * @return void + */ + refreshMain: function (url, callback) { + if (! url) { + url = $('#selflink').find('a').attr('href'); + url = url.substring(0, url.indexOf('?')); + } + url += CommonParams.getUrlQuery(); + $('', { href: url }) + .appendTo('body') + .trigger('click') + .remove(); + AJAX._callback = callback; + } +}; diff --git a/js/src/classes/Console/PMA_ConsoleResizer.js b/js/src/classes/Console/PMA_ConsoleResizer.js index 215ae31b94..0f2c1193a2 100644 --- a/js/src/classes/Console/PMA_ConsoleResizer.js +++ b/js/src/classes/Console/PMA_ConsoleResizer.js @@ -1,9 +1,12 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + /** * Resizer object * Careful: this object UI logics highly related with functions under PMA_console * Resizing min-height is 32, if small than it, console will collapse + * @namespace ConsoleResizer */ -export default class PMA_consoleResizer { +export default class ConsoleResizer { constructor (instance) { this._posY = 0; this._height = 0; diff --git a/js/src/classes/Console/PMA_consoleBookmarks.js b/js/src/classes/Console/PMA_consoleBookmarks.js index 72f41dc41b..8cbeb08a83 100644 --- a/js/src/classes/Console/PMA_consoleBookmarks.js +++ b/js/src/classes/Console/PMA_consoleBookmarks.js @@ -1,7 +1,11 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +import CommonParams from '../../variables/common_params'; +import { PMA_Messages as messages } from '../../variables/export_variables'; /** + * @namespace ConsoleBookmarks * Console bookmarks card, and bookmarks items management object */ -export default class PMA_consoleBookmarks { +export default class ConsoleBookmarks { constructor (instance) { this._bookmarks = []; this.pmaConsole = null; @@ -15,7 +19,7 @@ export default class PMA_consoleBookmarks { this.pmaConsole = instance; this.initialize(); } - addBookmark (queryString, targetDb, label, isShared, id) { + addBookmark (queryString, targetDb, label, isShared) { $('#pma_bookmarks').find('.add [name=shared]').prop('checked', false); $('#pma_bookmarks').find('.add [name=label]').val(''); $('#pma_bookmarks').find('.add [name=targetdb]').val(''); @@ -42,7 +46,7 @@ export default class PMA_consoleBookmarks { refresh () { $.get('import.php', { ajax_request: true, - server: PMA_commonParams.get('server'), + server: CommonParams.get('server'), console_bookmark_refresh: 'refresh' }, function (data) { if (data.console_message_bookmark) { @@ -71,7 +75,7 @@ export default class PMA_consoleBookmarks { $('#pma_bookmarks').find('.card.add [name=submit]').click(function () { if ($('#pma_bookmarks').find('.card.add [name=label]').val().length === 0 || self.pmaConsole.pmaConsoleInput.getText('bookmark').length === 0) { - alert(PMA_messages.strFormEmpty); + alert(messages.strFormEmpty); return; } $(this).prop('disabled', true); @@ -80,7 +84,7 @@ export default class PMA_consoleBookmarks { ajax_request: true, console_bookmark_add: 'true', label: $('#pma_bookmarks').find('.card.add [name=label]').val(), - server: PMA_commonParams.get('server'), + server: CommonParams.get('server'), db: $('#pma_bookmarks').find('.card.add [name=targetdb]').val(), bookmark_query: self.pmaConsole.pmaConsoleInput.getText('bookmark'), shared: $('#pma_bookmarks').find('.card.add [name=shared]').prop('checked') }, diff --git a/js/src/classes/Console/PMA_consoleDebug.js b/js/src/classes/Console/PMA_consoleDebug.js index ad063172c4..29523db8dc 100644 --- a/js/src/classes/Console/PMA_consoleDebug.js +++ b/js/src/classes/Console/PMA_consoleDebug.js @@ -1,4 +1,17 @@ -export default class PMA_consoleDebug { +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Module import + */ +import { PMA_Messages as messages } from '../../variables/export_variables'; +import { PMA_sprintf } from '../../utils/sprintf'; +import { escapeHtml } from '../../utils/Sanitise'; + +/** + * Console debug object + * @namespace ConsoleDebug + */ +export default class ConsoleDebug { constructor (instance) { this.pmaConsole = null; this._config = { @@ -123,7 +136,7 @@ export default class PMA_consoleDebug { $('
') .text( PMA_sprintf( - PMA_messages.strConsoleDebugArgsSummary, + messages.strConsoleDebugArgsSummary, dbgStep.args.length ) ) @@ -186,12 +199,12 @@ export default class PMA_consoleDebug { $('
') .append( '' + - PMA_messages.strConsoleDebugShowArgs + + messages.strConsoleDebugShowArgs + ' ' ) .append( '' + - PMA_messages.strConsoleDebugHideArgs + + messages.strConsoleDebugHideArgs + ' ' ) ); @@ -259,7 +272,7 @@ export default class PMA_consoleDebug { .text((parseInt(i) + 1) + '.') .append( $('').text( - PMA_messages.strConsoleDebugTimeTaken + + messages.strConsoleDebugTimeTaken + ' ' + queryInfo[i].time + 's' + ' (' + ((queryInfo[i].time * 100) / totalTime).toFixed(3) + '%)' ) @@ -304,7 +317,7 @@ export default class PMA_consoleDebug { } if (debugJson === false) { $('#debug_console').find('.debug>.welcome').text( - PMA_messages.strConsoleDebugError + messages.strConsoleDebugError ); return; } @@ -336,7 +349,7 @@ export default class PMA_consoleDebug { $('#debug_console').find('.debug>.welcome').append( $('').text( PMA_sprintf( - PMA_messages.strConsoleDebugSummary, + messages.strConsoleDebugSummary, totalUnique, totalExec, totalTime @@ -351,7 +364,7 @@ export default class PMA_consoleDebug { // For sorting queries function sortByTime (a, b) { - var order = ((PMA_console.config.Order === 'asc') ? 1 : -1); + var order = ((this.pmaConsole.config.Order === 'asc') ? 1 : -1); if (Array.isArray(a) && Array.isArray(b)) { // It is grouped var timeA = 0; @@ -370,7 +383,7 @@ export default class PMA_consoleDebug { } function sortByCount (a, b) { - var order = ((PMA_console.config.Oorder === 'asc') ? 1 : -1); + var order = ((this.pmaConsole.config.Order === 'asc') ? 1 : -1); return (a.length - b.length) * order; } diff --git a/js/src/classes/Console/PMA_consoleInput.js b/js/src/classes/Console/PMA_consoleInput.js index 5e64be11fd..b7102dc970 100644 --- a/js/src/classes/Console/PMA_consoleInput.js +++ b/js/src/classes/Console/PMA_consoleInput.js @@ -1,7 +1,14 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ /** * Console input object + * @namespace ConsoleInput */ -export default class PMA_consoleInput { +export default class ConsoleInput { + /** + * @constructor + * + * @param {object} pmaConsoleInstance Instance of pma console + */ constructor (pmaConsoleInstance) { /** * @var array, contains Codemirror objects or input jQuery objects @@ -23,9 +30,16 @@ export default class PMA_consoleInput { * @access private */ this._historyPreserveCurrent = null; - + /** + * @var object + * @access private + */ this.pmaConsole = null; + /** + * Bindings for accessing the instance of the class using this + * insde the methods. + */ this.setPmaConsole = this.setPmaConsole.bind(this); this.initialize = this.initialize.bind(this); this._historyNavigate = this._historyNavigate.bind(this); @@ -214,6 +228,7 @@ export default class PMA_consoleInput { * * @param string text * @param string target + * * @return void */ setText (text, target) { @@ -237,6 +252,13 @@ export default class PMA_consoleInput { } } } + /** + * Used for getting the text of input + * + * @param {string} target + * + * @return {string} + */ getText (target) { if (this._codemirror) { switch (target) { diff --git a/js/src/classes/Console/PMA_consoleMessages.js b/js/src/classes/Console/PMA_consoleMessages.js index 2398a0d12b..8c19c35d9b 100644 --- a/js/src/classes/Console/PMA_consoleMessages.js +++ b/js/src/classes/Console/PMA_consoleMessages.js @@ -1,7 +1,14 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Module import + */ +import { PMA_Messages as messages } from '../../variables/export_variables'; /** * Console messages, and message items management object + * @namespace ConsoleMessages */ -export default class PMA_consoleMessages { +export default class ConsoleMessages { constructor (instance) { this.pmaConsole = null; this.clear = this.clear.bind(this); @@ -167,7 +174,7 @@ export default class PMA_consoleMessages { $targetMessage.find('.action.requery').click(function () { var query = $(this).parent().siblings('.query').text(); var $message = $(this).closest('.message'); - if (confirm(PMA_messages.strConsoleRequeryConfirm + '\n' + + if (confirm(messages.strConsoleRequeryConfirm + '\n' + (query.length < 100 ? query : query.slice(0, 100) + '...')) ) { self.pmaConsole.execute(query, { db: $message.attr('targetdb'), table: $message.attr('targettable') }); @@ -189,7 +196,7 @@ export default class PMA_consoleMessages { }); $targetMessage.find('.action.delete_bookmark').click(function () { var $message = $(this).closest('.message'); - if (confirm(PMA_messages.strConsoleDeleteBookmarkConfirm + '\n' + $message.find('.bookmark_label').text())) { + if (confirm(messages.strConsoleDeleteBookmarkConfirm + '\n' + $message.find('.bookmark_label').text())) { $.post('import.php', { server: PMA_commonParams.get('server'), diff --git a/js/src/classes/ErrorReport.js b/js/src/classes/ErrorReport.js index b490544ceb..fc480bc08e 100644 --- a/js/src/classes/ErrorReport.js +++ b/js/src/classes/ErrorReport.js @@ -8,7 +8,7 @@ import CommonParams from '../variables/common_params'; import { PMA_ajaxShowMessage } from '../utils/show_ajax_messages'; import { PMA_getImage } from '../functions/get_image'; import TraceKit from 'tracekit'; -import { jQuery as $ } from '../utils/JqueryExtended'; +import { $ } from '../utils/JqueryExtended'; /** * This Object uses the library TraceKit to generate the backtrace of the @@ -39,6 +39,7 @@ var ErrorReport = { * @return void */ error_handler: function (exception) { + console.error(exception); if (exception.name === null || typeof(exception.name) === 'undefined') { exception.name = ErrorReport._extractExceptionName(exception); } diff --git a/js/src/classes/MicroHistory.js b/js/src/classes/MicroHistory.js new file mode 100644 index 0000000000..d7b2a49845 --- /dev/null +++ b/js/src/classes/MicroHistory.js @@ -0,0 +1,228 @@ +import { PMA_ajaxShowMessage } from '../utils/show_ajax_messages'; +import { AJAX } from '../ajax'; +import { PMA_Messages as messages } from '../variables/export_variables'; +import CommonParams from '../variables/common_params'; +import SetUrlHash from './SetUrlHash'; +/** + * An implementation of a client-side page cache. + * This object also uses the cache to provide a simple microhistory, + * that is the ability to use the back and forward buttons in the browser + */ +var MicroHistory = { + /** + * @var int The maximum number of pages to keep in the cache + */ + MAX: 6, + /** + * @var object A hash used to prime the cache with data about the initially + * loaded page. This is set in the footer, and then loaded + * by a double-queued event further down this file. + */ + primer: {}, + /** + * @var array Stores the content of the cached pages + */ + pages: [], + /** + * @var int The index of the currently loaded page + * This is used to know at which point in the history we are + */ + current: 0, + /** + * Saves a new page in the cache + * + * @param string hash The hash part of the url that is being loaded + * @param array scripts A list of scripts that is required for the page + * @param string menu A hash that links to a menu stored + * in a dedicated menu cache + * @param array params A list of parameters used by CommonParams() + * @param string rel A relationship to the current page: + * 'samepage': Forces the response to be treated as + * the same page as the current one + * 'newpage': Forces the response to be treated as + * a new page + * undefined: Default behaviour, 'samepage' if the + * selflinks of the two pages are the same. + * 'newpage' otherwise + * + * @return void + */ + add: function (hash, scripts, menu, params, rel) { + if (this.pages.length > MicroHistory.MAX) { + // Trim the cache, to the maximum number of allowed entries + // This way we will have a cached menu for every page + for (var i = 0; i < this.pages.length - this.MAX; i++) { + delete this.pages[i]; + } + } + while (this.current < this.pages.length) { + // trim the cache if we went back in the history + // and are now going forward again + this.pages.pop(); + } + if (rel === 'newpage' || + ( + typeof rel === 'undefined' && ( + typeof this.pages[this.current - 1] === 'undefined' || + this.pages[this.current - 1].hash !== hash + ) + ) + ) { + this.pages.push({ + hash: hash, + content: $('#page_content').html(), + scripts: scripts, + selflink: $('#selflink').html(), + menu: menu, + params: params + }); + SetUrlHash(this.current, hash); + this.current++; + } + }, + /** + * Restores a page from the cache. This is called when the hash + * part of the url changes and it's structure appears to be valid + * + * @param string index Which page from the history to load + * + * @return void + */ + navigate: function (index) { + if (typeof this.pages[index] === 'undefined' || + typeof this.pages[index].content === 'undefined' || + typeof this.pages[index].menu === 'undefined' || + ! MicroHistory.menus.get(this.pages[index].menu) + ) { + PMA_ajaxShowMessage( + '
' + messages.strInvalidPage + '
', + false + ); + } else { + AJAX.active = true; + var record = this.pages[index]; + AJAX.scriptHandler.reset(function () { + $('#page_content').html(record.content); + $('#selflink').html(record.selflink); + MicroHistory.menus.replace(MicroHistory.menus.get(record.menu)); + CommonParams.setAll(record.params); + AJAX.scriptHandler.load(record.scripts); + MicroHistory.current = ++index; + }); + } + }, + /** + * Resaves the content of the current page in the cache. + * Necessary in order not to show the user some outdated version of the page + * + * @return void + */ + update: function () { + var page = this.pages[this.current - 1]; + if (page) { + page.content = $('#page_content').html(); + } + }, + /** + * @var object Dedicated menu cache + */ + menus: { + /** + * Returns the number of items in an associative array + * + * @return int + */ + size: function (obj) { + var size = 0; + var key; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + size++; + } + } + return size; + }, + /** + * @var hash Stores the content of the cached menus + */ + data: {}, + /** + * Saves a new menu in the cache + * + * @param string hash The hash (trimmed md5) of the menu to be saved + * @param string content The HTML code of the menu to be saved + * + * @return void + */ + add: function (hash, content) { + if (this.size(this.data) > MicroHistory.MAX) { + // when the cache grows, we remove the oldest entry + var oldest; + var key; + var init = 0; + for (var i in this.data) { + if (this.data[i]) { + if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) { + oldest = this.data[i].timestamp; + key = i; + init = 1; + } + } + } + delete this.data[key]; + } + this.data[hash] = { + content: content, + timestamp: new Date() + }; + }, + /** + * Retrieves a menu given its hash + * + * @param string hash The hash of the menu to be retrieved + * + * @return string + */ + get: function (hash) { + if (this.data[hash]) { + return this.data[hash].content; + } else { + // This should never happen as long as the number of stored menus + // is larger or equal to the number of pages in the page cache + return ''; + } + }, + /** + * Prepares part of the parameter string used during page requests, + * this is necessary to tell the server which menus we have in the cache + * + * @return string + */ + getRequestParam: function () { + var param = ''; + var menuHashes = []; + for (var i in this.data) { + menuHashes.push(i); + } + var menuHashesParam = menuHashes.join('-'); + if (menuHashesParam) { + param = CommonParams.get('arg_separator') + 'menuHashes=' + menuHashesParam; + } + return param; + }, + /** + * Replaces the menu with new content + * + * @return void + */ + replace: function (content) { + $('#floating_menubar').html(content) + // Remove duplicate wrapper + // TODO: don't send it in the response + .children().first().remove(); + $('#topmenu').menuResizer(PMA_mainMenuResizerCallback); + } + } +}; + +export default MicroHistory; diff --git a/js/src/classes/Server/ProcessList.js b/js/src/classes/Server/ProcessList.js index 1a730f9423..23b7ec031d 100644 --- a/js/src/classes/Server/ProcessList.js +++ b/js/src/classes/Server/ProcessList.js @@ -1,10 +1,10 @@ import { PMA_ajaxShowMessage } from '../../utils/show_ajax_messages'; import { PMA_highlightSQL } from '../../utils/sql'; -import { PMA_Messages as PMA_messages } from '../../variables/export_variables'; +import { PMA_Messages as messages } from '../../variables/export_variables'; import CommonParams from '../../variables/common_params'; import { PMA_getImage } from '../../functions/get_image'; import { escapeHtml } from '../../utils/Sanitise'; -import { jQuery as $ } from '../../utils/JqueryExtended'; +import { $ } from '../../utils/JqueryExtended'; // object to store process list state information class ProcessList { constructor () { @@ -129,10 +129,10 @@ class ProcessList { */ setRefreshLabel () { var img = 'play'; - var label = PMA_messages.strStartRefresh; + var label = messages.strStartRefresh; if (this.autoRefresh) { img = 'pause'; - label = PMA_messages.strStopRefresh; + label = messages.strStopRefresh; this.refresh(); } $('a#toggleRefresh').html(PMA_getImage(img) + escapeHtml(label)); diff --git a/js/src/classes/SetUrlHash.js b/js/src/classes/SetUrlHash.js new file mode 100644 index 0000000000..e0d5ffdaf9 --- /dev/null +++ b/js/src/classes/SetUrlHash.js @@ -0,0 +1,117 @@ +import MicroHistory from './MicroHistory'; +import '../plugins/jquery/jquery.ba-hashchange-1.3'; + +/** + * URL hash management module. + * Allows direct bookmarking and microhistory. + */ +var SetUrlHash = (function (jQuery, window) { + /** + * Indictaes whether we have already completed + * the initialisation of the hash + * + * @access private + */ + var ready = false; + /** + * Stores a hash that needed to be set when we were not ready + * + * @access private + */ + var savedHash = ''; + /** + * Flag to indicate if the change of hash was triggered + * by a user pressing the back/forward button or if + * the change was triggered internally + * + * @access private + */ + var userChange = true; + + // Fix favicon disappearing in Firefox when setting location.hash + function resetFavicon () { + if (navigator.userAgent.indexOf('Firefox') > -1) { + // Move the link tags for the favicon to the bottom + // of the head element to force a reload of the favicon + $('head > link[href="favicon\\.ico"]').appendTo('head'); + } + } + + /** + * Sets the hash part of the URL + * + * @access public + */ + function setUrlHash (index, hash) { + /* + * Known problem: + * Setting hash leads to reload in webkit: + * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html + * + * so we expect that users are not running an ancient Safari version + */ + + userChange = false; + if (ready) { + window.location.hash = 'PMAURL-' + index + ':' + hash; + resetFavicon(); + } else { + savedHash = 'PMAURL-' + index + ':' + hash; + } + } + /** + * Start initialisation + */ + var urlhash = window.location.hash; + if (urlhash.substring(0, 8) === '#PMAURL-') { + // We have a valid hash, let's redirect the user + // to the page that it's pointing to + var colon_position = urlhash.indexOf(':'); + var questionmark_position = urlhash.indexOf('?'); + if (colon_position !== -1 && questionmark_position !== -1 && colon_position < questionmark_position) { + var hash_url = urlhash.substring(colon_position + 1, questionmark_position); + if (window.PMA_gotoWhitelist.indexOf(hash_url) !== -1) { + window.location = urlhash.substring( + colon_position + 1 + ); + } + } + } else { + // We don't have a valid hash, so we'll set it up + // when the page finishes loading + jQuery(function () { + /* Check if we should set URL */ + if (savedHash !== '') { + window.location.hash = savedHash; + savedHash = ''; + resetFavicon(); + } + // Indicate that we're done initialising + ready = true; + }); + } + /** + * Register an event handler for when the url hash changes + */ + + jQuery(function () { + jQuery(window).hashchange(function () { + if (userChange === false) { + // Ignore internally triggered hash changes + userChange = true; + } else if (/^#PMAURL-\d+:/.test(window.location.hash)) { + // Change page if the hash changed was triggered by a user action + var index = window.location.hash.substring( + 8, window.location.hash.indexOf(':') + ); + MicroHistory.navigate(index); + } + }); + }); + /** + * Publicly exposes a reference to the otherwise private setUrlHash function + */ + return setUrlHash; +}(jQuery, window)); + +export default SetUrlHash; diff --git a/js/src/config.js b/js/src/config.js index 40a4a5e49e..08cf178969 100644 --- a/js/src/config.js +++ b/js/src/config.js @@ -3,13 +3,15 @@ import * as Config from './functions/config'; import { defaultValues } from './variables/get_config'; /** - * Functions used in configuration forms and on user preferences pages + * @package PhpMyAdmin + * + * Config */ /** * Unbind all event handlers before tearing down a page */ -export function teardown1 () { +function teardownConfig () { $('.optbox input[id], .optbox select[id], .optbox textarea[id]').off('change').off('keyup'); $('.optbox input[type=button][name=submit_reset]').off('click'); $('div.tabs_contents').off(); @@ -19,7 +21,7 @@ export function teardown1 () { $('#prefs_autoload').find('a').off('click'); } -export function onload1 () { +function onloadConfigPrefsTab () { var $topmenu_upt = $('#topmenu2.user_prefs_tabs'); $topmenu_upt.find('li.active a').attr('rel', 'samepage'); $topmenu_upt.find('li:not(.active) a').attr('rel', 'newpage'); @@ -72,7 +74,7 @@ export function onload3 () { // Form reset buttons // -export function onload4 () { +function onloadConfigResetDefault () { $('.optbox input[type=button][name=submit_reset]').on('click', function () { var fields = $(this).closest('fieldset').find('input, select, textarea'); for (var i = 0, imax = fields.length; i < imax; i++) { @@ -113,15 +115,15 @@ export function onload6 () { .prop('disabled', false) .add('#export_text_file, #import_text_file') .on('click', function () { - var enable_id = $(this).attr('id'); - var disable_id; - if (enable_id.match(/local_storage$/)) { - disable_id = enable_id.replace(/local_storage$/, 'text_file'); + var enableId = $(this).attr('id'); + var disableId; + if (enableId.match(/local_storage$/)) { + disableId = enableId.replace(/local_storage$/, 'text_file'); } else { - disable_id = enable_id.replace(/text_file$/, 'local_storage'); + disableId = enableId.replace(/text_file$/, 'local_storage'); } - $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true); - $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false); + $('#opts_' + disableId).addClass('disabled').find('input').prop('disabled', true); + $('#opts_' + enableId).removeClass('disabled').find('input').prop('disabled', false); }); // detect localStorage state @@ -135,9 +137,9 @@ export function onload6 () { $('form.prefs-form').on('change', function () { var $form = $(this); var disabled = false; - if (!ls_supported) { + if (!lsSupported) { disabled = $form.find('input[type=radio][value$=local_storage]').prop('checked'); - } else if (!ls_exists && $form.attr('name') === 'prefs_import' && + } else if (!lsExists && $form.attr('name') === 'prefs_import' && $('#import_local_storage')[0].checked ) { disabled = true; @@ -168,3 +170,16 @@ export function onload6 () { // // END: User preferences import/export // ------------------------------------------------------------------ + +/** + * Module export + */ +export { + teardownConfig, + onloadConfigPrefsTab, + onloadConfigResetDefault, + onloadConfigRestore, + onloadConfigTabs, + onloadConfigValidations, + onloadPreferenceExport +}; diff --git a/js/src/console.js b/js/src/console.js index 527989cd29..b6a90fa379 100644 --- a/js/src/console.js +++ b/js/src/console.js @@ -4,15 +4,15 @@ * * @package phpMyAdmin-Console */ -import PMA_consoleBookmarks from './classes/Console/PMA_consoleBookmarks'; -import PMA_consoleDebug from './classes/Console/PMA_consoleDebug'; -import PMA_consoleInput from './classes/Console/PMA_consoleInput'; -import PMA_consoleMessages from './classes/Console/PMA_consoleMessages'; -import PMA_consoleResizer from './classes/Console/PMA_ConsoleResizer'; +import ConsoleBookmarks from './classes/Console/PMA_consoleBookmarks'; +import ConsoleDebug from './classes/Console/PMA_consoleDebug'; +import ConsoleInput from './classes/Console/PMA_consoleInput'; +import ConsoleMessages from './classes/Console/PMA_consoleMessages'; +import ConsoleResizer from './classes/Console/PMA_ConsoleResizer'; /** * Console object */ -var PMA_console = { +var Console = { /** * @var object, jQuery object, selector is '#pma_console>.content' * @access private @@ -69,18 +69,18 @@ var PMA_console = { return; } - PMA_console.config = configGet('Console', false); + Console.config = configGet('Console', false); - PMA_console.isEnabled = true; + Console.isEnabled = true; // Vars init - PMA_console.$consoleToolbar = $('#pma_console').find('>.toolbar'); - PMA_console.$consoleContent = $('#pma_console').find('>.content'); - PMA_console.$consoleAllContents = $('#pma_console').find('.content'); - PMA_console.$consoleTemplates = $('#pma_console').find('>.templates'); + Console.$consoleToolbar = $('#pma_console').find('>.toolbar'); + Console.$consoleContent = $('#pma_console').find('>.content'); + Console.$consoleAllContents = $('#pma_console').find('.content'); + Console.$consoleTemplates = $('#pma_console').find('>.templates'); // Generate a from for post - PMA_console.$requestForm = $('
' + + Console.$requestForm = $('' + '' + '' + '' + @@ -90,36 +90,37 @@ var PMA_console = { '' + '
' ); - PMA_console.$requestForm.children('[name=token]').val(PMA_commonParams.get('token')); - PMA_console.$requestForm.on('submit', AJAX.requestHandler); + Console.$requestForm.children('[name=token]').val(PMA_commonParams.get('token')); + Console.$requestForm.on('submit', AJAX.requestHandler); // Event binds shouldn't run again - if (PMA_console.isInitialized === false) { + if (Console.isInitialized === false) { // Load config first - if (PMA_console.config.AlwaysExpand === true) { + if (Console.config.AlwaysExpand === true) { $('#pma_console_options input[name=always_expand]').prop('checked', true); } - if (PMA_console.config.StartHistory === true) { + if (Console.config.StartHistory === true) { $('#pma_console_options').find('input[name=start_history]').prop('checked', true); } - if (PMA_console.config.CurrentQuery === true) { + if (Console.config.CurrentQuery === true) { $('#pma_console_options').find('input[name=current_query]').prop('checked', true); } - if (PMA_console.config.EnterExecutes === true) { + if (Console.config.EnterExecutes === true) { $('#pma_console_options').find('input[name=enter_executes]').prop('checked', true); } - if (PMA_console.config.DarkTheme === true) { + if (Console.config.DarkTheme === true) { $('#pma_console_options').find('input[name=dark_theme]').prop('checked', true); $('#pma_console').find('>.content').addClass('console_dark_theme'); } - PMA_console.pmaConsoleResizer = new PMA_consoleResizer(PMA_console); - PMA_console.pmaConsoleInput = new PMA_consoleInput(PMA_console); - PMA_console.pmaConsoleMessages = new PMA_consoleMessages(PMA_console); - PMA_console.pmaConsoleBookmarks = new PMA_consoleBookmarks(PMA_console); - PMA_console.pmaConsoleDebug = new PMA_consoleDebug(PMA_console); + // Instances of helper classes + Console.pmaConsoleResizer = new ConsoleResizer(Console); + Console.pmaConsoleInput = new ConsoleInput(Console); + Console.pmaConsoleMessages = new ConsoleMessages(Console); + Console.pmaConsoleBookmarks = new ConsoleBookmarks(Console); + Console.pmaConsoleDebug = new ConsoleDebug(Console); - PMA_console.$consoleToolbar.children('.console_switch').click(PMA_console.toggle); + Console.$consoleToolbar.children('.console_switch').click(Console.toggle); $('#pma_console').find('.toolbar').children().mousedown(function (event) { event.preventDefault(); @@ -127,42 +128,42 @@ var PMA_console = { }); $('#pma_console').find('.button.clear').click(function () { - PMA_console.pmaConsoleMessages.clear(); + Console.pmaConsoleMessages.clear(); }); $('#pma_console').find('.button.history').click(function () { - PMA_console.pmaConsoleMessages.showHistory(); + Console.pmaConsoleMessages.showHistory(); }); $('#pma_console').find('.button.options').click(function () { - PMA_console.showCard('#pma_console_options'); + Console.showCard('#pma_console_options'); }); $('#pma_console').find('.button.debug').click(function () { - PMA_console.showCard('#debug_console'); + Console.showCard('#debug_console'); }); - PMA_console.$consoleContent.click(function (event) { + Console.$consoleContent.click(function (event) { if (event.target === this) { - PMA_console.pmaConsoleInput.focus(); + Console.pmaConsoleInput.focus(); } }); $('#pma_console').find('.mid_layer').click(function () { - PMA_console.hideCard($(this).parent().children('.card')); + Console.hideCard($(this).parent().children('.card')); }); $('#debug_console').find('.switch_button').click(function () { - PMA_console.hideCard($(this).closest('.card')); + Console.hideCard($(this).closest('.card')); }); $('#pma_bookmarks').find('.switch_button').click(function () { - PMA_console.hideCard($(this).closest('.card')); + Console.hideCard($(this).closest('.card')); }); $('#pma_console_options').find('.switch_button').click(function () { - PMA_console.hideCard($(this).closest('.card')); + Console.hideCard($(this).closest('.card')); }); $('#pma_console_options').find('input[type=checkbox]').change(function () { - PMA_console.updateConfig(); + Console.updateConfig(); }); $('#pma_console_options').find('.button.default').click(function () { @@ -171,11 +172,11 @@ var PMA_console = { $('#pma_console_options').find('input[name=current_query]').prop('checked', true); $('#pma_console_options').find('input[name=enter_executes]').prop('checked', false); $('#pma_console_options').find('input[name=dark_theme]').prop('checked', false); - PMA_console.updateConfig(); + Console.updateConfig(); }); $('#pma_console_options').find('input[name=enter_executes]').change(function () { - PMA_console.pmaConsoleMessages.showInstructions(PMA_console.config.EnterExecutes); + Console.pmaConsoleMessages.showInstructions(Console.config.EnterExecutes); }); $(document).ajaxComplete(function (event, xhr, ajaxOptions) { @@ -187,32 +188,32 @@ var PMA_console = { } try { var data = JSON.parse(xhr.responseText); - PMA_console.ajaxCallback(data); + Console.ajaxCallback(data); } catch (e) { console.trace(); console.log('Failed to parse JSON: ' + e.message); } }); - PMA_console.isInitialized = true; + Console.isInitialized = true; } // Change console mode from cookie - switch (PMA_console.config.Mode) { + switch (Console.config.Mode) { case 'collapse': - PMA_console.collapse(); + Console.collapse(); break; /* jshint -W086 */// no break needed in default section case 'info': /* jshint +W086 */ - PMA_console.info(); + Console.info(); break; case 'show': - PMA_console.show(true); - PMA_console.scrollBottom(); + Console.show(true); + Console.scrollBottom(); break; default: - PMA_console.setConfig('Mode', 'info'); + Console.setConfig('Mode', 'info'); } }, /** @@ -224,40 +225,40 @@ var PMA_console = { if (typeof(queryString) !== 'string' || ! /[a-z]|[A-Z]/.test(queryString)) { return; } - PMA_console.$requestForm.children('textarea').val(queryString); - PMA_console.$requestForm.children('[name=server]').attr('value', PMA_commonParams.get('server')); + Console.$requestForm.children('textarea').val(queryString); + Console.$requestForm.children('[name=server]').attr('value', PMA_commonParams.get('server')); if (options && options.db) { - PMA_console.$requestForm.children('[name=db]').val(options.db); + Console.$requestForm.children('[name=db]').val(options.db); if (options.table) { - PMA_console.$requestForm.children('[name=table]').val(options.table); + Console.$requestForm.children('[name=table]').val(options.table); } else { - PMA_console.$requestForm.children('[name=table]').val(''); + Console.$requestForm.children('[name=table]').val(''); } } else { - PMA_console.$requestForm.children('[name=db]').val( + Console.$requestForm.children('[name=db]').val( (PMA_commonParams.get('db').length > 0 ? PMA_commonParams.get('db') : '')); } - PMA_console.$requestForm.find('[name=profiling]').remove(); + Console.$requestForm.find('[name=profiling]').remove(); if (options && options.profiling === true) { - PMA_console.$requestForm.append(''); + Console.$requestForm.append(''); } - if (! confirmQuery(PMA_console.$requestForm[0], PMA_console.$requestForm.children('textarea')[0].value)) { + if (! confirmQuery(Console.$requestForm[0], Console.$requestForm.children('textarea')[0].value)) { return; } - PMA_console.$requestForm.children('[name=console_message_id]') - .val(PMA_console.pmaConsoleMessages.appendQuery({ sql_query: queryString }).message_id); - PMA_console.$requestForm.trigger('submit'); - PMA_console.pmaConsoleInput.clear(); + Console.$requestForm.children('[name=console_message_id]') + .val(Console.pmaConsoleMessages.appendQuery({ sql_query: queryString }).message_id); + Console.$requestForm.trigger('submit'); + Console.pmaConsoleInput.clear(); PMA_reloadNavigation(); }, ajaxCallback: function (data) { if (data && data.console_message_id) { - PMA_console.pmaConsoleMessages.updateQuery(data.console_message_id, data.success, + Console.pmaConsoleMessages.updateQuery(data.console_message_id, data.success, (data._reloadQuerywindow ? data._reloadQuerywindow : false)); } else if (data && data._reloadQuerywindow) { if (data._reloadQuerywindow.sql_query.length > 0) { - PMA_console.pmaConsoleMessages.appendQuery(data._reloadQuerywindow, 'successed') - .$message.addClass(PMA_console.config.CurrentQuery ? '' : 'hide'); + Console.pmaConsoleMessages.appendQuery(data._reloadQuerywindow, 'successed') + .$message.addClass(Console.config.CurrentQuery ? '' : 'hide'); } } }, @@ -267,18 +268,18 @@ var PMA_console = { * @return void */ collapse: function () { - PMA_console.setConfig('Mode', 'collapse'); - var pmaConsoleHeight = Math.max(92, PMA_console.config.Height); + Console.setConfig('Mode', 'collapse'); + var pmaConsoleHeight = Math.max(92, Console.config.Height); - PMA_console.$consoleToolbar.addClass('collapsed'); - PMA_console.$consoleAllContents.height(pmaConsoleHeight); - PMA_console.$consoleContent.stop(); - PMA_console.$consoleContent.animate({ 'margin-bottom': -1 * PMA_console.$consoleContent.outerHeight() + 'px' }, + Console.$consoleToolbar.addClass('collapsed'); + Console.$consoleAllContents.height(pmaConsoleHeight); + Console.$consoleContent.stop(); + Console.$consoleContent.animate({ 'margin-bottom': -1 * Console.$consoleContent.outerHeight() + 'px' }, 'fast', 'easeOutQuart', function () { - PMA_console.$consoleContent.css({ display:'none' }); + Console.$consoleContent.css({ display:'none' }); $(window).trigger('resize'); }); - PMA_console.hideCard(); + Console.hideCard(); }, /** * Show console @@ -287,21 +288,21 @@ var PMA_console = { * @return void */ show: function (inputFocus) { - PMA_console.setConfig('Mode', 'show'); + Console.setConfig('Mode', 'show'); - var pmaConsoleHeight = Math.max(92, PMA_console.config.Height); - pmaConsoleHeight = Math.min(PMA_console.config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25); - PMA_console.$consoleContent.css({ display:'block' }); - if (PMA_console.$consoleToolbar.hasClass('collapsed')) { - PMA_console.$consoleToolbar.removeClass('collapsed'); + var pmaConsoleHeight = Math.max(92, Console.config.Height); + pmaConsoleHeight = Math.min(Console.config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25); + Console.$consoleContent.css({ display:'block' }); + if (Console.$consoleToolbar.hasClass('collapsed')) { + Console.$consoleToolbar.removeClass('collapsed'); } - PMA_console.$consoleAllContents.height(pmaConsoleHeight); - PMA_console.$consoleContent.stop(); - PMA_console.$consoleContent.animate({ 'margin-bottom': 0 }, + Console.$consoleAllContents.height(pmaConsoleHeight); + Console.$consoleContent.stop(); + Console.$consoleContent.animate({ 'margin-bottom': 0 }, 'fast', 'easeOutQuart', function () { $(window).trigger('resize'); if (inputFocus) { - PMA_console.pmaConsoleInput.focus(); + Console.pmaConsoleInput.focus(); } }); }, @@ -314,7 +315,7 @@ var PMA_console = { */ info: function () { // Under construction - PMA_console.collapse(); + Console.collapse(); }, /** * Toggle console mode between collapse/show @@ -323,13 +324,13 @@ var PMA_console = { * @return void */ toggle: function () { - switch (PMA_console.config.Mode) { + switch (Console.config.Mode) { case 'collapse': case 'info': - PMA_console.show(true); + Console.show(true); break; case 'show': - PMA_console.collapse(); + Console.collapse(); break; default: PMA_consoleInitialize(); @@ -341,7 +342,7 @@ var PMA_console = { * @return void */ scrollBottom: function () { - PMA_console.$consoleContent.scrollTop(PMA_console.$consoleContent.prop('scrollHeight')); + Console.$consoleContent.scrollTop(Console.$consoleContent.prop('scrollHeight')); }, /** * Show card @@ -367,9 +368,9 @@ var PMA_console = { } $card.parent().children('.mid_layer').show().fadeTo(0, 0.15); $card.addClass('show'); - PMA_console.pmaConsoleInput.blur(); + Console.pmaConsoleInput.blur(); if ($card.parents('.card').length > 0) { - PMA_console.showCard($card.parents('.card')); + Console.showCard($card.parents('.card')); } }, /** @@ -394,20 +395,20 @@ var PMA_console = { * @return void */ updateConfig: function () { - PMA_console.setConfig('AlwaysExpand', $('#pma_console_options input[name=always_expand]').prop('checked')); - PMA_console.setConfig('StartHistory', $('#pma_console_options').find('input[name=start_history]').prop('checked')); - PMA_console.setConfig('CurrentQuery', $('#pma_console_options').find('input[name=current_query]').prop('checked')); - PMA_console.setConfig('EnterExecutes', $('#pma_console_options').find('input[name=enter_executes]').prop('checked')); - PMA_console.setConfig('DarkTheme', $('#pma_console_options').find('input[name=dark_theme]').prop('checked')); + Console.setConfig('AlwaysExpand', $('#pma_console_options input[name=always_expand]').prop('checked')); + Console.setConfig('StartHistory', $('#pma_console_options').find('input[name=start_history]').prop('checked')); + Console.setConfig('CurrentQuery', $('#pma_console_options').find('input[name=current_query]').prop('checked')); + Console.setConfig('EnterExecutes', $('#pma_console_options').find('input[name=enter_executes]').prop('checked')); + Console.setConfig('DarkTheme', $('#pma_console_options').find('input[name=dark_theme]').prop('checked')); /* Setting the dark theme of the console*/ - if (PMA_console.config.DarkTheme) { + if (Console.config.DarkTheme) { $('#pma_console').find('>.content').addClass('console_dark_theme'); } else { $('#pma_console').find('>.content').removeClass('console_dark_theme'); } }, setConfig: function (key, value) { - PMA_console.config[key] = value; + Console.config[key] = value; configSet('Console/' + key, value); }, isSelect: function (queryString) { @@ -416,4 +417,4 @@ var PMA_console = { } }; -export default PMA_console; +export default Console; diff --git a/js/src/consts/files.js b/js/src/consts/files.js index 083a0a1f78..b48cafb556 100644 --- a/js/src/consts/files.js +++ b/js/src/consts/files.js @@ -7,7 +7,7 @@ * @type {Object} files */ const files = { - global: ['error_report', 'config', 'navigation', 'page_settings', 'shortcuts_handler'], + global: ['error_report', 'config', 'navigation', 'page_settings', 'shortcuts_handler', 'functions'], server_privileges: ['server_privileges'], server_databases: ['server_databases'], server_status_advisor: ['server_status_advisor'], @@ -21,7 +21,9 @@ const files = { server_user_groups: ['server_user_groups'], server_replication: ['server_privileges', 'replication'], server_export: ['export'], - server_import: ['import'] + server_import: ['import'], + db_search: ['db_search', 'sql'], + server_sql: ['sql'] }; export default files; diff --git a/js/src/db_search.js b/js/src/db_search.js new file mode 100644 index 0000000000..174b4f9f39 --- /dev/null +++ b/js/src/db_search.js @@ -0,0 +1,253 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * JavaScript functions used on Database Search page + * + * @requires jQuery + * @requires js/functions.js + * + * @package PhpMyAdmin + */ + +/** + * AJAX script for the Database Search page. + * + * Actions ajaxified here: + * Retrieve result of SQL query + */ + +import { PMA_Messages as messages } from './variables/export_variables'; +import { PMA_ajaxShowMessage, PMA_sprintf, PMA_ajaxRemoveMessage } from './utils/show_ajax_messages'; +import PMA_commonParams from './variables/common_params'; +import { PMA_makegrid } from './utils/makegrid'; + +/** + * Unbind all event handlers before tearing down a page + */ +export function teardownDbSearch () { + $('a.browse_results').off('click'); + $('a.delete_results').off('click'); + $('#buttonGo').off('click'); + $('#togglesearchresultlink').off('click'); + $('#togglequerybox').off('click'); + $('#togglesearchformlink').off('click'); + $(document).off('submit', '#db_search_form.ajax'); +} + +export function onloadDbSearch () { + /** Hide the table link in the initial search result */ + let icon = PMA_getImage('s_tbl', '', { 'id': 'table-image' }).toString(); + $('#table-info').prepend(icon).hide(); + + /** Hide the browse and deleted results in the new search criteria */ + $('#buttonGo').click(function () { + $('#table-info').hide(); + $('#browse-results').hide(); + $('#sqlqueryform').hide(); + $('#togglequerybox').hide(); + }); + /** + * Prepare a div containing a link for toggle the search results + */ + $('#togglesearchresultsdiv') + /** don't show it until we have results on-screen */ + .hide(); + + /** + * Changing the displayed text according to + * the hide/show criteria in search result forms + */ + $('#togglesearchresultlink') + .html(messages.strHideSearchResults) + .on('click', function () { + let $link = $(this); + $('#searchresults').slideToggle(); + if ($link.text() === messages.strHideSearchResults) { + $link.text(messages.strShowSearchResults); + } else { + $link.text(messages.strHideSearchResults); + } + /** avoid default click action */ + return false; + }); + + /** + * Prepare a div containing a link for toggle the search form, + * otherwise it's incorrectly displayed after a couple of clicks + */ + $('#togglesearchformdiv') + .hide(); // don't show it until we have results on-screen + + /** + * Changing the displayed text according to + * the hide/show criteria in search form + */ + $('#togglequerybox') + .hide() + .on('click', function () { + let $link = $(this); + $('#sqlqueryform').slideToggle('medium'); + if ($link.text() === messages.strHideQueryBox) { + $link.text(messages.strShowQueryBox); + } else { + $link.text(messages.strHideQueryBox); + } + /** avoid default click action */ + return false; + }); + + /** don't show it until we have results on-screen */ + + /** + * Changing the displayed text according to + * the hide/show criteria in search criteria form + */ + $('#togglesearchformlink') + .html(messages.strShowSearchCriteria) + .on('click', function () { + let $link = $(this); + $('#db_search_form').slideToggle(); + if ($link.text() === messages.strHideSearchCriteria) { + $link.text(messages.strShowSearchCriteria); + } else { + $link.text(messages.strHideSearchCriteria); + } + /** avoid default click action */ + return false; + }); + + /* + * Ajax Event handler for retrieving the results from a table + */ + $(document).on('click', 'a.browse_results', function (e) { + e.preventDefault(); + /** Hides the results shown by the delete criteria */ + let $msg = PMA_ajaxShowMessage(messages.strBrowsing, false); + $('#sqlqueryform').hide(); + $('#togglequerybox').hide(); + /** Load the browse results to the page */ + $('#table-info').show(); + let table_name = $(this).data('table-name'); + $('#table-link').attr({ 'href' : $(this).attr('href') }).text(table_name); + + let url = `${$(this).attr('href')} + #searchresults`; + + let browse_sql = $(this).data('browse-sql'); + let params = { + 'ajax_request': true, + 'is_js_confirmed': true, + 'sql_query' : browse_sql + }; + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success) { + $('#browse-results').html(data.message); + PMA_ajaxRemoveMessage($msg); + $('.table_results').each(function () { + PMA_makegrid(this); + }); + $('#browse-results').show(); + PMA_highlightSQL($('#browse-results')); + $('html, body') + .animate({ + scrollTop: $('#browse-results').offset().top + }, 1000); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + + /* + * Ajax Event handler for deleting the results from a table + */ + $(document).on('click', 'a.delete_results', function (e) { + e.preventDefault(); + /** Hides the results shown by the browse criteria */ + $('#table-info').hide(); + $('#sqlqueryform').hide(); + $('#togglequerybox').hide(); + /** Conformation message for deletion */ + let msg = PMA_sprintf( + messages.strConfirmDeleteResults, + $(this).data('table-name') + ); + if (confirm(msg)) { + let $msg = PMA_ajaxShowMessage(messages.strDeleting, false); + /** Load the deleted option to the page*/ + $('#sqlqueryform').html(''); + let params = { + 'ajax_request': true, + 'is_js_confirmed': true, + 'sql_query': $(this).data('delete-sql') + }; + let url = $(this).attr('href'); + + $.post(url, params, function (data) { + if (typeof data === 'undefined' || !data.success) { + PMA_ajaxShowMessage(data.error, false); + return; + } + + $('#sqlqueryform').html(data.sql_query); + /** Refresh the search results after the deletion */ + document.getElementById('buttonGo').click(); + $('#togglequerybox').html(messages.strHideQueryBox); + /** Show the results of the deletion option */ + $('#browse-results').hide(); + $('#sqlqueryform').show(); + $('#togglequerybox').show(); + $('html, body') + .animate({ + scrollTop: $('#browse-results').offset().top + }, 1000); + PMA_ajaxRemoveMessage($msg); + }); + } + }); + + /** + * Ajax Event handler for retrieving the result of an SQL Query + */ + $(document).on('submit', '#db_search_form.ajax', function (event) { + event.preventDefault(); + var $msgbox = PMA_ajaxShowMessage(messages.strSearching, false); + // jQuery object to reuse + var $form = $(this); + + PMA_prepareForAjaxRequest($form); + + var url = `${$form.serialize()} + ${PMA_commonParams.get('arg_separator')}submit_search=${$('#buttonGo').val()}`; + $.post($form.attr('action'), url, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + // found results + $('#searchresults').html(data.message); + + $('#togglesearchresultlink') + // always start with the Show message + .text(messages.strHideSearchResults); + $('#togglesearchresultsdiv') + // now it's time to show the div containing the link + .show(); + $('#searchresults').show(); + + + $('#db_search_form') + // workaround for Chrome problem (bug #3168569) + .slideToggle() + .hide(); + $('#togglesearchformlink') + // always start with the Show message + .text(messages.strShowSearchCriteria); + $('#togglesearchformdiv') + // now it's time to show the div containing the link + .show(); + } else { + // error message (zero rows) + $('#searchresults').html(data.error).show(); + } + + PMA_ajaxRemoveMessage($msgbox); + }); + }); +} diff --git a/js/src/functions.js b/js/src/functions.js new file mode 100644 index 0000000000..85f852c2b6 --- /dev/null +++ b/js/src/functions.js @@ -0,0 +1,111 @@ +import { AJAX } from './ajax'; +/** + * Here we register a function that will remove the onsubmit event from all + * forms that will be handled by the generic page loader. We then save this + * event handler in the "jQuery data", so that we can fire it up later in + * AJAX.requestHandler(). + * + * See bug #3583316 + */ +export function onload () { + // Registering the onload event for functions.js + // ensures that it will be fired for all pages + $('form').not('.ajax').not('.disableAjax').each(function () { + if ($(this).attr('onsubmit')) { + $(this).data('onsubmit', this.onsubmit).attr('onsubmit', ''); + } + }); + + var $page_content = $('#page_content'); + /** + * Workaround for passing submit button name,value on ajax form submit + * by appending hidden element with submit button name and value. + */ + $page_content.on('click', 'form input[type=submit]', function () { + var buttonName = $(this).attr('name'); + if (typeof buttonName === 'undefined') { + return; + } + $(this).closest('form').append($('', { + 'type' : 'hidden', + 'name' : buttonName, + 'value': $(this).val() + })); + }); + + /** + * Attach event listener to events when user modify visible + * Input,Textarea and select fields to make changes in forms + */ + $page_content.on( + 'keyup change', + 'form.lock-page textarea, ' + + 'form.lock-page input[type="text"], ' + + 'form.lock-page input[type="number"], ' + + 'form.lock-page select', + { value:1 }, + AJAX.lockPageHandler + ); + $page_content.on( + 'change', + 'form.lock-page input[type="checkbox"], ' + + 'form.lock-page input[type="radio"]', + { value:2 }, + AJAX.lockPageHandler + ); + /** + * Reset lock when lock-page form reset event is fired + * Note: reset does not bubble in all browser so attach to + * form directly. + */ + $('form.lock-page').on('reset', function (event) { + AJAX.resetLock(); + }); +} +/** + * Unbind all event handlers before tearing down a page + */ +export function teardown1 () { + $(document).off('click', 'a.themeselect'); + $(document).off('change', '.autosubmit'); + $('a.take_theme').off('click'); +} + +export function onload1 () { + /** + * Theme selector. + */ + $(document).on('click', 'a.themeselect', function (e) { + window.open( + e.target, + 'themes', + 'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes' + ); + return false; + }); + + /** + * Automatic form submission on change. + */ + $(document).on('change', '.autosubmit', function (e) { + e.preventDefault(); + debugger; + console.log($(this).closest('form').submit()); + $(this).closest('form').submit(); + debugger; + }); + + /** + * Theme changer. + */ + $('a.take_theme').on('click', function (e) { + var what = this.name; + if (window.opener && window.opener.document.forms.setTheme.elements.set_theme) { + window.opener.document.forms.setTheme.elements.set_theme.value = what; + window.opener.document.forms.setTheme.submit(); + window.close(); + return false; + } + return true; + }); +} diff --git a/js/src/functions/Common.js b/js/src/functions/Common.js new file mode 100644 index 0000000000..750fe342bf --- /dev/null +++ b/js/src/functions/Common.js @@ -0,0 +1,46 @@ +import { PMA_messages as PMA_messages } from '../variables//export_variables'; +import { PMA_sprintf } from '../utils/sprintf'; +import PMA_commonParams from '../variables/common_params'; +/** + * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query. + * This function is called while clicking links + * + * @param theLink object the link + * @param theSqlQuery object the sql query to submit + * + * @return boolean whether to run the query or not + */ +export function confirmLink (theLink, theSqlQuery) { + // Confirmation is not required in the configuration file + // or browser is Opera (crappy js implementation) + if (PMA_messages.strDoYouReally === '' || typeof(window.opera) !== 'undefined') { + return true; + } + + var is_confirmed = confirm(PMA_sprintf(PMA_messages.strDoYouReally, theSqlQuery)); + if (is_confirmed) { + if (typeof(theLink.href) !== 'undefined') { + theLink.href += PMA_commonParams.get('arg_separator') + 'is_js_confirmed=1'; + } else if (typeof(theLink.form) !== 'undefined') { + theLink.form.action += '?is_js_confirmed=1'; + } + } + + return is_confirmed; +} // end of the 'confirmLink()' function + +export function getJSConfirmCommonParam (elem, params) { + var $elem = $(elem); + var sep = PMA_commonParams.get('arg_separator'); + if (params) { + // Strip possible leading ? + if (params.substring(0,1) === '?') { + params = params.substr(1); + } + params += sep; + } else { + params = ''; + } + params += 'is_js_confirmed=1' + sep + 'ajax_request=true' + sep + 'fk_checks=' + ($elem.find('#fk_checks').is(':checked') ? 1 : 0); + return params; +} diff --git a/js/src/functions/Grid/Cell.js b/js/src/functions/Grid/Cell.js new file mode 100644 index 0000000000..d352a9591e --- /dev/null +++ b/js/src/functions/Grid/Cell.js @@ -0,0 +1,16 @@ +/** + * Return value of a cell in a table. + */ +export function PMA_getCellValue (td) { + var $td = $(td); + if ($td.is('.null')) { + return ''; + } else if ((! $td.is('.to_be_saved') + || $td.is('.set')) + && $td.data('original_data') + ) { + return $td.data('original_data'); + } else { + return $td.text(); + } +} \ No newline at end of file diff --git a/js/src/functions/Grid/GetFieldName.js b/js/src/functions/Grid/GetFieldName.js new file mode 100644 index 0000000000..ee7de276a5 --- /dev/null +++ b/js/src/functions/Grid/GetFieldName.js @@ -0,0 +1,41 @@ +/** + * Get the field name for the current field. Required to construct the query + * for grid editing + * + * @param $table_results enclosing results table + * @param $this_field jQuery object that points to the current field's tr + */ +export function getFieldName ($table_results, $this_field) { + var this_field_index = $this_field.index(); + // ltr or rtl direction does not impact how the DOM was generated + // check if the action column in the left exist + var left_action_exist = !$table_results.find('th:first').hasClass('draggable'); + // number of column span for checkbox and Actions + var left_action_skip = left_action_exist ? $table_results.find('th:first').attr('colspan') - 1 : 0; + + // If this column was sorted, the text of the a element contains something + // like 1 that is useful to indicate the order in case + // of a sort on multiple columns; however, we dont want this as part + // of the column name so we strip it ( .clone() to .end() ) + var field_name = $table_results + .find('thead') + .find('th:eq(' + (this_field_index - left_action_skip) + ') a') + .clone() // clone the element + .children() // select all the children + .remove() // remove all of them + .end() // go back to the selected element + .text(); // grab the text + // happens when just one row (headings contain no a) + if (field_name === '') { + var $heading = $table_results.find('thead').find('th:eq(' + (this_field_index - left_action_skip) + ')').children('span'); + // may contain column comment enclosed in a span - detach it temporarily to read the column name + var $tempColComment = $heading.children().detach(); + field_name = $heading.text(); + // re-attach the column comment + $heading.append($tempColComment); + } + + field_name = $.trim(field_name); + + return field_name; +} \ No newline at end of file diff --git a/js/src/functions/Grid/StickyColumns.js b/js/src/functions/Grid/StickyColumns.js new file mode 100644 index 0000000000..e941940d6c --- /dev/null +++ b/js/src/functions/Grid/StickyColumns.js @@ -0,0 +1,77 @@ +var prevScrollX = 0; +/* + * Set position, left, top, width of sticky_columns div + */ +function setStickyColumnsPosition ($sticky_columns, $table_results, position, top, left, margin_left) { + $sticky_columns + .css('position', position) + .css('top', top) + .css('left', left ? left : 'auto') + .css('margin-left', margin_left ? margin_left : '0px') + .css('width', $table_results.width()); +} + +/* + * Initialize sticky columns + */ +export function initStickyColumns ($table_results) { + return $('
') + .insertBefore($table_results) + .css('position', 'fixed') + .css('z-index', '99') + .css('width', $table_results.width()) + .css('margin-left', $('#page_content').css('margin-left')) + .css('top', $('#floating_menubar').height()) + .css('display', 'none'); +} + +/* + * Arrange/Rearrange columns in sticky header + */ +export function rearrangeStickyColumns ($sticky_columns, $table_results) { + var $originalHeader = $table_results.find('thead'); + var $originalColumns = $originalHeader.find('tr:first').children(); + var $clonedHeader = $originalHeader.clone(); + // clone width per cell + $clonedHeader.find('tr:first').children().width(function (i,val) { + var width = $originalColumns.eq(i).width(); + var is_firefox = navigator.userAgent.indexOf('Firefox') > -1; + if (! is_firefox) { + width += 1; + } + return width; + }); + $sticky_columns.empty().append($clonedHeader); +} + +/* + * Adjust sticky columns on horizontal/vertical scroll for all tables + */ +export function handleAllStickyColumns () { + $('.sticky_columns').each(function () { + handleStickyColumns($(this), $(this).next('.table_results')); + }); +} + +/* + * Adjust sticky columns on horizontal/vertical scroll + */ +export function handleStickyColumns ($sticky_columns, $table_results) { + var currentScrollX = $(window).scrollLeft(); + var windowOffset = $(window).scrollTop(); + var tableStartOffset = $table_results.offset().top; + var tableEndOffset = tableStartOffset + $table_results.height(); + if (windowOffset >= tableStartOffset && windowOffset <= tableEndOffset) { + // for horizontal scrolling + if (prevScrollX !== currentScrollX) { + prevScrollX = currentScrollX; + setStickyColumnsPosition($sticky_columns, $table_results, 'absolute', $('#floating_menubar').height() + windowOffset - tableStartOffset); + // for vertical scrolling + } else { + setStickyColumnsPosition($sticky_columns, $table_results, 'fixed', $('#floating_menubar').height(), $('#pma_navigation').width() - currentScrollX, $('#page_content').css('margin-left')); + } + $sticky_columns.show(); + } else { + $sticky_columns.hide(); + } +} diff --git a/js/src/functions/Server/ServerStatusMonitor.js b/js/src/functions/Server/ServerStatusMonitor.js new file mode 100644 index 0000000000..623ad2b106 --- /dev/null +++ b/js/src/functions/Server/ServerStatusMonitor.js @@ -0,0 +1,133 @@ +import { PMA_Messages as PMA_messages } from '../../variables/export_variables'; +export function getOsDetail (server_os, presetCharts) { + /* Add OS specific system info charts to the preset chart list */ + switch (server_os) { + case 'WINNT': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ + dataPoints: [{ type: 'cpu', name: 'loadavg' }] + }], + maxYLabel: 100 + }, + + 'memory': { + title: PMA_messages.strSystemMemory, + series: [{ + label: PMA_messages.strTotalMemory, + fill: true + }, { + dataType: 'memory', + label: PMA_messages.strUsedMemory, + fill: true + }], + nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + + 'swap': { + title: PMA_messages.strSystemSwap, + series: [{ + label: PMA_messages.strTotalSwap, + fill: true + }, { + label: PMA_messages.strUsedSwap, + fill: true + }], + nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }] }, + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }] } + ], + maxYLabel: 0 + } + }); + break; + + case 'Linux': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux' }], + maxYLabel: 0 + }, + 'memory': { + title: PMA_messages.strSystemMemory, + series: [ + { label: PMA_messages.strBufferedMemory, fill: true }, + { label: PMA_messages.strUsedMemory, fill: true }, + { label: PMA_messages.strCachedMemory, fill: true }, + { label: PMA_messages.strFreeMemory, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'Cached' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + 'swap': { + title: PMA_messages.strSystemSwap, + series: [ + { label: PMA_messages.strCachedSwap, fill: true }, + { label: PMA_messages.strUsedSwap, fill: true }, + { label: PMA_messages.strFreeSwap, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }); + break; + + case 'SunOS': + $.extend(presetCharts, { + 'cpu': { + title: PMA_messages.strSystemCPUUsage, + series: [{ + label: PMA_messages.strAverageLoad + }], + nodes: [{ + dataPoints: [{ type: 'cpu', name: 'loadavg' }] + }], + maxYLabel: 0 + }, + 'memory': { + title: PMA_messages.strSystemMemory, + series: [ + { label: PMA_messages.strUsedMemory, fill: true }, + { label: PMA_messages.strFreeMemory, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + }, + 'swap': { + title: PMA_messages.strSystemSwap, + series: [ + { label: PMA_messages.strUsedSwap, fill: true }, + { label: PMA_messages.strFreeSwap, fill: true } + ], + nodes: [ + { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, + { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } + ], + maxYLabel: 0 + } + }); + break; + } +} diff --git a/js/src/functions/Server/SeverStatusSorter.js b/js/src/functions/Server/SeverStatusSorter.js new file mode 100644 index 0000000000..fd7f1531d5 --- /dev/null +++ b/js/src/functions/Server/SeverStatusSorter.js @@ -0,0 +1,42 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Module import + */ +import { $ } from '../../utils/extend_jquery'; +import 'tablesorter'; +// TODO: tablesorter shouldn't sort already sorted columns +/** + * @access public + * + * @param {string} tabid Table id for chart drawing + * + * @return {void} + */ +function initTableSorter (tabid) { + var $table; + var opts; + switch (tabid) { + case 'statustabs_queries': + $table = $('#serverstatusqueriesdetails'); + opts = { + sortList: [[3, 1]], + headers: { + 1: { sorter: 'fancyNumber' }, + 2: { sorter: 'fancyNumber' } + } + }; + break; + } + $table.tablesorter(opts); + $table.find('tr:first th') + .append('
') + .addClass('header'); +} + +/** + * Module export + */ +export { + initTableSorter +}; diff --git a/js/src/functions/Sql.js b/js/src/functions/Sql.js new file mode 100644 index 0000000000..105374c36b --- /dev/null +++ b/js/src/functions/Sql.js @@ -0,0 +1,82 @@ +import { isStorageSupported } from './config'; +/** + * Saves SQL query in local storage or cookie + * + * @param string SQL query + * @return void + */ +export function PMA_autosaveSQL (query) { + if (isStorageSupported('localStorage')) { + window.localStorage.auto_saved_sql = query; + } else { + Cookies.set('auto_saved_sql', query); + } +} + +/** + * Saves SQL query in local storage or cookie + * + * @param string database name + * @param string table name + * @param string SQL query + * @return void + */ +export function PMA_showThisQuery (db, table, query) { + var show_this_query_object = { + 'db': db, + 'table': table, + 'query': query + }; + if (isStorageSupported('localStorage')) { + window.localStorage.show_this_query = 1; + window.localStorage.show_this_query_object = JSON.stringify(show_this_query_object); + } else { + Cookies.set('show_this_quey', 1); + Cookies.set('show_this_query_object', JSON.stringify(show_this_query_object)); + } +} + +/** + * Set query to codemirror if show this query is + * checked and query for the db and table pair exists + */ +export function setShowThisQuery () { + var db = $('input[name="db"]').val(); + var table = $('input[name="table"]').val(); + if (isStorageSupported('localStorage')) { + if (window.localStorage.show_this_query_object !== undefined) { + var stored_db = JSON.parse(window.localStorage.show_this_query_object).db; + var stored_table = JSON.parse(window.localStorage.show_this_query_object).table; + var stored_query = JSON.parse(window.localStorage.show_this_query_object).query; + } + if (window.localStorage.show_this_query !== undefined + && window.localStorage.show_this_query === '1') { + $('input[name="show_query"]').prop('checked', true); + if (db === stored_db && table === stored_table) { + if (codemirror_editor) { + codemirror_editor.setValue(stored_query); + } else if (document.sqlform) { + document.sqlform.sql_query.value = stored_query; + } + } + } else { + $('input[name="show_query"]').prop('checked', false); + } + } +} + +/** + * Saves SQL query with sort in local storage or cookie + * + * @param string SQL query + * @return void + */ +export function PMA_autosaveSQLSort (query) { + if (query) { + if (isStorageSupported('localStorage')) { + window.localStorage.auto_saved_sql_sort = query; + } else { + Cookies.set('auto_saved_sql_sort', query); + } + } +} diff --git a/js/src/functions/UpdateCode.js b/js/src/functions/UpdateCode.js new file mode 100644 index 0000000000..7513a1ffa6 --- /dev/null +++ b/js/src/functions/UpdateCode.js @@ -0,0 +1,50 @@ +/** + * Updates an element containing code. + * + * @param jQuery Object $base base element which contains the raw and the + * highlighted code. + * + * @param string htmlValue code in HTML format, displayed if code cannot be + * highlighted + * + * @param string rawValue raw code, used as a parameter for highlighter + * + * @return bool whether content was updated or not + */ +export function PMA_updateCode ($base, htmlValue, rawValue) { + var $code = $base.find('code'); + if ($code.length === 0) { + return false; + } + + // Determines the type of the content and appropriate CodeMirror mode. + var type = ''; + var mode = ''; + if ($code.hasClass('json')) { + type = 'json'; + mode = 'application/json'; + } else if ($code.hasClass('sql')) { + type = 'sql'; + mode = 'text/x-mysql'; + } else if ($code.hasClass('xml')) { + type = 'xml'; + mode = 'application/xml'; + } else { + return false; + } + + // Element used to display unhighlighted code. + var $notHighlighted = $('
' + htmlValue + '
'); + + // Tries to highlight code using CodeMirror. + if (typeof CodeMirror !== 'undefined') { + var $highlighted = $('
'); + CodeMirror.runMode(rawValue, mode, $highlighted[0]); + $notHighlighted.hide(); + $code.html('').append($notHighlighted, $highlighted[0]); + } else { + $code.html('').append($notHighlighted); + } + + return true; +} diff --git a/js/src/functions/chart.js b/js/src/functions/chart.js index 8c2f00864d..a1703ba503 100644 --- a/js/src/functions/chart.js +++ b/js/src/functions/chart.js @@ -2,11 +2,14 @@ * Creates a Profiling Chart. Used in sql.js * and in server_status_monitor.js */ +import { $ } from '../utils/extend_jquery'; import JQPlotChartFactory from '../classes/Chart'; +import { ChartType, ColumnType, DataTable } from '../classes/Chart'; -export function PMA_createProfilingChart (target, data) { +export function createProfilingChart (target, data) { // create the chart var factory = new JQPlotChartFactory(); + console.log(factory); var chart = factory.createChart(ChartType.PIE, target); // create the data table and add columns @@ -62,5 +65,6 @@ export function PMA_createProfilingChart (target, data) { '#2e3436' ] }); + console.log(chart); return chart; } diff --git a/js/src/functions/config.js b/js/src/functions/config.js index c9769174e3..522189c6c7 100644 --- a/js/src/functions/config.js +++ b/js/src/functions/config.js @@ -26,7 +26,7 @@ export function isStorageSupported (type, warn) { } return false; } -/** ******************** Common Functions for Srttings page ****************** */ +/** ******************** Common Functions for Settings page ****************** */ /** * Checks whether field has its default value * @@ -126,6 +126,8 @@ export function setFieldValue (field, field_type, value) { * Returns field type * * @param {Element} field + * + * @return {string} */ export function getFieldType (field) { var $field = $(field); @@ -321,7 +323,8 @@ export function setupRestoreField () { var field_sel; if ($(this).hasClass('restore-default')) { field_sel = href; - restoreField(field_sel.substr(1), defaultValues); + console.log(field_sel); + restoreField(field_sel.substr(1)); } else { field_sel = href.match(/^[^=]+/)[0]; var value = href.match(/\=(.+)$/)[1]; diff --git a/js/src/index.js b/js/src/index.js index b6ddf95105..24c29d5a28 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -4,7 +4,7 @@ * Module import */ import './variables/import_variables'; -import { jQuery as $ } from './utils/JqueryExtended'; +import { $ } from './utils/JqueryExtended'; import { AJAX } from './ajax'; import './variables/get_config'; import files from './consts/files'; @@ -15,9 +15,6 @@ import { escapeHtml } from './utils/Sanitise'; import { PMA_ajaxShowMessage } from './utils/show_ajax_messages'; import PMA_commonParams from './variables/common_params'; -// console.log(PMA_messages); -// console.log(PMA_ajaxShowMessage); - /** * Page load event handler */ @@ -116,7 +113,6 @@ $(document).ajaxError(function (event, request, settings) { for (let i in files.global) { AJAX.scriptHandler.add(files.global[i]); } - /** * This block of code is for importing javascript files needed * for the first time loading of the page. @@ -133,10 +129,6 @@ if (typeof files[firstPage] !== 'undefined' && firstPage.toLocaleLowerCase() !== for (let i in files[indexPage]) { AJAX.scriptHandler.add(files[indexPage][i], 1); } -} else if (typeof files[indexPage] !== 'undefined' && firstPage.toLocaleLowerCase() === 'index') { - for (let i in files[indexPage]) { - AJAX.scriptHandler.add(files[indexPage][i]); - } } $(function () { diff --git a/js/src/server_databases.js b/js/src/server_databases.js index f29e9f67aa..451dc91175 100644 --- a/js/src/server_databases.js +++ b/js/src/server_databases.js @@ -15,10 +15,16 @@ import { PMA_sprintf } from './utils/sprintf'; import './variables/import_variables'; import { PMA_ajaxShowMessage } from './utils/show_ajax_messages'; import { escapeHtml } from './utils/Sanitise'; -import { PMA_Messages as PMA_messages } from './variables/export_variables'; +import { PMA_Messages as messages } from './variables/export_variables'; +<<<<<<< HEAD import { jQuery as $ } from './utils/JqueryExtended'; +======= +import { $ } from './utils/extend_jquery'; +>>>>>>> Weekly progress. import { AJAX } from './ajax'; import CommonParams from './variables/common_params'; +import { PMA_reloadNavigation } from './functions/navigation'; +import { getJSConfirmCommonParam } from './functions/Common'; /** * @package PhpMyAdmin diff --git a/js/src/server_privileges.js b/js/src/server_privileges.js index e1fe4a6728..970971ce4b 100644 --- a/js/src/server_privileges.js +++ b/js/src/server_privileges.js @@ -16,7 +16,11 @@ import { checkPasswordStrength, displayPasswordGenerateButton } from './utils/pa import { PMA_Messages as messages } from './variables/export_variables'; import { PMA_ajaxShowMessage, PMA_ajaxRemoveMessage } from './utils/show_ajax_messages'; import CommonParams from './variables/common_params'; +<<<<<<< HEAD import { jQuery as $ } from './utils/JqueryExtended'; +======= +import { $ } from './utils/extend_jquery'; +>>>>>>> Weekly progress. import { PMA_getSQLEditor } from './utils/sql'; /** diff --git a/js/src/server_status_monitor.js b/js/src/server_status_monitor.js index ff8e84849c..b9a8f3b28b 100644 --- a/js/src/server_status_monitor.js +++ b/js/src/server_status_monitor.js @@ -1,4 +1,26 @@ /* vim: set expandtab sw=4 ts=4 sts=4: */ +import { PMA_Messages as messages } from './variables/export_variables'; +import { $ } from './utils/extend_jquery'; + +import 'updated-jqplot'; +import './plugins/jquery/jquery.sortableTable'; + +import '../../node_modules/updated-jqplot/dist/plugins/jqplot.pieRenderer.js'; +import '../../node_modules/updated-jqplot/dist/plugins/jqplot.enhancedPieLegendRenderer.js'; +import '../../node_modules/updated-jqplot/dist/plugins/jqplot.canvasTextRenderer.js'; +import '../../node_modules/updated-jqplot/dist/plugins/jqplot.canvasAxisLabelRenderer.js'; +import '../../node_modules/updated-jqplot/dist/plugins/jqplot.dateAxisRenderer.js'; +import '../../node_modules/updated-jqplot/dist/plugins/jqplot.highlighter.js'; +import '../../node_modules/updated-jqplot/dist/plugins/jqplot.cursor.js'; +import './plugins/jqplot/jqplot.byteFormatter'; + +import { getOsDetail } from './functions/Server/ServerStatusMonitor'; +import { isStorageSupported } from './functions/config'; +import { createProfilingChart } from './functions/chart'; +import CommonParams from './variables/common_params'; +import { escapeHtml } from './utils/Sanitise'; +import { PMA_getImage } from './functions/get_image'; + var runtime = {}; var server_time_diff; var server_os; @@ -160,9 +182,9 @@ export function onload3 () { var presetCharts = { // Query cache efficiency 'qce': { - title: PMA_messages.strQueryCacheEfficiency, + title: messages.strQueryCacheEfficiency, series: [{ - label: PMA_messages.strQueryCacheEfficiency + label: messages.strQueryCacheEfficiency }], nodes: [{ dataPoints: [{ type: 'statusvar', name: 'Qcache_hits' }, { type: 'statusvar', name: 'Com_select' }], @@ -172,9 +194,9 @@ export function onload3 () { }, // Query cache usage 'qcu': { - title: PMA_messages.strQueryCacheUsage, + title: messages.strQueryCacheUsage, series: [{ - label: PMA_messages.strQueryCacheUsed + label: messages.strQueryCacheUsed }], nodes: [{ dataPoints: [{ type: 'statusvar', name: 'Qcache_free_memory' }, { type: 'servervar', name: 'query_cache_size' }], @@ -188,150 +210,21 @@ export function onload3 () { var selectionTimeDiff = []; var selectionStartX; var selectionStartY; - var selectionEndX; - var selectionEndY; + // var selectionEndX; + // var selectionEndY; var drawTimeSpan = false; // chart tooltip - var tooltipBox; + // var tooltipBox; - /* Add OS specific system info charts to the preset chart list */ - switch (server_os) { - case 'WINNT': - $.extend(presetCharts, { - 'cpu': { - title: PMA_messages.strSystemCPUUsage, - series: [{ - label: PMA_messages.strAverageLoad - }], - nodes: [{ - dataPoints: [{ type: 'cpu', name: 'loadavg' }] - }], - maxYLabel: 100 - }, - - 'memory': { - title: PMA_messages.strSystemMemory, - series: [{ - label: PMA_messages.strTotalMemory, - fill: true - }, { - dataType: 'memory', - label: PMA_messages.strUsedMemory, - fill: true - }], - nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 } - ], - maxYLabel: 0 - }, - - 'swap': { - title: PMA_messages.strSystemSwap, - series: [{ - label: PMA_messages.strTotalSwap, - fill: true - }, { - label: PMA_messages.strUsedSwap, - fill: true - }], - nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }] }, - { dataPoints: [{ type: 'memory', name: 'SwapUsed' }] } - ], - maxYLabel: 0 - } - }); - break; - - case 'Linux': - $.extend(presetCharts, { - 'cpu': { - title: PMA_messages.strSystemCPUUsage, - series: [{ - label: PMA_messages.strAverageLoad - }], - nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux' }], - maxYLabel: 0 - }, - 'memory': { - title: PMA_messages.strSystemMemory, - series: [ - { label: PMA_messages.strBufferedMemory, fill: true }, - { label: PMA_messages.strUsedMemory, fill: true }, - { label: PMA_messages.strCachedMemory, fill: true }, - { label: PMA_messages.strFreeMemory, fill: true } - ], - nodes: [ - { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'Cached' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } - ], - maxYLabel: 0 - }, - 'swap': { - title: PMA_messages.strSystemSwap, - series: [ - { label: PMA_messages.strCachedSwap, fill: true }, - { label: PMA_messages.strUsedSwap, fill: true }, - { label: PMA_messages.strFreeSwap, fill: true } - ], - nodes: [ - { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } - ], - maxYLabel: 0 - } - }); - break; - - case 'SunOS': - $.extend(presetCharts, { - 'cpu': { - title: PMA_messages.strSystemCPUUsage, - series: [{ - label: PMA_messages.strAverageLoad - }], - nodes: [{ - dataPoints: [{ type: 'cpu', name: 'loadavg' }] - }], - maxYLabel: 0 - }, - 'memory': { - title: PMA_messages.strSystemMemory, - series: [ - { label: PMA_messages.strUsedMemory, fill: true }, - { label: PMA_messages.strFreeMemory, fill: true } - ], - nodes: [ - { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } - ], - maxYLabel: 0 - }, - 'swap': { - title: PMA_messages.strSystemSwap, - series: [ - { label: PMA_messages.strUsedSwap, fill: true }, - { label: PMA_messages.strFreeSwap, fill: true } - ], - nodes: [ - { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, - { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } - ], - maxYLabel: 0 - } - }); - break; - } + getOsDetail(server_os, presetCharts); // Default setting for the chart grid var defaultChartGrid = { 'c0': { - title: PMA_messages.strQuestions, + title: messages.strQuestions, series: [ - { label: PMA_messages.strQuestions } + { label: messages.strQuestions } ], nodes: [ { dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' } @@ -339,10 +232,10 @@ export function onload3 () { maxYLabel: 0 }, 'c1': { - title: PMA_messages.strChartConnectionsTitle, + title: messages.strChartConnectionsTitle, series: [ - { label: PMA_messages.strConnections }, - { label: PMA_messages.strProcesses } + { label: messages.strConnections }, + { label: messages.strProcesses } ], nodes: [ { dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' }, @@ -351,10 +244,10 @@ export function onload3 () { maxYLabel: 0 }, 'c2': { - title: PMA_messages.strTraffic, + title: messages.strTraffic, series: [ - { label: PMA_messages.strBytesSent }, - { label: PMA_messages.strBytesReceived } + { label: messages.strBytesSent }, + { label: messages.strBytesReceived } ], nodes: [ { dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 }, @@ -486,7 +379,7 @@ export function onload3 () { event.preventDefault(); var dlgButtons = { }; - dlgButtons[PMA_messages.strAddChart] = function () { + dlgButtons[messages.strAddChart] = function () { var type = $('input[name="chartType"]:checked').val(); if (type === 'preset') { @@ -496,7 +389,7 @@ export function onload3 () { // each time he adds a series // So here we only warn if he didn't add a series yet if (! newChart || ! newChart.nodes || newChart.nodes.length === 0) { - alert(PMA_messages.strAddOneSeriesWarning); + alert(messages.strAddOneSeriesWarning); return; } } @@ -512,7 +405,7 @@ export function onload3 () { $(this).dialog('close'); }; - dlgButtons[PMA_messages.strClose] = function () { + dlgButtons[messages.strClose] = function () { newChart = null; $('span#clearSeriesLink').hide(); $('#seriesPreview').html(''); @@ -553,7 +446,7 @@ export function onload3 () { buttons: dlgButtons }); - $('#seriesPreview').html('' + PMA_messages.strNone + ''); + $('#seriesPreview').html('' + messages.strNone + ''); return false; }); @@ -580,18 +473,18 @@ export function onload3 () { $('a[href="#importMonitorConfig"]').on('click', function (event) { event.preventDefault(); - $('#emptyDialog').dialog({ title: PMA_messages.strImportDialogTitle }); - $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':
' + + $('#emptyDialog').dialog({ title: messages.strImportDialogTitle }); + $('#emptyDialog').html(messages.strImportDialogMessage + ':
' + '
'); var dlgBtns = {}; - dlgBtns[PMA_messages.strImport] = function () { + dlgBtns[messages.strImport] = function () { var input = $('#emptyDialog').find('#import_file')[0]; var reader = new FileReader(); reader.onerror = function (event) { - alert(PMA_messages.strFailedParsingConfig + '\n' + event.target.error.code); + alert(messages.strFailedParsingConfig + '\n' + event.target.error.code); }; reader.onload = function (e) { var data = e.target.result; @@ -600,14 +493,14 @@ export function onload3 () { try { json = JSON.parse(data); } catch (err) { - alert(PMA_messages.strFailedParsingConfig); + alert(messages.strFailedParsingConfig); $('#emptyDialog').dialog('close'); return; } // Basic check, is this a monitor config json? if (!json || ! json.monitorCharts || ! json.monitorCharts) { - alert(PMA_messages.strFailedParsingConfig); + alert(messages.strFailedParsingConfig); $('#emptyDialog').dialog('close'); return; } @@ -618,8 +511,7 @@ export function onload3 () { window.localStorage.monitorSettings = JSON.stringify(json.monitorSettings); rebuildGrid(); } catch (err) { - console.log(err); - alert(PMA_messages.strFailedBuildingGrid); + alert(messages.strFailedBuildingGrid); // If an exception is thrown, load default again if (isStorageSupported('localStorage')) { window.localStorage.removeItem('monitorCharts'); @@ -633,7 +525,7 @@ export function onload3 () { reader.readAsText(input.files[0]); }; - dlgBtns[PMA_messages.strCancel] = function () { + dlgBtns[messages.strCancel] = function () { $(this).dialog('close'); }; @@ -659,9 +551,9 @@ export function onload3 () { event.preventDefault(); runtime.redrawCharts = ! runtime.redrawCharts; if (! runtime.redrawCharts) { - $(this).html(PMA_getImage('play') + PMA_messages.strResumeMonitor); + $(this).html(PMA_getImage('play') + messages.strResumeMonitor); } else { - $(this).html(PMA_getImage('pause') + PMA_messages.strPauseMonitor); + $(this).html(PMA_getImage('pause') + messages.strPauseMonitor); if (! runtime.charts) { initGrid(); $('a[href="#settingsPopup"]').show(); @@ -686,7 +578,7 @@ export function onload3 () { $.extend(vars, getvars); } - $.get('server_status_monitor.php' + PMA_commonParams.get('common_query'), vars, + $.get('server_status_monitor.php' + CommonParams.get('common_query'), vars, function (data) { var logVars; if (typeof data !== 'undefined' && data.success === true) { @@ -700,40 +592,40 @@ export function onload3 () { if (logVars.general_log === 'ON') { if (logVars.slow_query_log === 'ON') { - msg = PMA_messages.strBothLogOn; + msg = messages.strBothLogOn; } else { - msg = PMA_messages.strGenLogOn; + msg = messages.strGenLogOn; } } if (msg.length === 0 && logVars.slow_query_log === 'ON') { - msg = PMA_messages.strSlowLogOn; + msg = messages.strSlowLogOn; } if (msg.length === 0) { icon = PMA_getImage('s_error'); - msg = PMA_messages.strBothLogOff; + msg = messages.strBothLogOff; } - str = '' + PMA_messages.strCurrentSettings + '
'; + str = '' + messages.strCurrentSettings + '
'; str += icon + msg + '
'; if (logVars.log_output !== 'TABLE') { - str += PMA_getImage('s_error') + ' ' + PMA_messages.strLogOutNotTable + '
'; + str += PMA_getImage('s_error') + ' ' + messages.strLogOutNotTable + '
'; } else { - str += PMA_getImage('s_success') + ' ' + PMA_messages.strLogOutIsTable + '
'; + str += PMA_getImage('s_success') + ' ' + messages.strLogOutIsTable + '
'; } if (logVars.slow_query_log === 'ON') { if (logVars.long_query_time > 2) { str += PMA_getImage('s_attention') + ' '; - str += PMA_sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time); + str += PMA_sprintf(messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time); str += '
'; } if (logVars.long_query_time < 2) { str += PMA_getImage('s_success') + ' '; - str += PMA_sprintf(PMA_messages.strLongQueryTimeSet, logVars.long_query_time); + str += PMA_sprintf(messages.strLongQueryTimeSet, logVars.long_query_time); str += '
'; } } @@ -741,9 +633,9 @@ export function onload3 () { str += '
'; if (is_superuser) { - str += '

' + PMA_messages.strChangeSettings + ''; + str += '

' + messages.strChangeSettings + ''; str += '
'; @@ -812,7 +704,7 @@ export function onload3 () { $('input[name="chartType"]').on('change', function () { $('#chartVariableSettings').toggle(this.checked && this.value === 'variable'); var title = $('input[name="chartTitle"]').val(); - if (title === PMA_messages.strChartTitle || + if (title === messages.strChartTitle || title === $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text() ) { $('input[name="chartTitle"]') @@ -838,7 +730,7 @@ export function onload3 () { $('a[href="#kibDivisor"]').on('click', function (event) { event.preventDefault(); $('input[name="valueDivisor"]').val(1024); - $('input[name="valueUnit"]').val(PMA_messages.strKiB); + $('input[name="valueUnit"]').val(messages.strKiB); $('span.unitInput').toggle(true); $('input[name="useUnit"]').prop('checked', true); return false; @@ -847,7 +739,7 @@ export function onload3 () { $('a[href="#mibDivisor"]').on('click', function (event) { event.preventDefault(); $('input[name="valueDivisor"]').val(1024 * 1024); - $('input[name="valueUnit"]').val(PMA_messages.strMiB); + $('input[name="valueUnit"]').val(messages.strMiB); $('span.unitInput').toggle(true); $('input[name="useUnit"]').prop('checked', true); return false; @@ -855,7 +747,7 @@ export function onload3 () { $('a[href="#submitClearSeries"]').on('click', function (event) { event.preventDefault(); - $('#seriesPreview').html('' + PMA_messages.strNone + ''); + $('#seriesPreview').html('' + messages.strNone + ''); newChart = null; $('#clearSeriesLink').hide(); }); @@ -894,9 +786,9 @@ export function onload3 () { serie.unit = $('input[name="valueUnit"]').val(); } - var str = serie.display === 'differential' ? ', ' + PMA_messages.strDifferential : ''; - str += serie.valueDivisor ? (', ' + PMA_sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : ''; - str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : ''; + var str = serie.display === 'differential' ? ', ' + messages.strDifferential : ''; + str += serie.valueDivisor ? (', ' + PMA_sprintf(messages.strDividedBy, serie.valueDivisor)) : ''; + str += serie.unit ? (', ' + messages.strUnit + ': ' + serie.unit) : ''; var newSeries = { label: $('#variableInput').val().replace(/_/g, ' ') @@ -940,11 +832,11 @@ export function onload3 () { && typeof window.localStorage.monitorVersion !== 'undefined' && monitorProtocolVersion !== window.localStorage.monitorVersion ) { - $('#emptyDialog').dialog({ title: PMA_messages.strIncompatibleMonitorConfig }); - $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription); + $('#emptyDialog').dialog({ title: messages.strIncompatibleMonitorConfig }); + $('#emptyDialog').html(messages.strIncompatibleMonitorConfigDescription); var dlgBtns = {}; - dlgBtns[PMA_messages.strClose] = function () { + dlgBtns[messages.strClose] = function () { $(this).dialog('close'); }; @@ -1092,25 +984,25 @@ export function onload3 () { } }; - if (settings.title === PMA_messages.strSystemCPUUsage || - settings.title === PMA_messages.strQueryCacheEfficiency + if (settings.title === messages.strSystemCPUUsage || + settings.title === messages.strQueryCacheEfficiency ) { settings.axes.yaxis.tickOptions = { formatString: '%d %%' }; - } else if (settings.title === PMA_messages.strSystemMemory || - settings.title === PMA_messages.strSystemSwap + } else if (settings.title === messages.strSystemMemory || + settings.title === messages.strSystemSwap ) { settings.stackSeries = true; settings.axes.yaxis.tickOptions = { formatter: $.jqplot.byteFormatter(2) // MiB }; - } else if (settings.title === PMA_messages.strTraffic) { + } else if (settings.title === messages.strTraffic) { settings.axes.yaxis.tickOptions = { formatter: $.jqplot.byteFormatter(1) // KiB }; - } else if (settings.title === PMA_messages.strQuestions || - settings.title === PMA_messages.strConnections + } else if (settings.title === messages.strQuestions || + settings.title === messages.strConnections ) { settings.axes.yaxis.tickOptions = { formatter: function (format, val) { @@ -1306,12 +1198,12 @@ export function onload3 () { var dlgBtns = { }; - dlgBtns[PMA_messages.strFromSlowLog] = function () { + dlgBtns[messages.strFromSlowLog] = function () { loadLog('slow', min, max); $(this).dialog('close'); }; - dlgBtns[PMA_messages.strFromGeneralLog] = function () { + dlgBtns[messages.strFromGeneralLog] = function () { loadLog('general', min, max); $(this).dialog('close'); }; @@ -1352,12 +1244,12 @@ export function onload3 () { /* Called in regular intervals, this function updates the values of each chart in the grid */ function refreshChartGrid () { /* Send to server */ - runtime.refreshRequest = $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), { + runtime.refreshRequest = $.post('server_status_monitor.php' + CommonParams.get('common_query'), { ajax_request: true, chart_data: 1, type: 'chartgrid', requiredData: JSON.stringify(runtime.dataList), - server: PMA_commonParams.get('server') + server: CommonParams.get('server') }, function (data) { var chartData; if (typeof data !== 'undefined' && data.success === true) { @@ -1448,8 +1340,8 @@ export function onload3 () { elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints); } } - if (elem.title === PMA_messages.strSystemMemory || - elem.title === PMA_messages.strSystemSwap + if (elem.title === messages.strSystemMemory || + elem.title === messages.strSystemSwap ) { total += value; } @@ -1463,15 +1355,15 @@ export function onload3 () { (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2), (runtime.xmax - tickInterval), runtime.xmax]; - if (elem.title !== PMA_messages.strSystemCPUUsage && - elem.title !== PMA_messages.strQueryCacheEfficiency && - elem.title !== PMA_messages.strSystemMemory && - elem.title !== PMA_messages.strSystemSwap + if (elem.title !== messages.strSystemCPUUsage && + elem.title !== messages.strQueryCacheEfficiency && + elem.title !== messages.strSystemMemory && + elem.title !== messages.strSystemSwap ) { elem.chart.axes.yaxis.max = Math.ceil(elem.maxYLabel * 1.1); elem.chart.axes.yaxis.tickInterval = Math.ceil(elem.maxYLabel * 1.1 / 5); - } else if (elem.title === PMA_messages.strSystemMemory || - elem.title === PMA_messages.strSystemSwap + } else if (elem.title === messages.strSystemMemory || + elem.title === messages.strSystemSwap ) { elem.chart.axes.yaxis.max = Math.ceil(total * 1.1 / 100) * 100; elem.chart.axes.yaxis.tickInterval = Math.ceil(total * 1.1 / 5); @@ -1571,13 +1463,13 @@ export function onload3 () { opts.limitTypes = false; } - $('#emptyDialog').dialog({ title: PMA_messages.strAnalysingLogsTitle }); - $('#emptyDialog').html(PMA_messages.strAnalysingLogs + + $('#emptyDialog').dialog({ title: messages.strAnalysingLogsTitle }); + $('#emptyDialog').html(messages.strAnalysingLogs + ' '); var dlgBtns = {}; - dlgBtns[PMA_messages.strCancelRequest] = function () { + dlgBtns[messages.strCancelRequest] = function () { if (logRequest !== null) { logRequest.abort(); } @@ -1592,7 +1484,7 @@ export function onload3 () { }); - logRequest = $.get('server_status_monitor.php' + PMA_commonParams.get('common_query'), + logRequest = $.get('server_status_monitor.php' + CommonParams.get('common_query'), { ajax_request: true, log_data: 1, type: opts.src, @@ -1611,10 +1503,10 @@ export function onload3 () { } if (logData.rows.length === 0) { - $('#emptyDialog').dialog({ title: PMA_messages.strNoDataFoundTitle }); - $('#emptyDialog').html('

' + PMA_messages.strNoDataFound + '

'); + $('#emptyDialog').dialog({ title: messages.strNoDataFoundTitle }); + $('#emptyDialog').html('

' + messages.strNoDataFound + '

'); - dlgBtns[PMA_messages.strClose] = function () { + dlgBtns[messages.strClose] = function () { $(this).dialog('close'); }; @@ -1625,8 +1517,8 @@ export function onload3 () { runtime.logDataCols = buildLogTable(logData, opts.removeVariables); /* Show some stats in the dialog */ - $('#emptyDialog').dialog({ title: PMA_messages.strLoadingLogs }); - $('#emptyDialog').html('

' + PMA_messages.strLogDataLoaded + '

'); + $('#emptyDialog').dialog({ title: messages.strLoadingLogs }); + $('#emptyDialog').html('

' + messages.strLogDataLoaded + '

'); $.each(logData.sum, function (key, value) { key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase(); if (key === 'Total') { @@ -1639,15 +1531,15 @@ export function onload3 () { if (logData.numRows > 12) { $('#logTable').prepend( '
' + - ' ' + PMA_messages.strFiltersForLogTable + '' + + ' ' + messages.strFiltersForLogTable + '' + '
' + - ' ' + + ' ' + ' ' + '
' + - ((logData.numRows > 250) ? '
' : '') + + ((logData.numRows > 250) ? '
' : '') + '
' + ' ' + - ' ' + + ' ' + ' ' ); @@ -1663,7 +1555,7 @@ export function onload3 () { } } - dlgBtns[PMA_messages.strJumpToTable] = function () { + dlgBtns[messages.strJumpToTable] = function () { $(this).dialog('close'); $(document).scrollTop($('#logTable').offset().top); }; @@ -1826,8 +1718,8 @@ export function onload3 () { // Display some stats at the bottom of the table $('#logTable').find('table tfoot tr') .html('' + - PMA_messages.strSumRows + ' ' + rowSum + '' + - PMA_messages.strTotal + '' + totalSum + ''); + messages.strSumRows + ' ' + rowSum + '' + + messages.strTotal + '' + totalSum + ''); } } @@ -1906,17 +1798,17 @@ export function onload3 () { } $table.append('' + - '' + PMA_messages.strSumRows + - ' ' + data.numRows + '' + PMA_messages.strTotal + + '' + messages.strSumRows + + ' ' + data.numRows + '' + messages.strTotal + '' + data.sum.TOTAL + ''); // Append a tooltip to the count column, if there exist one if ($('#logTable').find('tr:first th:last').text().indexOf('#') > -1) { $('#logTable').find('tr:first th:last').append(' ' + PMA_getImage('b_help', '', { 'class': 'qroupedQueryInfoIcon' })); - var tooltipContent = PMA_messages.strCountColumnExplanation; + var tooltipContent = messages.strCountColumnExplanation; if (groupInserts) { - tooltipContent += '

' + PMA_messages.strMoreCountColumnExplanation + '

'; + tooltipContent += '

' + messages.strMoreCountColumnExplanation + '

'; } PMA_tooltip( @@ -1958,10 +1850,10 @@ export function onload3 () { var profilingChart = null; var dlgBtns = {}; - dlgBtns[PMA_messages.strAnalyzeQuery] = function () { + dlgBtns[messages.strAnalyzeQuery] = function () { loadQueryAnalysis(rowData); }; - dlgBtns[PMA_messages.strClose] = function () { + dlgBtns[messages.strClose] = function () { $(this).dialog('close'); }; @@ -1989,15 +1881,15 @@ export function onload3 () { var db = rowData.db || ''; $('#queryAnalyzerDialog').find('div.placeHolder').html( - PMA_messages.strAnalyzing + ' '); - $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), { + $.post('server_status_monitor.php' + CommonParams.get('common_query'), { ajax_request: true, query_analyzer: true, query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(), database: db, - server: PMA_commonParams.get('server') + server: CommonParams.get('server') }, function (data) { var i; var l; @@ -2006,7 +1898,7 @@ export function onload3 () { } if (data.error) { if (data.error.indexOf('1146') !== -1 || data.error.indexOf('1046') !== -1) { - data.error = PMA_messages.strServerLogError; + data.error = messages.strServerLogError; } $('#queryAnalyzerDialog').find('div.placeHolder').html('
' + data.error + '
'); return; @@ -2016,7 +1908,7 @@ export function onload3 () { $('#queryAnalyzerDialog').find('div.placeHolder') .html('
'); - var explain = '' + PMA_messages.strExplainOutput + ' ' + $('#explain_docu').html(); + var explain = '' + messages.strExplainOutput + ' ' + $('#explain_docu').html(); if (data.explain.length > 1) { explain += ' ('; for (i = 0; i < data.explain.length; i++) { @@ -2047,7 +1939,7 @@ export function onload3 () { explain += '
'; } - explain += '

' + PMA_messages.strAffectedRows + ' ' + data.affectedRows; + explain += '

' + messages.strAffectedRows + ' ' + data.affectedRows; $('#queryAnalyzerDialog').find('div.placeHolder td.explain').append(explain); @@ -2059,7 +1951,7 @@ export function onload3 () { if (data.profiling) { var chartData = []; - var numberTable = ''; + var numberTable = '
' + PMA_messages.strStatus + '' + PMA_messages.strTime + '
'; var duration; var otherTime = 0; @@ -2083,15 +1975,15 @@ export function onload3 () { } if (otherTime > 0) { - chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]); + chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + messages.strOther, otherTime]); } - numberTable += ''; + numberTable += ''; numberTable += '
' + messages.strStatus + '' + messages.strTime + '
' + PMA_messages.strTotalTime + '' + PMA_prettyProfilingNum(totalTime, 2) + '
' + messages.strTotalTime + '' + PMA_prettyProfilingNum(totalTime, 2) + '
'; $('#queryAnalyzerDialog').find('div.placeHolder td.chart').append( - '' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + ' ' + - '(' + PMA_messages.strTable + ', ' + PMA_messages.strChart + ')
' + + '' + messages.strProfilingResults + ' ' + $('#profiling_docu').html() + ' ' + + '(' + messages.strTable + ', ' + messages.strChart + ')
' + numberTable + '

'); $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showNums"]').on('click', function () { @@ -2106,7 +1998,7 @@ export function onload3 () { return false; }); - profilingChart = PMA_createProfilingChart( + profilingChart = createProfilingChart( 'queryProfiling', chartData ); @@ -2146,13 +2038,13 @@ export function onload4 () { function serverResponseError () { var btns = {}; - btns[PMA_messages.strReloadPage] = function () { + btns[messages.strReloadPage] = function () { window.location.reload(); }; - $('#emptyDialog').dialog({ title: PMA_messages.strRefreshFailed }); + $('#emptyDialog').dialog({ title: messages.strRefreshFailed }); $('#emptyDialog').html( PMA_getImage('s_attention') + - PMA_messages.strInvalidResponseExplanation + messages.strInvalidResponseExplanation ); $('#emptyDialog').dialog({ buttons: btns }); } diff --git a/js/src/server_status_queries.js b/js/src/server_status_queries.js index f5b151dd96..44f0eca50d 100644 --- a/js/src/server_status_queries.js +++ b/js/src/server_status_queries.js @@ -4,8 +4,8 @@ * Module import */ import { createProfilingChart } from './functions/chart'; -import { jQuery as $ } from './utils/JqueryExtended'; -import { initTableSorter } from './server_status_sorter'; +import { $ } from './utils/JqueryExtended'; +import { initTableSorter } from './functions/Server/SeverStatusSorter'; /** * @package PhpMyAdmin diff --git a/js/src/server_status_sorter.js b/js/src/server_status_sorter.js index 6171a83f06..503a79535d 100644 --- a/js/src/server_status_sorter.js +++ b/js/src/server_status_sorter.js @@ -1,25 +1,11 @@ -import { PMA_Messages as PMA_messages } from './variables/export_variables'; -// TODO: tablesorter shouldn't sort already sorted columns -export function initTableSorter (tabid) { - var $table; - var opts; - switch (tabid) { - case 'statustabs_queries': - $table = $('#serverstatusqueriesdetails'); - opts = { - sortList: [[3, 1]], - headers: { - // 1: { sorter: 'fancyNumber' }, - // 2: { sorter: 'fancyNumber' } - } - }; - break; - } - $table.tablesorter(opts); - $table.find('tr:first th') - .append('
') - .addClass('header'); -} +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Module import + */ +import { $ } from './utils/JqueryExtended'; +import 'tablesorter'; +import { PMA_Messages as messages } from './variables/export_variables'; $(function () { $.tablesorter.addParser({ @@ -29,8 +15,8 @@ $(function () { }, format: function (s) { var num = $.tablesorter.formatFloat( - s.replace(PMA_messages.strThousandsSeparator, '') - .replace(PMA_messages.strDecimalSeparator, '.') + s.replace(messages.strThousandsSeparator, '') + .replace(messages.strDecimalSeparator, '.') ); var factor = 1; diff --git a/js/src/server_user_groups.js b/js/src/server_user_groups.js index 1c0dec0758..bdc33b2c77 100644 --- a/js/src/server_user_groups.js +++ b/js/src/server_user_groups.js @@ -3,7 +3,7 @@ /** * Module import */ -import { PMA_Messages as PMA_messages } from './variables/export_variables'; +import { PMA_Messages as messages } from './variables/export_variables'; import { PMA_sprintf } from './utils/sprintf'; import { escapeHtml } from './utils/Sanitise'; @@ -25,6 +25,7 @@ function teardownServerUserGroups () { */ function onloadServerUserGroups () { // update the checkall checkbox on Edit user group page + // console.log($('input.checkall:checkbox:enabled')); $(checkboxes_sel).trigger('change'); $(document).on('click', 'a.deleteUserGroup.ajax', function (event) { @@ -32,22 +33,22 @@ function onloadServerUserGroups () { var $link = $(this); var groupName = $link.parents('tr').find('td:first').text(); var buttonOptions = {}; - buttonOptions[PMA_messages.strGo] = function () { + buttonOptions[messages.strGo] = function () { $(this).dialog('close'); $link.removeClass('ajax').trigger('click'); }; - buttonOptions[PMA_messages.strClose] = function () { + buttonOptions[messages.strClose] = function () { $(this).dialog('close'); }; $('
') .attr('id', 'confirmUserGroupDeleteDialog') - .append(PMA_sprintf(PMA_messages.strDropUserGroupWarning, escapeHtml(groupName))) + .append(PMA_sprintf(messages.strDropUserGroupWarning, escapeHtml(groupName))) .dialog({ width: 300, minWidth: 200, modal: true, buttons: buttonOptions, - title: PMA_messages.strConfirm, + title: messages.strConfirm, close: function () { $(this).remove(); } diff --git a/js/src/sql.js b/js/src/sql.js new file mode 100644 index 0000000000..962c33f0bf --- /dev/null +++ b/js/src/sql.js @@ -0,0 +1,893 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +import { $ } from './utils/extend_jquery'; +import './plugins/jquery/jquery.uitablefilter'; +import { PMA_Messages as PMA_messages } from './variables/export_variables'; +import { setShowThisQuery, PMA_autosaveSQL, PMA_autosaveSQLSort, PMA_showThisQuery } from './functions/Sql'; +import { isStorageSupported } from './functions/config'; +import { PMA_sprintf } from './utils/sprintf'; +import { escapeHtml } from './utils/Sanitise'; +import PMA_commonParams from './variables/common_params'; +import { PMA_ajaxShowMessage, PMA_ajaxRemoveMessage } from './utils/show_ajax_messages'; +import { initStickyColumns, rearrangeStickyColumns, handleStickyColumns } from './functions/Grid/StickyColumns'; +import { PMA_makegrid } from './utils/makegrid'; +import { AJAX } from './ajax'; + +/** + * @fileoverview functions used wherever an sql query form is used + * + * @requires jQuery + * @requires js/functions.js + * + */ + +var $data_a; + +/** + * Unbind all event handlers before tearing down a page + */ +export function teardown1 () { + $(document).off('click', 'a.delete_row.ajax'); + $(document).off('submit', '.bookmarkQueryForm'); + $('input#bkm_label').off('keyup'); + $(document).off('makegrid', '.sqlqueryresults'); + $(document).off('stickycolumns', '.sqlqueryresults'); + $('#togglequerybox').off('click'); + $(document).off('click', '#button_submit_query'); + $(document).off('change', '#id_bookmark'); + $('input[name=\'bookmark_variable\']').off('keypress'); + $(document).off('submit', '#sqlqueryform.ajax'); + $(document).off('click', 'input[name=navig].ajax'); + $(document).off('submit', 'form[name=\'displayOptionsForm\'].ajax'); + $(document).off('mouseenter', 'th.column_heading.pointer'); + $(document).off('mouseleave', 'th.column_heading.pointer'); + $(document).off('click', 'th.column_heading.marker'); + $(window).off('scroll'); + $(document).off('keyup', '.filter_rows'); + $(document).off('click', '#printView'); + if (codemirror_editor) { + codemirror_editor.off('change'); + } else { + $('#sqlquery').off('input propertychange'); + } + $('body').off('click', '.navigation .showAllRows'); + $('body').off('click', 'a.browse_foreign'); + $('body').off('click', '#simulate_dml'); + $('body').off('keyup', '#sqlqueryform'); + $('body').off('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]'); +} + +/** + * @description

Ajax scripts for sql and browse pages

+ * + * Actions ajaxified here: + *
    + *
  • Retrieve results of an SQL query
  • + *
  • Paginate the results table
  • + *
  • Sort the results table
  • + *
  • Change table according to display options
  • + *
  • Grid editing of data
  • + *
  • Saving a bookmark
  • + *
+ * + * @name document.ready + * @memberOf jQuery + */ +export function onload1 () { + if (codemirror_editor || document.sqlform) { + setShowThisQuery(); + } + $(function () { + if (codemirror_editor) { + codemirror_editor.on('change', function () { + PMA_autosaveSQL(codemirror_editor.getValue()); + }); + } else { + $('#sqlquery').on('input propertychange', function () { + PMA_autosaveSQL($('#sqlquery').val()); + }); + // Save sql query with sort + if ($('#RememberSorting') !== undefined && $('#RememberSorting').is(':checked')) { + $('select[name="sql_query"]').on('change', function () { + PMA_autosaveSQLSort($('select[name="sql_query"]').val()); + }); + } else { + if (isStorageSupported('localStorage') && window.localStorage.auto_saved_sql_sort !== undefined) { + window.localStorage.removeItem('auto_saved_sql_sort'); + } else { + Cookies.set('auto_saved_sql_sort', ''); + } + } + // If sql query with sort for current table is stored, change sort by key select value + var sortStoredQuery = (isStorageSupported('localStorage') && typeof window.localStorage.auto_saved_sql_sort !== 'undefined') ? window.localStorage.auto_saved_sql_sort : Cookies.get('auto_saved_sql_sort'); + if (typeof sortStoredQuery !== 'undefined' && sortStoredQuery !== $('select[name="sql_query"]').val() && $('select[name="sql_query"] option[value="' + sortStoredQuery + '"]').length !== 0) { + $('select[name="sql_query"]').val(sortStoredQuery).trigger('change'); + } + } + }); + + // Delete row from SQL results + $(document).on('click', 'a.delete_row.ajax', function (e) { + e.preventDefault(); + var question = PMA_sprintf(PMA_messages.strDoYouReally, escapeHtml($(this).closest('td').find('div').text())); + var $link = $(this); + $link.PMA_confirm(question, $link.attr('href'), function (url) { + $msgbox = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + var params = 'ajax_request=1' + argsep + 'is_js_confirmed=1'; + var postData = $link.getPostData(); + if (postData) { + params += argsep + postData; + } + $.post(url, params, function (data) { + if (data.success) { + PMA_ajaxShowMessage(data.message); + $link.closest('tr').remove(); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + }); + + // Ajaxification for 'Bookmark this SQL query' + $(document).on('submit', '.bookmarkQueryForm', function (e) { + e.preventDefault(); + PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($(this).attr('action'), 'ajax_request=1' + argsep + $(this).serialize(), function (data) { + if (data.success) { + PMA_ajaxShowMessage(data.message); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); + }); + + /* Hides the bookmarkoptions checkboxes when the bookmark label is empty */ + $('input#bkm_label').on('keyup', function () { + $('input#id_bkm_all_users, input#id_bkm_replace') + .parent() + .toggle($(this).val().length > 0); + }).trigger('keyup'); + + /** + * Attach Event Handler for 'Copy to clipbpard + */ + $(document).on('click', '#copyToClipBoard', function (event) { + event.preventDefault(); + + var textArea = document.createElement('textarea'); + + // + // *** This styling is an extra step which is likely not required. *** + // + // Why is it here? To ensure: + // 1. the element is able to have focus and selection. + // 2. if element was to flash render it has minimal visual impact. + // 3. less flakyness with selection and copying which **might** occur if + // the textarea element is not visible. + // + // The likelihood is the element won't even render, not even a flash, + // so some of these are just precautions. However in IE the element + // is visible whilst the popup box asking the user for permission for + // the web page to copy to the clipboard. + // + + // Place in top-left corner of screen regardless of scroll position. + textArea.style.position = 'fixed'; + textArea.style.top = 0; + textArea.style.left = 0; + + // Ensure it has a small width and height. Setting to 1px / 1em + // doesn't work as this gives a negative w/h on some browsers. + textArea.style.width = '2em'; + textArea.style.height = '2em'; + + // We don't need padding, reducing the size if it does flash render. + textArea.style.padding = 0; + + // Clean up any borders. + textArea.style.border = 'none'; + textArea.style.outline = 'none'; + textArea.style.boxShadow = 'none'; + + // Avoid flash of white box if rendered for any reason. + textArea.style.background = 'transparent'; + + textArea.value = ''; + + $('#serverinfo a').each(function () { + textArea.value += $(this).text().split(':')[1].trim() + '/'; + }); + textArea.value += '\t\t' + window.location.href; + textArea.value += '\n'; + $('.success').each(function () { + textArea.value += $(this).text() + '\n\n'; + }); + + $('.sql pre').each(function () { + textArea.value += $(this).text() + '\n\n'; + }); + + $('.table_results .column_heading a').each(function () { + // Don't copy ordering number text within tag + textArea.value += $(this).clone().find('small').remove().end().text() + '\t'; + }); + + textArea.value += '\n'; + $('.table_results tbody tr').each(function () { + $(this).find('.data span').each(function () { + textArea.value += $(this).text() + '\t'; + }); + textArea.value += '\n'; + }); + + document.body.appendChild(textArea); + + textArea.select(); + + try { + document.execCommand('copy'); + } catch (err) { + alert('Sorry! Unable to copy'); + } + + document.body.removeChild(textArea); + }); // end of Copy to Clipboard action + + /** + * Attach Event Handler for 'Print' link + */ + $(document).on('click', '#printView', function (event) { + event.preventDefault(); + + // Take to preview mode + printPreview(); + }); // end of 'Print' action + + /** + * Attach the {@link makegrid} function to a custom event, which will be + * triggered manually everytime the table of results is reloaded + * @memberOf jQuery + */ + $(document).on('makegrid', '.sqlqueryresults', function () { + $('.table_results').each(function () { + PMA_makegrid(this); + }); + }); + + /* + * Attach a custom event for sticky column headings which will be + * triggered manually everytime the table of results is reloaded + * @memberOf jQuery + */ + $(document).on('stickycolumns', '.sqlqueryresults', function () { + $('.sticky_columns').remove(); + $('.table_results').each(function () { + var $table_results = $(this); + // add sticky columns div + var $stick_columns = initStickyColumns($table_results); + rearrangeStickyColumns($stick_columns, $table_results); + // adjust sticky columns on scroll + $(window).on('scroll', function () { + handleStickyColumns($stick_columns, $table_results); + }); + }); + }); + + /** + * Append the "Show/Hide query box" message to the query input form + * + * @memberOf jQuery + * @name appendToggleSpan + */ + // do not add this link more than once + if (! $('#sqlqueryform').find('a').is('#togglequerybox')) { + $('') + .html(PMA_messages.strHideQueryBox) + .appendTo('#sqlqueryform') + // initially hidden because at this point, nothing else + // appears under the link + .hide(); + + // Attach the toggling of the query box visibility to a click + $('#togglequerybox').bind('click', function () { + var $link = $(this); + $link.siblings().slideToggle('fast'); + if ($link.text() === PMA_messages.strHideQueryBox) { + $link.text(PMA_messages.strShowQueryBox); + // cheap trick to add a spacer between the menu tabs + // and "Show query box"; feel free to improve! + $('#togglequerybox_spacer').remove(); + $link.before('
'); + } else { + $link.text(PMA_messages.strHideQueryBox); + } + // avoid default click action + return false; + }); + } + + + /** + * Event handler for sqlqueryform.ajax button_submit_query + * + * @memberOf jQuery + */ + $(document).on('click', '#button_submit_query', function (event) { + $('.success,.error').hide(); + // hide already existing error or success message + var $form = $(this).closest('form'); + // the Go button related to query submission was clicked, + // instead of the one related to Bookmarks, so empty the + // id_bookmark selector to avoid misinterpretation in + // import.php about what needs to be done + $form.find('select[name=id_bookmark]').val(''); + // let normal event propagation happen + if (isStorageSupported('localStorage')) { + window.localStorage.removeItem('auto_saved_sql'); + } else { + Cookies.set('auto_saved_sql', ''); + } + var isShowQuery = $('input[name="show_query"').is(':checked'); + if (isShowQuery) { + window.localStorage.show_this_query = '1'; + var db = $('input[name="db"]').val(); + var table = $('input[name="table"]').val(); + var query; + if (codemirror_editor) { + query = codemirror_editor.getValue(); + } else { + query = $('#sqlquery').val(); + } + PMA_showThisQuery(db, table, query); + } else { + window.localStorage.show_this_query = '0'; + } + }); + + /** + * Event handler to show appropiate number of variable boxes + * based on the bookmarked query + */ + $(document).on('change', '#id_bookmark', function (event) { + var varCount = $(this).find('option:selected').data('varcount'); + if (typeof varCount === 'undefined') { + varCount = 0; + } + + var $varDiv = $('#bookmark_variables'); + $varDiv.empty(); + for (var i = 1; i <= varCount; i++) { + $varDiv.append($('')); + $varDiv.append($('')); + } + + if (varCount === 0) { + $varDiv.parent('.formelement').hide(); + } else { + $varDiv.parent('.formelement').show(); + } + }); + + /** + * Event handler for hitting enter on sqlqueryform bookmark_variable + * (the Variable textfield in Bookmarked SQL query section) + * + * @memberOf jQuery + */ + $('input[name=bookmark_variable]').on('keypress', function (event) { + // force the 'Enter Key' to implicitly click the #button_submit_bookmark + var keycode = (event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode)); + if (keycode === 13) { // keycode for enter key + // When you press enter in the sqlqueryform, which + // has 2 submit buttons, the default is to run the + // #button_submit_query, because of the tabindex + // attribute. + // This submits #button_submit_bookmark instead, + // because when you are in the Bookmarked SQL query + // section and hit enter, you expect it to do the + // same action as the Go button in that section. + $('#button_submit_bookmark').trigger('click'); + return false; + } else { + return true; + } + }); + + /** + * Ajax Event handler for 'SQL Query Submit' + * + * @see PMA_ajaxShowMessage() + * @memberOf jQuery + * @name sqlqueryform_submit + */ + $(document).on('submit', '#sqlqueryform.ajax', function (event) { + event.preventDefault(); + + var $form = $(this); + if (codemirror_editor) { + $form[0].elements.sql_query.value = codemirror_editor.getValue(); + } + if (! checkSqlQuery($form[0])) { + return false; + } + + // remove any div containing a previous error message + $('div.error').remove(); + + var $msgbox = PMA_ajaxShowMessage(); + var $sqlqueryresultsouter = $('#sqlqueryresultsouter'); + + PMA_prepareForAjaxRequest($form); + + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_page_request=true', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + // success happens if the query returns rows or not + + // show a message that stays on screen + if (typeof data.action_bookmark !== 'undefined') { + // view only + if ('1' === data.action_bookmark) { + $('#sqlquery').text(data.sql_query); + // send to codemirror if possible + setQuery(data.sql_query); + } + // delete + if ('2' === data.action_bookmark) { + $('#id_bookmark option[value=\'' + data.id_bookmark + '\']').remove(); + // if there are no bookmarked queries now (only the empty option), + // remove the bookmark section + if ($('#id_bookmark option').length === 1) { + $('#fieldsetBookmarkOptions').hide(); + $('#fieldsetBookmarkOptionsFooter').hide(); + } + } + } + $sqlqueryresultsouter + .show() + .html(data.message); + PMA_highlightSQL($sqlqueryresultsouter); + + if (data._menu) { + if (history && history.pushState) { + history.replaceState({ + menu : data._menu + }, + null + ); + AJAX.handleMenu.replace(data._menu); + } else { + PMA_MicroHistory.menus.replace(data._menu); + PMA_MicroHistory.menus.add(data._menuHash, data._menu); + } + } else if (data._menuHash) { + if (! (history && history.pushState)) { + PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash)); + } + } + + if (data._params) { + PMA_commonParams.setAll(data._params); + } + + if (typeof data.ajax_reload !== 'undefined') { + if (data.ajax_reload.reload) { + if (data.ajax_reload.table_name) { + PMA_commonParams.set('table', data.ajax_reload.table_name); + PMA_commonActions.refreshMain(); + } else { + PMA_reloadNavigation(); + } + } + } else if (typeof data.reload !== 'undefined') { + // this happens if a USE or DROP command was typed + PMA_commonActions.setDb(data.db); + var url; + if (data.db) { + if (data.table) { + url = 'table_sql.php'; + } else { + url = 'db_sql.php'; + } + } else { + url = 'server_sql.php'; + } + PMA_commonActions.refreshMain(url, function () { + $('#sqlqueryresultsouter') + .show() + .html(data.message); + PMA_highlightSQL($('#sqlqueryresultsouter')); + }); + } + + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); + $('#togglequerybox').show(); + PMA_init_slider(); + + if (typeof data.action_bookmark === 'undefined') { + if ($('#sqlqueryform input[name="retain_query_box"]').is(':checked') !== true) { + if ($('#togglequerybox').siblings(':visible').length > 0) { + $('#togglequerybox').trigger('click'); + } + } + } + } else if (typeof data !== 'undefined' && data.success === false) { + // show an error message that stays on screen + $sqlqueryresultsouter + .show() + .html(data.error); + } + PMA_ajaxRemoveMessage($msgbox); + }); // end $.post() + }); // end SQL Query submit + + /** + * Ajax Event handler for the display options + * @memberOf jQuery + * @name displayOptionsForm_submit + */ + $(document).on('submit', 'form[name=\'displayOptionsForm\'].ajax', function (event) { + event.preventDefault(); + + var $form = $(this); + + var $msgbox = PMA_ajaxShowMessage(); + var argsep = PMA_commonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_request=true', function (data) { + PMA_ajaxRemoveMessage($msgbox); + var $sqlqueryresults = $form.parents('.sqlqueryresults'); + $sqlqueryresults + .html(data.message) + .trigger('makegrid') + .trigger('stickycolumns'); + PMA_init_slider(); + PMA_highlightSQL($sqlqueryresults); + }); // end $.post() + }); // end displayOptionsForm handler + + // Filter row handling. --STARTS-- + $(document).on('keyup', '.filter_rows', function () { + var unique_id = $(this).data('for'); + var $target_table = $('.table_results[data-uniqueId=\'' + unique_id + '\']'); + var $header_cells = $target_table.find('th[data-column]'); + var target_columns = Array(); + // To handle colspan=4, in case of edit,copy etc options. + var dummy_th = ($('.edit_row_anchor').length !== 0 ? + '' + : ''); + // Selecting columns that will be considered for filtering and searching. + $header_cells.each(function () { + target_columns.push($.trim($(this).text())); + }); + + var phrase = $(this).val(); + // Set same value to both Filter rows fields. + $('.filter_rows[data-for=\'' + unique_id + '\']').not(this).val(phrase); + // Handle colspan. + $target_table.find('thead > tr').prepend(dummy_th); + $.uiTableFilter($target_table, phrase, target_columns); + $target_table.find('th.dummy_th').remove(); + }); + // Filter row handling. --ENDS-- + + // Prompt to confirm on Show All + $('body').on('click', '.navigation .showAllRows', function (e) { + e.preventDefault(); + var $form = $(this).parents('form'); + + if (! $(this).is(':checked')) { // already showing all rows + submitShowAllForm(); + } else { + $form.PMA_confirm(PMA_messages.strShowAllRowsWarning, $form.attr('action'), function (url) { + submitShowAllForm(); + }); + } + + function submitShowAllForm () { + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + }); + + $('body').on('keyup', '#sqlqueryform', function () { + PMA_handleSimulateQueryButton(); + }); + + /** + * Ajax event handler for 'Simulate DML'. + */ + $('body').on('click', '#simulate_dml', function () { + var $form = $('#sqlqueryform'); + var query = ''; + var delimiter = $('#id_sql_delimiter').val(); + var db_name = $form.find('input[name="db"]').val(); + + if (codemirror_editor) { + query = codemirror_editor.getValue(); + } else { + query = $('#sqlquery').val(); + } + + if (query.length === 0) { + alert(PMA_messages.strFormEmpty); + $('#sqlquery').focus(); + return false; + } + + var $msgbox = PMA_ajaxShowMessage(); + $.ajax({ + type: 'POST', + url: $form.attr('action'), + data: { + server: PMA_commonParams.get('server'), + db: db_name, + ajax_request: '1', + simulate_dml: '1', + sql_query: query, + sql_delimiter: delimiter + }, + success: function (response) { + PMA_ajaxRemoveMessage($msgbox); + if (response.success) { + var dialog_content = '
'; + if (response.sql_data) { + var len = response.sql_data.length; + for (var i = 0; i < len; i++) { + dialog_content += '' + PMA_messages.strSQLQuery + + '' + response.sql_data[i].sql_query + + PMA_messages.strMatchedRows + + ' ' + response.sql_data[i].matched_rows + '
'; + if (i < len - 1) { + dialog_content += '
'; + } + } + } else { + dialog_content += response.message; + } + dialog_content += '
'; + var $dialog_content = $(dialog_content); + var button_options = {}; + button_options[PMA_messages.strClose] = function () { + $(this).dialog('close'); + }; + var $response_dialog = $('
').append($dialog_content).dialog({ + minWidth: 540, + maxHeight: 400, + modal: true, + buttons: button_options, + title: PMA_messages.strSimulateDML, + open: function () { + PMA_highlightSQL($(this)); + }, + close: function () { + $(this).remove(); + } + }); + } else { + PMA_ajaxShowMessage(response.error); + } + }, + error: function (response) { + PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest); + } + }); + }); + + /** + * Handles multi submits of results browsing page such as edit, delete and export + */ + $('body').on('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.closest('form'); + var argsep = PMA_commonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + PMA_ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); +} // end $() + +/** + * Starting from some th, change the class of all td under it. + * If isAddClass is specified, it will be used to determine whether to add or remove the class. + */ +function PMA_changeClassForColumn ($this_th, newclass, isAddClass) { + // index 0 is the th containing the big T + var th_index = $this_th.index(); + var has_big_t = $this_th.closest('tr').children(':first').hasClass('column_action'); + // .eq() is zero-based + if (has_big_t) { + th_index--; + } + var $table = $this_th.parents('.table_results'); + if (! $table.length) { + $table = $this_th.parents('table').siblings('.table_results'); + } + var $tds = $table.find('tbody tr').find('td.data:eq(' + th_index + ')'); + if (isAddClass === undefined) { + $tds.toggleClass(newclass); + } else { + $tds.toggleClass(newclass, isAddClass); + } +} + +/** + * Handles browse foreign values modal dialog + * + * @param object $this_a reference to the browse foreign value link + */ +function browseForeignDialog ($this_a) { + var formId = '#browse_foreign_form'; + var showAllId = '#foreign_showAll'; + var tableId = '#browse_foreign_table'; + var filterId = '#input_foreign_filter'; + var $dialog = null; + $.get($this_a.attr('href'), { 'ajax_request': true }, function (data) { + // Creates browse foreign value dialog + $dialog = $('
').append(data.message).dialog({ + title: PMA_messages.strBrowseForeignValues, + width: Math.min($(window).width() - 100, 700), + maxHeight: $(window).height() - 100, + dialogClass: 'browse_foreign_modal', + close: function (ev, ui) { + // remove event handlers attached to elements related to dialog + $(tableId).off('click', 'td a.foreign_value'); + $(formId).off('click', showAllId); + $(formId).off('submit'); + // remove dialog itself + $(this).remove(); + }, + modal: true + }); + }).done(function () { + var showAll = false; + $(tableId).on('click', 'td a.foreign_value', function (e) { + e.preventDefault(); + var $input = $this_a.prev('input[type=text]'); + // Check if input exists or get CEdit edit_box + if ($input.length === 0) { + $input = $this_a.closest('.edit_area').prev('.edit_box'); + } + // Set selected value as input value + $input.val($(this).data('key')); + $dialog.dialog('close'); + }); + $(formId).on('click', showAllId, function () { + showAll = true; + }); + $(formId).on('submit', function (e) { + e.preventDefault(); + // if filter value is not equal to old value + // then reset page number to 1 + if ($(filterId).val() !== $(filterId).data('old')) { + $(formId).find('select[name=pos]').val('0'); + } + var postParams = $(this).serializeArray(); + // if showAll button was clicked to submit form then + // add showAll button parameter to form + if (showAll) { + postParams.push({ + name: $(showAllId).attr('name'), + value: $(showAllId).val() + }); + } + // updates values in dialog + $.post($(this).attr('action') + '?ajax_request=1', postParams, function (data) { + var $obj = $('
').html(data.message); + $(formId).html($obj.find(formId).html()); + $(tableId).html($obj.find(tableId).html()); + }); + showAll = false; + }); + }); +} + +function checkSavedQuery () { + if (isStorageSupported('localStorage') && window.localStorage.auto_saved_sql !== undefined) { + PMA_ajaxShowMessage(PMA_messages.strPreviousSaveQuery); + } +} + +export function onload2 () { + $('body').on('click', 'a.browse_foreign', function (e) { + e.preventDefault(); + browseForeignDialog($(this)); + }); + + /** + * vertical column highlighting in horizontal mode when hovering over the column header + */ + $(document).on('mouseenter', 'th.column_heading.pointer', function (e) { + PMA_changeClassForColumn($(this), 'hover', true); + }); + $(document).on('mouseleave', 'th.column_heading.pointer', function (e) { + PMA_changeClassForColumn($(this), 'hover', false); + }); + + /** + * vertical column marking in horizontal mode when clicking the column header + */ + $(document).on('click', 'th.column_heading.marker', function () { + PMA_changeClassForColumn($(this), 'marked'); + }); + + /** + * create resizable table + */ + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); + + /** + * Check if there is any saved query + */ + if (codemirror_editor || document.sqlform) { + checkSavedQuery(); + } +} + +/* + * Profiling Chart + */ +function makeProfilingChart () { + if ($('#profilingchart').length === 0 || + $('#profilingchart').html().length !== 0 || + !$.jqplot || !$.jqplot.Highlighter || !$.jqplot.PieRenderer + ) { + return; + } + + var data = []; + $.each(JSON.parse($('#profilingChartData').html()), function (key, value) { + data.push([key, parseFloat(value)]); + }); + + // Remove chart and data divs contents + $('#profilingchart').html('').show(); + $('#profilingChartData').html(''); + + PMA_createProfilingChart('profilingchart', data); +} + +/* + * initialize profiling data tables + */ +function initProfilingTables () { + if (!$.tablesorter) { + return; + } + + $('#profiletable').tablesorter({ + widgets: ['zebra'], + sortList: [[0, 0]], + textExtraction: function (node) { + if (node.children.length > 0) { + return node.children[0].innerHTML; + } else { + return node.innerHTML; + } + } + }); + + $('#profilesummarytable').tablesorter({ + widgets: ['zebra'], + sortList: [[1, 1]], + textExtraction: function (node) { + if (node.children.length > 0) { + return node.children[0].innerHTML; + } else { + return node.innerHTML; + } + } + }); +} + +export function onload4 () { + makeProfilingChart(); + initProfilingTables(); +} diff --git a/js/src/utils/DateTime.js b/js/src/utils/DateTime.js new file mode 100644 index 0000000000..ef74c685d6 --- /dev/null +++ b/js/src/utils/DateTime.js @@ -0,0 +1,147 @@ +import { PMA_Messages as PMA_messages } from '../variables/export_variables'; +import { PMA_tooltip } from './show_ajax_messages'; +/* + * Adds a date/time picker to an element + * + * @param object $this_element a jQuery object pointing to the element + */ +export function PMA_addDatepicker ($this_element, type, options) { + var showTimepicker = true; + if (type === 'date') { + showTimepicker = false; + } + + var defaultOptions = { + showOn: 'button', + buttonImage: themeCalendarImage, // defined in js/messages.php + buttonImageOnly: true, + stepMinutes: 1, + stepHours: 1, + showSecond: true, + showMillisec: true, + showMicrosec: true, + showTimepicker: showTimepicker, + showButtonPanel: false, + dateFormat: 'yy-mm-dd', // yy means year with four digits + timeFormat: 'HH:mm:ss.lc', + constrainInput: false, + altFieldTimeOnly: false, + showAnim: '', + beforeShow: function (input, inst) { + // Remember that we came from the datepicker; this is used + // in tbl_change.js by verificationsAfterFieldChange() + $this_element.data('comes_from', 'datepicker'); + if ($(input).closest('.cEdit').length > 0) { + setTimeout(function () { + inst.dpDiv.css({ + top: 0, + left: 0, + position: 'relative' + }); + }, 0); + } + setTimeout(function () { + // Fix wrong timepicker z-index, doesn't work without timeout + $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index')); + // Integrate tooltip text into dialog + var tooltip = $this_element.tooltip('instance'); + if (typeof tooltip !== 'undefined') { + tooltip.disable(); + var $note = $('

'); + $note.text(tooltip.option('content')); + $('div.ui-datepicker').append($note); + } + }, 0); + }, + onSelect: function () { + $this_element.data('datepicker').inline = true; + }, + onClose: function (dateText, dp_inst) { + // The value is no more from the date picker + $this_element.data('comes_from', ''); + if (typeof $this_element.data('datepicker') !== 'undefined') { + $this_element.data('datepicker').inline = false; + } + var tooltip = $this_element.tooltip('instance'); + if (typeof tooltip !== 'undefined') { + tooltip.enable(); + } + } + }; + if (type === 'time') { + $this_element.timepicker($.extend(defaultOptions, options)); + // Add a tip regarding entering MySQL allowed-values for TIME data-type + PMA_tooltip($this_element, 'input', PMA_messages.strMysqlAllowedValuesTipTime); + } else { + $this_element.datetimepicker($.extend(defaultOptions, options)); + } +} + +/** + * Add a date/time picker to each element that needs it + * (only when jquery-ui-timepicker-addon.js is loaded) + */ +function addDateTimePicker () { + if ($.timepicker !== undefined) { + $('input.timefield, input.datefield, input.datetimefield').each(function () { + var decimals = $(this).parent().attr('data-decimals'); + var type = $(this).parent().attr('data-type'); + + var showMillisec = false; + var showMicrosec = false; + var timeFormat = 'HH:mm:ss'; + var hourMax = 23; + // check for decimal places of seconds + if (decimals > 0 && type.indexOf('time') !== -1) { + if (decimals > 3) { + showMillisec = true; + showMicrosec = true; + timeFormat = 'HH:mm:ss.lc'; + } else { + showMillisec = true; + timeFormat = 'HH:mm:ss.l'; + } + } + if (type === 'time') { + hourMax = 99; + } + PMA_addDatepicker($(this), type, { + showMillisec: showMillisec, + showMicrosec: showMicrosec, + timeFormat: timeFormat, + hourMax: hourMax + }); + // Add a tip regarding entering MySQL allowed-values + // for TIME and DATE data-type + if ($(this).hasClass('timefield')) { + PMA_tooltip($(this), 'input', PMA_messages.strMysqlAllowedValuesTipTime); + } else if ($(this).hasClass('datefield')) { + PMA_tooltip($(this), 'input', PMA_messages.strMysqlAllowedValuesTipDate); + } + }); + } +} + +/** + * Toggle the Datetimepicker UI if the date value entered + * by the user in the 'text box' is not going to be accepted + * by the Datetimepicker plugin (but is accepted by MySQL) + */ +export function toggleDatepickerIfInvalid ($td, $input_field) { + // Regex allowed by the Datetimepicker UI + var dtexpDate = new RegExp(['^([0-9]{4})', + '-(((01|03|05|07|08|10|12)-((0[1-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)', + '-((0[1-9])|([1-2][0-9])|30)))$'].join('')); + var dtexpTime = new RegExp(['^(([0-1][0-9])|(2[0-3]))', + ':((0[0-9])|([1-5][0-9]))', + ':((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$'].join('')); + + // If key-ed in Time or Date values are unsupported by the UI, close it + if ($td.attr('data-type') === 'date' && ! dtexpDate.test($input_field.val())) { + $input_field.datepicker('hide'); + } else if ($td.attr('data-type') === 'time' && ! dtexpTime.test($input_field.val())) { + $input_field.datepicker('hide'); + } else { + $input_field.datepicker('show'); + } +} diff --git a/js/src/utils/Sanitise.js b/js/src/utils/Sanitise.js index 4431a6e9f0..a16be9289d 100644 --- a/js/src/utils/Sanitise.js +++ b/js/src/utils/Sanitise.js @@ -48,10 +48,35 @@ function escapeJsString (unsafe) { } } +/** + * decode a string URL_encoded + * + * @param string str + * @return string the URL-decoded string + */ +function PMA_urldecode (str) { + if (typeof str !== 'undefined') { + return decodeURIComponent(str.replace(/\+/g, '%20')); + } +} + +/** + * endecode a string URL_decoded + * + * @param string str + * @return string the URL-encoded string + */ +function PMA_urlencode (str) { + if (typeof str !== 'undefined') { + return encodeURIComponent(str).replace(/\%20/g, '+'); + } +} + /** * Module export */ export { escapeHtml, - escapeJsString + escapeJsString, + PMA_urlencode }; diff --git a/js/src/utils/makegrid.js b/js/src/utils/makegrid.js new file mode 100644 index 0000000000..8d1ce2b1ff --- /dev/null +++ b/js/src/utils/makegrid.js @@ -0,0 +1,2255 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Module import + */ +import { $ } from './extend_jquery'; +import { PMA_Messages as messages } from '../variables/export_variables'; +import { PMA_ajaxShowMessage, PMA_tooltip } from './show_ajax_messages'; +import { rearrangeStickyColumns } from '../functions/Grid/StickyColumns'; +import { PMA_getCellValue } from '../functions/Grid/Cell'; +import { PMA_updateCode } from '../functions/UpdateCode'; +import CommonParams from '../variables/common_params'; +import { getFieldName } from '../functions/Grid/GetFieldName'; +import { AJAX } from '../ajax'; +import { PMA_addDatepicker, toggleDatepickerIfInvalid } from './DateTime'; +import { escapeHtml, PMA_urlencode } from './Sanitise'; +import { confirmLink } from '../functions/Common'; +import { PMA_highlightSQL } from '../utils/sql'; +/** + * Create advanced table (resize, reorder, and show/hide columns; and also grid editing). + * This function is designed mainly for table DOM generated from browsing a table in the database. + * For using this function in other table DOM, you may need to: + * - add "draggable" class in the table header , in order to make it resizable, sortable or hidable + * - have at least one non-"draggable" header in the table DOM for placing column visibility drop-down arrow + * - pass the value "false" for the parameter "enableGridEdit" + * - adjust other parameter value, to select which features that will be enabled + * + * @param t the table DOM element + * @param enableResize Optional, if false, column resizing feature will be disabled + * @param enableReorder Optional, if false, column reordering feature will be disabled + * @param enableVisib Optional, if false, show/hide column feature will be disabled + * @param enableGridEdit Optional, if false, grid editing feature will be disabled + */ +function PMA_makegrid (t, enableResize = true, enableReorder = true, enableVisib = true, enableGridEdit = true) { + var g = { + /** ********* + * Constant + ***********/ + minColWidth: 15, + + + /** ********* + * Variables, assigned with default value, changed later + ***********/ + actionSpan: 5, // number of colspan in Actions header in a table + tableCreateTime: null, // table creation time, used for saving column order and visibility to server, only available in "Browse tab" + + // Column reordering variables + colOrder: [], // array of column order + + // Column visibility variables + colVisib: [], // array of column visibility + showAllColText: '', // string, text for "show all" button under column visibility list + visibleHeadersCount: 0, // number of visible data headers + + // Table hint variables + reorderHint: '', // string, hint for column reordering + sortHint: '', // string, hint for column sorting + markHint: '', // string, hint for column marking + copyHint: '', // string, hint for copy column name + showReorderHint: false, + showSortHint: false, + showMarkHint: false, + + // Grid editing + isCellEditActive: false, // true if current focus is in edit cell + isEditCellTextEditable: false, // true if current edit cell is editable in the text input box (not textarea) + currentEditCell: null, // reference to that currently being edited + cellEditHint: '', // hint shown when doing grid edit + gotoLinkText: '', // "Go to link" text + wasEditedCellNull: false, // true if last value of the edited cell was NULL + maxTruncatedLen: 0, // number of characters that can be displayed in a cell + saveCellsAtOnce: false, // $cfg[saveCellsAtOnce] + isCellEdited: false, // true if at least one cell has been edited + saveCellWarning: '', // string, warning text when user want to leave a page with unsaved edited data + lastXHR : null, // last XHR object used in AJAX request + isSaving: false, // true when currently saving edited data, used to handle double posting caused by pressing ENTER in grid edit text box in Chrome browser + alertNonUnique: '', // string, alert shown when saving edited nonunique table + + // Common hidden inputs + token: null, + server: null, + db: null, + table: null, + + + /** ********** + * Functions + ************/ + + /** + * Start to resize column. Called when clicking on column separator. + * + * @param e event + * @param obj dragged div object + */ + dragStartRsz: function (e, obj) { + var n = $(g.cRsz).find('div').index(obj); // get the index of separator (i.e., column index) + $(obj).addClass('colborder_active'); + g.colRsz = { + x0: e.pageX, + n: n, + obj: obj, + objLeft: $(obj).position().left, + objWidth: $(g.t).find('th.draggable:visible:eq(' + n + ') span').outerWidth() + }; + $(document.body).css('cursor', 'col-resize').noSelect(); + if (g.isCellEditActive) { + g.hideEditCell(); + } + }, + + /** + * Start to reorder column. Called when clicking on table header. + * + * @param e event + * @param obj table header object + */ + dragStartReorder: function (e, obj) { + // prepare the cCpy (column copy) and cPointer (column pointer) from the dragged column + $(g.cCpy).text($(obj).text()); + var objPos = $(obj).position(); + $(g.cCpy).css({ + top: objPos.top + 20, + left: objPos.left, + height: $(obj).height(), + width: $(obj).width() + }); + $(g.cPointer).css({ + top: objPos.top + }); + + // get the column index, zero-based + var n = g.getHeaderIdx(obj); + + g.colReorder = { + x0: e.pageX, + y0: e.pageY, + n: n, + newn: n, + obj: obj, + objTop: objPos.top, + objLeft: objPos.left + }; + + $(document.body).css('cursor', 'move').noSelect(); + if (g.isCellEditActive) { + g.hideEditCell(); + } + }, + + /** + * Handle mousemove event when dragging. + * + * @param e event + */ + dragMove: function (e) { + if (g.colRsz) { + let dx = e.pageX - g.colRsz.x0; + if (g.colRsz.objWidth + dx > g.minColWidth) { + $(g.colRsz.obj).css('left', g.colRsz.objLeft + dx + 'px'); + } + } else if (g.colReorder) { + // dragged column animation + let dx = e.pageX - g.colReorder.x0; + $(g.cCpy) + .css('left', g.colReorder.objLeft + dx) + .show(); + + // pointer animation + var hoveredCol = g.getHoveredCol(e); + if (hoveredCol) { + var newn = g.getHeaderIdx(hoveredCol); + g.colReorder.newn = newn; + if (newn !== g.colReorder.n) { + // show the column pointer in the right place + var colPos = $(hoveredCol).position(); + var newleft = newn < g.colReorder.n ? + colPos.left : + colPos.left + $(hoveredCol).outerWidth(); + $(g.cPointer) + .css({ + left: newleft, + visibility: 'visible' + }); + } else { + // no movement to other column, hide the column pointer + $(g.cPointer).css('visibility', 'hidden'); + } + } + } + }, + + /** + * Stop the dragging action. + * + * @param e event + */ + dragEnd: function (e) { + if (g.colRsz) { + var dx = e.pageX - g.colRsz.x0; + var nw = g.colRsz.objWidth + dx; + if (nw < g.minColWidth) { + nw = g.minColWidth; + } + var n = g.colRsz.n; + // do the resizing + g.resize(n, nw); + + g.reposRsz(); + g.reposDrop(); + g.colRsz = false; + $(g.cRsz).find('div').removeClass('colborder_active'); + rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t)); + } else if (g.colReorder) { + // shift columns + if (g.colReorder.newn !== g.colReorder.n) { + g.shiftCol(g.colReorder.n, g.colReorder.newn); + // assign new position + var objPos = $(g.colReorder.obj).position(); + g.colReorder.objTop = objPos.top; + g.colReorder.objLeft = objPos.left; + g.colReorder.n = g.colReorder.newn; + // send request to server to remember the column order + if (g.tableCreateTime) { + g.sendColPrefs(); + } + g.refreshRestoreButton(); + } + + // animate new column position + $(g.cCpy).stop(true, true) + .animate({ + top: g.colReorder.objTop, + left: g.colReorder.objLeft + }, 'fast') + .fadeOut(); + $(g.cPointer).css('visibility', 'hidden'); + + g.colReorder = false; + rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t)); + } + $(document.body).css('cursor', 'inherit').noSelect(false); + }, + + /** + * Resize column n to new width "nw" + * + * @param n zero-based column index + * @param nw new width of the column in pixel + */ + resize: function (n, nw) { + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:visible:eq(' + n + ') span,' + + 'td:visible:eq(' + (g.actionSpan + n) + ') span') + .css('width', nw); + }); + }, + + /** + * Reposition column resize bars. + */ + reposRsz: function () { + $(g.cRsz).find('div').hide(); + var $firstRowCols = $(g.t).find('tr:first th.draggable:visible'); + var $resizeHandles = $(g.cRsz).find('div').removeClass('condition'); + $(g.t).find('table.pma_table').find('thead th:first').removeClass('before-condition'); + for (var n = 0, l = $firstRowCols.length; n < l; n++) { + var $col = $($firstRowCols[n]); + var colWidth; + if (navigator.userAgent.toLowerCase().indexOf('safari') !== -1) { + colWidth = $col.outerWidth(); + } else { + colWidth = $col.outerWidth(true); + } + $($resizeHandles[n]).css('left', $col.position().left + colWidth) + .show(); + if ($col.hasClass('condition')) { + $($resizeHandles[n]).addClass('condition'); + if (n > 0) { + $($resizeHandles[n - 1]).addClass('condition'); + } + } + } + if ($($resizeHandles[0]).hasClass('condition')) { + $(g.t).find('thead th:first').addClass('before-condition'); + } + $(g.cRsz).css('height', $(g.t).height()); + }, + + /** + * Shift column from index oldn to newn. + * + * @param oldn old zero-based column index + * @param newn new zero-based column index + */ + shiftCol: function (oldn, newn) { + $(g.t).find('tr').each(function () { + if (newn < oldn) { + $(this).find('th.draggable:eq(' + newn + '),' + + 'td:eq(' + (g.actionSpan + newn) + ')') + .before($(this).find('th.draggable:eq(' + oldn + '),' + + 'td:eq(' + (g.actionSpan + oldn) + ')')); + } else { + $(this).find('th.draggable:eq(' + newn + '),' + + 'td:eq(' + (g.actionSpan + newn) + ')') + .after($(this).find('th.draggable:eq(' + oldn + '),' + + 'td:eq(' + (g.actionSpan + oldn) + ')')); + } + }); + // reposition the column resize bars + g.reposRsz(); + + // adjust the column visibility list + if (newn < oldn) { + $(g.cList).find('.lDiv div:eq(' + newn + ')') + .before($(g.cList).find('.lDiv div:eq(' + oldn + ')')); + } else { + $(g.cList).find('.lDiv div:eq(' + newn + ')') + .after($(g.cList).find('.lDiv div:eq(' + oldn + ')')); + } + // adjust the colOrder + var tmp = g.colOrder[oldn]; + g.colOrder.splice(oldn, 1); + g.colOrder.splice(newn, 0, tmp); + // adjust the colVisib + if (g.colVisib.length > 0) { + tmp = g.colVisib[oldn]; + g.colVisib.splice(oldn, 1); + g.colVisib.splice(newn, 0, tmp); + } + }, + + /** + * Find currently hovered table column's header (excluding actions column). + * + * @param e event + * @return the hovered column's th object or undefined if no hovered column found. + */ + getHoveredCol: function (e) { + var hoveredCol; + var $headers = $(g.t).find('th.draggable:visible'); + $headers.each(function () { + var left = $(this).offset().left; + var right = left + $(this).outerWidth(); + if (left <= e.pageX && e.pageX <= right) { + hoveredCol = this; + } + }); + return hoveredCol; + }, + + /** + * Get a zero-based index from a tag in a table. + * + * @param obj table header object + * @return zero-based index of the specified table header in the set of table headers (visible or not) + */ + getHeaderIdx: function (obj) { + return $(obj).parents('tr').find('th.draggable').index(obj); + }, + + /** + * Reposition the columns back to normal order. + */ + restoreColOrder: function () { + // use insertion sort, since we already have shiftCol function + for (var i = 1; i < g.colOrder.length; i++) { + var x = g.colOrder[i]; + var j = i - 1; + while (j >= 0 && x < g.colOrder[j]) { + j--; + } + if (j !== i - 1) { + g.shiftCol(i, j + 1); + } + } + if (g.tableCreateTime) { + // send request to server to remember the column order + g.sendColPrefs(); + } + g.refreshRestoreButton(); + }, + + /** + * Send column preferences (column order and visibility) to the server. + */ + sendColPrefs: function () { + if ($(g.t).is('.ajax')) { // only send preferences if ajax class + var postParams = { + ajax_request: true, + db: g.db, + table: g.table, + token: g.token, + server: g.server, + set_col_prefs: true, + table_create_time: g.tableCreateTime + }; + if (g.colOrder.length > 0) { + $.extend(postParams, { col_order: g.colOrder.toString() }); + } + if (g.colVisib.length > 0) { + $.extend(postParams, { col_visib: g.colVisib.toString() }); + } + $.post('sql.php', postParams, function (data) { + if (data.success !== true) { + var $tempDiv = $(document.createElement('div')); + $tempDiv.html(data.error); + $tempDiv.addClass('error'); + PMA_ajaxShowMessage($tempDiv, false); + } + }); + } + }, + + /** + * Refresh restore button state. + * Make restore button disabled if the table is similar with initial state. + */ + refreshRestoreButton: function () { + // check if table state is as initial state + var isInitial = true; + for (var i = 0; i < g.colOrder.length; i++) { + if (g.colOrder[i] !== i) { + isInitial = false; + break; + } + } + // check if only one visible column left + var isOneColumn = g.visibleHeadersCount === 1; + // enable or disable restore button + if (isInitial || isOneColumn) { + $(g.o).find('div.restore_column').hide(); + } else { + $(g.o).find('div.restore_column').show(); + } + }, + + /** + * Update current hint using the boolean values (showReorderHint, showSortHint, etc.). + * + */ + updateHint: function () { + var text = ''; + if (!g.colRsz && !g.colReorder) { // if not resizing or dragging + if (g.visibleHeadersCount > 1) { + g.showReorderHint = true; + } + if ($(t).find('th.marker').length > 0) { + g.showMarkHint = true; + } + if (g.showSortHint && g.sortHint) { + text += text.length > 0 ? '
' : ''; + text += '- ' + g.sortHint; + } + if (g.showMultiSortHint && g.strMultiSortHint) { + text += text.length > 0 ? '
' : ''; + text += '- ' + g.strMultiSortHint; + } + if (g.showMarkHint && + g.markHint && + ! g.showSortHint && // we do not show mark hint, when sort hint is shown + g.showReorderHint && + g.reorderHint + ) { + text += text.length > 0 ? '
' : ''; + text += '- ' + g.reorderHint; + text += text.length > 0 ? '
' : ''; + text += '- ' + g.markHint; + text += text.length > 0 ? '
' : ''; + text += '- ' + g.copyHint; + } + } + return text; + }, + + /** + * Toggle column's visibility. + * After calling this function and it returns true, afterToggleCol() must be called. + * + * @return boolean True if the column is toggled successfully. + */ + toggleCol: function (n) { + if (g.colVisib[n]) { + // can hide if more than one column is visible + if (g.visibleHeadersCount > 1) { + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .hide(); + }); + g.colVisib[n] = 0; + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', false); + } else { + // cannot hide, force the checkbox to stay checked + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true); + return false; + } + } else { // column n is not visible + $(g.t).find('tr').each(function () { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .show(); + }); + g.colVisib[n] = 1; + $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true); + } + return true; + }, + + /** + * This must be called if toggleCol() returns is true. + * + * This function is separated from toggleCol because, sometimes, we want to toggle + * some columns together at one time and do just one adjustment after it, e.g. in showAllColumns(). + */ + afterToggleCol: function () { + // some adjustments after hiding column + g.reposRsz(); + g.reposDrop(); + g.sendColPrefs(); + + // check visible first row headers count + g.visibleHeadersCount = $(g.t).find('tr:first th.draggable:visible').length; + g.refreshRestoreButton(); + }, + + /** + * Show columns' visibility list. + * + * @param obj The drop down arrow of column visibility list + */ + showColList: function (obj) { + // only show when not resizing or reordering + if (!g.colRsz && !g.colReorder) { + var pos = $(obj).position(); + // check if the list position is too right + if (pos.left + $(g.cList).outerWidth(true) > $(document).width()) { + pos.left = $(document).width() - $(g.cList).outerWidth(true); + } + $(g.cList).css({ + left: pos.left, + top: pos.top + $(obj).outerHeight(true) + }) + .show(); + $(obj).addClass('coldrop-hover'); + } + }, + + /** + * Hide columns' visibility list. + */ + hideColList: function () { + $(g.cList).hide(); + $(g.cDrop).find('.coldrop-hover').removeClass('coldrop-hover'); + }, + + /** + * Reposition the column visibility drop-down arrow. + */ + reposDrop: function () { + var $th = $(t).find('th:not(.draggable)'); + for (var i = 0; i < $th.length; i++) { + var $cd = $(g.cDrop).find('div:eq(' + i + ')'); // column drop-down arrow + var pos = $($th[i]).position(); + $cd.css({ + left: pos.left + $($th[i]).width() - $cd.width(), + top: pos.top + }); + } + }, + + /** + * Show all hidden columns. + */ + showAllColumns: function () { + for (var i = 0; i < g.colVisib.length; i++) { + if (!g.colVisib[i]) { + g.toggleCol(i); + } + } + g.afterToggleCol(); + }, + + /** + * Show edit cell, if it can be shown + * + * @param cell element to be edited + */ + showEditCell: function (cell) { + if ($(cell).is('.grid_edit') && + !g.colRsz && !g.colReorder) { + if (!g.isCellEditActive) { + var $cell = $(cell); + + if ('string' === $cell.attr('data-type') || + 'blob' === $cell.attr('data-type') || + 'json' === $cell.attr('data-type') + ) { + g.cEdit = g.cEditTextarea; + } else { + g.cEdit = g.cEditStd; + } + + // remove all edit area and hide it + $(g.cEdit).find('.edit_area').empty().hide(); + // reposition the cEdit element + $(g.cEdit).css({ + top: $cell.position().top, + left: $cell.position().left + }) + .show() + .find('.edit_box') + .css({ + width: $cell.outerWidth(), + height: $cell.outerHeight() + }); + // fill the cell edit with text from + var value = PMA_getCellValue(cell); + if ($cell.attr('data-type') === 'json') { + value = JSON.stringify(JSON.parse(value), null, 4); + } + $(g.cEdit).find('.edit_box').val(value); + + g.currentEditCell = cell; + $(g.cEdit).find('.edit_box').focus(); + moveCursorToEnd($(g.cEdit).find('.edit_box')); + $(g.cEdit).find('*').prop('disabled', false); + } + } + + function moveCursorToEnd (input) { + var originalValue = input.val(); + var originallength = originalValue.length; + input.val(''); + input.blur().focus().val(originalValue); + input[0].setSelectionRange(originallength, originallength); + } + }, + + /** + * Remove edit cell and the edit area, if it is shown. + * + * @param force Optional, force to hide edit cell without saving edited field. + * @param data Optional, data from the POST AJAX request to save the edited field + * or just specify "true", if we want to replace the edited field with the new value. + * @param field Optional, the edited . If not specified, the function will + * use currently edited from g.currentEditCell. + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + hideEditCell: function (force, data, field, options) { + if (g.isCellEditActive && !force) { + // cell is being edited, save or post the edited data + if (options !== undefined) { + g.saveOrPostEditedCell(options); + } else { + g.saveOrPostEditedCell(); + } + return; + } + + // cancel any previous request + if (g.lastXHR !== null) { + g.lastXHR.abort(); + g.lastXHR = null; + } + + if (data) { + if (g.currentEditCell) { // save value of currently edited cell + // replace current edited field with the new value + var $thisField = $(g.currentEditCell); + var isNull = $thisField.data('value') === null; + if (isNull) { + $thisField.find('span').html('NULL'); + $thisField.addClass('null'); + } else { + $thisField.removeClass('null'); + var value = data.isNeedToRecheck + ? data.truncatableFieldValue + : $thisField.data('value'); + + // Truncates the text. + $thisField.removeClass('truncated'); + if (CommonParams.get('pftext') === 'P' && value.length > g.maxTruncatedLen) { + $thisField.addClass('truncated'); + value = value.substring(0, g.maxTruncatedLen) + '...'; + } + + // Add
before carriage return. + var newHtml = escapeHtml(value); + newHtml = newHtml.replace(/\n/g, '
\n'); + + // remove decimal places if column type not supported + if (($thisField.attr('data-decimals') === 0) && ($thisField.attr('data-type').indexOf('time') !== -1)) { + newHtml = newHtml.substring(0, newHtml.indexOf('.')); + } + + // remove addtional decimal places + if (($thisField.attr('data-decimals') > 0) && ($thisField.attr('data-type').indexOf('time') !== -1)) { + newHtml = newHtml.substring(0, newHtml.length - (6 - $thisField.attr('data-decimals'))); + } + + var selector = 'span'; + if ($thisField.hasClass('hex') && $thisField.find('a').length) { + selector = 'a'; + } + + // Updates the code keeping highlighting (if any). + var $target = $thisField.find(selector); + if (!PMA_updateCode($target, newHtml, value)) { + $target.html(newHtml); + } + } + if ($thisField.is('.bit')) { + $thisField.find('span').text($thisField.data('value')); + } + } + if (data.transformations !== undefined) { + $.each(data.transformations, function (cellIndex, value) { + var $thisField = $(g.t).find('.to_be_saved:eq(' + cellIndex + ')'); + $thisField.find('span').html(value); + }); + } + if (data.relations !== undefined) { + $.each(data.relations, function (cellIndex, value) { + var $thisField = $(g.t).find('.to_be_saved:eq(' + cellIndex + ')'); + $thisField.find('span').html(value); + }); + } + + // refresh the grid + g.reposRsz(); + g.reposDrop(); + } + + // hide the cell editing area + $(g.cEdit).hide(); + $(g.cEdit).find('.edit_box').blur(); + g.isCellEditActive = false; + g.currentEditCell = null; + // destroy datepicker in edit area, if exist + var $dp = $(g.cEdit).find('.hasDatepicker'); + if ($dp.length > 0) { + $(document).bind('mousedown', $.datepicker._checkExternalClick); + $dp.datepicker('destroy'); + // change the cursor in edit box back to normal + // (the cursor become a hand pointer when we add datepicker) + $(g.cEdit).find('.edit_box').css('cursor', 'inherit'); + } + }, + + /** + * Show drop-down edit area when edit cell is focused. + */ + showEditArea: function () { + if (!g.isCellEditActive) { // make sure the edit area has not been shown + g.isCellEditActive = true; + g.isEditCellTextEditable = false; + /** + * @var $td current edited cell + */ + var $td = $(g.currentEditCell); + /** + * @var $editArea the editing area + */ + var $editArea = $(g.cEdit).find('.edit_area'); + /** + * @var whereClause WHERE clause for the edited cell + */ + var whereClause = $td.parent('tr').find('.where_clause').val(); + /** + * @var fieldName String containing the name of this field. + * @see getFieldName() + */ + var fieldName = getFieldName($(t), $td); + /** + * @var relationCurrValue String current value of the field (for fields that are foreign keyed). + */ + var relationCurrValue = $td.text(); + /** + * @var relationKeyOrDisplayColumn String relational key if in 'Relational display column' mode, + * relational display column if in 'Relational key' mode (for fields that are foreign keyed). + */ + var relationKeyOrDisplayColumn = $td.find('a').attr('title'); + /** + * @var currValue String current value of the field (for fields that are of type enum or set). + */ + var currValue = $td.find('span').text(); + + // empty all edit area, then rebuild it based on $td classes + $editArea.empty(); + + // remember this instead of testing more than once + var isNull = $td.is('.null'); + + // add goto link, if this cell contains a link + if ($td.find('a').length > 0) { + var gotoLink = document.createElement('div'); + gotoLink.className = 'goto_link'; + $(gotoLink).append(g.gotoLinkText + ' ').append($td.find('a').clone()); + $editArea.append(gotoLink); + } + + g.wasEditedCellNull = false; + if ($td.is(':not(.not_null)')) { + // append a null checkbox + $editArea.append('
'); + + var $checkbox = $editArea.find('.null_div input'); + // check if current is NULL + if (isNull) { + $checkbox.prop('checked', true); + g.wasEditedCellNull = true; + } + + // if the select/editor is changed un-check the 'checkbox_null__'. + if ($td.is('.enum, .set')) { + $editArea.on('change', 'select', function () { + $checkbox.prop('checked', false); + }); + } else if ($td.is('.relation')) { + $editArea.on('change', 'select', function () { + $checkbox.prop('checked', false); + }); + $editArea.on('click', '.browse_foreign', function () { + $checkbox.prop('checked', false); + }); + } else { + $(g.cEdit).on('keypress change paste', '.edit_box', function () { + $checkbox.prop('checked', false); + }); + // Capture ctrl+v (on IE and Chrome) + $(g.cEdit).on('keydown', '.edit_box', function (e) { + if (e.ctrlKey && e.which === 86) { + $checkbox.prop('checked', false); + } + }); + $editArea.on('keydown', 'textarea', function () { + $checkbox.prop('checked', false); + }); + } + + // if null checkbox is clicked empty the corresponding select/editor. + $checkbox.click(function () { + if ($td.is('.enum')) { + $editArea.find('select').val(''); + } else if ($td.is('.set')) { + $editArea.find('select').find('option').each(function () { + var $option = $(this); + $option.prop('selected', false); + }); + } else if ($td.is('.relation')) { + // if the dropdown is there to select the foreign value + if ($editArea.find('select').length > 0) { + $editArea.find('select').val(''); + } + } else { + $editArea.find('textarea').val(''); + } + $(g.cEdit).find('.edit_box').val(''); + }); + } + + // reset the position of the edit_area div after closing datetime picker + $(g.cEdit).find('.edit_area').css({ 'top' :'0','position':'' }); + + if ($td.is('.relation')) { + // handle relations + $editArea.addClass('edit_area_loading'); + + // initialize the original data + $td.data('original_data', null); + + /** + * @var postParams Object containing parameters for the POST request + */ + let postParams = { + 'ajax_request' : true, + 'get_relational_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : fieldName, + 'curr_value' : relationCurrValue, + 'relation_key_or_display_column' : relationKeyOrDisplayColumn + }; + + g.lastXHR = $.post('sql.php', postParams, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + if ($(data.dropdown).is('select')) { + // save original_data + var value = $(data.dropdown).val(); + $td.data('original_data', value); + // update the text input field, in case where the "Relational display column" is checked + $(g.cEdit).find('.edit_box').val(value); + } + + $editArea.append(data.dropdown); + $editArea.append('
' + g.cellEditHint + '
'); + + // for 'Browse foreign values' options, + // hide the value next to 'Browse foreign values' link + $editArea.find('span.curr_value').hide(); + // handle update for new values selected from new window + $editArea.find('span.curr_value').change(function () { + $(g.cEdit).find('.edit_box').val($(this).text()); + }); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + g.isEditCellTextEditable = true; + } else if ($td.is('.enum')) { + // handle enum fields + $editArea.addClass('edit_area_loading'); + + /** + * @var postParams Object containing parameters for the POST request + */ + let postParams = { + 'ajax_request' : true, + 'get_enum_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : fieldName, + 'curr_value' : currValue + }; + g.lastXHR = $.post('sql.php', postParams, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + $editArea.append(data.dropdown); + $editArea.append('
' + g.cellEditHint + '
'); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + } else if ($td.is('.set')) { + // handle set fields + $editArea.addClass('edit_area_loading'); + + /** + * @var postParams Object containing parameters for the POST request + */ + let postParams = { + 'ajax_request' : true, + 'get_set_values' : true, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'column' : fieldName, + 'curr_value' : currValue + }; + + // if the data is truncated, get the full data + if ($td.is('.truncated')) { + postParams.get_full_values = true; + postParams.where_clause = whereClause; + } + + g.lastXHR = $.post('sql.php', postParams, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + $editArea.append(data.select); + $td.data('original_data', $(data.select).val().join()); + $editArea.append('
' + g.cellEditHint + '
'); + }); // end $.post() + + $editArea.show(); + $editArea.on('change', 'select', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + } else if ($td.is('.truncated, .transformed')) { + if ($td.is('.to_be_saved')) { // cell has been edited + var value = $td.data('value'); + $(g.cEdit).find('.edit_box').val(value); + $editArea.append(''); + $editArea.find('textarea').val(value); + $editArea + .on('keyup', 'textarea', function () { + $(g.cEdit).find('.edit_box').val($(this).val()); + }); + $(g.cEdit).on('keyup', '.edit_box', function () { + $editArea.find('textarea').val($(this).val()); + }); + $editArea.append('
' + g.cellEditHint + '
'); + } else { + // handle truncated/transformed values values + $editArea.addClass('edit_area_loading'); + + // initialize the original data + $td.data('original_data', null); + + /** + * @var sqlQuery String containing the SQL query used to retrieve value of truncated/transformed data + */ + var sqlQuery = 'SELECT `' + fieldName + '` FROM `' + g.table + '` WHERE ' + whereClause; + + // Make the Ajax call and get the data, wrap it and insert it + g.lastXHR = $.post('sql.php', { + 'server' : g.server, + 'db' : g.db, + 'ajax_request' : true, + 'sql_query' : sqlQuery, + 'grid_edit' : true + }, function (data) { + g.lastXHR = null; + $editArea.removeClass('edit_area_loading'); + if (typeof data !== 'undefined' && data.success === true) { + $td.data('original_data', data.value); + $(g.cEdit).find('.edit_box').val(data.value); + } else { + PMA_ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + g.isEditCellTextEditable = true; + } else if ($td.is('.timefield, .datefield, .datetimefield, .timestampfield')) { + var $inputField = $(g.cEdit).find('.edit_box'); + + // remember current datetime value in $input_field, if it is not null + var datetimeValue = !isNull ? $inputField.val() : ''; + + var showMillisec = false; + var showMicrosec = false; + var timeFormat = 'HH:mm:ss'; + // check for decimal places of seconds + if (($td.attr('data-decimals') > 0) && ($td.attr('data-type').indexOf('time') !== -1)) { + if (datetimeValue && datetimeValue.indexOf('.') === false) { + datetimeValue += '.'; + } + if ($td.attr('data-decimals') > 3) { + showMillisec = true; + showMicrosec = true; + timeFormat = 'HH:mm:ss.lc'; + + if (datetimeValue) { + datetimeValue += '000000'; + let datetimeValue = datetimeValue.substring(0, datetimeValue.indexOf('.') + 7); + $inputField.val(datetimeValue); + } + } else { + showMillisec = true; + timeFormat = 'HH:mm:ss.l'; + + if (datetimeValue) { + datetimeValue += '000'; + let datetimeValue = datetimeValue.substring(0, datetimeValue.indexOf('.') + 4); + $inputField.val(datetimeValue); + } + } + } + + // add datetime picker + PMA_addDatepicker($inputField, $td.attr('data-type'), { + showMillisec: showMillisec, + showMicrosec: showMicrosec, + timeFormat: timeFormat + }); + + $inputField.on('keyup', function (e) { + if (e.which === 13) { + // post on pressing "Enter" + e.preventDefault(); + e.stopPropagation(); + g.saveOrPostEditedCell(); + } else if (e.which === 27) { + // Nothing defined for this yet + } else { + toggleDatepickerIfInvalid($td, $inputField); + } + }); + + $inputField.datepicker('show'); + toggleDatepickerIfInvalid($td, $inputField); + + // unbind the mousedown event to prevent the problem of + // datepicker getting closed, needs to be checked for any + // change in names when updating + $(document).off('mousedown', $.datepicker._checkExternalClick); + + // move ui-datepicker-div inside cEdit div + var datepickerDiv = $('#ui-datepicker-div'); + datepickerDiv.css({ 'top': 0, 'left': 0, 'position': 'relative' }); + $(g.cEdit).append(datepickerDiv); + + // cancel any click on the datepicker element + $editArea.find('> *').click(function (e) { + e.stopPropagation(); + }); + + g.isEditCellTextEditable = true; + } else { + g.isEditCellTextEditable = true; + // only append edit area hint if there is a null checkbox + if ($editArea.children().length > 0) { + $editArea.append('
' + g.cellEditHint + '
'); + } + } + if ($editArea.children().length > 0) { + $editArea.show(); + } + } + }, + + /** + * Post the content of edited cell. + * + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + postEditedCell: function (options) { + if (g.isSaving) { + return; + } + g.isSaving = true; + /** + * @var relationFields Array containing the name/value pairs of relational fields + */ + var relationFields = {}; + /** + * @var relationalDisplay string 'K' if relational key, 'D' if relational display column + */ + var relationalDisplay = $(g.o).find('input[nameD]:checked').val(); + /** + * @var transformFields Array containing the name/value pairs for transformed fields + */ + var transformFields = {}; + /** + * @var transformationFields Boolean, if there are any transformed fields in the edited cells + */ + var transformationFields = false; + /** + * @var fullSqlQuery String containing the complete SQL query to update this table + */ + var fullSqlQuery = ''; + /** + * @var relFieldsList String, url encoded representation of {@link relations_fields} + */ + var relFieldsList = ''; + /** + * @var transformFieldsList String, url encoded representation of {@link transformFields} + */ + var transformFieldsList = ''; + /** + * @var fullWhereClause Array containing where clause for updated fields + */ + var fullWhereClause = []; + /** + * @var isUnique Boolean, whether the rows in this table is unique or not + */ + var isUnique = $(g.t).find('td.edit_row_anchor').is('.nonunique') ? 0 : 1; + /** + * multi edit variables + */ + var meFieldsName = []; + var meFieldsType = []; + var meFields = []; + var meFieldsNull = []; + + // alert user if edited table is not unique + if (!isUnique) { + alert(g.alertNonUnique); + } + + // loop each edited row + $(g.t).find('td.to_be_saved').parents('tr').each(function () { + var $tr = $(this); + var whereClause = $tr.find('.where_clause').val(); + if (typeof whereClause === 'undefined') { + whereClause = ''; + } + fullWhereClause.push(whereClause); + var conditionArray = JSON.parse($tr.find('.condition_array').val()); + + /** + * multi edit variables, for current row + * @TODO array indices are still not correct, they should be md5 of field's name + */ + var fieldsName = []; + var fieldsType = []; + var fields = []; + var fieldsNull = []; + + // loop each edited cell in a row + $tr.find('.to_be_saved').each(function () { + /** + * @var $thisField Object referring to the td that is being edited + */ + var $thisField = $(this); + + /** + * @var fieldName String containing the name of this field. + * @see getFieldName() + */ + var fieldName = getFieldName($(g.t), $thisField); + + /** + * @var thisFieldParams Array temporary storage for the name/value of current field + */ + var thisFieldParams = {}; + + if ($thisField.is('.transformed')) { + transformationFields = true; + } + thisFieldParams[fieldName] = $thisField.data('value'); + + /** + * @var isNull String capturing whether 'checkbox_null__' is checked. + */ + var isNull = thisFieldParams[fieldName] === null; + + fieldsName.push(fieldName); + + if (isNull) { + fieldsNull.push('on'); + fields.push(''); + } else { + if ($thisField.is('.bit')) { + fieldsType.push('bit'); + } else if ($thisField.hasClass('hex')) { + fieldsType.push('hex'); + } + fieldsNull.push(''); + // Convert \n to \r\n to be consistent with form submitted value. + // The internal browser representation has to be just \n + // while form submitted value \r\n, see specification: + // https://www.w3.org/TR/html5/forms.html#the-textarea-element + fields.push($thisField.data('value').replace(/\n/g, '\r\n')); + + var cellIndex = $thisField.index('.to_be_saved'); + if ($thisField.is(':not(.relation, .enum, .set, .bit)')) { + if ($thisField.is('.transformed')) { + transformFields[cellIndex] = {}; + $.extend(transformFields[cellIndex], thisFieldParams); + } + } else if ($thisField.is('.relation')) { + relationFields[cellIndex] = {}; + $.extend(relationFields[cellIndex], thisFieldParams); + } + } + // check if edited field appears in WHERE clause + if (whereClause.indexOf(PMA_urlencode(fieldName)) > -1) { + var fieldStr = '`' + g.table + '`.' + '`' + fieldName + '`'; + for (var field in conditionArray) { + if (field.indexOf(fieldStr) > -1) { + conditionArray[field] = isNull ? 'IS NULL' : '= \'' + thisFieldParams[fieldName].replace(/'/g, '\'\'') + '\''; + break; + } + } + } + }); // end of loop for every edited cells in a row + + // save new_clause + var newClause = ''; + for (var field in conditionArray) { + newClause += field + ' ' + conditionArray[field] + ' AND '; + } + newClause = newClause.substring(0, newClause.length - 5); // remove the last AND + $tr.data('new_clause', newClause); + // save condition_array + $tr.find('.condition_array').val(JSON.stringify(conditionArray)); + + meFieldsName.push(fieldsName); + meFieldsType.push(fieldsType); + meFields.push(fields); + meFieldsNull.push(fieldsNull); + }); // end of loop for every edited rows + + relFieldsList = $.param(relationFields); + transformFieldsList = $.param(transformFields); + + // Make the Ajax post after setting all parameters + /** + * @var postParams Object containing parameters for the POST request + */ + var postParams = { 'ajax_request' : true, + 'sql_query' : fullSqlQuery, + 'server' : g.server, + 'db' : g.db, + 'table' : g.table, + 'clause_is_unique' : isUnique, + 'where_clause' : fullWhereClause, + 'fields[multi_edit]' : meFields, + 'fields_name[multi_edit]' : meFieldsName, + 'fields_type[multi_edit]' : meFieldsType, + 'fields_null[multi_edit]' : meFieldsNull, + 'rel_fields_list' : relFieldsList, + 'do_transformations' : transformationFields, + 'transform_fields_list' : transformFieldsList, + 'relational_display' : relationalDisplay, + 'goto' : 'sql.php', + 'submit_type' : 'save' + }; + + if (!g.saveCellsAtOnce) { + $(g.cEdit).find('*').prop('disabled', true); + $(g.cEdit).find('.edit_box').addClass('edit_box_posting'); + } else { + $(g.o).find('div.save_edited').addClass('saving_edited_data') + .find('input').prop('disabled', true); // disable the save button + } + + $.ajax({ + type: 'POST', + url: 'tbl_replace.php', + data: postParams, + success: + function (data) { + g.isSaving = false; + if (!g.saveCellsAtOnce) { + $(g.cEdit).find('*').prop('disabled', false); + $(g.cEdit).find('.edit_box').removeClass('edit_box_posting'); + } else { + $(g.o).find('div.save_edited').removeClass('saving_edited_data') + .find('input').prop('disabled', false); // enable the save button back + } + if (typeof data !== 'undefined' && data.success === true) { + if (typeof options === 'undefined' || ! options.move) { + PMA_ajaxShowMessage(data.message); + } + + // update where_clause related data in each edited row + $(g.t).find('td.to_be_saved').parents('tr').each(function () { + var newClause = $(this).data('new_clause'); + var $whereClause = $(this).find('.where_clause'); + var oldClause = $whereClause.val(); + var decodedOldClause = oldClause; + var decodedNewClause = newClause; + + $whereClause.val(newClause); + // update Edit, Copy, and Delete links also + $(this).find('a').each(function () { + $(this).attr('href', $(this).attr('href').replace(oldClause, newClause)); + // update delete confirmation in Delete link + if ($(this).attr('href').indexOf('DELETE') > -1) { + $(this).removeAttr('onclick') + .off('click') + .on('click', function () { + return confirmLink(this, 'DELETE FROM `' + g.db + '`.`' + g.table + '` WHERE ' + + decodedNewClause + (isUnique ? '' : ' LIMIT 1')); + }); + } + }); + // update the multi edit checkboxes + $(this).find('input[type=checkbox]').each(function () { + var $checkbox = $(this); + var checkboxName = $checkbox.attr('name'); + var checkboxValue = $checkbox.val(); + + $checkbox.attr('name', checkboxName.replace(oldClause, newClause)); + $checkbox.val(checkboxValue.replace(decodedOldClause, decodedNewClause)); + }); + }); + // update the display of executed SQL query command + if (typeof data.sql_query !== 'undefined') { + // extract query box + var $resultQuery = $($.parseHTML(data.sql_query)); + var sqlOuter = $resultQuery.find('.sqlOuter').wrap('

').parent().html(); + var tools = $resultQuery.find('.tools').wrap('

').parent().html(); + // sqlOuter and tools will not be present if 'Show SQL queries' configuration is off + if (typeof sqlOuter !== 'undefined' && typeof tools !== 'undefined') { + $(g.o).find('.result_query:not(:last)').remove(); + var $existingQuery = $(g.o).find('.result_query'); + // If two query box exists update query in second else add a second box + if ($existingQuery.find('div.sqlOuter').length > 1) { + $existingQuery.children(':nth-child(4)').remove(); + $existingQuery.children(':nth-child(4)').remove(); + $existingQuery.append(sqlOuter + tools); + } else { + $existingQuery.append(sqlOuter + tools); + } + PMA_highlightSQL($existingQuery); + } + } + // hide and/or update the successfully saved cells + g.hideEditCell(true, data); + + // remove the "Save edited cells" button + $(g.o).find('div.save_edited').hide(); + // update saved fields + $(g.t).find('.to_be_saved') + .removeClass('to_be_saved') + .data('value', null) + .data('original_data', null); + + g.isCellEdited = false; + } else { + PMA_ajaxShowMessage(data.error, false); + if (!g.saveCellsAtOnce) { + $(g.t).find('.to_be_saved') + .removeClass('to_be_saved'); + } + } + } + }).done(function () { + if (options !== undefined && options.move) { + g.showEditCell(options.cell); + } + }); // end $.ajax() + }, + + /** + * Save edited cell, so it can be posted later. + */ + saveEditedCell: function () { + /** + * @var $thisField Object referring to the td that is being edited + */ + var $thisField = $(g.currentEditCell); + var $testElement = ''; // to test the presence of a element + + var needToPost = false; + + /** + * @var fieldName String containing the name of this field. + * @see getFieldName() + */ + var fieldName = getFieldName($(g.t), $thisField); + + /** + * @var thisFieldParams Array temporary storage for the name/value of current field + */ + var thisFieldParams = {}; + + /** + * @var isNull String capturing whether 'checkbox_null__' is checked. + */ + var isNull = $(g.cEdit).find('input:checkbox').is(':checked'); + + if ($(g.cEdit).find('.edit_area').is('.edit_area_loading')) { + // the edit area is still loading (retrieving cell data), no need to post + needToPost = false; + } else if (isNull) { + if (!g.wasEditedCellNull) { + thisFieldParams[fieldName] = null; + needToPost = true; + } + } else { + if ($thisField.is('.bit')) { + thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val(); + } else if ($thisField.is('.set')) { + $testElement = $(g.cEdit).find('select'); + thisFieldParams[fieldName] = $testElement.map(function () { + return $(this).val(); + }).get().join(','); + } else if ($thisField.is('.relation, .enum')) { + // for relation and enumeration, take the results from edit box value, + // because selected value from drop-down, new window or multiple + // selection list will always be updated to the edit box + thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val(); + } else if ($thisField.hasClass('hex')) { + if ($(g.cEdit).find('.edit_box').val().match(/^(0x)?[a-f0-9]*$/i) !== null) { + thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val(); + } else { + var hexError = '

' + messages.strEnterValidHex + '
'; + PMA_ajaxShowMessage(hexError, false); + thisFieldParams[fieldName] = PMA_getCellValue(g.currentEditCell); + } + } else { + thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val(); + } + if (g.wasEditedCellNull || thisFieldParams[fieldName] !== PMA_getCellValue(g.currentEditCell)) { + needToPost = true; + } + } + + if (needToPost) { + $(g.currentEditCell).addClass('to_be_saved') + .data('value', thisFieldParams[fieldName]); + if (g.saveCellsAtOnce) { + $(g.o).find('div.save_edited').show(); + } + g.isCellEdited = true; + } + + return needToPost; + }, + + /** + * Save or post currently edited cell, depending on the "saveCellsAtOnce" configuration. + * + * @param field Optional, this object contains a boolean named move (true, if called from move* functions) + * and a to which the grid_edit should move + */ + saveOrPostEditedCell: function (options) { + var saved = g.saveEditedCell(); + // Check if $cfg['SaveCellsAtOnce'] is false + if (!g.saveCellsAtOnce) { + // Check if need_to_post is true + if (saved) { + // Check if this function called from 'move' functions + if (options !== undefined && options.move) { + g.postEditedCell(options); + } else { + g.postEditedCell(); + } + // need_to_post is false + } else { + // Check if this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true); + } + } + // $cfg['SaveCellsAtOnce'] is true + } else { + // If need_to_post + if (saved) { + // If this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true, true, false, options); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true, true); + } + } else { + // If this function called from 'move' functions + if (options !== undefined && options.move) { + g.hideEditCell(true, false, false, options); + g.showEditCell(options.cell); + // NOT called from 'move' functions + } else { + g.hideEditCell(true); + } + } + } + }, + + /** + * Initialize column resize feature. + */ + initColResize: function () { + // create column resizer div + g.cRsz = document.createElement('div'); + g.cRsz.className = 'cRsz'; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + // create column borders + $firstRowCols.each(function () { + var cb = document.createElement('div'); // column border + $(cb).addClass('colborder') + .mousedown(function (e) { + g.dragStartRsz(e, this); + }); + $(g.cRsz).append(cb); + }); + g.reposRsz(); + + // attach to global div + $(g.gDiv).prepend(g.cRsz); + }, + + /** + * Initialize column reordering feature. + */ + initColReorder: function () { + g.cCpy = document.createElement('div'); // column copy, to store copy of dragged column header + g.cPointer = document.createElement('div'); // column pointer, used when reordering column + + // adjust g.cCpy + g.cCpy.className = 'cCpy'; + $(g.cCpy).hide(); + + // adjust g.cPointer + g.cPointer.className = 'cPointer'; + $(g.cPointer).css('visibility', 'hidden'); // set visibility to hidden instead of calling hide() to force browsers to cache the image in cPointer class + + // assign column reordering hint + g.reorderHint = messages.strColOrderHint; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + // initialize column order + var $colOrder = $(g.o).find('.col_order'); // check if column order is passed from PHP + if ($colOrder.length > 0) { + g.colOrder = $colOrder.val().split(','); + for (let i = 0; i < g.colOrder.length; i++) { + g.colOrder[i] = parseInt(g.colOrder[i], 10); + } + } else { + g.colOrder = []; + for (let i = 0; i < $firstRowCols.length; i++) { + g.colOrder.push(i); + } + } + + // register events + $(g.t).find('th.draggable') + .mousedown(function (e) { + $(g.o).addClass('turnOffSelect'); + if (g.visibleHeadersCount > 1) { + g.dragStartReorder(e, this); + } + }) + .mouseenter(function () { + if (g.visibleHeadersCount > 1) { + $(this).css('cursor', 'move'); + } else { + $(this).css('cursor', 'inherit'); + } + }) + .mouseleave(function () { + g.showReorderHint = false; + $(this).tooltip('option', { + content: g.updateHint() + }); + }) + .dblclick(function (e) { + e.preventDefault(); + $('
') + .prop('title', messages.strColNameCopyTitle) + .addClass('modal-copy') + .text(messages.strColNameCopyText) + .append( + $('') + .prop('readonly', true) + .val($(this).data('column')) + ) + .dialog({ + resizable: false, + modal: true + }) + .find('input').focus().select(); + }); + $(g.t).find('th.draggable a') + .dblclick(function (e) { + e.stopPropagation(); + }); + // restore column order when the restore button is clicked + $(g.o).find('div.restore_column').click(function () { + g.restoreColOrder(); + }); + + // attach to global div + $(g.gDiv).append(g.cPointer); + $(g.gDiv).append(g.cCpy); + + // prevent default "dragstart" event when dragging a link + $(g.t).find('th a').on('dragstart', function () { + return false; + }); + + // refresh the restore column button state + g.refreshRestoreButton(); + }, + + /** + * Initialize column visibility feature. + */ + initColVisib: function () { + g.cDrop = document.createElement('div'); // column drop-down arrows + g.cList = document.createElement('div'); // column visibility list + + // adjust g.cDrop + g.cDrop.className = 'cDrop'; + + // adjust g.cList + g.cList.className = 'cList'; + $(g.cList).hide(); + + // assign column visibility related hints + g.showAllColText = messages.strShowAllCol; + + // get data columns in the first row of the table + var $firstRowCols = $(g.t).find('tr:first th.draggable'); + + var i; + // initialize column visibility + var $colVisib = $(g.o).find('.col_visib'); // check if column visibility is passed from PHP + if ($colVisib.length > 0) { + g.colVisib = $colVisib.val().split(','); + for (i = 0; i < g.colVisib.length; i++) { + g.colVisib[i] = parseInt(g.colVisib[i], 10); + } + } else { + g.colVisib = []; + for (i = 0; i < $firstRowCols.length; i++) { + g.colVisib.push(1); + } + } + + // make sure we have more than one column + if ($firstRowCols.length > 1) { + var $colVisibTh = $(g.t).find('th:not(.draggable)'); + PMA_tooltip( + $colVisibTh, + 'th', + messages.strColVisibHint + ); + + // create column visibility drop-down arrow(s) + $colVisibTh.each(function () { + // var $th = $(this); + var cd = document.createElement('div'); // column drop-down arrow + // var pos = $th.position(); + $(cd).addClass('coldrop') + .click(function () { + if (g.cList.style.display === 'none') { + g.showColList(this); + } else { + g.hideColList(); + } + }); + $(g.cDrop).append(cd); + }); + + // add column visibility control + g.cList.innerHTML = '
'; + var $listDiv = $(g.cList).find('div'); + + var tempClick = function () { + if (g.toggleCol($(this).index())) { + g.afterToggleCol(); + } + }; + + for (i = 0; i < $firstRowCols.length; i++) { + var currHeader = $firstRowCols[i]; + var listElmt = document.createElement('div'); + $(listElmt).text($(currHeader).text()) + .prepend(''); + $listDiv.append(listElmt); + // add event on click + $(listElmt).click(tempClick); + } + // add "show all column" button + var showAll = document.createElement('div'); + $(showAll).addClass('showAllColBtn') + .text(g.showAllColText); + $(g.cList).append(showAll); + $(showAll).click(function () { + g.showAllColumns(); + }); + // prepend "show all column" button at top if the list is too long + if ($firstRowCols.length > 10) { + var clone = showAll.cloneNode(true); + $(g.cList).prepend(clone); + $(clone).click(function () { + g.showAllColumns(); + }); + } + } + + // hide column visibility list if we move outside the list + $(g.t).find('td, th.draggable').mouseenter(function () { + g.hideColList(); + }); + + // attach to global div + $(g.gDiv).append(g.cDrop); + $(g.gDiv).append(g.cList); + + // some adjustment + g.reposDrop(); + }, + + /** + * Move currently Editing Cell to Up + */ + moveUp: function (e) { + e.preventDefault(); + var $thisField = $(g.currentEditCell); + var fieldName = getFieldName($(g.t), $thisField); + + var whereClause = $thisField.parents('tr').first().find('.where_clause').val(); + if (typeof whereClause === 'undefined') { + whereClause = ''; + } + var found = false; + var $prevRow; + var $foundRow; + // var j = 0; + + $thisField.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === whereClause) { + found = true; + $foundRow = $(this); + } + if (!found) { + $prevRow = $(this); + } + }); + + var newCell; + + if (found && $prevRow) { + $prevRow.children('td').each(function () { + if (getFieldName($(g.t), $(this)) === fieldName) { + newCell = this; + } + }); + } + + if (newCell) { + g.hideEditCell(false, false, false, { move : true, cell : newCell }); + } + }, + + /** + * Move currently Editing Cell to Down + */ + moveDown: function (e) { + e.preventDefault(); + + var $thisField = $(g.currentEditCell); + var fieldName = getFieldName($(g.t), $thisField); + + var whereClause = $thisField.parents('tr').first().find('.where_clause').val(); + if (typeof whereClause === 'undefined') { + whereClause = ''; + } + var found = false; + var $foundRow; + var $nextRow; + var j = 0; + var nextRowFound = false; + $thisField.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === whereClause) { + found = true; + $foundRow = $(this); + } + if (found) { + if (j >= 1 && ! nextRowFound) { + $nextRow = $(this); + nextRowFound = true; + } else { + j++; + } + } + }); + + var newCell; + if (found && $nextRow) { + $nextRow.children('td').each(function () { + if (getFieldName($(g.t), $(this)) === fieldName) { + newCell = this; + } + }); + } + + if (newCell) { + g.hideEditCell(false, false, false, { move : true, cell : newCell }); + } + }, + + /** + * Move currently Editing Cell to Left + */ + moveLeft: function (e) { + e.preventDefault(); + + var $thisField = $(g.currentEditCell); + var fieldName = getFieldName($(g.t), $thisField); + + var whereClause = $thisField.parents('tr').first().find('.where_clause').val(); + if (typeof whereClause === 'undefined') { + whereClause = ''; + } + var found = false; + var $foundRow; + // var j = 0; + $thisField.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === whereClause) { + found = true; + $foundRow = $(this); + } + }); + + var leftCell; + var cellFound = false; + if (found) { + $foundRow.children('td.grid_edit').each(function () { + if (getFieldName($(g.t), $(this)) === fieldName) { + cellFound = true; + } + if (!cellFound) { + leftCell = this; + } + }); + } + + if (leftCell) { + g.hideEditCell(false, false, false, { move : true, cell : leftCell }); + } + }, + + /** + * Move currently Editing Cell to Right + */ + moveRight: function (e) { + e.preventDefault(); + + var $thisField = $(g.currentEditCell); + var fieldName = getFieldName($(g.t), $thisField); + + var whereClause = $thisField.parents('tr').first().find('.where_clause').val(); + if (typeof whereClause === 'undefined') { + whereClause = ''; + } + var found = false; + var $foundRow; + var j = 0; + $thisField.parents('tr').first().parents('tbody').children().each(function () { + if ($(this).find('.where_clause').val() === whereClause) { + found = true; + $foundRow = $(this); + } + }); + + var rightCell; + var cellFound = false; + var nextCellFound = false; + if (found) { + $foundRow.children('td.grid_edit').each(function () { + if (getFieldName($(g.t), $(this)) === fieldName) { + cellFound = true; + } + if (cellFound) { + if (j >= 1 && ! nextCellFound) { + rightCell = this; + nextCellFound = true; + } else { + j++; + } + } + }); + } + + if (rightCell) { + g.hideEditCell(false, false, false, { move : true, cell : rightCell }); + } + }, + + /** + * Initialize grid editing feature. + */ + initGridEdit: function () { + function startGridEditing (e, cell) { + if (g.isCellEditActive) { + g.saveOrPostEditedCell(); + } else { + g.showEditCell(cell); + } + e.stopPropagation(); + } + + function handleCtrlNavigation (e) { + if ((e.ctrlKey && e.which === 38) || (e.altKey && e.which === 38)) { + g.moveUp(e); + } else if ((e.ctrlKey && e.which === 40) || (e.altKey && e.which === 40)) { + g.moveDown(e); + } else if ((e.ctrlKey && e.which === 37) || (e.altKey && e.which === 37)) { + g.moveLeft(e); + } else if ((e.ctrlKey && e.which === 39) || (e.altKey && e.which === 39)) { + g.moveRight(e); + } + } + + // create cell edit wrapper element + g.cEditStd = document.createElement('div'); + g.cEdit = g.cEditStd; + g.cEditTextarea = document.createElement('div'); + + // adjust g.cEditStd + g.cEditStd.className = 'cEdit'; + $(g.cEditStd).html('
'); + $(g.cEditStd).hide(); + + // adjust g.cEdit + g.cEditTextarea.className = 'cEdit'; + $(g.cEditTextarea).html('
'); + $(g.cEditTextarea).hide(); + + // assign cell editing hint + g.cellEditHint = messages.strCellEditHint; + g.saveCellWarning = messages.strSaveCellWarning; + g.alertNonUnique = messages.strAlertNonUnique; + g.gotoLinkText = messages.strGoToLink; + + // initialize cell editing configuration + g.saveCellsAtOnce = $(g.o).find('.save_cells_at_once').val(); + g.maxTruncatedLen = CommonParams.get('LimitChars'); + + // register events + $(g.t).find('td.data.click1') + .click(function (e) { + startGridEditing(e, this); + // prevent default action when clicking on "link" in a table + if ($(e.target).is('.grid_edit a')) { + e.preventDefault(); + } + }); + + $(g.t).find('td.data.click2') + .click(function (e) { + var $cell = $(this); + // In the case of relational link, We want single click on the link + // to goto the link and double click to start grid-editing. + var $link = $(e.target); + if ($link.is('.grid_edit.relation a')) { + e.preventDefault(); + // get the click count and increase + var clicks = $cell.data('clicks'); + clicks = (typeof clicks === 'undefined') ? 1 : clicks + 1; + + if (clicks === 1) { + // if there are no previous clicks, + // start the single click timer + var timer = setTimeout(function () { + // temporarily remove ajax class so the page loader will not handle it, + // submit and then add it back + $link.removeClass('ajax'); + AJAX.requestHandler.call($link[0]); + $link.addClass('ajax'); + $cell.data('clicks', 0); + }, 700); + $cell.data('clicks', clicks); + $cell.data('timer', timer); + } else { + // this is a double click, cancel the single click timer + // and make the click count 0 + clearTimeout($cell.data('timer')); + $cell.data('clicks', 0); + // start grid-editing + startGridEditing(e, this); + } + } + }) + .dblclick(function (e) { + if ($(e.target).is('.grid_edit a')) { + e.preventDefault(); + } else { + startGridEditing(e, this); + } + }); + + $(g.cEditStd).on('keydown', 'input.edit_box, select', handleCtrlNavigation); + + $(g.cEditStd).find('.edit_box').focus(function () { + g.showEditArea(); + }); + $(g.cEditStd).on('keydown', '.edit_box, select', function (e) { + if (e.which === 13) { + // post on pressing "Enter" + e.preventDefault(); + g.saveOrPostEditedCell(); + } + }); + $(g.cEditStd).keydown(function (e) { + if (!g.isEditCellTextEditable) { + // prevent text editing + e.preventDefault(); + } + }); + + $(g.cEditTextarea).on('keydown', 'textarea.edit_box, select', handleCtrlNavigation); + + $(g.cEditTextarea).find('.edit_box').focus(function () { + g.showEditArea(); + }); + $(g.cEditTextarea).on('keydown', '.edit_box, select', function (e) { + if (e.which === 13 && !e.shiftKey) { + // post on pressing "Enter" + e.preventDefault(); + g.saveOrPostEditedCell(); + } + }); + $(g.cEditTextarea).keydown(function (e) { + if (!g.isEditCellTextEditable) { + // prevent text editing + e.preventDefault(); + } + }); + $('html').click(function (e) { + // hide edit cell if the click is not fromDat edit area + if ($(e.target).parents().index($(g.cEdit)) === -1 && + !$(e.target).parents('.ui-datepicker-header').length && + !$('.browse_foreign_modal.ui-dialog:visible').length && + !$(e.target).closest('.dismissable').length + ) { + g.hideEditCell(); + } + }).keydown(function (e) { + if (e.which === 27 && g.isCellEditActive) { + // cancel on pressing "Esc" + g.hideEditCell(true); + } + }); + $(g.o).find('div.save_edited').click(function () { + g.hideEditCell(); + g.postEditedCell(); + }); + $(window).on('beforeunload', function () { + if (g.isCellEdited) { + return g.saveCellWarning; + } + }); + + // attach to global div + $(g.gDiv).append(g.cEditStd); + $(g.gDiv).append(g.cEditTextarea); + + // add hint for grid editing feature when hovering "Edit" link in each table row + if (messages.strGridEditFeatureHint !== undefined) { + PMA_tooltip( + $(g.t).find('.edit_row_anchor a'), + 'a', + messages.strGridEditFeatureHint + ); + } + } + }; + + /** **************** + * Initialize grid + ******************/ + + // wrap all truncated data cells with span indicating the original length + // todo update the original length after a grid edit + $(t).find('td.data.truncated:not(:has(span))') + .wrapInner(function () { + return ''; + }); + + // wrap remaining cells, except actions cell, with span + $(t).find('th, td:not(:has(span))') + .wrapInner(''); + + // create grid elements + g.gDiv = document.createElement('div'); // create global div + + // initialize the table variable + g.t = t; + + // enclosing .sqlqueryresults div + g.o = $(t).parents('.sqlqueryresults'); + + // get data columns in the first row of the table + var $firstRowCols = $(t).find('tr:first th.draggable'); + + // initialize visible headers count + g.visibleHeadersCount = $firstRowCols.filter(':visible').length; + + // assign first column (actions) span + if (! $(t).find('tr:first th:first').hasClass('draggable')) { // action header exist + g.actionSpan = $(t).find('tr:first th:first').prop('colspan'); + } else { + g.actionSpan = 0; + } + + // assign table create time + // table_create_time will only available if we are in "Browse" tab + g.tableCreateTime = $(g.o).find('.table_create_time').val(); + + // assign the hints + g.sortHint = messages.strSortHint; + g.strMultiSortHint = messages.strMultiSortHint; + g.markHint = messages.strColMarkHint; + g.copyHint = messages.strColNameCopyHint; + + // assign common hidden inputs + var $commonHiddenInputs = $(g.o).find('div.common_hidden_inputs'); + g.server = $commonHiddenInputs.find('input[name=server]').val(); + g.db = $commonHiddenInputs.find('input[name=db]').val(); + g.table = $commonHiddenInputs.find('input[name=table]').val(); + + // add table class + $(t).addClass('pma_table'); + + // add relative position to global div so that resize handlers are correctly positioned + $(g.gDiv).css('position', 'relative'); + + // link the global div + $(t).before(g.gDiv); + $(g.gDiv).append(t); + + // FEATURES + if (enableResize) { + g.initColResize(); + } + // disable reordering for result from EXPLAIN or SHOW syntax, which do not have a table navigation panel + if (enableReorder && + $(g.o).find('table.navigation').length > 0) { + g.initColReorder(); + } + if (enableVisib) { + g.initColVisib(); + } + // make sure we have the ajax class + if (enableGridEdit && + $(t).is('.ajax')) { + g.initGridEdit(); + } + + // create tooltip for each with draggable class + PMA_tooltip( + $(t).find('th.draggable'), + 'th', + g.updateHint() + ); + + // register events for hint tooltip (anchors inside draggable th) + $(t).find('th.draggable a') + .mouseenter(function () { + g.showSortHint = true; + g.showMultiSortHint = true; + $(t).find('th.draggable').tooltip('option', { + content: g.updateHint() + }); + }) + .mouseleave(function () { + g.showSortHint = false; + g.showMultiSortHint = false; + $(t).find('th.draggable').tooltip('option', { + content: g.updateHint() + }); + }); + + // register events for dragging-related feature + if (enableResize || enableReorder) { + $(document).mousemove(function (e) { + g.dragMove(e); + }); + $(document).mouseup(function (e) { + $(g.o).removeClass('turnOffSelect'); + g.dragEnd(e); + }); + } + + // some adjustment + $(t).removeClass('data'); + $(g.gDiv).addClass('data'); +} + +/** + * Module export + */ +export { + PMA_makegrid +}; diff --git a/js/src/utils/menu_resizer.js b/js/src/utils/menu_resizer.js index d09a4fc5ea..cd7150c387 100644 --- a/js/src/utils/menu_resizer.js +++ b/js/src/utils/menu_resizer.js @@ -1,4 +1,10 @@ /* vim: set expandtab sw=4 ts=4 sts=4: */ + +/** + * Module import + */ +import { PMA_getImage } from '../functions/get_image'; +import { PMA_messages as messages } from '../variables/export_variables'; /** * Handles the resizing of a menu according to the available screen width * @@ -17,7 +23,13 @@ * To restore the menu to a state like before it was initialized: * $('#myMenu').menuResizer('destroy'); * - * @package PhpMyAdmin + * @access private + * + * @param {Element} $container + * + * @param {function} widthCalculator + * + * @return {void} */ function MenuResizer ($container, widthCalculator) { var self = this; @@ -45,7 +57,7 @@ function MenuResizer ($container, widthCalculator) { // create submenu container var link = $('', { href: '#', 'class': 'tab nowrap' }) - .text(PMA_messages.strMore) + .text(messages.strMore) .on('click', false); // same as event.preventDefault() var img = $container.find('li img'); if (img.length) { @@ -80,54 +92,54 @@ MenuResizer.prototype.resize = function () { var wmax = this.widthCalculator.call(this.$container); var windowWidth = $(window).width(); var $submenu = this.$container.find('.submenu:last'); - var submenu_w = $submenu.outerWidth(true); - var $submenu_ul = $submenu.find('ul'); + var submenuW = $submenu.outerWidth(true); + var $submenuUl = $submenu.find('ul'); var $li = this.$container.find('> li'); - var $li2 = $submenu_ul.find('li'); - var more_shown = $li2.length > 0; + var $li2 = $submenuUl.find('li'); + var moreShown = $li2.length > 0; // Calculate the total width used by all the shown tabs - var total_len = more_shown ? submenu_w : 0; + var totalLen = moreShown ? submenuW : 0; var l = $li.length - 1; var i; for (i = 0; i < l; i++) { - total_len += $($li[i]).outerWidth(true); + totalLen += $($li[i]).outerWidth(true); } var hasVScroll = document.body.scrollHeight > document.body.clientHeight; if (hasVScroll) { windowWidth += 15; } - var navigationwidth = wmax; + // var navigationwidth = wmax; if (windowWidth < 768) { wmax = 2000; } // Now hide menu elements that don't fit into the menubar var hidden = false; // Whether we have hidden any tabs - while (total_len >= wmax && --l >= 0) { // Process the tabs backwards + while (totalLen >= wmax && --l >= 0) { // Process the tabs backwards hidden = true; var el = $($li[l]); - var el_width = el.outerWidth(true); - el.data('width', el_width); - if (! more_shown) { - total_len -= el_width; - el.prependTo($submenu_ul); - total_len += submenu_w; - more_shown = true; + var elWidth = el.outerWidth(true); + el.data('width', elWidth); + if (! moreShown) { + totalLen -= elWidth; + el.prependTo($submenuUl); + totalLen += submenuW; + moreShown = true; } else { - total_len -= el_width; - el.prependTo($submenu_ul); + totalLen -= elWidth; + el.prependTo($submenuUl); } } // If we didn't hide any tabs, then there might be some space to show some if (! hidden) { // Show menu elements that do fit into the menubar for (i = 0, l = $li2.length; i < l; i++) { - total_len += $($li2[i]).data('width'); + totalLen += $($li2[i]).data('width'); // item fits or (it is the last item // and it would fit if More got removed) - if (total_len < wmax || - (i === $li2.length - 1 && total_len - submenu_w < wmax) + if (totalLen < wmax || + (i === $li2.length - 1 && totalLen - submenuW < wmax) ) { $($li2[i]).insertBefore($submenu); } else { @@ -143,7 +155,7 @@ MenuResizer.prototype.resize = function () { } else { $('.navigationbar').css({ 'width': 'auto' }); $('.navigationbar').css({ 'overflow': 'visible' }); - if ($submenu_ul.find('li').length > 0) { + if ($submenuUl.find('li').length > 0) { $submenu.addClass('shown'); } else { $submenu.removeClass('shown'); @@ -152,10 +164,10 @@ MenuResizer.prototype.resize = function () { if (this.$container.find('> li').length === 1) { // If there is only the "More" tab left, then we need // to align the submenu to the left edge of the tab - $submenu_ul.removeClass().addClass('only'); + $submenuUl.removeClass().addClass('only'); } else { // Otherwise we align the submenu to the right edge of the tab - $submenu_ul.removeClass().addClass('notonly'); + $submenuUl.removeClass().addClass('notonly'); } if ($submenu.find('.tabactive').length) { $submenu diff --git a/js/src/utils/show_ajax_messages.js b/js/src/utils/show_ajax_messages.js index 4c5afc840f..6a2defcf94 100644 --- a/js/src/utils/show_ajax_messages.js +++ b/js/src/utils/show_ajax_messages.js @@ -11,9 +11,9 @@ import { PMA_Messages as messages } from '../variables/export_variables'; import { PMA_highlightSQL } from './sql'; /** - * @var {int} ajax_message_count Number of AJAX messages shown since page load + * @var {int} ajaxMessageCount Number of AJAX messages shown since page load */ -let ajax_message_count = 0; +let ajaxMessageCount = 0; /** * Create a jQuery UI tooltip @@ -129,7 +129,7 @@ const PMA_ajaxShowMessage = (message, timeout, type) => { .prependTo('#page_content'); } // Update message count to create distinct message elements every time - ajax_message_count++; + ajaxMessageCount++; // Remove all old messages, if any $('span.ajax_notification[id^=ajax_message_num]').remove(); /** @@ -138,7 +138,7 @@ const PMA_ajaxShowMessage = (message, timeout, type) => { */ var $retval = $( '' ) .hide() @@ -186,7 +186,7 @@ const PMA_ajaxShowMessage = (message, timeout, type) => { * @return nothing */ function PMA_ajaxRemoveMessage ($thisMsgbox) { - if ($thisMsgbox !== undefined && $thisMsgbox instanceof jQuery) { + if ($thisMsgbox !== undefined && $thisMsgbox instanceof $) { $thisMsgbox .stop(true, true) .fadeOut('medium'); diff --git a/js/src/variables/import_variables.js b/js/src/variables/import_variables.js index 6117b02bb7..4bf9ebb833 100644 --- a/js/src/variables/import_variables.js +++ b/js/src/variables/import_variables.js @@ -46,6 +46,6 @@ Variables.setGlobalVars(window.globalVars); /** * Importing common parameters like db, table, url etc * - * @argument {hash} window.common_params + * @argument {hash} window.commonParams */ -CommonParams.setAll(window.common_params); +CommonParams.setAll(window.commonParams); diff --git a/libraries/classes/Header.php b/libraries/classes/Header.php index 72b4717e5f..123e9b7b1c 100644 --- a/libraries/classes/Header.php +++ b/libraries/classes/Header.php @@ -182,7 +182,6 @@ class Header // the user preferences have not been merged at this point $this->_scripts->addFile('messages.php', array('l' => $GLOBALS['lang'])); - $this->_scripts->addFile('common_params.php', array('l' => $GLOBALS['lang'])); $this->_scripts->addFile('vendors~index_new.js'); $this->_scripts->addFile('index_new.js'); $this->_scripts->addFile('keyhandler.js'); @@ -220,6 +219,7 @@ class Header $this->_scripts->addFile('shortcuts_handler'); } $this->_scripts->addCode($this->getJsParamsCode()); + $this->_scripts->addCodeNew($this->getJsParamsCode(true)); } /** @@ -287,9 +287,11 @@ class Header * Returns, as a string, a list of parameters * used on the client side * + * @param bool $flag to check for CommonParams for Modular Code + * * @return string */ - public function getJsParamsCode(): string + public function getJsParamsCode($flag = false): string { $params = $this->getJsParams(); foreach ($params as $key => $value) { @@ -299,7 +301,11 @@ class Header $params[$key] = $key . ':"' . Sanitize::escapeJsString($value) . '"'; } } - return 'PMA_commonParams.setAll({' . implode(',', $params) . '});'; + if ($flag) { + return 'var commonParams = {' . implode(',', $params) . '};'; + } else { + return 'PMA_commonParams.setAll({' . implode(',', $params) . '});'; + } } /** diff --git a/libraries/classes/Scripts.php b/libraries/classes/Scripts.php index 29125bb576..e2e043475b 100644 --- a/libraries/classes/Scripts.php +++ b/libraries/classes/Scripts.php @@ -36,6 +36,13 @@ class Scripts * @var array of strings */ private $_code; + /** + * An array of discrete javascript code snippets for top script + * + * @access private + * @var array of strings + */ + private $_codeNew; /** * Returns HTML code to include javascript file. @@ -157,6 +164,19 @@ class Scripts $this->_code .= "$code\n"; } + /** + * Adds a temporary new code snippet to the code to be executed + * at the top of the script before other scripts + * + * @param string $code The JS code to be added + * + * @return void + */ + public function addCodeNew($code) + { + $this->_codeNew .= "$code\n"; + } + /** * Returns a list with filenames and a flag to indicate * whether to register onload events for this file @@ -187,6 +207,13 @@ class Scripts public function getDisplay() { $retval = ''; + + $retval .= ''; + if (count($this->_files) > 0) { $retval .= $this->_includeFiles( $this->_files diff --git a/server_sql.php b/server_sql.php index 68d46d0687..05f9c6af88 100644 --- a/server_sql.php +++ b/server_sql.php @@ -24,9 +24,9 @@ PageSettings::showGroup('Sql'); $response = Response::getInstance(); $header = $response->getHeader(); $scripts = $header->getScripts(); -$scripts->addFile('makegrid.js'); -$scripts->addFile('vendor/jquery/jquery.uitablefilter.js'); -$scripts->addFile('sql.js'); +// $scripts->addFile('makegrid.js'); +// $scripts->addFile('vendor/jquery/jquery.uitablefilter.js'); +$scripts->addFile('sql'); require_once 'libraries/server_common.inc.php'; diff --git a/server_status_monitor.php b/server_status_monitor.php index ff9263e32a..e82753c93b 100644 --- a/server_status_monitor.php +++ b/server_status_monitor.php @@ -72,21 +72,21 @@ if ($response->isAjax()) { */ $header = $response->getHeader(); $scripts = $header->getScripts(); -$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); -$scripts->addFile('vendor/jquery/jquery.sortableTable.js'); +// $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); +// $scripts->addFile('vendor/jquery/jquery.sortableTable.js'); // for charting -$scripts->addFile('vendor/jqplot/jquery.jqplot.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.pieRenderer.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.canvasTextRenderer.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.dateAxisRenderer.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.highlighter.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.cursor.js'); -$scripts->addFile('jqplot/plugins/jqplot.byteFormatter.js'); +// $scripts->addFile('vendor/jqplot/jquery.jqplot.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.pieRenderer.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.canvasTextRenderer.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.dateAxisRenderer.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.highlighter.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.cursor.js'); +// $scripts->addFile('jqplot/plugins/jqplot.byteFormatter.js'); -$scripts->addFile('server_status_monitor.js'); -$scripts->addFile('server_status_sorter.js'); +$scripts->addFile('server_status_monitor'); +$scripts->addFile('server_status_sorter'); /** * Output diff --git a/server_status_queries.php b/server_status_queries.php index 9c2e09b80b..20a1d70a41 100644 --- a/server_status_queries.php +++ b/server_status_queries.php @@ -24,12 +24,12 @@ $header = $response->getHeader(); $scripts = $header->getScripts(); // for charting -$scripts->addFile('chart.js'); -$scripts->addFile('vendor/jqplot/jquery.jqplot.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.pieRenderer.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.highlighter.js'); -$scripts->addFile('vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js'); -$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); +// $scripts->addFile('chart.js'); +// $scripts->addFile('vendor/jqplot/jquery.jqplot.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.pieRenderer.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.highlighter.js'); +// $scripts->addFile('vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js'); +// $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); $scripts->addFile('server_status_sorter'); $scripts->addFile('server_status_queries'); diff --git a/test/classes/ScriptsTest.php b/test/classes/ScriptsTest.php index b3d8730770..572a784994 100644 --- a/test/classes/ScriptsTest.php +++ b/test/classes/ScriptsTest.php @@ -117,6 +117,24 @@ class ScriptsTest extends PmaTestCase ); } + /** + * test for addCodeNew + * + * @return void + */ + public function testAddCodeNew() + { + + $this->object->addCode('alert(\'CodeAdded\');'); + + $this->assertEquals( + '', + $this->object->getDisplay() + ); + } + /** * test for addCode * @@ -129,10 +147,10 @@ class ScriptsTest extends PmaTestCase $this->assertEquals( '', + alert(\'CodeAdded\'); + AJAX.scriptHandler; + $(function() {}); + // ]]>', $this->object->getDisplay() ); } diff --git a/webpack.config.babel.js b/webpack.config.babel.js index fcd0d02143..88e53e6915 100644 --- a/webpack.config.babel.js +++ b/webpack.config.babel.js @@ -46,7 +46,10 @@ function WebpackConfig (env) { new webpack.optimize.OccurrenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), - new webpack.NoEmitOnErrorsPlugin() + new webpack.NoEmitOnErrorsPlugin(), + new webpack.ProvidePlugin({ + jQuery: 'jquery' + }), ]; if (MODE === 'development') { plugins.push(new BundleAnalyzer()); From 2c643597fd78fe77c1ee7930c98903304dc23c99 Mon Sep 17 00:00:00 2001 From: Piyush Vijay Date: Sun, 8 Jul 2018 22:06:06 +0530 Subject: [PATCH 02/16] Edit package.json for updated-jqplot package. Adding eslint scripts and script for creating production build edited Signed-Off-By: Piyush Vijay --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 00a4a3c193..e5f10dd777 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "js-cookie": "2.2.0", "sprintf-js": "^1.1.1", "tracekit": "0.4.5", - "updated-jqplot": "1.0.9", + "updated-jqplot": "1.0.9-2", "zxcvbn": "4.4.2" }, "devDependencies": { From 63a54d14f37af8553992e246daf4fa3e47631a05 Mon Sep 17 00:00:00 2001 From: Piyush Vijay Date: Sun, 8 Jul 2018 22:56:12 +0530 Subject: [PATCH 03/16] Adding jQplot package and its plugins and tablesorter are as module in js files and removed from php files. Signed-Off-By: Piyush Vijay --- js/src/classes/Chart.js | 7 +++---- js/src/functions/Server/SeverStatusSorter.js | 2 +- js/src/server_plugins.js | 6 ++++++ js/src/server_status_monitor.js | 15 +++++++-------- js/src/server_status_sorter.js | 2 +- js/src/server_variables.js | 1 + .../Server/ServerPluginsController.php | 1 - server_status_monitor.php | 13 +------------ server_status_queries.php | 7 ------- server_status_variables.php | 3 +-- 10 files changed, 21 insertions(+), 36 deletions(-) diff --git a/js/src/classes/Chart.js b/js/src/classes/Chart.js index 725068a0c4..af9db92277 100644 --- a/js/src/classes/Chart.js +++ b/js/src/classes/Chart.js @@ -1,9 +1,8 @@ import { $ } from '../utils/extend_jquery'; import 'updated-jqplot'; -import '../../../node_modules/updated-jqplot/dist/plugins/jqplot.pieRenderer'; -import '../../../node_modules/updated-jqplot/dist/plugins/jqplot.highlighter'; -import '../../../node_modules/updated-jqplot/dist/plugins/jqplot.enhancedPieLegendRenderer'; -window.test2 = $; +import 'updated-jqplot/dist/plugins/jqplot.pieRenderer'; +import 'updated-jqplot/dist/plugins/jqplot.highlighter'; +import 'updated-jqplot/dist/plugins/jqplot.enhancedPieLegendRenderer'; /** * Chart type enumerations diff --git a/js/src/functions/Server/SeverStatusSorter.js b/js/src/functions/Server/SeverStatusSorter.js index fd7f1531d5..020da354e4 100644 --- a/js/src/functions/Server/SeverStatusSorter.js +++ b/js/src/functions/Server/SeverStatusSorter.js @@ -4,7 +4,7 @@ * Module import */ import { $ } from '../../utils/extend_jquery'; -import 'tablesorter'; +import '../../plugins/jquery/jquery.tablesorter'; // TODO: tablesorter shouldn't sort already sorted columns /** * @access public diff --git a/js/src/server_plugins.js b/js/src/server_plugins.js index c02c2c4528..3633d1f637 100644 --- a/js/src/server_plugins.js +++ b/js/src/server_plugins.js @@ -1,5 +1,11 @@ /* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Module import + */ +import { $ } from './utils/extend_jquery'; +import './plugins/jquery/jquery.tablesorter'; + /** * @package PhpMyAdmin * diff --git a/js/src/server_status_monitor.js b/js/src/server_status_monitor.js index b9a8f3b28b..6d10b95d57 100644 --- a/js/src/server_status_monitor.js +++ b/js/src/server_status_monitor.js @@ -5,13 +5,13 @@ import { $ } from './utils/extend_jquery'; import 'updated-jqplot'; import './plugins/jquery/jquery.sortableTable'; -import '../../node_modules/updated-jqplot/dist/plugins/jqplot.pieRenderer.js'; -import '../../node_modules/updated-jqplot/dist/plugins/jqplot.enhancedPieLegendRenderer.js'; -import '../../node_modules/updated-jqplot/dist/plugins/jqplot.canvasTextRenderer.js'; -import '../../node_modules/updated-jqplot/dist/plugins/jqplot.canvasAxisLabelRenderer.js'; -import '../../node_modules/updated-jqplot/dist/plugins/jqplot.dateAxisRenderer.js'; -import '../../node_modules/updated-jqplot/dist/plugins/jqplot.highlighter.js'; -import '../../node_modules/updated-jqplot/dist/plugins/jqplot.cursor.js'; +import 'updated-jqplot/dist/plugins/jqplot.pieRenderer.js'; +import 'updated-jqplot/dist/plugins/jqplot.enhancedPieLegendRenderer.js'; +import 'updated-jqplot/dist/plugins/jqplot.canvasTextRenderer.js'; +import 'updated-jqplot/dist/plugins/jqplot.canvasAxisLabelRenderer.js'; +import 'updated-jqplot/dist/plugins/jqplot.dateAxisRenderer.js'; +import 'updated-jqplot/dist/plugins/jqplot.highlighter.js'; +import 'updated-jqplot/dist/plugins/jqplot.cursor.js'; import './plugins/jqplot/jqplot.byteFormatter'; import { getOsDetail } from './functions/Server/ServerStatusMonitor'; @@ -2011,7 +2011,6 @@ export function onload3 () { /* Saves the monitor to localstorage */ function saveMonitor () { var gridCopy = {}; - $.each(runtime.charts, function (key, elem) { gridCopy[key] = {}; gridCopy[key].nodes = elem.nodes; diff --git a/js/src/server_status_sorter.js b/js/src/server_status_sorter.js index 503a79535d..ac5cdc4032 100644 --- a/js/src/server_status_sorter.js +++ b/js/src/server_status_sorter.js @@ -4,7 +4,7 @@ * Module import */ import { $ } from './utils/JqueryExtended'; -import 'tablesorter'; +import './plugins/jquery/jquery.tablesorter'; import { PMA_Messages as messages } from './variables/export_variables'; $(function () { diff --git a/js/src/server_variables.js b/js/src/server_variables.js index c49fcb29a0..ac60217991 100644 --- a/js/src/server_variables.js +++ b/js/src/server_variables.js @@ -3,6 +3,7 @@ /** * Module import */ +import { $ } from './utils/extend_jquery'; import { editVariable } from './functions/Server/ServerVariables'; /** diff --git a/libraries/classes/Controllers/Server/ServerPluginsController.php b/libraries/classes/Controllers/Server/ServerPluginsController.php index 0b8e438e53..fb7583d4fc 100644 --- a/libraries/classes/Controllers/Server/ServerPluginsController.php +++ b/libraries/classes/Controllers/Server/ServerPluginsController.php @@ -49,7 +49,6 @@ class ServerPluginsController extends Controller $header = $this->response->getHeader(); $scripts = $header->getScripts(); - $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); $scripts->addFile('server_plugins'); /** diff --git a/server_status_monitor.php b/server_status_monitor.php index e82753c93b..e27ab81f3d 100644 --- a/server_status_monitor.php +++ b/server_status_monitor.php @@ -72,18 +72,7 @@ if ($response->isAjax()) { */ $header = $response->getHeader(); $scripts = $header->getScripts(); -// $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); -// $scripts->addFile('vendor/jquery/jquery.sortableTable.js'); -// for charting -// $scripts->addFile('vendor/jqplot/jquery.jqplot.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.pieRenderer.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.canvasTextRenderer.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.dateAxisRenderer.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.highlighter.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.cursor.js'); -// $scripts->addFile('jqplot/plugins/jqplot.byteFormatter.js'); +$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); $scripts->addFile('server_status_monitor'); $scripts->addFile('server_status_sorter'); diff --git a/server_status_queries.php b/server_status_queries.php index 20a1d70a41..591613b73c 100644 --- a/server_status_queries.php +++ b/server_status_queries.php @@ -23,13 +23,6 @@ $response = Response::getInstance(); $header = $response->getHeader(); $scripts = $header->getScripts(); -// for charting -// $scripts->addFile('chart.js'); -// $scripts->addFile('vendor/jqplot/jquery.jqplot.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.pieRenderer.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.highlighter.js'); -// $scripts->addFile('vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js'); -// $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); $scripts->addFile('server_status_sorter'); $scripts->addFile('server_status_queries'); diff --git a/server_status_variables.php b/server_status_variables.php index 328cbb43f6..0e9a3060b0 100644 --- a/server_status_variables.php +++ b/server_status_variables.php @@ -38,8 +38,7 @@ $response = Response::getInstance(); $header = $response->getHeader(); $scripts = $header->getScripts(); $scripts->addFile('server_status_variables'); -$scripts->addFile('vendor/jquery/jquery.tablesorter.js'); -$scripts->addFile('server_status_sorter.js'); +$scripts->addFile('server_status_sorter'); $response->addHTML('
'); $response->addHTML($serverStatusData->getMenuHtml()); From 91afae1798342fb5d6200849972527181e6d228a Mon Sep 17 00:00:00 2001 From: Piyush Vijay Date: Sun, 8 Jul 2018 23:03:56 +0530 Subject: [PATCH 04/16] No need to review this commit as these files are directly copied from old vendor and plugins folder. These files can be ignored during review. They are not availabe as npm mpdule right now and their alternative are largerr in size with the same functionality. So they are being used as it is. Their size will be reduced in the outpul bundle after using webpack plugins to emove comments from js files. Signed-Off-By: Piyush Vijay --- js/src/plugins/jqplot/jqplot.byteFormatter.js | 46 + .../jquery/jquery.ba-hashchange-1.3.js | 390 ++++++ js/src/plugins/jquery/jquery.sortableTable.js | 272 +++++ js/src/plugins/jquery/jquery.tablesorter.js | 1046 +++++++++++++++++ js/src/plugins/jquery/jquery.uitablefilter.js | 117 ++ 5 files changed, 1871 insertions(+) create mode 100644 js/src/plugins/jqplot/jqplot.byteFormatter.js create mode 100644 js/src/plugins/jquery/jquery.ba-hashchange-1.3.js create mode 100644 js/src/plugins/jquery/jquery.sortableTable.js create mode 100644 js/src/plugins/jquery/jquery.tablesorter.js create mode 100644 js/src/plugins/jquery/jquery.uitablefilter.js diff --git a/js/src/plugins/jqplot/jqplot.byteFormatter.js b/js/src/plugins/jqplot/jqplot.byteFormatter.js new file mode 100644 index 0000000000..610692d3c7 --- /dev/null +++ b/js/src/plugins/jqplot/jqplot.byteFormatter.js @@ -0,0 +1,46 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * jqplot formatter for byte values + * + * @package phpMyAdmin + */ +(function ($) { + 'use strict'; + var formatByte = function (val, index) { + var units = [ + PMA_messages.strB, + PMA_messages.strKiB, + PMA_messages.strMiB, + PMA_messages.strGiB, + PMA_messages.strTiB, + PMA_messages.strPiB, + PMA_messages.strEiB + ]; + while (val >= 1024 && index <= 6) { + val /= 1024; + index++; + } + var format = '%.1f'; + if (Math.floor(val) === val) { + format = '%.0f'; + } + return $.jqplot.sprintf( + format + ' ' + units[index], val + ); + }; + /** + * The index indicates what unit the incoming data will be in. + * 0 for bytes, 1 for kilobytes and so on... + */ + $.jqplot.byteFormatter = function (index) { + index = index || 0; + return function (format, val) { + if (typeof val === 'number') { + val = parseFloat(val) || 0; + return formatByte(val, index); + } else { + return String(val); + } + }; + }; +}(jQuery)); diff --git a/js/src/plugins/jquery/jquery.ba-hashchange-1.3.js b/js/src/plugins/jquery/jquery.ba-hashchange-1.3.js new file mode 100644 index 0000000000..bba582bfe1 --- /dev/null +++ b/js/src/plugins/jquery/jquery.ba-hashchange-1.3.js @@ -0,0 +1,390 @@ +/*! + * jQuery hashchange event - v1.3 - 7/21/2010 + * http://benalman.com/projects/jquery-hashchange-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Script: jQuery hashchange event +// +// *Version: 1.3, Last updated: 7/21/2010* +// +// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ +// GitHub - http://github.com/cowboy/jquery-hashchange/ +// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js +// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. +// +// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ +// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, +// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. +// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ +// +// About: Known issues +// +// While this jQuery hashchange event implementation is quite stable and +// robust, there are a few unfortunate browser bugs surrounding expected +// hashchange event-based behaviors, independent of any JavaScript +// window.onhashchange abstraction. See the following examples for more +// information: +// +// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ +// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ +// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ +// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ +// +// Also note that should a browser natively support the window.onhashchange +// event, but not report that it does, the fallback polling loop will be used. +// +// About: Release History +// +// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more +// "removable" for mobile-only development. Added IE6/7 document.title +// support. Attempted to make Iframe as hidden as possible by using +// techniques from http://www.paciellogroup.com/blog/?p=604. Added +// support for the "shortcut" format $(window).hashchange( fn ) and +// $(window).hashchange() like jQuery provides for built-in events. +// Renamed jQuery.hashchangeDelay to and +// lowered its default value to 50. Added +// and properties plus document-domain.html +// file to address access denied issues when setting document.domain in +// IE6/7. +// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin +// from a page on another domain would cause an error in Safari 4. Also, +// IE6/7 Iframe is now inserted after the body (this actually works), +// which prevents the page from scrolling when the event is first bound. +// Event can also now be bound before DOM ready, but it won't be usable +// before then in IE6/7. +// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug +// where browser version is incorrectly reported as 8.0, despite +// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. +// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special +// window.onhashchange functionality into a separate plugin for users +// who want just the basic event & back button support, without all the +// extra awesomeness that BBQ provides. This plugin will be included as +// part of jQuery BBQ, but also be available separately. + +(function($,window,undefined){ + '$:nomunge'; // Used by YUI compressor. + + // Reused string. + var str_hashchange = 'hashchange', + + // Method / object references. + doc = document, + fake_onhashchange, + special = $.event.special, + + // Does the browser support window.onhashchange? Note that IE8 running in + // IE7 compatibility mode reports true for 'onhashchange' in window, even + // though the event isn't supported, so also test document.documentMode. + doc_mode = doc.documentMode, + supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + url = url || location.href; + return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Method: jQuery.fn.hashchange + // + // Bind a handler to the window.onhashchange event or trigger all bound + // window.onhashchange event handlers. This behavior is consistent with + // jQuery's built-in event handlers. + // + // Usage: + // + // > jQuery(window).hashchange( [ handler ] ); + // + // Arguments: + // + // handler - (Function) Optional handler to be bound to the hashchange + // event. This is a "shortcut" for the more verbose form: + // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, + // all bound window.onhashchange event handlers will be triggered. This + // is a shortcut for the more verbose + // jQuery(window).trigger( 'hashchange' ). These forms are described in + // the section. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements. + + // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and + // $(elem).hashchange() for triggering, like jQuery does for built-in events. + $.fn[ str_hashchange ] = function( fn ) { + return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); + }; + + // Property: jQuery.fn.hashchange.delay + // + // The numeric interval (in milliseconds) at which the + // polling loop executes. Defaults to 50. + + // Property: jQuery.fn.hashchange.domain + // + // If you're setting document.domain in your JavaScript, and you want hash + // history to work in IE6/7, not only must this property be set, but you must + // also set document.domain BEFORE jQuery is loaded into the page. This + // property is only applicable if you are supporting IE6/7 (or IE8 operating + // in "IE7 compatibility" mode). + // + // In addition, the property must be set to the + // path of the included "document-domain.html" file, which can be renamed or + // modified if necessary (note that the document.domain specified must be the + // same in both your main JavaScript as well as in this file). + // + // Usage: + // + // jQuery.fn.hashchange.domain = document.domain; + + // Property: jQuery.fn.hashchange.src + // + // If, for some reason, you need to specify an Iframe src file (for example, + // when setting document.domain as in ), you can + // do so using this property. Note that when using this property, history + // won't be recorded in IE6/7 until the Iframe src file loads. This property + // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 + // compatibility" mode). + // + // Usage: + // + // jQuery.fn.hashchange.src = 'path/to/file.html'; + + $.fn[ str_hashchange ].delay = 50; + /* + $.fn[ str_hashchange ].domain = null; + $.fn[ str_hashchange ].src = null; + */ + + // Event: hashchange event + // + // Fired when location.hash changes. In browsers that support it, the native + // HTML5 window.onhashchange event is used, otherwise a polling loop is + // initialized, running every milliseconds to + // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 + // compatibility" mode), a hidden Iframe is created to allow the back button + // and hash-based history to work. + // + // Usage as described in : + // + // > // Bind an event handler. + // > jQuery(window).hashchange( function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).hashchange(); + // + // A more verbose usage that allows for event namespacing: + // + // > // Bind an event handler. + // > jQuery(window).bind( 'hashchange', function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).trigger( 'hashchange' ); + // + // Additional Notes: + // + // * The polling loop and Iframe are not created until at least one handler + // is actually bound to the 'hashchange' event. + // * If you need the bound handler(s) to execute immediately, in cases where + // a location.hash exists on page load, via bookmark or page refresh for + // example, use jQuery(window).hashchange() or the more verbose + // jQuery(window).trigger( 'hashchange' ). + // * The event can be bound before DOM ready, but since it won't be usable + // before then in IE6/7 (due to the necessary Iframe), recommended usage is + // to bind it inside a DOM ready handler. + + // Override existing $.event.special.hashchange methods (allowing this plugin + // to be defined after jQuery BBQ in BBQ's source code). + special[ str_hashchange ] = $.extend( special[ str_hashchange ], { + + // Called only when the first 'hashchange' event is bound to window. + setup: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to create our own. And we don't want to call this + // until the user binds to the event, just in case they never do, since it + // will create a polling loop and possibly even a hidden Iframe. + $( fake_onhashchange.start ); + }, + + // Called only when the last 'hashchange' event is unbound from window. + teardown: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to stop ours (if possible). + $( fake_onhashchange.stop ); + } + + }); + + // fake_onhashchange does all the work of triggering the window.onhashchange + // event for browsers that don't natively support it, including creating a + // polling loop to watch for hash changes and in IE 6/7 creating a hidden + // Iframe to enable back and forward. + fake_onhashchange = (function(){ + var self = {}, + timeout_id, + + // Remember the initial hash so it doesn't get triggered immediately. + last_hash = get_fragment(), + + fn_retval = function(val){ return val; }, + history_set = fn_retval, + history_get = fn_retval; + + // Start the polling loop. + self.start = function() { + timeout_id || poll(); + }; + + // Stop the polling loop. + self.stop = function() { + timeout_id && clearTimeout( timeout_id ); + timeout_id = undefined; + }; + + // This polling loop checks every $.fn.hashchange.delay milliseconds to see + // if location.hash has changed, and triggers the 'hashchange' event on + // window when necessary. + function poll() { + var hash = get_fragment(), + history_hash = history_get( last_hash ); + + if ( hash !== last_hash ) { + history_set( last_hash = hash, history_hash ); + + $(window).trigger( str_hashchange ); + + } else if ( history_hash !== last_hash ) { + location.href = location.href.replace( /#.*/, '' ) + history_hash; + } + + timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); + }; + + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + (window.navigator.userAgent.indexOf("MSIE ") > -1 || !!window.navigator.userAgent.match(/Trident.*rv\:11\./)) && !supports_onhashchange && (function(){ + // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 + // when running in "IE7 compatibility" mode. + + var iframe, + iframe_src; + + // When the event is bound and polling starts in IE 6/7, create a hidden + // Iframe for history handling. + self.start = function(){ + if ( !iframe ) { + iframe_src = $.fn[ str_hashchange ].src; + iframe_src = iframe_src && iframe_src + get_fragment(); + + // Create hidden Iframe. Attempt to make Iframe as hidden as possible + // by using techniques from http://www.paciellogroup.com/blog/?p=604. + iframe = $('