@@ -3,178 +3,137 @@ import T from 'prop-types';
3
3
4
4
import { Datagrid } from 'react-admin' ;
5
5
6
- /* utils */
7
6
import isEmpty from 'lodash/isEmpty' ;
7
+ import filter from 'lodash/filter' ;
8
+ import get from 'lodash/get' ;
8
9
9
- /* icons */
10
- import Icon from '@material-ui/icons/ViewColumn' ;
11
- import IconClose from '@material-ui/icons/Close' ;
12
-
13
- /* material-ui */
10
+ import ColumnIcon from '@material-ui/icons/ViewColumn' ;
14
11
import Button from '@material-ui/core/Button' ;
15
- import DialogTitle from '@material-ui/core/DialogTitle' ;
16
- import DialogContent from '@material-ui/core/DialogContent' ;
17
- import DialogActions from '@material-ui/core/DialogActions' ;
18
- import Dialog from '@material-ui/core/Dialog' ;
19
- import FormControlLabel from '@material-ui/core/FormControlLabel' ;
20
- import FormGroup from '@material-ui/core/FormGroup' ;
21
- import Checkbox from '@material-ui/core/Checkbox' ;
22
12
23
- const LS = 'raColumnsConfig' ;
13
+ import SelectionDialog from './SelectionDialog' ;
14
+ import LocalStorage from './LocalStorage' ;
15
+
16
+ const arrayToSelection = values =>
17
+ values . reduce ( ( selection , columnName ) => {
18
+ selection [ columnName ] = true ;
19
+ return selection ;
20
+ } , { } ) ;
24
21
25
22
// CustomizableDatagrid allows to show/hide columns dynamically
26
23
// the preferences are stored in local storage
27
24
class CustomizableDatagrid extends Component {
28
25
constructor ( props ) {
29
26
super ( props ) ;
30
27
this . state = {
31
- open : false ,
32
- selection : { } ,
28
+ modalOpened : false ,
29
+ selection : this . getInitialSelection ( ) ,
33
30
} ;
31
+ }
32
+
33
+ getColumnNames ( ) {
34
+ const { children } = this . props ;
35
+ return filter ( React . Children . map ( children , field => get ( field , [ 'props' , 'source' ] ) ) ) ;
36
+ }
34
37
35
- const { defaultColumns, resource, children } = props ;
38
+ getColumnLabels ( ) {
39
+ const { children } = this . props ;
40
+ return filter (
41
+ React . Children . map (
42
+ children ,
43
+ field =>
44
+ field && {
45
+ source : get ( field , [ 'props' , 'source' ] ) ,
46
+ label : get ( field , [ 'props' , 'label' ] ) ,
47
+ } ,
48
+ ) ,
49
+ item => item && item . source ,
50
+ ) ;
51
+ }
36
52
37
- let localStorageValue = null ;
38
- let localStorageValueForResource = null ;
53
+ getInitialSelection ( ) {
54
+ const { defaultColumns , resource , children , storage } = this . props ;
39
55
40
- // we try to retrieve the local storage value for this resource
41
- try {
42
- localStorageValue = JSON . parse (
43
- isEmpty ( window . localStorage . getItem ( LS ) ) ? '{}' : window . localStorage . getItem ( LS ) ,
44
- ) ;
45
- localStorageValueForResource = localStorageValue [ resource ] || { } ;
46
- } catch ( e ) { } // ignore - window.localStorage is unreliable
56
+ const previousSelection = storage . get ( resource ) ;
47
57
48
- // if this is the first time the user come to the application / view
49
- if ( ! isEmpty ( defaultColumns ) && isEmpty ( localStorageValueForResource ) ) {
50
- defaultColumns . forEach ( defaultValue => {
51
- this . state . selection [ defaultValue ] = true ;
52
- } ) ;
53
- }
54
- // we try to apply the local storage state to our internal state
55
- else if ( ! isEmpty ( localStorageValueForResource ) ) {
56
- Object . keys ( localStorageValueForResource ) . forEach ( key => {
57
- this . state . selection [ key ] = localStorageValueForResource [ key ] ;
58
- } ) ;
58
+ // if we have a previously stored value, let's return it
59
+ if ( ! isEmpty ( previousSelection ) ) {
60
+ return previousSelection ;
59
61
}
60
- // otherwise we fallback on the default behaviour
61
- // -> display all columns
62
- else {
63
- React . Children . forEach ( children , field => {
64
- this . state . selection [ field . props . source ] = true ;
65
- } ) ;
62
+
63
+ // if defaultColumns are set let's return them
64
+ if ( ! isEmpty ( defaultColumns ) ) {
65
+ return arrayToSelection ( defaultColumns ) ;
66
66
}
67
67
68
- this . updateLocalStorage ( ) ;
68
+ // otherwise we fallback on the default behaviour : display all columns
69
+ return arrayToSelection ( this . getColumnNames ( ) ) ;
69
70
}
70
71
71
- // updates the local storage with the internal state value
72
- updateLocalStorage = ( ) => {
73
- const { resource } = this . props ;
74
- const { selection } = this . state ;
72
+ // updates the storage with the internal state value
73
+ updateStorage = ( ) => {
74
+ this . props . storage . set ( this . props . resource , this . state . selection ) ;
75
+ } ;
75
76
76
- // maybe there isnt an old value
77
- let oldValue = { } ;
78
- try {
79
- oldValue = JSON . parse ( window . localStorage . getItem ( LS ) ) ;
80
- } catch ( e ) { }
77
+ toggleColumn = columnName => {
78
+ const previousSelection = this . state . selection ;
79
+ const selection = {
80
+ ...previousSelection ,
81
+ [ columnName ] : ! previousSelection [ columnName ] ,
82
+ } ;
83
+ this . setState ( { selection } , this . updateStorage ) ;
84
+ } ;
81
85
82
- const value = JSON . stringify ( {
83
- ...oldValue ,
84
- [ resource ] : selection ,
85
- } ) ;
86
+ handleOpen = ( ) => this . setState ( { modalOpened : true } ) ;
87
+ handleClose = ( ) => this . setState ( { modalOpened : false } ) ;
86
88
87
- try {
88
- window . localStorage . setItem ( LS , value ) ;
89
- } catch ( e ) { }
90
- } ;
89
+ renderChild = child => {
90
+ const source = get ( child , [ 'props' , 'source' ] ) ;
91
+ const { selection } = this . state ;
91
92
92
- toggleColumn = event => {
93
- this . setState (
94
- {
95
- selection : {
96
- ...this . state . selection ,
97
- [ event . target . value ] : ! this . state . selection [ event . target . value ] ,
98
- } ,
99
- } ,
100
- this . updateLocalStorage ,
101
- ) ;
102
- } ;
93
+ // Show children without source, or children explicitly visible
94
+ if ( ! source || selection [ source ] ) {
95
+ return React . cloneElement ( child , { } ) ;
96
+ }
103
97
104
- handleOpen = ( ) => this . setState ( { open : true } ) ;
105
- handleClose = ( ) => this . setState ( { open : false } ) ;
98
+ return null ;
99
+ } ;
106
100
107
101
render ( ) {
108
102
const { children, defaultColumns, ...rest } = this . props ;
109
-
110
- const columns = React . Children . map ( children , field => field . props . source ) ;
103
+ const { selection, modalOpened } = this . state ;
111
104
112
105
return (
113
106
< div >
114
107
< div style = { { float : 'right' , marginRight : '1rem' } } >
115
- < Button
116
- variant = "outlined"
117
- mini
118
- color = "secondary"
119
- aria-label = "add"
120
- onClick = { this . handleOpen }
121
- >
122
- < Icon />
108
+ < Button variant = "outlined" mini aria-label = "add" onClick = { this . handleOpen } >
109
+ < ColumnIcon />
123
110
</ Button >
124
- { this . state . open && (
125
- < Dialog
126
- maxWidth = "xs"
127
- aria-labelledby = "confirmation-dialog-title"
128
- open = { this . state . open }
129
- onEscapeKeyDown = { this . handleClose }
130
- onBackdropClick = { this . handleClose }
131
- >
132
- < DialogTitle id = "confirmation-dialog-title" > Configuration</ DialogTitle >
133
- < DialogContent >
134
- < FormGroup >
135
- { columns . map ( column => (
136
- < FormControlLabel
137
- key = { column }
138
- control = {
139
- < Checkbox
140
- checked = { ! ! this . state . selection [ column ] }
141
- onChange = { this . toggleColumn }
142
- value = { column }
143
- />
144
- }
145
- label = { column }
146
- />
147
- ) ) }
148
- </ FormGroup >
149
- </ DialogContent >
150
- < DialogActions >
151
- < Button onClick = { this . handleClose } color = "primary" >
152
- < IconClose />
153
- </ Button >
154
- </ DialogActions >
155
- </ Dialog >
156
- ) }
157
111
</ div >
158
- < Datagrid { ... rest } >
159
- { React . Children . map (
160
- children ,
161
- child =>
162
- child && ! ! this . state . selection [ child . props . source ]
163
- ? React . cloneElement ( child , { } )
164
- : null ,
165
- ) }
166
- </ Datagrid >
112
+ { modalOpened && (
113
+ < SelectionDialog
114
+ selection = { selection }
115
+ columns = { this . getColumnLabels ( ) }
116
+ onColumnClicked = { this . toggleColumn }
117
+ onClose = { this . handleClose }
118
+ />
119
+ ) }
120
+ < Datagrid { ... rest } > { React . Children . map ( children , this . renderChild ) } < /Datagrid >
167
121
</ div >
168
122
) ;
169
123
}
170
124
}
171
125
172
126
CustomizableDatagrid . propTypes = {
173
127
defaultColumns : T . arrayOf ( T . string ) ,
128
+ storage : T . shape ( {
129
+ get : T . func . isRequired ,
130
+ set : T . func . isRequired ,
131
+ } ) ,
174
132
} ;
175
133
176
134
CustomizableDatagrid . defaultProps = {
177
135
defaultColumns : [ ] ,
136
+ storage : LocalStorage ,
178
137
} ;
179
138
180
139
export default CustomizableDatagrid ;
0 commit comments