|
1 | 1 | module TableView
|
2 | 2 |
|
3 |
| -using WebIO |
4 |
| -using JSExpr |
5 |
| -using JuliaDB |
6 |
| -using DataValues |
| 3 | +using Tables |
| 4 | +using WebIO, JSExpr, JSON, Dates, UUIDs |
| 5 | +using Observables: @map |
7 | 6 |
|
8 |
| -import JuliaDB: DNDSparse, DNextTable, NextTable |
| 7 | +export showtable |
9 | 8 |
|
10 |
| -function JuliaDB.subtable(t::DNextTable, r) |
11 |
| - table(collect(rows(t)[r]), pkey=t.pkey) |
12 |
| -end |
| 9 | +const ag_grid_imports = [] |
13 | 10 |
|
14 |
| -showna(xs) = xs |
15 |
| -function showna(xs::AbstractArray{T}) where {T<:DataValue} |
16 |
| - map(xs) do x |
17 |
| - isnull(x) ? "NA" : get(x) |
| 11 | +function __init__() |
| 12 | + empty!(ag_grid_imports) |
| 13 | + for f in ["ag-grid.js", "ag-grid.css", "ag-grid-light.css", "ag-grid-dark.css"] |
| 14 | + push!(ag_grid_imports, normpath(joinpath(@__DIR__, "..", "deps", "ag-grid", f))) |
18 | 15 | end
|
19 | 16 | end
|
20 | 17 |
|
21 |
| -function showna(xs::Columns) |
22 |
| - rows(map(showna, columns(xs))) |
23 |
| -end |
24 |
| - |
25 |
| -function showtable(t::Union{DNextTable, NextTable}; rows=1:100, colopts=Dict(), kwargs...) |
26 |
| - w = Scope(imports=["https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.js", |
27 |
| - "https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.css"]) |
| 18 | +function showtable(table; dark = false, height = 500) |
| 19 | + if !Tables.istable(typeof(table)) |
| 20 | + throw(ArgumentError("Argument is not a table.")) |
| 21 | + end |
28 | 22 |
|
29 |
| - trunc_rows = max(1, first(rows)):min(length(t), last(rows)) |
30 |
| - subt = JuliaDB.subtable(t, trunc_rows) |
| 23 | + tablelength = Base.IteratorSize(table) == Base.HasLength() ? length(Tables.rows(table)) : nothing |
| 24 | + |
| 25 | + rows = Tables.rows(table) |
| 26 | + schema = Tables.schema(table) |
| 27 | + if schema === nothing |
| 28 | + types = [] |
| 29 | + for (i, c) in enumerate(Tables.eachcolumn(first(rows))) |
| 30 | + push!(types, typeof(c)) |
| 31 | + end |
| 32 | + names = collect(propertynames(first(rows))) |
| 33 | + else |
| 34 | + names = schema.names |
| 35 | + types = schema.types |
| 36 | + end |
| 37 | + w = Scope(imports = ag_grid_imports) |
| 38 | + |
| 39 | + coldefs = [( |
| 40 | + headerName = n, |
| 41 | + headerTooltip = types[i], |
| 42 | + field = n, |
| 43 | + type = types[i] <: Union{Missing, T where T <: Number} ? "numericColumn" : nothing, |
| 44 | + filter = types[i] <: Union{Missing, T where T <: Dates.Date} ? "agDateColumnFilter" : |
| 45 | + types[i] <: Union{Missing, T where T <: Number} ? "agNumberColumnFilter" : nothing |
| 46 | + ) for (i, n) in enumerate(names)] |
| 47 | + |
| 48 | + id = string("grid-", string(uuid1())[1:8]) |
| 49 | + w.dom = dom"div"(className = "ag-theme-balham$(dark ? "-dark" : "")", |
| 50 | + style = Dict("width" => "100%", |
| 51 | + "height" => "$(height)px"), |
| 52 | + id = id) |
| 53 | + |
| 54 | + tablelength === nothing || tablelength > 10_000 ? _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id) : |
| 55 | + _showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id) |
31 | 56 |
|
32 |
| - headers = colnames(subt) |
33 |
| - cols = [merge(Dict(:data=>n), get(colopts, n, Dict())) for n in headers] |
| 57 | + w |
| 58 | +end |
34 | 59 |
|
| 60 | +function _showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id) |
35 | 61 | options = Dict(
|
36 |
| - :data => showna(collect(JuliaDB.rows(subt))), |
37 |
| - :colHeaders => headers, |
38 |
| - :modifyColWidth => @js(w -> w > 300 ? 300 : w), |
39 |
| - :modifyRowHeight => @js(h -> h > 60 ? 50 : h), |
40 |
| - :manualColumnResize => true, |
41 |
| - :manualRowResize => true, |
42 |
| - :columns => cols, |
43 |
| - :width => 800, |
44 |
| - :height => 400, |
| 62 | + :rowData => JSONText(table2json(rows, names, types)), |
| 63 | + :columnDefs => coldefs, |
| 64 | + :enableSorting => true, |
| 65 | + :enableFilter => true, |
| 66 | + :enableColResize => true, |
| 67 | + :multiSortKey => "ctrl", |
45 | 68 | )
|
46 |
| - if (length(t.pkey) > 0 && t.pkey == [1:length(t.pkey);]) |
47 |
| - options[:fixedColumnsLeft] = length(t.pkey) |
48 |
| - end |
49 | 69 |
|
50 |
| - merge!(options, Dict(kwargs)) |
51 |
| - |
52 |
| - handler = @js function (Handsontable) |
53 |
| - @var sizefix = document.createElement("style"); |
54 |
| - sizefix.textContent = """ |
55 |
| - .htCore td { |
56 |
| - white-space:nowrap |
57 |
| - } |
58 |
| - """ |
59 |
| - this.dom.appendChild(sizefix) |
60 |
| - this.hot = @new Handsontable(this.dom, $options); |
| 70 | + handler = @js function (agGrid) |
| 71 | + @var gridOptions = $options |
| 72 | + @var el = document.getElementById($id) |
| 73 | + this.table = @new agGrid.Grid(el, gridOptions) |
| 74 | + gridOptions.columnApi.autoSizeColumns($names) |
61 | 75 | end
|
62 | 76 | onimport(w, handler)
|
63 |
| - w.dom = dom"div"() |
64 |
| - w |
65 | 77 | end
|
66 | 78 |
|
67 |
| -function showtable(t::Union{DNDSparse, NDSparse}; rows=1:100, colopts=Dict(), kwargs...) |
68 |
| - w = Scope(imports=["https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.js", |
69 |
| - "https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.css"]) |
70 |
| - data = Observable{Any}(w, "data", []) |
71 |
| - |
72 |
| - trunc_rows = max(1, first(rows)):min(length(t), last(rows)) |
73 | 79 |
|
74 |
| - ks = keys(t)[trunc_rows] |
75 |
| - vs = values(t)[trunc_rows] |
76 |
| - |
77 |
| - if !isa(keys(t), Columns) |
78 |
| - ks = collect(ks) |
79 |
| - vs = collect(vs) |
| 80 | +function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id) |
| 81 | + rowparams = Observable(w, "rowparams", Dict("startRow" => 1, |
| 82 | + "endRow" => 100, |
| 83 | + "successCallback" => @js v -> nothing)) |
| 84 | + requestedrows = Observable(w, "requestedrows", JSONText("{}")) |
| 85 | + on(rowparams) do x |
| 86 | + requestedrows[] = JSONText(table2json(rows, names, types, requested = [x["startRow"], x["endRow"]])) |
80 | 87 | end
|
81 | 88 |
|
82 |
| - subt = NDSparse(showna(ks), showna(vs)) |
83 |
| - |
84 |
| - headers = colnames(subt) |
85 |
| - cols = [merge(Dict(:data=>n), get(colopts, n, Dict())) for n in headers] |
| 89 | + onjs(requestedrows, @js function (val) |
| 90 | + ($rowparams[]).successCallback(val, $(tablelength)) |
| 91 | + end) |
86 | 92 |
|
87 | 93 | options = Dict(
|
88 |
| - :data => JuliaDB.rows(subt), |
89 |
| - :colHeaders => headers, |
90 |
| - :fixedColumnsLeft => ndims(t), |
91 |
| - :modifyColWidth => @js(w -> w > 300 ? 300 : w), |
92 |
| - :modifyRowHeight => @js(h -> h > 60 ? 50 : h), |
93 |
| - :manualColumnResize => true, |
94 |
| - :manualRowResize => true, |
95 |
| - :columns => cols, |
96 |
| - :width => 800, |
97 |
| - :height => 400, |
| 94 | + :columnDefs => coldefs, |
| 95 | + :enableSorting => true, |
| 96 | + :enableFilter => true, |
| 97 | + :maxConcurrentDatasourceRequests => 1, |
| 98 | + :cacheBlockSize => 1000, |
| 99 | + :maxBlocksInCache => 100, |
| 100 | + :enableColResize => true, |
| 101 | + :multiSortKey => "ctrl", |
| 102 | + :rowModelType => "infinite", |
| 103 | + :datasource => Dict( |
| 104 | + "getRows" => |
| 105 | + @js function (rowParams) |
| 106 | + $rowparams[] = rowParams |
| 107 | + end |
| 108 | + , |
| 109 | + "rowCount" => tablelength |
| 110 | + ) |
98 | 111 | )
|
99 | 112 |
|
100 |
| - merge!(options, Dict(kwargs)) |
101 |
| - |
102 |
| - handler = @js function (Handsontable) |
103 |
| - @var sizefix = document.createElement("style"); |
104 |
| - sizefix.textContent = """ |
105 |
| - .htCore td { |
106 |
| - white-space:nowrap |
107 |
| - } |
108 |
| - """ |
109 |
| - this.dom.appendChild(sizefix) |
110 |
| - this.hot = @new Handsontable(this.dom, $options); |
| 113 | + handler = @js function (agGrid) |
| 114 | + @var gridOptions = $options |
| 115 | + @var el = document.getElementById($id) |
| 116 | + this.table = @new agGrid.Grid(el, gridOptions) |
| 117 | + gridOptions.columnApi.autoSizeColumns($names) |
111 | 118 | end
|
112 | 119 | onimport(w, handler)
|
113 |
| - w.dom = dom"div"() |
114 |
| - w |
115 | 120 | end
|
116 | 121 |
|
117 |
| -showtable(t; kwargs...) = showtable(table(t); kwargs...) |
| 122 | +# directly write JSON instead of allocating temporary dicts etc |
| 123 | +function table2json(rows, names, types; requested = nothing) |
| 124 | + io = IOBuffer() |
| 125 | + print(io, '[') |
| 126 | + for (i, row) in enumerate(rows) |
| 127 | + if requested == nothing || first(requested) <= i <= last(requested) |
| 128 | + print(io, '{') |
| 129 | + i = 1 |
| 130 | + for col in Tables.eachcolumn(row) |
| 131 | + JSON.print(io, names[i]) |
| 132 | + i += 1 |
| 133 | + print(io, ':') |
| 134 | + if col isa Number |
| 135 | + JSON.print(io, col) |
| 136 | + else |
| 137 | + JSON.print(io, sprint(print, col)) |
| 138 | + end |
| 139 | + print(io, ',') |
| 140 | + end |
| 141 | + skip(io, -1) |
| 142 | + print(io, '}') |
| 143 | + print(io, ',') |
| 144 | + end |
| 145 | + end |
| 146 | + skip(io, -1) |
| 147 | + print(io, ']') |
118 | 148 |
|
119 |
| -end # module |
| 149 | + String(take!(io)) |
| 150 | +end |
| 151 | +end |
0 commit comments