alchemy-form 0.1.5 → 0.1.6

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/assets/stylesheets/form/alchemy_field_array.scss +4 -0
  3. package/assets/stylesheets/form/alchemy_toggle.scss +2 -0
  4. package/assets/stylesheets/form/query_builder.scss +185 -0
  5. package/config/routes.js +8 -0
  6. package/controller/form_api_controller.js +34 -2
  7. package/element/20_query_builder_base.js +82 -0
  8. package/element/alchemy_field.js +19 -2
  9. package/element/alchemy_select.js +37 -2
  10. package/element/alchemy_select_item.js +9 -0
  11. package/element/alchemy_table.js +123 -21
  12. package/element/query_builder.js +90 -0
  13. package/element/query_builder_entry.js +388 -0
  14. package/element/query_builder_group.js +221 -0
  15. package/element/query_builder_value.js +435 -0
  16. package/helper/form_actions/00_form_action.js +328 -0
  17. package/helper/form_actions/url_action.js +69 -0
  18. package/helper/query_builder_variable_definition/00_variable_definition.js +351 -0
  19. package/helper/query_builder_variable_definition/boolean_variable_definition.js +24 -0
  20. package/helper/query_builder_variable_definition/number_variable_definition.js +106 -0
  21. package/helper/query_builder_variable_definition/string_variable_definition.js +46 -0
  22. package/helper_field/query_builder_assignment.js +11 -0
  23. package/helper_field/query_builder_field.js +91 -0
  24. package/helper_field/query_builder_value.js +56 -0
  25. package/package.json +1 -1
  26. package/view/form/elements/alchemy_field_array.hwk +3 -1
  27. package/view/form/elements/alchemy_field_array_entry.hwk +3 -1
  28. package/view/form/elements/alchemy_select_item.hwk +6 -1
  29. package/view/form/elements/query_builder.hwk +1 -0
  30. package/view/form/elements/query_builder_entry.hwk +33 -0
  31. package/view/form/elements/query_builder_group.hwk +64 -0
  32. package/view/form/elements/query_builder_value.hwk +10 -0
  33. package/view/form/inputs/edit/query_builder.hwk +5 -0
  34. package/view/form/inputs/edit/query_builder_assignment.hwk +6 -0
  35. package/view/form/inputs/edit/query_builder_value.hwk +11 -0
  36. package/view/form/select/qb_item.hwk +7 -0
@@ -0,0 +1,351 @@
1
+ /**
2
+ * ValueType
3
+ *
4
+ * @constructor
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 0.1.6
8
+ * @version 0.1.6
9
+ *
10
+ * @param {Object} config
11
+ */
12
+ const VariableDefinition = Function.inherits('Alchemy.Base', 'Alchemy.QueryBuilder.VariableDefinition', function VariableDefinition(config) {
13
+ this.applyConfig(config);
14
+ });
15
+
16
+ /**
17
+ * Make this an abtract class
18
+ */
19
+ VariableDefinition.makeAbstractClass();
20
+
21
+ /**
22
+ * This class starts a new group
23
+ */
24
+ VariableDefinition.startNewGroup('qb_variable_definitions');
25
+
26
+ /**
27
+ * The machine-readable name of the variable
28
+ *
29
+ * @author Jelle De Loecker <jelle@elevenways.be>
30
+ * @since 0.1.6
31
+ * @version 0.1.6
32
+ */
33
+ VariableDefinition.setProperty('name');
34
+
35
+ /**
36
+ * The human-readable title of the variable
37
+ *
38
+ * @author Jelle De Loecker <jelle@elevenways.be>
39
+ * @since 0.1.6
40
+ * @version 0.1.6
41
+ */
42
+ VariableDefinition.setProperty('title');
43
+
44
+ /**
45
+ * The description of the variable
46
+ *
47
+ * @author Jelle De Loecker <jelle@elevenways.be>
48
+ * @since 0.1.6
49
+ * @version 0.1.6
50
+ */
51
+ VariableDefinition.setProperty('description');
52
+
53
+ /**
54
+ * Is this variable readonly? (Mainly used for assignments)
55
+ *
56
+ * @author Jelle De Loecker <jelle@elevenways.be>
57
+ * @since 0.1.6
58
+ * @version 0.1.6
59
+ */
60
+ VariableDefinition.setProperty('readonly', false);
61
+
62
+ /**
63
+ * The flags of this variable
64
+ *
65
+ * @author Jelle De Loecker <jelle@elevenways.be>
66
+ * @since 0.1.6
67
+ * @version 0.1.6
68
+ */
69
+ VariableDefinition.setProperty('flags');
70
+
71
+ /**
72
+ * Get the type_name from the constructor
73
+ *
74
+ * @author Jelle De Loecker <jelle@elevenways.be>
75
+ * @since 0.1.6
76
+ * @version 0.1.6
77
+ */
78
+ VariableDefinition.setProperty(function type_name() {
79
+ return this.constructor.type_name;
80
+ });
81
+
82
+ /**
83
+ * The `id` property just refers to the `name`
84
+ *
85
+ * @author Jelle De Loecker <jelle@elevenways.be>
86
+ * @since 0.1.6
87
+ * @version 0.1.6
88
+ */
89
+ VariableDefinition.setProperty(function id() {
90
+ return this.name;
91
+ });
92
+
93
+ /**
94
+ * Make sure this definition has operators
95
+ *
96
+ * @author Jelle De Loecker <jelle@elevenways.be>
97
+ * @since 0.1.6
98
+ * @version 0.1.6
99
+ */
100
+ VariableDefinition.constitute(function prepareOperators() {
101
+ this.logical_operators = {};
102
+ this.assignment_operators = {};
103
+ });
104
+
105
+ /**
106
+ * Create the correct variable definition
107
+ *
108
+ * @author Jelle De Loecker <jelle@elevenways.be>
109
+ * @since 0.1.6
110
+ * @version 0.1.6
111
+ */
112
+ VariableDefinition.setStatic(function cast(entry) {
113
+
114
+ if (!entry) {
115
+ return null;
116
+ }
117
+
118
+ if (entry instanceof VariableDefinition) {
119
+ return entry;
120
+ }
121
+
122
+ if (!entry.type) {
123
+ return null;
124
+ }
125
+
126
+ let constructor = VariableDefinition.getMember(entry.type);
127
+
128
+ if (!constructor) {
129
+ return null;
130
+ }
131
+
132
+ let result = new constructor(entry);
133
+
134
+ return result;
135
+ });
136
+
137
+ /**
138
+ * Add a logical operator
139
+ *
140
+ * @author Jelle De Loecker <jelle@elevenways.be>
141
+ * @since 0.1.6
142
+ * @version 0.1.6
143
+ *
144
+ * @param {String} name
145
+ * @param {Object} config
146
+ */
147
+ VariableDefinition.setStatic(function addLogicalOperator(name, config) {
148
+
149
+ if (typeof config == 'string') {
150
+ config = {
151
+ title : config
152
+ };
153
+ } else if (!config) {
154
+ config = {};
155
+ }
156
+
157
+ if(!config.title) {
158
+ config.title = name.titleize();
159
+ }
160
+
161
+ config.id = name;
162
+
163
+ this.constitute(function _addOperator() {
164
+ this.logical_operators[name] = config;
165
+ });
166
+ });
167
+
168
+ /**
169
+ * Add an assignment operator
170
+ *
171
+ * @author Jelle De Loecker <jelle@elevenways.be>
172
+ * @since 0.1.6
173
+ * @version 0.1.6
174
+ *
175
+ * @param {String} name
176
+ * @param {Object} config
177
+ */
178
+ VariableDefinition.setStatic(function addAssignmentOperator(name, config) {
179
+
180
+ if (typeof config == 'string') {
181
+ config = {
182
+ title : config
183
+ };
184
+ } else if (!config) {
185
+ config = {};
186
+ }
187
+
188
+ if(!config.title) {
189
+ config.title = name.titleize();
190
+ }
191
+
192
+ config.id = name;
193
+
194
+ this.constitute(function _addOperator() {
195
+ this.assignment_operators[name] = config;
196
+ });
197
+ });
198
+
199
+ /**
200
+ * Apply configuration
201
+ *
202
+ * @author Jelle De Loecker <jelle@elevenways.be>
203
+ * @since 0.1.6
204
+ * @version 0.1.6
205
+ */
206
+ VariableDefinition.setMethod(function applyConfig(config) {
207
+
208
+ if (!config) {
209
+ return;
210
+ }
211
+
212
+ if (config.name != null) {
213
+ this.name = config.name;
214
+ }
215
+
216
+ if (config.title != null) {
217
+ this.title = config.title;
218
+ }
219
+
220
+ if (config.description != null) {
221
+ this.description = config.description;
222
+ }
223
+ });
224
+
225
+ /**
226
+ * Return an simple object for JSON-ifying
227
+ *
228
+ * @author Jelle De Loecker <jelle@elevenways.be>
229
+ * @since 0.1.6
230
+ * @version 0.1.6
231
+ *
232
+ * @return {Object}
233
+ */
234
+ VariableDefinition.setMethod(function toJSON() {
235
+ return {
236
+ name : this.name,
237
+ title : this.title,
238
+ description : this.description,
239
+ type : this.type_name,
240
+ };
241
+ });
242
+
243
+ /**
244
+ * Return an object for json-drying this object
245
+ *
246
+ * @author Jelle De Loecker <jelle@elevenways.be>
247
+ * @since 0.1.6
248
+ * @version 0.1.6
249
+ *
250
+ * @return {Object}
251
+ */
252
+ VariableDefinition.setMethod(function toDry() {
253
+ return {
254
+ value : this.toJSON(),
255
+ };
256
+ });
257
+
258
+ /**
259
+ * unDry an object
260
+ *
261
+ * @author Jelle De Loecker <jelle@elevenways.be>
262
+ * @since 0.1.6
263
+ * @version 0.1.6
264
+ *
265
+ * @return {VariableDefinition}
266
+ */
267
+ VariableDefinition.setStatic(function unDry(obj) {
268
+ return new this(obj);
269
+ });
270
+
271
+ /**
272
+ * Get all available logical operators
273
+ *
274
+ * @author Jelle De Loecker <jelle@elevenways.be>
275
+ * @since 0.1.6
276
+ * @version 0.1.6
277
+ */
278
+ VariableDefinition.setMethod(function getLogicalOperators(value) {
279
+ return Object.values(this.constructor.logical_operators);
280
+ });
281
+
282
+ /**
283
+ * Get all available logical operators
284
+ *
285
+ * @author Jelle De Loecker <jelle@elevenways.be>
286
+ * @since 0.1.6
287
+ * @version 0.1.6
288
+ */
289
+ VariableDefinition.setMethod(function getAssignmentOperators(value) {
290
+ return Object.values(this.constructor.assignment_operators);
291
+ });
292
+
293
+ /**
294
+ * Create a value input element
295
+ *
296
+ * @author Jelle De Loecker <jelle@elevenways.be>
297
+ * @since 0.1.6
298
+ * @version 0.1.6
299
+ */
300
+ VariableDefinition.setMethod(function createValueInput(renderer, value_data) {
301
+
302
+ let input = renderer.createElement('input');
303
+ input.setAttribute('type', 'text');
304
+
305
+ return input;
306
+ });
307
+
308
+ /**
309
+ * The `equals` operator
310
+ *
311
+ * @author Jelle De Loecker <jelle@elevenways.be>
312
+ * @since 0.1.6
313
+ * @version 0.1.6
314
+ */
315
+ VariableDefinition.addLogicalOperator('equals');
316
+
317
+ /**
318
+ * The `not_equals` operator
319
+ *
320
+ * @author Jelle De Loecker <jelle@elevenways.be>
321
+ * @since 0.1.6
322
+ * @version 0.1.6
323
+ */
324
+ VariableDefinition.addLogicalOperator('not_equals');
325
+
326
+ /**
327
+ * The `is_empty` operator
328
+ *
329
+ * @author Jelle De Loecker <jelle@elevenways.be>
330
+ * @since 0.1.6
331
+ * @version 0.1.6
332
+ */
333
+ VariableDefinition.addLogicalOperator('is_empty');
334
+
335
+ /**
336
+ * The `is_null` operator
337
+ *
338
+ * @author Jelle De Loecker <jelle@elevenways.be>
339
+ * @since 0.1.6
340
+ * @version 0.1.6
341
+ */
342
+ VariableDefinition.addLogicalOperator('is_null');
343
+
344
+ /**
345
+ * The `set` operator sets a value
346
+ *
347
+ * @author Jelle De Loecker <jelle@elevenways.be>
348
+ * @since 0.1.6
349
+ * @version 0.1.6
350
+ */
351
+ VariableDefinition.addAssignmentOperator('set', 'Set');
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The Boolean Value Type
3
+ *
4
+ * @constructor
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 0.1.6
8
+ * @version 0.1.6
9
+ */
10
+ const BooleanDefinition = Function.inherits('Alchemy.QueryBuilder.VariableDefinition', 'Boolean');
11
+
12
+ /**
13
+ * Create a value input element
14
+ *
15
+ * @author Jelle De Loecker <jelle@elevenways.be>
16
+ * @since 0.1.6
17
+ * @version 0.1.6
18
+ *
19
+ * @param {Hawkejs.Renderer}
20
+ */
21
+ BooleanDefinition.setMethod(function createValueInput(renderer) {
22
+ let toggle = renderer.createElement('alchemy-toggle');
23
+ return toggle;
24
+ });
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The Number Value Type
3
+ *
4
+ * @constructor
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 0.1.6
8
+ * @version 0.1.6
9
+ */
10
+ const NumberDefinition = Function.inherits('Alchemy.QueryBuilder.VariableDefinition', 'Number');
11
+
12
+ /**
13
+ * The `<` operator
14
+ *
15
+ * @author Jelle De Loecker <jelle@elevenways.be>
16
+ * @since 0.1.6
17
+ * @version 0.1.6
18
+ */
19
+ NumberDefinition.addLogicalOperator('lt', 'Is Less Than');
20
+
21
+ /**
22
+ * The `<=` operator
23
+ *
24
+ * @author Jelle De Loecker <jelle@elevenways.be>
25
+ * @since 0.1.6
26
+ * @version 0.1.6
27
+ */
28
+ NumberDefinition.addLogicalOperator('lte', 'Is Less Or Equal Than');
29
+
30
+ /**
31
+ * The `>` operator
32
+ *
33
+ * @author Jelle De Loecker <jelle@elevenways.be>
34
+ * @since 0.1.6
35
+ * @version 0.1.6
36
+ */
37
+ NumberDefinition.addLogicalOperator('gt', 'Is Greater Than');
38
+
39
+ /**
40
+ * The `>=` operator
41
+ *
42
+ * @author Jelle De Loecker <jelle@elevenways.be>
43
+ * @since 0.1.6
44
+ * @version 0.1.6
45
+ */
46
+ NumberDefinition.addLogicalOperator('gte', 'Is Greater Or Equal Than');
47
+
48
+ /**
49
+ * The `add` assignment operator adds a value
50
+ *
51
+ * @author Jelle De Loecker <jelle@elevenways.be>
52
+ * @since 0.1.6
53
+ * @version 0.1.6
54
+ */
55
+ NumberDefinition.addAssignmentOperator('add');
56
+
57
+ /**
58
+ * The `subtract` assignment operator subtracts a value
59
+ *
60
+ * @author Jelle De Loecker <jelle@elevenways.be>
61
+ * @since 0.1.6
62
+ * @version 0.1.6
63
+ */
64
+ NumberDefinition.addAssignmentOperator('subtract');
65
+
66
+ /**
67
+ * The `multiply` assignment operator multiplies a value
68
+ *
69
+ * @author Jelle De Loecker <jelle@elevenways.be>
70
+ * @since 0.1.6
71
+ * @version 0.1.6
72
+ */
73
+ NumberDefinition.addAssignmentOperator('multiply');
74
+
75
+ /**
76
+ * The `divide` assignment operator divides a value
77
+ *
78
+ * @author Jelle De Loecker <jelle@elevenways.be>
79
+ * @since 0.1.6
80
+ * @version 0.1.6
81
+ */
82
+ NumberDefinition.addAssignmentOperator('divide');
83
+
84
+ /**
85
+ * The `module` assignment operator leaves the rest
86
+ *
87
+ * @author Jelle De Loecker <jelle@elevenways.be>
88
+ * @since 0.1.6
89
+ * @version 0.1.6
90
+ */
91
+ NumberDefinition.addAssignmentOperator('modulo');
92
+
93
+ /**
94
+ * Create a value input element
95
+ *
96
+ * @author Jelle De Loecker <jelle@elevenways.be>
97
+ * @since 0.1.6
98
+ * @version 0.1.6
99
+ */
100
+ NumberDefinition.setMethod(function createValueInput(renderer, value_data) {
101
+
102
+ let input = renderer.createElement('input');
103
+ input.setAttribute('type', 'number');
104
+
105
+ return input;
106
+ });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The String Value Type
3
+ *
4
+ * @constructor
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 0.1.6
8
+ * @version 0.1.6
9
+ */
10
+ const StringDefinition = Function.inherits('Alchemy.QueryBuilder.VariableDefinition', 'String');
11
+
12
+ /**
13
+ * The `contains` operator
14
+ *
15
+ * @author Jelle De Loecker <jelle@elevenways.be>
16
+ * @since 0.1.6
17
+ * @version 0.1.6
18
+ */
19
+ StringDefinition.addLogicalOperator('contains');
20
+
21
+ /**
22
+ * The `starts_with` operator
23
+ *
24
+ * @author Jelle De Loecker <jelle@elevenways.be>
25
+ * @since 0.1.6
26
+ * @version 0.1.6
27
+ */
28
+ StringDefinition.addLogicalOperator('starts_with');
29
+
30
+ /**
31
+ * The `ends_with` operator
32
+ *
33
+ * @author Jelle De Loecker <jelle@elevenways.be>
34
+ * @since 0.1.6
35
+ * @version 0.1.6
36
+ */
37
+ StringDefinition.addLogicalOperator('ends_with');
38
+
39
+ /**
40
+ * The `append` assignment operator adds text
41
+ *
42
+ * @author Jelle De Loecker <jelle@elevenways.be>
43
+ * @since 0.1.6
44
+ * @version 0.1.6
45
+ */
46
+ StringDefinition.addAssignmentOperator('append');
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A QueryBuilderAssignment field lets you set variable values
3
+ * using the QueryBuilder logic
4
+ *
5
+ * @constructor
6
+ *
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 0.1.6
9
+ * @version 0.1.6
10
+ */
11
+ const QueryBuilderAssignment = Function.inherits('Alchemy.Field.QueryBuilder', 'QueryBuilderAssignment');
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Schema fields are nested schema's
3
+ *
4
+ * @constructor
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 0.1.6
8
+ * @version 0.1.6
9
+ */
10
+ const QueryBuilderField = Function.inherits('Alchemy.Field', function QueryBuilder(schema, name, options) {
11
+
12
+ if (!options) {
13
+ options = {};
14
+ }
15
+
16
+ QueryBuilder.super.call(this, schema, name, options);
17
+ });
18
+
19
+ /**
20
+ * Set the datatype name
21
+ *
22
+ * @author Jelle De Loecker <jelle@develry.be>
23
+ * @since 0.1.6
24
+ * @version 0.1.6
25
+ */
26
+ QueryBuilderField.setDatatype('object');
27
+
28
+ /**
29
+ * This field value is self-contained
30
+ *
31
+ * @author Jelle De Loecker <jelle@develry.be>
32
+ * @since 0.1.6
33
+ * @version 0.1.6
34
+ */
35
+ QueryBuilderField.setSelfContained(true);
36
+
37
+ /**
38
+ * Cast the given value to this field's type
39
+ *
40
+ * @author Jelle De Loecker <jelle@elevenways.be>
41
+ * @since 0.1.6
42
+ * @version 0.1.6
43
+ */
44
+ QueryBuilderField.setMethod(function cast(value, to_datasource) {
45
+ return value;
46
+ });
47
+
48
+ /**
49
+ * Load remote data
50
+ *
51
+ * @author Jelle De Loecker <jelle@elevenways.be>
52
+ * @since 0.1.6
53
+ * @version 0.1.6
54
+ *
55
+ * @param {Object} config
56
+ * @param {HTMLElement} element
57
+ */
58
+ QueryBuilderField.setMethod(function loadData(config, element) {
59
+
60
+ if (element) {
61
+ let form = element.queryParents('alchemy-form');
62
+
63
+ if (form) {
64
+ let doc = form.document;
65
+
66
+ if (doc && doc.root_document) {
67
+ doc = doc.root_document;
68
+ }
69
+
70
+ let model_name,
71
+ $pk;
72
+
73
+ if (doc) {
74
+ model_name = doc.$model_name;
75
+ $pk = doc.$pk;
76
+ }
77
+
78
+ return element.hawkejs_helpers.Alchemy.getResource({
79
+ name : 'FormApi#queryBuilderData',
80
+ post : true,
81
+ body : {
82
+ model : model_name,
83
+ $pk : $pk,
84
+ config : config,
85
+ }
86
+ });
87
+ }
88
+ }
89
+
90
+ return [];
91
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * A QueryBuilderValue field lets you get variable values
3
+ * using the QueryBuilder logic
4
+ *
5
+ * @constructor
6
+ *
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 0.1.6
9
+ * @version 0.1.6
10
+ */
11
+ const QueryBuilderValue = Function.inherits('Alchemy.Field.QueryBuilder', 'QueryBuilderValue');
12
+
13
+ /**
14
+ * Load remote data
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 0.1.6
18
+ * @version 0.1.6
19
+ *
20
+ * @param {Object} config
21
+ * @param {HTMLElement} element
22
+ */
23
+ QueryBuilderValue.setMethod(function loadData(config, element) {
24
+
25
+ if (element) {
26
+ let form = element.queryParents('alchemy-form');
27
+
28
+ if (form) {
29
+ let doc = form.document;
30
+
31
+ if (doc && doc.root_document) {
32
+ doc = doc.root_document;
33
+ }
34
+
35
+ let model_name,
36
+ $pk;
37
+
38
+ if (doc) {
39
+ model_name = doc.$model_name;
40
+ $pk = doc.$pk;
41
+ }
42
+
43
+ return element.hawkejs_helpers.Alchemy.getResource({
44
+ name : 'FormApi#queryBuilderData',
45
+ post : true,
46
+ body : {
47
+ model : model_name,
48
+ $pk : $pk,
49
+ config : config,
50
+ }
51
+ });
52
+ }
53
+ }
54
+
55
+ return [];
56
+ });
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.5",
4
+ "version": "0.1.6",
5
5
  "repository": {
6
6
  "type" : "git",
7
7
  "url" : "https://github.com/11ways/alchemy-form.git"
@@ -2,7 +2,9 @@
2
2
  <% view_files = alchemy_field.view_files %>
3
3
  <% values = self.original_value %>
4
4
 
5
- <button class="add-entry">Add entry</button>
5
+ <button class="add-entry">
6
+ {%t "add-entry" name=alchemy_field.field_name title=alchemy_field.field_title %}
7
+ </button>
6
8
 
7
9
  <div class="entries">
8
10
  {% with values as value %}
@@ -11,5 +11,7 @@
11
11
  <% include(view_files, variables) %>
12
12
  </div>
13
13
  <div class="button">
14
- <button class="remove">Remove</button>
14
+ <button class="remove">
15
+ {%t "remove-entry" name=alchemy_field.field_name title=alchemy_field.field_title %}
16
+ </button>
15
17
  </div>