Drag and Drop table content with JavaScript
Content of HTML table cells can be dragged to another table cell or another table. It isn't difficult to define onMouseMove handler and change top / left element styles to move the object. In case with tables, you will have to determine somehow destination table cell. Attaching onMouseOver handler on table cells will not work, because browser doesn't fire events to the elements beneath the dragged object.
| Anyway, after taking care of the current scroll position and calculating table cells positions, here is example that should work in FF3, IE7 / 8 and Google Chrome. Please try to drag green, blue or orange bordered objects, change properties (radio button and check-boxes) and click on "Save" button. |
Version 2.3.1
Download redips2.tar.gz |
| You | can | not | drop | here |
|
Drag
|
and
|
drop
|
||
|
content
|
|
|||
|
with
|
||||
|
JavaScript
|
||||
|
|
| Table2 |
and
|
|||
|
Drag
|
drop
|
table
|
||
|
|
||||
|
with
|
JavaScript
|
|||
| Table3 |
|
|||
|
Clone
|
||||
|
(1) Clone
|
(2) Clone
|
|||
|
|
Trash |
You can try another example built on this Drag and Drop library.
"Save" button will scan tables, create query string and send to the PHP page. Demo shows how to collect table content and accept parameters on the server side. More about accepting parameters you can read in my post Reading multiple parameters in PHP. Orange object "Clone" will be duplicated first because of "clone" keyword in his class name. If you drop object on cell named "Trash", object will be deleted from the table (with or without confirmation). Script has built in autoscroll and option to forbid landing to non empty cells or cells named with class "forbid". Table can contain rowspan / colspan cells and different background color for every cell.
Here are minimal steps to enable content dragging in table:
- put <script type="text/javascript" src="drag.js"></script> to the head section
- start drag.js initialization: <body onload="REDIPS.drag.init()">
- place table(s) inside <div id="drag"> to enable content dragging
- add .drag{position: relative;} class to CSS file
- place <div class="drag">Hello World</div> to the table cell
Other features of drag.js:
- functions and data structure are defined in namespace (easier integration with other JS frameworks)
- JSLint: No problems found in drag.js (tough one, huh)
- movable div element can contain form elements and links
- forbidding or allowing table cells marked with class name "mark" (depends on "marked_cell" parameter)
- option to define exceptions and allow certain DIV elements to the marked table cell
- option to define single content cell on the table declared with "multiple" drop option
- cloning
- for unlimited cloning add "clone" class name to the div object
<div class="drag clone">Hello World</div> - to limit cloning and transform last object to the ordinary movable object add 'climit1_X' class name
<div class="drag clone climit1_4">Hello World</div> - to limit cloning and transform last object to immovable object add 'climit2_X' class name
<div class="drag clone climit2_4">Hello World</div> - where X is integer and defines number of cloned elements (in previous examples, each climit will allow only 4 cloned elements)
- for unlimited cloning add "clone" class name to the div object
- allowed nested tables
- dropping objects only to empty cells
- switching content of table cells
- overwrite table cell content with dropped element
- table cell with "trash" class name becomes trashcan
- enabled handlers to place custom code on events: clicked, moved, not moved, dropped, switched, cloned, cloned end1, cloned end2, deleted and undeleted
- deleting cloned div if the cloned div is dragged outside of any table
- enabling / disabling dragging
How drag.js works?
Script will search for div elements (with class name "drag") inside tables closed in <div id="drag"> and attach onMouseDown event handler. When user clicks with left mouse button on div element, onMouseMove and onMouseUp handlers will be attached to the document level.
While dragging div element, script changes "left" and "top" styles of the object. This is function of the onMouseMove handler. When user releases left mouse button, onMouseUp event handler will unlink onMouseMove and onMouseUp event handlers. This way, browser will listen and process mouse move events only when user holds left mouse button on div element.
As I mentioned, onMouseDown is defined on the elements you want to drag. Elements beneath the dragged object will not be able to catch onMouseOver event. Why? Because you are dragging object and that object only can catch the onMouseOver event.
So, to detect destination table cells, script calculates all table cell coordinates (with scroll page offset) and store them to arrays. Arrays are searched inside onMouseMove handler and after left mouse button is released, object will drop to the current (highlighted) table cell.
In redips2.tar.gz (34KB) package you will find several examples including example how to save/recall table using PHP and MySQL. Instead of using well commented drag.js (in production) from the package, I will suggest to compress it first. Compression will cut out comments and unnecessary space. Finally, drag-min.js will function as the original but with 50% smaller size.
Happy dragging and dropping!
Related posts
- JavaScript Drag and Drop example 4
- JavaScript Drag and Drop example 3
- JavaScript Drag and Drop example 2
- Autocomplete without AJAX
- Image hover using Javascript
Great JavaScript tool. Thanks for this.
Darko,
what a great piece of code! Thanks a lot for your work. I am struggeling with the following:
Basically I need more than just 2 tables to drag and drop. Besides the tables are very large and I want to toggle their visibility. The tables I do not need I want to sort of collapse (display:block vs display:none). Unfortunately when I make one of the tables reappear their is a sort of offset. Any idea about that?
Thanks in advance for sharing your thoughts.
Michael
Found a bug where if I have div tags within the div with class=drag and I try to switch table cells there is an error. The error occurs when you try to drag a cell into another cell that has nested div tags. The fix was not too hard - it seems to work without breaking anything else.
Modified the code in drag.js starting at line 357:
else if (REDIPS.drag.drop_option === 'switch') {
// remove dragged element from DOM (source cell) - node still exists in memory
obj.parentNode.removeChild(obj);
// move object from the destination to the source cell
target_elements = target_cell.getElementsByTagName('DIV');
target_elements_length = target_elements.length;
for (i = 0; i < target_elements_length; i++) {
// source_cell is defined in onmouseup
if (target_elements[0] != null) { //fixes issue with nested DIVS
source_cell.appendChild(target_elements[0]); // '0', not 'i' because NodeList objects in the DOM are live
}
}
Added the if statement to make sure the target_elements object is not null. Please let me know if anyone sees a better way to handling this.
Thanks so much dbunic for this script - great job!
What a fantastic library, thanks very much for this. I've just been playing around with your example 6, and wondered whether it was possible to define an 'only' class for all clones of a div. With your example at the if I made div a infinitely cloneable I would have to code
rd.only.div.ac0 = 'last';
rd.only.div.ac1 = 'last';
rd.only.div.ac2 = 'last';
rd.only.div.ac4 = 'last';
... forever in order to get the functionality I want. Is there a way to get around this? Many thanks.
I have the same issue.
I've tried defining a variable that should extract all id's that start with a specific keyword but with no success
Here is my code:
var gk = $("div[id^='gk_']");
rd.only.div.gk = 'last';
I was hoping that all the divs that had the ids like gk_1, gk_2 etc. would be defined as 'last'.
@chai - I haven't any experience with combining "freeze header" and my drag-n-drop library. I know that you posted question more than a month ago, but can you specify what was the problem? If "freeze header" lib fixes div position and emulates table header with DIV elements, then there shouldn't be a problem. Anyway, it would be easier to help if you can show the problematic example.
@Pete - If I understood well, you want to allow only different content in table cells of right table. Unfortunately, lib has the following possibilities: forbid dropping to the cell, control only certain content to the cell, allow single / multiple content and overwrite content. Currently there is no option to forbid dropping the same content to the table cell. But, you can place custom JS code in myhandler_dropped handler to test if content already exists in target table cell.
And you asked about placing table cell content horizontally. Lib simply adds DIV element to the table cell so you can modify CSS to achieve placing DIV elements side by side instead of vertical positioning.
@SP - Additional action like adding table rows and columns can be done inside myhandler_dropped handler. You only have to place JS code which will be executed after content dropping. Please see example of how to add table rows / columns dynamically on my post: Adding table rows & columns in JavaScript
Save button in this demo only shows prepared query string. Please try to find save() function In JS code of example1 and you will see how to call save_content() function from the drag-n-drop library. Instead of "window.open" you can specify "window.location.href" to point browser to your page.
If you want to place tables side by side, please use DIVs and CSS like in example3. Drag-n-drop library uses tables to define dropping positions.
@Bill - Yes, it's possible to make objects infinitely clone-able. Please find the 1025 line in drag.js:
// remove clone from the class name of the new object
obj_new.className = obj_new.className.replace('clone', '');
If you comment this line, cloned object will stay clone-able.
Drag-n-drop library now has new drop option: "overwrite". If you specify "overwrite":
REDIPS.drag.drop_option = 'overwrite';
... old content will be overwritten with the dropped content.
@Sandy - Drag-n-drop lib is table cell oriented, so spanning across more rows or columns are not possible. But after DIV element is dropped to the table cell, you can clone element to the other table cells like in example3 (if checkbox in upper left corner is checked). Please try.
Hi, thanks for putting this online its fantastic. I have just been playing around with example 1. I am new to all this and wondered if there was a way to set it so 'switch content' is not turned on as a button but programmed to be permanently the case.
It's this bit below I would like to just happen without the user having to click a button but I am a little stuck!!
Thank you
Jonathan
@Michael - I suppose you should run REDIPS.drag.init(); after table is shown. Initialization will scan table cell positions and offset should fit properly.
@Doony - Your code correction is applied and my lib is better than before. Thank you very much!
@hgbreton & Sorin Haidau - Example6 is modified and now every cloned element of element A or B can be placed to the last row no matter how many elements will be cloned. I used myhandler_cloned event handler ... Here is the code:
// after element is cloned define dropping for last row - only for clones of element A or element B
rd.myhandler_cloned = function () {
// define variables
var cloned_id = rd.obj.id; // cloned id
// if cloned if begins with a or b define dropping only for last row
if (cloned_id.substr(0, 1) === 'a' || cloned_id.substr(0, 1) === 'b') {
rd.only.div[cloned_id] = 'last';
}
}
This two line can be ignored - just leave them out.
rd.only.div.ac0 = 'last';
rd.only.div.bc0 = 'last';
@Jonathan - If you want the "switch" mode behavior, just define drop option after initialization. Here is how:
// initialization
REDIPS.drag.init();
// switch mode
REDIPS.drag.drop_option = 'switch';