alchemy-form 0.1.1 → 0.1.4

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.
@@ -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
+ });
@@ -28,24 +28,79 @@ AlchemyField.constitute(function prepareSchema() {
28
28
  // this.schema.addField('widgets', widgets, {array: true});
29
29
  });
30
30
 
31
+ /**
32
+ * Find the alchemy-form parent
33
+ *
34
+ * @author Jelle De Loecker <jelle@elevenways.be>
35
+ * @since 0.1.4
36
+ * @version 0.1.4
37
+ */
38
+ AlchemyField.enforceProperty(function alchemy_form(new_value) {
39
+
40
+ if (!new_value && this.config && this.config.alchemy_form) {
41
+ new_value = this.config.alchemy_form;
42
+ }
43
+
44
+ if (!new_value) {
45
+
46
+ let parent = this.parent_instance;
47
+
48
+ while (parent) {
49
+
50
+ new_value = parent.alchemy_form;
51
+
52
+ if (new_value) {
53
+ break;
54
+ }
55
+
56
+ if (parent.element) {
57
+ new_value = parent.element.querySelector('alchemy-form');
58
+
59
+ if (new_value) {
60
+ break;
61
+ }
62
+ }
63
+
64
+ parent = parent.parent_instance;
65
+ }
66
+ }
67
+
68
+ return new_value;
69
+ });
70
+
31
71
  /**
32
72
  * Populate the widget
33
73
  *
34
74
  * @author Jelle De Loecker <jelle@elevenways.be>
35
75
  * @since 0.1.0
36
- * @version 0.1.0
76
+ * @version 0.1.4
37
77
  */
38
78
  AlchemyField.setMethod(function populateWidget() {
39
79
 
40
80
  let config = this.config;
41
81
 
82
+ let alchemy_form = this.alchemy_form;
83
+
42
84
  let field_el = this.createElement('alchemy-field');
85
+
86
+ if (alchemy_form) {
87
+ field_el.alchemy_form = alchemy_form;
88
+ }
89
+
43
90
  field_el.field_name = config.field;
44
91
 
45
92
  if (config.view) {
46
93
  field_el.field_view = config.view;
47
94
  }
48
95
 
96
+ if (config.readonly) {
97
+ field_el.readonly = true;
98
+ }
99
+
100
+ if (config.widget_settings) {
101
+ field_el.widget_settings = config.widget_settings;
102
+ }
103
+
49
104
  this.element.append(field_el);
50
105
  });
51
106
 
@@ -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.4
20
20
  */
21
21
  AlchemyForm.setMethod(function populateWidget() {
22
22
 
@@ -31,7 +31,22 @@ AlchemyForm.setMethod(function populateWidget() {
31
31
  form.classList.add('alchemy-widgets-container');
32
32
 
33
33
  if (this.config && this.config.widgets) {
34
- col.widget.value = this.config.widgets;
34
+ let widgets = this.config.widgets.slice(0),
35
+ widget,
36
+ i;
37
+
38
+ for (i = 0; i < widgets.length; i++) {
39
+ widget = widgets[i];
40
+
41
+ if (widget.type == 'alchemy_field') {
42
+ widget = Object.assign({}, widget);
43
+ widget.config = Object.assign({}, widget.config);
44
+ widget.config.alchemy_form = form;
45
+ widgets[i] = widget;
46
+ }
47
+ }
48
+
49
+ col.widget.value = widgets;
35
50
  }
36
51
 
37
52
  let record = this.element.getContextVariable('record');
@@ -49,6 +64,12 @@ AlchemyForm.setMethod(function populateWidget() {
49
64
  form.append(col.widget);
50
65
 
51
66
  this.element.append(form);
67
+
68
+ let violations = this.element.getContextVariable('form_violations');
69
+
70
+ if (violations) {
71
+ form.showError(violations);
72
+ }
52
73
  });
53
74
 
54
75
  /**
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "alchemy-form",
3
3
  "description": "Form plugin for Alchemy",
4
- "version": "0.1.1",
4
+ "version": "0.1.4",
5
5
  "repository": {
6
6
  "type" : "git",
7
7
  "url" : "https://github.com/11ways/alchemy-form.git"
8
8
  },
9
9
  "peerDependencies": {
10
- "alchemymvc" : "~1.1.3"
10
+ "alchemymvc" : "~1.2.0"
11
11
  },
12
12
  "license": "MIT",
13
13
  "engines": {
14
- "node" : ">=8.9.0"
14
+ "node" : ">=12.0.0"
15
15
  }
16
16
  }
@@ -1,9 +1,7 @@
1
- {% with self.sub_fields as sub_field %}
2
- {% each %}
1
+ {% each self.sub_fields as sub_field %}
3
2
  <alchemy-field
4
3
  #alchemy_field_schema=<% self %>
5
4
  #schema=<% self.schema %>
6
5
  field-name=<% sub_field.name %>
7
6
  ></alchemy-field>
8
- {% /each %}
9
- {% /with %}
7
+ {% /each %}
@@ -1 +1 @@
1
- <div class="code-editor"><% Hawkejs.replaceChildren($0, child_nodes) %></div>
1
+ <div class="code-editor"><% Blast.Classes.Hawkejs.replaceChildren($0, child_nodes) %></div>
@@ -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