Hopefully a useful method of implementing drag and drop in an Anvil native way (no extra JS libraries to pick through!)
Step 1:
Create a blank app (I’ve used a Material Design 3 here).
Step 2:
In you main form, set your imports:
import anvil.js
Step 3:
Set up your form’s init to make the drop zones work and give your drop columns an identifier:
class Form1(Form1Template):
def __init__(self, **properties):
self.init_components(**properties)
grid1 = anvil.js.get_dom_node(self.grid_panel_1)
grid1.classList.add('gridnumber1')
grid2 = anvil.js.get_dom_node(self.grid_panel_2)
grid2.classList.add('gridnumber2')
# Add event handlers to grid_panel_1 for dropping items
drop_node = anvil.js.get_dom_node(self.grid_panel_1)
drop_node.removeEventListener('dragover', self.on_drag_over)
drop_node.removeEventListener('dragenter', self.on_drag_enter)
drop_node.removeEventListener('drop', self.on_drop)
drop_node.addEventListener('dragover', self.on_drag_over)
drop_node.addEventListener('dragenter', self.on_drag_enter)
drop_node.addEventListener('drop', self.on_drop)
# Add event handlers to grid_panel_2 for dropping items
drop_node = anvil.js.get_dom_node(self.grid_panel_2)
drop_node.removeEventListener('dragover', self.on_drag_over)
drop_node.removeEventListener('dragenter', self.on_drag_enter)
drop_node.removeEventListener('drop', self.on_drop)
drop_node.addEventListener('dragover', self.on_drag_over)
drop_node.addEventListener('dragenter', self.on_drag_enter)
drop_node.addEventListener('drop', self.on_drop)
self.make_draggable()
Step 4:
Set up your make_draggable function:
def make_draggable(self,**events):
# Make the label elements draggable in grid_panel_1
self.components1 = self.grid_panel_1.get_components()
for node in self.components1:
drag_node = anvil.js.get_dom_node(node)
drag_node.setAttribute('draggable', 'true')
drag_node.removeEventListener('dragstart', self.on_drag_start)
drag_node.removeEventListener('dragend', self.on_drag_end)
drag_node.addEventListener('dragstart', self.on_drag_start)
drag_node.addEventListener('dragend', self.on_drag_end)
# Make the label elements draggable in grid_panel_2
self.components2 = self.grid_panel_2.get_components()
for node in self.components2:
drag_node = anvil.js.get_dom_node(node)
drag_node.setAttribute('draggable', 'true')
drag_node.removeEventListener('dragstart', self.on_drag_start)
drag_node.removeEventListener('dragend', self.on_drag_end)
drag_node.addEventListener('dragstart', self.on_drag_start)
drag_node.addEventListener('dragend', self.on_drag_end)
Step 5:
Set up your drag and drop event functions:
def on_drag_start(self, event):
# Set the data that will be transferred during the drag operation
event.dataTransfer.setData('text/html', event.target.outerHTML)
def on_drag_end(self, event):
# Remove the item from the source panel
source_component = event.target
source_panel = source_component.parentNode
if source_component and source_component.parentNode:
if source_component.parentNode.contains(source_component):
source_component.parentNode.removeChild(source_component)
def on_drag_over(self, event):
# Allow dropping on the target by preventing the default action
event.preventDefault()
def on_drag_enter(self, event):
# Get a reference to the target panel
target_panel = anvil.js.get_dom_node(event.target)
# Check that the target panel is a valid drop target
if target_panel.classList.contains('drop-target'):
# Add a CSS class to indicate that the panel is a valid drop target
target_panel.classList.add('drag-over')
# Allow the drop operation
event.preventDefault()
def on_drop(self, event):
# Get the data that was transferred during the drag operation
data = event.dataTransfer.getData('text/html')
# Remove the class from the target to unhighlight it
event.target.classList.remove('drag-over')
# Add the dropped item to the target panel
target_panel = anvil.js.get_dom_node(event.target)
newcomponent = HtmlTemplate()
newcomponent.html = data
if target_panel.classList.contains("gridnumber1"):
oldcomponent = Label(text=data)
oldcomponent.remove_from_parent()
self.grid_panel_1.add_component(newcomponent)
elif target_panel.classList.contains("gridnumber2"):
oldcomponent = Label(text=data)
oldcomponent.remove_from_parent()
self.grid_panel_2.add_component(newcomponent)
else:
print("Didn't work")
self.make_draggable()
Step 6:
Add column panels to your form and call them grid_panel_1 and grid_panel_2.
Drop a few cards in your grid_panel_1 column.
Run your app!
Recap:
Without using additional libraries, you’ve made items in your columns draggable AND droppable.
How you handle the initial creation of the cards and the handling of data is totally open to your imagination!
Goodnight, Jira. Fairwell, Trello.
Anvilistas assemble!
Updated: to remove a custom html component and just use HtmlTemplate instead, thanks to @jshaffstall
Optional:
I’ve taken the choice to make my grid-item cards “elevated-card” role components, and to give them some interactivity I’ve updated the anvil-role-elevated-card CSS as follows:
.anvil-role-elevated-card {
overflow: hidden;
border-radius: 12px;
background-color: %color:Surface%;
/* 2dp */ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
padding: 15px;
transition: all 0.2s ease-in-out;
}
.anvil-role-elevated-card:hover {
transform: scale(0.98); /* reduce size to 0.97 on hover */
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2); /* add a small box shadow */
opacity: 0.9; /* reduce opacity to 0.8 on hover */
}