1
0
mirror of https://github.com/vrana/adminer.git synced 2025-08-30 09:39:51 +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'"), ">",
"<span class='jsonly handle'>=</span>";
"<span class='jsonly handle'></span>";
if ($functions || $grouping) {
echo "<select name='columns[$i][fun]'>",
@@ -499,7 +499,7 @@ class Adminer {
if ($key != "" && $val == "") continue;
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"),
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'>",

View File

@@ -288,7 +288,14 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign
?>
<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>
<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>
@@ -308,9 +315,9 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign
<?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>
</tr></thead>
<tbody>
<?php
echo script("mixin(qsl('tbody'), {onclick: editingClick, onkeydown: editingKeydown, oninput: editingInput});");
echo "<tbody>\n";
foreach ($fields as $i => $field) {
$i++;
$orig = $field[($_POST ? "orig" : "field")];
@@ -319,6 +326,9 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", $foreign
$style = $display ? "" : "style='display: none;'";
echo "<tr $style>\n";
if (support("move_col")) {
echo "<th class='handle jsonly'></th>";
}
if ($type == "PROCEDURE") {
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>";
if (support("move_col")) {
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' name='down[$i]' src='../adminer/static/down.gif' alt='↓' title='" . lang('Move down') . "'> ";
"<input type='image' class='icon hidden' name='up[$i]' src='../adminer/static/up.gif' alt='↑' title='" . lang('Move up') . "'> ",
"<input type='image' class='icon hidden' name='down[$i]' src='../adminer/static/down.gif' alt='↓' title='" . lang('Move down') . "'> ";
}
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 "</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

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?')) . "'); };", "");
}
/** Print header for hidden fieldset (close by </div></fieldset>)
* @param string
* @param string
* @param bool
*/
/**
* Prints header for hidden fieldset (close by </div></fieldset>)
* @param $id string
* @param $legend string
*/
function print_fieldset($id, $legend, $visible = false, $sortable = false) {
echo "<fieldset><legend>";
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; }
td table { width: 100%; margin: 0; }
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; }
thead th { text-align: center; padding: .2em .5em; }
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; }
.loadmore { margin-left: 1ex; }
.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; }
.sortable { position: relative; }
.sortable .no-sort .handle { opacity: 0; cursor: default; }
.handle { cursor: grab; vertical-align: middle; }
.handle:before { content: "="; display: inline-block; width: 18px; height: 18px; overflow: hidden; font-size: 130%; text-align: center; line-height: 16px; opacity: 0.2; }
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; }
.dragging { position: absolute; z-index: 1; }
.dragging .handle { cursor: grabbing; }
.dragging { position: absolute; margin: 0; background: #fff; }
.dragging * { cursor: grabbing; }
table.dragging { background: #eee; }
/* .edit used in designs */
#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; }

View File

@@ -253,10 +253,6 @@ function editingClick(event) {
var name = el.name;
if (/^add\[/.test(name)) {
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)) {
editingRemoveRow.call(el, 'fields\$1[field]');
} else {
@@ -353,7 +349,10 @@ function editingAddRow(focus) {
}
}
tags[0].oninput = editingNameChange;
initSortableRow(row2);
row.parentNode.insertBefore(row2, row.nextSibling);
if (focus) {
input.oninput = editingNameChange;
input.focus();
@@ -375,22 +374,6 @@ function editingRemoveRow(name) {
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 = '';
/** Clear length and hide collation or unsigned

View File

@@ -544,8 +544,8 @@ function selectSearchSearch() {
// Sorting.
(function() {
let placeholderRow, draggingRow, nextRow;
let startY, maxY;
let placeholderRow, nextRow, dragHelper;
let startY, minY, maxY;
/**
* Initializes sortable list of DIV elements.
@@ -576,27 +576,52 @@ function selectSearchSearch() {
event.preventDefault();
const parent = row.parentNode;
startY = event.clientY - row.offsetTop;
maxY = parent.offsetHeight - row.offsetHeight;
startY = event.clientY - getOffsetTop(row);
minY = getOffsetTop(parent);
maxY = minY + parent.offsetHeight - row.offsetHeight;
placeholderRow = row.cloneNode(true);
placeholderRow.classList.add("placeholder");
placeholderRow.style.top = (event.clientY - startY) + "px";
parent.insertBefore(placeholderRow, row);
draggingRow = row;
draggingRow.classList.add("dragging");
parent.insertBefore(draggingRow, parent.firstChild);
nextRow = row.nextElementSibling;
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("mouseup", () => {
draggingRow.classList.remove("dragging");
parent.insertBefore(draggingRow, placeholderRow);
dragHelper.classList.remove("dragging");
parent.insertBefore(dragHelper.tagName === "TABLE" ? dragHelper.firstChild : dragHelper, placeholderRow);
placeholderRow.remove();
window.removeEventListener("mousemove", updateSorting);
@@ -605,8 +630,11 @@ function selectSearchSearch() {
};
function updateSorting(event) {
let top = Math.min(Math.max(event.clientY - startY, 0), maxY);
draggingRow.style.top = top + "px";
let top = Math.min(Math.max(event.clientY - startY, minY), maxY);
dragHelper.style.top = `${top}px`;
const parent = placeholderRow.parentNode;
top = top - minY + parent.offsetTop;
let sibling;
if (top > placeholderRow.offsetTop + placeholderRow.offsetHeight / 2) {
@@ -1149,6 +1177,18 @@ function cloneNode(el) {
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) {
var target = event.target;
var maxLength = target.getAttribute('data-maxlength');