monomer icon indicating copy to clipboard operation
monomer copied to clipboard

Building (polymorphic) tables/datagrids

Open Clindbergh opened this issue 2 years ago • 6 comments

As part of Zurihac 2022 I am considering to explore Monomer further and implement a basic table functionality. I am relatively new with Haskell, but I think there could be a number of interesting ways to explore this concept further. I hope to find some team members to work together on this concept.

Ideas (roughly ordered from basic to advanced)

  1. Given an arbitrary record render a table with all its values. Alternatively render only some values of a Field type. Perhaps some code generation capabilities may be required which (I think) TemplateHaskell or the lens library does (again, I'm quite junior with this).
  2. Scrolling to view more records if the table is to large to be displayed entirely.
  3. Selecting one or multiple rows (or even cells or columns)
  4. Lexical ordering by a given column.
    1. Sorting by a column could be triggered by clicking its header. A second click may change the order (ascending/descending) or set it to default.
    2. Secondary, tertiary sorting could be triggered by clicking another, currently unsorted column.
  5. Add and remove columns to be displayed.
  6. Move the column order by dragging its headers.
  7. Filter the table using textboxes on the column headers.
  8. Display images or possibly other types in the cells (e.g. use an icon for boolean values).
  9. Display children records
  10. Paging
  11. Editing cell values inline. Clicking a label would swap the label with a textfield.
  12. Perhaps exotic: Hierarchical rows: Expanding a row would show its children. Similarly hierarchical (or grouped) headers may be possible.
  13. Load remote data

There is likely a number of ways to develop this concept further. Here is an example of a commercial component for the web.

Questions

  • How do you llike this idea? Are there any features you would like to see or can you make a prioritization?
  • Can you think of any (hidden) pitfalls or missing functionality that would impede the implementation?
  • Are you aware of any similar efforts with Monomer or Haskell in general?
  • Do you have any recommendation regarding the implementation? I wonder if it might be reasonable to implement the table as a widget.
  • Perhaps we can work towards a Pull Request either as a library functionality or as an example project. Let me know if you're interested.

Clindbergh avatar May 31 '22 11:05 Clindbergh

Funnily enough I have been working on a datagrid library recently.

It is a bit of toy, and not really suitable for public consumption, and I'm making no commitments regarding maintaining it. Certainly I'd prefer it if there was another project with similar features I could use instead (I might contribute but again no commitments).

The repo is now (I just hit the button) public: https://github.com/Dretch/monomer-hagrid -- It has some of features you list (2 - though there is a bug with scrollbars not showing up as you might expect, 4 - but only single column sorting, 8 - custom cell widgets can be used). You are welcome to take anything from it.

Dretch avatar May 31 '22 16:05 Dretch

Awesome, thanks for publishing! I have not yet been able to study it in-depth or running it, but it looks like a nice starting point. I am excited to try it. Definitely helpful for learning purposes.

Regarding the scollbar: I saw that Monomer already has some Support for it Scroll.hs. If the table becomes too large, it might become a performance issue - but that's an advanced topic.

Clindbergh avatar Jun 01 '22 09:06 Clindbergh

@Dretch's solution will give you full control over tables, but if you don't want that much power, you can build table from other widgets. Something like (haven't tested this):

table = vstack [
    flip styleBasic [paddingB 5] $ box headerRow
  , separatorLine
  , vscroll $ vstack $ tableItems
  ] `styleBasic` [padding 10, border 1 gray]
  where
    colWidths = [0.2, 1.0, 0.7, 0.5]
    labels = ["#", "Name", "Location", "Phone Number"]

    headerRow = hstack $
      zipWith mkCol (zip (repeat labelRed) labels) colWidths
    tableItems = zipWith mkRow [0..] $ model ^. people

    mkCol (widget,lens) width =
      widget lens `styleBasic` [flexWidth width]

    -- TODO: 2nd param for label in - swap label with
    -- input field when double clicking.
    mkRow index _ =
      let
        lens' field = people . singular (ix index) . field
        cols = [
          (labelS, index+1)
        , (flip textField_ [placeholder "name"], lens' name)
        , (flip textField_ [placeholder "location"], lens' location)
        , (numericField, lens' phone_num)
        ]
      in
        hstack $ zipWith mkCol cols colWidths 

    labelRed l = label l `styleBasic` [textColor firebrick]

MuhammedZakir avatar Jun 01 '22 15:06 MuhammedZakir

Regarding the scollbar: I saw that Monomer already has some Support for it Scroll.hs. If the table becomes too large, it might become a performance issue - but that's an advanced topic.

Currently monomer-hagrid is using two built-in scrolls: one vertical and one horizontal. This doesn't really work very well though because both the vertical scrollbar is only visible when the horizontal one is scrolled all the way to the right. Really it should be using a bi-directional scroll, but this requires a little extra work to keep the headers at the top all the time (rather than scrolling with the content). This is probably straightforward to implement though.

Regarding performance: so far this is the only area that has been challenging. My example works ok with 100 rows, but is very slow (e.g. column resizing lags) with 1000 rows. I have spent only a little time profiling, but it seemed that lots of time was being spent calculating text sizes. Some datagrid libraries use some sort of "virtual grid" where only items on screen get turned into real widgets... but this is quite complicated and 1000 rows is really not that much that i'd really expect to need that. So maybe the text layout could be optimized somewhat, with some time invested.

Dretch avatar Jun 03 '22 20:06 Dretch

@Clindbergh sorry for the long delay, I've been dealing with personal issues, and I'm slowly resuming work on the library.

A data table would be really useful! It's one thing I always have in my mind regarding possible widgets to add but, at the same time, I always felt it should be an external project since data tables tend to have a large scope and be very opinionated.

The list you propose is great, and it's pretty logical in terms of the progression of complexity. In general, I try to play with ideas for a bit to get a feeling about how the widget will be used; it also helps me realize if I need all the features I envisioned and see how the more advanced features could fit. A couple of minor points about the list:

  • I would probably leave the nested/hierarchical functionalities last since they could be a significant effort.
  • Regarding external data load, providing a generic interface the clients can use to provide data could be enough. Depending on your use case, just receiving the list of items as a parameter might be enough.

Regarding the implementation, it depends a bit on what you want to do. If your plan is a text-only table, implementing a widget with custom rendering from scratch might be simpler. You will have to take care of layout and formatting, but optimizations will be much easier. On the other hand, if you want to allow using any available widget for content (which seems to be the case based on the mention of checkbox and images), you can use a Composite or do something similar to what selectList does. Doing it the selectList way requires more work, but it's more flexible because you have direct handling of events and it gives you the possibility of rendering custom content.

If you have started working on your project and want input on how parts of the library work or how I would approach certain things, let me know and I'll happily take a look.

fjvallarino avatar Jun 12 '22 20:06 fjvallarino

@Dretch I agree that the text layout functions need performance improvements. The code is pretty naive, and several things could be done to improve performance. I'll take a look in the next few weeks.

Regarding the virtual grid, it's something I have thought about and could be implemented as a custom container that is embedded inside a scroll and takes care of creating/dropping rows of widgets as needed (scroll's onChange event provides information about the visible content). The only potential issue is the internal state of widgets. For example:

A row has a textField, and the user moves the cursor/caret and then scrolls, causing the row to become invisible. Since only a few rows are kept active, this row would be dropped and its state lost. When the user scrolls back, the caret would be at the end of the textField and not in the expected position. It is a minor issue, but it's important to know there are caveats. The internal state would not be a problem if the tables are mostly static or content editing is handled differently (e.g., laying out something on top).

fjvallarino avatar Jun 12 '22 20:06 fjvallarino

I'm not sure there's an action pending here. From my point of view, I suggest using @Dretch's hagrid for a feature-full table, or the solution proposed by @MuhammedZakir when a simpler table suffices.

I'll close the issue now. Please re-open or create a new one if you feel something is missing. Thanks!

fjvallarino avatar Oct 27 '22 18:10 fjvallarino

Perfectly fine, thanks @fjvallarino! And thank you @Dretch & @MuhammedZakir for your valuable inputs!

Clindbergh avatar Jan 12 '23 18:01 Clindbergh