# gTable

Definition: | include xpGUI.e gdx id = gTable(sequence columns, data, [integer rows=10, ] string attributes="", dword_seq args={}) -- or (WIP) -- gdx id = gTable(rtn data, string attributes="", dword_seq args={})
Description: | INCOMPLETE: There is no scrolling, sorting, or selection or any kind, as yet... (The plan is to implement scrolling and selection on a gList first) Creates a resizeable table with multiple and optionally sortable columns. columns: each element should be "title" or {"title",width[,"align"]}, see below. data: data[1] contains the raw sortable data, data[2] contains formatting info or pre-formatted data, see below. rows: the default number of visible lines to display, should be <= length(data[1]). For more information on the attributes and args parameters see gSetAttributes(). This is a (lightly) paranormalised function (see technicalia). Returns: the identifier of the created element.
pwa/p2js: | Outdated: still uses a HTML <table> approach, whereas desktop/Phix has been(/is being) replaced by a gCanvas-based approach, which deserves properly porting when finished.
See Also: | gCanvas, gList
Example: |
    --DEV wip...
    -- demo\xpGUI\gTable.exw
    include xpGUI.e
    constant columns = {{"Chq",38,"R"},
                        {"Date",100,"C"},
                        {"Amount",100,"R"}},
             -- or just {"Chq","Date","Amount"},
            ?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`},
                      "%[3]2d/%[2]02d/%[1]4d",
    --                {`1500.89`,`3646.86`,`3807.94`},
                      "%.2f"}}  -- (see note)
    function data(integer line, col)
    end function

    gdx table = gTable(columns,data,3),
          dlg = gDialog(table,"gTable")
    gShow(dlg)
    gMainLoop()
Comments: | DEV this all needs rewriting: Fairly obviously, length(data[1]) determines how many rows there are in the table. Likewise length(columns) determines how many columns are shown, and must match the length of every data[1][i]. Logically it should also match length(data[2]), except trailing 0 are assumed should the latter be shorter. If column[i] is a string, it is treated as width 80 and left aligned. If instead it is {"title",width[,"align"]}, width specifies the minimum width, and maximum if negative, else it gets a pro-rata share of any available space. The align string should be "L", "C", or "R" for left/centre/right alignment, or a similar two-character version should you want to specify the {title,data} alignment differently, eg "CR" makes the column title centred but the data right-aligned. The values in data[1] should sort naturally, which is not the case for display/formatted versions of dates and amounts, so those need to go in data[2], as a complete set of pre-formatted strings (as shown commented out), or a single format string (as shown), or a formatting routine (not shown, but there is one in demo\xpGUI\gTable.exw), or 0 for "common sense" formatting of the values in data[1]. Without that final %.2f format the amounts could appear with an apparently random number of decimal places, likewise (correctly sortable) dates as eg "{2020,12,31}". For case-insensitive sorting, data[1][i] should contain lower/upper versions and data[2][i] the originals: the "double storage" cost is deemed a small price to pay for keeping the interface simple. Columns can be sorted by clicking on their headings, with twice flipping the order, and a third click removing it. 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, allocating any slack in a pro-rata fashion, for example column widths of {80,20,100} get 80/200, 20/120, and 100/100 respectively, with the slack reduced by the width actually alloted to each column after each step. ? Column titles can also be dragged left/right to reorder them. Only a single line can be selected at a time (currently), and the tables are always read-only. The intention is that instead of any in-place editing, updates would be performed via a set of fields for the currently selected row shown below or to one side of the main table, probably with an "Apply" button or similar, and all specified/maintained by the programmer rather than by the gTable() itself. The number of visible lines may of course change when the table is resized, or be ignored/overidden by [outer] SIZE settings. ? If rows is negative, a fixed length table is assumed and resizing (heightwise) disabled, though it can still get cropped, it would be down to a MINSIZE on the dialog to prevent that, or perhaps intervening containers, rather than on the table itself. [DEV] (""preemptively written, need to check what transpires nearer completion...) data[1] contains the master/sortable values. data[2] is reserved for (string) display versions of data[1] or format string/handlers: if data[2] is too short or data[2][col] is 0, then data[1][row][col] is displayed as-is/sprint()d 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 a function that can format data[1][row][col] properly for display (see demo\xpGUI\gTable.exw for examples), or data[2][col] can be a sequence of pre-formatted strings, as now commented out in the example above. 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] the appropriate matching preformatted string. For case-insensitive sorting, data[1][row][col] should be lower/upper(data[2][col][row]). Note that data[1] is one-per-row whereas data[2] is one-per-column (see example above). A gList() is designed to cope with rapidly changing data much more efficiently than a gTable.
Attributes: |
SIZE | (non inheritable) the initial size is determined from the sum of the column widths and the number of visible lines. ?? Explicitly setting a size causes auto-expansion/cropping of columns, and the "rows" parameter of creation to be completely ignored.
? ROWS | (creation only) As per gList.htm#ROWS but with width on a non-expanding table potentailly accumulated from the column definitions. DEV: or as part of columns??
? SELMODE | Specifies whether selection should be line or cell based. Possible values: "LINE", "CELL". Default: "LINE". The alias CELLMODE can also be used, and behaves identically.
? SELECTED | Returns 0 if nothing selected, the currently selected row (integer) when SELMODE is "LINE", of a {row,column} pair when "CELL".
Also: | ? ACTIVE, EXPAND (see note), FONT, MINSIZE, MAXSIZE, TIP, SIZE, VISIBLE Note that SCROLLSIZE and VIEWPORT are automatically handled on a gTable, and you should not attempt to tamper with them.
Handlers: | Note that a gTable uses the standard CLICK, KEY, and REDRAW handlers of the gCanvas on which it is based, and unpredictable results will occur if said are overridden, at least as things stand. The internal redraw handler (xpGUI.e/xpg_redraw_table) is wholly responsible for keeping the column titles static while scrolling the rest of the table. Naturally, for all of the following specify a gdx id should you have some use for it, or just omit it otherwise.
? DATA | [DEV not yet implemented] A fetch function for retrieving the gTable details, from the column headers to specific entries at a specified row and column. function get_data([gdx id,] integer line, col) id: (optional) identifier of the gTable element currently being displayed. line: The row or 0 for column headers, -1 for sort status, as shown above. col: The column (meaningless when line is zero). Must return a string or {string, options}, where options is a sequence of pairs, the first of which is one of bgclr, fgclr, font, ...???
? EDITED | [DEV not yet implemented] Action generted when a table entry is edited. function edited([gdx id,] integer line, col, string s) id: (optional) identifier of the gTable element currently being displayed. line, col: The row and column which has just been edited. s: The updated contents. Note that since the application owns the table data, the revised content (as a string) is passed to this function and should be used to update the table data, or rejected. Immediately after this function returns, get_data(line,col) will be invoked and that result redisplayed in the modified table cell. When no such handler is attached, the table is read-only.
? SELECT | [DEV not yet implemented] Action generated when a row [and cell] is selected, becoming the current row[/cell]. procedure select([gdx id,] integer line[, col]) id: (optional) identifier of the gTable element that activated the event. line[, col]: The row [and column] selected. The col parameter is assumed present when SELMODE is "CELL". Since JavaScript is a typeless language, and to clarify matters: 3 parameters: (id, line, col) with col=0 when SELMODE is "LINE". 2 parameters: (id, line) when SELMODE is "LINE". 2 parameters: (line, col) when SELMODE is "CELL". 1 parameter: (line) and SELMODE must be "LINE". Alternatively the SELECTED attribute can be used on-demand.
? SORT | [DEV not yet implemented] procedure to implement column sorting. procedure sortcol([gdx table, ]integer col) id: (optional) identifier of the gTable element that activated the event. col: The column to be sorted. When no such handler is attached the table simply cannot be sorted by the user. Note the application is responsible for remembering which if any column is sorted, not the gTable. Whether sorting can be undone at all, toggles on and off, toggles ascending and descending, or cycles though {unsorted, ascending, descending} is an application choice, with no specific assistance from the gTable. When using tag sorts it is often helpful to "stack" them: for instance if a table has name and date columns, if the user sorts by name then date the result should be name within date, and vice versa. In other words sort using the previous tags (including whether descending) rather than just re-sorting the original table. The differences can be subtle, especially on small tables, but quite significant on very large tables. Toggling to an unsorted state would most likely effectively purge all traces of any such prior "stack". get_data(-1) should return 0 if unsorted, a +ve column for ascending and -ve for descending. When a tagsort is being used, get_data() should also map each row index to the (unmodified) table data, in other words a gTable always asks for a contiguous (ascending) range of rows, such as 15..30.
also | CLICK, IDROP, KEY, MOUSEMOVE: All common handlers are supported.
Technicalia | As this is a paranormalised function, with columns and data non-optional, and with no expectation that you should attempt to memorise this lot, except maybe "cdraa", you can assume that all of the following are potentially perfectly valid, with the fairly obvious restrictions that any parameters must be in strict cdraa-order, and no args without attributes:
                            gTable(columns,data)
                            gTable(columns,data,rows)
                            gTable(columns,data,attributes)
                            gTable(columns,data,attributes,args)
                            gTable(columns,data,rows,attributes)
                            gTable(columns,data,rows,attributes,args)  -- (the full set)
Note that in practice rows is actually defined as object, and attributes as sequence rather than string, but they are all manually and thoroughly verified to be of the documented types, after being repositioned (if necessary) by xpGUI.e/paranormalise_raa(bCheckRid:=false).