-
Notifications
You must be signed in to change notification settings - Fork 5
/
blogpost
289 lines (225 loc) · 10.8 KB
/
blogpost
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
There are many frameworks and concepts around in the Javascript world. In todays blogpost we are going to write a pure mobile Javascript application with OnsenUI and `Redux`, which is a very small but powerful library that enables to write Javascript applications in a conceptual different way.
Redux is inspired by Flux (https://facebook.github.io/flux/), which was developed by Facebook. Unlike Flux, Redux is quite flexible and does not require any specific infrastructure like GraphQL, which is required by Flux. We will use Redux in Combination with OnsenUI to build a simple integer Calculator App. One of the nice things about this app is that it will look both on android and ios due to the Autostyle.
Lets first understand what Redux is and how it works. Redux consists of three main components work. super
* Store: This stores the complete state of our application. Given the content of the store, we should be able to save and recover the current state of the application.
* Actions: Actions are how the application interacts with the store. The application sends an action with data and then the actinos and datas are handled by a so called reducer.
* Reducer: The Reducer is a function that takes the current state and an action and tranforms it to a new State.
In addition to these three components, there are three principles(https://github.com/reactjs/redux/blob/master/docs/introduction/ThreePrinciples.md) of how the application should interact with it.
1. Single Source of Truth, the store: As mentioned previously the store is the only place where data is stored. This enables us the application to do nice things like undoing an action and also helps to more easily debug.
2. State is read-only: The state of the application can only be changed via actions. This is very useful for debugging, since a state change can be easily looked at.
3. The reducer consists only of pure functions: Pure functions are functions, that do not change their parameter. Lets look at an example: Let's imagine our state is an object and we want to write a pure function called `handleLoaded` that should add the property `isLoaded : true` to the state:
``
`
function handleLoadedSimple: (state, action) {
// this is not pure, since it manipulates the original project
state.isLoaded = true;
return state;
}
function handleLoadedPure: (state, action) {
return {...state, isLoaded: true};
}
```
As one can see, one needs to be careful to write pure functions and don't manipulate the paramaters. If the function would be not pure, it would be hard to recover to previous state and would make debugging.
CALCULATOR
Let us first look at our html/css structure. The caluclator consists of 6 rows: The first one will be a simple div that contains the screen and the rest of the buttons are consistens of buttons for inputing numbers and actions. For simplicity we will ony support integer manipulation. We use the power of ons-button and ons-page:
HTML JUST BUTTONS
```
<ons-page>
<ons-row style="height: 20%">
<div id="screen" class="screen" > </div>
</ons-row>
<ons-row style="height:80%">
<ons-row style=" height: 20%">
<ons-button onclick="clean()" class="actionButton" > C </ons-button>
<ons-button onclick="changeSign()" class="actionButton"> ± </ons-button>
<ons-button onclick="mod()" class="actionButton"> % </ons-button>
<ons-button onclick="divide()" class="actionButton"> ÷ </ons-button>
</ons-row>
<ons-row style=" height: 20%">
<ons-button onclick="type(7)" > 7 </ons-button>
<ons-button onclick="type(8)"> 8 </ons-button>
<ons-button onclick="type(9)"> 9 </ons-button>
<ons-button onclick="multiply()" class="actionButton"> × </ons-button>
</ons-row>
<ons-row style=" height: 20%">
<ons-button onclick="type(4)"> 4 </ons-button>
<ons-button onclick="type(5)"> 5 </ons-button>
<ons-button onclick="type(6)"> 6 </ons-button>
<ons-button onclick="minus()" class="actionButton"> - </ons-button>
</ons-row>
<ons-row style=" height: 20%">
<ons-button onclick="type(1)"> 1 </ons-button>
<ons-button onclick="type(2)"> 2 </ons-button>
<ons-button onclick="type(3)"> 3 </ons-button>
<ons-button onclick="plus()" class="actionButton"> + </ons-button>
</ons-row>
<ons-row style=" height: 20%">
<ons-button onclick="type(0)" style="flex: 1 1 calc(75% - 4px);" > 0 </ons-button>
<ons-button onclick="equal()" class="actionButton"> = </ons-button>
</ons-row>
</ons-row>
</ons-page>
```
There is now real magic going on here. Every row is split equally accross the screen and every column has equal height using flexbox and percentages. The colors of the buttons are done by different colors:
```
.button {
display: flex;
flex: 1 1 calc(25% - 4px);
align-items: center;
justify-content: center;
font-size: 20px;
margin: 2px;
opacity: 0.7;
}
ons-row {
background: rgba(180, 206, 232, 0.3);
}
.actionButton {
opacity: 1;
}
.screen {
font-size: 40px;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
}
```
Javascript code
To write the the Javascript code we need to first think about the state of our application and the actions. For the actions we use:
* 'TYPE' : this action will mean that we type a number to our screen
* 'CHANGE_SIGN' : This means that we change the number of the screen.
* 'CLEAN' : This will clean the screen
* 'EQUAL' : Show us the result (EQUAL sign)
* 'OPERATION' : This action will be called whenever we press an action button that will do a mathematic operation with two OPERATORS.
We can now already write some part of our javascript code:
```
// all Actions
var changeSign = () => store.dispatch({type: 'CHANGE_SIGN'})
var clean = () => store.dispatch({type:'CLEAN'})
var divide = () => store.dispatch({type: 'OPERATION', func: (a, b) => Math.floor(a / b) });
var equal = () => store.dispatch({type: 'EQUAL', func: (a, b) => b});
var minus = () => store.dispatch({type: 'OPERATION', func: (a, b) => a - b});
var mod = () => store.dispatch({type: 'OPERATION', func: (a, b) => a % b });
var multiply = () => store.dispatch({type: 'OPERATION', func: (a, b) => a * b});
var plus = () => store.dispatch({type: 'OPERATION', func: (a, b) => a + b});
var type = (number) => store.dispatch({type: 'TYPE', number});
// call store with our reducer
var store = Redux.createStore(calculator)
function render() {
var el = document.getElementById('screen');
el.innerHTML = store.getState().number
}
store.subscribe(render)
ons.ready(render);
```
The Redux Framework manages the store. We will need to give it our reducer with `Redux.createStore(ourReducer)`, which we will define later. Since we want to rerender everytime the state changes, we need to listen to the store. Fortunatly Redux provides us with a function `store.subscribe`, which we are given our render function.
The hard complicated part is writing the reducer. Our reducer will have four states:
* number: the number being displayed
* operation: This will store the last function that has been like plus, minus etc.
* storedFunc: This function will be used to store results and operations, that are not direcly diplayed. For example, if we type `1 +`, the value of the storedFunc will be `(x) => 1 +x`. This makes evaluation quite easy, since we have to only call `storedFunc(num)`.
* originalFunc: This function is necessary to support repeating the last operation for the '=' sign.
* lastAction: This will include our lastType
Now that we have the state and operations, we only need to write the reducer. We will split our reducer into two, one for adding the lastAction and the other one doing the main state transformation.
```
function lastAction(state, action) {
return {...state, lastAction: action.type};
}
function calculator(state, action) {
console.log('before');
console.log(state);
var ret = lastAction(help(state, action), action);
console.log('after');
return ret;
}
```
Now that we have defined this function, we are writing our gerneral reducer that handles all the actions, but 'EQUAL' and 'OPERATION', whcih should be strayedforward:
```
function help(state, action) {
const initialState = {
number: 0,
storedFunc: (x) => x,
};
if (typeof state === 'undefined') {
return initialState;
}
switch (action.type) {
case "TYPE":
var number = state.number;
if (state.lastAction !== 'TYPE') {
number = 0;
}
return {...state, number: 10 * number + action.number};
case 'CLEAN':
return initialState;
case 'CHANGE_SIGN':
if (state.lastAction === 'OPERATION' ) {
return {...state, number: 0}
} else if (state.lastAction === 'EQUAL') {
return {...state, number: -state.number, storedFunc: (x) => x}
}
return {...state, number: -state.number}
case "OPERATION":
return handleOperation(state, action);
case "EQUAL":
return handleEqual(state, action);
default:
return state
}
}
```
the
```
function handleEqual(state, action) {
if (state.lastAction !== 'EQUAL') {
var myFun = (a) => {
return state.operation(a, state.number);
}
return {
...state,
storedFunc: myFun,
number: state.storedFunc(state.number)
};
} else {
return {
...state,
number: state.storedFunc(state.number)
};
}
}
```
and now
```
function handleOperation(state, action) {
if ( state.lastAction == 'EQUAL') {
var myFun = (a) => action.func(state.originalFunc(state.number), a);
return {
...state,
operation: action.func,
storedFunc: myFun,
number: state.number
};
} else if (state.lastAction == 'OPERATION') {
var myFun = (a) => action.func(state.number, a);
return {
...state,
operation: action.func,
storedFunc: myFun,
number: state.number
};
}
var myFun = (a) => action.func(state.storedFunc(state.number), a);
return {
...state,
operation: action.func,
originalFunc: state.storedFunc,
storedFunc: myFun,
number: state.storedFunc(state.number)
};
}
```
And now we are done and have our app. It might take a while to get used to tis code thinking, we could give you a small glance at the word of redux and functional programming.
** Resources
We only covered a small app that shows how to develop in Redux. There are tons of resources on the internet. I truly can recommend the video tutorial:
https://egghead.io/lessons/javascript-redux-the-single-immutable-state-tree
and having a look at https://github.com/reactjs/redux. If any questions arise, feel free to ask a question in our community(https://community.onsen.io/).