diff options
Diffstat (limited to 'srcs/phpmyadmin/js/table')
| -rw-r--r-- | srcs/phpmyadmin/js/table/change.js | 724 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/chart.js | 425 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/find_replace.js | 46 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/gis_visualization.js | 368 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/operations.js | 321 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/relation.js | 248 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/select.js | 409 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/structure.js | 505 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/tracking.js | 106 | ||||
| -rw-r--r-- | srcs/phpmyadmin/js/table/zoom_plot_jqplot.js | 607 |
10 files changed, 3759 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/js/table/change.js b/srcs/phpmyadmin/js/table/change.js new file mode 100644 index 0000000..bd7d4ba --- /dev/null +++ b/srcs/phpmyadmin/js/table/change.js @@ -0,0 +1,724 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview function used in table data manipulation pages + * + * @requires jQuery + * @requires jQueryUI + * @requires js/functions.js + * + */ + +/* global extendingValidatorMessages */ // js/messages.php +/* global openGISEditor, gisEditorLoaded, loadJSAndGISEditor, loadGISEditor */ // js/gis_data_editor.js + +/** + * Modify form controls when the "NULL" checkbox is checked + * + * @param theType string the MySQL field type + * @param urlField string the urlencoded field name - OBSOLETE + * @param md5Field string the md5 hashed field name + * @param multiEdit string the multi_edit row sequence number + * + * @return boolean always true + */ +function nullify (theType, urlField, md5Field, multiEdit) { + var rowForm = document.forms.insertForm; + + if (typeof(rowForm.elements['funcs' + multiEdit + '[' + md5Field + ']']) !== 'undefined') { + rowForm.elements['funcs' + multiEdit + '[' + md5Field + ']'].selectedIndex = -1; + } + + // "ENUM" field with more than 20 characters + if (Number(theType) === 1) { + rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'][1].selectedIndex = -1; + // Other "ENUM" field + } else if (Number(theType) === 2) { + var elts = rowForm.elements['fields' + multiEdit + '[' + md5Field + ']']; + // when there is just one option in ENUM: + if (elts.checked) { + elts.checked = false; + } else { + var eltsCnt = elts.length; + for (var i = 0; i < eltsCnt; i++) { + elts[i].checked = false; + } // end for + } // end if + // "SET" field + } else if (Number(theType) === 3) { + rowForm.elements['fields' + multiEdit + '[' + md5Field + '][]'].selectedIndex = -1; + // Foreign key field (drop-down) + } else if (Number(theType) === 4) { + rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].selectedIndex = -1; + // foreign key field (with browsing icon for foreign values) + } else if (Number(theType) === 6) { + rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].value = ''; + // Other field types + } else /* if (theType === 5)*/ { + rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].value = ''; + } // end if... else if... else + + return true; +} // end of the 'nullify()' function + + +/** + * javascript DateTime format validation. + * its used to prevent adding default (0000-00-00 00:00:00) to database when user enter wrong values + * Start of validation part + */ +// function checks the number of days in febuary +function daysInFebruary (year) { + return (((year % 4 === 0) && (((year % 100 !== 0)) || (year % 400 === 0))) ? 29 : 28); +} +// function to convert single digit to double digit +function fractionReplace (number) { + var num = parseInt(number, 10); + return num >= 1 && num <= 9 ? '0' + num : '00'; +} + +/* function to check the validity of date +* The following patterns are accepted in this validation (accepted in mysql as well) +* 1) 2001-12-23 +* 2) 2001-1-2 +* 3) 02-12-23 +* 4) And instead of using '-' the following punctuations can be used (+,.,*,^,@,/) All these are accepted by mysql as well. Therefore no issues +*/ +function isDate (val, tmstmp) { + var value = val.replace(/[.|*|^|+|//|@]/g, '-'); + var arrayVal = value.split('-'); + for (var a = 0; a < arrayVal.length; a++) { + if (arrayVal[a].length === 1) { + arrayVal[a] = fractionReplace(arrayVal[a]); + } + } + value = arrayVal.join('-'); + var pos = 2; + var dtexp = new RegExp(/^([0-9]{4})-(((01|03|05|07|08|10|12)-((0[0-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)-((0[0-9])|([1-2][0-9])|30))|((00)-(00)))$/); + if (value.length === 8) { + pos = 0; + } + if (dtexp.test(value)) { + var month = parseInt(value.substring(pos + 3, pos + 5), 10); + var day = parseInt(value.substring(pos + 6, pos + 8), 10); + var year = parseInt(value.substring(0, pos + 2), 10); + if (month === 2 && day > daysInFebruary(year)) { + return false; + } + if (value.substring(0, pos + 2).length === 2) { + year = parseInt('20' + value.substring(0, pos + 2), 10); + } + if (tmstmp === true) { + if (year < 1978) { + return false; + } + if (year > 2038 || (year > 2037 && day > 19 && month >= 1) || (year > 2037 && month > 1)) { + return false; + } + } + } else { + return false; + } + return true; +} + +/* function to check the validity of time +* The following patterns are accepted in this validation (accepted in mysql as well) +* 1) 2:3:4 +* 2) 2:23:43 +* 3) 2:23:43.123456 +*/ +function isTime (val) { + var arrayVal = val.split(':'); + for (var a = 0, l = arrayVal.length; a < l; a++) { + if (arrayVal[a].length === 1) { + arrayVal[a] = fractionReplace(arrayVal[a]); + } + } + var newVal = arrayVal.join(':'); + var tmexp = new RegExp(/^(-)?(([0-7]?[0-9][0-9])|(8[0-2][0-9])|(83[0-8])):((0[0-9])|([1-5][0-9])):((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$/); + return tmexp.test(newVal); +} + +/** + * To check whether insert section is ignored or not + */ +function checkForCheckbox (multiEdit) { + if ($('#insert_ignore_' + multiEdit).length) { + return $('#insert_ignore_' + multiEdit).is(':unchecked'); + } + return true; +} + +function verificationsAfterFieldChange (urlField, multiEdit, theType) { + var evt = window.event || arguments.callee.caller.arguments[0]; + var target = evt.target || evt.srcElement; + var $thisInput = $(':input[name^=\'fields[multi_edit][' + multiEdit + '][' + + urlField + ']\']'); + // the function drop-down that corresponds to this input field + var $thisFunction = $('select[name=\'funcs[multi_edit][' + multiEdit + '][' + + urlField + ']\']'); + var functionSelected = false; + if (typeof $thisFunction.val() !== 'undefined' && + $thisFunction.val() !== null && + $thisFunction.val().length > 0 + ) { + functionSelected = true; + } + + // To generate the textbox that can take the salt + var newSaltBox = '<br><input type=text name=salt[multi_edit][' + multiEdit + '][' + urlField + ']' + + ' id=salt_' + target.id + ' placeholder=\'' + Messages.strEncryptionKey + '\'>'; + + // If encrypting or decrypting functions that take salt as input is selected append the new textbox for salt + if (target.value === 'AES_ENCRYPT' || + target.value === 'AES_DECRYPT' || + target.value === 'DES_ENCRYPT' || + target.value === 'DES_DECRYPT' || + target.value === 'ENCRYPT') { + if (!($('#salt_' + target.id).length)) { + $thisInput.after(newSaltBox); + } + } else { + // Remove the textbox for salt + $('#salt_' + target.id).prev('br').remove(); + $('#salt_' + target.id).remove(); + } + + if (target.value === 'AES_DECRYPT' + || target.value === 'AES_ENCRYPT' + || target.value === 'MD5') { + $('#' + target.id).rules('add', { + validationFunctionForFuns: { + param: $thisInput, + depends: function () { + return checkForCheckbox(multiEdit); + } + } + }); + } + + // Unchecks the corresponding "NULL" control + $('input[name=\'fields_null[multi_edit][' + multiEdit + '][' + urlField + ']\']').prop('checked', false); + + // Unchecks the Ignore checkbox for the current row + $('input[name=\'insert_ignore_' + multiEdit + '\']').prop('checked', false); + + var charExceptionHandling; + if (theType.substring(0,4) === 'char') { + charExceptionHandling = theType.substring(5,6); + } else if (theType.substring(0,7) === 'varchar') { + charExceptionHandling = theType.substring(8,9); + } + if (functionSelected) { + $thisInput.removeAttr('min'); + $thisInput.removeAttr('max'); + // @todo: put back attributes if corresponding function is deselected + } + + if ($thisInput.data('rulesadded') === null && ! functionSelected) { + // call validate before adding rules + $($thisInput[0].form).validate(); + // validate for date time + if (theType === 'datetime' || theType === 'time' || theType === 'date' || theType === 'timestamp') { + $thisInput.rules('add', { + validationFunctionForDateTime: { + param: theType, + depends: function () { + return checkForCheckbox(multiEdit); + } + } + }); + } + // validation for integer type + if ($thisInput.data('type') === 'INT') { + var mini = parseInt($thisInput.attr('min')); + var maxi = parseInt($thisInput.attr('max')); + $thisInput.rules('add', { + number: { + param : true, + depends: function () { + return checkForCheckbox(multiEdit); + } + }, + min: { + param: mini, + depends: function () { + if (isNaN($thisInput.val())) { + return false; + } else { + return checkForCheckbox(multiEdit); + } + } + }, + max: { + param: maxi, + depends: function () { + if (isNaN($thisInput.val())) { + return false; + } else { + return checkForCheckbox(multiEdit); + } + } + } + }); + // validation for CHAR types + } else if ($thisInput.data('type') === 'CHAR') { + var maxlen = $thisInput.data('maxlength'); + if (typeof maxlen !== 'undefined') { + if (maxlen <= 4) { + maxlen = charExceptionHandling; + } + $thisInput.rules('add', { + maxlength: { + param: maxlen, + depends: function () { + return checkForCheckbox(multiEdit); + } + } + }); + } + // validate binary & blob types + } else if ($thisInput.data('type') === 'HEX') { + $thisInput.rules('add', { + validationFunctionForHex: { + param: true, + depends: function () { + return checkForCheckbox(multiEdit); + } + } + }); + } + $thisInput.data('rulesadded', true); + } else if ($thisInput.data('rulesadded') === true && functionSelected) { + // remove any rules added + $thisInput.rules('remove'); + // remove any error messages + $thisInput + .removeClass('error') + .removeAttr('aria-invalid') + .siblings('.error') + .remove(); + $thisInput.data('rulesadded', null); + } +} +/* End of fields validation*/ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/change.js', function () { + $(document).off('click', 'span.open_gis_editor'); + $(document).off('click', 'input[name^=\'insert_ignore_\']'); + $(document).off('click', 'input[name=\'gis_data[save]\']'); + $(document).off('click', 'input.checkbox_null'); + $('select[name="submit_type"]').off('change'); + $(document).off('change', '#insert_rows'); +}); + +/** + * Ajax handlers for Change Table page + * + * Actions Ajaxified here: + * Submit Data to be inserted into the table. + * Restart insertion with 'N' rows. + */ +AJAX.registerOnload('table/change.js', function () { + if ($('#insertForm').length) { + // validate the comment form when it is submitted + $('#insertForm').validate(); + jQuery.validator.addMethod('validationFunctionForHex', function (value) { + return value.match(/^[a-f0-9]*$/i) !== null; + }); + + jQuery.validator.addMethod('validationFunctionForFuns', function (value, element, options) { + if (value.substring(0, 3) === 'AES' && options.data('type') !== 'HEX') { + return false; + } + + return !(value.substring(0, 3) === 'MD5' && + typeof options.data('maxlength') !== 'undefined' && + options.data('maxlength') < 32); + }); + + jQuery.validator.addMethod('validationFunctionForDateTime', function (value, element, options) { + var dtValue = value; + var theType = options; + if (theType === 'date') { + return isDate(dtValue); + } else if (theType === 'time') { + return isTime(dtValue); + } else if (theType === 'datetime' || theType === 'timestamp') { + var tmstmp = false; + dtValue = dtValue.trim(); + if (dtValue === 'CURRENT_TIMESTAMP' || dtValue === 'current_timestamp()') { + return true; + } + if (theType === 'timestamp') { + tmstmp = true; + } + if (dtValue === '0000-00-00 00:00:00') { + return true; + } + var dv = dtValue.indexOf(' '); + if (dv === -1) { // Only the date component, which is valid + return isDate(dtValue, tmstmp); + } + + return isDate(dtValue.substring(0, dv), tmstmp) && + isTime(dtValue.substring(dv + 1)); + } + }); + /* + * message extending script must be run + * after initiation of functions + */ + extendingValidatorMessages(); + } + + $.datepicker.initialized = false; + + $(document).on('click', 'span.open_gis_editor', function (event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children('input[type=\'text\']').val(); + // Field name + var field = $span.parents('tr').children('td:first').find('input[type=\'hidden\']').val(); + // Column type + var type = $span.parents('tr').find('span.column_type').text(); + // Names of input field and null checkbox + var inputName = $span.parent('td').children('input[type=\'text\']').attr('name'); + + openGISEditor(); + if (!gisEditorLoaded) { + loadJSAndGISEditor(value, field, type, inputName); + } else { + loadGISEditor(value, field, type, inputName); + } + }); + + /** + * Forced validation check of fields + */ + $(document).on('click','input[name^=\'insert_ignore_\']', function () { + $('#insertForm').valid(); + }); + + /** + * Uncheck the null checkbox as geometry data is placed on the input field + */ + $(document).on('click', 'input[name=\'gis_data[save]\']', function () { + var inputName = $('form#gis_data_editor_form').find('input[name=\'input_name\']').val(); + var $nullCheckbox = $('input[name=\'' + inputName + '\']').parents('tr').find('.checkbox_null'); + $nullCheckbox.prop('checked', false); + }); + + /** + * Handles all current checkboxes for Null; this only takes care of the + * checkboxes on currently displayed rows as the rows generated by + * "Continue insertion" are handled in the "Continue insertion" code + * + */ + $(document).on('click', 'input.checkbox_null', function () { + nullify( + // use hidden fields populated by tbl_change.php + $(this).siblings('.nullify_code').val(), + $(this).closest('tr').find('input:hidden').first().val(), + $(this).siblings('.hashed_field').val(), + $(this).siblings('.multi_edit').val() + ); + }); + + /** + * Reset the auto_increment column to 0 when selecting any of the + * insert options in submit_type-dropdown. Only perform the reset + * when we are in edit-mode, and not in insert-mode(no previous value + * available). + */ + $('select[name="submit_type"]').on('change', function () { + var thisElemSubmitTypeVal = $(this).val(); + var $table = $('table.insertRowTable'); + var autoIncrementColumn = $table.find('input[name^="auto_increment"]'); + autoIncrementColumn.each(function () { + var $thisElemAIField = $(this); + var thisElemName = $thisElemAIField.attr('name'); + + var prevValueField = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields_prev') + '"]'); + var valueField = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields') + '"]'); + var previousValue = $(prevValueField).val(); + if (previousValue !== undefined) { + if (thisElemSubmitTypeVal === 'insert' + || thisElemSubmitTypeVal === 'insertignore' + || thisElemSubmitTypeVal === 'showinsert' + ) { + $(valueField).val(0); + } else { + $(valueField).val(previousValue); + } + } + }); + }); + + /** + * Handle ENTER key when press on Continue insert with field + */ + $('#insert_rows').on('keypress', function (e) { + var key = e.which; + if (key === 13) { + addNewContinueInsertionFiels(e); + } + }); + + /** + * Continue Insertion form + */ + $(document).on('change', '#insert_rows', addNewContinueInsertionFiels); +}); + +function addNewContinueInsertionFiels (event) { + event.preventDefault(); + /** + * @var columnCount Number of number of columns table has. + */ + var columnCount = $('table.insertRowTable:first').find('tr').has('input[name*=\'fields_name\']').length; + /** + * @var curr_rows Number of current insert rows already on page + */ + var currRows = $('table.insertRowTable').length; + /** + * @var target_rows Number of rows the user wants + */ + var targetRows = $('#insert_rows').val(); + + // remove all datepickers + $('input.datefield, input.datetimefield').each(function () { + $(this).datepicker('destroy'); + }); + + if (currRows < targetRows) { + var tempIncrementIndex = function () { + var $thisElement = $(this); + /** + * Extract the index from the name attribute for all input/select fields and increment it + * name is of format funcs[multi_edit][10][<long random string of alphanum chars>] + */ + + /** + * @var this_name String containing name of the input/select elements + */ + var thisName = $thisElement.attr('name'); + /** split {@link thisName} at [10], so we have the parts that can be concatenated later */ + var nameParts = thisName.split(/\[\d+\]/); + /** extract the [10] from {@link nameParts} */ + var oldRowIndexString = thisName.match(/\[\d+\]/)[0]; + /** extract 10 - had to split into two steps to accomodate double digits */ + var oldRowIndex = parseInt(oldRowIndexString.match(/\d+/)[0], 10); + + /** calculate next index i.e. 11 */ + newRowIndex = oldRowIndex + 1; + /** generate the new name i.e. funcs[multi_edit][11][foobarbaz] */ + var newName = nameParts[0] + '[' + newRowIndex + ']' + nameParts[1]; + + var hashedField = nameParts[1].match(/\[(.+)\]/)[1]; + $thisElement.attr('name', newName); + + /** If element is select[name*='funcs'], update id */ + if ($thisElement.is('select[name*=\'funcs\']')) { + var thisId = $thisElement.attr('id'); + var idParts = thisId.split(/_/); + var oldIdIndex = idParts[1]; + var prevSelectedValue = $('#field_' + oldIdIndex + '_1').val(); + var newIdIndex = parseInt(oldIdIndex) + columnCount; + var newId = 'field_' + newIdIndex + '_1'; + $thisElement.attr('id', newId); + $thisElement.find('option').filter(function () { + return $(this).text() === prevSelectedValue; + }).attr('selected','selected'); + + // If salt field is there then update its id. + var nextSaltInput = $thisElement.parent().next('td').next('td').find('input[name*=\'salt\']'); + if (nextSaltInput.length !== 0) { + nextSaltInput.attr('id', 'salt_' + newId); + } + } + + // handle input text fields and textareas + if ($thisElement.is('.textfield') || $thisElement.is('.char') || $thisElement.is('textarea')) { + // do not remove the 'value' attribute for ENUM columns + // special handling for radio fields after updating ids to unique - see below + if ($thisElement.closest('tr').find('span.column_type').html() !== 'enum') { + $thisElement.val($thisElement.closest('tr').find('span.default_value').html()); + } + $thisElement + .off('change') + // Remove onchange attribute that was placed + // by tbl_change.php; it refers to the wrong row index + .attr('onchange', null) + // Keep these values to be used when the element + // will change + .data('hashed_field', hashedField) + .data('new_row_index', newRowIndex) + .on('change', function () { + var $changedElement = $(this); + verificationsAfterFieldChange( + $changedElement.data('hashed_field'), + $changedElement.data('new_row_index'), + $changedElement.closest('tr').find('span.column_type').html() + ); + }); + } + + if ($thisElement.is('.checkbox_null')) { + $thisElement + // this event was bound earlier by jQuery but + // to the original row, not the cloned one, so unbind() + .off('click') + // Keep these values to be used when the element + // will be clicked + .data('hashed_field', hashedField) + .data('new_row_index', newRowIndex) + .on('click', function () { + var $changedElement = $(this); + nullify( + $changedElement.siblings('.nullify_code').val(), + $thisElement.closest('tr').find('input:hidden').first().val(), + $changedElement.data('hashed_field'), + '[multi_edit][' + $changedElement.data('new_row_index') + ']' + ); + }); + } + }; + + var tempReplaceAnchor = function () { + var $anchor = $(this); + var newValue = 'rownumber=' + newRowIndex; + // needs improvement in case something else inside + // the href contains this pattern + var newHref = $anchor.attr('href').replace(/rownumber=\d+/, newValue); + $anchor.attr('href', newHref); + }; + + while (currRows < targetRows) { + /** + * @var $last_row Object referring to the last row + */ + var $lastRow = $('#insertForm').find('.insertRowTable:last'); + + // need to access this at more than one level + // (also needs improvement because it should be calculated + // just once per cloned row, not once per column) + var newRowIndex = 0; + + // Clone the insert tables + $lastRow + .clone(true, true) + .insertBefore('#actions_panel') + .find('input[name*=multi_edit],select[name*=multi_edit],textarea[name*=multi_edit]') + .each(tempIncrementIndex) + .end() + .find('.foreign_values_anchor') + .each(tempReplaceAnchor); + + // Insert/Clone the ignore checkboxes + if (currRows === 1) { + $('<input id="insert_ignore_1" type="checkbox" name="insert_ignore_1" checked="checked">') + .insertBefore('table.insertRowTable:last') + .after('<label for="insert_ignore_1">' + Messages.strIgnore + '</label>'); + } else { + /** + * @var $last_checkbox Object reference to the last checkbox in #insertForm + */ + var $lastCheckbox = $('#insertForm').children('input:checkbox:last'); + + /** name of {@link $lastCheckbox} */ + var lastCheckboxName = $lastCheckbox.attr('name'); + /** index of {@link $lastCheckbox} */ + var lastCheckboxIndex = parseInt(lastCheckboxName.match(/\d+/), 10); + /** name of new {@link $lastCheckbox} */ + var newName = lastCheckboxName.replace(/\d+/, lastCheckboxIndex + 1); + + $('<br><div class="clearfloat"></div>') + .insertBefore('table.insertRowTable:last'); + + $lastCheckbox + .clone() + .attr({ 'id': newName, 'name': newName }) + .prop('checked', true) + .insertBefore('table.insertRowTable:last'); + + $('label[for^=insert_ignore]:last') + .clone() + .attr('for', newName) + .insertBefore('table.insertRowTable:last'); + + $('<br>') + .insertBefore('table.insertRowTable:last'); + } + currRows++; + } + // recompute tabindex for text fields and other controls at footer; + // IMO it's not really important to handle the tabindex for + // function and Null + var tabIndex = 0; + $('.textfield, .char, textarea') + .each(function () { + tabIndex++; + $(this).attr('tabindex', tabIndex); + // update the IDs of textfields to ensure that they are unique + $(this).attr('id', 'field_' + tabIndex + '_3'); + + // special handling for radio fields after updating ids to unique + if ($(this).closest('tr').find('span.column_type').html() === 'enum') { + if ($(this).val() === $(this).closest('tr').find('span.default_value').html()) { + $(this).prop('checked', true); + } else { + $(this).prop('checked', false); + } + } + }); + $('.control_at_footer') + .each(function () { + tabIndex++; + $(this).attr('tabindex', tabIndex); + }); + } else if (currRows > targetRows) { + /** + * Displays alert if data loss possible on decrease + * of rows. + */ + var checkLock = jQuery.isEmptyObject(AJAX.lockedTargets); + if (checkLock || confirm(Messages.strConfirmRowChange) === true) { + while (currRows > targetRows) { + $('input[id^=insert_ignore]:last') + .nextUntil('fieldset') + .addBack() + .remove(); + currRows--; + } + } else { + document.getElementById('insert_rows').value = currRows; + } + } + // Add all the required datepickers back + Functions.addDateTimePicker(); +} + +// eslint-disable-next-line no-unused-vars +function changeValueFieldType (elem, searchIndex) { + var fieldsValue = $('select#fieldID_' + searchIndex); + if (0 === fieldsValue.size()) { + return; + } + + var type = $(elem).val(); + if ('IN (...)' === type || + 'NOT IN (...)' === type || + 'BETWEEN' === type || + 'NOT BETWEEN' === type + ) { + $('#fieldID_' + searchIndex).attr('multiple', ''); + } else { + $('#fieldID_' + searchIndex).removeAttr('multiple'); + } +} diff --git a/srcs/phpmyadmin/js/table/chart.js b/srcs/phpmyadmin/js/table/chart.js new file mode 100644 index 0000000..2a6f43f --- /dev/null +++ b/srcs/phpmyadmin/js/table/chart.js @@ -0,0 +1,425 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ + +/* global ColumnType, DataTable, JQPlotChartFactory */ // js/chart.js +/* global codeMirrorEditor */ // js/functions.js + +var chartData = {}; +var tempChartTitle; + +var currentChart = null; +var currentSettings = null; + +var dateTimeCols = []; +var numericCols = []; + +function extractDate (dateString) { + var matches; + var match; + var dateTimeRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/; + var dateRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2}/; + + matches = dateTimeRegExp.exec(dateString); + if (matches !== null && matches.length > 0) { + match = matches[0]; + return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2), match.substr(11, 2), match.substr(14, 2), match.substr(17, 2)); + } else { + matches = dateRegExp.exec(dateString); + if (matches !== null && matches.length > 0) { + match = matches[0]; + return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2)); + } + } + return null; +} + +function queryChart (data, columnNames, settings) { + if ($('#querychart').length === 0) { + return; + } + + var plotSettings = { + title : { + text : settings.title, + escapeHtml: true + }, + grid : { + drawBorder : false, + shadow : false, + background : 'rgba(0,0,0,0)' + }, + legend : { + show : true, + placement : 'outsideGrid', + location : 'e', + rendererOptions: { + numberColumns: 2 + } + }, + axes : { + xaxis : { + label : Functions.escapeHtml(settings.xaxisLabel) + }, + yaxis : { + label : settings.yaxisLabel + } + }, + stackSeries : settings.stackSeries + }; + + // create the chart + var factory = new JQPlotChartFactory(); + var chart = factory.createChart(settings.type, 'querychart'); + + // create the data table and add columns + var dataTable = new DataTable(); + if (settings.type === 'timeline') { + dataTable.addColumn(ColumnType.DATE, columnNames[settings.mainAxis]); + } else if (settings.type === 'scatter') { + dataTable.addColumn(ColumnType.NUMBER, columnNames[settings.mainAxis]); + } else { + dataTable.addColumn(ColumnType.STRING, columnNames[settings.mainAxis]); + } + + var i; + var values = []; + if (settings.seriesColumn === null) { + $.each(settings.selectedSeries, function (index, element) { + dataTable.addColumn(ColumnType.NUMBER, columnNames[element]); + }); + + // set data to the data table + var columnsToExtract = [settings.mainAxis]; + $.each(settings.selectedSeries, function (index, element) { + columnsToExtract.push(element); + }); + var newRow; + var row; + var col; + for (i = 0; i < data.length; i++) { + row = data[i]; + newRow = []; + for (var j = 0; j < columnsToExtract.length; j++) { + col = columnNames[columnsToExtract[j]]; + if (j === 0) { + if (settings.type === 'timeline') { // first column is date type + newRow.push(extractDate(row[col])); + } else if (settings.type === 'scatter') { + newRow.push(parseFloat(row[col])); + } else { // first column is string type + newRow.push(row[col]); + } + } else { // subsequent columns are of type, number + newRow.push(parseFloat(row[col])); + } + } + values.push(newRow); + } + dataTable.setData(values); + } else { + var seriesNames = {}; + var seriesNumber = 1; + var seriesColumnName = columnNames[settings.seriesColumn]; + for (i = 0; i < data.length; i++) { + if (! seriesNames[data[i][seriesColumnName]]) { + seriesNames[data[i][seriesColumnName]] = seriesNumber; + seriesNumber++; + } + } + + $.each(seriesNames, function (seriesName) { + dataTable.addColumn(ColumnType.NUMBER, seriesName); + }); + + var valueMap = {}; + var xValue; + var value; + var mainAxisName = columnNames[settings.mainAxis]; + var valueColumnName = columnNames[settings.valueColumn]; + for (i = 0; i < data.length; i++) { + xValue = data[i][mainAxisName]; + value = valueMap[xValue]; + if (! value) { + value = [xValue]; + valueMap[xValue] = value; + } + seriesNumber = seriesNames[data[i][seriesColumnName]]; + value[seriesNumber] = parseFloat(data[i][valueColumnName]); + } + + $.each(valueMap, function (index, value) { + values.push(value); + }); + dataTable.setData(values); + } + + // draw the chart and return the chart object + chart.draw(dataTable, plotSettings); + return chart; +} + +function drawChart () { + currentSettings.width = $('#resizer').width() - 20; + currentSettings.height = $('#resizer').height() - 20; + + // TODO: a better way using .redraw() ? + if (currentChart !== null) { + currentChart.destroy(); + } + + var columnNames = []; + $('select[name="chartXAxis"] option').each(function () { + columnNames.push(Functions.escapeHtml($(this).text())); + }); + try { + currentChart = queryChart(chartData, columnNames, currentSettings); + if (currentChart !== null) { + $('#saveChart').attr('href', currentChart.toImageString()); + } + } catch (err) { + Functions.ajaxShowMessage(err.message, false); + } +} + +function getSelectedSeries () { + var val = $('select[name="chartSeries"]').val() || []; + var ret = []; + $.each(val, function (i, v) { + ret.push(parseInt(v, 10)); + }); + return ret; +} + +function onXAxisChange () { + var $xAxisSelect = $('select[name="chartXAxis"]'); + currentSettings.mainAxis = parseInt($xAxisSelect.val(), 10); + if (dateTimeCols.indexOf(currentSettings.mainAxis) !== -1) { + $('span.span_timeline').show(); + } else { + $('span.span_timeline').hide(); + if (currentSettings.type === 'timeline') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + } + if (numericCols.indexOf(currentSettings.mainAxis) !== -1) { + $('span.span_scatter').show(); + } else { + $('span.span_scatter').hide(); + if (currentSettings.type === 'scatter') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + } + var xAxisTitle = $xAxisSelect.children('option:selected').text(); + $('input[name="xaxis_label"]').val(xAxisTitle); + currentSettings.xaxisLabel = xAxisTitle; +} + +function onDataSeriesChange () { + var $seriesSelect = $('select[name="chartSeries"]'); + currentSettings.selectedSeries = getSelectedSeries(); + var yAxisTitle; + if (currentSettings.selectedSeries.length === 1) { + $('span.span_pie').show(); + yAxisTitle = $seriesSelect.children('option:selected').text(); + } else { + $('span.span_pie').hide(); + if (currentSettings.type === 'pie') { + $('input#radio_line').prop('checked', true); + currentSettings.type = 'line'; + } + yAxisTitle = Messages.strYValues; + } + $('input[name="yaxis_label"]').val(yAxisTitle); + currentSettings.yaxisLabel = yAxisTitle; +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/chart.js', function () { + $('input[name="chartType"]').off('click'); + $('input[name="barStacked"]').off('click'); + $('input[name="chkAlternative"]').off('click'); + $('input[name="chartTitle"]').off('focus').off('keyup').off('blur'); + $('select[name="chartXAxis"]').off('change'); + $('select[name="chartSeries"]').off('change'); + $('select[name="chartSeriesColumn"]').off('change'); + $('select[name="chartValueColumn"]').off('change'); + $('input[name="xaxis_label"]').off('keyup'); + $('input[name="yaxis_label"]').off('keyup'); + $('#resizer').off('resizestop'); + $('#tblchartform').off('submit'); +}); + +AJAX.registerOnload('table/chart.js', function () { + // handle manual resize + $('#resizer').on('resizestop', function () { + // make room so that the handle will still appear + $('#querychart').height($('#resizer').height() * 0.96); + $('#querychart').width($('#resizer').width() * 0.96); + if (currentChart !== null) { + currentChart.redraw({ + resetAxes : true + }); + } + }); + + // handle chart type changes + $('input[name="chartType"]').on('click', function () { + var type = currentSettings.type = $(this).val(); + if (type === 'bar' || type === 'column' || type === 'area') { + $('span.barStacked').show(); + } else { + $('input[name="barStacked"]').prop('checked', false); + $.extend(true, currentSettings, { stackSeries : false }); + $('span.barStacked').hide(); + } + drawChart(); + }); + + // handle chosing alternative data format + $('input[name="chkAlternative"]').on('click', function () { + var $seriesColumn = $('select[name="chartSeriesColumn"]'); + var $valueColumn = $('select[name="chartValueColumn"]'); + var $chartSeries = $('select[name="chartSeries"]'); + if ($(this).is(':checked')) { + $seriesColumn.prop('disabled', false); + $valueColumn.prop('disabled', false); + $chartSeries.prop('disabled', true); + currentSettings.seriesColumn = parseInt($seriesColumn.val(), 10); + currentSettings.valueColumn = parseInt($valueColumn.val(), 10); + } else { + $seriesColumn.prop('disabled', true); + $valueColumn.prop('disabled', true); + $chartSeries.prop('disabled', false); + currentSettings.seriesColumn = null; + currentSettings.valueColumn = null; + } + drawChart(); + }); + + // handle stacking for bar, column and area charts + $('input[name="barStacked"]').on('click', function () { + if ($(this).is(':checked')) { + $.extend(true, currentSettings, { stackSeries : true }); + } else { + $.extend(true, currentSettings, { stackSeries : false }); + } + drawChart(); + }); + + // handle changes in chart title + $('input[name="chartTitle"]') + .on('focus', function () { + tempChartTitle = $(this).val(); + }) + .on('keyup', function () { + currentSettings.title = $('input[name="chartTitle"]').val(); + drawChart(); + }) + .on('blur', function () { + if ($(this).val() !== tempChartTitle) { + drawChart(); + } + }); + + // handle changing the x-axis + $('select[name="chartXAxis"]').on('change', function () { + onXAxisChange(); + drawChart(); + }); + + // handle changing the selected data series + $('select[name="chartSeries"]').on('change', function () { + onDataSeriesChange(); + drawChart(); + }); + + // handle changing the series column + $('select[name="chartSeriesColumn"]').on('change', function () { + currentSettings.seriesColumn = parseInt($(this).val(), 10); + drawChart(); + }); + + // handle changing the value column + $('select[name="chartValueColumn"]').on('change', function () { + currentSettings.valueColumn = parseInt($(this).val(), 10); + drawChart(); + }); + + // handle manual changes to the chart x-axis labels + $('input[name="xaxis_label"]').on('keyup', function () { + currentSettings.xaxisLabel = $(this).val(); + drawChart(); + }); + + // handle manual changes to the chart y-axis labels + $('input[name="yaxis_label"]').on('keyup', function () { + currentSettings.yaxisLabel = $(this).val(); + drawChart(); + }); + + // handler for ajax form submission + $('#tblchartform').submit(function () { + var $form = $(this); + if (codeMirrorEditor) { + $form[0].elements.sql_query.value = codeMirrorEditor.getValue(); + } + if (!Functions.checkSqlQuery($form[0])) { + return false; + } + + var $msgbox = Functions.ajaxShowMessage(); + Functions.prepareForAjaxRequest($form); + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && + data.success === true && + typeof data.chartData !== 'undefined') { + chartData = JSON.parse(data.chartData); + drawChart(); + Functions.ajaxRemoveMessage($msgbox); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }, 'json'); // end $.post() + + return false; + }); + + // from jQuery UI + $('#resizer').resizable({ + minHeight: 240, + minWidth: 300 + }) + .width($('#div_view_options').width() - 50) + .trigger('resizestop'); + + currentSettings = { + type : 'line', + width : $('#resizer').width() - 20, + height : $('#resizer').height() - 20, + xaxisLabel : $('input[name="xaxis_label"]').val(), + yaxisLabel : $('input[name="yaxis_label"]').val(), + title : $('input[name="chartTitle"]').val(), + stackSeries : false, + mainAxis : parseInt($('select[name="chartXAxis"]').val(), 10), + selectedSeries : getSelectedSeries(), + seriesColumn : null + }; + + var vals = $('input[name="dateTimeCols"]').val().split(' '); + $.each(vals, function (i, v) { + dateTimeCols.push(parseInt(v, 10)); + }); + + vals = $('input[name="numericCols"]').val().split(' '); + $.each(vals, function (i, v) { + numericCols.push(parseInt(v, 10)); + }); + + onXAxisChange(); + onDataSeriesChange(); + + $('#tblchartform').trigger('submit'); +}); diff --git a/srcs/phpmyadmin/js/table/find_replace.js b/srcs/phpmyadmin/js/table/find_replace.js new file mode 100644 index 0000000..9470ca7 --- /dev/null +++ b/srcs/phpmyadmin/js/table/find_replace.js @@ -0,0 +1,46 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/find_replace.js', function () { + $('#find_replace_form').off('submit'); + $('#toggle_find').off('click'); +}); + +/** + * Bind events + */ +AJAX.registerOnload('table/find_replace.js', function () { + $('<div id="toggle_find_div"><a id="toggle_find"></a></div>') + .insertAfter('#find_replace_form') + .hide(); + + $('#toggle_find') + .html(Messages.strHideFindNReplaceCriteria) + .on('click', function () { + var $link = $(this); + $('#find_replace_form').slideToggle(); + if ($link.text() === Messages.strHideFindNReplaceCriteria) { + $link.text(Messages.strShowFindNReplaceCriteria); + } else { + $link.text(Messages.strHideFindNReplaceCriteria); + } + return false; + }); + + $('#find_replace_form').submit(function (e) { + e.preventDefault(); + var findReplaceForm = $('#find_replace_form'); + Functions.prepareForAjaxRequest(findReplaceForm); + var $msgbox = Functions.ajaxShowMessage(); + $.post(findReplaceForm.attr('action'), findReplaceForm.serialize(), function (data) { + Functions.ajaxRemoveMessage($msgbox); + if (data.success === true) { + $('#toggle_find_div').show(); + $('#toggle_find').trigger('click'); + $('#sqlqueryresultsouter').html(data.preview); + } else { + $('#sqlqueryresultsouter').html(data.error); + } + }); + }); +}); diff --git a/srcs/phpmyadmin/js/table/gis_visualization.js b/srcs/phpmyadmin/js/table/gis_visualization.js new file mode 100644 index 0000000..da07fb7 --- /dev/null +++ b/srcs/phpmyadmin/js/table/gis_visualization.js @@ -0,0 +1,368 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used for visualizing GIS data + * + * @requires jquery + * @requires vendor/jquery/jquery.svg.js + * @requires vendor/jquery/jquery.mousewheel.js + * @requires vendor/jquery/jquery.event.drag-2.2.js + */ + +/* global drawOpenLayers */ // templates/table/gis_visualization/gis_visualization.twig + +// Constants +var zoomFactor = 1.5; +var defaultX = 0; +var defaultY = 0; + +// Variables +var x = 0; +var y = 0; +var scale = 1; + +var svg; + +/** + * Zooms and pans the visualization. + */ +function zoomAndPan () { + var g = svg.getElementById('groupPanel'); + if (!g) { + return; + } + + g.setAttribute('transform', 'translate(' + x + ', ' + y + ') scale(' + scale + ')'); + var id; + var circle; + $('circle.vector').each(function () { + id = $(this).attr('id'); + circle = svg.getElementById(id); + $(svg).on('change', circle, { + r : (3 / scale), + 'stroke-width' : (2 / scale) + }); + }); + + var line; + $('polyline.vector').each(function () { + id = $(this).attr('id'); + line = svg.getElementById(id); + $(svg).on('change', line, { + 'stroke-width' : (2 / scale) + }); + }); + + var polygon; + $('path.vector').each(function () { + id = $(this).attr('id'); + polygon = svg.getElementById(id); + $(svg).on('change', polygon, { + 'stroke-width' : (0.5 / scale) + }); + }); +} + +/** + * Initially loads either SVG or OSM visualization based on the choice. + */ +function selectVisualization () { + if ($('#choice').prop('checked') !== true) { + $('#openlayersmap').hide(); + } else { + $('#placeholder').hide(); + } +} + +/** + * Adds necessary styles to the div that coontains the openStreetMap. + */ +function styleOSM () { + var $placeholder = $('#placeholder'); + var cssObj = { + 'border' : '1px solid #aaa', + 'width' : $placeholder.width(), + 'height' : $placeholder.height(), + 'float' : 'right' + }; + $('#openlayersmap').css(cssObj); +} + +/** + * Loads the SVG element and make a reference to it. + */ +function loadSVG () { + var $placeholder = $('#placeholder'); + + $placeholder.svg({ + onLoad: function (svgRef) { + svg = svgRef; + } + }); + + // Removes the second SVG element unnecessarily added due to the above command + $placeholder.find('svg:nth-child(2)').remove(); +} + +/** + * Adds controllers for zooming and panning. + */ +function addZoomPanControllers () { + var $placeholder = $('#placeholder'); + if ($('#placeholder').find('svg').length > 0) { + var pmaThemeImage = $('#pmaThemeImage').val(); + // add panning arrows + $('<img class="button" id="left_arrow" src="' + pmaThemeImage + 'west-mini.png">').appendTo($placeholder); + $('<img class="button" id="right_arrow" src="' + pmaThemeImage + 'east-mini.png">').appendTo($placeholder); + $('<img class="button" id="up_arrow" src="' + pmaThemeImage + 'north-mini.png">').appendTo($placeholder); + $('<img class="button" id="down_arrow" src="' + pmaThemeImage + 'south-mini.png">').appendTo($placeholder); + // add zooming controls + $('<img class="button" id="zoom_in" src="' + pmaThemeImage + 'zoom-plus-mini.png">').appendTo($placeholder); + $('<img class="button" id="zoom_world" src="' + pmaThemeImage + 'zoom-world-mini.png">').appendTo($placeholder); + $('<img class="button" id="zoom_out" src="' + pmaThemeImage + 'zoom-minus-mini.png">').appendTo($placeholder); + } +} + +/** + * Resizes the GIS visualization to fit into the space available. + */ +function resizeGISVisualization () { + var $placeholder = $('#placeholder'); + var oldWidth = $placeholder.width(); + var visWidth = $('#div_view_options').width() - 48; + + // Assign new value for width + $placeholder.width(visWidth); + $('svg').attr('width', visWidth); + + // Assign the offset created due to resizing to defaultX and center the svg. + defaultX = (visWidth - oldWidth) / 2; + x = defaultX; + y = 0; + scale = 1; +} + +/** + * Initialize the GIS visualization. + */ +function initGISVisualization () { + // Loads either SVG or OSM visualization based on the choice + selectVisualization(); + // Resizes the GIS visualization to fit into the space available + resizeGISVisualization(); + if (typeof OpenLayers !== 'undefined') { + // Configure OpenLayers + // eslint-disable-next-line no-underscore-dangle + OpenLayers._getScriptLocation = function () { + return './js/vendor/openlayers/'; + }; + // Adds necessary styles to the div that coontains the openStreetMap + styleOSM(); + // Draws openStreetMap with openLayers + drawOpenLayers(); + } + // Loads the SVG element and make a reference to it + loadSVG(); + // Adds controllers for zooming and panning + addZoomPanControllers(); + zoomAndPan(); +} + +function getRelativeCoords (e) { + var position = $('#placeholder').offset(); + return { + x : e.pageX - position.left, + y : e.pageY - position.top + }; +} + +/** + * Ajax handlers for GIS visualization page + * + * Actions Ajaxified here: + * + * Zooming in and zooming out on mousewheel movement. + * Panning the visualization on dragging. + * Zooming in on double clicking. + * Zooming out on clicking the zoom out button. + * Panning on clicking the arrow buttons. + * Displaying tooltips for GIS objects. + */ + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/gis_visualization.js', function () { + $(document).off('click', '#choice'); + $(document).off('mousewheel', '#placeholder'); + $(document).off('dragstart', 'svg'); + $(document).off('mouseup', 'svg'); + $(document).off('drag', 'svg'); + $(document).off('dblclick', '#placeholder'); + $(document).off('click', '#zoom_in'); + $(document).off('click', '#zoom_world'); + $(document).off('click', '#zoom_out'); + $(document).off('click', '#left_arrow'); + $(document).off('click', '#right_arrow'); + $(document).off('click', '#up_arrow'); + $(document).off('click', '#down_arrow'); + $('.vector').off('mousemove').off('mouseout'); +}); + +AJAX.registerOnload('table/gis_visualization.js', function () { + // If we are in GIS visualization, initialize it + if ($('#gis_div').length > 0) { + initGISVisualization(); + } + + if (typeof OpenLayers === 'undefined') { + $('#choice, #labelChoice').hide(); + } + $(document).on('click', '#choice', function () { + if ($(this).prop('checked') === false) { + $('#placeholder').show(); + $('#openlayersmap').hide(); + } else { + $('#placeholder').hide(); + $('#openlayersmap').show(); + } + }); + + $(document).on('mousewheel', '#placeholder', function (event, delta) { + event.preventDefault(); + var relCoords = getRelativeCoords(event); + if (delta > 0) { + // zoom in + scale *= zoomFactor; + // zooming in keeping the position under mouse pointer unmoved. + x = relCoords.x - (relCoords.x - x) * zoomFactor; + y = relCoords.y - (relCoords.y - y) * zoomFactor; + zoomAndPan(); + } else { + // zoom out + scale /= zoomFactor; + // zooming out keeping the position under mouse pointer unmoved. + x = relCoords.x - (relCoords.x - x) / zoomFactor; + y = relCoords.y - (relCoords.y - y) / zoomFactor; + zoomAndPan(); + } + return true; + }); + + var dragX = 0; + var dragY = 0; + + $(document).on('dragstart', 'svg', function (event, dd) { + $('#placeholder').addClass('placeholderDrag'); + dragX = Math.round(dd.offsetX); + dragY = Math.round(dd.offsetY); + }); + + $(document).on('mouseup', 'svg', function () { + $('#placeholder').removeClass('placeholderDrag'); + }); + + $(document).on('drag', 'svg', function (event, dd) { + var newX = Math.round(dd.offsetX); + x += newX - dragX; + dragX = newX; + var newY = Math.round(dd.offsetY); + y += newY - dragY; + dragY = newY; + zoomAndPan(); + }); + + $(document).on('dblclick', '#placeholder', function (event) { + scale *= zoomFactor; + // zooming in keeping the position under mouse pointer unmoved. + var relCoords = getRelativeCoords(event); + x = relCoords.x - (relCoords.x - x) * zoomFactor; + y = relCoords.y - (relCoords.y - y) * zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_in', function (e) { + e.preventDefault(); + // zoom in + scale *= zoomFactor; + + var $placeholder = $('#placeholder').find('svg'); + var width = $placeholder.attr('width'); + var height = $placeholder.attr('height'); + // zooming in keeping the center unmoved. + x = width / 2 - (width / 2 - x) * zoomFactor; + y = height / 2 - (height / 2 - y) * zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_world', function (e) { + e.preventDefault(); + scale = 1; + x = defaultX; + y = defaultY; + zoomAndPan(); + }); + + $(document).on('click', '#zoom_out', function (e) { + e.preventDefault(); + // zoom out + scale /= zoomFactor; + + var $placeholder = $('#placeholder').find('svg'); + var width = $placeholder.attr('width'); + var height = $placeholder.attr('height'); + // zooming out keeping the center unmoved. + x = width / 2 - (width / 2 - x) / zoomFactor; + y = height / 2 - (height / 2 - y) / zoomFactor; + zoomAndPan(); + }); + + $(document).on('click', '#left_arrow', function (e) { + e.preventDefault(); + x += 100; + zoomAndPan(); + }); + + $(document).on('click', '#right_arrow', function (e) { + e.preventDefault(); + x -= 100; + zoomAndPan(); + }); + + $(document).on('click', '#up_arrow', function (e) { + e.preventDefault(); + y += 100; + zoomAndPan(); + }); + + $(document).on('click', '#down_arrow', function (e) { + e.preventDefault(); + y -= 100; + zoomAndPan(); + }); + + /** + * Detect the mousemove event and show tooltips. + */ + $('.vector').on('mousemove', function (event) { + var contents = $.trim(Functions.escapeHtml($(this).attr('name'))); + $('#tooltip').remove(); + if (contents !== '') { + $('<div id="tooltip">' + contents + '</div>').css({ + position : 'absolute', + top : event.pageY + 10, + left : event.pageX + 10, + border : '1px solid #fdd', + padding : '2px', + 'background-color' : '#fee', + opacity : 0.90 + }).appendTo('body').fadeIn(200); + } + }); + + /** + * Detect the mouseout event and hide tooltips. + */ + $('.vector').on('mouseout', function () { + $('#tooltip').remove(); + }); +}); diff --git a/srcs/phpmyadmin/js/table/operations.js b/srcs/phpmyadmin/js/table/operations.js new file mode 100644 index 0000000..9bd2af8 --- /dev/null +++ b/srcs/phpmyadmin/js/table/operations.js @@ -0,0 +1,321 @@ +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/operations.js', function () { + $(document).off('submit', '#copyTable.ajax'); + $(document).off('submit', '#moveTableForm'); + $(document).off('submit', '#tableOptionsForm'); + $(document).off('submit', '#partitionsForm'); + $(document).off('click', '#tbl_maintenance li a.maintain_action.ajax'); + $(document).off('click', '#drop_tbl_anchor.ajax'); + $(document).off('click', '#drop_view_anchor.ajax'); + $(document).off('click', '#truncate_tbl_anchor.ajax'); +}); + +/** + * jQuery coding for 'Table operations'. Used on tbl_operations.php + * Attach Ajax Event handlers for Table operations + */ +AJAX.registerOnload('table/operations.js', function () { + /** + *Ajax action for submitting the "Copy table" + **/ + $(document).on('submit', '#copyTable.ajax', function (event) { + event.preventDefault(); + var $form = $(this); + Functions.prepareForAjaxRequest($form); + var argsep = CommonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'submit_copy=Go', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + if ($form.find('input[name=\'switch_to_new\']').prop('checked')) { + CommonParams.set( + 'db', + $form.find('select[name=\'target_db\']').val() + ); + CommonParams.set( + 'table', + $form.find('input[name=\'new_name\']').val() + ); + CommonActions.refreshMain(false, function () { + Functions.ajaxShowMessage(data.message); + }); + } else { + Functions.ajaxShowMessage(data.message); + } + // Refresh navigation when the table is copied + Navigation.reload(); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // end $.post() + });// end of copyTable ajax submit + + /** + *Ajax action for submitting the "Move table" + */ + $(document).on('submit', '#moveTableForm', function (event) { + event.preventDefault(); + var $form = $(this); + Functions.prepareForAjaxRequest($form); + var argsep = CommonParams.get('arg_separator'); + $.post($form.attr('action'), $form.serialize() + argsep + 'submit_move=1', function (data) { + if (typeof data !== 'undefined' && data.success === true) { + CommonParams.set('db', data.params.db); + CommonParams.set('table', data.params.table); + CommonActions.refreshMain('tbl_sql.php', function () { + Functions.ajaxShowMessage(data.message); + }); + // Refresh navigation when the table is copied + Navigation.reload(); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); + + /** + * Ajax action for submitting the "Table options" + */ + $(document).on('submit', '#tableOptionsForm', function (event) { + event.preventDefault(); + event.stopPropagation(); + var $form = $(this); + var $tblNameField = $form.find('input[name=new_name]'); + var $tblCollationField = $form.find('select[name=tbl_collation]'); + var collationOrigValue = $('select[name="tbl_collation"] option[selected]').val(); + var $changeAllColumnCollationsCheckBox = $('#checkbox_change_all_collations'); + var question = Messages.strChangeAllColumnCollationsWarning; + + if ($tblNameField.val() !== $tblNameField[0].defaultValue) { + // reload page and navigation if the table has been renamed + Functions.prepareForAjaxRequest($form); + + if ($tblCollationField.val() !== collationOrigValue && $changeAllColumnCollationsCheckBox.is(':checked')) { + $form.confirm(question, $form.attr('action'), function () { + submitOptionsForm(); + }); + } else { + submitOptionsForm(); + } + } else { + if ($tblCollationField.val() !== collationOrigValue && $changeAllColumnCollationsCheckBox.is(':checked')) { + $form.confirm(question, $form.attr('action'), function () { + $form.removeClass('ajax').trigger('submit').addClass('ajax'); + }); + } else { + $form.removeClass('ajax').trigger('submit').addClass('ajax'); + } + } + + function submitOptionsForm () { + $.post($form.attr('action'), $form.serialize(), function (data) { + if (typeof data !== 'undefined' && data.success === true) { + CommonParams.set('table', data.params.table); + CommonActions.refreshMain(false, function () { + $('#page_content').html(data.message); + Functions.highlightSql($('#page_content')); + }); + // Refresh navigation when the table is renamed + Navigation.reload(); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + }); + + /** + *Ajax events for actions in the "Table maintenance" + **/ + $(document).on('click', '#tbl_maintenance li a.maintain_action.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } + if ($('.result_query').length !== 0) { + $('.result_query').remove(); + } + // variables which stores the common attributes + var params = $.param({ + 'ajax_request': 1, + 'server': CommonParams.get('server') + }); + var postData = $link.getPostData(); + if (postData) { + params += CommonParams.get('arg_separator') + postData; + } + + $.post($link.attr('href'), params, function (data) { + function scrollToTop () { + $('html, body').animate({ scrollTop: 0 }); + } + var $tempDiv; + if (typeof data !== 'undefined' && data.success === true && data.sql_query !== undefined) { + Functions.ajaxShowMessage(data.message); + $('<div class=\'sqlqueryresults ajax\'></div>').prependTo('#page_content'); + $('.sqlqueryresults').html(data.sql_query); + Functions.highlightSql($('#page_content')); + scrollToTop(); + } else if (typeof data !== 'undefined' && data.success === true) { + $tempDiv = $('<div id=\'temp_div\'></div>'); + $tempDiv.html(data.message); + var $success = $tempDiv.find('.result_query .success'); + Functions.ajaxShowMessage($success); + $('<div class=\'sqlqueryresults ajax\'></div>').prependTo('#page_content'); + $('.sqlqueryresults').html(data.message); + Functions.highlightSql($('#page_content')); + Functions.initSlider(); + $('.sqlqueryresults').children('fieldset,br').remove(); + scrollToTop(); + } else { + $tempDiv = $('<div id=\'temp_div\'></div>'); + $tempDiv.html(data.error); + + var $error; + if ($tempDiv.find('.error code').length !== 0) { + $error = $tempDiv.find('.error code').addClass('error'); + } else { + $error = $tempDiv; + } + + Functions.ajaxShowMessage($error, false); + } + }); // end $.post() + });// end of table maintenance ajax click + + /** + * Ajax action for submitting the "Partition Maintenance" + * Also, asks for confirmation when DROP partition is submitted + */ + $(document).on('submit', '#partitionsForm', function (event) { + event.preventDefault(); + var $form = $(this); + + function submitPartitionMaintenance () { + var argsep = CommonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true'; + Functions.ajaxShowMessage(Messages.strProcessingRequest); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + + if ($('#partition_operation_DROP').is(':checked')) { + $form.confirm(Messages.strDropPartitionWarning, $form.attr('action'), function () { + submitPartitionMaintenance(); + }); + } else if ($('#partition_operation_TRUNCATE').is(':checked')) { + $form.confirm(Messages.strTruncatePartitionWarning, $form.attr('action'), function () { + submitPartitionMaintenance(); + }); + } else { + submitPartitionMaintenance(); + } + }); + + $(document).on('click', '#drop_tbl_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = Messages.strDropTableStrongWarning + ' '; + question += Functions.sprintf( + Messages.strDoYouReally, + 'DROP TABLE `' + Functions.escapeHtml(CommonParams.get('db')) + '`.`' + Functions.escapeHtml(CommonParams.get('table') + '`') + ) + Functions.getForeignKeyCheckboxLoader(); + + $(this).confirm(question, $(this).attr('href'), function (url) { + var $msgbox = Functions.ajaxShowMessage(Messages.strProcessingRequest); + + var params = Functions.getJsConfirmCommonParam(this, $link.getPostData()); + + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + Functions.ajaxRemoveMessage($msgbox); + // Table deleted successfully, refresh both the frames + Navigation.reload(); + CommonParams.set('table', ''); + CommonActions.refreshMain( + CommonParams.get('opendb_url'), + function () { + Functions.ajaxShowMessage(data.message); + } + ); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // end $.post() + }, Functions.loadForeignKeyCheckbox); + }); // end of Drop Table Ajax action + + $(document).on('click', '#drop_view_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = Messages.strDropTableStrongWarning + ' '; + question += Functions.sprintf( + Messages.strDoYouReally, + 'DROP VIEW `' + Functions.escapeHtml(CommonParams.get('table') + '`') + ); + + $(this).confirm(question, $(this).attr('href'), function (url) { + var $msgbox = Functions.ajaxShowMessage(Messages.strProcessingRequest); + var params = Functions.getJsConfirmCommonParam(this, $link.getPostData()); + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + Functions.ajaxRemoveMessage($msgbox); + // Table deleted successfully, refresh both the frames + Navigation.reload(); + CommonParams.set('table', ''); + CommonActions.refreshMain( + CommonParams.get('opendb_url'), + function () { + Functions.ajaxShowMessage(data.message); + } + ); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // end $.post() + }); + }); // end of Drop View Ajax action + + $(document).on('click', '#truncate_tbl_anchor.ajax', function (event) { + event.preventDefault(); + var $link = $(this); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = Messages.strTruncateTableStrongWarning + ' '; + question += Functions.sprintf( + Messages.strDoYouReally, + 'TRUNCATE `' + Functions.escapeHtml(CommonParams.get('db')) + '`.`' + Functions.escapeHtml(CommonParams.get('table') + '`') + ) + Functions.getForeignKeyCheckboxLoader(); + $(this).confirm(question, $(this).attr('href'), function (url) { + Functions.ajaxShowMessage(Messages.strProcessingRequest); + + var params = Functions.getJsConfirmCommonParam(this, $link.getPostData()); + + $.post(url, params, function (data) { + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } + if ($('.result_query').length !== 0) { + $('.result_query').remove(); + } + if (typeof data !== 'undefined' && data.success === true) { + Functions.ajaxShowMessage(data.message); + $('<div class=\'sqlqueryresults ajax\'></div>').prependTo('#page_content'); + $('.sqlqueryresults').html(data.sql_query); + Functions.highlightSql($('#page_content')); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // end $.post() + }, Functions.loadForeignKeyCheckbox); + }); // end of Truncate Table Ajax action +}); // end $(document).ready for 'Table operations' diff --git a/srcs/phpmyadmin/js/table/relation.js b/srcs/phpmyadmin/js/table/relation.js new file mode 100644 index 0000000..600dfc1 --- /dev/null +++ b/srcs/phpmyadmin/js/table/relation.js @@ -0,0 +1,248 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * for tbl_relation.php + * + */ + +var TableRelation = {}; + +TableRelation.showHideClauses = function ($thisDropdown) { + if ($thisDropdown.val() === '') { + $thisDropdown.parent().nextAll('span').hide(); + } else { + if ($thisDropdown.is('select[name^="destination_foreign_column"]')) { + $thisDropdown.parent().nextAll('span').show(); + } + } +}; + +/** + * Sets dropdown options to values + */ +TableRelation.setDropdownValues = function ($dropdown, values, selectedValue) { + $dropdown.empty(); + var optionsAsString = ''; + // add an empty string to the beginning for empty selection + values.unshift(''); + $.each(values, function () { + optionsAsString += '<option value=\'' + Functions.escapeHtml(this) + '\'' + (selectedValue === Functions.escapeHtml(this) ? ' selected=\'selected\'' : '') + '>' + Functions.escapeHtml(this) + '</option>'; + }); + $dropdown.append($(optionsAsString)); +}; + +/** + * Retrieves and populates dropdowns to the left based on the selected value + * + * @param $dropdown the dropdown whose value got changed + */ +TableRelation.getDropdownValues = function ($dropdown) { + var foreignDb = null; + var foreignTable = null; + var $databaseDd; + var $tableDd; + var $columnDd; + var foreign = ''; + // if the changed dropdown is for foreign key constraints + if ($dropdown.is('select[name^="destination_foreign"]')) { + $databaseDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_db"]'); + $tableDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_table"]'); + $columnDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_column"]'); + foreign = '_foreign'; + } else { // internal relations + $databaseDd = $dropdown.parent().find('select[name^="destination_db"]'); + $tableDd = $dropdown.parent().find('select[name^="destination_table"]'); + $columnDd = $dropdown.parent().find('select[name^="destination_column"]'); + } + + // if the changed dropdown is a database selector + if ($dropdown.is('select[name^="destination' + foreign + '_db"]')) { + foreignDb = $dropdown.val(); + // if no database is selected empty table and column dropdowns + if (foreignDb === '') { + TableRelation.setDropdownValues($tableDd, []); + TableRelation.setDropdownValues($columnDd, []); + return; + } + } else { // if a table selector + foreignDb = $databaseDd.val(); + foreignTable = $dropdown.val(); + // if no table is selected empty the column dropdown + if (foreignTable === '') { + TableRelation.setDropdownValues($columnDd, []); + return; + } + } + var $msgbox = Functions.ajaxShowMessage(); + var $form = $dropdown.parents('form'); + var $db = $form.find('input[name="db"]').val(); + var $table = $form.find('input[name="table"]').val(); + var argsep = CommonParams.get('arg_separator'); + var params = 'getDropdownValues=true' + argsep + 'ajax_request=true' + + argsep + 'db=' + encodeURIComponent($db) + + argsep + 'table=' + encodeURIComponent($table) + + argsep + 'foreign=' + (foreign !== '') + + argsep + 'foreignDb=' + encodeURIComponent(foreignDb) + + (foreignTable !== null ? + argsep + 'foreignTable=' + encodeURIComponent(foreignTable) : '' + ); + var $server = $form.find('input[name="server"]'); + if ($server.length > 0) { + params += argsep + 'server=' + $form.find('input[name="server"]').val(); + } + $.ajax({ + type: 'POST', + url: 'tbl_relation.php', + data: params, + dataType: 'json', + success: function (data) { + Functions.ajaxRemoveMessage($msgbox); + if (typeof data !== 'undefined' && data.success) { + // if the changed dropdown is a database selector + if (foreignTable === null) { + // set values for table and column dropdowns + TableRelation.setDropdownValues($tableDd, data.tables); + TableRelation.setDropdownValues($columnDd, []); + } else { // if a table selector + // set values for the column dropdown + var primary = null; + if (typeof data.primary !== 'undefined' + && 1 === data.primary.length + ) { + primary = data.primary[0]; + } + TableRelation.setDropdownValues($columnDd.first(), data.columns, primary); + TableRelation.setDropdownValues($columnDd.slice(1), data.columns); + } + } else { + Functions.ajaxShowMessage(data.error, false); + } + } + }); +}; + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/relation.js', function () { + $('body').off('change', + 'select[name^="destination_db"], ' + + 'select[name^="destination_table"], ' + + 'select[name^="destination_foreign_db"], ' + + 'select[name^="destination_foreign_table"]' + ); + $('body').off('click', 'a.add_foreign_key_field'); + $('body').off('click', 'a.add_foreign_key'); + $('a.drop_foreign_key_anchor.ajax').off('click'); +}); + +AJAX.registerOnload('table/relation.js', function () { + /** + * Ajax event handler to fetch table/column dropdown values. + */ + $('body').on('change', + 'select[name^="destination_db"], ' + + 'select[name^="destination_table"], ' + + 'select[name^="destination_foreign_db"], ' + + 'select[name^="destination_foreign_table"]', + function () { + TableRelation.getDropdownValues($(this)); + } + ); + + /** + * Ajax event handler to add a column to a foreign key constraint. + */ + $('body').on('click', 'a.add_foreign_key_field', function (event) { + event.preventDefault(); + event.stopPropagation(); + + // Add field. + $(this) + .prev('span') + .clone(true, true) + .insertBefore($(this)) + .find('select') + .val(''); + + // Add foreign field. + var $sourceElem = $('select[name^="destination_foreign_column[' + + $(this).attr('data-index') + ']"]:last').parent(); + $sourceElem + .clone(true, true) + .insertAfter($sourceElem) + .find('select') + .val(''); + }); + + /** + * Ajax event handler to add a foreign key constraint. + */ + $('body').on('click', 'a.add_foreign_key', function (event) { + event.preventDefault(); + event.stopPropagation(); + + var $prevRow = $(this).closest('tr').prev('tr'); + var $newRow = $prevRow.clone(true, true); + + // Update serial number. + var currIndex = $newRow + .find('a.add_foreign_key_field') + .attr('data-index'); + var newIndex = parseInt(currIndex) + 1; + $newRow.find('a.add_foreign_key_field').attr('data-index', newIndex); + + // Update form parameter names. + $newRow.find('select[name^="foreign_key_fields_name"]:not(:first), ' + + 'select[name^="destination_foreign_column"]:not(:first)' + ).each(function () { + $(this).parent().remove(); + }); + $newRow.find('input, select').each(function () { + $(this).attr('name', + $(this).attr('name').replace(/\d/, newIndex) + ); + }); + $newRow.find('input[type="text"]').each(function () { + $(this).val(''); + }); + // Finally add the row. + $newRow.insertAfter($prevRow); + }); + + /** + * Ajax Event handler for 'Drop Foreign key' + */ + $('a.drop_foreign_key_anchor.ajax').on('click', function (event) { + event.preventDefault(); + var $anchor = $(this); + + // Object containing reference to the current field's row + var $currRow = $anchor.parents('tr'); + + var dropQuery = Functions.escapeHtml( + $currRow.children('td') + .children('.drop_foreign_key_msg') + .val() + ); + + var question = Functions.sprintf(Messages.strDoYouReally, dropQuery); + + $anchor.confirm(question, $anchor.attr('href'), function (url) { + var $msg = Functions.ajaxShowMessage(Messages.strDroppingForeignKey, false); + var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData()); + $.post(url, params, function (data) { + if (data.success === true) { + Functions.ajaxRemoveMessage($msg); + CommonActions.refreshMain(false, function () { + // Do nothing + }); + } else { + Functions.ajaxShowMessage(Messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); + }); // end Drop Foreign key + + var windowWidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowWidth - 35) + 'px'); +}); diff --git a/srcs/phpmyadmin/js/table/select.js b/srcs/phpmyadmin/js/table/select.js new file mode 100644 index 0000000..3050e3c --- /dev/null +++ b/srcs/phpmyadmin/js/table/select.js @@ -0,0 +1,409 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview JavaScript functions used on tbl_select.php + * + * @requires jQuery + * @requires js/functions.js + */ + +/* global changeValueFieldType */ // js/table/change.js +/* global openGISEditor, gisEditorLoaded, loadJSAndGISEditor, loadGISEditor */ // js/gis_data_editor.js + +var TableSelect = {}; + +/** + * Checks if given data-type is numeric or date. + * + * @param {string} dataType Column data-type + * + * @return {(boolean|string)} + */ +TableSelect.checkIfDataTypeNumericOrDate = function (dataType) { + // To test for numeric data-types. + var numericRegExp = new RegExp( + 'TINYINT|SMALLINT|MEDIUMINT|INT|BIGINT|DECIMAL|FLOAT|DOUBLE|REAL', + 'i' + ); + + // To test for date data-types. + var dateRegExp = new RegExp( + 'DATETIME|DATE|TIMESTAMP|TIME|YEAR', + 'i' + ); + + // Return matched data-type + if (numericRegExp.test(dataType)) { + return numericRegExp.exec(dataType)[0]; + } + + if (dateRegExp.test(dataType)) { + return dateRegExp.exec(dataType)[0]; + } + + return false; +}; + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/select.js', function () { + $('#togglesearchformlink').off('click'); + $(document).off('submit', '#tbl_search_form.ajax'); + $('select.geom_func').off('change'); + $(document).off('click', 'span.open_search_gis_editor'); + $('body').off('change', 'select[name*="criteriaColumnOperators"]'); // Fix for bug #13778, changed 'click' to 'change' +}); + +AJAX.registerOnload('table/select.js', function () { + /** + * Prepare a div containing a link, otherwise it's incorrectly displayed + * after a couple of clicks + */ + $('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>') + .insertAfter('#tbl_search_form') + // don't show it until we have results on-screen + .hide(); + + $('#togglesearchformlink') + .html(Messages.strShowSearchCriteria) + .on('click', function () { + var $link = $(this); + $('#tbl_search_form').slideToggle(); + if ($link.text() === Messages.strHideSearchCriteria) { + $link.text(Messages.strShowSearchCriteria); + } else { + $link.text(Messages.strHideSearchCriteria); + } + // avoid default click action + return false; + }); + + var tableRows = $('#fieldset_table_qbe select'); + $.each(tableRows, function (index, item) { + $(item).on('change', function () { + changeValueFieldType(this, index); + }); + }); + + /** + * Ajax event handler for Table search + */ + $(document).on('submit', '#tbl_search_form.ajax', function (event) { + var unaryFunctions = [ + 'IS NULL', + 'IS NOT NULL', + '= \'\'', + '!= \'\'' + ]; + + var geomUnaryFunctions = [ + 'IsEmpty', + 'IsSimple', + 'IsRing', + 'IsClosed', + ]; + + // jQuery object to reuse + var $searchForm = $(this); + event.preventDefault(); + + // empty previous search results while we are waiting for new results + $('#sqlqueryresultsouter').empty(); + var $msgbox = Functions.ajaxShowMessage(Messages.strSearching, false); + + Functions.prepareForAjaxRequest($searchForm); + + var values = {}; + $searchForm.find(':input').each(function () { + var $input = $(this); + if ($input.attr('type') === 'checkbox' || $input.attr('type') === 'radio') { + if ($input.is(':checked')) { + values[this.name] = $input.val(); + } + } else { + values[this.name] = $input.val(); + } + }); + var columnCount = $('select[name="columnsToDisplay[]"] option').length; + // Submit values only for the columns that have unary column operator or a search criteria + for (var a = 0; a < columnCount; a++) { + if ($.inArray(values['criteriaColumnOperators[' + a + ']'], unaryFunctions) >= 0) { + continue; + } + + if (values['geom_func[' + a + ']'] && + $.isArray(values['geom_func[' + a + ']'], geomUnaryFunctions) >= 0) { + continue; + } + + if (values['criteriaValues[' + a + ']'] === '' || values['criteriaValues[' + a + ']'] === null) { + delete values['criteriaValues[' + a + ']']; + delete values['criteriaColumnOperators[' + a + ']']; + delete values['criteriaColumnNames[' + a + ']']; + delete values['criteriaColumnTypes[' + a + ']']; + delete values['criteriaColumnCollations[' + a + ']']; + } + } + // If all columns are selected, use a single parameter to indicate that + if (values['columnsToDisplay[]'] !== null) { + if (values['columnsToDisplay[]'].length === columnCount) { + delete values['columnsToDisplay[]']; + values.displayAllColumns = true; + } + } else { + values.displayAllColumns = true; + } + + $.post($searchForm.attr('action'), values, function (data) { + Functions.ajaxRemoveMessage($msgbox); + if (typeof data !== 'undefined' && data.success === true) { + if (typeof data.sql_query !== 'undefined') { // zero rows + $('#sqlqueryresultsouter').html(data.sql_query); + } else { // results found + $('#sqlqueryresultsouter').html(data.message); + $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns'); + } + $('#tbl_search_form') + // workaround for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome. + .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(); + // needed for the display options slider in the results + Functions.initSlider(); + $('html, body').animate({ scrollTop: 0 }, 'fast'); + } else { + $('#sqlqueryresultsouter').html(data.error); + } + Functions.highlightSql($('#sqlqueryresultsouter')); + }); // end $.post() + }); + + // Following section is related to the 'function based search' for geometry data types. + // Initialy hide all the open_gis_editor spans + $('span.open_search_gis_editor').hide(); + + $('select.geom_func').on('change', function () { + var $geomFuncSelector = $(this); + + var binaryFunctions = [ + 'Contains', + 'Crosses', + 'Disjoint', + 'Equals', + 'Intersects', + 'Overlaps', + 'Touches', + 'Within', + 'MBRContains', + 'MBRDisjoint', + 'MBREquals', + 'MBRIntersects', + 'MBROverlaps', + 'MBRTouches', + 'MBRWithin', + 'ST_Contains', + 'ST_Crosses', + 'ST_Disjoint', + 'ST_Equals', + 'ST_Intersects', + 'ST_Overlaps', + 'ST_Touches', + 'ST_Within' + ]; + + var tempArray = [ + 'Envelope', + 'EndPoint', + 'StartPoint', + 'ExteriorRing', + 'Centroid', + 'PointOnSurface' + ]; + var outputGeomFunctions = binaryFunctions.concat(tempArray); + + // If the chosen function takes two geometry objects as parameters + var $operator = $geomFuncSelector.parents('tr').find('td:nth-child(5)').find('select'); + if ($.inArray($geomFuncSelector.val(), binaryFunctions) >= 0) { + $operator.prop('readonly', true); + } else { + $operator.prop('readonly', false); + } + + // if the chosen function's output is a geometry, enable GIS editor + var $editorSpan = $geomFuncSelector.parents('tr').find('span.open_search_gis_editor'); + if ($.inArray($geomFuncSelector.val(), outputGeomFunctions) >= 0) { + $editorSpan.show(); + } else { + $editorSpan.hide(); + } + }); + + $(document).on('click', 'span.open_search_gis_editor', function (event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children('input[type=\'text\']').val(); + // Field name + var field = 'Parameter'; + // Column type + var geomFunc = $span.parents('tr').find('.geom_func').val(); + var type; + if (geomFunc === 'Envelope') { + type = 'polygon'; + } else if (geomFunc === 'ExteriorRing') { + type = 'linestring'; + } else { + type = 'point'; + } + // Names of input field and null checkbox + var inputName = $span.parent('td').children('input[type=\'text\']').attr('name'); + // Token + + openGISEditor(); + if (!gisEditorLoaded) { + loadJSAndGISEditor(value, field, type, inputName); + } else { + loadGISEditor(value, field, type, inputName); + } + }); + + /** + * Ajax event handler for Range-Search. + */ + $('body').on('change', 'select[name*="criteriaColumnOperators"]', function () { // Fix for bug #13778, changed 'click' to 'change' + var $sourceSelect = $(this); + // Get the column name. + var columnName = $(this) + .closest('tr') + .find('th:first') + .text(); + + // Get the data-type of column excluding size. + var dataType = $(this) + .closest('tr') + .find('td[data-type]') + .attr('data-type'); + dataType = TableSelect.checkIfDataTypeNumericOrDate(dataType); + + // Get the operator. + var operator = $(this).val(); + + if ((operator === 'BETWEEN' || operator === 'NOT BETWEEN') && dataType) { + var $msgbox = Functions.ajaxShowMessage(); + $.ajax({ + url: 'tbl_select.php', + type: 'POST', + data: { + 'server': CommonParams.get('server'), + 'ajax_request': 1, + 'db': $('input[name="db"]').val(), + 'table': $('input[name="table"]').val(), + 'column': columnName, + 'range_search': 1 + }, + success: function (response) { + Functions.ajaxRemoveMessage($msgbox); + if (response.success) { + // Get the column min value. + var min = response.column_data.min + ? '(' + Messages.strColumnMin + + ' ' + response.column_data.min + ')' + : ''; + // Get the column max value. + var max = response.column_data.max + ? '(' + Messages.strColumnMax + + ' ' + response.column_data.max + ')' + : ''; + var buttonOptions = {}; + buttonOptions[Messages.strGo] = function () { + var minValue = $('#min_value').val(); + var maxValue = $('#max_value').val(); + var finalValue = ''; + if (minValue.length && maxValue.length) { + finalValue = minValue + ', ' + + maxValue; + } + var $targetField = $sourceSelect.closest('tr') + .find('[name*="criteriaValues"]'); + + // If target field is a select list. + if ($targetField.is('select')) { + $targetField.val(finalValue); + var $options = $targetField.find('option'); + var $closestMin = null; + var $closestMax = null; + // Find closest min and max value. + $options.each(function () { + if ( + $closestMin === null + || Math.abs($(this).val() - minValue) < Math.abs($closestMin.val() - minValue) + ) { + $closestMin = $(this); + } + + if ( + $closestMax === null + || Math.abs($(this).val() - maxValue) < Math.abs($closestMax.val() - maxValue) + ) { + $closestMax = $(this); + } + }); + + $closestMin.attr('selected', 'selected'); + $closestMax.attr('selected', 'selected'); + } else { + $targetField.val(finalValue); + } + $(this).dialog('close'); + }; + buttonOptions[Messages.strCancel] = function () { + $(this).dialog('close'); + }; + + // Display dialog box. + $('<div></div>').append( + '<fieldset>' + + '<legend>' + operator + '</legend>' + + '<label for="min_value">' + Messages.strMinValue + + '</label>' + + '<input type="text" id="min_value">' + '<br>' + + '<span class="small_font">' + min + '</span>' + '<br>' + + '<label for="max_value">' + Messages.strMaxValue + + '</label>' + + '<input type="text" id="max_value">' + '<br>' + + '<span class="small_font">' + max + '</span>' + + '</fieldset>' + ).dialog({ + width: 'auto', + maxHeight: 400, + modal: true, + buttons: buttonOptions, + title: Messages.strRangeSearch, + open: function () { + // Add datepicker wherever required. + Functions.addDatepicker($('#min_value'), dataType); + Functions.addDatepicker($('#max_value'), dataType); + }, + close: function () { + $(this).remove(); + } + }); + } else { + Functions.ajaxShowMessage(response.error); + } + }, + error: function () { + Functions.ajaxShowMessage(Messages.strErrorProcessingRequest); + } + }); + } + }); + var windowWidth = $(window).width(); + $('.jsresponsive').css('max-width', (windowWidth - 69) + 'px'); +}); diff --git a/srcs/phpmyadmin/js/table/structure.js b/srcs/phpmyadmin/js/table/structure.js new file mode 100644 index 0000000..8be0671 --- /dev/null +++ b/srcs/phpmyadmin/js/table/structure.js @@ -0,0 +1,505 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview functions used on the table structure page + * @name Table Structure + * + * @requires jQuery + * @requires jQueryUI + * @required js/functions.js + */ + +// eslint-disable-next-line no-unused-vars +/* global primaryIndexes:writable, indexes:writable, fulltextIndexes:writable, spatialIndexes:writable */ // js/functions.js +/* global sprintf */ // js/vendor/sprintf.js + +/** + * AJAX scripts for tbl_structure.php + * + * Actions ajaxified here: + * Drop Column + * Add Primary Key + * Drop Primary Key/Index + * + */ + +/** + * Reload fields table + */ +function reloadFieldForm () { + $.post($('#fieldsForm').attr('action'), $('#fieldsForm').serialize() + CommonParams.get('arg_separator') + 'ajax_request=true', function (formData) { + var $tempDiv = $('<div id=\'temp_div\'><div>').append(formData.message); + $('#fieldsForm').replaceWith($tempDiv.find('#fieldsForm')); + $('#addColumns').replaceWith($tempDiv.find('#addColumns')); + $('#move_columns_dialog').find('ul').replaceWith($tempDiv.find('#move_columns_dialog ul')); + $('#moveColumns').removeClass('move-active'); + }); + $('#page_content').show(); +} + +function checkFirst () { + if ($('select[name=after_field] option:selected').data('pos') === 'first') { + $('input[name=field_where]').val('first'); + } else { + $('input[name=field_where]').val('after'); + } +} +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/structure.js', function () { + $(document).off('click', 'a.drop_column_anchor.ajax'); + $(document).off('click', 'a.add_key.ajax'); + $(document).off('click', '#move_columns_anchor'); + $(document).off('click', '#printView'); + $(document).off('submit', '.append_fields_form.ajax'); + $('body').off('click', '#fieldsForm.ajax button[name="submit_mult"], #fieldsForm.ajax input[name="submit_mult"]'); + $(document).off('click', 'a[name^=partition_action].ajax'); + $(document).off('click', '#remove_partitioning.ajax'); +}); + +AJAX.registerOnload('table/structure.js', function () { + // Re-initialize variables. + primaryIndexes = []; + indexes = []; + fulltextIndexes = []; + spatialIndexes = []; + + /** + *Ajax action for submitting the "Column Change" and "Add Column" form + */ + $('.append_fields_form.ajax').off(); + $(document).on('submit', '.append_fields_form.ajax', function (event) { + event.preventDefault(); + /** + * @var the_form object referring to the export form + */ + var $form = $(this); + var fieldCnt = $form.find('input[name=orig_num_fields]').val(); + + + function submitForm () { + var $msg = Functions.ajaxShowMessage(Messages.strProcessingRequest); + $.post($form.attr('action'), $form.serialize() + CommonParams.get('arg_separator') + 'do_save_data=1', function (data) { + if ($('.sqlqueryresults').length !== 0) { + $('.sqlqueryresults').remove(); + } else if ($('.error:not(.tab)').length !== 0) { + $('.error:not(.tab)').remove(); + } + if (typeof data.success !== 'undefined' && data.success === true) { + $('#page_content') + .empty() + .append(data.message) + .show(); + Functions.highlightSql($('#page_content')); + $('.result_query .notice').remove(); + reloadFieldForm(); + $form.remove(); + Functions.ajaxRemoveMessage($msg); + Functions.initSlider(); + Navigation.reload(); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // end $.post() + } + + function checkIfConfirmRequired ($form) { + var i = 0; + var id; + var elm; + var val; + var nameOrig; + var elmOrig; + var valOrig; + var checkRequired = false; + for (i = 0; i < fieldCnt; i++) { + id = '#field_' + i + '_5'; + elm = $(id); + val = elm.val(); + + nameOrig = 'input[name=field_collation_orig\\[' + i + '\\]]'; + elmOrig = $form.find(nameOrig); + valOrig = elmOrig.val(); + + if (val && valOrig && val !== valOrig) { + checkRequired = true; + break; + } + } + return checkRequired; + } + + /* + * First validate the form; if there is a problem, avoid submitting it + * + * Functions.checkTableEditForm() needs a pure element and not a jQuery object, + * this is why we pass $form[0] as a parameter (the jQuery object + * is actually an array of DOM elements) + */ + if (Functions.checkTableEditForm($form[0], fieldCnt)) { + // OK, form passed validation step + + Functions.prepareForAjaxRequest($form); + if (Functions.checkReservedWordColumns($form)) { + // User wants to submit the form + + // If Collation is changed, Warn and Confirm + if (checkIfConfirmRequired($form)) { + var question = sprintf( + Messages.strChangeColumnCollation, 'https://wiki.phpmyadmin.net/pma/Garbled_data' + ); + $form.confirm(question, $form.attr('action'), function () { + submitForm(); + }); + } else { + submitForm(); + } + } + } + }); // end change table button "do_save_data" + + /** + * Attach Event Handler for 'Drop Column' + */ + $(document).on('click', 'a.drop_column_anchor.ajax', function (event) { + event.preventDefault(); + /** + * @var curr_table_name String containing the name of the current table + */ + var currTableName = $(this).closest('form').find('input[name=table]').val(); + /** + * @var curr_row Object reference to the currently selected row (i.e. field in the table) + */ + var $currRow = $(this).parents('tr'); + /** + * @var curr_column_name String containing name of the field referred to by {@link curr_row} + */ + var currColumnName = $currRow.children('th').children('label').text().trim(); + currColumnName = Functions.escapeHtml(currColumnName); + /** + * @var $after_field_item Corresponding entry in the 'After' field. + */ + var $afterFieldItem = $('select[name=\'after_field\'] option[value=\'' + currColumnName + '\']'); + /** + * @var question String containing the question to be asked for confirmation + */ + var question = Functions.sprintf(Messages.strDoYouReally, 'ALTER TABLE `' + Functions.escapeHtml(currTableName) + '` DROP `' + Functions.escapeHtml(currColumnName) + '`;'); + var $thisAnchor = $(this); + $thisAnchor.confirm(question, $thisAnchor.attr('href'), function (url) { + var $msg = Functions.ajaxShowMessage(Messages.strDroppingColumn, false); + var params = Functions.getJsConfirmCommonParam(this, $thisAnchor.getPostData()); + params += CommonParams.get('arg_separator') + 'ajax_page_request=1'; + $.post(url, params, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + Functions.ajaxRemoveMessage($msg); + if ($('.result_query').length) { + $('.result_query').remove(); + } + if (data.sql_query) { + $('<div class="result_query"></div>') + .html(data.sql_query) + .prependTo('#structure_content'); + Functions.highlightSql($('#page_content')); + } + // Adjust the row numbers + for (var $row = $currRow.next(); $row.length > 0; $row = $row.next()) { + var newVal = parseInt($row.find('td:nth-child(2)').text(), 10) - 1; + $row.find('td:nth-child(2)').text(newVal); + } + $afterFieldItem.remove(); + $currRow.hide('medium').remove(); + + // Remove the dropped column from select menu for 'after field' + $('select[name=after_field]').find( + '[value="' + currColumnName + '"]' + ).remove(); + + // by default select the (new) last option to add new column + // (in case last column is dropped) + $('select[name=after_field] option:last').attr('selected','selected'); + + // refresh table stats + if (data.tableStat) { + $('#tablestatistics').html(data.tableStat); + } + // refresh the list of indexes (comes from sql.php) + $('.index_info').replaceWith(data.indexes_list); + Navigation.reload(); + } else { + Functions.ajaxShowMessage(Messages.strErrorProcessingRequest + ' : ' + data.error, false); + } + }); // end $.post() + }); + }); // end of Drop Column Anchor action + + /** + * Attach Event Handler for 'Print' link + */ + $(document).on('click', '#printView', function (event) { + event.preventDefault(); + + // Take to preview mode + Functions.printPreview(); + }); // end of Print View action + + /** + * Ajax Event handler for adding keys + */ + $(document).on('click', 'a.add_key.ajax', function (event) { + event.preventDefault(); + + var $this = $(this); + var currTableName = $this.closest('form').find('input[name=table]').val(); + var currColumnName = $this.parents('tr').children('th').children('label').text().trim(); + + var addClause = ''; + if ($this.is('.add_primary_key_anchor')) { + addClause = 'ADD PRIMARY KEY'; + } else if ($this.is('.add_index_anchor')) { + addClause = 'ADD INDEX'; + } else if ($this.is('.add_unique_anchor')) { + addClause = 'ADD UNIQUE'; + } else if ($this.is('.add_spatial_anchor')) { + addClause = 'ADD SPATIAL'; + } else if ($this.is('.add_fulltext_anchor')) { + addClause = 'ADD FULLTEXT'; + } + var question = Functions.sprintf(Messages.strDoYouReally, 'ALTER TABLE `' + + Functions.escapeHtml(currTableName) + '` ' + addClause + '(`' + Functions.escapeHtml(currColumnName) + '`);'); + + var $thisAnchor = $(this); + + $thisAnchor.confirm(question, $thisAnchor.attr('href'), function (url) { + Functions.ajaxShowMessage(); + AJAX.source = $this; + + var params = Functions.getJsConfirmCommonParam(this, $thisAnchor.getPostData()); + params += CommonParams.get('arg_separator') + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); + }); // end Add key + + /** + * Inline move columns + **/ + $(document).on('click', '#move_columns_anchor', function (e) { + e.preventDefault(); + + if ($(this).hasClass('move-active')) { + return; + } + + /** + * @var button_options Object that stores the options passed to jQueryUI + * dialog + */ + var buttonOptions = {}; + + buttonOptions[Messages.strGo] = function (event) { + event.preventDefault(); + var $msgbox = Functions.ajaxShowMessage(); + var $this = $(this); + var $form = $this.find('form'); + var serialized = $form.serialize(); + // check if any columns were moved at all + if (serialized === $form.data('serialized-unmoved')) { + Functions.ajaxRemoveMessage($msgbox); + $this.dialog('close'); + return; + } + $.post($form.prop('action'), serialized + CommonParams.get('arg_separator') + 'ajax_request=true', function (data) { + if (data.success === false) { + Functions.ajaxRemoveMessage($msgbox); + $this + .clone() + .html(data.error) + .dialog({ + title: $(this).prop('title'), + height: 230, + width: 900, + modal: true, + buttons: buttonOptionsError + }); // end dialog options + } else { + // sort the fields table + var $fieldsTable = $('table#tablestructure tbody'); + // remove all existing rows and remember them + var $rows = $fieldsTable.find('tr').remove(); + // loop through the correct order + for (var i in data.columns) { + var theColumn = data.columns[i]; + var $theRow = $rows + .find('input:checkbox[value=\'' + theColumn + '\']') + .closest('tr'); + // append the row for this column to the table + $fieldsTable.append($theRow); + } + var $firstrow = $fieldsTable.find('tr').eq(0); + // Adjust the row numbers and colors + for (var $row = $firstrow; $row.length > 0; $row = $row.next()) { + $row + .find('td:nth-child(2)') + .text($row.index() + 1) + .end() + .removeClass('odd even') + .addClass($row.index() % 2 === 0 ? 'odd' : 'even'); + } + Functions.ajaxShowMessage(data.message); + $this.dialog('close'); + } + }); + }; + buttonOptions[Messages.strPreviewSQL] = function () { + // Function for Previewing SQL + var $form = $('#move_column_form'); + Functions.previewSql($form); + }; + buttonOptions[Messages.strCancel] = function () { + $(this).dialog('close'); + }; + + var buttonOptionsError = {}; + buttonOptionsError[Messages.strOK] = function () { + $(this).dialog('close').remove(); + }; + + var columns = []; + + $('#tablestructure').find('tbody tr').each(function () { + var colName = $(this).find('input:checkbox').eq(0).val(); + var hiddenInput = $('<input>') + .prop({ + name: 'move_columns[]', + type: 'hidden' + }) + .val(colName); + columns[columns.length] = $('<li></li>') + .addClass('placeholderDrag') + .text(colName) + .append(hiddenInput); + }); + + var colList = $('#move_columns_dialog').find('ul') + .find('li').remove().end(); + for (var i in columns) { + colList.append(columns[i]); + } + colList.sortable({ + axis: 'y', + containment: $('#move_columns_dialog').find('div'), + tolerance: 'pointer' + }).disableSelection(); + var $form = $('#move_columns_dialog').find('form'); + $form.data('serialized-unmoved', $form.serialize()); + + $('#move_columns_dialog').dialog({ + modal: true, + buttons: buttonOptions, + open: function () { + if ($('#move_columns_dialog').parents('.ui-dialog').height() > $(window).height()) { + $('#move_columns_dialog').dialog('option', 'height', $(window).height()); + } + }, + beforeClose: function () { + $('#move_columns_anchor').removeClass('move-active'); + } + }); + }); + + /** + * Handles multi submits in table structure page such as change, browse, drop, primary etc. + */ + $('body').on('click', '#fieldsForm.ajax button[name="submit_mult"], #fieldsForm.ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.parents('form'); + var argsep = CommonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + Functions.ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + }); + + /** + * Handles clicks on Action links in partition table + */ + $(document).on('click', 'a[name^=partition_action].ajax', function (e) { + e.preventDefault(); + var $link = $(this); + + function submitPartitionAction (url) { + var params = 'ajax_request=true&ajax_page_request=true&' + $link.getPostData(); + Functions.ajaxShowMessage(); + AJAX.source = $link; + $.post(url, params, AJAX.responseHandler); + } + + if ($link.is('#partition_action_DROP')) { + $link.confirm(Messages.strDropPartitionWarning, $link.attr('href'), function (url) { + submitPartitionAction(url); + }); + } else if ($link.is('#partition_action_TRUNCATE')) { + $link.confirm(Messages.strTruncatePartitionWarning, $link.attr('href'), function (url) { + submitPartitionAction(url); + }); + } else { + submitPartitionAction($link.attr('href')); + } + }); + + /** + * Handles remove partitioning + */ + $(document).on('click', '#remove_partitioning.ajax', function (e) { + e.preventDefault(); + var $link = $(this); + var question = Messages.strRemovePartitioningWarning; + $link.confirm(question, $link.attr('href'), function (url) { + var params = Functions.getJsConfirmCommonParam({ + 'ajax_request' : true, + 'ajax_page_request' : true + }, $link.getPostData()); + Functions.ajaxShowMessage(); + AJAX.source = $link; + $.post(url, params, AJAX.responseHandler); + }); + }); + + $(document).on('change', 'select[name=after_field]', function () { + checkFirst(); + }); +}); + +/** Handler for "More" dropdown in structure table rows */ +AJAX.registerOnload('table/structure.js', function () { + var windowwidth = $(window).width(); + if (windowwidth > 768) { + if (! $('#fieldsForm').hasClass('HideStructureActions')) { + $('.table-structure-actions').width(function () { + var width = 5; + $(this).find('li').each(function () { + width += $(this).outerWidth(true); + }); + return width; + }); + } + } + + $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px'); + var tableRows = $('.central_columns'); + $.each(tableRows, function (index, item) { + if ($(item).hasClass('add_button')) { + $(item).on('click', function () { + $('input:checkbox').prop('checked', false); + $('#checkbox_row_' + (index + 1)).prop('checked', true); + $('button[value=add_to_central_columns]').trigger('click'); + }); + } else { + $(item).on('click', function () { + $('input:checkbox').prop('checked', false); + $('#checkbox_row_' + (index + 1)).prop('checked', true); + $('button[value=remove_from_central_columns]').trigger('click'); + }); + } + }); +}); diff --git a/srcs/phpmyadmin/js/table/tracking.js b/srcs/phpmyadmin/js/table/tracking.js new file mode 100644 index 0000000..7d2d943 --- /dev/null +++ b/srcs/phpmyadmin/js/table/tracking.js @@ -0,0 +1,106 @@ +/** + * Unbind all event handlers before tearing down the page + */ +AJAX.registerTeardown('table/tracking.js', function () { + $('body').off('click', '#versionsForm.ajax button[name="submit_mult"], #versionsForm.ajax input[name="submit_mult"]'); + $('body').off('click', 'a.delete_version_anchor.ajax'); + $('body').off('click', 'a.delete_entry_anchor.ajax'); +}); + +/** + * Bind event handlers + */ +AJAX.registerOnload('table/tracking.js', function () { + $('#versions tr:first th').append($('<div class="sorticon"></div>')); + $('#versions').tablesorter({ + sortList: [[1, 0]], + headers: { + 0: { sorter: false }, + 1: { sorter: 'integer' }, + 5: { sorter: false }, + 6: { sorter: false } + } + }); + + if ($('#ddl_versions tbody tr').length > 0) { + $('#ddl_versions tr:first th').append($('<div class="sorticon"></div>')); + $('#ddl_versions').tablesorter({ + sortList: [[0, 0]], + headers: { + 0: { sorter: 'integer' }, + 3: { sorter: false }, + 4: { sorter: false } + } + }); + } + + if ($('#dml_versions tbody tr').length > 0) { + $('#dml_versions tr:first th').append($('<div class="sorticon"></div>')); + $('#dml_versions').tablesorter({ + sortList: [[0, 0]], + headers: { + 0: { sorter: 'integer' }, + 3: { sorter: false }, + 4: { sorter: false } + } + }); + } + + /** + * Handles multi submit for tracking versions + */ + $('body').on('click', '#versionsForm.ajax button[name="submit_mult"], #versionsForm.ajax input[name="submit_mult"]', function (e) { + e.preventDefault(); + var $button = $(this); + var $form = $button.parent('form'); + var argsep = CommonParams.get('arg_separator'); + var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val(); + + if ($button.val() === 'delete_version') { + var question = Messages.strDeleteTrackingVersionMultiple; + $button.confirm(question, $form.attr('action'), function (url) { + Functions.ajaxShowMessage(); + AJAX.source = $form; + $.post(url, submitData, AJAX.responseHandler); + }); + } else { + Functions.ajaxShowMessage(); + AJAX.source = $form; + $.post($form.attr('action'), submitData, AJAX.responseHandler); + } + }); + + /** + * Ajax Event handler for 'Delete version' + */ + $('body').on('click', 'a.delete_version_anchor.ajax', function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = Messages.strDeleteTrackingVersion; + $anchor.confirm(question, $anchor.attr('href'), function (url) { + Functions.ajaxShowMessage(); + AJAX.source = $anchor; + var argSep = CommonParams.get('arg_separator'); + var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData()); + params += argSep + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); + }); + + /** + * Ajax Event handler for 'Delete tracking report entry' + */ + $('body').on('click', 'a.delete_entry_anchor.ajax', function (e) { + e.preventDefault(); + var $anchor = $(this); + var question = Messages.strDeletingTrackingEntry; + $anchor.confirm(question, $anchor.attr('href'), function (url) { + Functions.ajaxShowMessage(); + AJAX.source = $anchor; + var argSep = CommonParams.get('arg_separator'); + var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData()); + params += argSep + 'ajax_page_request=1'; + $.post(url, params, AJAX.responseHandler); + }); + }); +}); diff --git a/srcs/phpmyadmin/js/table/zoom_plot_jqplot.js b/srcs/phpmyadmin/js/table/zoom_plot_jqplot.js new file mode 100644 index 0000000..659e6a7 --- /dev/null +++ b/srcs/phpmyadmin/js/table/zoom_plot_jqplot.js @@ -0,0 +1,607 @@ +// TODO: change the axis +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + ** @fileoverview JavaScript functions used on tbl_select.php + ** + ** @requires jQuery + ** @requires js/functions.js + **/ + + +/** + ** Display Help/Info + **/ +function displayHelp () { + $('<div></div>') + .append(Messages.strDisplayHelp) + .appendTo('#page_content') + .dialog({ + width: 450, + height: 'auto', + title: Messages.strHelpTitle + }); + return false; +} + +/** + ** Extend the array object for max function + ** @param array + **/ +Array.max = function (array) { + return Math.max.apply(Math, array); +}; + +/** + ** Extend the array object for min function + ** @param array + **/ +Array.min = function (array) { + return Math.min.apply(Math, array); +}; + +/** + ** Checks if a string contains only numeric value + ** @param n: String (to be checked) + **/ +function isNumeric (n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} + +/** + ** Checks if an object is empty + ** @param n: Object (to be checked) + **/ +function isEmpty (obj) { + var name; + for (name in obj) { + return false; + } + return true; +} + +/** + ** Converts a date/time into timestamp + ** @param val String Date + ** @param type Sring Field type(datetime/timestamp/time/date) + **/ +function getTimeStamp (val, type) { + if (type.toString().search(/datetime/i) !== -1 || + type.toString().search(/timestamp/i) !== -1 + ) { + return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', val); + } else if (type.toString().search(/time/i) !== -1) { + return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', '1970-01-01 ' + val); + } else if (type.toString().search(/date/i) !== -1) { + return $.datepicker.parseDate('yy-mm-dd', val); + } +} + +/** + ** Classifies the field type into numeric,timeseries or text + ** @param field: field type (as in database structure) + **/ +function getType (field) { + if (field.toString().search(/int/i) !== -1 || + field.toString().search(/decimal/i) !== -1 || + field.toString().search(/year/i) !== -1 + ) { + return 'numeric'; + } else if (field.toString().search(/time/i) !== -1 || + field.toString().search(/date/i) !== -1 + ) { + return 'time'; + } else { + return 'text'; + } +} + +/** + * Unbind all event handlers before tearing down a page + */ +AJAX.registerTeardown('table/zoom_plot_jqplot.js', function () { + $('#tableid_0').off('change'); + $('#tableid_1').off('change'); + $('#tableid_2').off('change'); + $('#tableid_3').off('change'); + $('#inputFormSubmitId').off('click'); + $('#togglesearchformlink').off('click'); + $(document).off('keydown', '#dataDisplay :input'); + $('button.button-reset').off('click'); + $('div#resizer').off('resizestop'); + $('div#querychart').off('jqplotDataClick'); +}); + +AJAX.registerOnload('table/zoom_plot_jqplot.js', function () { + var currentChart = null; + var searchedDataKey = null; + var xLabel = $('#tableid_0').val(); + var yLabel = $('#tableid_1').val(); + // will be updated via Ajax + var xType = $('#types_0').val(); + var yType = $('#types_1').val(); + var dataLabel = $('#dataLabel').val(); + + // Get query result + var searchedData; + try { + searchedData = JSON.parse($('#querydata').html()); + } catch (err) { + searchedData = null; + } + + /** + ** Input form submit on field change + **/ + + // first column choice corresponds to the X axis + $('#tableid_0').on('change', function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : CommonParams.get('server'), + 'db' : CommonParams.get('db'), + 'table' : CommonParams.get('table'), + 'field' : $('#tableid_0').val(), + 'it' : 0 + }, function (data) { + $('#tableFieldsId').find('tr:eq(1) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(1) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(1) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(1) td:eq(3)').html(data.field_value); + xLabel = $('#tableid_0').val(); + $('#types_0').val(data.field_type); + xType = data.field_type; + $('#collations_0').val(data.field_collations); + Functions.addDateTimePicker(); + }); + }); + + // second column choice corresponds to the Y axis + $('#tableid_1').on('change', function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : CommonParams.get('server'), + 'db' : CommonParams.get('db'), + 'table' : CommonParams.get('table'), + 'field' : $('#tableid_1').val(), + 'it' : 1 + }, function (data) { + $('#tableFieldsId').find('tr:eq(2) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(2) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(2) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(2) td:eq(3)').html(data.field_value); + yLabel = $('#tableid_1').val(); + $('#types_1').val(data.field_type); + yType = data.field_type; + $('#collations_1').val(data.field_collations); + Functions.addDateTimePicker(); + }); + }); + + $('#tableid_2').on('change', function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : CommonParams.get('server'), + 'db' : CommonParams.get('db'), + 'table' : CommonParams.get('table'), + 'field' : $('#tableid_2').val(), + 'it' : 2 + }, function (data) { + $('#tableFieldsId').find('tr:eq(4) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(4) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(4) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(4) td:eq(3)').html(data.field_value); + $('#types_2').val(data.field_type); + $('#collations_2').val(data.field_collations); + Functions.addDateTimePicker(); + }); + }); + + $('#tableid_3').on('change', function () { + // AJAX request for field type, collation, operators, and value field + $.post('tbl_zoom_select.php', { + 'ajax_request' : true, + 'change_tbl_info' : true, + 'server' : CommonParams.get('server'), + 'db' : CommonParams.get('db'), + 'table' : CommonParams.get('table'), + 'field' : $('#tableid_3').val(), + 'it' : 3 + }, function (data) { + $('#tableFieldsId').find('tr:eq(5) td:eq(0)').html(data.field_type); + $('#tableFieldsId').find('tr:eq(5) td:eq(1)').html(data.field_collation); + $('#tableFieldsId').find('tr:eq(5) td:eq(2)').html(data.field_operators); + $('#tableFieldsId').find('tr:eq(5) td:eq(3)').html(data.field_value); + $('#types_3').val(data.field_type); + $('#collations_3').val(data.field_collations); + Functions.addDateTimePicker(); + }); + }); + + /** + * Input form validation + **/ + $('#inputFormSubmitId').on('click', function () { + if ($('#tableid_0').get(0).selectedIndex === 0 || $('#tableid_1').get(0).selectedIndex === 0) { + Functions.ajaxShowMessage(Messages.strInputNull); + } else if (xLabel === yLabel) { + Functions.ajaxShowMessage(Messages.strSameInputs); + } + }); + + /** + ** Prepare a div containing a link, otherwise it's incorrectly displayed + ** after a couple of clicks + **/ + $('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>') + .insertAfter('#zoom_search_form') + // don't show it until we have results on-screen + .hide(); + + $('#togglesearchformlink') + .html(Messages.strShowSearchCriteria) + .on('click', function () { + var $link = $(this); + $('#zoom_search_form').slideToggle(); + if ($link.text() === Messages.strHideSearchCriteria) { + $link.text(Messages.strShowSearchCriteria); + } else { + $link.text(Messages.strHideSearchCriteria); + } + // avoid default click action + return false; + }); + + /** + ** Set dialog properties for the data display form + **/ + var buttonOptions = {}; + /* + * Handle saving of a row in the editor + */ + buttonOptions[Messages.strSave] = function () { + // Find changed values by comparing form values with selectedRow Object + var newValues = {};// Stores the values changed from original + var sqlTypes = {}; + var it = 0; + var xChange = false; + var yChange = false; + var key; + var tempGetVal = function () { + return $(this).val(); + }; + for (key in selectedRow) { + var oldVal = selectedRow[key]; + var newVal = ($('#edit_fields_null_id_' + it).prop('checked')) ? null : $('#edit_fieldID_' + it).val(); + if (newVal instanceof Array) { // when the column is of type SET + newVal = $('#edit_fieldID_' + it).map(tempGetVal).get().join(','); + } + if (oldVal !== newVal) { + selectedRow[key] = newVal; + newValues[key] = newVal; + if (key === xLabel) { + xChange = true; + searchedData[searchedDataKey][xLabel] = newVal; + } else if (key === yLabel) { + yChange = true; + searchedData[searchedDataKey][yLabel] = newVal; + } + } + var $input = $('#edit_fieldID_' + it); + if ($input.hasClass('bit')) { + sqlTypes[key] = 'bit'; + } else { + sqlTypes[key] = null; + } + it++; + } // End data update + + // Update the chart series and replot + if (xChange || yChange) { + // Logic similar to plot generation, replot only if xAxis changes or yAxis changes. + // Code includes a lot of checks so as to replot only when necessary + if (xChange) { + xCord[searchedDataKey] = selectedRow[xLabel]; + // [searchedDataKey][0] contains the x value + if (xType === 'numeric') { + series[0][searchedDataKey][0] = selectedRow[xLabel]; + } else if (xType === 'time') { + series[0][searchedDataKey][0] = + getTimeStamp(selectedRow[xLabel], $('#types_0').val()); + } else { + series[0][searchedDataKey][0] = ''; + // TODO: text values + } + currentChart.series[0].data = series[0]; + // TODO: axis changing + currentChart.replot(); + } + if (yChange) { + yCord[searchedDataKey] = selectedRow[yLabel]; + // [searchedDataKey][1] contains the y value + if (yType === 'numeric') { + series[0][searchedDataKey][1] = selectedRow[yLabel]; + } else if (yType === 'time') { + series[0][searchedDataKey][1] = + getTimeStamp(selectedRow[yLabel], $('#types_1').val()); + } else { + series[0][searchedDataKey][1] = ''; + // TODO: text values + } + currentChart.series[0].data = series[0]; + // TODO: axis changing + currentChart.replot(); + } + } // End plot update + + // Generate SQL query for update + if (!isEmpty(newValues)) { + var sqlQuery = 'UPDATE `' + CommonParams.get('table') + '` SET '; + for (key in newValues) { + sqlQuery += '`' + key + '`='; + var value = newValues[key]; + + // null + if (value === null) { + sqlQuery += 'NULL, '; + + // empty + } else if ($.trim(value) === '') { + sqlQuery += '\'\', '; + + // other + } else { + // type explicitly identified + if (sqlTypes[key] !== null) { + if (sqlTypes[key] === 'bit') { + sqlQuery += 'b\'' + value + '\', '; + } + // type not explicitly identified + } else { + if (!isNumeric(value)) { + sqlQuery += '\'' + value + '\', '; + } else { + sqlQuery += value + ', '; + } + } + } + } + // remove two extraneous characters ', ' + sqlQuery = sqlQuery.substring(0, sqlQuery.length - 2); + sqlQuery += ' WHERE ' + Sql.urlDecode(searchedData[searchedDataKey].where_clause); + + // Post SQL query to sql.php + $.post('sql.php', { + 'server' : CommonParams.get('server'), + 'db' : CommonParams.get('db'), + 'ajax_request' : true, + 'sql_query' : sqlQuery, + 'inline_edit' : false + }, function (data) { + if (typeof data !== 'undefined' && data.success === true) { + $('#sqlqueryresultsouter').html(data.sql_query); + Functions.highlightSql($('#sqlqueryresultsouter')); + } else { + Functions.ajaxShowMessage(data.error, false); + } + }); // End $.post + }// End database update + $('#dataDisplay').dialog('close'); + }; + buttonOptions[Messages.strCancel] = function () { + $(this).dialog('close'); + }; + $('#dataDisplay').dialog({ + autoOpen: false, + title: Messages.strDataPointContent, + modal: true, + buttons: buttonOptions, + width: $('#dataDisplay').width() + 80, + open: function () { + $(this).find('input[type=checkbox]').css('margin', '0.5em'); + } + }); + /** + * Attach Ajax event handlers for input fields + * in the dialog. Used to submit the Ajax + * request when the ENTER key is pressed. + */ + $(document).on('keydown', '#dataDisplay :input', function (e) { + if (e.which === 13) { // 13 is the ENTER key + e.preventDefault(); + if (typeof buttonOptions[Messages.strSave] === 'function') { + buttonOptions[Messages.strSave].call(); + } + } + }); + + + /* + * Generate plot using jqplot + */ + + if (searchedData !== null) { + $('#zoom_search_form') + .slideToggle() + .hide(); + $('#togglesearchformlink') + .text(Messages.strShowSearchCriteria); + $('#togglesearchformdiv').show(); + var selectedRow; + var series = []; + var xCord = []; + var yCord = []; + var xVal; + var yVal; + var format; + + var options = { + series: [ + // for a scatter plot + { showLine: false } + ], + grid: { + drawBorder: false, + shadow: false, + background: 'rgba(0,0,0,0)' + }, + axes: { + xaxis: { + label: $('#tableid_0').val(), + labelRenderer: $.jqplot.CanvasAxisLabelRenderer + }, + yaxis: { + label: $('#tableid_1').val(), + labelRenderer: $.jqplot.CanvasAxisLabelRenderer + } + }, + highlighter: { + show: true, + tooltipAxes: 'y', + yvalues: 2, + // hide the first y value + formatString: '<span class="hide">%s</span>%s' + }, + cursor: { + show: true, + zoom: true, + showTooltip: false + } + }; + + // If data label is not set, do not show tooltips + if (dataLabel === '') { + options.highlighter.show = false; + } + + // Classify types as either numeric,time,text + xType = getType(xType); + yType = getType(yType); + + // could have multiple series but we'll have just one + series[0] = []; + + if (xType === 'time') { + var originalXType = $('#types_0').val(); + if (originalXType === 'date') { + format = '%Y-%m-%d'; + } + // TODO: does not seem to work + // else if (originalXType === 'time') { + // format = '%H:%M'; + // } else { + // format = '%Y-%m-%d %H:%M'; + // } + $.extend(options.axes.xaxis, { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: format + } + }); + } + if (yType === 'time') { + var originalYType = $('#types_1').val(); + if (originalYType === 'date') { + format = '%Y-%m-%d'; + } + $.extend(options.axes.yaxis, { + renderer: $.jqplot.DateAxisRenderer, + tickOptions: { + formatString: format + } + }); + } + + $.each(searchedData, function (key, value) { + if (xType === 'numeric') { + xVal = parseFloat(value[xLabel]); + } + if (xType === 'time') { + xVal = getTimeStamp(value[xLabel], originalXType); + } + if (yType === 'numeric') { + yVal = parseFloat(value[yLabel]); + } + if (yType === 'time') { + yVal = getTimeStamp(value[yLabel], originalYType); + } + series[0].push([ + xVal, + yVal, + // extra Y values + value[dataLabel], // for highlighter + // (may set an undefined value) + value.where_clause, // for click on point + key // key from searchedData + ]); + }); + + // under IE 8, the initial display is mangled; after a manual + // resizing, it's ok + // under IE 9, everything is fine + currentChart = $.jqplot('querychart', series, options); + currentChart.resetZoom(); + + $('button.button-reset').on('click', function (event) { + event.preventDefault(); + currentChart.resetZoom(); + }); + + $('div#resizer').resizable(); + $('div#resizer').on('resizestop', function () { + // make room so that the handle will still appear + $('div#querychart').height($('div#resizer').height() * 0.96); + $('div#querychart').width($('div#resizer').width() * 0.96); + currentChart.replot({ resetAxes: true }); + }); + + $('div#querychart').on('jqplotDataClick', + function (event, seriesIndex, pointIndex, data) { + searchedDataKey = data[4]; // key from searchedData (global) + var fieldId = 0; + var postParams = { + 'ajax_request' : true, + 'get_data_row' : true, + 'server' : CommonParams.get('server'), + 'db' : CommonParams.get('db'), + 'table' : CommonParams.get('table'), + 'where_clause' : data[3] + }; + + $.post('tbl_zoom_select.php', postParams, function (data) { + // Row is contained in data.row_info, + // now fill the displayResultForm with row values + var key; + for (key in data.row_info) { + var $field = $('#edit_fieldID_' + fieldId); + var $fieldNull = $('#edit_fields_null_id_' + fieldId); + if (data.row_info[key] === null) { + $fieldNull.prop('checked', true); + $field.val(''); + } else { + $fieldNull.prop('checked', false); + if ($field.attr('multiple')) { // when the column is of type SET + $field.val(data.row_info[key].split(',')); + } else { + $field.val(data.row_info[key]); + } + } + fieldId++; + } + selectedRow = data.row_info; + }); + + $('#dataDisplay').dialog('open'); + } + ); + } + + $('#help_dialog').on('click', function () { + displayHelp(); + }); +}); |
