-
Notifications
You must be signed in to change notification settings - Fork 1
/
EditUser.html
175 lines (150 loc) · 5.68 KB
/
EditUser.html
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
<div class="editUser">
{{#if isLoading}}
<span class="loader"></span>
{{else}}
<!-- TODO new vs edit -->
<form on:submit="saveUser(event)" class="{{isDirty ? 'isDirty' : ''}}">
<fieldset>
<legend>Edit User #{{item.id}}</legend>
<input type="hidden" name="id" value="{{item.id}}">
<!-- TODO Refactor to a re-used component. I guess a svelte form fields component library builds up here. -->
<div class="input-single {{fieldClass('firstName', isDirty, validate)}}">
<label for="firstName">First name</label>
<input name="firstName" type="text" placeholder="e.g. Bob" required autofocus ref:firstName bind:value="item.firstName">
{{#if isDirty && !validate.fields.firstName.valid}}
<div class="spacer-top-small alert alert-danger">{{validate.fields.firstName.message}}</div>
{{/if}}
</div>
<div class="input-single {{fieldClass('lastName', isDirty, validate)}}">
<label for="lastName">Last name</label>
<input name="lastName" type="text" placeholder="e.g. Connor" required bind:value="item.lastName">
</div>
<div class="input-single {{fieldClass('email', isDirty, validate)}}">
<label for="email">Email</label>
<input name="email" type="email" placeholder="[email protected]" required bind:value="item.email">
</div>
<div class="input-single {{fieldClass('iq', isDirty, validate)}}">
<label for="iq">IQ (value: {{item.iq}})</label>
<input name="iq" type="range" min="75" max="160" required bind:value="item.iq">
</div>
<div class="input-single">
<button type="submit" class="button" disabled="{{!isDirty || !validate.valid}}">Save</button>
<a href="#" on:click="cancelEdit(event)">Cancel</a>
</div>
</fieldset>
</form>
{{/if}}
</div>
<script>
import isEqual from 'lodash.isequal'
// TODO Move somewhere generic? Use lodash.clone?
const clone = (input) => (JSON.parse(JSON.stringify(input)))
export default {
data () {
return {
isLoading: false, // TODO What about passing this in>
item: {},
originalItem: {},
items: []
}
},
oncreate () {
// Some trickery so that if the items haven't been loaded yet, trigger that request and, once done, update the component's state.
if (this.get('items').length === 0) {
var self = this
this.set({ isLoading: true })
let observer = null;
observer = this.observe('items', () => {
self.set({ isLoading: false })
observer.cancel()
}, { init: false })
// FIXME Hack because main.js use of app.on() for requestData is bound after app is finished being created!
setTimeout(() => {
self.fire('requestData')
}, 250)
}
// Lazy loaded items code:
// Wait for parent component to compute the value of item (see itemToEdit in parent), then store the original version.
if (this.get('items').length === 0) {
let lazyItemObserver = null
lazyItemObserver = this.observe('item', (current) => {
this.set({ originalItem: clone(current) })
lazyItemObserver.cancel()
}, { init: false })
} else {
// Subsequent loads of the screen will have item passed in immediately, because items will be loaded.
this.set({ originalItem: clone(this.get('item')) })
}
},
helpers: {
fieldClass: (fieldName, isDirty, validate) => {
if (!isDirty) { return '' }
return validate.fields[fieldName].valid ? 'input-valid' : 'input-invalid'
}
},
computed: {
isDirty: (item, originalItem) => {
return !isEqual(item, originalItem)
},
// TODO Homebrew, find some generic validation JS library
// Should identify changed fields, validate them according to rules
validate: (item, originalItem) => {
let result = {
valid: true,
fields: {
firstName: {
valid: true
},
lastName: {
valid: true
},
email: {
valid: true
},
iq: {
valid: true
}
}
}
// A demo showing why we need a validation library.
if (item.firstName !== originalItem.firstName) {
if (item.firstName.split(' ').length > 1) {
result.valid = false
result.fields.firstName.valid = false
result.fields.firstName.message = 'No multiple first-names thanks.'
}
}
// console.log('validate result:', result)
return result
}
},
methods: {
/**
* TODO Best practice here? Two-way bindings for convenience?
*/
saveUser: function (event) {
event.preventDefault()
// TODO validate input (or the item), return a payload the UI can use. Standardise.
let validate = this.get('validate')
if (!validate.valid) {
return
}
let editItem = this.get('item')
// Check any fields were actually changed first.
if (isEqual(this.get('originalItem'), editItem)) {
return
}
// Changed
this.fire('saveUser', editItem)
},
/**
* Interesting.. don't put routing knowledge into this component, just fire the semantic event and let the "app" take care of it.
* Keeps routing within the app component, rather than scattered throughout the subcomponents?
*/
cancelEdit: function (event) {
event.preventDefault()
this.fire('cancelEdit', event)
}
}
}
</script>