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,6 +79,12 @@ Schema.setStatic(function unDry(value, custom_method, whenDone) {
79
79
  Schema.setDeprecatedProperty('modelName', 'model_name');
80
80
  Schema.setDeprecatedProperty('modelClass', 'model_class');
81
81
  Schema.setDeprecatedProperty('modelInstance', 'model_instance');
82
+ Schema.setDeprecatedProperty('hasTranslations', 'has_translations');
83
+ Schema.setDeprecatedProperty('hasAlternates', 'has_alternates');
84
+ Schema.setDeprecatedProperty('translatableFields', 'translatable_fields');
85
+ Schema.setDeprecatedProperty('indexFields', 'index_fields');
86
+ Schema.setDeprecatedProperty('enumValues', 'enum_values');
87
+ Schema.setDeprecatedProperty('hasBehaviours', 'has_behaviours');
82
88
 
83
89
  /**
84
90
  * Set a reference to itself
@@ -128,6 +134,19 @@ Schema.setProperty(function field_count() {
128
134
  return result;
129
135
  });
130
136
 
137
+ /**
138
+ * Does this schema have translatable fields?
139
+ *
140
+ * @author Jelle De Loecker <jelle@elevenways.be>
141
+ * @since 1.3.0
142
+ * @version 1.3.0
143
+ *
144
+ * @type {Boolean}
145
+ */
146
+ Schema.setProperty(function has_translatable_fields() {
147
+ return this.has_translations;
148
+ });
149
+
131
150
  /**
132
151
  * Clone for JSON-Dry (JSON.clone())
133
152
  *
@@ -164,19 +183,33 @@ Schema.setMethod(function clone() {
164
183
  *
165
184
  * @author Jelle De Loecker <jelle@develry.be>
166
185
  * @since 1.1.0
167
- * @version 1.1.0
186
+ * @version 1.3.0
168
187
  *
169
188
  * @return {Object}
170
189
  */
171
190
  Schema.setMethod(function toDry() {
172
191
 
173
- var value = {
192
+ const value = {
174
193
  name : this.name,
175
194
  associations : this.associations,
176
195
  fields : [],
177
- rules : this.rules,
196
+ rules : new Deck(),
178
197
  };
179
198
 
199
+ let rule_dict = this.rules.getDict();
200
+
201
+ // Only add rules that are manually added to the schema,
202
+ // not ones defined by the field (else we would revive the rules double)
203
+ for (let key in rule_dict) {
204
+ let entry = rule_dict[key];
205
+
206
+ if (entry.options?.from_field) {
207
+ continue;
208
+ }
209
+
210
+ value.rules.set(key, entry);
211
+ }
212
+
180
213
  if (this.parent) {
181
214
  value.parent = this.parent;
182
215
  }
@@ -216,7 +249,7 @@ Schema.setMethod(function toDry() {
216
249
  *
217
250
  * @author Jelle De Loecker <jelle@develry.be>
218
251
  * @since 1.1.0
219
- * @version 1.1.0
252
+ * @version 1.3.0
220
253
  */
221
254
  Schema.setMethod(function init() {
222
255
  this.associations = {};
@@ -225,19 +258,19 @@ Schema.setMethod(function init() {
225
258
  this.indexes = {};
226
259
 
227
260
  // All fields belonging to an index group
228
- this.indexFields = {};
261
+ this.index_fields = {};
229
262
 
230
263
  // All translatable fields
231
- this.translatableFields = {};
264
+ this.translatable_fields = {};
232
265
 
233
266
  // Amount of translatable fields
234
- this.hasTranslations = 0;
267
+ this.has_translations = 0;
235
268
 
236
269
  // Amount of alternate indexes
237
- this.hasAlternates = 0;
270
+ this.has_alternates = 0;
238
271
 
239
272
  // Enum values
240
- this.enumValues = {};
273
+ this.enum_values = {};
241
274
 
242
275
  // Attached behaviours
243
276
  this.behaviours = {};
@@ -249,7 +282,7 @@ Schema.setMethod(function init() {
249
282
  this.rules = new Deck();
250
283
 
251
284
  // Behaviour count
252
- this.hasBehaviours = 0;
285
+ this.has_behaviours = 0;
253
286
  });
254
287
 
255
288
  /**
@@ -448,9 +481,9 @@ Schema.setMethod(function getPrivateFields() {
448
481
  /**
449
482
  * Add a field to this schema
450
483
  *
451
- * @author Jelle De Loecker <jelle@develry.be>
484
+ * @author Jelle De Loecker <jelle@elevenways.be>
452
485
  * @since 0.2.0
453
- * @version 1.1.7
486
+ * @version 1.3.0
454
487
  *
455
488
  * @param {String} name
456
489
  * @param {String} type
@@ -510,8 +543,8 @@ Schema.setMethod(function addField(name, type, options) {
510
543
  field = new FieldClass(this, name, options);
511
544
 
512
545
  if (field.requires_translating) {
513
- this.hasTranslations++;
514
- this.translatableFields[name] = field;
546
+ this.has_translations++;
547
+ this.translatable_fields[name] = field;
515
548
  }
516
549
 
517
550
  this.set(name, field);
@@ -521,12 +554,12 @@ Schema.setMethod(function addField(name, type, options) {
521
554
  i;
522
555
 
523
556
  for (i = 0; i < rules.length; i++) {
524
- this.addRule(rules[i], [name]);
557
+ this.addRule(rules[i], {from_field: true, fields: [name]});
525
558
  }
526
559
  }
527
560
 
528
561
  if (options.required) {
529
- this.addRule('not_empty', [name]);
562
+ this.addRule('not_empty', {from_field: true, fields: [name]});
530
563
  }
531
564
 
532
565
  return field;
@@ -706,7 +739,7 @@ Schema.setMethod(function getFieldNames() {
706
739
  *
707
740
  * @author Jelle De Loecker <jelle@develry.be>
708
741
  * @since 0.2.0
709
- * @version 1.1.0
742
+ * @version 1.3.0
710
743
  *
711
744
  * @param {String|FieldType} _field
712
745
  * @param {Object} options
@@ -772,7 +805,7 @@ Schema.setMethod(function addIndex(_field, _options) {
772
805
  // it needs the 'alternate' property in order to be used
773
806
  // as an alternate method of updating without _id
774
807
  if (options.alternate) {
775
- this.hasAlternates++;
808
+ this.has_alternates++;
776
809
  }
777
810
 
778
811
  that.getDatasource().done(function gotDs(err, datasource) {
@@ -799,7 +832,7 @@ Schema.setMethod(function addIndex(_field, _options) {
799
832
 
800
833
  // Store the field order in the index groups
801
834
  that.indexes[options.name].fields[path] = options.order;
802
- that.indexFields[path] = options;
835
+ that.index_fields[path] = options;
803
836
 
804
837
  datasource.ensureIndex(that.model_class, that.indexes[options.name], function ensuredIndex(err, result) {
805
838
 
@@ -0,0 +1,341 @@
1
+ let cached_sitemap;
2
+
3
+ /**
4
+ * Sitemap class
5
+ *
6
+ * @constructor
7
+ *
8
+ * @author Jelle De Loecker <jelle@elevenways.be>
9
+ * @since 1.3.0
10
+ * @version 1.3.0
11
+ *
12
+ */
13
+ const Sitemap = Function.inherits('Alchemy.Base', function Sitemap() {
14
+ this.categories = {};
15
+ });
16
+
17
+ /**
18
+ * Get the sitemap
19
+ *
20
+ * @author Jelle De Loecker <jelle@elevenways.be>
21
+ * @since 1.3.0
22
+ * @version 1.3.0
23
+ *
24
+ * @return {Pledge}
25
+ */
26
+ Sitemap.setStatic(function get() {
27
+
28
+ if (cached_sitemap) {
29
+ return cached_sitemap;
30
+ }
31
+
32
+ let pledge = new Pledge();
33
+ let sitemap = new Sitemap();
34
+ cached_sitemap = pledge;
35
+
36
+ let loading = sitemap.load();
37
+
38
+ Pledge.done(loading, err => {
39
+
40
+ if (err) {
41
+ pledge.reject(err);
42
+ } else {
43
+ pledge.resolve(sitemap);
44
+ }
45
+ });
46
+
47
+ return cached_sitemap;
48
+ });
49
+
50
+ /**
51
+ * Get all the categories
52
+ *
53
+ * @author Jelle De Loecker <jelle@elevenways.be>
54
+ * @since 1.3.0
55
+ * @version 1.3.0
56
+ *
57
+ * @return {Object}
58
+ */
59
+ Sitemap.setMethod(function getCategories(prefix) {
60
+
61
+ let result = [],
62
+ category,
63
+ new_category,
64
+ entry,
65
+ name;
66
+
67
+ for (name in this.categories) {
68
+ category = this.categories[name];
69
+ new_category = [];
70
+
71
+ for (entry of category) {
72
+
73
+ if (entry.prefix && prefix && entry.prefix != prefix) {
74
+ continue;
75
+ }
76
+
77
+ new_category.push(entry);
78
+ }
79
+
80
+ if (new_category.length) {
81
+ result.push({
82
+ name : name,
83
+ pages : new_category,
84
+ });
85
+ }
86
+ }
87
+
88
+ return result;
89
+ });
90
+
91
+ /**
92
+ * Start loading the contents
93
+ *
94
+ * @author Jelle De Loecker <jelle@elevenways.be>
95
+ * @since 1.3.0
96
+ * @version 1.3.0
97
+ *
98
+ * @return {Pledge}
99
+ */
100
+ Sitemap.setMethod(function load() {
101
+
102
+ let sections = Router.getFullRoutes(),
103
+ promises = [],
104
+ key;
105
+
106
+ for (key in sections) {
107
+ let promise = this.processSection(sections[key]);
108
+ promises.push(promise);
109
+ }
110
+
111
+ return Function.parallel(promises);
112
+ });
113
+
114
+ /**
115
+ * Add a URL to the sitemap
116
+ *
117
+ * @author Jelle De Loecker <jelle@elevenways.be>
118
+ * @since 1.3.0
119
+ * @version 1.3.0
120
+ */
121
+ Sitemap.setMethod(function addUrl(category, url, config = {}, prefix = '') {
122
+
123
+ if (!this.categories[category]) {
124
+ this.categories[category] = [];
125
+ }
126
+
127
+ this.categories[category].push({
128
+ url : url,
129
+ title : config.title,
130
+ changefreq : config.changefreq,
131
+ priority : config.priority,
132
+ lastmod : config.lastmod,
133
+ prefix : prefix,
134
+ });
135
+ });
136
+
137
+ /**
138
+ * Process a route section
139
+ *
140
+ * @author Jelle De Loecker <jelle@elevenways.be>
141
+ * @since 1.3.0
142
+ * @version 1.3.0
143
+ *
144
+ * @return {Promise}
145
+ */
146
+ Sitemap.setMethod(function processSection(section) {
147
+
148
+ let promise,
149
+ tasks = [],
150
+ route,
151
+ key;
152
+
153
+ for (key in section) {
154
+ route = section[key];
155
+
156
+ if (!route.sitemap) {
157
+ continue;
158
+ }
159
+
160
+ promise = this.processRoute(route, route.sitemap);
161
+
162
+ if (promise) {
163
+ tasks.push(promise);
164
+ }
165
+ }
166
+
167
+ return Function.parallel(tasks);
168
+ });
169
+
170
+ /**
171
+ * Process a route & its sitemap config
172
+ *
173
+ * @author Jelle De Loecker <jelle@elevenways.be>
174
+ * @since 1.3.0
175
+ * @version 1.3.0
176
+ *
177
+ * @return {Promise}
178
+ */
179
+ Sitemap.setTypedMethod([Types.Alchemy.Route], function processRoute(route) {
180
+
181
+ if (!route.sitemap) {
182
+ return;
183
+ }
184
+
185
+ return this.processRoute(route, route.sitemap);
186
+ });
187
+
188
+ /**
189
+ * Process a route & its sitemap config
190
+ *
191
+ * @author Jelle De Loecker <jelle@elevenways.be>
192
+ * @since 1.3.0
193
+ * @version 1.3.0
194
+ *
195
+ * @return {Promise}
196
+ */
197
+ Sitemap.setTypedMethod([Types.Alchemy.Route, Types.Boolean], function processRoute(route, value) {
198
+
199
+ if (!value) {
200
+ return;
201
+ }
202
+
203
+ return this.processRoute(route, {category: 'general'});
204
+ });
205
+
206
+ /**
207
+ * Process a route & its sitemap config
208
+ *
209
+ * @author Jelle De Loecker <jelle@elevenways.be>
210
+ * @since 1.3.0
211
+ * @version 1.3.0
212
+ *
213
+ * @return {Promise}
214
+ */
215
+ Sitemap.setTypedMethod([Types.Alchemy.Route, Types.Object], function processRoute(route, config) {
216
+
217
+ const category = config.category;
218
+
219
+ if (!category) {
220
+ return;
221
+ }
222
+
223
+ let criteria = config.criteria;
224
+
225
+ if (criteria) {
226
+
227
+ if (typeof criteria == 'function') {
228
+
229
+ let prefixes = [];
230
+
231
+ for (let prefix in route.paths) {
232
+ if (prefix) {
233
+ prefixes.push(prefix);
234
+ }
235
+ }
236
+
237
+ if (prefixes.length) {
238
+ let tasks = [];
239
+
240
+ for (let prefix of prefixes) {
241
+ let prefixed_criteria = criteria(prefix);
242
+ let task = this.processRouteCriteria(route, config, prefixed_criteria);
243
+
244
+ if (task) {
245
+ tasks.push(task);
246
+ }
247
+ }
248
+
249
+ return Function.parallel(tasks);
250
+ }
251
+
252
+ criteria = criteria();
253
+ }
254
+
255
+ return this.processRouteCriteria(route, config, criteria);
256
+ }
257
+
258
+ return this.addRouteWithParameters(route, config);
259
+ });
260
+
261
+ /**
262
+ * Process a route & its sitemap config
263
+ *
264
+ * @author Jelle De Loecker <jelle@elevenways.be>
265
+ * @since 1.3.0
266
+ * @version 1.3.0
267
+ *
268
+ * @return {Promise}
269
+ */
270
+ Sitemap.setTypedMethod([Types.Alchemy.Route, Types.Object, Types.Alchemy.Criteria], async function processRouteCriteria(route, config, criteria) {
271
+
272
+ let prefix = criteria.options.locale;
273
+
274
+ for await (const record of criteria) {
275
+ this.addRouteWithParameters(route, config, record, prefix);
276
+ }
277
+ });
278
+
279
+ /**
280
+ * Add a route with the given parameters
281
+ *
282
+ * @author Jelle De Loecker <jelle@elevenways.be>
283
+ * @since 1.3.0
284
+ * @version 1.3.0
285
+ *
286
+ * @return {Promise}
287
+ */
288
+ Sitemap.setMethod(function addRouteWithParameters(route, config, parameters, forced_prefix) {
289
+
290
+ if (!parameters) {
291
+ parameters = {};
292
+ } else {
293
+ parameters = Object.create(parameters);
294
+ }
295
+
296
+ let route_parameters = Object.assign(parameters, config.parameters),
297
+ prefixes = [];
298
+
299
+ if (forced_prefix) {
300
+ prefixes.push(forced_prefix);
301
+ } else {
302
+ for (let prefix in route.paths) {
303
+ prefixes.push(prefix);
304
+ }
305
+ }
306
+
307
+ for (let prefix of prefixes) {
308
+
309
+ if (!route.paths[prefix]) {
310
+ continue;
311
+ }
312
+
313
+ let url = Router.getUrl(route.name, route_parameters, {
314
+ absolute : true,
315
+ locale : prefix,
316
+ extra_get_parameters : false,
317
+ });
318
+
319
+ let route_config = Object.assign({}, config);
320
+
321
+ if (parameters && parameters.getDisplayFieldValue) {
322
+ route_config.title = parameters.getDisplayFieldValue({prefix});
323
+ }
324
+
325
+ if (!route_config.title) {
326
+ if (route.title) {
327
+ if (typeof route.title == 'object' && !(route.title instanceof Classes.Alchemy.Microcopy)) {
328
+ route_config.title = route.title[prefix] || route.title[''];
329
+ } else {
330
+ route_config.title = route.title;
331
+ }
332
+ }
333
+ }
334
+
335
+ if (!route_config.title) {
336
+ route_config.title = route.name;
337
+ }
338
+
339
+ this.addUrl(config.category, url, route_config, prefix);
340
+ }
341
+ });
package/lib/core/base.js CHANGED
@@ -233,9 +233,9 @@ Base.setStatic(function getAllChildren() {
233
233
  /**
234
234
  * Find a member of this group
235
235
  *
236
- * @author Jelle De Loecker <jelle@develry.be>
236
+ * @author Jelle De Loecker <jelle@elevenways.be>
237
237
  * @since 0.3.0
238
- * @version 1.1.4
238
+ * @version 1.3.0
239
239
  */
240
240
  Base.setStatic(function getMember(name) {
241
241
 
@@ -243,6 +243,10 @@ Base.setStatic(function getMember(name) {
243
243
  throw new Error('Unable to get class member of ' + this.name + ', given name is empty');
244
244
  }
245
245
 
246
+ if (name.includes('.')) {
247
+ return this.getDescendant(name);
248
+ }
249
+
246
250
  if (this.group == null) {
247
251
  return null;
248
252
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@develry.be>
7
7
  * @since 0.1.0
8
- * @version 1.0.5
8
+ * @version 1.3.0
9
9
  */
10
10
  var Alchemy = Function.inherits('Alchemy.Client.Base', function Alchemy() {
11
11
 
@@ -22,6 +22,8 @@ var Alchemy = Function.inherits('Alchemy.Client.Base', function Alchemy() {
22
22
 
23
23
  // Sent subscriptions
24
24
  this.sent_subscriptions = [];
25
+
26
+ this.distinct_problems = new Map();
25
27
  });
26
28
 
27
29
  /**
@@ -125,6 +127,73 @@ Alchemy.setMethod(function initScene(scene, options) {
125
127
  });
126
128
  });
127
129
 
130
+
131
+ /**
132
+ * Log a distinct warning
133
+ *
134
+ * @author Jelle De Loecker <jelle@elevenways.be>
135
+ * @since 1.3.0
136
+ * @version 1.3.0
137
+ *
138
+ * @param {String} id
139
+ * @param {String} message
140
+ * @param {Object} options
141
+ *
142
+ * @return {Object}
143
+ */
144
+ Alchemy.setMethod(function distinctProblem(id, message, options = {}) {
145
+
146
+ if (!id) {
147
+ return;
148
+ }
149
+
150
+ let do_log = true,
151
+ entry = this.distinct_problems.get(id),
152
+ now = Date.now();
153
+
154
+ if (!entry) {
155
+ entry = {
156
+ id,
157
+ last_seen : 0,
158
+ last_logged : 0,
159
+ counter : 0,
160
+ repeat_after : options.repeat_after || 0,
161
+ };
162
+
163
+ this.distinct_problems.set(id, entry);
164
+ } else {
165
+ if (entry.repeat_after) {
166
+ let wait_until = entry.last_logged + entry.repeat_after;
167
+
168
+ if (now >= wait_until) {
169
+ do_log = true;
170
+ }
171
+ } else {
172
+ do_log = false;
173
+ }
174
+ }
175
+
176
+ if (do_log) {
177
+ let args = ['[Distinct]', id];
178
+
179
+ if (message) {
180
+ args.push(message);
181
+ }
182
+
183
+ if (options.error) {
184
+ args.push(options.error);
185
+ }
186
+
187
+ this.printLog(options.level || 'error', args, options);
188
+ entry.last_logged = now;
189
+ }
190
+
191
+ entry.counter++;
192
+ entry.last_seen = now;
193
+
194
+ return entry;
195
+ });
196
+
128
197
  /**
129
198
  * Actually print a log message
130
199
  *
@@ -511,7 +580,7 @@ Alchemy.setMethod(function linkup(type, data, cb) {
511
580
  *
512
581
  * @author Jelle De Loecker <jelle@develry.be>
513
582
  * @since 0.3.0
514
- * @version 1.2.5
583
+ * @version 1.3.0
515
584
  *
516
585
  * @param {Object} variables
517
586
  * @param {Object} render_data
@@ -542,7 +611,7 @@ Alchemy.setMethod(function markLinks(variables, render_data) {
542
611
  elements = document.querySelectorAll('[data-alchemy-language-switch]');
543
612
 
544
613
  for (i = 0; i < elements.length; i++) {
545
- hawkejs.scene.general_renderer.helpers.Router.updateLanguageSwitcher(elements[i]);
614
+ hawkejs.scene.general_renderer.helpers.Router.updateLanguageSwitcher(elements[i], variables);
546
615
  }
547
616
 
548
617
  // Get all the elements with a breadcrumb set
@@ -835,17 +904,17 @@ Alchemy.decorateMethod(Blast.Decorators.memoize({max_age: 500, ignore_callbacks:
835
904
  /**
836
905
  * Get a route
837
906
  *
838
- * @author Jelle De Loecker <jelle@develry.be>
907
+ * @author Jelle De Loecker <jelle@elevenways.be>
839
908
  * @since 1.1.0
840
- * @version 1.1.0
909
+ * @version 1.3.0
841
910
  *
842
911
  * @param {String} href The href or route name
843
912
  * @param {Object} parameters Route parameters
844
913
  *
845
914
  * @return {String}
846
915
  */
847
- Alchemy.setMethod(function routeUrl(href, parameters) {
848
- let temp = hawkejs.scene.helpers.Router.routeUrl(href, parameters);
916
+ Alchemy.setMethod(function routeUrl(href, parameters, options) {
917
+ let temp = hawkejs.scene.helpers.Router.routeUrl(href, parameters, options);
849
918
 
850
919
  if (temp) {
851
920
  temp = String(temp);
@@ -294,19 +294,22 @@ ClientBase.setMethod(function getModel(name, init, options) {
294
294
  *
295
295
  * @author Jelle De Loecker <jelle@elevenways.be>
296
296
  * @since 1.1.1
297
- * @version 1.1.1
297
+ * @version 1.3.0
298
298
  *
299
299
  * @param {String} name The name of the event to emit
300
300
  * @param {Array} args The parameters to pass to the event
301
301
  * @param {Function} next The optional callback
302
+ *
303
+ * @return {Pledge}
302
304
  */
303
305
  ClientBase.setMethod(function issueEvent(name, args, next) {
304
306
 
305
307
  let that = this,
306
308
  method_name,
307
- promise;
309
+ promise,
310
+ pledge = new Pledge();
308
311
 
309
- if (typeof args == 'function') {
312
+ if (typeof args == 'function' || !Array.isArray(args)) {
310
313
  next = args;
311
314
  args = [];
312
315
  }
@@ -326,20 +329,23 @@ ClientBase.setMethod(function issueEvent(name, args, next) {
326
329
  Pledge.done(promise, function doneEventPromise(err, result) {
327
330
 
328
331
  if (err) {
332
+ pledge.reject(err);
329
333
  return next(err);
330
334
  }
331
335
 
332
- if (result === false) {
333
- return;
334
- }
336
+ if (result !== false) {
337
+ let event_args = args.slice(0);
335
338
 
336
- let event_args = args.slice(0);
339
+ event_args.unshift(name);
340
+ event_args.push(next);
337
341
 
338
- event_args.unshift(name);
339
- event_args.push(next);
342
+ that.emit.apply(that, event_args);
343
+ }
340
344
 
341
- that.emit.apply(that, event_args);
345
+ pledge.resolve(result);
342
346
  });
347
+
348
+ return pledge;
343
349
  });
344
350
 
345
351
  if (!Blast.isBrowser) {