alchemymvc 1.2.8 → 1.3.0

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.
@@ -79,7 +79,7 @@ Sluggable.setStatic(function getSource(source, schema) {
79
79
  *
80
80
  * @author Jelle De Loecker <jelle@develry.be>
81
81
  * @since 0.2.0
82
- * @version 0.2.0
82
+ * @version 1.3.0
83
83
  *
84
84
  * @param {Schema} schema
85
85
  * @param {Object} options
@@ -113,7 +113,9 @@ Sluggable.setStatic(function attached(schema, new_options) {
113
113
  // Create the target if it doesn't exist yet
114
114
  if (!target) {
115
115
 
116
- field_options = {};
116
+ field_options = {
117
+ description: 'A human-readable yet unique identifier'
118
+ };
117
119
 
118
120
  // See if the target field we're adding needs to be translatable
119
121
  if (options.translatable || (options.translatable == null && source && source.is_translatable)) {
@@ -78,7 +78,7 @@ LoopConduit.setMethod(function copyParentProperties(conduit) {
78
78
  *
79
79
  * @author Jelle De Loecker <jelle@elevenways.be>
80
80
  * @since 1.1.3
81
- * @version 1.2.7
81
+ * @version 1.3.0
82
82
  *
83
83
  * @param {Object} options
84
84
  * @param {Function} callback
@@ -165,7 +165,7 @@ LoopConduit.setMethod(function setOptions(options, callback) {
165
165
  }
166
166
 
167
167
  if (options.params) {
168
- this.params = options.params;
168
+ this.setRouteParameters(options.params);
169
169
  }
170
170
 
171
171
  if (options.arguments) {
@@ -46,6 +46,43 @@ Router.setProperty(function current_url() {
46
46
  }
47
47
  });
48
48
 
49
+ /**
50
+ * Check if the given URL is a local one
51
+ *
52
+ * @author Jelle De Loecker <jelle@elevenways.be>
53
+ * @since 1.3.0
54
+ * @version 1.3.0
55
+ *
56
+ * @param {String|RURL} url
57
+ *
58
+ * @return {Boolean}
59
+ */
60
+ Router.setMethod(function isLocalUrl(url) {
61
+
62
+ url = RURL.parse(url);
63
+
64
+ if (!url.hostname) {
65
+ return true;
66
+ }
67
+
68
+ if (Blast.isNode && alchemy.settings.url) {
69
+ // @TODO: Would be nice to not have to parse this every time
70
+ let server_url = RURL.parse(alchemy.settings.url);
71
+
72
+ if (server_url.hostname == url.hostname) {
73
+ return true;
74
+ }
75
+ }
76
+
77
+ let current_url = this.current_url;
78
+
79
+ if (current_url && current_url.hostname == url.hostname) {
80
+ return true;
81
+ }
82
+
83
+ return false;
84
+ });
85
+
49
86
  /**
50
87
  * Apply directive to an element
51
88
  *
@@ -284,14 +321,14 @@ Router.setMethod(function routeConfig(name, socket_route) {
284
321
  *
285
322
  * @author Jelle De Loecker <jelle@develry.be>
286
323
  * @since 0.2.0
287
- * @version 1.2.1
324
+ * @version 1.3.0
288
325
  *
289
326
  * @param {String} name
290
327
  * @param {Object} parameters
291
328
  *
292
329
  * @return {RURL}
293
330
  */
294
- Router.setMethod(function routeUrl(name, _parameters, options) {
331
+ Router.setMethod(function routeUrl(name, parameters, options) {
295
332
 
296
333
  var parameters,
297
334
  base_url,
@@ -307,10 +344,9 @@ Router.setMethod(function routeUrl(name, _parameters, options) {
307
344
 
308
345
  url = '';
309
346
  config = options.config || this.routeConfig(name);
310
- parameters = Blast.Bound.Object.assign({}, _parameters);
311
347
 
312
- if (options.locale) {
313
- locales = [options.locale];
348
+ if (options.locale || options.prefix) {
349
+ locales = [options.prefix || options.locale];
314
350
  } else if (options.locales) {
315
351
  locales = options.locales;
316
352
  } else if (this.view) {
@@ -393,11 +429,24 @@ Router.setMethod(function routeUrl(name, _parameters, options) {
393
429
  }
394
430
 
395
431
  if (options.full || options.absolute) {
396
- base_url = this.view.internal('url') || RURL.parse(window.location);
397
- url.protocol = base_url.protocol;
398
- url.host = base_url.host;
399
- url.hostname = base_url.hostname;
400
- url.port = base_url.port;
432
+ if (this.view) {
433
+ base_url = this.view.internal('url');
434
+ }
435
+
436
+ if (!base_url) {
437
+ if (Blast.isBrowser) {
438
+ base_url = RURL.parse(window.location);
439
+ } else {
440
+ base_url = RURL.parse(alchemy.settings.url);
441
+ }
442
+ }
443
+
444
+ if (base_url) {
445
+ url.protocol = base_url.protocol;
446
+ url.host = base_url.host;
447
+ url.hostname = base_url.hostname;
448
+ url.port = base_url.port;
449
+ }
401
450
  }
402
451
 
403
452
  url._locales = locales;
@@ -631,11 +680,12 @@ Router.setMethod(function getRouteVariables() {
631
680
  *
632
681
  * @author Jelle De Loecker <jelle@elevenways.be>
633
682
  * @since 1.2.5
634
- * @version 1.2.5
683
+ * @version 1.3.0
635
684
  *
636
685
  * @param {Element} element The element to apply to
686
+ * @param {Object} variables
637
687
  */
638
- Router.setMethod(function updateLanguageSwitcher(element) {
688
+ Router.setMethod(function updateLanguageSwitcher(element, variables) {
639
689
 
640
690
  let language = element.getAttribute('data-alchemy-language-switch');
641
691
 
@@ -643,7 +693,7 @@ Router.setMethod(function updateLanguageSwitcher(element) {
643
693
  return;
644
694
  }
645
695
 
646
- let url = this.translateCurrentRoute(language);
696
+ let url = this.translateCurrentRoute(language, variables);
647
697
 
648
698
  if (!url) {
649
699
  url = '/' + language;
@@ -657,11 +707,21 @@ Router.setMethod(function updateLanguageSwitcher(element) {
657
707
  *
658
708
  * @author Jelle De Loecker <jelle@elevenways.be>
659
709
  * @since 1.2.5
660
- * @version 1.2.5
710
+ * @version 1.3.0
661
711
  *
662
712
  * @param {String} prefix The prefix to use
713
+ * @param {Object} variables
663
714
  */
664
- Router.setMethod(function translateCurrentRoute(prefix) {
715
+ Router.setMethod(function translateCurrentRoute(prefix, variables) {
716
+
717
+ let current_route_translations = variables?.__current_route_translations,
718
+ current_route_translation = current_route_translations?.[prefix] ?? null;
719
+
720
+ if (current_route_translation) {
721
+ return current_route_translation;
722
+ } else if (current_route_translation === false) {
723
+ return false;
724
+ }
665
725
 
666
726
  let info = this.getRouteVariables();
667
727
 
@@ -685,6 +745,11 @@ Router.setMethod(function translateCurrentRoute(prefix) {
685
745
  url.pathname = '/' + prefix + url.pathname;
686
746
  }
687
747
 
748
+ // Don't return urls with missing parameters
749
+ if (url.pathname.indexOf('{') > -1) {
750
+ return false;
751
+ }
752
+
688
753
  // Add the get queries
689
754
  if (info.url && info.url.search) {
690
755
  for (key in info.url.query) {
@@ -705,7 +770,7 @@ Router.setMethod(function translateCurrentRoute(prefix) {
705
770
  *
706
771
  * @author Jelle De Loecker <jelle@elevenways.be>
707
772
  * @since 1.2.5
708
- * @version 1.2.5
773
+ * @version 1.3.0
709
774
  *
710
775
  * @param {Element} element The element to apply to
711
776
  * @param {String} language The actual language
@@ -718,7 +783,7 @@ Router.setMethod(function languageSwitcherDirective(element, language, options)
718
783
  element.setAttribute('data-alchemy-language-switch', language);
719
784
  element.setAttribute('rel', 'nofollow');
720
785
 
721
- this.updateLanguageSwitcher(element);
786
+ this.updateLanguageSwitcher(element, element.hawkejs_renderer?.variables);
722
787
  });
723
788
 
724
789
  /**
@@ -159,6 +159,22 @@ Controller.setMethod(function addComponent(name, options) {
159
159
  this.components[underscored] = new Blast.Classes.Alchemy.Client.Component[name](this, options);
160
160
  }, false);
161
161
 
162
+ /**
163
+ * Change the response URL (or disable it)
164
+ *
165
+ * @author Jelle De Loecker <jelle@elevenways.be>
166
+ * @since 1.3.0
167
+ * @version 1.3.0
168
+ *
169
+ * @param {String|RURL|Boolean} new_url
170
+ */
171
+ Controller.setMethod(function setResponseUrl(new_url) {
172
+
173
+ // @TODO: will only work when called on the server-side
174
+ if (this.conduit) {
175
+ this.conduit.setResponseUrl(new_url);
176
+ }
177
+ });
162
178
 
163
179
  /**
164
180
  * Set a variable for ViewRender, through conduit
@@ -308,12 +324,12 @@ Controller.setMethod(function end(message) {
308
324
  *
309
325
  * @author Jelle De Loecker <jelle@develry.be>
310
326
  * @since 0.2.0
311
- * @version 1.1.0
327
+ * @version 1.3.0
312
328
  *
313
329
  * @param {String} name The name of the action to execute
314
330
  * @param {Array} args Arguments to apply to the action
315
331
  */
316
- Controller.setMethod(function doAction(name, args) {
332
+ Controller.setMethod(async function doAction(name, args) {
317
333
 
318
334
  var that = this;
319
335
 
@@ -338,43 +354,42 @@ Controller.setMethod(function doAction(name, args) {
338
354
  this.initial_action_arguments = args;
339
355
  }
340
356
 
341
- if (this.conduit && this.conduit.route && this.conduit.route.options && this.conduit.route.options.title) {
357
+ const route = this.conduit?.route;
342
358
 
343
- let title = this.conduit.route.options.title;
359
+ if (route) {
344
360
 
345
- if (alchemy.settings && alchemy.settings.title_suffix) {
346
- title += alchemy.settings.title_suffix;
347
- }
361
+ if (route.options?.title) {
362
+ let title = route.options.title;
348
363
 
349
- this.setTitle(title);
350
- }
364
+ if (alchemy.settings && alchemy.settings.title_suffix) {
365
+ title += alchemy.settings.title_suffix;
366
+ }
351
367
 
352
- Function.series(function initializing(next) {
353
- that.issueEvent('initializing', next);
354
- }, function filtering(next) {
355
- that.issueEvent('filtering', next);
356
- }, function starting(next) {
357
- that.issueEvent('starting', [name], next);
358
- }, function actioning(next) {
359
-
360
- var result = that.constructor.actions[name].apply(that, args);
361
-
362
- if (result && typeof result.then == 'function') {
363
- result.then(function() {
364
- next();
365
- }).catch(next);
366
- } else {
367
- next();
368
+ this.setTitle(title);
368
369
  }
369
370
 
370
- }, function done(err) {
371
+ if (route.requires_data_for_translation && route.visible_location !== false && this.conduit.prefix) {
372
+ let route_translations = route.getRouteTranslations(this, this.conduit);
371
373
 
372
- if (err != null) {
373
- that.conduit.error(err);
374
- return;
374
+ if (route_translations) {
375
+ route_translations = await route_translations;
376
+ this.internal('current_route_translations', route_translations);
377
+ }
375
378
  }
376
379
 
377
- });
380
+ if (route.visible_location !== true) {
381
+ this.setResponseUrl(route.visible_location);
382
+ }
383
+ }
384
+
385
+ try {
386
+ await this.issueEvent('initializing');
387
+ await this.issueEvent('filtering');
388
+ await this.issueEvent('starting', [name]);
389
+ await this.constructor.actions[name].apply(this, args);
390
+ } catch (err) {
391
+ this.conduit.error(err);
392
+ }
378
393
  });
379
394
 
380
395
  /**
@@ -590,7 +590,7 @@ NoSQL.setStatic(function areComparable(a, b) {
590
590
  *
591
591
  * @author Jelle De Loecker <jelle@develry.be>
592
592
  * @since 1.1.0
593
- * @version 1.2.2
593
+ * @version 1.3.0
594
594
  *
595
595
  * @param {Criteria} criteria
596
596
  * @param {Group} group
@@ -710,12 +710,14 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
710
710
  obj = {};
711
711
 
712
712
  let field_entry = {},
713
- name = entry.target_path;
713
+ name = entry.target_path,
714
+ queries_property = name.indexOf('.') > -1;
714
715
 
715
716
  // Do we need to look into an object itself?
716
717
  // (Like the "timestamp" property of a date field when stored with units)
717
- if (entry.db_property) {
718
+ if (entry.db_property && !queries_property) {
718
719
  name += '.' + entry.db_property;
720
+ queries_property = true;
719
721
  }
720
722
 
721
723
  if (entry.association) {
@@ -815,15 +817,46 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
815
817
  throw new Error('Unknown criteria expression: "' + item.type + '"');
816
818
  }
817
819
 
820
+ let multiple_fields,
821
+ prefixed_name = name;
822
+
823
+ // Temporary fix to actually query translatable field contents
824
+ // (entry.field can be undefined if trying to query a path)
825
+ if (entry.field && entry.field.is_translatable) {
826
+
827
+ let prefix = criteria.options.locale;
828
+
829
+ // If a prefix is specified, only query that translation
830
+ if (prefix) {
831
+ prefixed_name = name + '.' + prefix;
832
+ } else {
833
+ multiple_fields = [];
834
+
835
+ // No prefixes specified, so look through all translations
836
+ for (let key in Prefix.all()) {
837
+ multiple_fields.push(name + '.' + key);
838
+ }
839
+ }
840
+ }
841
+
818
842
  if (obj && obj.$or) {
819
843
 
820
844
  let $or = [],
821
845
  i;
822
846
 
823
847
  for (i = 0; i < obj.$or.length; i++) {
824
- $or.push({
825
- [name] : obj.$or[i]
826
- });
848
+
849
+ if (multiple_fields) {
850
+ for (let name of multiple_fields) {
851
+ $or.push({
852
+ [name] : obj.$or[i]
853
+ });
854
+ }
855
+ } else {
856
+ $or.push({
857
+ [prefixed_name] : obj.$or[i]
858
+ });
859
+ }
827
860
  }
828
861
 
829
862
  if (not) {
@@ -861,18 +894,19 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
861
894
 
862
895
  // Temporary fix to actually query translatable field contents
863
896
  // (entry.field can be undefined if trying to query a path)
864
- if (entry.field && entry.field.is_translatable) {
897
+ if (multiple_fields) {
898
+
865
899
  let $or = [];
866
900
 
867
- for (let key in Prefix.all()) {
901
+ for (let name of multiple_fields) {
868
902
  let temp = {};
869
- temp[name + '.' + key] = obj;
903
+ temp[name] = obj;
870
904
  $or.push(temp);
871
905
  }
872
906
 
873
907
  field_entry.$or = $or;
874
908
  } else {
875
- field_entry[name] = obj;
909
+ field_entry[prefixed_name] = obj;
876
910
  }
877
911
  }
878
912
 
@@ -45,7 +45,7 @@ Enum.setMethod(function cast(value) {
45
45
  *
46
46
  * @author Jelle De Loecker <jelle@develry.be>
47
47
  * @since 0.2.0
48
- * @version 1.2.1
48
+ * @version 1.3.0
49
49
  *
50
50
  * @return {EnumValues}
51
51
  */
@@ -55,10 +55,10 @@ Enum.setMethod(function getValues() {
55
55
  // or its pluralized name, if nothing was found
56
56
  if (this.options && this.options.values) {
57
57
  return this.options.values;
58
- } else if (this.schema.enumValues[this.name]) {
59
- return this.schema.enumValues[this.name];
58
+ } else if (this.schema.enum_values[this.name]) {
59
+ return this.schema.enum_values[this.name];
60
60
  } else {
61
- return this.schema.enumValues[this.name.pluralize()];
61
+ return this.schema.enum_values[this.name.pluralize()];
62
62
  }
63
63
  });
64
64
 
@@ -69,7 +69,7 @@ SchemaField.setProperty(function root_model() {
69
69
  *
70
70
  * @author Jelle De Loecker <jelle@elevenways.be>
71
71
  * @since 1.1.4
72
- * @version 1.1.4
72
+ * @version 1.3.0
73
73
  *
74
74
  * @type {Boolean}
75
75
  */
@@ -80,7 +80,7 @@ SchemaField.setProperty(function requires_translating() {
80
80
  }
81
81
 
82
82
  if (this.field_schema) {
83
- return !!this.field_schema.hasTranslations;
83
+ return !!this.field_schema.has_translations;
84
84
  }
85
85
 
86
86
  return false;
@@ -364,7 +364,7 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) {
364
364
  *
365
365
  * @author Jelle De Loecker <jelle@elevenways.be>
366
366
  * @since 1.1.4
367
- * @version 1.2.6
367
+ * @version 1.3.0
368
368
  */
369
369
  SchemaField.setMethod(function translateRecord(prefixes, record, allow_empty) {
370
370
 
@@ -385,13 +385,13 @@ SchemaField.setMethod(function translateRecord(prefixes, record, allow_empty) {
385
385
  for (schema_record of subject) {
386
386
  count++;
387
387
 
388
- if (this.field_schema.hasTranslations && schema_record) {
388
+ if (this.field_schema.has_translations && schema_record) {
389
389
 
390
390
  let field_name,
391
391
  field;
392
392
 
393
- for (field_name in this.field_schema.translatableFields) {
394
- field = this.field_schema.translatableFields[field_name];
393
+ for (field_name in this.field_schema.translatable_fields) {
394
+ field = this.field_schema.translatable_fields[field_name];
395
395
  field.translateRecord(prefixes, schema_record, allow_empty);
396
396
 
397
397
  if (schema_record.$translated_fields) {
@@ -641,7 +641,7 @@ Document.setMethod(function toDry() {
641
641
  *
642
642
  * @author Jelle De Loecker <jelle@elevenways.be>
643
643
  * @since 1.0.4
644
- * @version 1.2.5
644
+ * @version 1.3.0
645
645
  *
646
646
  * @param {Object} record
647
647
  * @param {Object} options
@@ -721,7 +721,7 @@ Document.setMethod(function setDataRecord(record, options) {
721
721
  }
722
722
 
723
723
  // If this has object fields we need to clone the document already
724
- if (Blast.isBrowser && this.hasObjectFields() && !this.$is_cloned) {
724
+ if (this.hasObjectFields() && !this.$is_cloned) {
725
725
  this.storeCurrentDataAsOriginalRecord();
726
726
  }
727
727
 
@@ -731,6 +731,30 @@ Document.setMethod(function setDataRecord(record, options) {
731
731
  }
732
732
  });
733
733
 
734
+ /**
735
+ * Get the translated value of a certain field.
736
+ * If this document has already been translated, a new instance will be queried
737
+ *
738
+ * @author Jelle De Loecker <jelle@elevenways.be>
739
+ * @since 1.3.0
740
+ * @version 1.3.0
741
+ *
742
+ * @param {String} prefix
743
+ */
744
+ Document.setMethod(async function getTranslatedValueOfField(field_name, prefix) {
745
+
746
+ const model = this.$model;
747
+
748
+ let crit = model.find();
749
+ crit.setOption('locale', prefix);
750
+ crit.select(field_name);
751
+ crit.where(model.primary_key, this.$pk);
752
+
753
+ let doc = await model.find('first', crit);
754
+
755
+ return doc?.[field_name];
756
+ });
757
+
734
758
  /**
735
759
  * Refresh the values
736
760
  *
@@ -793,7 +817,7 @@ Document.setMethod(function getCleanOptions() {
793
817
  *
794
818
  * @author Jelle De Loecker <jelle@develry.be>
795
819
  * @since 0.2.0
796
- * @version 1.0.7
820
+ * @version 1.3.0
797
821
  *
798
822
  * @param {Object} data
799
823
  * @param {Function} callback
@@ -890,11 +914,7 @@ Document.setMethod(function save(data, options, callback) {
890
914
  }
891
915
  }
892
916
 
893
- if (use_data) {
894
- sub_pledge = this.$model.save(data, options, updateDoc);
895
- } else {
896
- sub_pledge = this.$model.save(main, options, updateDoc);
897
- }
917
+ sub_pledge = this.$model.save(this, options, updateDoc);
898
918
 
899
919
  return pledge;
900
920
  });
@@ -1146,7 +1166,7 @@ Document.setMethod(function hasFieldValue(name) {
1146
1166
  *
1147
1167
  * @author Jelle De Loecker <jelle@develry.be>
1148
1168
  * @since 1.0.4
1149
- * @version 1.1.0
1169
+ * @version 1.3.0
1150
1170
  *
1151
1171
  * @param {String} name The optional field name
1152
1172
  *
@@ -1169,7 +1189,18 @@ Document.setMethod(function hasChanged(name) {
1169
1189
 
1170
1190
  // If we only want to check a single field
1171
1191
  if (name) {
1172
- result = !Object.alike(this.$attributes.original_record[name], this[name]);
1192
+ let current_value,
1193
+ old_value;
1194
+
1195
+ if (name.includes('.')) {
1196
+ current_value = Object.path(this, name);
1197
+ old_value = Object.path(this.$attributes.original_record, name);
1198
+ } else {
1199
+ current_value = this[name];
1200
+ old_value = this.$attributes.original_record[name];
1201
+ }
1202
+
1203
+ result = !Object.alike(old_value, current_value);
1173
1204
  } else {
1174
1205
 
1175
1206
  let key;
@@ -215,6 +215,17 @@ FieldSet.setMethod(function addField(name, options) {
215
215
  return config;
216
216
  });
217
217
 
218
+ /**
219
+ * Remove all current fields
220
+ *
221
+ * @author Jelle De Loecker <jelle@elevenways.be>
222
+ * @since 1.3.0
223
+ * @version 1.3.0
224
+ */
225
+ FieldSet.setMethod(function clear() {
226
+ this.fields = new Deck();
227
+ });
228
+
218
229
  /**
219
230
  * Get a fieldconfig by its field name
220
231
  *