This repository has been archived by the owner on Nov 14, 2017. It is now read-only.
/
user.edit.js
126 lines (107 loc) · 3.91 KB
/
user.edit.js
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
let {assoc, filter, identity, merge, values} = require("ramda")
let {Observable: $} = require("rx")
let {derive, deriveN, pluck, setState, store, toOverState, toState, view} = require("rx-utils")
let {br, button, div, input, label, h1, p} = require("@cycle/dom")
let {validate} = require("../../../rx-utils")
let {flattenObject} = require("../../../helpers")
let {parseString, parseInteger} = require("../../../parsers")
let {formatString, formatInteger} = require("../../../formatters")
let {UserEdit} = require("../types")
let {makeUser} = require("../makers")
let seeds = require("../seeds/user.edit")
let menu = require("../chunks/menu")
let filterX = filter(identity)
let Points = UserEdit.meta.props.points
let Bonus = UserEdit.meta.props.bonus
// :: {Observable *} -> {Observable *}
module.exports = function (src) {
let textFrom = (s) => src.DOM.select(s).events("input")::pluck("target.value").map(parseString).share()
let numberFrom = (s) => src.DOM.select(s).events("input")::pluck("target.value").map(parseInteger).share()
let clickFrom = (s) => src.DOM.select(s).events("click").map((e) => true).share()
// DERIVED STATE
let user = deriveN(
(params, users) => users[params.id],
[src.navi::view("params"), src.state::view("users")]
)
let model = deriveN((user, form) => {
try {
return makeUser(merge(user, form))
} catch (err) {
if (err instanceof TypeError) { return null }
else { throw err }
}
}, [user, src.state2])
let errors = store(seeds, $.merge(
src.state2::view("points").skip(1)::validate(Points)::toState("points"),
src.state2::view("bonus").skip(1)::validate(Bonus)::toState("bonus")
))
let hasErrors = derive((es) => Boolean(filterX(values(flattenObject(es))).length), errors)
// INTENTS
let intents = {
changePoints: numberFrom("#points"),
changeBonus: numberFrom("#bonus"),
editUser: clickFrom("#submit").debounce(100),
}
// ACTIONS
let actions = {
editUser: model.filter(identity)
.sample(intents.editUser)
.share(),
}
// STATE 2
let state2 = store(seeds, $.merge(
// Reset form on page enter
user.sample(src.navi::view("route").delay(1)).map(UserEdit)::toState(""),
// Track fields
intents.changePoints::toState("points"),
intents.changeBonus::toState("bonus")
))
// DOM
let DOM = $
.combineLatest(src.navi, user, state2, model, errors).debounce(1)
.map(([navi, user, form, model, errors]) => {
console.log("render user.edit")
return div([
h1("Edit User"),
menu({navi}),
br(),
div(".form-element", [
label({htmlFor: "username"}, "Username:"),
br(),
input("#username", {type: "text", value: formatString(user.username), readOnly: true}),
p(errors.username),
]),
div(".form-element", [
label({htmlFor: "email"}, "Email:"),
br(),
input("#email", {type: "text", value: formatString(user.email), readOnly: true}),
p(errors.email),
]),
div(".form-element", [
label({htmlFor: "points"}, "Points:"),
br(),
input("#points", {type: "text", value: formatInteger(form.points), autocomplete: "off"}),
p(errors.points),
]),
div(".form-element", [
label({htmlFor: "bonus"}, "Bonus:"),
br(),
input("#bonus", {type: "text", value: formatInteger(form.bonus), autocomplete: "off"}),
p(errors.bonus),
]),
button("#submit.form-element", {type: "submit", disabled: !model}, "Edit"),
])
}
)
// UPDATE
let update = $.merge(
actions.editUser::toOverState("users", (u) => assoc(u.id, u))
)
// REDIRECT
let redirect = $.merge(
// Redirect to edit page after valid submit
actions.editUser.delay(1).map((user) => window.unroute(`/users/:id`, {id: user.id}))
)
// SINKS
return {DOM, update, redirect, state2}
}