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" ;
22import { 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)
410
511function random ( max ) {
612 return Math . round ( Math . random ( ) * 1000 ) % max ;
@@ -16,7 +22,7 @@ const N = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "s
1622let nextId = 1 ;
1723
1824function buildData ( count ) {
19- const data = new Array ( count ) ;
25+ const data = Array ( count ) ;
2026 for ( let i = 0 ; i < count ; i ++ ) {
2127 data [ i ] = {
2228 id : nextId ++ ,
@@ -26,100 +32,114 @@ function buildData(count) {
2632 return data ;
2733}
2834
29- const STORE = createStore (
30- { data : createBox ( [ ] ) , selected : 0 } ,
31- function ( state , action ) {
35+ const STORE = createStore ( { data : [ ] , selected : 0 } ,
36+ ( state , action ) => {
3237 const { data, selected } = state ;
33- const itemList = data . value ;
3438 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+ }
3843 case "run" :
39- return { data : createBox ( buildData ( 1000 ) ) , selected : 0 } ;
44+ return { data : buildData ( 1000 ) , selected : 0 } ;
4045 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 + " !!!" } ;
4652 }
47- return { data, selected } ;
53+ return { data : newData , selected } ;
54+ }
4855 case "select" :
49- return { data, selected : action . id } ;
56+ return { data, selected : action . item . id } ;
5057 case "runlots" :
51- return { data : createBox ( buildData ( 10000 ) ) , selected : 0 } ;
58+ return { data : buildData ( 10000 ) , selected : 0 } ;
5259 case "clear" :
53- return { data : createBox ( [ ] ) , selected : 0 } ;
60+ return { data : [ ] , selected : 0 } ;
5461 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 } ;
6163 }
6264 return state ;
6365 } ,
64- invalidate ,
66+ withNextFrame ( requestDirtyCheck ) ,
6567) ;
6668
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 ) ;
6972
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 ( ) ) ;
8575
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 ) ;
9883
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+ ] )
104104 ) ;
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+ } ) ;
106111
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+ )
125118) ;
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