From 45f832037bc071bae7e602f15408b22a287cec82 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Jul 2026 10:54:07 +0000
Subject: [PATCH 1/2] Add drag-and-drop functionality to x0 framework
---
example/drag_drop/README.md | 63 +++++
example/drag_drop/sql/insert-txt.sql | 31 +++
example/drag_drop/sql/sys-config.sql | 9 +
example/drag_drop/static/menu.json | 8 +
example/drag_drop/static/object.json | 327 +++++++++++++++++++++++++
example/drag_drop/static/skeleton.json | 50 ++++
python/Index.py | 1 +
www/static/globalstyles.css | 6 +
www/sysDragDropHandler.js | 66 +++++
www/sysInitOnLoad.js | 7 +
www/sysObjFormfieldList.js | 57 +++++
www/sysObjList.js | 84 +++++++
www/sysObjTreeSimple.js | 101 +++++++-
13 files changed, 806 insertions(+), 4 deletions(-)
create mode 100644 example/drag_drop/README.md
create mode 100644 example/drag_drop/sql/insert-txt.sql
create mode 100644 example/drag_drop/sql/sys-config.sql
create mode 100644 example/drag_drop/static/menu.json
create mode 100644 example/drag_drop/static/object.json
create mode 100644 example/drag_drop/static/skeleton.json
create mode 100644 www/sysDragDropHandler.js
diff --git a/example/drag_drop/README.md b/example/drag_drop/README.md
new file mode 100644
index 00000000..733dedd6
--- /dev/null
+++ b/example/drag_drop/README.md
@@ -0,0 +1,63 @@
+# Example #16: Drag and Drop
+
+This example demonstrates how to implement drag-and-drop functionality in the **x0-framework**,
+enabling objects to be dragged between UI components for seamless data transfer.
+
+## URL
+
+Open URL: `http://x0-app.x0.localnet/python/Index.py?appid=example16`
+
+## Scenarios Demonstrated
+
+### 1. List → List (Append by Drag)
+
+- **Source**: `sysObjList.js` (`SourceList`) configured with `"DragSource": true`.
+- **Target**: `sysObjList.js` (`DestinationList`) configured with `"DropTarget": true`.
+- Drag any row from the **Source List** tab and drop it onto the **Destination List** in the
+ **Drop Targets** tab. The row data is appended to the destination list.
+
+### 2. List → FormfieldList (Set Form Data by Drag)
+
+- **Source**: same `SourceList` above.
+- **Target**: `sysObjFormfieldList.js` (`DestinationForm`) configured with `"DropTarget": true`.
+- Drag any row from the **Source List** tab and drop it onto the **Form Drop Target** in the
+ **Drop Targets** tab. The row data populates the form fields.
+
+### 3. Tree Item → Tree Node (Move by Drag)
+
+- **Tree**: `sysObjTreeSimple.js` (`DragDropTree`) configured with both `"DragSource": true`
+ and `"DropTarget": true`.
+- In the **Tree** tab, drag any item from one node and drop it onto another node's header.
+ The item is moved to the target node's children list.
+
+## Configuration
+
+All drag-and-drop behaviour is declared declaratively in `object.json`:
+
+| Object type | Attribute | Effect |
+|------------------|----------------|-------------------------------------------|
+| `List` | `DragSource: true` | Rows can be dragged as data sources |
+| `List` | `DropTarget: true` | Rows from other lists can be dropped |
+| `FormfieldList` | `DropTarget: true` | Dragged row data sets the form fields |
+| `TreeSimple` | `DragSource: true` | Tree items can be dragged |
+| `TreeSimple` | `DropTarget: true` | Tree nodes accept dropped items |
+
+## Visual Feedback
+
+Drop targets highlight with a dashed blue outline (`.sysDragDropOver`) while a draggable
+object is hovered over them.
+
+## Main Components
+
+- [`sysObjList.js`](../../../www/sysObjList.js) — drag source rows and drop target list
+- [`sysObjFormfieldList.js`](../../../www/sysObjFormfieldList.js) — drop target form
+- [`sysObjTreeSimple.js`](../../../www/sysObjTreeSimple.js) — drag source items and drop target nodes
+- [`sysDragDropHandler.js`](../../../www/sysDragDropHandler.js) — central coordinator
+
+## Object Metadata
+
+All UI and logic are defined in `./static/object.json`.
+
+---
+
+**Note:** *x0-app* and *x0-db* docker containers must be up and running for viewing.
diff --git a/example/drag_drop/sql/insert-txt.sql b/example/drag_drop/sql/insert-txt.sql
new file mode 100644
index 00000000..bb49fe9f
--- /dev/null
+++ b/example/drag_drop/sql/insert-txt.sql
@@ -0,0 +1,31 @@
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.LINK1', 'menu', 'Drag and Drop');
+
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TAB1', 'tabcontainer', 'Source List');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TAB2', 'tabcontainer', 'Drop Targets');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TAB3', 'tabcontainer', 'Tree');
+
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.HEADER.COL1', 'table-header', 'Col1');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.HEADER.COL2', 'table-header', 'Col2');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.HEADER.COL3', 'table-header', 'Col3');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.HEADER.COL4', 'table-header', 'Col4');
+
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.FORM.HEADER', 'form-section', 'Form Drop Target');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.FORM.SUBHEADER', 'form-section', 'Drag a row from the Source List onto this form');
+
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.FORM.LABEL.COL1', 'form-label', 'Column 1');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.FORM.LABEL.COL2', 'form-label', 'Column 2');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.FORM.LABEL.COL3', 'form-label', 'Column 3');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.FORM.LABEL.COL4', 'form-label', 'Column 4');
+
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.PULLDOWN.OPT1', 'pulldown-option', 'Option 10');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.PULLDOWN.OPT2', 'pulldown-option', 'Option 20');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.PULLDOWN.OPT3', 'pulldown-option', 'Option 30');
+
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TREE.NODE1', 'tree-node', 'Group A');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TREE.NODE2', 'tree-node', 'Group B');
+
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TREE.ITEM1', 'tree-item', 'Document 1');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TREE.ITEM2', 'tree-item', 'Document 2');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TREE.ITEM3', 'tree-item', 'Document 3');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TREE.ITEM4', 'tree-item', 'Document 4');
+INSERT INTO webui.text (id, "group", value_en) VALUES ('TXT.DRAGDROP.TREE.ITEM5', 'tree-item', 'Document 5');
diff --git a/example/drag_drop/sql/sys-config.sql b/example/drag_drop/sql/sys-config.sql
new file mode 100644
index 00000000..59650949
--- /dev/null
+++ b/example/drag_drop/sql/sys-config.sql
@@ -0,0 +1,9 @@
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'index_title', 'x0 Example Drag and Drop');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'debug_level', '10');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'display_language', 'en');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'default_screen', 'Screen1');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'parent_window_url', 'null');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'subdir', '/examples/drag_drop');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'config_file_menu', 'menu.json');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'config_file_object', 'object.json');
+INSERT INTO system.config (app_id, config_group, "value") VALUES ('example16', 'config_file_skeleton', 'skeleton.json');
diff --git a/example/drag_drop/static/menu.json b/example/drag_drop/static/menu.json
new file mode 100644
index 00000000..9807a154
--- /dev/null
+++ b/example/drag_drop/static/menu.json
@@ -0,0 +1,8 @@
+[
+ {
+ "Link1":
+ {
+ "RefID": "sysMenu"
+ }
+ }
+]
diff --git a/example/drag_drop/static/object.json b/example/drag_drop/static/object.json
new file mode 100644
index 00000000..9b7ed1e4
--- /dev/null
+++ b/example/drag_drop/static/object.json
@@ -0,0 +1,327 @@
+{
+ "Link1":
+ {
+ "Type": "Link",
+ "Attributes": {
+ "Style": "btn btn-primary d-none",
+ "TextID": "TXT.DRAGDROP.LINK1",
+ "ScreenID": "Screen1"
+ }
+ },
+
+ "TabContainer1":
+ {
+ "Type": "TabContainer",
+ "Attributes":
+ {
+ "Tabs": [
+ {
+ "ID": "Tab1",
+ "Attributes":
+ {
+ "Default": true,
+ "TextID": "TXT.DRAGDROP.TAB1",
+ "Style": "col-md-5"
+ }
+ },
+ {
+ "ID": "Tab2",
+ "Attributes":
+ {
+ "TextID": "TXT.DRAGDROP.TAB2",
+ "Style": "col-md-7"
+ }
+ },
+ {
+ "ID": "Tab3",
+ "Attributes":
+ {
+ "TextID": "TXT.DRAGDROP.TAB3",
+ "Style": "col-md-5"
+ }
+ }
+ ]
+ }
+ },
+
+ "SourceListConnector":
+ {
+ "Type": "ServiceConnector",
+ "Attributes":
+ {
+ "OnEvent":
+ {
+ "Events": [ "InitSystem" ],
+ "ServiceCall": "/python/IntegrationTestGetTableData2.py"
+ }
+ }
+ },
+
+ "SourceList":
+ {
+ "Type": "List",
+ "Attributes":
+ {
+ "Style": "border rounded-top rounded-bottom",
+ "HeaderRowStyle": "row fw-bold fs-6 m-1 border-bottom",
+ "RowCount": 10,
+ "DragSource": true,
+ "SrcDataObjects":
+ {
+ "Dummy":
+ {
+ "Type": "HardcodedValues",
+ "Values": {
+ "LimitRows": "10"
+ }
+ }
+ },
+ "Columns": [
+ {
+ "ID": "col1",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL1",
+ "HeaderStyle": "col-md-3 p-2 border-end"
+ },
+ {
+ "ID": "col2",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL2",
+ "HeaderStyle": "col-md-3 p-2 border-end"
+ },
+ {
+ "ID": "col3",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL3",
+ "HeaderStyle": "col-md-3 p-2 border-end"
+ },
+ {
+ "ID": "col4",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL4",
+ "HeaderStyle": "col-md-3 p-2"
+ }
+ ],
+ "RowStyle": "row m-1 border-bottom",
+ "RowAfterElements": 4,
+ "ColStyle": [
+ "col-md-3 p-2 border-end",
+ "col-md-3 p-2 border-end",
+ "col-md-3 p-2 border-end",
+ "col-md-3 p-2"
+ ]
+ }
+ },
+
+ "DestinationList":
+ {
+ "Type": "List",
+ "Attributes":
+ {
+ "Style": "border rounded-top rounded-bottom",
+ "HeaderRowStyle": "row fw-bold fs-6 m-1 border-bottom",
+ "RowCount": 20,
+ "DropTarget": true,
+ "DataMaxRows": 50,
+ "Columns": [
+ {
+ "ID": "col1",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL1",
+ "HeaderStyle": "col-md-3 p-2 border-end"
+ },
+ {
+ "ID": "col2",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL2",
+ "HeaderStyle": "col-md-3 p-2 border-end"
+ },
+ {
+ "ID": "col3",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL3",
+ "HeaderStyle": "col-md-3 p-2 border-end"
+ },
+ {
+ "ID": "col4",
+ "HeaderTextID": "TXT.DRAGDROP.HEADER.COL4",
+ "HeaderStyle": "col-md-3 p-2"
+ }
+ ],
+ "RowStyle": "row m-1 border-bottom",
+ "RowAfterElements": 4,
+ "ColStyle": [
+ "col-md-3 p-2 border-end",
+ "col-md-3 p-2 border-end",
+ "col-md-3 p-2 border-end",
+ "col-md-3 p-2"
+ ]
+ }
+ },
+
+ "DestinationForm": {
+ "Type": "FormfieldList",
+ "Attributes": {
+ "DropTarget": true,
+ "Sections": [
+ {
+ "ID": "Section1",
+ "Object": "FormSectionHeader",
+ "ObjectAttributes": {
+ "Style": "card card-line bg-body-tertiary border border-dark border-2 mb-3",
+ "SubStyle": "card-body mt-n6",
+ "HeaderIcon": "fa-solid fa-down-to-line",
+ "HeaderTextID": "TXT.DRAGDROP.FORM.HEADER",
+ "SubHeaderTextID": "TXT.DRAGDROP.FORM.SUBHEADER"
+ },
+ "Formfields": [
+ "col1Descr",
+ "col1",
+ "col2Descr",
+ "col2",
+ "col3Descr",
+ "col3",
+ "col4Descr",
+ "col4"
+ ],
+ "RowStyle": "row",
+ "RowAfterElements": 4,
+ "ColStyle": "col-md-6",
+ "ColAfterElements": 2
+ }
+ ]
+ }
+ },
+
+ "col1Descr": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "label",
+ "TextID": "TXT.DRAGDROP.FORM.LABEL.COL1"
+ }
+ },
+
+ "col1": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "checkbox",
+ "Value": true,
+ "Style": "form-check"
+ }
+ },
+
+ "col2Descr": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "label",
+ "TextID": "TXT.DRAGDROP.FORM.LABEL.COL2"
+ }
+ },
+
+ "col2": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "text",
+ "Disabled": true,
+ "Style": "form-control mb-1 p-1 w-100"
+ }
+ },
+
+ "col3Descr": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "label",
+ "TextID": "TXT.DRAGDROP.FORM.LABEL.COL3"
+ }
+ },
+
+ "col3": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "pulldown",
+ "Style": "form-control mb-1 p-1 w-100",
+ "Options": [
+ {
+ "TextID": "TXT.DRAGDROP.PULLDOWN.OPT1",
+ "Value": "10"
+ },
+ {
+ "TextID": "TXT.DRAGDROP.PULLDOWN.OPT2",
+ "Value": "20",
+ "Default": true
+ },
+ {
+ "TextID": "TXT.DRAGDROP.PULLDOWN.OPT3",
+ "Value": "30"
+ }
+ ]
+ }
+ },
+
+ "col4Descr": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "label",
+ "TextID": "TXT.DRAGDROP.FORM.LABEL.COL4"
+ }
+ },
+
+ "col4": {
+ "Type": "Formfield",
+ "Attributes": {
+ "Type": "textarea",
+ "Style": "form-control mb-1 p-1 w-100"
+ }
+ },
+
+ "DragDropTree":
+ {
+ "Type": "TreeSimple",
+ "Attributes":
+ {
+ "DragSource": true,
+ "DropTarget": true,
+ "TreeItems":
+ [
+ {
+ "Type": "Node",
+ "TextID": "TXT.DRAGDROP.TREE.NODE1",
+ "Children":
+ [
+ {
+ "Type": "Item",
+ "TextID": "TXT.DRAGDROP.TREE.ITEM1",
+ "Icon": "fa-solid fa-file",
+ "ScreenID": "Screen1"
+ },
+ {
+ "Type": "Item",
+ "TextID": "TXT.DRAGDROP.TREE.ITEM2",
+ "Icon": "fa-solid fa-file",
+ "ScreenID": "Screen1"
+ },
+ {
+ "Type": "Item",
+ "TextID": "TXT.DRAGDROP.TREE.ITEM3",
+ "Icon": "fa-solid fa-file",
+ "ScreenID": "Screen1"
+ }
+ ]
+ },
+ {
+ "Type": "Node",
+ "TextID": "TXT.DRAGDROP.TREE.NODE2",
+ "Children":
+ [
+ {
+ "Type": "Item",
+ "TextID": "TXT.DRAGDROP.TREE.ITEM4",
+ "Icon": "fa-solid fa-file",
+ "ScreenID": "Screen1"
+ },
+ {
+ "Type": "Item",
+ "TextID": "TXT.DRAGDROP.TREE.ITEM5",
+ "Icon": "fa-solid fa-file",
+ "ScreenID": "Screen1"
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+}
diff --git a/example/drag_drop/static/skeleton.json b/example/drag_drop/static/skeleton.json
new file mode 100644
index 00000000..d4bad5b2
--- /dev/null
+++ b/example/drag_drop/static/skeleton.json
@@ -0,0 +1,50 @@
+{
+ "Screen1":
+ [
+ {
+ "TabContainer1":
+ {
+ "RefID": "Screen1"
+ }
+ },
+
+ {
+ "SourceListConnector":
+ {
+ "RefID": "TabContainer1",
+ "ElementID": "Tab1Content"
+ }
+ },
+
+ {
+ "SourceList":
+ {
+ "RefID": "SourceListConnector"
+ }
+ },
+
+ {
+ "DestinationList":
+ {
+ "RefID": "TabContainer1",
+ "ElementID": "Tab2Content"
+ }
+ },
+
+ {
+ "DestinationForm":
+ {
+ "RefID": "TabContainer1",
+ "ElementID": "Tab2Content"
+ }
+ },
+
+ {
+ "DragDropTree":
+ {
+ "RefID": "TabContainer1",
+ "ElementID": "Tab3Content"
+ }
+ }
+ ]
+}
diff --git a/python/Index.py b/python/Index.py
index 9930fbda..1aaeb6ba 100755
--- a/python/Index.py
+++ b/python/Index.py
@@ -60,6 +60,7 @@
+
diff --git a/www/static/globalstyles.css b/www/static/globalstyles.css
index 1bd8aedf..1002ed98 100644
--- a/www/static/globalstyles.css
+++ b/www/static/globalstyles.css
@@ -133,3 +133,9 @@ a:active {
width: 160px;
float: right;
}
+
+.sysDragDropOver {
+ outline: 2px dashed #2b5575;
+ background-color: rgba(43, 85, 117, 0.12);
+ cursor: copy;
+}
diff --git a/www/sysDragDropHandler.js b/www/sysDragDropHandler.js
new file mode 100644
index 00000000..761a74ea
--- /dev/null
+++ b/www/sysDragDropHandler.js
@@ -0,0 +1,66 @@
+//-------1---------2---------3---------4---------5---------6---------7--------//
+//- Copyright WEB/codeX, clickIT 2011 - 2025 -//
+//-------1---------2---------3---------4---------5---------6---------7--------//
+//- -//
+//-------1---------2---------3---------4---------5---------6---------7--------//
+//- SYSTEM OBJECT "DragDropHandler" -//
+//-------1---------2---------3---------4---------5---------6---------7--------//
+//- -//
+//- Central coordinator for HTML5 drag-and-drop operations. -//
+//- Tracks the currently dragged object and its data payload so that -//
+//- any registered drop-target can retrieve them on the "drop" event. -//
+//- -//
+//-------1---------2---------3---------4---------5---------6---------7--------//
+
+
+//------------------------------------------------------------------------------
+//- CONSTRUCTOR "sysDragDropHandler"
+//------------------------------------------------------------------------------
+
+function sysDragDropHandler()
+{
+ this.DragSourceObject = null; //- Currently dragged source object reference
+ this.DragSourceData = null; //- Currently dragged data payload
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "setDragSource"
+//------------------------------------------------------------------------------
+
+sysDragDropHandler.prototype.setDragSource = function(SourceObject, SourceData)
+{
+ this.DragSourceObject = SourceObject;
+ this.DragSourceData = SourceData;
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "getDragData"
+//------------------------------------------------------------------------------
+
+sysDragDropHandler.prototype.getDragData = function()
+{
+ return this.DragSourceData;
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "getDragSourceObject"
+//------------------------------------------------------------------------------
+
+sysDragDropHandler.prototype.getDragSourceObject = function()
+{
+ return this.DragSourceObject;
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "clearDragSource"
+//------------------------------------------------------------------------------
+
+sysDragDropHandler.prototype.clearDragSource = function()
+{
+ this.DragSourceObject = null;
+ this.DragSourceData = null;
+}
diff --git a/www/sysInitOnLoad.js b/www/sysInitOnLoad.js
index fd109aed..7636e04a 100644
--- a/www/sysInitOnLoad.js
+++ b/www/sysInitOnLoad.js
@@ -185,6 +185,13 @@ function InitOk(XHR) {
sysFactory.Reactor = new sysReactor();
+ //----------------------------------------------------------------------------
+ //- Construct Global Drag and Drop Handler
+ //----------------------------------------------------------------------------
+
+ sysFactory.DragDropHandler = new sysDragDropHandler();
+
+
//----------------------------------------------------------------------------
//- Construct Global Async Notify Indicator
//----------------------------------------------------------------------------
diff --git a/www/sysObjFormfieldList.js b/www/sysObjFormfieldList.js
index e0ccc250..15ba4020 100644
--- a/www/sysObjFormfieldList.js
+++ b/www/sysObjFormfieldList.js
@@ -85,6 +85,23 @@ sysFormfieldList.prototype.init = function()
EventListenerObj['Element'] = this.EventListenerRightClick.bind(this);
this.EventListeners['ContextMenuOpen'] = EventListenerObj;
+ if (Attributes.DropTarget === true) {
+ var EventListenerObj = new Object();
+ EventListenerObj['Type'] = 'dragover';
+ EventListenerObj['Element'] = this.onDragOver.bind(this);
+ this.EventListeners['DragOver'] = EventListenerObj;
+
+ var EventListenerObj = new Object();
+ EventListenerObj['Type'] = 'dragleave';
+ EventListenerObj['Element'] = this.onDragLeave.bind(this);
+ this.EventListeners['DragLeave'] = EventListenerObj;
+
+ var EventListenerObj = new Object();
+ EventListenerObj['Type'] = 'drop';
+ EventListenerObj['Element'] = this.onDrop.bind(this);
+ this.EventListeners['Drop'] = EventListenerObj;
+ }
+
this.render();
}
@@ -459,3 +476,43 @@ sysFormfieldList.prototype.initOnChangeItems = function()
this.FormfieldItems[ItemKey].processOnChangeItem();
}
}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDragOver"
+//------------------------------------------------------------------------------
+
+sysFormfieldList.prototype.onDragOver = function(Event)
+{
+ Event.preventDefault();
+ this.addDOMElementStyle('sysDragDropOver');
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDragLeave"
+//------------------------------------------------------------------------------
+
+sysFormfieldList.prototype.onDragLeave = function(Event)
+{
+ const Element = this.getElement();
+ if (Element !== null && !Element.contains(Event.relatedTarget)) {
+ this.removeDOMElementStyle('sysDragDropOver');
+ }
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDrop"
+//------------------------------------------------------------------------------
+
+sysFormfieldList.prototype.onDrop = function(Event)
+{
+ Event.preventDefault();
+ this.removeDOMElementStyle('sysDragDropOver');
+ const DragData = sysFactory.DragDropHandler.getDragData();
+ if (DragData !== null) {
+ this.setData(DragData);
+ }
+ sysFactory.DragDropHandler.clearDragSource();
+}
diff --git a/www/sysObjList.js b/www/sysObjList.js
index cb88357e..2e76ec5e 100644
--- a/www/sysObjList.js
+++ b/www/sysObjList.js
@@ -59,6 +59,15 @@ sysListRow.prototype.init = function()
EventListenerObj['Type'] = 'mousedown';
EventListenerObj['Element'] = this.EventListenerSelect.bind(this);
this.EventListeners['RowSelect'] = EventListenerObj;
+
+ if (this.ParentObject.JSONConfig.Attributes.DragSource === true) {
+ this.DOMAttributes = { 'draggable': 'true' };
+
+ var EventListenerObj = new Object();
+ EventListenerObj['Type'] = 'dragstart';
+ EventListenerObj['Element'] = this.onDragStart.bind(this);
+ this.EventListeners['DragStart'] = EventListenerObj;
+ }
}
@@ -269,6 +278,17 @@ sysListRow.prototype.updateIndex = function(UpdateIndex)
}
+//------------------------------------------------------------------------------
+//- METHOD "onDragStart"
+//------------------------------------------------------------------------------
+
+sysListRow.prototype.onDragStart = function(Event)
+{
+ sysFactory.DragDropHandler.setDragSource(this, this.RowData);
+ Event.dataTransfer.effectAllowed = 'copy';
+}
+
+
//------------------------------------------------------------------------------
//- CONSTRUCTOR "sysList"
//------------------------------------------------------------------------------
@@ -296,6 +316,8 @@ function sysList()
this.ChildObjects = new Array(); //- Child Objects
+ this.EventListeners = new Object(); //- Event Listeners
+
this.PaginationObject = new sysPagination(this); //- Pagination Processing
this.RowsSelectable = true; //- Multi Row Selection Default
@@ -381,6 +403,23 @@ sysList.prototype.init = function()
this.DOMStyle = Attributes.Style;
+ if (Attributes.DropTarget === true) {
+ var EventListenerObj = new Object();
+ EventListenerObj['Type'] = 'dragover';
+ EventListenerObj['Element'] = this.onDragOver.bind(this);
+ this.EventListeners['DragOver'] = EventListenerObj;
+
+ var EventListenerObj = new Object();
+ EventListenerObj['Type'] = 'dragleave';
+ EventListenerObj['Element'] = this.onDragLeave.bind(this);
+ this.EventListeners['DragLeave'] = EventListenerObj;
+
+ var EventListenerObj = new Object();
+ EventListenerObj['Type'] = 'drop';
+ EventListenerObj['Element'] = this.onDrop.bind(this);
+ this.EventListeners['Drop'] = EventListenerObj;
+ }
+
this.renderPage();
}
@@ -710,3 +749,48 @@ sysList.prototype.appendData = function(DataObj)
this.addRow(AppendRowObj, this.RowItems.length+1);
this.renderPage();
}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDragOver"
+//------------------------------------------------------------------------------
+
+sysList.prototype.onDragOver = function(Event)
+{
+ Event.preventDefault();
+ this.addDOMElementStyle('sysDragDropOver');
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDragLeave"
+//------------------------------------------------------------------------------
+
+sysList.prototype.onDragLeave = function(Event)
+{
+ const Element = this.getElement();
+ if (Element !== null && !Element.contains(Event.relatedTarget)) {
+ this.removeDOMElementStyle('sysDragDropOver');
+ }
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDrop"
+//------------------------------------------------------------------------------
+
+sysList.prototype.onDrop = function(Event)
+{
+ Event.preventDefault();
+ this.removeDOMElementStyle('sysDragDropOver');
+ const DragSourceObj = sysFactory.DragDropHandler.getDragSourceObject();
+ if (DragSourceObj !== null && DragSourceObj !== undefined) {
+ if (DragSourceObj.ParentObject !== this) {
+ const DragData = sysFactory.DragDropHandler.getDragData();
+ if (DragData !== null) {
+ this.appendData(DragData);
+ }
+ }
+ }
+ sysFactory.DragDropHandler.clearDragSource();
+}
diff --git a/www/sysObjTreeSimple.js b/www/sysObjTreeSimple.js
index 67494c7c..5d53ea44 100644
--- a/www/sysObjTreeSimple.js
+++ b/www/sysObjTreeSimple.js
@@ -40,7 +40,7 @@ sysObjTreeSimple.prototype.init = function()
for (const RootItem of Attributes.TreeItems)
{
console.debug('TreeSimple RootItem:%o', RootItem);
- let NodeItem = new sysObjTreeSimpleNode(this.IndentLevel);
+ let NodeItem = new sysObjTreeSimpleNode(this, this.IndentLevel);
NodeItem.JSONConfig = {
"Attributes": {
@@ -78,7 +78,7 @@ sysObjTreeSimple.prototype.addTreeItems = function(RootObj, NodeItem, ChildItems
}
if (ChildItem.Type == 'Node') {
- TreeItem = new sysObjTreeSimpleNode(this.IndentLevel);
+ TreeItem = new sysObjTreeSimpleNode(RootObj, this.IndentLevel);
}
TreeItem.JSONConfig = {
@@ -103,11 +103,12 @@ sysObjTreeSimple.prototype.addTreeItems = function(RootObj, NodeItem, ChildItems
//- CONSTRUCTOR "sysObjTreeSimpleNode"
//------------------------------------------------------------------------------
-function sysObjTreeSimpleNode(IndentLevel)
+function sysObjTreeSimpleNode(TreeRootObj, IndentLevel)
{
this.ChildObjects = new Array(); //- Child Objects
this.DOMStyle = 'sysTreeNodeRoot'; //- CSS Style
this.IndentLevel = IndentLevel; //- Tree Indent Level
+ this.TreeRootObj = TreeRootObj; //- Tree Root Object Reference
}
//- inherit sysBaseObject
@@ -146,6 +147,26 @@ sysObjTreeSimpleNode.prototype.init = function()
EventListenerObj['Element'] = this.toggleVisibleState.bind(this.OpenCloseIcon);
this.OpenCloseIcon.EventListeners["OpenClose"] = EventListenerObj;
+ //- define node header div (visible area carrying drop-target listeners)
+ this.NodeHeaderDiv = new sysObjDiv();
+
+ if (this.TreeRootObj !== undefined && this.TreeRootObj.JSONConfig.Attributes.DropTarget === true) {
+ var DragOverEvent = new Object();
+ DragOverEvent['Type'] = 'dragover';
+ DragOverEvent['Element'] = this.onDragOver.bind(this);
+ this.NodeHeaderDiv.EventListeners['DragOver'] = DragOverEvent;
+
+ var DragLeaveEvent = new Object();
+ DragLeaveEvent['Type'] = 'dragleave';
+ DragLeaveEvent['Element'] = this.onDragLeave.bind(this);
+ this.NodeHeaderDiv.EventListeners['DragLeave'] = DragLeaveEvent;
+
+ var DropEvent = new Object();
+ DropEvent['Type'] = 'drop';
+ DropEvent['Element'] = this.onDrop.bind(this);
+ this.NodeHeaderDiv.EventListeners['Drop'] = DropEvent;
+ }
+
//- setup recursive object structure
const ObjDefs = [
{
@@ -157,7 +178,7 @@ sysObjTreeSimpleNode.prototype.init = function()
},
{
"id": "item-container",
- "SysObject": new sysObjDiv(),
+ "SysObject": this.NodeHeaderDiv,
"JSONAttributes": {
"Style": "sysTreeItemContainer sysTreeItemIndent" + this.IndentLevel
},
@@ -216,6 +237,58 @@ sysObjTreeSimpleNode.prototype.toggleVisibleState = function()
}
+//------------------------------------------------------------------------------
+//- METHOD "onDragOver"
+//------------------------------------------------------------------------------
+
+sysObjTreeSimpleNode.prototype.onDragOver = function(Event)
+{
+ Event.preventDefault();
+ Event.stopPropagation();
+ this.NodeHeaderDiv.addDOMElementStyle('sysDragDropOver');
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDragLeave"
+//------------------------------------------------------------------------------
+
+sysObjTreeSimpleNode.prototype.onDragLeave = function(Event)
+{
+ const Element = this.NodeHeaderDiv.getElement();
+ if (Element !== null && !Element.contains(Event.relatedTarget)) {
+ this.NodeHeaderDiv.removeDOMElementStyle('sysDragDropOver');
+ }
+}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDrop"
+//------------------------------------------------------------------------------
+
+sysObjTreeSimpleNode.prototype.onDrop = function(Event)
+{
+ Event.preventDefault();
+ Event.stopPropagation();
+ this.NodeHeaderDiv.removeDOMElementStyle('sysDragDropOver');
+
+ const DragSourceObj = sysFactory.DragDropHandler.getDragSourceObject();
+
+ if (DragSourceObj !== null && DragSourceObj !== undefined) {
+ if (DragSourceObj instanceof sysObjTreeSimpleItem) {
+ //- prevent dropping onto the same parent node
+ if (DragSourceObj.ParentObject !== this.ItemContainerObj) {
+ DragSourceObj.remove();
+ this.ItemContainerObj.addObject(DragSourceObj);
+ DragSourceObj.renderObject(this.ItemContainerObj.DOMObjectID);
+ }
+ }
+ }
+
+ sysFactory.DragDropHandler.clearDragSource();
+}
+
+
//------------------------------------------------------------------------------
//- CONSTRUCTOR "sysObjTreeSimpleItem"
//------------------------------------------------------------------------------
@@ -269,6 +342,15 @@ sysObjTreeSimpleItem.prototype.init = function()
EventLinkClick['Element'] = this.activateSelected.bind(this.SelectedIndicator);
this.ItemContainerObj.EventListeners["LinkClick"] = EventLinkClick;
+ if (this.TreeRootObj !== undefined && this.TreeRootObj.JSONConfig.Attributes.DragSource === true) {
+ this.ItemContainerObj.DOMAttributes = { 'draggable': 'true' };
+
+ var DragStartEvent = new Object();
+ DragStartEvent['Type'] = 'dragstart';
+ DragStartEvent['Element'] = this.onDragStart.bind(this);
+ this.ItemContainerObj.EventListeners['DragStart'] = DragStartEvent;
+ }
+
//- setup recursive object structure
const ObjDefs = [
{
@@ -348,3 +430,14 @@ sysObjTreeSimpleItem.prototype.activateSelected = function()
this.ParentObject.TreeRootObj.LastSelectedItem = this;
}
+
+
+//------------------------------------------------------------------------------
+//- METHOD "onDragStart"
+//------------------------------------------------------------------------------
+
+sysObjTreeSimpleItem.prototype.onDragStart = function(Event)
+{
+ sysFactory.DragDropHandler.setDragSource(this, this.JSONConfig.Attributes);
+ Event.dataTransfer.effectAllowed = 'move';
+}
From 639ba4cefc9144a0708cb574940f23f17d24d1e7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Jul 2026 11:10:31 +0000
Subject: [PATCH 2/2] Use RuntimeSetDataFunc/RuntimeAppendDataFunc in
drag-and-drop drop handlers
---
www/sysObjFormfieldList.js | 2 +-
www/sysObjList.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/www/sysObjFormfieldList.js b/www/sysObjFormfieldList.js
index 15ba4020..67bc6c44 100644
--- a/www/sysObjFormfieldList.js
+++ b/www/sysObjFormfieldList.js
@@ -512,7 +512,7 @@ sysFormfieldList.prototype.onDrop = function(Event)
this.removeDOMElementStyle('sysDragDropOver');
const DragData = sysFactory.DragDropHandler.getDragData();
if (DragData !== null) {
- this.setData(DragData);
+ this.RuntimeSetDataFunc(DragData);
}
sysFactory.DragDropHandler.clearDragSource();
}
diff --git a/www/sysObjList.js b/www/sysObjList.js
index 2e76ec5e..2c1b1a88 100644
--- a/www/sysObjList.js
+++ b/www/sysObjList.js
@@ -788,7 +788,7 @@ sysList.prototype.onDrop = function(Event)
if (DragSourceObj.ParentObject !== this) {
const DragData = sysFactory.DragDropHandler.getDragData();
if (DragData !== null) {
- this.appendData(DragData);
+ this.RuntimeAppendDataFunc(DragData);
}
}
}