Skip to content

Commit 206d744

Browse files
authored
Add optional Table prop onColumnChange (#28)
* Add optional Table prop onColumnChange * Fix new columns missing in dropdown
1 parent b4b34dc commit 206d744

File tree

4 files changed

+159
-115
lines changed

4 files changed

+159
-115
lines changed

docs/Examples/Layouts.example.purs

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ docs = (\c -> element c {}) $ withRouter $ toReactComponent identity component {
275275
, renderCell: \rowData -> R.text rowData.createdDate
276276
}
277277
]
278+
, onColumnChange: toNullable Nothing
278279
}
279280

280281
overviewTableData =

docs/Examples/Table.example.purs

+30-26
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ docs = unit # make component { initialState, render }
3535
{ sort: SortString "asc"
3636
, sortBy: Just (ColumnName "createdDate")
3737
, selected: ["10cms9", "0mf7w"]
38+
, ex2Columns:
39+
[ { required: true
40+
, name: ColumnName "product-type"
41+
, label: notNull "Product type"
42+
, filterLabel: null
43+
, sortBy: notNull $ ColumnName "title"
44+
, style: css {}
45+
, hidden: false
46+
, sticky: false
47+
, renderCell: \rowData ->
48+
Link.link Link.defaults
49+
{ href = rowData.link
50+
, text = R.text rowData.title
51+
}
52+
}
53+
, { required: true
54+
, name: ColumnName "created-date"
55+
, label: notNull "Created date"
56+
, filterLabel: null
57+
, sortBy: notNull $ ColumnName "createdDate"
58+
, style: css {}
59+
, hidden: false
60+
, sticky: false
61+
, renderCell: \rowData -> R.text rowData.createdDate
62+
}
63+
]
3864
}
3965

4066
render self =
@@ -109,6 +135,7 @@ docs = unit # make component { initialState, render }
109135
, renderCell: \rowData -> R.text rowData.createdDate
110136
}
111137
]
138+
, onColumnChange: null
112139
}
113140
]
114141
}
@@ -149,32 +176,9 @@ docs = unit # make component { initialState, render }
149176
, renderCell: R.text <<< _.title
150177
, sticky: false
151178
}
152-
, columns:
153-
[ { required: true
154-
, name: ColumnName "product-type"
155-
, label: notNull "Product type"
156-
, filterLabel: null
157-
, sortBy: notNull $ ColumnName "title"
158-
, style: css {}
159-
, hidden: false
160-
, sticky: false
161-
, renderCell: \rowData ->
162-
Link.link Link.defaults
163-
{ href = rowData.link
164-
, text = R.text rowData.title
165-
}
166-
}
167-
, { required: true
168-
, name: ColumnName "created-date"
169-
, label: notNull "Created date"
170-
, filterLabel: null
171-
, sortBy: notNull $ ColumnName "createdDate"
172-
, style: css {}
173-
, hidden: false
174-
, sticky: false
175-
, renderCell: \rowData -> R.text rowData.createdDate
176-
}
177-
]
179+
, columns: self.state.ex2Columns
180+
, onColumnChange: notNull $ mkEffectFn1 \columns ->
181+
self.setState _ { ex2Columns = columns }
178182
}
179183
]
180184
}

src/Lumi/Components/Table.js

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ exports.checkIsEventTargetInTree = function(domNode, e) {
1919
return e.target === domNode || domNode.contains(e.target);
2020
};
2121

22+
exports.isRightClick = function(e) {
23+
return e.button === 2;
24+
};
25+
2226
exports.hasWindowSelection = function() {
2327
return window.getSelection().type === "Range";
2428
};

src/Lumi/Components/Table.purs

+124-89
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Data.Array (delete, elem, find, fold, length, mapMaybe, null, snoc)
1313
import Data.Array as Array
1414
import Data.Either (either)
1515
import Data.Foldable (for_)
16-
import Data.Maybe (Maybe(..), fromMaybe, isJust, maybe)
16+
import Data.Maybe (Maybe(..), fromMaybe, isJust, isNothing, maybe)
1717
import Data.Monoid (guard)
1818
import Data.Newtype (class Newtype, un)
1919
import Data.Nullable (Nullable, toMaybe)
@@ -92,6 +92,23 @@ type TableProps row =
9292
, hidden :: Boolean
9393
, sticky :: Boolean
9494
}
95+
, onColumnChange ::
96+
Nullable
97+
( EffectFn1
98+
( Array
99+
{ required :: Boolean
100+
, name :: ColumnName
101+
, label :: Nullable String
102+
, filterLabel :: Nullable String
103+
, sortBy :: Nullable ColumnName
104+
, style :: R.CSS
105+
, renderCell :: row -> JSX
106+
, hidden :: Boolean
107+
, sticky :: Boolean
108+
}
109+
)
110+
Unit
111+
)
95112
}
96113

97114
component :: forall row. Component (TableProps row)
@@ -108,7 +125,7 @@ data Action
108125
table :: forall a. TableProps a -> JSX
109126
table = make component
110127
{ initialState
111-
, didMount
128+
, didMount: syncProps
112129
, didUpdate
113130
, render
114131
}
@@ -123,19 +140,16 @@ table = make component
123140
, menuStyle: { top: "0px", left: "0px" }
124141
}
125142

126-
didMount self = do
127-
maybeSavedColumnSort <- loadColumnState $ columnSaveKey self.props.name
128-
syncProps self maybeSavedColumnSort
129-
130143
didUpdate self _ = do
131144
case toMaybe self.props.selected of
132145
Nothing -> pure unit
133146
Just selected ->
134147
when (selected /= self.state.selected) do
135-
syncProps self Nothing
148+
syncProps self
136149

137-
syncProps self maybeSavedColumnSort = do
138-
when (null self.state.columns) do
150+
syncProps self = do
151+
when (isNothing (toMaybe self.props.onColumnChange) && null self.state.columns) do
152+
maybeSavedColumnSort <- loadColumnState $ columnSaveKey self.props.name
139153
self.setState \state -> state
140154
{ columns =
141155
case maybeSavedColumnSort of
@@ -157,21 +171,21 @@ table = make component
157171
self.setState \state -> state { showMenu = false }
158172

159173
setColumnSort self newColumnOrder = do
160-
self.setStateThen (\state ->
161-
let
162-
columnsSorted = sortColumnsBy state.columns $
163-
newColumnOrder <#> \{ name, hidden } ->
164-
{ name: ColumnName name
165-
, hidden
166-
}
167-
in
168-
state { columns = columnsSorted })
169-
do
170-
props <- readProps self
171-
state <- readState self
172-
saveColumnState
173-
(columnSaveKey props.name)
174-
(getColumnSortFields <$> state.columns)
174+
case toMaybe self.props.onColumnChange of
175+
Nothing ->
176+
self.setStateThen (\state ->
177+
let
178+
columnsSorted = state.columns `sortColumnsBy` newColumnOrder
179+
in
180+
state { columns = columnsSorted })
181+
do
182+
props <- readProps self
183+
state <- readState self
184+
saveColumnState
185+
(columnSaveKey props.name)
186+
(getColumnSortFields <$> state.columns)
187+
Just onColumnChange ->
188+
runEffectFn1 onColumnChange $ self.props.columns `sortColumnsBy` newColumnOrder
175189

176190
onSelect self { shift, key, checked } = do
177191
self.setStateThen (\state ->
@@ -225,79 +239,97 @@ table = make component
225239
runEffectFn1 props.onSelect state.selected
226240

227241
sortColumnsBy columns newColumnOrder =
228-
newColumnOrder `flip mapMaybe` \newCol -> do
229-
matchedCol <- columns `flip find` \c -> c.name == newCol.name
230-
pure $ matchedCol { hidden = newCol.hidden }
242+
let
243+
matches =
244+
newColumnOrder `flip Array.mapMaybe` \newCol -> do
245+
matchedCol <- columns # find (\c -> c.name == newCol.name)
246+
pure $ matchedCol { hidden = newCol.hidden }
247+
newColumnOrderNames = map _.name newColumnOrder
248+
nonMatches =
249+
columns
250+
# Array.filter (\c -> c.name `not elem` newColumnOrderNames)
251+
# map _ { hidden = true }
252+
in
253+
matches <> nonMatches
231254

232255
getColumnSortFields { name, hidden } = { name, hidden }
233256

234257
render self =
235-
renderLumiTable \tableRef ->
236-
[ if not self.state.showMenu
237-
then empty
238-
else renderFilterDropdown
239-
{ close: closeMenu self
240-
, reorderItems: setColumnSort self
241-
, items: self.state.columns <#> \{ name, label, filterLabel, hidden } ->
242-
{ name: un ColumnName name
243-
, label
244-
, filterLabel
245-
, hidden
246-
}
247-
, style: R.css self.state.menuStyle
258+
let
259+
columns =
260+
if isNothing $ toMaybe self.props.onColumnChange
261+
then self.state.columns
262+
else self.props.columns
263+
in
264+
renderLumiTable \tableRef ->
265+
[ if not self.state.showMenu
266+
then empty
267+
else renderFilterDropdown
268+
{ close: closeMenu self
269+
, reorderItems: setColumnSort self <<< map \{ name, hidden } ->
270+
{ name: ColumnName name
271+
, hidden
272+
}
273+
, items: columns <#> \{ name, label, filterLabel, hidden } ->
274+
{ name: un ColumnName name
275+
, label
276+
, filterLabel
277+
, hidden
278+
}
279+
, style: R.css self.state.menuStyle
280+
}
281+
, element scrollObserver
282+
{ node: tableRef
283+
, render: \{ hasScrolledY, hasScrolledX } ->
284+
R.table
285+
{ className:
286+
let
287+
isCompact = contains (Pattern "compact") (show self.props.variant)
288+
isFixed = contains (Pattern "fixed") (show self.props.variant)
289+
in
290+
joinWith " "
291+
$ [ "lumi" ]
292+
<> guard isCompact [ "compact" ]
293+
<> guard isFixed [ "fixed" ]
294+
<> guard hasScrolledX [ "has-scrolled-x" ]
295+
<> guard hasScrolledY [ "has-scrolled-y" ]
296+
<> guard self.props.selectable [ "selectable" ]
297+
, children:
298+
[ renderTableHead columns tableRef
299+
, R.tbody_
300+
let
301+
tableProps =
302+
{ columns
303+
, primaryColumn
304+
, selectable: self.props.selectable
305+
, getRowKey: self.props.getRowKey
306+
, rowEq: self.props.rowEq
307+
, onNavigate: self.props.onNavigate
308+
, onSelect: onSelect self
309+
}
310+
in
311+
self.props.rows # map \row ->
312+
keyed
313+
(tableProps.getRowKey row)
314+
(tableRow
315+
{ tableProps
316+
, row
317+
, isSelected:
318+
tableProps.selectable &&
319+
tableProps.getRowKey row `elem` selected
320+
})
321+
]
322+
}
248323
}
249-
, element scrollObserver
250-
{ node: tableRef
251-
, render: \{ hasScrolledY, hasScrolledX } ->
252-
R.table
253-
{ className:
254-
let
255-
isCompact = contains (Pattern "compact") (show self.props.variant)
256-
isFixed = contains (Pattern "fixed") (show self.props.variant)
257-
in
258-
joinWith " "
259-
$ [ "lumi" ]
260-
<> guard isCompact [ "compact" ]
261-
<> guard isFixed [ "fixed" ]
262-
<> guard hasScrolledX [ "has-scrolled-x" ]
263-
<> guard hasScrolledY [ "has-scrolled-y" ]
264-
<> guard self.props.selectable [ "selectable" ]
265-
, children:
266-
[ renderTableHead tableRef
267-
, R.tbody_
268-
let
269-
tableProps =
270-
{ columns: self.state.columns
271-
, primaryColumn
272-
, selectable: self.props.selectable
273-
, getRowKey: self.props.getRowKey
274-
, rowEq: self.props.rowEq
275-
, onNavigate: self.props.onNavigate
276-
, onSelect: onSelect self
277-
}
278-
in
279-
self.props.rows # map \row ->
280-
keyed
281-
(tableProps.getRowKey row)
282-
(tableRow
283-
{ tableProps
284-
, row
285-
, isSelected:
286-
tableProps.selectable &&
287-
tableProps.getRowKey row `elem` selected
288-
})
289-
]
290-
}
291-
}
292-
]
324+
]
293325
where
294326
selected = fromMaybe self.state.selected (toMaybe self.props.selected)
295327

296328
primaryColumn = toMaybe self.props.primaryColumn
297329

298-
renderTableHead tableRef =
330+
renderTableHead columns tableRef =
299331
element (R.unsafeCreateDOMComponent "thead")
300-
{ onContextMenu: Events.handler preventDefault \e -> do
332+
{ onContextMenu: Events.handler (preventDefault >>> stopPropagation) \e -> do
301333
{ x, y } <- runEffectFn2 getMouseEventPositionWithOffset tableRef e
302334
openMenu self
303335
{ top: show (y - 2.0) <> "px"
@@ -307,7 +339,7 @@ table = make component
307339
[ R.tr_ $
308340
[ renderHeadCheckbox ]
309341
<> (maybe [] (pure <<< renderHeadPrimaryCell) primaryColumn)
310-
<> (map renderHeadCell self.state.columns)
342+
<> (map renderHeadCell columns)
311343
]
312344
}
313345

@@ -320,7 +352,7 @@ table = make component
320352
then empty
321353
else R.th
322354
{ style: R.css { width: "2rem" }
323-
, onClick: Events.handler stopPropagation (const (pure unit))
355+
, onClick: Events.handler (stopPropagation) (const (pure unit))
324356
, children:
325357
[ input checkbox
326358
{ checked =
@@ -409,8 +441,9 @@ table = make component
409441
, handler: case maybeMenuRef of
410442
Nothing -> \e -> pure unit
411443
Just menuRef -> \e -> do
444+
412445
isEventTargetInTree <- runEffectFn2 checkIsEventTargetInTree menuRef e
413-
when (not isEventTargetInTree) close
446+
when (not isRightClick e && not isEventTargetInTree) close
414447
, options: { capture: false, once: false, passive: false }
415448
}
416449
$ filterDropdown
@@ -561,6 +594,8 @@ foreign import getMouseEventPositionWithOffset :: EffectFn2 Node SyntheticEvent
561594

562595
foreign import checkIsEventTargetInTree :: EffectFn2 Node Event Boolean
563596

597+
foreign import isRightClick :: Event -> Boolean
598+
564599
foreign import hasWindowSelection :: Effect Boolean
565600

566601
foreign import scrollObserver

0 commit comments

Comments
 (0)