# IupTable

Definition: | include pGUI.e Ihandle ih = IupTable(sequence columns, data, integer visible=10, string attributes="", dword_seq args={})
Description: | Creates a resizeable table with multiple sortable columns. An IupTable is a Phix/pGUI invention, expressly written for pwa/p2js, but of course it can be freely used for strictly desktop-only purposes. It provides common ground between an HTML table and a desktop IupMatrix.
pwa/p2js: | Supported. Quite possibly deserves a complete rewrite along the lines of IupGraph, though, so if you can’t quite get this to behave, maybe a gentle prod in that direction...
Comments: | Columns can be sorted by clicking on their headings. It maintains a stack of sort orders, so for instance if you click on amount then date, it shows items in amount order within each unique date, plus any with both the same amount and date matching (aka all clicked columns) in their original order. Columns can also be resized by dragging the edges between column headings, and resizing the window expands the table to fit, retaining the existing column width ratios as best it can. Only a single line can be selected at a time (currently), and the tables are always read-only. Internally, pGUI.e implements an IupTable via an IupMatrix, whereas (obviously) pGUI.js implements an IupTable via <table>. The internals of IupTable are really quite straightforward: they just adopt a very narrow view of what we need to achieve from an IupMatrix, and/or the html, css, and JavaScript we need to use in a browser. Obviously, should IupTable not quite meet your needs, you are free to take a copy of it and tweak it, the only real question being whether you or I can make such changes work on both the desktop and browser. columns: each title can be a string or a {title,width,align} triplet. data: should be length 2 with length(data[1][row])=length(columns) and length(data[2])<=length(columns). visible: the number of lines to show. For more information on the attributes and args[nee data] parameters see IupSetAttributes. data[1] contains the master/sortable values. data[2] is reserved for (string) display versions of data[1] or format string/callbacks: if data[2] is too short or data[2][col] is 0, then data[1][row][col] is displayed as-is if data[2][col] is a (single) string then sprintf(data[2][col],data[1][row][col]) is displayed. if data[2][col] is a non-zero integer it is assumed to be the routine_id of a function that will format data[1][row][col] properly for display (see demo\pGUI\IupTable.exw for examples), or data[2][col] can be a sequence of pre-formatted strings, as (now commented out) in the example below. for dates, data[1][row][col] should be eg 20201122 or timedate-format, and data[2][col] a format string/routine or data[2][col][row] a string. for case-insensitive sorting, data[1][row][col] should be lower/upper(data[2][col][row]). Note that data[1] is one-per-line whereas data[2] is one-per-column (see example below). If column titles are specified as strings (as per the example below), the width defaults to 80, corresponding to 20 characters, and the alignment to "ALEFT" (legal values are that, "ARIGHT", and "ACENTER"). Note that IupMatrix() and hence IupTable() does not support individual column heading alignment, afaik, whereas under p2js column headings are aligned the same as the column data. Returns: the identifier of the created element.
See Also: | IupMatrix
Example: | Initial appearance: Sorted appearance: Browser appearance:
    include pGUI.e
    constant columns = {"Chq","Date","Amount"},
                       -- or eg {..,{"Amount",100,"ARIGHT"}}
             data = {{{1,{2020,1,13},1500.89},
                      {2,{2020,7,10},3646.86},
                      {3,{2019,3,20},3807.94}},
                     {0,
    --                {`13/07/2020`,`20/01/2020`,`10/03/2019`}, -- (pre-1.0.1)
                      "%[3]2d/%[2]02/%[1]4d",
    --                {`1500.89`,`3646.86`,`3807.94`}, -- (pre-1.0.1)
                      "%.2f"}}  -- (see note)
    IupOpen()
    Ihandle table = IupTable(columns,data,3),
            dlg = IupDialog(table)
    IupSetAttribute(dlg,"RASTERSIZE","280x134") -- (see note)
    IupSetAttribute(dlg,"TITLE","IupTable demo")
    IupSetAttributeHandle(NULL,"PARENTDIALOG",dlg)
    IupShow(dlg)
    if platform()!=JS then
        IupMainLoop()
        IupClose()
    end if
In the above, data[2][1] is 0, meaning that values from data[1][i][1] should be displayed for column 1. Putting pre-formatted strings in data[1] for dates and amounts would not sort correctly so instead we provide a format string (and prior to 1.0.1 had to supply a complete table of pre-formatted strings). Without that final %.2f format the amounts could appear with an apparently random number of decimal places, likewise dates as eg "{2020,1,13}". Note that while data[2] could contain formatting routines for each column (see demo\pGUI\IupTable.exw for examples), it simply cannot, for instance, "undo" case-insensitive values stored in data[1]. The "double storage" cost of case-insensitive columns is deemed a small and rare edge case price to pay for keeping the interface simple. Note my efforts have focused on getting width-wise fitting. The table is rarely as neatly fitting vertically as the above images imply, not that it really matters when the table is longer than the viewport, which many are. With the latter in mind, I have deliberately shown a vertical scrollbar (via explicitly setting RASTERSIZE), although it isn’t really needed. I have also lost several handfuls of hair over trying to force when the scrollbars should and should not appear: there are still a few minor hiccups which are beyond my ability to fix, see demo\pGUI\pGUI.e routine IupTableResize_cb() for more details. #### UPDATE Creating IupTable() as a p2js-compatible alternative for IupMatrix() was my first foray into such adventures, beguiled by the idea that I could use IupMatrix on desktop/Phix for not much effort, however in reality I have spent far longer fighting it than I would have spent writing code to perform all the drawing and mouse handling operations directly. In the more recent IupGraph() alternative for IupPlot() I took a more sensible approach, namely creating an IupCanvas() and doing everything by hand, as opposed to wrapping IupPlot() on the desktop and doing things manually only inside pGUI.js, and as a bonus just auto-transpiling it and not having to write any JavaScript at all. A complete rewite of all this using lessons learnt from the latter is probably in order, but layout/sizing issues, in general across all containers and controls, have a much higher priority - though I’ll quietly ignore any such in IupTable!
Extra Functions | IupTableClick_cb(): see CLICK_CB IupTableEnterItem_cb(): see ENTERITEM_CB IupTableResize_cb(): see RESIZE_CB integer idx = IupTableGetSelected(Ihandle table) table: Identifier of the interface element. Returns the index of the currently selected line, or 0 if none. Note this can subscript the data, whereas the line numbers passed to click_cb and enteritem_cb cannot, at least certainly not with column sorting in effect (if needed an IupTableMapLine() function could easily be added). IupTableClearSelected(Ihandle table) table: Identifier of the interface element. Clears currently selected line, if any. sequence data = IupTableGetData(Ihandle table) ih: Identifier of the interface element. Returns a copy of the data stored for display in the table - probably not needed in most cases, since the application is probably perfectly aware what data it previously associated with which table, and still has a copy of that. IupTableSetData(Ihandle table, sequence data, bool bReset=true) ih: Identifier of the interface element. data: an updated copy of the data to be stored for display in the table. data must be a sequence of length 2, as per the parameter in the initial IupTable() call. bReset: removes any column-sorting and selection if either bReset is true or the length of data[1] is changed. The display is automatically refreshed. It may or may not prove helpful to add an idx=0 parameter to both the above routines, such that if zero then data is the whole shebang, otherwise it is a single element.
Attributes: | General
RASTERSIZE+ | (non inheritable) the initial size is determined from the sum of the column widths and the number of visible lines. Unlike some other controls it should not be necessary to set this to null to allow the automatic layout to use smaller values.
Callbacks: | You may not use or override VALUE_CB or COLRESIZE_CB or MAP_CB. (You can use MAP_CB on the dialog, just not on the table itself.) Internally it uses CLICK_CB, ENTERITEM_CB, and the containing dialog’s RESIZE_CB, however they can be overidden/extended as below. It also uses MOUSEMOVE_CB and LEAVEWINDOW_CB, if ever needed the internal routines could easily be made global as per CLICK_CB etc, but any override implies the default hover effects are not wanted, and everything (bar said) will work just fine without them. Other callbacks and attributes of IupMatrix may function in a desktop-only scenario, but nothing else is yet tested or supported under pwa/p2js.
CLICK_CB | Action generated when any mouse button is pressed over a cell. function click_cb(Ihandle ih, integer lin, col, atom pStatus) ih: identifier of the element that activated the event. lin, col: Coordinates of the cell where the mouse button was pressed. pStatus: (a char*) Status of the mouse buttons and some keyboard keys at the moment the event is generated. The same macros used for BUTTON_CB can be used for this status. If overidden, it should invoke the internal default, which implements column sorting, as follows:
    function click_cb(Ihandle ih, integer l, c, atom pStatus)
        if l=0 then return IupTableClick_cb(ih, l, c, pStatus) end if
        ...
        return IUP_DEFAULT
    end function
ENTERITEM_CB | Action generated when a matrix cell is selected, becoming the current cell. Also called when matrix is getting focus. Also called when focus is changed because lines or columns were added or removed (since 3.9). function enteritem_cb(Ihandle ih, integer lin, col) ih: identifier of the element that activated the event. lin, col: Coordinates of the selected cell. If overidden, it should invoke the internal default, which implements proper line selection/marking/focus settings, as follows:
    function enteritem_cb(Ihandle table, integer lin, col)
        {} = IupTableEnterItem_cb(table,lin,col)
        integer idx = IupTableGetSelected(table) -- (usually rqd)
        ...
        return IUP_DEFAULT
    end function
RESIZE_CB | If overidden, or a dialog contains more than one IupTable, it should replicate/replace the internal workings as follows:
    function resize_cb(Ihandle dlg, integer width, height)
        for t=1 to length(tables) do -- (declared/set manually)
            IupSetAttributePtr(dlg,"TABLE",tables[i])
            {} = IupTableResize_cb(dlg, width, height)
        end for
        ...
        return IUP_DEFAULT
    end function
also | K_ANY (aka KEY_CB)
Example: | See demo\pGUI\IupTable.exw