/
flight.ml
268 lines (210 loc) · 5.79 KB
/
flight.ml
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
open Fmlib_browser
type date = {
year: int;
month: int;
day: int;
}
type kind =
| Oneway
| Return
type state =
| Booking of kind * date option * date option
| Booked of date * date option
type msg =
| Select_oneway
| Select_return
| Flight_date of date option
| Return_date of date option
| Book
| Reset
let init: state =
Booking (Oneway, None, None)
let update (state: state) (msg: msg): state =
match msg, state with
| Reset, _ ->
init
| Select_oneway, Booking (_, flight, _) ->
Booking (Oneway, flight, None)
| Select_return, Booking (_, flight, return) ->
Booking (Return, flight, return)
| Book, Booking (_, Some flight, return) ->
Booked (flight, return)
| Flight_date flight, Booking (kind, _, return) ->
Booking (kind, flight, return)
| Return_date return, Booking (kind, flight, _) ->
Booking (kind, flight, return)
| _ ->
assert false (* cannot happen *)
(* Helper functions for view functions
* ============================================================
*)
let compare (d1: date) (d2: date): int =
let cmp_year = compare d1.year d2.year in
if cmp_year <> 0 then
cmp_year
else
let cmp_month = compare d1.month d2.month in
if cmp_month <> 0 then
cmp_month
else
compare d1.day d2.day
let string_of_date (date: date): string =
let open Printf in
(if date.year < 1000 then
sprintf "%04d" date.year
else
string_of_int date.year
)
^ "-" ^ sprintf "%02d" date.month
^ "-" ^ sprintf "%02d" date.day
let string_of_date_opt: date option -> string =
function
| None ->
""
| Some date ->
string_of_date date
let date_of_string (str: string): date option =
match String.split_on_char '-' str with
| [""] ->
None
| [year; month; day] ->
let open Fmlib_std.Option in
let* year = int_of_string_opt year in
let* month = int_of_string_opt month in
let* day = int_of_string_opt day in
return {year; month; day}
| _ ->
assert false (* cannot happen *)
let decode_date: date option Decoder.t =
let open Decoder in
field "target" (field "value" (map date_of_string string))
let decode_selection: msg Decoder.t =
let open Decoder in
field
"target"
(field
"selectedIndex"
(map
(fun i -> if i = 0 then Select_oneway else Select_return)
int))
(* View functions
* ============================================================
*)
let view_date_opt
(date: date option)
(txt: string)
(decode: msg Decoder.t)
: msg Html.t
=
let open Html in
let open Attribute in
p [] [
label [] [
input [
attribute "type" "date"
; value (string_of_date_opt date)
; on "input" decode
] []
; text txt
]
]
let view_booking
(kind: kind)
(flight: date option)
(return: date option)
: msg Html.t
=
let open Html in
let open Attribute in
let date d lab dec =
view_date_opt d lab dec
and decode_flight =
Decoder.map (fun date -> Flight_date date) decode_date
and decode_return =
Decoder.map (fun date -> Return_date date) decode_date
in
let dates =
match kind with
| Oneway ->
[date flight "" decode_flight]
| Return ->
[date flight " flight" decode_flight
; date return " return flight" decode_return]
and book_button =
match kind, flight, return with
| Oneway, Some _, _ ->
[p [] [button [on_click Book] [text "Book"]]]
| Return, Some d1, Some d2 ->
if compare d1 d2 < 0 then
[p [] [button [on_click Book] [text "Book"]]]
else
[p
[color "red"]
[text "return flight has to be after the flight"]
]
| _ ->
[]
and one_way =
match kind with Oneway -> true | Return -> false
in
div []
(
h1 [] [text "Book your flight"]
::
select
[on "change" decode_selection]
[ node
"option"
[property "selected" Value.(bool one_way)]
[text "One way"]
; node
"option"
[property "selected" Value.(bool (not one_way))]
[text "Return"]
]
::
(dates @ book_button)
)
let view_booked
(flight: date)
(return: date option)
: msg Html.t
=
let open Html in
let open Attribute in
let date_element txt date =
p [] [
label [] [
input [ attribute "type" "date"
; attribute "readOnly" ""
; value (string_of_date date)
][]
; text txt
]
]
in
let reset =
[p [] [button [on_click Reset] [text "New booking"]]]
in
let lst =
match return with
| None ->
reset
| Some return ->
date_element " return flight" return :: reset
in
div [] (
h1 [] [text "Booking Confirmation"]
::
date_element " flight" flight
::
lst
)
let view: state -> msg Html.t =
function
| Booking (kind, flight, return) ->
view_booking kind flight return
| Booked (flight, return) ->
view_booked flight return
let _ =
sandbox init view update