alchemymvc 1.2.2 → 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
  }
@@ -1,18 +1,20 @@
1
- var bcrypt = alchemy.use('bcrypt'),
2
- regex = /^\$2[ayb]\$/;
3
-
4
1
  /**
5
2
  * The Password Field class
6
3
  *
7
4
  * @constructor
8
5
  *
9
- * @author Jelle De Loecker <jelle@develry.be>
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
10
7
  * @since 0.2.0
11
- * @version 1.1.0
8
+ * @version 1.2.3
12
9
  */
13
- var Password = Function.inherits('Alchemy.Field', function Password(schema, name, options) {
14
- Password.super.call(this, schema, name, options);
15
- });
10
+ const Password = Function.inherits('Alchemy.Field', 'Password');
11
+
12
+ if (Blast.isBrowser) {
13
+ return true;
14
+ }
15
+
16
+ const bcrypt = alchemy.use('bcrypt'),
17
+ regex = /^\$2[ayb]\$/;
16
18
 
17
19
  /**
18
20
  * Make sure the password is hashed before storing it
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@develry.be>
7
7
  * @since 0.2.0
8
- * @version 1.1.3
8
+ * @version 1.2.4
9
9
  */
10
10
  var SchemaField = Function.inherits('Alchemy.Field', function Schema(schema, name, options) {
11
11
  Schema.super.call(this, schema, name, options);
@@ -21,18 +21,20 @@ var SchemaField = Function.inherits('Alchemy.Field', function Schema(schema, nam
21
21
  this.field_schema.setName(name);
22
22
  }
23
23
 
24
- if (!this.field_schema.parent || schema.root_schema == this.field_schema.parent) {
25
- this.field_schema.setParent(schema);
26
- } else {
27
- // It's possible the given schema already had the correct parent
28
- // But if that's not the case, throw an error
29
- if (this.field_schema.parent != schema) {
30
- throw new Error('Unable to re-use a schema for field "' + name + '", use `schema.clone()`!');
24
+ if (schema) {
25
+ if (!this.field_schema.parent || schema.root_schema == this.field_schema.parent) {
26
+ this.field_schema.setParent(schema);
27
+ } else {
28
+ // It's possible the given schema already had the correct parent
29
+ // But if that's not the case, throw an error
30
+ if (this.field_schema.parent != schema) {
31
+ throw new Error('Unable to re-use a schema for field "' + name + '", use `schema.clone()`!');
32
+ }
31
33
  }
32
- }
33
34
 
34
- if (!this.field_schema.model_name) {
35
- this.field_schema.setModel(schema.model_name);
35
+ if (!this.field_schema.model_name) {
36
+ this.field_schema.setModel(schema.model_name);
37
+ }
36
38
  }
37
39
  });
38
40
 
@@ -242,7 +244,7 @@ SchemaField.setMethod(function _toDatasource(value, data, datasource, callback)
242
244
  *
243
245
  * @author Jelle De Loecker <jelle@develry.be>
244
246
  * @since 0.2.0
245
- * @version 1.1.0
247
+ * @version 1.2.4
246
248
  *
247
249
  * @param {Mixed} value
248
250
  * @param {Function} callback
@@ -262,6 +264,12 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) {
262
264
  recursive = 1;
263
265
  }
264
266
 
267
+ if (recursive && Blast.isBrowser) {
268
+ // @TODO: this will mostly fail on the browser, so disable it for now.
269
+ // Maybe make it configurable later
270
+ recursive = 0;
271
+ }
272
+
265
273
  // Get associated records if the subschema has associations defined
266
274
  if (recursive && this.field_schema && !Object.isEmpty(this.field_schema.associations)) {
267
275
 
@@ -53,13 +53,38 @@ 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
59
84
  *
60
85
  * @author Jelle De Loecker <jelle@develry.be>
61
86
  * @since 1.1.0
62
- * @version 1.1.5
87
+ * @version 1.2.3
63
88
  *
64
89
  * @param {Object} data
65
90
  *
@@ -81,10 +106,14 @@ Criteria.setStatic(function unDry(data) {
81
106
  // Revive the group instance
82
107
  criteria.group = Group.revive(data.group, criteria);
83
108
 
109
+ if (!data.options) {
110
+ data.options = {};
111
+ }
112
+
84
113
  // Revive the select
85
114
  data.options.select = Select.revive(data.options.select, criteria);
86
115
 
87
- criteria.options = data.options;
116
+ criteria.options = data.options || {};
88
117
 
89
118
  return criteria;
90
119
  });
@@ -248,7 +277,7 @@ Criteria.setProperty(function recursive_level() {
248
277
  *
249
278
  * @author Jelle De Loecker <jelle@develry.be>
250
279
  * @since 1.1.0
251
- * @version 1.1.0
280
+ * @version 1.2.3
252
281
  */
253
282
  Criteria.setMethod(Blast.checksumSymbol, function toChecksum() {
254
283
 
@@ -259,7 +288,7 @@ Criteria.setMethod(Blast.checksumSymbol, function toChecksum() {
259
288
 
260
289
  for (key in this.options) {
261
290
 
262
- if (key == 'init_model' || key == 'init_record') {
291
+ if (key == 'init_model' || key == 'init_record' || key == 'assoc_cache') {
263
292
  continue;
264
293
  }
265
294
 
@@ -319,21 +348,30 @@ Criteria.setMethod(Symbol.asyncIterator, function asyncIterator() {
319
348
  *
320
349
  * @author Jelle De Loecker <jelle@develry.be>
321
350
  * @since 1.1.0
322
- * @version 1.1.5
351
+ * @version 1.2.3
323
352
  *
324
353
  * @return {Object}
325
354
  */
326
355
  Criteria.setMethod(function toJSON() {
327
356
 
328
- var result = {},
329
- options = Object.assign({}, this.options);
357
+ let result = {},
358
+ options;
330
359
 
331
360
  if (this.model && this.model.name) {
332
361
  result.model = this.model.name;
333
362
  }
334
363
 
335
- if (options.init_record) {
336
- options.init_record = null;
364
+ if (this.options) {
365
+ let key;
366
+ options = {};
367
+
368
+ for (key in this.options) {
369
+ if (key == 'assoc_cache' || key == 'init_record') {
370
+ continue;
371
+ }
372
+
373
+ options[key] = this.options[key];
374
+ }
337
375
  }
338
376
 
339
377
  result.group = this.group;
@@ -388,7 +426,7 @@ Criteria.setMethod(function clone() {
388
426
  *
389
427
  * @author Jelle De Loecker <jelle@develry.be>
390
428
  * @since 1.1.0
391
- * @version 1.2.0
429
+ * @version 1.2.3
392
430
  *
393
431
  * @return {String[]}
394
432
  */
@@ -396,14 +434,14 @@ Criteria.setMethod(function getFieldsToSelect() {
396
434
 
397
435
  let result;
398
436
 
399
- if (this.options.select.fields && this.options.select.fields.length) {
437
+ if (this.options?.select?.fields?.length) {
400
438
  result = this.options.select.fields.slice(0);
401
439
  }
402
440
 
403
441
  // Fields can sometimes be required for a query (like in a join) but they
404
442
  // won't be selected if other fields are explicitly set.
405
443
  // So in that case: add these special fields to the projection
406
- if (result && this.options.select.query_fields && this.options.select.query_fields) {
444
+ if (result && this.options?.select?.query_fields) {
407
445
  result.push(...this.options.select.query_fields);
408
446
  }
409
447
 
@@ -479,7 +517,7 @@ Criteria.setMethod(function getAssociationConfiguration(alias) {
479
517
  *
480
518
  * @author Jelle De Loecker <jelle@develry.be>
481
519
  * @since 1.1.0
482
- * @version 1.1.0
520
+ * @version 1.2.3
483
521
  *
484
522
  * @param {String} name
485
523
  * @param {Object} item
@@ -524,6 +562,16 @@ Criteria.setMethod(function getCriteriaForAssociation(name, item) {
524
562
  assoc_crit.setOption('assoc_key', assoc_key);
525
563
  assoc_crit.setOption('assoc_value', value);
526
564
 
565
+ // Make the assoc_cache if it doesn't exist yet
566
+ if (options.create_references !== false && !options.assoc_cache) {
567
+ options.assoc_cache = {};
568
+ }
569
+
570
+ // Add the assoc_cache
571
+ if (options.assoc_cache) {
572
+ assoc_crit.setOption('assoc_cache', options.assoc_cache);
573
+ }
574
+
527
575
  // Take over the locale option
528
576
  if (options.locale) {
529
577
  assoc_crit.setOption('locale', options.locale);
@@ -1310,13 +1358,17 @@ var Select = Function.inherits('Alchemy.Base', 'Alchemy.Criteria', function Sele
1310
1358
  *
1311
1359
  * @author Jelle De Loecker <jelle@develry.be>
1312
1360
  * @since 1.1.0
1313
- * @version 1.1.0
1361
+ * @version 1.2.3
1314
1362
  *
1315
1363
  * @return {Select}
1316
1364
  */
1317
1365
  Select.setStatic(function revive(data, criteria) {
1318
1366
 
1319
- var result = new Select(criteria),
1367
+ if (!data) {
1368
+ return;
1369
+ }
1370
+
1371
+ let result = new Select(criteria),
1320
1372
  key;
1321
1373
 
1322
1374
  result.fields = data.fields;
@@ -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.1.0
308
+ * @version 1.2.5
309
309
  *
310
310
  * @param {Object} obj
311
311
  * @param {Boolean|String} cloned
@@ -349,6 +349,26 @@ Document.setStatic(function unDry(obj, cloned) {
349
349
  obj.$options.model = null;
350
350
  }
351
351
 
352
+ if (Blast.isBrowser && obj.$options?.private_fields) {
353
+ let model = alchemy.getModel(obj.$model_name),
354
+ field;
355
+
356
+ for (field of obj.$options.private_fields) {
357
+
358
+ // @TODO: don't let documents undry before schema is ready?
359
+ if (!model.schema) {
360
+ continue;
361
+ }
362
+
363
+ if (model.schema.has(field.name)) {
364
+ continue;
365
+ }
366
+
367
+ model.schema.addField(field.name, field.constructor.type_name, field.options);
368
+ model.constructor.Document.setFieldGetter(field.name);
369
+ }
370
+ }
371
+
352
372
  DocClass.call(result, obj.$record, obj.$options);
353
373
 
354
374
  if (cloned || Blast.isNode || obj[Blast.Classes.IndexedDb.from_cache_symbol]) {
@@ -619,9 +639,9 @@ Document.setMethod(function toDry() {
619
639
  /**
620
640
  * Actually initialize this instance
621
641
  *
622
- * @author Jelle De Loecker <jelle@develry.be>
642
+ * @author Jelle De Loecker <jelle@elevenways.be>
623
643
  * @since 1.0.4
624
- * @version 1.1.0
644
+ * @version 1.2.5
625
645
  *
626
646
  * @param {Object} record
627
647
  * @param {Object} options
@@ -661,17 +681,43 @@ Document.setMethod(function setDataRecord(record, options) {
661
681
  // The original record
662
682
  this.$record = record;
663
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
+
664
689
  if (Blast.isNode && this.constructor.namespace.indexOf('Alchemy.Document') == -1) {
665
- let field,
690
+ let delete_field,
691
+ field,
666
692
  key;
667
693
 
668
694
  for (key in this.$main) {
669
695
  field = this.$model.schema.get(key);
670
696
 
671
- if (!field || field.is_private) {
697
+ if (!field) {
698
+ delete_field = true;
699
+ } else if (field.is_private) {
700
+ delete_field = true;
701
+
702
+ if (options.keep_private_fields) {
703
+ delete_field = false;
704
+ }
705
+ } else {
706
+ delete_field = false;
707
+ }
708
+
709
+ if (delete_field) {
672
710
  delete this.$main[key];
673
711
  }
674
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
+ }
675
721
  }
676
722
 
677
723
  // If this has object fields we need to clone the document already
@@ -143,14 +143,37 @@ DocumentList.setMethod(function createIterator() {
143
143
  *
144
144
  * @author Jelle De Loecker <jelle@develry.be>
145
145
  * @since 1.0.0
146
- * @version 1.1.3
146
+ * @version 1.2.4
147
147
  *
148
148
  * @return {Object}
149
149
  */
150
150
  DocumentList.setMethod(function toDry() {
151
+
152
+ let options;
153
+
154
+ if (this.options) {
155
+ let val,
156
+ key;
157
+
158
+ options = {};
159
+
160
+ for (key in this.options) {
161
+ val = this.options[key];
162
+
163
+ if (key == 'options') {
164
+ val = Object.assign({}, val);
165
+ val.assoc_cache = undefined;
166
+ } else if (key == 'assoc_cache') {
167
+ continue;
168
+ }
169
+
170
+ options[key] = val;
171
+ }
172
+ }
173
+
151
174
  return {
152
175
  value: {
153
- options : this.options,
176
+ options : options,
154
177
  records : this.records,
155
178
  available : this.available,
156
179
  }