alchemymvc 1.2.4 → 1.2.5

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.
@@ -29,14 +29,19 @@ var Route = Function.inherits('Alchemy.Helper.Router', function Route(renderer)
29
29
  *
30
30
  * @author Jelle De Loecker <jelle@develry.be>
31
31
  * @since 1.1.0
32
- * @version 1.1.3
32
+ * @version 1.2.5
33
33
  *
34
34
  * @type {RURL}
35
35
  */
36
36
  Router.setProperty(function current_url() {
37
- if (this.view && this.view.variables && this.view.variables.__url) {
38
- return this.view.variables.__url.clone();
37
+ if (this.view_render?.variables?.__url) {
38
+ return this.view_render.variables.__url.clone();
39
39
  } else if (Blast.isBrowser) {
40
+
41
+ if (hawkejs?.scene?.opening_url?.url) {
42
+ return Blast.Classes.RURL.parse(hawkejs.scene.opening_url.url);
43
+ }
44
+
40
45
  return Blast.Classes.RURL.parse(window.location);
41
46
  }
42
47
  });
@@ -580,6 +585,129 @@ Router.setMethod(function getAnchor(name, parameters, options) {
580
585
  return anchor;
581
586
  });
582
587
 
588
+ /**
589
+ * Get the current route variables
590
+ *
591
+ * @author Jelle De Loecker <jelle@elevenways.be>
592
+ * @since 1.2.5
593
+ * @version 1.2.5
594
+ *
595
+ * @return {Object}
596
+ */
597
+ Router.setMethod(function getRouteVariables() {
598
+
599
+ let params,
600
+ route,
601
+ url;
602
+
603
+ if (this.view_render?.variables?.__route) {
604
+ route = this.view_render.variables.__route;
605
+ params = this.view_render.variables.__urlparams;
606
+ url = this.view_render.variables.__url;
607
+ } else if (Blast.isBrowser) {
608
+ route = alchemy.current_route;
609
+ params = alchemy.current_url_params;
610
+ url = alchemy.current_url;
611
+ }
612
+
613
+ return {route, params, url};
614
+ });
615
+
616
+ /**
617
+ * Update language switcher info
618
+ *
619
+ * @author Jelle De Loecker <jelle@elevenways.be>
620
+ * @since 1.2.5
621
+ * @version 1.2.5
622
+ *
623
+ * @param {Element} element The element to apply to
624
+ */
625
+ Router.setMethod(function updateLanguageSwitcher(element) {
626
+
627
+ let language = element.getAttribute('data-alchemy-language-switch');
628
+
629
+ if (!language) {
630
+ return;
631
+ }
632
+
633
+ let url = this.translateCurrentRoute(language);
634
+
635
+ if (!url) {
636
+ url = '/' + language;
637
+ }
638
+
639
+ element.setAttribute('href', url);
640
+ });
641
+
642
+ /**
643
+ * Get a translated URL for the current route
644
+ *
645
+ * @author Jelle De Loecker <jelle@elevenways.be>
646
+ * @since 1.2.5
647
+ * @version 1.2.5
648
+ *
649
+ * @param {String} prefix The prefix to use
650
+ */
651
+ Router.setMethod(function translateCurrentRoute(prefix) {
652
+
653
+ let info = this.getRouteVariables();
654
+
655
+ if (!info.route) {
656
+ return;
657
+ }
658
+
659
+ let config = this.routeConfig(info.route);
660
+
661
+ if (!config) {
662
+ return;
663
+ }
664
+
665
+ // Get the url string
666
+ let url = this.routeUrl(info.route, info.params, {locale: prefix});
667
+
668
+ // Turn it into an RURL object
669
+ url = RURL.parse(url);
670
+
671
+ if (url && url.pathname == '/') {
672
+ url.pathname = '/' + prefix + url.pathname;
673
+ }
674
+
675
+ // Add the get queries
676
+ if (info.url && info.url.search) {
677
+ for (key in info.url.query) {
678
+
679
+ if (key == 'hajax' || key == 'h_diversion' || key == 'htop') {
680
+ continue;
681
+ }
682
+
683
+ url.addQuery(key, info.url.query[key]);
684
+ }
685
+ }
686
+
687
+ return url;
688
+ });
689
+
690
+ /**
691
+ * Turn the given element into a language switcher
692
+ *
693
+ * @author Jelle De Loecker <jelle@elevenways.be>
694
+ * @since 1.2.5
695
+ * @version 1.2.5
696
+ *
697
+ * @param {Element} element The element to apply to
698
+ * @param {String} language The actual language
699
+ * @param {Object} options
700
+ */
701
+ Router.setMethod(function languageSwitcherDirective(element, language, options) {
702
+
703
+ element.setAttribute('hreflang', language);
704
+ element.setAttribute('data-he-link', 'false');
705
+ element.setAttribute('data-alchemy-language-switch', language);
706
+ element.setAttribute('rel', 'nofollow');
707
+
708
+ this.updateLanguageSwitcher(element);
709
+ });
710
+
583
711
  /**
584
712
  * The switch language element
585
713
  *
@@ -589,6 +717,10 @@ Router.setMethod(function getAnchor(name, parameters, options) {
589
717
  */
590
718
  Router.setMethod(function languageSwitcher(options) {
591
719
 
720
+ if (arguments.length == 3) {
721
+ return this.languageSwitcherDirective(...arguments);
722
+ }
723
+
592
724
  var prefixes = this.view.expose('prefixes'),
593
725
  prefix,
594
726
  config,
@@ -15,7 +15,7 @@ const Paginate = Function.inherits('Alchemy.Client.Component', 'Paginate');
15
15
  *
16
16
  * @author Jelle De Loecker <jelle@elevenways.be>
17
17
  * @since 0.0.1
18
- * @version 1.2.2
18
+ * @version 1.2.5
19
19
  *
20
20
  * @param {Model} model
21
21
  * @param {Criteria} criteria
@@ -26,6 +26,8 @@ Paginate.setMethod(function find(model, criteria) {
26
26
 
27
27
  const conduit = this.controller.conduit;
28
28
 
29
+ criteria = Blast.Classes.Alchemy.Criteria.Criteria.cast(criteria);
30
+
29
31
  // Get the model if a name has been given
30
32
  if (typeof model == 'string') {
31
33
  model = this.getModel(model);
@@ -405,6 +405,26 @@ Conduit.setMethod(function chooseBestLocale(locales) {
405
405
  return 'en';
406
406
  });
407
407
 
408
+ /**
409
+ * Check if this request has a permission
410
+ *
411
+ * @author Jelle De Loecker <jelle@elevenways.be>
412
+ * @since 1.2.5
413
+ * @version 1.2.5
414
+ *
415
+ * @param {String} permission
416
+ *
417
+ * @return {Boolean} True if the user has the permission, false otherwise
418
+ */
419
+ Conduit.setMethod(function hasPermission(permission) {
420
+
421
+ if (alchemy.permission_checker != null) {
422
+ return alchemy.permission_checker.conduitHasPermission(this, permission);
423
+ }
424
+
425
+ return false;
426
+ });
427
+
408
428
  if (Blast.isNode) {
409
429
  return;
410
430
  }
@@ -53,6 +53,31 @@ Criteria.setStatic(function isCriteria(instance) {
53
53
  return false;
54
54
  });
55
55
 
56
+ /**
57
+ * Make sure to get a criteria
58
+ *
59
+ * @author Jelle De Loecker <jelle@elevenways.be>
60
+ * @since 1.2.5
61
+ * @version 1.2.5
62
+ *
63
+ * @param {Object} obj
64
+ *
65
+ * @return {Criteria}
66
+ */
67
+ Criteria.setStatic(function cast(obj) {
68
+
69
+ if (Criteria.isCriteria(obj)) {
70
+ return obj;
71
+ }
72
+
73
+ let instance = new Criteria();
74
+
75
+ if (obj) {
76
+ instance.applyOldOptions(obj);
77
+ }
78
+
79
+ return instance;
80
+ });
56
81
 
57
82
  /**
58
83
  * Undry the given object
@@ -81,7 +106,7 @@ Criteria.setStatic(function unDry(data) {
81
106
  // Revive the group instance
82
107
  criteria.group = Group.revive(data.group, criteria);
83
108
 
84
- if(!data.options) {
109
+ if (!data.options) {
85
110
  data.options = {};
86
111
  }
87
112
 
@@ -305,7 +305,7 @@ Document.setStatic(function getDocumentClass(model) {
305
305
  *
306
306
  * @author Jelle De Loecker <jelle@develry.be>
307
307
  * @since 1.0.0
308
- * @version 1.2.4
308
+ * @version 1.2.5
309
309
  *
310
310
  * @param {Object} obj
311
311
  * @param {Boolean|String} cloned
@@ -365,6 +365,7 @@ Document.setStatic(function unDry(obj, cloned) {
365
365
  }
366
366
 
367
367
  model.schema.addField(field.name, field.constructor.type_name, field.options);
368
+ model.constructor.Document.setFieldGetter(field.name);
368
369
  }
369
370
  }
370
371
 
@@ -638,9 +639,9 @@ Document.setMethod(function toDry() {
638
639
  /**
639
640
  * Actually initialize this instance
640
641
  *
641
- * @author Jelle De Loecker <jelle@develry.be>
642
+ * @author Jelle De Loecker <jelle@elevenways.be>
642
643
  * @since 1.0.4
643
- * @version 1.2.4
644
+ * @version 1.2.5
644
645
  *
645
646
  * @param {Object} record
646
647
  * @param {Object} options
@@ -680,9 +681,13 @@ Document.setMethod(function setDataRecord(record, options) {
680
681
  // The original record
681
682
  this.$record = record;
682
683
 
684
+ // @TODO: Find a cleaner way of setting these values
685
+ if (record[name].$translated_fields) {
686
+ this.$hold.translated_fields = record[name].$translated_fields;
687
+ }
688
+
683
689
  if (Blast.isNode && this.constructor.namespace.indexOf('Alchemy.Document') == -1) {
684
- let delete_field = false,
685
- added_private_field_info = false,
690
+ let delete_field,
686
691
  field,
687
692
  key;
688
693
 
@@ -696,20 +701,23 @@ Document.setMethod(function setDataRecord(record, options) {
696
701
 
697
702
  if (options.keep_private_fields) {
698
703
  delete_field = false;
699
-
700
- if (!added_private_field_info) {
701
- added_private_field_info = true;
702
-
703
- let fields = this.$model.schema.getPrivateFields();
704
- options.private_fields = JSON.clone(fields, 'toHawkejs');
705
- }
706
704
  }
705
+ } else {
706
+ delete_field = false;
707
707
  }
708
708
 
709
709
  if (delete_field) {
710
710
  delete this.$main[key];
711
711
  }
712
712
  }
713
+
714
+ if (options?.keep_private_fields) {
715
+ let fields = this.$model.schema.getPrivateFields();
716
+
717
+ if (fields?.length) {
718
+ options.private_fields = JSON.clone(fields, 'toHawkejs');
719
+ }
720
+ }
713
721
  }
714
722
 
715
723
  // If this has object fields we need to clone the document already
@@ -131,7 +131,7 @@ FieldConfig.setMethod(function getContextModel() {
131
131
  *
132
132
  * @author Jelle De Loecker <jelle@elevenways.be>
133
133
  * @since 1.2.2
134
- * @version 1.2.2
134
+ * @version 1.2.5
135
135
  */
136
136
  FieldConfig.setMethod(function getModel() {
137
137
 
@@ -142,13 +142,15 @@ FieldConfig.setMethod(function getModel() {
142
142
  }
143
143
 
144
144
  if (this.association) {
145
- let config = model.getAssociation(this.association);
145
+ try {
146
+ let config = model.getAssociation(this.association);
146
147
 
147
- if (config) {
148
- model = alchemy.getModel(config.modelName);
149
- } else {
150
- model = null;
151
- }
148
+ if (config) {
149
+ model = alchemy.getModel(config.modelName);
150
+ } else {
151
+ model = null;
152
+ }
153
+ } catch (ignored_error) {}
152
154
  }
153
155
 
154
156
  return model;
@@ -159,17 +161,28 @@ FieldConfig.setMethod(function getModel() {
159
161
  *
160
162
  * @author Jelle De Loecker <jelle@elevenways.be>
161
163
  * @since 1.2.2
162
- * @version 1.2.2
164
+ * @version 1.2.5
165
+ *
166
+ * @return {Field}
163
167
  */
164
168
  FieldConfig.setMethod(function getFieldDefinition() {
165
169
 
166
- let model = this.getModel();
170
+ let model = this.getModel(),
171
+ result;
167
172
 
168
- if (!model) {
169
- return;
173
+ if (model) {
174
+ result = model.getField(this.local_path);
170
175
  }
171
176
 
172
- return model.getField(this.local_path);
177
+ if (!result && this.options?.type) {
178
+ let constructor = Classes.Alchemy.Field.Field.getMember(this.options.type);
179
+
180
+ if (constructor) {
181
+ result = new constructor(null, this.name);
182
+ }
183
+ }
184
+
185
+ return result;
173
186
  });
174
187
 
175
188
  /**
@@ -65,7 +65,7 @@ FieldSet.setStatic(function fromArray(fields) {
65
65
  *
66
66
  * @author Jelle De Loecker <jelle@elevenways.be>
67
67
  * @since 1.1.3
68
- * @version 1.1.3
68
+ * @version 1.2.5
69
69
  *
70
70
  * @type {Deck}
71
71
  */
@@ -88,6 +88,14 @@ FieldSet.enforceProperty(function fields(new_value, old_value) {
88
88
  };
89
89
  }
90
90
 
91
+ if (!entry.options) {
92
+ entry.options = {};
93
+ }
94
+
95
+ if (entry.type) {
96
+ entry.options.type = entry.type;
97
+ }
98
+
91
99
  field = new Classes.Alchemy.Criteria.FieldConfig(entry.name, entry.options);
92
100
  deck.set(entry.name, field);
93
101
  }
@@ -205,4 +213,17 @@ FieldSet.setMethod(function addField(name, options) {
205
213
  this.fields.set(name, config);
206
214
 
207
215
  return config;
216
+ });
217
+
218
+ /**
219
+ * Get a fieldconfig by its field name
220
+ *
221
+ * @author Jelle De Loecker <jelle@elevenways.be>
222
+ * @since 1.2.5
223
+ * @version 1.2.5
224
+ *
225
+ * @return {Alchemy.Form.FieldConfig}
226
+ */
227
+ FieldSet.setMethod(function get(name) {
228
+ return this.fields.get(name);
208
229
  });
@@ -32,7 +32,7 @@ if (Blast.isBrowser) {
32
32
  *
33
33
  * @param {Object} options
34
34
  */
35
- var Model = Function.inherits('Alchemy.Client.Base', 'Alchemy.Client.Model', function Model(options) {
35
+ const Model = Function.inherits('Alchemy.Client.Base', 'Alchemy.Client.Model', function Model(options) {
36
36
  this.init(options);
37
37
  });
38
38
 
@@ -49,6 +49,22 @@ Model.constitute(function setModelName() {
49
49
  this.table = this.model_name.tableize();
50
50
  });
51
51
 
52
+ /**
53
+ * Map these events to methods
54
+ *
55
+ * @author Jelle De Loecker <jelle@elevenways.be>
56
+ * @since 1.2.5
57
+ * @version 1.2.5
58
+ */
59
+ Model.mapEventToMethod({
60
+ saved : 'afterSave',
61
+ finding : 'beforeFind',
62
+ queried : 'afterQuery',
63
+ associated : 'afterAssociated',
64
+ found : 'afterData',
65
+ foundDocuments : 'afterFind',
66
+ });
67
+
52
68
  /**
53
69
  * Is the given action enabled on the server?
54
70
  *
@@ -485,7 +501,7 @@ Model.setMethod(function getAliasModel(alias) {
485
501
  *
486
502
  * @author Jelle De Loecker <jelle@develry.be>
487
503
  * @since 0.0.1
488
- * @version 1.1.3
504
+ * @version 1.2.5
489
505
  *
490
506
  * @param {String} type The type of find (first, all)
491
507
  * @param {Criteria} criteria The criteria object
@@ -530,14 +546,10 @@ Model.setMethod(function find(type, criteria, callback) {
530
546
  error = new TypeError('Find type should be a string');
531
547
  }
532
548
 
533
- if (!Blast.Classes.Alchemy.Criteria.Criteria.isCriteria(criteria)) {
534
- try {
535
- let options = criteria;
536
- criteria = new Blast.Classes.Alchemy.Criteria();
537
- criteria.applyOldOptions(options);
538
- } catch (err) {
539
- error = err;
540
- }
549
+ try {
550
+ criteria = Blast.Classes.Alchemy.Criteria.Criteria.cast(criteria);
551
+ } catch (err) {
552
+ error = err;
541
553
  }
542
554
 
543
555
  if (error != null) {
@@ -5,6 +5,7 @@ var fileCache = alchemy.shared('files.fileCache'),
5
5
  libua = alchemy.use('useragent'),
6
6
  zlib = alchemy.use('zlib'),
7
7
  BODY = Symbol('body'),
8
+ TESTED_ROUTES = Symbol('tested_routes'),
8
9
  magic,
9
10
  fs = alchemy.use('fs'),
10
11
  prefixes = alchemy.shared('Routing.prefixes');
@@ -265,6 +266,62 @@ Conduit.setMethod(function setRequestBody(body) {
265
266
  Object.assign(this.body, body);
266
267
  });
267
268
 
269
+ /**
270
+ * Has the given route been tested yet?
271
+ *
272
+ * @author Jelle De Loecker <jelle@elevenways.be>
273
+ * @since 1.2.5
274
+ * @version 1.2.5
275
+ *
276
+ * @param {Route}
277
+ */
278
+ Conduit.setMethod(function hasRouteBeenTested(route) {
279
+
280
+ if (!route || !this[TESTED_ROUTES]) {
281
+ return false;
282
+ }
283
+
284
+ return this[TESTED_ROUTES].has(route);
285
+ });
286
+
287
+ /**
288
+ * Mark this route as having been tested
289
+ *
290
+ * @author Jelle De Loecker <jelle@elevenways.be>
291
+ * @since 1.2.5
292
+ * @version 1.2.5
293
+ *
294
+ * @param {Route}
295
+ */
296
+ Conduit.setMethod(function markRouteAsTested(route) {
297
+
298
+ if (!this[TESTED_ROUTES]) {
299
+ this[TESTED_ROUTES] = new Set();
300
+ }
301
+
302
+ this[TESTED_ROUTES].add(route);
303
+ });
304
+
305
+ /**
306
+ * Rewrite a certain URL parameter
307
+ * (Causing some kind of redirect)
308
+ *
309
+ * @author Jelle De Loecker <jelle@elevenways.be>
310
+ * @since 1.2.5
311
+ * @version 1.2.5
312
+ *
313
+ * @param {String} route_param
314
+ * @param {*} new_value
315
+ */
316
+ Conduit.setMethod(function rewriteRequestRouteParam(route_param, new_value) {
317
+
318
+ if (!this.rewritten_request_route_param) {
319
+ this.rewritten_request_route_param = {};
320
+ }
321
+
322
+ this.rewritten_request_route_param[route_param] = new_value;
323
+ });
324
+
268
325
  /**
269
326
  * Set the request files
270
327
  *
@@ -426,7 +483,7 @@ Conduit.setMethod(function time() {
426
483
  *
427
484
  * @author Jelle De Loecker <jelle@develry.be>
428
485
  * @since 0.2.0
429
- * @version 1.1.5
486
+ * @version 1.2.5
430
487
  *
431
488
  * @param {IncomingMessage} req
432
489
  * @param {ServerResponse} res
@@ -457,6 +514,12 @@ Conduit.setMethod(async function parseRequest() {
457
514
  this.encrypted = this.request.connection.encrypted;
458
515
  }
459
516
 
517
+ if (this.rewritten_request_route_param) {
518
+ let params = Object.assign({}, this.route_string_parameters, this.rewritten_request_route_param);
519
+ let new_url = this.route.generateUrl(params, this);
520
+ this.overrideResponseUrl(new_url);
521
+ }
522
+
460
523
  // If the url has already been parsed, return early
461
524
  if (this.url) {
462
525
  return;
@@ -1170,7 +1233,7 @@ Conduit.setMethod(function callHandler() {
1170
1233
  *
1171
1234
  * @author Jelle De Loecker <jelle@develry.be>
1172
1235
  * @since 1.1.0
1173
- * @version 1.1.0
1236
+ * @version 1.2.5
1174
1237
  *
1175
1238
  * @param {String|Object} options Options or url
1176
1239
  */
@@ -1214,11 +1277,27 @@ Conduit.setMethod(function postpone(options) {
1214
1277
  // Nullify the response
1215
1278
  this.response = null;
1216
1279
 
1217
- let old_url = String(this.url);
1218
-
1219
1280
  // Set the original url
1220
- this.setHeader('x-history-url', old_url);
1221
- this.expose('redirected_to', old_url);
1281
+ this.overrideResponseUrl(this.url)
1282
+ });
1283
+
1284
+ /**
1285
+ * Set the response url
1286
+ *
1287
+ * @author Jelle De Loecker <jelle@develry.be>
1288
+ * @since 1.2.5
1289
+ * @version 1.2.5
1290
+ *
1291
+ * @param {String|RURL} url
1292
+ */
1293
+ Conduit.setMethod(function overrideResponseUrl(url) {
1294
+
1295
+ if (typeof url != 'string') {
1296
+ url = String(url);
1297
+ }
1298
+
1299
+ this.setHeader('x-history-url', url);
1300
+ this.expose('redirected_to', url);
1222
1301
  });
1223
1302
 
1224
1303
  /**
@@ -1226,7 +1305,7 @@ Conduit.setMethod(function postpone(options) {
1226
1305
  *
1227
1306
  * @author Jelle De Loecker <jelle@develry.be>
1228
1307
  * @since 0.2.0
1229
- * @version 1.1.0
1308
+ * @version 1.2.5
1230
1309
  *
1231
1310
  * @param {Number} status 3xx redirection codes. 302 (temporary redirect) by default
1232
1311
  * @param {String|Object} options Options or url
@@ -1283,8 +1362,7 @@ Conduit.setMethod(function redirect(status, options) {
1283
1362
  }
1284
1363
 
1285
1364
  // Register the new url as the one to use for the history
1286
- this.setHeader('x-history-url', url);
1287
- this.expose('redirected_to', url);
1365
+ this.overrideResponseUrl(url);
1288
1366
 
1289
1367
  this.original_path = url;
1290
1368
 
@@ -21,21 +21,12 @@ Function.getNamespace('Alchemy.Element').setStatic('default_element_prefix', 'al
21
21
  *
22
22
  * @author Jelle De Loecker <jelle@elevenways.be>
23
23
  * @since 1.1.3
24
- * @version 1.2.1
24
+ * @version 1.2.5
25
25
  *
26
26
  * @return {RURL}
27
27
  */
28
28
  Element.setMethod(function getCurrentUrl() {
29
- if (Blast.isBrowser) {
30
-
31
- if (hawkejs.scene.opening_url && hawkejs.scene.opening_url.url) {
32
- return Blast.Classes.RURL.parse(hawkejs.scene.opening_url.url);
33
- }
34
-
35
- return Blast.Classes.RURL.parse(window.location);
36
- } else if (this.hawkejs_renderer && this.hawkejs_renderer.variables && this.hawkejs_renderer.variables.__url) {
37
- return this.hawkejs_renderer.variables.__url.clone();
38
- }
29
+ return this.hawkejs_renderer?.helpers?.Router?.current_url;
39
30
  });
40
31
 
41
32
  /**
@@ -1049,7 +1049,7 @@ Field.setMethod(function getRules() {
1049
1049
  *
1050
1050
  * @author Jelle De Loecker <jelle@develry.be>
1051
1051
  * @since 1.1.4
1052
- * @version 1.1.4
1052
+ * @version 1.2.5
1053
1053
  */
1054
1054
  Field.setMethod(function translateRecord(prefixes, record, allow_empty) {
1055
1055
 
@@ -1058,4 +1058,10 @@ Field.setMethod(function translateRecord(prefixes, record, allow_empty) {
1058
1058
  // Use the final result, if we found something or not
1059
1059
  record[this.name] = found.result;
1060
1060
  record['_prefix_' + this.name] = found.prefix;
1061
+
1062
+ if (!record.$translated_fields) {
1063
+ record.$translated_fields = {};
1064
+ }
1065
+
1066
+ record.$translated_fields[this.name] = found.prefix;
1061
1067
  });