alchemy-form 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -126,6 +126,15 @@ Table.setAttribute('page-size', {number: true});
126
126
  */
127
127
  Table.setAttribute('show-filters', {boolean: true});
128
128
 
129
+ /**
130
+ * Keep track of the loadRemote calls
131
+ *
132
+ * @author Jelle De Loecker <jelle@elevenways.be>
133
+ * @since 0.1.3
134
+ * @version 0.1.3
135
+ */
136
+ Table.setProperty('load_remote_counter', 0);
137
+
129
138
  /**
130
139
  * Look for changes to the show-filters attribute
131
140
  *
@@ -834,7 +843,7 @@ Table.setMethod(function selectRow(row) {
834
843
  *
835
844
  * @author Jelle De Loecker <jelle@elevenways.be>
836
845
  * @since 0.1.0
837
- * @version 0.1.0
846
+ * @version 0.1.3
838
847
  */
839
848
  Table.setMethod(function loadRemoteData() {
840
849
 
@@ -859,14 +868,24 @@ Table.setMethod(function loadRemoteData() {
859
868
  body : body
860
869
  };
861
870
 
871
+ let load_remote_id = ++this.load_remote_counter;
872
+
862
873
  this.delayAssemble(async function() {
863
874
 
875
+ if (load_remote_id != that.load_remote_counter) {
876
+ return;
877
+ }
878
+
864
879
  let result = await that.getResource(options);
865
880
 
866
881
  if (!result) {
867
882
  return;
868
883
  }
869
884
 
885
+ if (load_remote_id != that.load_remote_counter) {
886
+ return;
887
+ }
888
+
870
889
  let records = result.records;
871
890
 
872
891
  that.records = records;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The alchemy-number-input custom element
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.1.3
6
+ * @version 0.1.3
7
+ */
8
+ var NumberInput = Function.inherits('Alchemy.Element.Form.FeedbackInput', 'NumberInput');
9
+
10
+ /**
11
+ * The hawkejs template to use
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 0.1.3
15
+ * @version 0.1.3
16
+ */
17
+ NumberInput.setTemplateFile('form/elements/number_input');
18
+
19
+ /**
20
+ * The value property
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.1.3
24
+ * @version 0.1.3
25
+ */
26
+ NumberInput.setProperty(function value() {
27
+
28
+ var input = this.input_el,
29
+ value = input.value;
30
+
31
+ if (value === '') {
32
+ return null;
33
+ }
34
+
35
+ return Number(value);
36
+
37
+ }, function setValue(value) {
38
+
39
+ var input = this.input_el;
40
+
41
+ if (value == null || value === '') {
42
+ input.value = '';
43
+ } else {
44
+ input.value = value;
45
+ }
46
+
47
+ });
48
+
49
+ /**
50
+ * The disabled property
51
+ *
52
+ * @author Jelle De Loecker <jelle@elevenways.be>
53
+ * @since 0.1.3
54
+ * @version 0.1.3
55
+ */
56
+ NumberInput.setProperty(function disabled() {
57
+ return this.input_el.disabled;
58
+ }, function setDisabled(value) {
59
+ return this.input_el.disabled = value;
60
+ });
61
+
62
+ /**
63
+ * Added to the DOM for the first time
64
+ *
65
+ * @author Jelle De Loecker <jelle@elevenways.be>
66
+ * @since 0.1.3
67
+ * @version 0.1.3
68
+ */
69
+ NumberInput.setMethod(function introduced() {
70
+
71
+ var that = this,
72
+ minus = this.querySelector('.minus'),
73
+ plus = this.querySelector('.plus'),
74
+ input = this.querySelector('.input');
75
+
76
+ var accommodations_summary_element = this.accommodations_summary;
77
+
78
+ minus.addEventListener('click', function onMinus(e) {
79
+ e.preventDefault();
80
+ adjustValue(-1, that);
81
+ });
82
+
83
+ plus.addEventListener('click', function onPlus(e) {
84
+ e.preventDefault();
85
+ adjustValue(+1, that);
86
+ });
87
+
88
+ function adjustValue(change, obj) {
89
+
90
+ var max = Number(input.getAttribute('max')),
91
+ min = Number(input.getAttribute('min')),
92
+ val = Number(input.value) || 0;
93
+
94
+ val += change;
95
+
96
+ if (val > max) {
97
+ val = max;
98
+ }
99
+
100
+ if (val < min) {
101
+ val = min;
102
+ }
103
+
104
+ input.value = val;
105
+ }
106
+ });
@@ -3,11 +3,9 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.1.0
6
- * @version 0.1.0
6
+ * @version 0.1.3
7
7
  */
8
- var StringInput = Function.inherits('Alchemy.Element.Form.FeedbackInput', function StringInput() {
9
- StringInput.super.call(this);
10
- });
8
+ const StringInput = Function.inherits('Alchemy.Element.Form.FeedbackInput', 'StringInput');
11
9
 
12
10
  /**
13
11
  * The template to use for the content of this element
@@ -16,4 +14,334 @@ var StringInput = Function.inherits('Alchemy.Element.Form.FeedbackInput', functi
16
14
  * @since 0.1.0
17
15
  * @version 0.1.0
18
16
  */
19
- StringInput.setTemplateFile('form/elements/string_input');
17
+ StringInput.setTemplateFile('form/elements/string_input');
18
+
19
+ /**
20
+ * The required attribute
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.1.3
24
+ * @version 0.1.3
25
+ */
26
+ StringInput.setAttribute('required', {boolean: true});
27
+
28
+ /**
29
+ * The required attribute
30
+ *
31
+ * @author Jelle De Loecker <jelle@elevenways.be>
32
+ * @since 0.1.3
33
+ * @version 0.1.3
34
+ */
35
+ StringInput.setAssignedProperty('validate');
36
+
37
+ /**
38
+ * Revalidate the value
39
+ *
40
+ * @author Jelle De Loecker <jelle@elevenways.be>
41
+ * @since 0.1.3
42
+ * @version 0.1.3
43
+ *
44
+ * @return {Object[]}
45
+ */
46
+ StringInput.setMethod(function getValidationConfig() {
47
+
48
+ let result = [],
49
+ config = this.validate,
50
+ types;
51
+
52
+ if (config && typeof config == 'string') {
53
+ types = config;
54
+ config = null;
55
+ } else if (config && typeof config == 'object') {
56
+ config = Array.cast(config);
57
+ } else {
58
+ types = this.getAttribute('validate');
59
+ }
60
+
61
+ if (types) {
62
+ types = types.split(',');
63
+ } else {
64
+ types = [];
65
+ }
66
+
67
+ // Required always comes first if defined as an attribute
68
+ if (this.required) {
69
+ types.unshift('required');
70
+ }
71
+
72
+ for (let type of types) {
73
+ if (type == 'email') {
74
+ result.push({
75
+ type : 'regex',
76
+ regex : /[a-zA-Z0-9_\.-]+@[a-zA-Z0-9_\.-]+\.[a-zA-Z]{2,}/,
77
+ message : 'microcopy:invalid-email',
78
+ when : 'onblur',
79
+ });
80
+ } else if (type == 'regex') {
81
+ result.push({
82
+ type : 'regex',
83
+ regex : RegExp.interpret(this.getAttribute('validate-regex')),
84
+ message : 'microcopy:invalid-pattern-match',
85
+ when : 'onblur',
86
+ });
87
+ } else if (type == 'remote') {
88
+ let route = this.getAttribute('validate-route'),
89
+ url = this.getAttribute('validate-url');
90
+
91
+ result.push({
92
+ type : 'remote',
93
+ route : route,
94
+ url : url,
95
+ message : 'microcopy:invalid-remote',
96
+ when : 'onblur',
97
+ });
98
+ } else if (type == 'required') {
99
+ result.push({
100
+ type : 'required',
101
+ message : 'microcopy:cannot-be-empty'
102
+ })
103
+ }
104
+ }
105
+
106
+ let min_length = Number(this.getAttribute('min-length')) || 0;
107
+
108
+ if (min_length > 0) {
109
+ // Checking the minimum length only has to happen on blur
110
+ result.push({
111
+ type : 'min_length',
112
+ length : min_length,
113
+ message : 'microcopy:is-too-short',
114
+ when : 'onblur',
115
+ });
116
+ }
117
+
118
+ let max_length = Number(this.getAttribute('max-length')) || 0;
119
+
120
+ if (max_length > 0) {
121
+ // Checking maximum length has to happen immediately
122
+ result.push({
123
+ type : 'max_length',
124
+ length : max_length,
125
+ message : 'microcopy:is-too-long',
126
+ });
127
+ }
128
+
129
+ if (config) {
130
+ for (let entry of config) {
131
+
132
+ if (!entry.when) {
133
+ entry.when = 'onblur';
134
+ }
135
+
136
+ result.push(entry);
137
+ }
138
+ }
139
+
140
+ return result;
141
+ });
142
+
143
+ /**
144
+ * Revalidate the value
145
+ *
146
+ * @author Jelle De Loecker <jelle@elevenways.be>
147
+ * @since 0.1.3
148
+ * @version 0.1.3
149
+ *
150
+ * @param {Object} trigger The revalidation trigger
151
+ */
152
+ StringInput.setMethod(async function revalidate(trigger, force) {
153
+
154
+ // Get the current value
155
+ let value = this.value;
156
+
157
+ if (value) {
158
+ value = value.trim();
159
+ }
160
+
161
+ // Don't revalidate the same value twice
162
+ if (this.last_validated_value == value) {
163
+ return;
164
+ }
165
+
166
+ if (this.success_el) {
167
+ this.success_el.innerHTML = '';
168
+ }
169
+
170
+ // Get the validation counter (important for async validations)
171
+ let validation_counter = ++this.validation_counter;
172
+
173
+ let config = this.getValidationConfig(),
174
+ error_params,
175
+ check_count = 0,
176
+ skip_count = 0,
177
+ required;
178
+
179
+ if (config && config.length) {
180
+
181
+ required = config.findByPath('type', 'required');
182
+
183
+ for (let entry of config) {
184
+
185
+ if (!value.length && !required) {
186
+ skip_count++;
187
+ continue;
188
+ }
189
+
190
+ if (entry.when) {
191
+
192
+ // Always check when it's being submitted
193
+ if (trigger != 'submit') {
194
+ if (entry.when == 'onblur' && (!trigger || trigger.type != 'blur')) {
195
+ skip_count++;
196
+ continue;
197
+ }
198
+ }
199
+ }
200
+
201
+ check_count++;
202
+
203
+ if (entry.type == 'regex') {
204
+ if (!entry.regex.test(value)) {
205
+ error_params = [entry.message, null, true];
206
+ break;
207
+ }
208
+ } else if (entry.type == 'min_length') {
209
+ if (value.length < entry.length) {
210
+ error_params = [entry.message, {
211
+ value_length: value.length,
212
+ min_length : entry.length,
213
+ }, true];
214
+ break;
215
+ }
216
+ } else if (entry.type == 'max_length') {
217
+ if (value.length > entry.length) {
218
+ error_params = [entry.message, {
219
+ value_length : value.length,
220
+ max_length : entry.length,
221
+ }, true];
222
+ break;
223
+ }
224
+ } else if (entry.type == 'remote') {
225
+
226
+ let url;
227
+
228
+ if (entry.url) {
229
+ url = Classes.RURL.parse(entry.url);
230
+ } else if (entry.route) {
231
+ url = hawkejs.scene.helpers.Router.routeUrl(entry.route);
232
+ }
233
+
234
+ if(!url) {
235
+ continue;
236
+ }
237
+
238
+ url.param('value', value);
239
+
240
+ try {
241
+
242
+ let result = await Blast.fetch(url);
243
+
244
+ if (result) {
245
+ if (!result.allowed) {
246
+ error_params = [result.message || entry.message, null, true];
247
+ break;
248
+ }
249
+ }
250
+ } catch (err) {
251
+ error_params = [entry.message || err.message, null, true];
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ // Remove errors if not required and empty
259
+ if (!required && !value.length) {
260
+ this.removeErrors();
261
+ }
262
+
263
+ // Make sure something was checked, otherwise exit early
264
+ if (!check_count) {
265
+ return;
266
+ }
267
+
268
+ if (validation_counter != this.validation_counter) {
269
+ // Another validation happened while we were waiting for the result
270
+ if (!force) {
271
+ return;
272
+ }
273
+ }
274
+
275
+ if (!skip_count) {
276
+ // Remember we validated this value (if all the checks were done)
277
+ this.last_validated_value = value;
278
+ }
279
+
280
+ if (error_params) {
281
+ this.addError(...error_params);
282
+ return;
283
+ }
284
+
285
+ // Remove all errors when we got this far
286
+ this.removeErrors();
287
+
288
+ // Mark it as valid, but only if we checked all the checks
289
+ if (!skip_count) {
290
+ this.classList.add('valid');
291
+ this.inputbox_el.classList.add('valid');
292
+
293
+ let message = this.getAttribute('success-message');
294
+
295
+ if (message) {
296
+ this.addSuccess(message);
297
+ }
298
+ }
299
+ });
300
+
301
+ /**
302
+ * Actions to perform when this element
303
+ * has been added to the DOM for the first time
304
+ *
305
+ * @author Jelle De Loecker <jelle@elevenways.be>
306
+ * @since 0.1.3
307
+ * @version 0.1.3
308
+ */
309
+ StringInput.setMethod(function introduced() {
310
+
311
+ const that = this,
312
+ input = this.input_el;
313
+
314
+ if (this.readonly) {
315
+ input.disabled = true;
316
+ }
317
+
318
+ let doRevalidate = Fn.throttle((e, force) => {
319
+ this.revalidate(e, force);
320
+ }, {
321
+ minimum_wait : 300,
322
+ immediate : false,
323
+ reset_on_call : true
324
+ });
325
+
326
+ input.addEventListener('focus', e => {
327
+ this.inputbox_el.classList.add('focus');
328
+ });
329
+
330
+ input.addEventListener('blur', e => {
331
+ this.inputbox_el.classList.remove('focus');
332
+ doRevalidate(e, true);
333
+ });
334
+
335
+ input.addEventListener('keyup', function onKeyup(e) {
336
+ doRevalidate(e);
337
+ that.emit('changing');
338
+ });
339
+
340
+ input.addEventListener('input',function onInput(e) {
341
+ doRevalidate(e);
342
+ });
343
+
344
+ if (this.value) {
345
+ this.revalidate();
346
+ }
347
+ });
@@ -16,7 +16,7 @@ const AlchemyForm = Function.inherits('Alchemy.Widget', 'AlchemyForm');
16
16
  *
17
17
  * @author Jelle De Loecker <jelle@elevenways.be>
18
18
  * @since 0.1.0
19
- * @version 0.1.0
19
+ * @version 0.1.3
20
20
  */
21
21
  AlchemyForm.setMethod(function populateWidget() {
22
22
 
@@ -49,6 +49,12 @@ AlchemyForm.setMethod(function populateWidget() {
49
49
  form.append(col.widget);
50
50
 
51
51
  this.element.append(form);
52
+
53
+ let violations = this.element.getContextVariable('form_violations');
54
+
55
+ if (violations) {
56
+ form.showError(violations);
57
+ }
52
58
  });
53
59
 
54
60
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemy-form",
3
3
  "description": "Form plugin for Alchemy",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "repository": {
6
6
  "type" : "git",
7
7
  "url" : "https://github.com/11ways/alchemy-form.git"
@@ -0,0 +1,36 @@
1
+ <label
2
+ for="nr_input_<% self.getAttribute('input-name') || '' %>"
3
+ >
4
+ <div class="inputlabel">
5
+ <span class="label" data-he-slot="label"></span>
6
+ <span class="description" data-he-slot="description"></span>
7
+ <hr class="spacer">
8
+ </div>
9
+ </label>
10
+
11
+ <div class="row">
12
+
13
+ <button
14
+ class="control minus"
15
+ tabindex="-1"
16
+ aria-hidden="true"
17
+ ></button>
18
+
19
+ <input
20
+ class="input"
21
+ value="<% self.getAttribute('start') %>"
22
+ min="<% self.getAttribute('min') %>"
23
+ max="<% self.getAttribute('max') %>"
24
+ type="number"
25
+ id="nr_input_<% self.getAttribute('input-name') || '' %>"
26
+ name=<% self.getAttribute('input-name') || '' %>
27
+
28
+ <% if (self.hasAttribute('disabled')) print('disabled="disabled"'); %>
29
+ >
30
+
31
+ <button
32
+ class="control plus"
33
+ tabindex="-1"
34
+ aria-hidden="true"
35
+ ></button>
36
+ </div>
@@ -1,6 +1,13 @@
1
1
  <%
2
+
2
3
  if (value) {
3
- value = value.format('Y-m-d H:i:s');
4
+
5
+ // Make sure it's a date
6
+ value = Date.create(value);
7
+
8
+ // According to MDN `toISOString()` should work,
9
+ // but neither Chrome or Firefox allow that format (it still contains timezone info)
10
+ value = value.format('Y-m-d\\TH:i:s');
4
11
  }
5
12
  %>
6
13
  <input