1
- import { connect , render , map , element , onClick , stopDirtyChecking , setupScheduler , invalidateHandler , invalidate } from "ivi" ;
1
+ import { _ , render , Events , onClick , withNextFrame , requestDirtyCheck , elementProto , component , selector , TrackByKey , key } from "ivi" ;
2
2
import { h1 , div , span , table , tbody , tr , td , a , button } from "ivi-html" ;
3
- import { createStore , createBox } from "ivi-state" ;
3
+ import { createStore } from "ivi-state" ;
4
+
5
+ // @localvoid
6
+ // Implemented in almost exactly the same way as react-redux implementation:
7
+ // - state is completely immutable
8
+ // - each row is a stateful component
9
+ // - two selectors per each row (react-redux is using one selector)
4
10
5
11
function random ( max ) {
6
12
return Math . round ( Math . random ( ) * 1000 ) % max ;
@@ -16,7 +22,7 @@ const N = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "s
16
22
let nextId = 1 ;
17
23
18
24
function buildData ( count ) {
19
- const data = new Array ( count ) ;
25
+ const data = Array ( count ) ;
20
26
for ( let i = 0 ; i < count ; i ++ ) {
21
27
data [ i ] = {
22
28
id : nextId ++ ,
@@ -26,100 +32,114 @@ function buildData(count) {
26
32
return data ;
27
33
}
28
34
29
- const STORE = createStore (
30
- { data : createBox ( [ ] ) , selected : 0 } ,
31
- function ( state , action ) {
35
+ const STORE = createStore ( { data : [ ] , selected : 0 } ,
36
+ ( state , action ) => {
32
37
const { data, selected } = state ;
33
- const itemList = data . value ;
34
38
switch ( action . type ) {
35
- case "delete" :
36
- itemList . splice ( itemList . findIndex ( ( d ) => d . id === action . id ) , 1 ) ;
37
- return { data : createBox ( itemList ) , selected } ;
39
+ case "delete" : {
40
+ const idx = data . indexOf ( action . item ) ;
41
+ return { data : [ ...data . slice ( 0 , idx ) , ...data . slice ( idx + 1 ) ] , selected } ;
42
+ }
38
43
case "run" :
39
- return { data : createBox ( buildData ( 1000 ) ) , selected : 0 } ;
44
+ return { data : buildData ( 1000 ) , selected : 0 } ;
40
45
case "add" :
41
- return { data : createBox ( itemList . concat ( buildData ( 1000 ) ) ) , selected } ;
42
- case "update" :
43
- for ( let i = 0 ; i < itemList . length ; i += 10 ) {
44
- const r = itemList [ i ] ;
45
- itemList [ i ] = { id : r . id , label : r . label + " !!!" } ;
46
+ return { data : data . concat ( buildData ( 1000 ) ) , selected } ;
47
+ case "update" : {
48
+ const newData = data . slice ( ) ;
49
+ for ( let i = 0 ; i < newData . length ; i += 10 ) {
50
+ const r = newData [ i ] ;
51
+ newData [ i ] = { id : r . id , label : r . label + " !!!" } ;
46
52
}
47
- return { data, selected } ;
53
+ return { data : newData , selected } ;
54
+ }
48
55
case "select" :
49
- return { data, selected : action . id } ;
56
+ return { data, selected : action . item . id } ;
50
57
case "runlots" :
51
- return { data : createBox ( buildData ( 10000 ) ) , selected : 0 } ;
58
+ return { data : buildData ( 10000 ) , selected : 0 } ;
52
59
case "clear" :
53
- return { data : createBox ( [ ] ) , selected : 0 } ;
60
+ return { data : [ ] , selected : 0 } ;
54
61
case "swaprows" :
55
- if ( itemList . length > 998 ) {
56
- const a = itemList [ 1 ] ;
57
- itemList [ 1 ] = itemList [ 998 ] ;
58
- itemList [ 998 ] = a ;
59
- }
60
- return { data : createBox ( itemList ) , selected } ;
62
+ return { data : [ data [ 0 ] , data [ 998 ] , ...data . slice ( 2 , 998 ) , data [ 1 ] , data [ 999 ] ] , selected } ;
61
63
}
62
64
return state ;
63
65
} ,
64
- invalidate ,
66
+ withNextFrame ( requestDirtyCheck ) ,
65
67
) ;
66
68
67
- const GlyphIcon = element ( span ( "" , { "aria-hidden" : "true" } ) ) ;
68
- const RemoveRowButton = element ( td ( "col-md-1" ) . c ( a ( ) . c ( GlyphIcon ( "glyphicon glyphicon-remove" ) ) ) ) ;
69
+ const useItems = selector ( ( ) => STORE . state . data ) ;
70
+ const useItem = selector ( ( idx ) => STORE . state . data [ idx ] ) ;
71
+ const useSelected = selector ( ( item ) => STORE . state . selected === item . id ) ;
69
72
70
- const Row = connect (
71
- ( _ , idx ) => {
72
- const state = STORE . state ;
73
- const item = state . data . value [ idx ] ;
74
- return state . selected === item . id ? { id : item . id , label : item . label , selected : true } : item ;
75
- } ,
76
- ( item ) => (
77
- stopDirtyChecking ( tr ( item . selected === true ? "danger" : "" ) . c (
78
- td ( "col-md-1" ) . t ( item . id ) ,
79
- td ( "col-md-4" ) . c ( a ( ) . t ( item . label ) ) ,
80
- RemoveRowButton ( ) ,
81
- td ( "col-md-6" ) ,
82
- ) )
83
- ) ,
84
- ) ;
73
+ const GlyphIcon = elementProto ( span ( "glyphicon glyphicon-remove" , { "aria-hidden" : "true" } ) ) ;
74
+ const RemoveButton = a ( _ , _ , GlyphIcon ( ) ) ;
85
75
86
- const RowList = connect (
87
- ( ) => STORE . state . data ,
88
- ( { value } ) => (
89
- tbody ( ) . e ( onClick ( ( ev ) => {
90
- const target = ev . target ;
91
- STORE . dispatch ( {
92
- type : target . matches ( ".glyphicon" ) ? "delete" : "select" ,
93
- id : + target . closest ( "tr" ) . firstChild . textContent ,
94
- } ) ;
95
- } ) ) . c ( map ( value , ( { id } , i ) => Row ( i ) . k ( id ) ) )
96
- ) ,
97
- ) ;
76
+ const Row = component ( ( c ) => {
77
+ let item ;
78
+ // @localvoid : it is possible to combine multiple selectors into one, like it is traditionally done in react-redux.
79
+ // It will slightly improve performance and reduce memory consumption, but I have nothing to hide, selectors are
80
+ // super cheap in ivi.
81
+ const getItem = useItem ( c ) ;
82
+ const isSelected = useSelected ( c ) ;
98
83
99
- function Button ( text , id ) {
100
- return div ( "col-sm-6 smallpad" ) . c (
101
- button ( "btn btn-primary btn-block" , { type : "button" , id } )
102
- . e ( onClick ( ( ) => { STORE . dispatch ( { type : id } ) ; } ) )
103
- . t ( text ) ,
84
+ const selectItem = onClick ( ( ) => { STORE . dispatch ( { type : "select" , item } ) ; } ) ;
85
+ const deleteItem = onClick ( ( ) => { STORE . dispatch ( { type : "delete" , item } ) ; } ) ;
86
+
87
+ return ( idx ) => (
88
+ item = getItem ( idx ) ,
89
+
90
+ tr ( isSelected ( item ) ? "danger" : "" , _ , [
91
+ td ( "col-md-1" , _ , item . id ) ,
92
+ td ( "col-md-4" , _ ,
93
+ Events ( selectItem ,
94
+ a ( _ , _ , item . label ) ,
95
+ ) ,
96
+ ) ,
97
+ td ( "col-md-1" , _ ,
98
+ Events ( deleteItem ,
99
+ RemoveButton ,
100
+ ) ,
101
+ ) ,
102
+ td ( "col-md-6" ) ,
103
+ ] )
104
104
) ;
105
- }
105
+ } ) ;
106
+
107
+ const RowList = component ( ( c ) => {
108
+ const getItems = useItems ( c ) ;
109
+ return ( ) => tbody ( _ , _ , TrackByKey ( getItems ( ) . map ( ( { id } , i ) => key ( id , Row ( i ) ) ) ) ) ;
110
+ } ) ;
106
111
107
- setupScheduler ( invalidateHandler ) ;
108
- render (
109
- div ( "container" ) . c (
110
- stopDirtyChecking ( div ( "jumbotron" ) . c ( div ( "row" ) . c (
111
- div ( "col-md-6" ) . c ( h1 ( ) . t ( "ivi" ) ) ,
112
- div ( "col-md-6" ) . c ( div ( "row" ) . c (
113
- Button ( "Create 1,000 rows" , "run" ) ,
114
- Button ( "Create 10,000 rows" , "runlots" ) ,
115
- Button ( "Append 1,000 rows" , "add" ) ,
116
- Button ( "Update every 10th row" , "update" ) ,
117
- Button ( "Clear" , "clear" ) ,
118
- Button ( "Swap Rows" , "swaprows" ) ,
119
- ) ) ,
120
- ) ) ) ,
121
- table ( "table table-hover table-striped test-data" ) . c ( RowList ( ) ) ,
122
- GlyphIcon ( "preloadicon glyphicon glyphicon-remove" ) ,
123
- ) ,
124
- document . getElementById ( "main" ) ,
112
+ const Button = ( text , id ) => (
113
+ div ( "col-sm-6 smallpad" , _ ,
114
+ Events ( onClick ( ( ) => { STORE . dispatch ( { type : id } ) ; } ) ,
115
+ button ( "btn btn-primary btn-block" , { type : "button" , id } , text ) ,
116
+ )
117
+ )
125
118
) ;
119
+ // `withNextFrame()` runs rendering function inside of a sync frame update tick.
120
+ withNextFrame ( ( ) => {
121
+ render (
122
+ div ( "container" , _ , [
123
+ div ( "jumbotron" , _ ,
124
+ div ( "row" , _ , [
125
+ div ( "col-md-6" , _ ,
126
+ h1 ( _ , _ , "ivi" )
127
+ ) ,
128
+ div ( "col-md-6" , _ ,
129
+ div ( "row" , _ , [
130
+ Button ( "Create 1,000 rows" , "run" ) ,
131
+ Button ( "Create 10,000 rows" , "runlots" ) ,
132
+ Button ( "Append 1,000 rows" , "add" ) ,
133
+ Button ( "Update every 10th row" , "update" ) ,
134
+ Button ( "Clear" , "clear" ) ,
135
+ Button ( "Swap Rows" , "swaprows" ) ,
136
+ ] ) ,
137
+ ) ,
138
+ ] ) ,
139
+ ) ,
140
+ table ( "table table-hover table-striped test-data" , _ , RowList ( ) ) ,
141
+ GlyphIcon ( "preloadicon glyphicon glyphicon-remove" )
142
+ ] ) ,
143
+ document . getElementById ( "main" ) ,
144
+ ) ;
145
+ } ) ( ) ;
0 commit comments