1
0
mirror of https://github.com/vrana/adminer.git synced 2025-09-05 20:02:54 +02:00

Add drag-n-drop moving of rows in table editing

This commit is contained in:
Peter Knut
2024-10-18 16:27:38 +02:00
parent 25a1ccf75b
commit 18e26a3b68
6 changed files with 91 additions and 52 deletions

View File

@@ -418,7 +418,7 @@ class Adminer {
); );
echo "<div ", ($key != "" ? "" : "class='no-sort'"), ">", echo "<div ", ($key != "" ? "" : "class='no-sort'"), ">",
"<span class='jsonly handle'>=</span>"; "<span class='jsonly handle'></span>";
if ($functions || $grouping) { if ($functions || $grouping) {
echo "<select name='columns[$i][fun]'>", echo "<select name='columns[$i][fun]'>",
@@ -499,7 +499,7 @@ class Adminer {
if ($key != "" && $val == "") continue; if ($key != "" && $val == "") continue;
echo "<div ", ($key != "" ? "" : "class='no-sort'"), ">", echo "<div ", ($key != "" ? "" : "class='no-sort'"), ">",
"<span class='jsonly handle'>=</span>", "<span class='jsonly handle'></span>",
select_input("name='order[$i]'", $columns, $val, $key !== "" ? "selectFieldChange" : "selectAddRow"), select_input("name='order[$i]'", $columns, $val, $key !== "" ? "selectFieldChange" : "selectAddRow"),
checkbox("desc[$i]", 1, isset($_GET["desc"][$key]), lang('descending')), checkbox("desc[$i]", 1, isset($_GET["desc"][$key]), lang('descending')),
" <input type='image' src='../adminer/static/cross.gif' class='jsonly icon remove' title='" . h(lang('Remove')) . "' alt='x'>", " <input type='image' src='../adminer/static/cross.gif' class='jsonly icon remove' title='" . h(lang('Remove')) . "' alt='x'>",

View File

@@ -288,7 +288,14 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign
?> ?>
<thead><tr> <thead><tr>
<?php if ($type == "PROCEDURE") { ?><td></td><?php } ?> <?php
if (support("move_col")) {
echo "<td class='jsonly'></td>";
}
if ($type == "PROCEDURE") {
echo "<td></td>";
}
?>
<th id="label-name"><?php echo ($type == "TABLE" ? lang('Column name') : lang('Parameter name')); ?></th> <th id="label-name"><?php echo ($type == "TABLE" ? lang('Column name') : lang('Parameter name')); ?></th>
<td id="label-type"><?php echo lang('Type'); ?><textarea id="enum-edit" rows="4" cols="12" wrap="off" style="display: none;"></textarea><?php echo script("qs('#enum-edit').onblur = editingLengthBlur;"); ?></td> <td id="label-type"><?php echo lang('Type'); ?><textarea id="enum-edit" rows="4" cols="12" wrap="off" style="display: none;"></textarea><?php echo script("qs('#enum-edit').onblur = editingLengthBlur;"); ?></td>
<td id="label-length"><?php echo lang('Length'); ?></td> <td id="label-length"><?php echo lang('Length'); ?></td>
@@ -308,9 +315,9 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign
<?php } ?> <?php } ?>
<td><?php echo "<input type='image' class='icon' name='add[" . (support("move_col") ? 0 : count($fields)) . "]' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'>" . script("row_count = " . count($fields) . ";"); ?></td> <td><?php echo "<input type='image' class='icon' name='add[" . (support("move_col") ? 0 : count($fields)) . "]' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'>" . script("row_count = " . count($fields) . ";"); ?></td>
</tr></thead> </tr></thead>
<tbody>
<?php <?php
echo script("mixin(qsl('tbody'), {onclick: editingClick, onkeydown: editingKeydown, oninput: editingInput});"); echo "<tbody>\n";
foreach ($fields as $i => $field) { foreach ($fields as $i => $field) {
$i++; $i++;
$orig = $field[($_POST ? "orig" : "field")]; $orig = $field[($_POST ? "orig" : "field")];
@@ -319,6 +326,9 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign
$style = $display ? "" : "style='display: none;'"; $style = $display ? "" : "style='display: none;'";
echo "<tr $style>\n"; echo "<tr $style>\n";
if (support("move_col")) {
echo "<th class='handle jsonly'></th>";
}
if ($type == "PROCEDURE") { if ($type == "PROCEDURE") {
echo "<td>", html_select("fields[$i][inout]", explode("|", $inout), $field["inout"]), "</td>\n"; echo "<td>", html_select("fields[$i][inout]", explode("|", $inout), $field["inout"]), "</td>\n";
} }
@@ -353,14 +363,17 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign
echo "<td>"; echo "<td>";
if (support("move_col")) { if (support("move_col")) {
echo "<input type='image' class='icon' name='add[$i]' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'> ", echo "<input type='image' class='icon' name='add[$i]' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'> ",
"<input type='image' class='icon' name='up[$i]' src='../adminer/static/up.gif' alt='↑' title='" . lang('Move up') . "'> ", "<input type='image' class='icon hidden' name='up[$i]' src='../adminer/static/up.gif' alt='↑' title='" . lang('Move up') . "'> ",
"<input type='image' class='icon' name='down[$i]' src='../adminer/static/down.gif' alt='↓' title='" . lang('Move down') . "'> "; "<input type='image' class='icon hidden' name='down[$i]' src='../adminer/static/down.gif' alt='↓' title='" . lang('Move down') . "'> ";
} }
if ($orig == "" || support("drop_col")) { if ($orig == "" || support("drop_col")) {
echo "<input type='image' class='icon' name='drop_col[$i]' src='../adminer/static/cross.gif' alt='x' title='" . lang('Remove') . "'>"; echo "<input type='image' class='icon' name='drop_col[$i]' src='../adminer/static/cross.gif' alt='x' title='" . lang('Remove') . "'>";
} }
echo "</td>\n</tr>\n"; echo "</td>\n</tr>\n";
} }
echo "</tbody>";
echo script("mixin(qs('#edit-fields tbody'), {onclick: editingClick, onkeydown: editingKeydown, oninput: editingInput}); initSortable('#edit-fields tbody');");
} }
/** Move fields up and down or add field /** Move fields up and down or add field

View File

@@ -250,11 +250,11 @@ function confirm($message = "", $selector = "qsl('input')") {
return script("$selector.onclick = function () { return confirm('" . ($message ? js_escape($message) : lang('Are you sure?')) . "'); };", ""); return script("$selector.onclick = function () { return confirm('" . ($message ? js_escape($message) : lang('Are you sure?')) . "'); };", "");
} }
/** Print header for hidden fieldset (close by </div></fieldset>) /**
* @param string * Prints header for hidden fieldset (close by </div></fieldset>)
* @param string * @param $id string
* @param bool * @param $legend string
*/ */
function print_fieldset($id, $legend, $visible = false, $sortable = false) { function print_fieldset($id, $legend, $visible = false, $sortable = false) {
echo "<fieldset><legend>"; echo "<fieldset><legend>";
echo "<a href='#fieldset-$id'>$legend</a>"; echo "<a href='#fieldset-$id'>$legend</a>";

View File

@@ -10,7 +10,7 @@ h3 { font-weight: normal; font-size: 130%; margin: 1em 0 0; }
form { margin: 0; } form { margin: 0; }
td table { width: 100%; margin: 0; } td table { width: 100%; margin: 0; }
table { margin: 1em 20px 0 0; border-collapse: collapse; font-size: 90%; } table { margin: 1em 20px 0 0; border-collapse: collapse; font-size: 90%; }
td, th { border: 1px solid #999; padding: .2em .3em; } td, th { box-sizing: border-box; border: 1px solid #999; padding: .2em .3em; }
th { background: #eee; text-align: left; } th { background: #eee; text-align: left; }
thead th { text-align: center; padding: .2em .5em; } thead th { text-align: center; padding: .2em .5em; }
thead td, thead th { background: #ddf; } /* position: sticky; causes Firefox to lose borders */ thead td, thead th { background: #ddf; } /* position: sticky; causes Firefox to lose borders */
@@ -72,12 +72,15 @@ p.nowrap { white-space: pre; }
.logout { margin-top: .5em; position: absolute; top: 0; right: 0; } .logout { margin-top: .5em; position: absolute; top: 0; right: 0; }
.loadmore { margin-left: 1ex; } .loadmore { margin-left: 1ex; }
.tables-filter { padding: .8em 1em 0; } .tables-filter { padding: .8em 1em 0; }
.handle { display: inline-block; width: 18px; height: 18px; padding-right: 5px; vertical-align: middle; overflow: hidden; font-size: 130%; text-align: center; line-height: 16px; opacity: 0.2; cursor: grab; } .handle { cursor: grab; vertical-align: middle; }
.sortable { position: relative; } .handle:before { content: "="; display: inline-block; width: 18px; height: 18px; overflow: hidden; font-size: 130%; text-align: center; line-height: 16px; opacity: 0.2; }
.sortable .no-sort .handle { opacity: 0; cursor: default; } span.handle { display: inline-block; width: 18px; height: 18px; padding-right: .3em; }
th.handle:before { vertical-align: middle; }
.no-sort .handle { cursor: default; opacity: 0; }
.placeholder { opacity: 0; } .placeholder { opacity: 0; }
.dragging { position: absolute; z-index: 1; } .dragging { position: absolute; margin: 0; background: #fff; }
.dragging .handle { cursor: grabbing; } .dragging * { cursor: grabbing; }
table.dragging { background: #eee; }
/* .edit used in designs */ /* .edit used in designs */
#menu { position: absolute; margin: 10px 0 0; padding: 0 0 30px 0; top: 2em; left: 0; width: 19em; } #menu { position: absolute; margin: 10px 0 0; padding: 0 0 30px 0; top: 2em; left: 0; width: 19em; }
#menu p, #logins, #tables { padding: .8em 1em; margin: 0; border-bottom: 1px solid #ccc; } #menu p, #logins, #tables { padding: .8em 1em; margin: 0; border-bottom: 1px solid #ccc; }

View File

@@ -253,10 +253,6 @@ function editingClick(event) {
var name = el.name; var name = el.name;
if (/^add\[/.test(name)) { if (/^add\[/.test(name)) {
editingAddRow.call(el, 1); editingAddRow.call(el, 1);
} else if (/^up\[/.test(name)) {
editingMoveRow.call(el, 1);
} else if (/^down\[/.test(name)) {
editingMoveRow.call(el);
} else if (/^drop_col\[/.test(name)) { } else if (/^drop_col\[/.test(name)) {
editingRemoveRow.call(el, 'fields\$1[field]'); editingRemoveRow.call(el, 'fields\$1[field]');
} else { } else {
@@ -353,7 +349,10 @@ function editingAddRow(focus) {
} }
} }
tags[0].oninput = editingNameChange; tags[0].oninput = editingNameChange;
initSortableRow(row2);
row.parentNode.insertBefore(row2, row.nextSibling); row.parentNode.insertBefore(row2, row.nextSibling);
if (focus) { if (focus) {
input.oninput = editingNameChange; input.oninput = editingNameChange;
input.focus(); input.focus();
@@ -375,22 +374,6 @@ function editingRemoveRow(name) {
return false; return false;
} }
/** Move table row for field
* @param [boolean]
* @return boolean false for success
* @this HTMLInputElement
*/
function editingMoveRow(up){
var row = parentTag(this, 'tr');
if (!('nextElementSibling' in row)) {
return true;
}
row.parentNode.insertBefore(row, up
? row.previousElementSibling
: row.nextElementSibling ? row.nextElementSibling.nextElementSibling : row.parentNode.firstChild);
return false;
}
var lastType = ''; var lastType = '';
/** Clear length and hide collation or unsigned /** Clear length and hide collation or unsigned

View File

@@ -544,8 +544,8 @@ function selectSearchSearch() {
// Sorting. // Sorting.
(function() { (function() {
let placeholderRow, draggingRow, nextRow; let placeholderRow, nextRow, dragHelper;
let startY, maxY; let startY, minY, maxY;
/** /**
* Initializes sortable list of DIV elements. * Initializes sortable list of DIV elements.
@@ -576,27 +576,52 @@ function selectSearchSearch() {
event.preventDefault(); event.preventDefault();
const parent = row.parentNode; const parent = row.parentNode;
startY = event.clientY - row.offsetTop; startY = event.clientY - getOffsetTop(row);
maxY = parent.offsetHeight - row.offsetHeight; minY = getOffsetTop(parent);
maxY = minY + parent.offsetHeight - row.offsetHeight;
placeholderRow = row.cloneNode(true); placeholderRow = row.cloneNode(true);
placeholderRow.classList.add("placeholder"); placeholderRow.classList.add("placeholder");
placeholderRow.style.top = (event.clientY - startY) + "px";
parent.insertBefore(placeholderRow, row); parent.insertBefore(placeholderRow, row);
draggingRow = row; nextRow = row.nextElementSibling;
draggingRow.classList.add("dragging");
parent.insertBefore(draggingRow, parent.firstChild);
nextRow = placeholderRow.nextElementSibling; let top = event.clientY - startY;
let left = getOffsetLeft(row);
let width = row.getBoundingClientRect().width;
updateSorting(event); if (row.tagName === "TR") {
const firstChild = row.firstElementChild;
const borderWidth = (firstChild.offsetWidth - firstChild.clientWidth) / 2;
const borderHeight = (firstChild.offsetHeight - firstChild.clientHeight) / 2;
minY -= borderHeight;
maxY -= borderHeight;
top -= borderHeight;
left -= borderWidth;
width += 2 * borderWidth;
for (const child of row.children) {
child.style.width = child.getBoundingClientRect().width + "px";
}
dragHelper = document.createElement("table");
dragHelper.appendChild(row);
} else {
dragHelper = row;
}
dragHelper.style.top = `${top}px`;
dragHelper.style.left = `${left}px`;
dragHelper.style.width = `${width}px`;
dragHelper.classList.add("dragging");
document.body.appendChild(dragHelper);
window.addEventListener("mousemove", updateSorting); window.addEventListener("mousemove", updateSorting);
window.addEventListener("mouseup", () => { window.addEventListener("mouseup", () => {
draggingRow.classList.remove("dragging"); dragHelper.classList.remove("dragging");
parent.insertBefore(draggingRow, placeholderRow); parent.insertBefore(dragHelper.tagName === "TABLE" ? dragHelper.firstChild : dragHelper, placeholderRow);
placeholderRow.remove(); placeholderRow.remove();
window.removeEventListener("mousemove", updateSorting); window.removeEventListener("mousemove", updateSorting);
@@ -605,8 +630,11 @@ function selectSearchSearch() {
}; };
function updateSorting(event) { function updateSorting(event) {
let top = Math.min(Math.max(event.clientY - startY, 0), maxY); let top = Math.min(Math.max(event.clientY - startY, minY), maxY);
draggingRow.style.top = top + "px"; dragHelper.style.top = `${top}px`;
const parent = placeholderRow.parentNode;
top = top - minY + parent.offsetTop;
let sibling; let sibling;
if (top > placeholderRow.offsetTop + placeholderRow.offsetHeight / 2) { if (top > placeholderRow.offsetTop + placeholderRow.offsetHeight / 2) {
@@ -1149,6 +1177,18 @@ function cloneNode(el) {
return el2; return el2;
} }
function getOffsetTop(element) {
let box = element.getBoundingClientRect();
return box.top + window.scrollY;
}
function getOffsetLeft(element) {
let box = element.getBoundingClientRect();
return box.left + window.scrollX;
}
oninput = function (event) { oninput = function (event) {
var target = event.target; var target = event.target;
var maxLength = target.getAttribute('data-maxlength'); var maxLength = target.getAttribute('data-maxlength');