My Convoluted Journey for Tables With Custom Rendering in V2

currently running: 2.0-beta (0.10.4-242-gb1b13635)

Motivation

I’m not a web developer – I’m sure there are easier ways to do this directly in js, but I was working off of the APIs and for fun :stuck_out_tongue:

One particular workflow that I had going in V1 was a table in my index.md that had:

Recently Modified Pages

name status
page1 [delete]
page2 [protected]

Where [delete] or [protected] are buttons – one that deletes the page, and one disabled one to prevent oopsies. I used this quite a bit because I often create scratch pages for testing.

Which looks fine until you mouseout of the markdown text – then it looks like

Ah, the dreaded html _isWidget true

End Result

Strategy Over Time:

  1. Build table / tr / td by hand using the DOM API, generating one row per table row, and one cell per key/val pair
  2. Realize that constructing by hand means you have to implement everything
  3. Find a way to convert markdown back into the dom API to enable reuse (see below)
  4. Customize the table implementation to inject data classes, and other custom definitions.

Key Insight

Looking over the implementation of the DOM API, I figured I could copy how the current library constructs nodes – a little stackoverflow later, I get:

-- takes HTML and translates back to DOM
-- helpful for integrating w. the dom API while reusing
-- other HTML components like markdown rendering
function dom_.fromHtml(html)
  local div = js.window.document.createElement('div')
  div.innerHTML = html
  return div.firstChild  
end

-- Takes raw table values and reuses linkifying, hashtags, etc..
function dom_.fromMarkdown(md)
  return dom_.fromHtml(markdown.markdownToHtml(md))
end

What this allowed me to do was reuse most of the cell rendering while retaining the ability to customize :slight_smile:

Code

The actual table implementation is not very interesting – namely lots of pairs, ipairs (and util methods to convert between the various forms). But the API surface to reach the level of specificity might be of interest

tbl = tbl or {}

function tbl.deletePageWidget(data, cssPrefix)
  function css_class(elm)
    unique = cssPrefix .. elm
    return table.concat({unique, cssPrefix}," ")
    -- this allows for both targeted: css-prefix-td
    -- and compound: css-prefix td
  end
  
  cssPrefix = cssPrefix or "delete-table-"
  -- I don't know how to map from lists in lua so I wrote one
  return tbl.table(tbl.foreach(
    data,
    function(row)
      return { 
        name = row.name,
        -- protected == key below
        protected = table.includes(row.tags, "protected")
      }
    end
  ), tbl.opts{
    -- injectable / customizable
    rowOpts = {
      tdClass = css_class("td"),
      trClass = css_class("tr"),
      headerClass = css_class("header"),
      -- also have 
      -- onRow(data, isHeader, onCell, onCellMatch, refKeys, tagKeys)
      -- onCell(key, cell, row, header)
      -- onCellMatch(key, cell, row, header)

      -- key is the column, cell is the value
      -- row is the entire row (i.e. for constructing the delete function)
      onCell = function(key, cell, row, isHeader)
        if key == "name" and not isHeader then
           -- references are otherwise just text in a cell; need to do this explicitly
           -- or otherwise pass information on which columns are refs
          return navigation.refLink{ ref = cell }
        elseif key == "protected" and cell then
          return dom.button { "Protected", disabled="" }
        elseif key == "protected" and not cell then
          return dom.button { 
            "Delete", class=css_class("button"),
            onclick = function()
              -- my own delete function with other hooks
              delete.deletePage(row.name)
            end
          }
        end
        -- sane defaults for rendering hashtags, other table text
        return dom_.fromMarkdown(cell)
      end
      },
})  
end

If there’s a way to lift a lot of the business logic to JS (like table manipulation – I found lua tables somewhat confusing), would love to hear it!

Otherwise this was a fun little detour, during which I took no notes and was just tinkering with the environment :sweat_smile:

4 Likes