Resize Data Grid Column by Drag & reszie

I’ve been looking for the solution for some time: how to let the user to resize the columns if needed.

The only thing that I need to fix somehow would be to find out in which column the dragged component is located. To change the part loop in def on_drag

That line need to be changed: if x['title'] == 'expand':

How can I acces this information (yellow marked)?:

from anvil import js

  def __init__(self, **properties):
    self.make_draggable()
  
  def make_draggable(self, **events):
      # Make the label elements draggable in Header Row data_row_panel_1
      self.components1 = self.data_row_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.removeEventListener('drag', self.on_drag)
          drag_node.addEventListener('dragstart', self.on_drag_start)
          drag_node.addEventListener('dragend', self.on_drag_end)
          drag_node.addEventListener('drag', self.on_drag)

  def on_drag(self, event):
      # Check if a valid drop target is selected
      if self.resizing:
          # Calculate the new width based on the mouse position
          new_width = event.clientX - self.resizing_offset
          if new_width > 0:
              # Set the new width for the right column
              for x in self.data_grid_assay.columns:
                if x['title'] == 'expand':
                  x['width'] = new_width
              self.data_grid_assay.columns = self.data_grid_assay.columns

  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)
  
      # Store the current column being resized
      self.resizing = event.target
  
      # Store the initial mouse position
      self.resizing_offset = event.clientX - self.resizing.getBoundingClientRect().width
  
  def on_drag_end(self, event):
      # Reset the resizing variables
      self.resizing = None
      self.resizing_offset = 0

I’m not sure that Drag and Drop is the right tool for the job. What object would you pick up? And where would you drop it to add it?

If you just mean to Drag something that’s already there, like a (grabbable) dividing line between two columns, that wouldn’t be “and Drop”, as I understand it.

1 Like

You’re right. It is only Drag. But how I can add it to a Data grid to expand the width by dragging? Almost every Table in diverse programs and apps has that option.
Ecen Anvil but only in editor

I’m fairly sure this has been discussed before, here on the forum. What has forum Search found for you so far?

Belive me I’ve searched. There were few topis how to resize it, but only by the edition of the dict passed to columns. Like I’m already using. There was no topic with drag & resize.

And few topics with drag components around.

I need somehow to access this infgormation:

That’s what it looks like after Anvil has converted it to HTML. Many more details to sift through.

There is a way to get the grid column id. It’s stored in the Form’s .yaml file. This file isn’t directly visible in the IDE. The IDE renders it, visually, as the form itself. But if you’ve used Git to get a local copy of your app’s files, you’ll find it there.

In the file, in each Data Grid, each column has an id. It’s actually fairly readable (excerpt follows):

- type: DataGrid
  properties:
    role: null
    columns:
    - {id: DKZQJJ, title: ID, data_key: fund_id, $$hashKey: 'object:1439', width: '40',
      expand: false}
    - {width: '200', title: Fund Name, id: TGNTOU, data_key: description, expand: false,
      $$hashKey: 'object:1374'}
    - {width: '', title: 'Same for All Years?     Enter Rate(s)', id: ZGWAKB, data_key: fee,
      expand: false, $$hashKey: 'object:1376'}
    auto_header: true
    tooltip: ''
    border: ''
    foreground: ''
    rows_per_page: 6
    visible: true
    wrap_on: never
    show_page_controls: true
    spacing_above: small
    spacing_below: none
    background: ''
  name: data_grid_1
  layout_properties: {grid_position: 'XTMNTS,RGILUI', full_width_row: false}
  components:
  - type: RepeatingPanel
    properties: {role: null, tooltip: '', border: '', foreground: '', items: null,
      visible: true, spacing_above: none, spacing_below: none, item_template: Row_VariableSubaccountFees,
      background: ''}
    name: rp_funds
    layout_properties: {}
    data_bindings: []
  - type: Label
    properties: {role: text, align: left, tooltip: '', border: '', foreground: '',
      visible: true, text: All fees above will be deducted from the Accumulation Fund Value (AV).,
      font_size: null, font: '', spacing_above: none, icon_align: left, spacing_below: none,
      italic: false, background: 'theme:White', bold: false, underline: false, icon: ''}
    name: label_1
    layout_properties: {grid_position: 'MMJURN,BCEEHF', slot: footer}
  data_bindings: []

In principle, you can also extract it from the downloadable project file, also in .yaml format, but that’s harder, because that .yaml file contains every file in the app.

These column ids are stable, though if you use the IDE to remove a column, and to add one, the new one will likely have a completely new id.

Python code tends to use the same member names, internally, so you’ll find it as id also in the column definitions. Try print(self.data_grid_1.columns), and see what shows up in the IDE’s “Running App Console” tab.

This is only a piece of the puzzle, but it may help get you started.

1 Like

The Grid I’m using was created not by code, so I can see the IDs on each object located in column:

I need a way that directly access it from the dragged component and use it to resize the corresponding column of data grid.
but component.column or component.layout_properties['grid_position'] don’t exist. There has to be a way to access that information from the component. That is why I thought about HTML.

Ok, I found the solution. I read the docs about HTML Anchor Element.

I have to acces HTML Anchor element with .getAttribute('data-grid-col-id')
But the component itself don’t hold the information like in HTML the previous div does. So in my case 1x current_element.parentElement to acces the right level. I put in a loop anyway, just in case someone will put the component in some container first.

      # Start from the current element and traverse up the DOM tree
      current_element = self.resizing
      # Keep looping until we find an element with the data-grid-col-id attribute or reach the top of the DOM
      while current_element is not None:
          # Check if the current element has the data-grid-col-id attribute
          self.col_id = current_element.getAttribute('data-grid-col-id')
          
          if self.col_id is not None:
              # Found the attribute, break out of the loop
              break
          
          # Move up to the parent element
          current_element = current_element.parentElement

To recreate:

  1. hide the header column of data grid
  2. add Data Row panel as custom pinned header row
  3. add at least 1 component to the column that you want to resize (Label, Link …)
  4. paste the code and change names to fit your components.
  5. Consider adding right CSS styles to allow expaning beyond the screen with overflow-x: auto

Whole code:

from anvil import js

  def __init__(self, **properties):

    self.on_drag_start = self.on_drag_start
    self.on_drag_end = self.on_drag_end
    self.on_drag = self.on_drag
    self.make_draggable()

  def make_draggable(self, **events):
      # Make the label elements draggable in grid_panel_1
      self.components1 = self.data_row_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.removeEventListener('drag', self.on_drag)
          drag_node.addEventListener('dragstart', self.on_drag_start)
          drag_node.addEventListener('dragend', self.on_drag_end)
          drag_node.addEventListener('drag', self.on_drag)

  def on_drag(self, event):
      # Check if a valid drop target is selected
      if self.resizing:
          # Calculate the new width based on the mouse position
          new_width = event.clientX - self.resizing_offset
          if new_width > 0:
              # Set the new width for the column
              #self.resizing.style.width = f"{new_width}px"
              for x in self.data_grid_assay.columns:
                if x['id'] == self.col_id:
                  x['width'] = new_width
              self.data_grid_assay.columns = self.data_grid_assay.columns
  
  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)
  
      # Store the current column being resized
      self.resizing = event.target
    
      # Store the initial mouse position
      self.resizing_offset = event.clientX - self.resizing.getBoundingClientRect().width
    
      # Start from the current element and traverse up the DOM tree
      current_element = self.resizing
      # Keep looping until we find an element with the data-grid-col-id attribute or reach the top of the DOM
      while current_element is not None:
          # Check if the current element has the data-grid-col-id attribute
          self.col_id = current_element.getAttribute('data-grid-col-id')
          
          if self.col_id is not None:
              # Found the attribute, break out of the loop
              break
          
          # Move up to the parent element
          current_element = current_element.parentElement

  
  def on_drag_end(self, event):
      # Reset the resizing variables
      self.resizing = None
      self.resizing_offset = 0

Be really careful with addEventListener and removeEventListener when using a method as a callback.
In your code the event listeners never get removed
If you add print statements on the first line of each method
you might find that as you drag and then re-drag the same elements,
you’ll get more and more print statements

How to fix

Short version:
add the following lines to your __init__ method


self.on_drag_start = self.on_drag_start
self.on_drag_end = self.on_drag_end
self.on_drag = self.on_drag

Explanation

in JavaScript an event listener is only removed if it is identical to an existing event listener

In a client/server side repl try

>>> class Foo:
        def bar(self):
            pass
>>> foo = Foo()
>>> foo.bar == foo.bar
True # equal
>>> foo.bar is foo.bar
False # but not identical
>>> foo.bar = foo.bar
>>> foo.bar is foo.bar
True # now identical

The above snippet demonstrates that methods on instances are equal but not identical
If we do the assignment you see that we can make methods identical

This is really subtle and has caught me out a number of times!

Related discussion

1 Like

Just to dig this up, as of v133 this doesn’t work in Firefox because drag events don’t include any coordinates like clientX or similar