alchemymvc 1.2.6 → 1.2.7

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.
@@ -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.1.7
81
+ * @version 1.2.7
82
82
  *
83
83
  * @param {Object} options
84
84
  * @param {Function} callback
@@ -136,8 +136,7 @@ LoopConduit.setMethod(function setOptions(options, callback) {
136
136
 
137
137
  // @WARNING: It's best to just generate the URL
138
138
  // and let it parse all the information that way
139
- options.href = alchemy.routeUrl(options.name, options.params, {extra_get_parameters: false});
140
- // @TODO: alchemy.routeUrl will ignore the `keep_get_parameters` option
139
+ options.href = this.routeUrl(options.name, options.params, {extra_get_parameters: false});
141
140
  }
142
141
 
143
142
  if (!this.method) {
@@ -1,13 +1,11 @@
1
1
  /**
2
- * The custom time-ago element
2
+ * The custom al-time-ago element
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@develry.be>
5
5
  * @since 0.1.0
6
6
  * @version 0.1.0
7
7
  */
8
- var TimeAgo = Function.inherits('Alchemy.Element', function TimeAgo() {
9
- return TimeAgo.super.call(this);
10
- });
8
+ const TimeAgo = Function.inherits('Alchemy.Element', 'TimeAgo');
11
9
 
12
10
  /**
13
11
  * Set the time
@@ -28,9 +28,9 @@ Alchemy.setStatic(function onScene(scene, options) {
28
28
  /**
29
29
  * Perform a resource request
30
30
  *
31
- * @author Jelle De Loecker <jelle@develry.be>
31
+ * @author Jelle De Loecker <jelle@elevenways.be>
32
32
  * @since 0.0.1
33
- * @version 1.1.3
33
+ * @version 1.2.7
34
34
  *
35
35
  * @param {String|Object} options
36
36
  * @param {Object} data
@@ -40,11 +40,7 @@ Alchemy.setStatic(function onScene(scene, options) {
40
40
  */
41
41
  Alchemy.setMethod(function getResource(options, data, callback) {
42
42
 
43
- var that = this,
44
- conduit,
45
- config,
46
- pledge,
47
- url;
43
+ const that = this;
48
44
 
49
45
  if (typeof options == 'string') {
50
46
 
@@ -70,6 +66,8 @@ Alchemy.setMethod(function getResource(options, data, callback) {
70
66
  options.params = data;
71
67
  }
72
68
 
69
+ let pledge;
70
+
73
71
  if (!options.name && !options.href) {
74
72
  pledge = Classes.Pledge.reject(new Error('Unable to get alchemy resource, a `name` or `href` option is required'));
75
73
  pledge.done(callback);
@@ -86,7 +84,7 @@ Alchemy.setMethod(function getResource(options, data, callback) {
86
84
  }
87
85
 
88
86
  if (Blast.isNode) {
89
- conduit = this.view.server_var('conduit');
87
+ const conduit = this.view.server_var('conduit');
90
88
 
91
89
  if (!conduit) {
92
90
  pledge = Classes.Pledge.reject(new Error('Could not find conduit, alchemy resource will not be fetched'));
@@ -102,12 +100,15 @@ Alchemy.setMethod(function getResource(options, data, callback) {
102
100
  return pledge;
103
101
  }
104
102
 
103
+ let method = 'get',
104
+ url;
105
+
105
106
  if (options.href) {
106
107
  url = options.href;
107
108
  } else {
108
109
 
109
110
  // See if this is a socket route
110
- config = this.view.helpers.Router.routeConfig(options.name, true);
111
+ let config = this.view.helpers.Router.routeConfig(options.name, true);
111
112
 
112
113
  if (config && config.socket_route) {
113
114
  pledge = new Classes.Pledge();
@@ -119,10 +120,18 @@ Alchemy.setMethod(function getResource(options, data, callback) {
119
120
  return pledge;
120
121
  }
121
122
 
123
+ config = this.view.helpers.Router.routeConfig(options.name);
124
+
125
+ if (config && config.methods) {
126
+ if (config.methods.indexOf('get') == -1) {
127
+ method = config.methods[0];
128
+ }
129
+ }
130
+
122
131
  // Get the url to the resource
123
132
  url = hawkejs.scene.helpers.Router.routeUrl(options.name, options.params);
124
133
 
125
- if (!url && typeof name == 'string') {
134
+ if (!url && typeof options.name == 'string') {
126
135
  url = hawkejs.scene.helpers.Router.routeUrl('APIResource', {name: options.name});
127
136
  }
128
137
 
@@ -136,7 +145,9 @@ Alchemy.setMethod(function getResource(options, data, callback) {
136
145
  options.href = url;
137
146
 
138
147
  if (data) {
139
- options.get = data;
148
+ options[method] = data;
149
+ } else if (method != 'get') {
150
+ options[method] = true;
140
151
  }
141
152
 
142
153
  pledge = hawkejs.scene.fetch(options);
@@ -51,7 +51,7 @@ Router.setProperty(function current_url() {
51
51
  *
52
52
  * @author Jelle De Loecker <jelle@develry.be>
53
53
  * @since 1.1.0
54
- * @version 1.1.5
54
+ * @version 1.2.7
55
55
  *
56
56
  * @param {Element} element The element to apply to
57
57
  * @param {String} name The route name
@@ -69,7 +69,6 @@ Router.setMethod(function applyDirective(element, name, options) {
69
69
  }
70
70
 
71
71
  let attribute_name,
72
- param_source,
73
72
  params = {},
74
73
  url;
75
74
 
@@ -77,6 +76,10 @@ Router.setMethod(function applyDirective(element, name, options) {
77
76
  Object.assign(params, element.parameters);
78
77
  }
79
78
 
79
+ if (options.parameters) {
80
+ Object.assign(params, options.parameters);
81
+ }
82
+
80
83
  if (config.keys && config.keys.length) {
81
84
 
82
85
  let key,
@@ -99,12 +102,13 @@ Router.setMethod(function applyDirective(element, name, options) {
99
102
  }
100
103
 
101
104
  if (config.breadcrumb) {
102
- element.setAttribute('data-breadcrumb', this.getTrail(name, params));
105
+ let link_breadcrumb = this.getTrail(name, params);
106
+ element.setAttribute('data-breadcrumb', link_breadcrumb);
103
107
 
104
- if (this.renderer) {
108
+ if (this.renderer && link_breadcrumb) {
105
109
  let page_breadcrumb = this.renderer.internal('breadcrumb');
106
110
 
107
- if (page_breadcrumb) {
111
+ if (page_breadcrumb && page_breadcrumb.startsWith(link_breadcrumb)) {
108
112
 
109
113
  if (element.parentElement) {
110
114
  // @TODO: We need to make sure the options (classnames & such)
@@ -112,7 +116,13 @@ Router.setMethod(function applyDirective(element, name, options) {
112
116
  this.renderer.ensureElementOptions(element.parentElement);
113
117
  }
114
118
 
115
- alchemy.markLinkElement(element, 1);
119
+ let level = 2;
120
+
121
+ if (page_breadcrumb == link_breadcrumb) {
122
+ level = 1;
123
+ }
124
+
125
+ alchemy.markLinkElement(element, level);
116
126
  }
117
127
  }
118
128
  }
@@ -167,7 +177,7 @@ Router.setMethod(function applyDirective(element, name, options) {
167
177
  *
168
178
  * @author Jelle De Loecker <jelle@develry.be>
169
179
  * @since 0.2.0
170
- * @version 1.1.0
180
+ * @version 1.2.7
171
181
  *
172
182
  * @param {String} name
173
183
  * @param {Boolean} socket_route Look in the socket routes
@@ -244,6 +254,9 @@ Router.setMethod(function routeConfig(name, socket_route) {
244
254
  result.breadcrumb = route.breadcrumb;
245
255
  result.has_breadcrumb_assignments = route.has_breadcrumb_assignments;
246
256
  result.keys = route.keys;
257
+ result.methods = route.methods;
258
+ result.permission = route.permission;
259
+ result.has_permission_assignments = route.has_permission_assignments;
247
260
  }
248
261
 
249
262
  let router_options;
@@ -659,13 +659,18 @@ Criteria.setMethod(function augment(type) {
659
659
  *
660
660
  * @author Jelle De Loecker <jelle@develry.be>
661
661
  * @since 1.1.0
662
- * @version 1.1.0
662
+ * @version 1.2.7
663
663
  *
664
664
  * @param {Number} amount
665
665
  *
666
666
  * @return {Criteria}
667
667
  */
668
668
  Criteria.setMethod(function limit(amount) {
669
+
670
+ if (typeof amount != 'number') {
671
+ amount = parseInt(amount);
672
+ }
673
+
669
674
  this.options.limit = amount;
670
675
  return this;
671
676
  });
@@ -675,13 +680,18 @@ Criteria.setMethod(function limit(amount) {
675
680
  *
676
681
  * @author Jelle De Loecker <jelle@develry.be>
677
682
  * @since 1.1.0
678
- * @version 1.1.0
683
+ * @version 1.2.7
679
684
  *
680
685
  * @param {Number} amount
681
686
  *
682
687
  * @return {Criteria}
683
688
  */
684
689
  Criteria.setMethod(function skip(amount) {
690
+
691
+ if (typeof amount != 'number') {
692
+ amount = parseInt(amount);
693
+ }
694
+
685
695
  this.options.skip = amount;
686
696
  return this;
687
697
  });
@@ -691,7 +701,7 @@ Criteria.setMethod(function skip(amount) {
691
701
  *
692
702
  * @author Jelle De Loecker <jelle@develry.be>
693
703
  * @since 1.1.3
694
- * @version 1.1.3
704
+ * @version 1.2.7
695
705
  *
696
706
  * @param {Number} page A 1-indexed page number
697
707
  * @param {Number} page_size
@@ -700,6 +710,14 @@ Criteria.setMethod(function skip(amount) {
700
710
  */
701
711
  Criteria.setMethod(function page(page, page_size) {
702
712
 
713
+ if (typeof page != 'number') {
714
+ page = parseInt(page);
715
+ }
716
+
717
+ if (page_size && typeof page_size != 'number') {
718
+ page_size = parseInt(page_size);
719
+ }
720
+
703
721
  if (!page) {
704
722
  throw new Error('A page number is required');
705
723
  }
@@ -161,7 +161,7 @@ FieldConfig.setMethod(function getModel() {
161
161
  *
162
162
  * @author Jelle De Loecker <jelle@elevenways.be>
163
163
  * @since 1.2.2
164
- * @version 1.2.5
164
+ * @version 1.2.7
165
165
  *
166
166
  * @return {Field}
167
167
  */
@@ -178,7 +178,7 @@ FieldConfig.setMethod(function getFieldDefinition() {
178
178
  let constructor = Classes.Alchemy.Field.Field.getMember(this.options.type);
179
179
 
180
180
  if (constructor) {
181
- result = new constructor(null, this.name);
181
+ result = new constructor(null, this.name, this.options);
182
182
  }
183
183
  }
184
184
 
@@ -188,7 +188,7 @@ Model.prepareStaticProperty('Document', function getDocumentClass() {
188
188
  *
189
189
  * @author Jelle De Loecker <jelle@develry.be>
190
190
  * @since 1.0.0
191
- * @version 1.1.3
191
+ * @version 1.2.7
192
192
  *
193
193
  * @param {String} model_name
194
194
  * @param {Boolean} allow_create
@@ -237,8 +237,9 @@ Model.setStatic(function getClass(model_name, allow_create ) {
237
237
 
238
238
  if (config && config.super) {
239
239
  config = {
240
- parent : config.super.name,
241
- primary_key : config.prototype.primary_key
240
+ parent : config.super.name,
241
+ primary_key : config.prototype.primary_key,
242
+ display_field : config.prototype.display_field,
242
243
  };
243
244
  }
244
245
  }
@@ -252,8 +253,14 @@ Model.setStatic(function getClass(model_name, allow_create ) {
252
253
 
253
254
  ModelClass.setProperty('$model_name', model_name);
254
255
 
255
- if (config && config.primary_key) {
256
- ModelClass.setProperty('primary_key', config.primary_key);
256
+ if (config) {
257
+ if (config.primary_key) {
258
+ ModelClass.setProperty('primary_key', config.primary_key);
259
+ }
260
+
261
+ if (config.display_field) {
262
+ ModelClass.setProperty('display_field', config.display_field);
263
+ }
257
264
  }
258
265
 
259
266
  if (Blast.isBrowser) {
@@ -414,6 +421,66 @@ Model.setMethod(function getFindOptions(options) {
414
421
  return Object.assign({}, def_options, options);
415
422
  });
416
423
 
424
+
425
+ /**
426
+ * Get the title to display for this record
427
+ *
428
+ * @author Jelle De Loecker <jelle@elevenways.be>
429
+ * @since 0.0.1
430
+ * @version 1.2.7
431
+ *
432
+ * @param {Object} item The record item of this model
433
+ * @param {String|Array} fallbacks Extra fallbacks to use
434
+ *
435
+ * @return {String} The display title to use
436
+ */
437
+ Model.setMethod(function getDisplayTitle(item, fallbacks) {
438
+
439
+ if (!item) {
440
+ return 'Undefined item';
441
+ }
442
+
443
+ let main;
444
+
445
+ if (item[this.modelName]) {
446
+ main = item[this.modelName];
447
+ } else {
448
+ main = item;
449
+ }
450
+
451
+ if (!main) {
452
+ return 'Undefined item';
453
+ }
454
+
455
+ let field,
456
+ val,
457
+ i;
458
+
459
+ let fields = Array.cast(this.display_field);
460
+
461
+ if (fallbacks) {
462
+ fields = fields.concat(fallbacks);
463
+ }
464
+
465
+ for (i = 0; i < fields.length; i++) {
466
+ val = main[fields[i]];
467
+
468
+ if (Object.isObject(val)) {
469
+ field = this.getField(fields[i]);
470
+
471
+ if (field && field.isTranslatable) {
472
+ val = alchemy.pickTranslation(this.conduit, val).result;
473
+ }
474
+ }
475
+
476
+ if (val && typeof val == 'string') {
477
+ return val;
478
+ }
479
+ }
480
+
481
+ return main[this.primary_key] || '';
482
+ });
483
+
417
484
  /**
418
485
  * Return association configuration
419
486
  *
@@ -2277,6 +2277,23 @@ Conduit.setMethod(function session(name, value) {
2277
2277
  this.sessionData[name] = value;
2278
2278
  });
2279
2279
 
2280
+ /**
2281
+ * Get a route URL
2282
+ *
2283
+ * @author Jelle De Loecker <jelle@elevenways.be>
2284
+ * @since 1.2.7
2285
+ * @version 1.2.7
2286
+ *
2287
+ * @param {String} name
2288
+ * @param {Object} parameters
2289
+ * @param {Object} options
2290
+ *
2291
+ * @return {String}
2292
+ */
2293
+ Conduit.setMethod(function routeUrl(name, parameters, options) {
2294
+ return this.renderer.helpers.Router.routeUrl(name, parameters, options);
2295
+ });
2296
+
2280
2297
  /**
2281
2298
  * Get a parameter from the route
2282
2299
  *
@@ -300,7 +300,7 @@ Document.setProperty(function $model() {
300
300
  *
301
301
  * @author Jelle De Loecker <jelle@develry.be>
302
302
  * @since 0.3.3
303
- * @version 1.0.3
303
+ * @version 1.2.7
304
304
  */
305
305
  Document.setProperty(function conduit() {
306
306
  if (this.$conduit) {
@@ -314,6 +314,10 @@ Document.setProperty(function conduit() {
314
314
  }
315
315
  }, function setConduit(conduit) {
316
316
  this.$conduit = conduit;
317
+
318
+ if (this.$options.model) {
319
+ this.$options.model.conduit = conduit;
320
+ }
317
321
  });
318
322
 
319
323
  /**
@@ -384,9 +388,9 @@ Document.setMethod(function toDry() {
384
388
  /**
385
389
  * Simplify the object for Hawkejs
386
390
  *
387
- * @author Jelle De Loecker <jelle@develry.be>
391
+ * @author Jelle De Loecker <jelle@elevenways.be>
388
392
  * @since 0.2.0
389
- * @version 1.2.4
393
+ * @version 1.2.7
390
394
  *
391
395
  * @param {WeakMap} wm
392
396
  *
@@ -410,7 +414,7 @@ Document.setMethod(function toHawkejs(wm) {
410
414
  record = JSON.clone(this.$record, 'toHawkejs', wm);
411
415
 
412
416
  // Sometimes we get an EMPTY $record value,
413
- // this is probably because it's already in the process of being clones
417
+ // this is probably because it's already in the process of being cloned
414
418
  if (!Object.isEmpty(record)) {
415
419
  // Get clean options
416
420
  let options = JSON.clone(this.getCleanOptions(), 'toHawkejs', wm);
@@ -425,6 +429,16 @@ Document.setMethod(function toHawkejs(wm) {
425
429
  result.$_hold = JSON.clone(this.$_hold, 'toHawkejs', wm);
426
430
  }
427
431
 
432
+ const conduit = this.conduit;
433
+
434
+ if (conduit) {
435
+ result.conduit = conduit;
436
+
437
+ if (result.$options?.model) {
438
+ result.$options.model.conduit = conduit;
439
+ }
440
+ }
441
+
428
442
  return result;
429
443
  });
430
444
 
@@ -39,29 +39,53 @@ File.setProperty('is_file', true);
39
39
  *
40
40
  * @author Jelle De Loecker <jelle@develry.be>
41
41
  * @since 1.1.0
42
- * @version 1.2.0
42
+ * @version 1.2.7
43
43
  */
44
44
  File.setStatic(function from(obj) {
45
45
 
46
- let path;
46
+ if (!obj) {
47
+ return;
48
+ }
49
+
50
+ let path,
51
+ name,
52
+ type,
53
+ hash,
54
+ size;
47
55
 
48
56
  if (typeof obj == 'string') {
49
57
  path = obj;
50
- obj = null;
58
+ } else if (obj.originalFilename) {
59
+ // Formidable 2.0
60
+ path = obj.filepath;
61
+ name = obj.originalFilename;
62
+ type = obj.mimetype;
63
+ hash = obj.hash;
64
+ size = obj.size;
51
65
  } else {
52
66
  path = obj.path;
67
+ name = obj.name;
68
+ type = obj.type;
69
+ hash = obj.hash;
70
+ size = obj.size;
53
71
  }
54
72
 
55
73
  let file = new File(path);
56
74
 
57
- if (obj) {
58
- file.stat = {
59
- size : obj.size
60
- };
75
+ if (size != null) {
76
+ file.stat = {size};
77
+ }
78
+
79
+ if (name != null) {
80
+ file.name = name;
81
+ }
82
+
83
+ if (type != null) {
84
+ file.type = type;
85
+ }
61
86
 
62
- file.name = obj.name;
63
- file.type = obj.type;
64
- file.hash = obj.hash;
87
+ if (hash != null) {
88
+ file.hash = hash;
65
89
  }
66
90
 
67
91
  return file;
@@ -201,6 +201,7 @@ Model.staticCompose('schema', function createSchema(doNext) {
201
201
 
202
202
  Model.setDeprecatedProperty('modelName', 'model_name');
203
203
  Model.setDeprecatedProperty('blueprint', 'schema');
204
+ Model.setDeprecatedProperty('displayField', 'display_field');
204
205
 
205
206
  /**
206
207
  * The default database config to use
@@ -214,7 +215,7 @@ Model.setProperty('dbConfig', 'default');
214
215
  *
215
216
  * @type {String}
216
217
  */
217
- Model.setProperty('displayField', 'title');
218
+ Model.setProperty('display_field', 'title');
218
219
 
219
220
  /**
220
221
  * Translate is on by default
@@ -506,16 +507,17 @@ Model.setStatic(function getField(name) {
506
507
  * Get the model's public configuration
507
508
  * (This is used to create the client-side Model instances)
508
509
  *
509
- * @author Jelle De Loecker <jelle@develry.be>
510
+ * @author Jelle De Loecker <jelle@elevenways.be>
510
511
  * @since 1.0.0
511
- * @version 1.1.0
512
+ * @version 1.2.7
512
513
  */
513
514
  Model.setStatic(function getClientConfig() {
514
515
 
515
- var result = {
516
- name : this.model_name,
517
- schema : this.schema,
518
- primary_key : this.prototype.primary_key
516
+ const result = {
517
+ name : this.model_name,
518
+ schema : this.schema,
519
+ primary_key : this.prototype.primary_key,
520
+ display_field : this.prototype.display_field,
519
521
  };
520
522
 
521
523
  if (this.super.name != 'Model') {
@@ -641,7 +643,7 @@ Model.setMethod(function aggregate(pipeline, callback) {
641
643
  *
642
644
  * @author Jelle De Loecker <jelle@develry.be>
643
645
  * @since 0.2.0
644
- * @version 1.2.6
646
+ * @version 1.2.7
645
647
  *
646
648
  * @param {Array} items
647
649
  * @param {Object} options Optional options object
@@ -686,9 +688,15 @@ Model.setMethod(function translateItems(items, options, callback) {
686
688
  // Get the (optional) attached conduit
687
689
  let conduit = this.conduit;
688
690
 
691
+ // Should we use fallback translations?
692
+ const allow_fallbacks = options.allow_fallback_translations ?? alchemy.settings.allow_fallback_translations ?? false;
693
+
694
+ let use_predefined_prefixes = false;
695
+
689
696
  // If prefixes are given as an option, only use those
690
697
  if (options.prefixes) {
691
698
  prefix = options.prefixes;
699
+ use_predefined_prefixes = true;
692
700
  } else {
693
701
  // Possible prefixes
694
702
  prefix = [];
@@ -698,14 +706,22 @@ Model.setMethod(function translateItems(items, options, callback) {
698
706
  prefix.include(options.locale);
699
707
  }
700
708
 
701
- // Append the visited prefix after that (if there is one)
702
- if (conduit && conduit.prefix) {
703
- prefix.include(conduit.prefix);
704
- }
709
+ if (conduit) {
710
+
711
+ // Append the visited prefix after that (if there is one)
712
+ if (conduit.prefix) {
713
+ prefix.include(conduit.prefix);
714
+ }
715
+
716
+ // Add the active prefix
717
+ if (conduit.active_prefix) {
718
+ prefix.include(conduit.active_prefix);
719
+ }
705
720
 
706
- // Append all the allowed locales after that
707
- if (conduit && conduit.locales) {
708
- prefix.include(conduit.locales);
721
+ // Append all the allowed locales after that
722
+ if (conduit.locales) {
723
+ prefix.include(conduit.locales);
724
+ }
709
725
  }
710
726
 
711
727
  // Add all available prefixes last
@@ -718,8 +734,15 @@ Model.setMethod(function translateItems(items, options, callback) {
718
734
 
719
735
  // @DEPRECATED: empty keys should no longer be allowed
720
736
  prefix.push('');
737
+
738
+ if (!allow_fallbacks) {
739
+ prefix = [prefix[0]];
740
+ }
721
741
  }
722
742
 
743
+ // Deduplicate the prefixes
744
+ prefix = Array.from(new Set(prefix));
745
+
723
746
  for (i = 0; i < items.length; i++) {
724
747
  item = items[i];
725
748
 
@@ -743,6 +766,10 @@ Model.setMethod(function translateItems(items, options, callback) {
743
766
  prefixes.unshift(items.item_prefixes[i]);
744
767
  }
745
768
 
769
+ if (!allow_fallbacks && !use_predefined_prefixes) {
770
+ prefixes = [prefixes[0]];
771
+ }
772
+
746
773
  let field;
747
774
 
748
775
  for (j = 0; j < collection.length; j++) {
@@ -1147,65 +1174,6 @@ Model.setMethod(function processDatasourceFormat(ds_data, options, callback) {
1147
1174
  return pledge;
1148
1175
  });
1149
1176
 
1150
- /**
1151
- * Get the title to display for this record
1152
- *
1153
- * @author Jelle De Loecker <jelle@develry.be>
1154
- * @since 0.0.1
1155
- * @version 0.1.0
1156
- *
1157
- * @param {Object} item The record item of this model
1158
- * @param {String|Array} fallbacks Extra fallbacks to use
1159
- *
1160
- * @return {String} The display title to use
1161
- */
1162
- Model.setMethod(function getDisplayTitle(item, fallbacks) {
1163
-
1164
- var fields,
1165
- field,
1166
- main,
1167
- val,
1168
- i;
1169
-
1170
- if (!item) {
1171
- return 'Undefined item';
1172
- }
1173
-
1174
- if (item[this.modelName]) {
1175
- main = item[this.modelName];
1176
- } else {
1177
- main = item;
1178
- }
1179
-
1180
- if (!main) {
1181
- return 'Undefined item';
1182
- }
1183
-
1184
- fields = Array.cast(this.displayField);
1185
-
1186
- if (fallbacks) {
1187
- fields = fields.concat(fallbacks);
1188
- }
1189
-
1190
- for (i = 0; i < fields.length; i++) {
1191
- val = main[fields[i]];
1192
-
1193
- if (Object.isObject(val)) {
1194
- field = this.getField(fields[i]);
1195
-
1196
- if (field && field.isTranslatable) {
1197
- val = alchemy.pickTranslation(this.conduit, val).result;
1198
- }
1199
- }
1200
-
1201
- if (val && typeof val == 'string') {
1202
- return val;
1203
- }
1204
- }
1205
-
1206
- return main[this.primary_key] || '';
1207
- });
1208
-
1209
1177
  /**
1210
1178
  * Clear the cache of this and all associated models
1211
1179
  *
@@ -1145,9 +1145,9 @@ RouterClass.setMethod(function getOptions(result) {
1145
1145
  /**
1146
1146
  * Get an object of all the routes in this router and its children
1147
1147
  *
1148
- * @author Jelle De Loecker <jelle@develry.be>
1148
+ * @author Jelle De Loecker <jelle@elevenways.be>
1149
1149
  * @since 0.2.0
1150
- * @version 1.1.0
1150
+ * @version 1.2.7
1151
1151
  *
1152
1152
  * @param {Object} result Optional object to store sectioned results in
1153
1153
  *
@@ -1190,7 +1190,10 @@ RouterClass.setMethod(function getRoutes(result) {
1190
1190
  controller : route.controller,
1191
1191
  action : route.action,
1192
1192
  keys : route.keys,
1193
- has_breadcrumb_assignments : route.has_breadcrumb_assignments
1193
+ has_breadcrumb_assignments : route.has_breadcrumb_assignments || undefined,
1194
+ methods : route.methods,
1195
+ permission : route.permission || undefined,
1196
+ has_permission_assignments : route.has_permission_assignments || undefined,
1194
1197
  };
1195
1198
  }
1196
1199
 
package/lib/core/base.js CHANGED
@@ -179,24 +179,6 @@ Base.setStatic(function createClassTitle(suggested_root) {
179
179
  return suggested_root.titleize();
180
180
  });
181
181
 
182
- /**
183
- * Make this an abstract class
184
- *
185
- * @author Jelle De Loecker <jelle@develry.be>
186
- * @since 1.0.0
187
- * @version 1.0.0
188
- *
189
- * @param {Boolean} value
190
- */
191
- Base.setStatic(function makeAbstractClass(value) {
192
-
193
- if (value == null) {
194
- value = true;
195
- }
196
-
197
- this.setStatic('is_abstract_class', value, false);
198
- });
199
-
200
182
  /**
201
183
  * Make this start a new group
202
184
  *
@@ -403,8 +403,6 @@ Alchemy.setMethod(function checksum(value) {
403
403
  */
404
404
  Alchemy.setMethod(function markLinkElement(element, active_nr) {
405
405
 
406
- var mark_wrapper;
407
-
408
406
  // Always remove the current classes
409
407
  element.classList.remove('active-link');
410
408
  element.classList.remove('active-sublink');
@@ -424,6 +422,30 @@ Alchemy.setMethod(function markLinkElement(element, active_nr) {
424
422
  }
425
423
  });
426
424
 
425
+ /**
426
+ * Get the configuration for the given route
427
+ *
428
+ * @author Jelle De Loecker <jelle@elevenways.be>
429
+ * @since 1.2.7
430
+ * @version 1.2.7
431
+ *
432
+ * @param {String} name The route name
433
+ *
434
+ * @return {Object}
435
+ */
436
+ Alchemy.setMethod(function routeConfig(name) {
437
+
438
+ let result;
439
+
440
+ if (Blast.isNode) {
441
+ result = Classes.Alchemy.Helper.Router.prototype.routeConfig.call({}, name);
442
+ } else {
443
+ result = hawkejs.scene.general_renderer.helpers.Router.routeConfig(name);
444
+ }
445
+
446
+ return result;
447
+ });
448
+
427
449
  const markLinkElement = Alchemy.prototype.markLinkElement;
428
450
 
429
451
  // From here on, only client-side code is added
@@ -1112,4 +1134,28 @@ Alchemy.setMethod(function createSchema(parent) {
1112
1134
  */
1113
1135
  Alchemy.setMethod(function getClientModel(name, init, options) {
1114
1136
  return Classes.Alchemy.Client.Base.prototype.getModel.call(this, name, init, options);
1137
+ });
1138
+
1139
+ /**
1140
+ * Check if the current client-side user has a certain permission
1141
+ *
1142
+ * @author Jelle De Loecker <jelle@elevenways.be>
1143
+ * @since 1.2.7
1144
+ * @version 1.2.7
1145
+ *
1146
+ * @param {String} permission
1147
+ *
1148
+ * @return {Boolean}
1149
+ */
1150
+ Alchemy.setMethod(function hasPermission(permission) {
1151
+
1152
+ let user_data = hawkejs.scene.exposed['acl-user-data'];
1153
+
1154
+ if (user_data && user_data.permissions) {
1155
+ if (user_data.permissions.hasPermission?.(permission)) {
1156
+ return true;
1157
+ }
1158
+ }
1159
+
1160
+ return false;
1115
1161
  });
@@ -128,7 +128,7 @@ function getMiddlePaths(paths, ext, new_ext) {
128
128
  *
129
129
  * @author Jelle De Loecker <jelle@develry.be>
130
130
  * @since 0.2.0
131
- * @version 0.5.0
131
+ * @version 1.2.7
132
132
  */
133
133
  Alchemy.setMethod(function styleMiddleware(req, res, nextMiddleware) {
134
134
 
@@ -243,7 +243,7 @@ Alchemy.setMethod(function styleMiddleware(req, res, nextMiddleware) {
243
243
  }
244
244
 
245
245
  if (!compiled || !compiled.path) {
246
- return req.conduit.notFound();
246
+ return nextMiddleware();
247
247
  }
248
248
 
249
249
  return req.conduit.serveFile(compiled.path, {onError: function onError(err) {
@@ -306,7 +306,7 @@ Alchemy.setMethod(function sourcemapMiddleware(req, res, nextMiddleware) {
306
306
  *
307
307
  * @author Jelle De Loecker <jelle@develry.be>
308
308
  * @since 0.2.0
309
- * @version 1.0.4
309
+ * @version 1.2.7
310
310
  *
311
311
  * @param {String} path
312
312
  * @param {Object} options
@@ -414,6 +414,7 @@ Alchemy.setMethod(function minifyScript(path, options, callback) {
414
414
  compress: {
415
415
  keep_fargs : true,
416
416
  keep_fnames : true,
417
+ keep_classnames : true,
417
418
  hoist_funs : false,
418
419
  drop_console : !alchemy.settings.debug,
419
420
  dead_code : true,
@@ -423,7 +424,8 @@ Alchemy.setMethod(function minifyScript(path, options, callback) {
423
424
  }
424
425
  },
425
426
  mangle: {
426
- keep_fnames : true
427
+ keep_fnames : true,
428
+ keep_classnames : true,
427
429
  }
428
430
  };
429
431
 
@@ -216,7 +216,7 @@ Alchemy.setProperty(function environment() {
216
216
  *
217
217
  * @author Jelle De Loecker <jelle@develry.be>
218
218
  * @since 0.5.0
219
- * @version 1.1.0
219
+ * @version 1.2.7
220
220
  *
221
221
  * @param {Object} options
222
222
  */
@@ -257,6 +257,8 @@ Alchemy.setMethod(function startJaneway(options) {
257
257
  // Don't mess with the indentation
258
258
  options.change_indent = false;
259
259
 
260
+ options.extra_output = out;
261
+
260
262
  this.on('janeway_propose_geometry', function onProposeGeometry(data) {
261
263
  out.columns = data.cols || data.width;
262
264
  out.rows = data.rows || data.height;
@@ -268,7 +270,7 @@ Alchemy.setMethod(function startJaneway(options) {
268
270
  });
269
271
 
270
272
  this.on('janeway_redraw', function onRedrawRequest() {
271
- that.Janeway.redraw();
273
+ that.Janeway.reloadScreen();
272
274
  });
273
275
  }
274
276
 
@@ -877,7 +879,7 @@ Alchemy.setMethod(function searchModule(startPath, moduleName, recurse) {
877
879
  *
878
880
  * @author Jelle De Loecker <jelle@develry.be>
879
881
  * @since 0.0.1
880
- * @version 1.2.2
882
+ * @version 1.2.7
881
883
  *
882
884
  * @param {String} moduleName
883
885
  * @param {Object} options
@@ -953,24 +955,47 @@ Alchemy.setMethod(function findModule(moduleName, options) {
953
955
  internal = false;
954
956
 
955
957
  let last_piece = moduleName.split(libpath.sep).last(),
956
- package_path;
958
+ package_path,
959
+ package_base;
957
960
 
958
961
  let module_dir = libpath.dirname(module_path);
959
962
  result.module_dir = module_dir;
960
963
 
961
964
  // If the path doesn't end with the module name, look for it
962
965
  if (!module_path.endsWith(last_piece)) {
963
- package_path = module_path.beforeLast(last_piece) + last_piece;
964
- package_path = libpath.resolve(package_path, 'package.json');
966
+ package_base = (module_path.beforeLast(last_piece) + last_piece).split(libpath.sep);
965
967
  } else {
966
- package_path = libpath.resolve(module_dir, 'package.json');
967
- }
968
+ package_base = module_dir.split(libpath.sep);
969
+ }
970
+
971
+ //package_path = libpath.resolve(package_path, 'package.json');
972
+
973
+ while (package_base.length) {
974
+
975
+ package_path = libpath.resolve(package_base.join(libpath.sep), 'package.json');
976
+
977
+ try {
978
+ package_json = require(package_path);
979
+ } catch (err) {
980
+ package_json = false;
981
+ }
968
982
 
969
- try {
970
- package_json = require(package_path);
971
- } catch (err) {
972
- package_json = false;
983
+ if (package_json) {
984
+ if (package_json.name != moduleName) {
985
+ package_json = false;
986
+ }
987
+
988
+ break;
989
+ }
990
+
991
+ package_base.pop();
973
992
  }
993
+
994
+ if (package_json) {
995
+ module_dir = package_base.join(libpath.sep);
996
+ result.module_dir = module_dir;
997
+ }
998
+
974
999
  } else {
975
1000
  internal = true;
976
1001
  package_json = {
@@ -1357,7 +1382,7 @@ Alchemy.setMethod(function addAppcacheEntry(entry) {
1357
1382
  *
1358
1383
  * @author Jelle De Loecker <jelle@develry.be>
1359
1384
  * @since 1.1.0
1360
- * @version 1.2.6
1385
+ * @version 1.2.7
1361
1386
  *
1362
1387
  * @param {IncomingMessage} req
1363
1388
  * @param {OutgoingMessage} res Optional
@@ -1383,10 +1408,10 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1383
1408
  // Multipart data is handled by "formidable"
1384
1409
  if (req.headers['content-type'] && req.headers['content-type'].startsWith('multipart/form-data')) {
1385
1410
 
1386
- let form = new formidable.IncomingForm({multiples: true});
1387
-
1388
- // md5 hash by default
1389
- form.hash = 'md5';
1411
+ let form = new formidable.IncomingForm({
1412
+ multiples : true,
1413
+ hashAlgorithm : 'md5',
1414
+ });
1390
1415
 
1391
1416
  form.parse(req, function parsedMultipart(err, form_fields, form_files) {
1392
1417
 
package/lib/stages.js CHANGED
@@ -120,11 +120,19 @@ alchemy.sputnik.add(function datasources() {
120
120
  *
121
121
  * @author Jelle De Loecker <jelle@develry.be>
122
122
  * @since 0.0.1
123
- * @version 1.1.0
123
+ * @version 1.2.7
124
124
  */
125
125
  alchemy.sputnik.add(function plugins() {
126
126
  // Load in the plugins
127
- alchemy.startPlugins();
127
+ try {
128
+ alchemy.startPlugins();
129
+ } catch (err) {
130
+ // Constitutors sometimes throw errors during this stage.
131
+ // Not sure yet why they don't get caught by sputnik
132
+ // @TODO: refactor!
133
+ log.error('Caught error during "plugins" stage:', err);
134
+ throw err;
135
+ }
128
136
  });
129
137
 
130
138
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.2.6",
4
+ "version": "1.2.7",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -22,7 +22,7 @@
22
22
  "chokidar" : "~3.5.3",
23
23
  "formidable" : "~2.0.1",
24
24
  "graceful-fs" : "~4.2.9",
25
- "hawkejs" : "~2.2.10",
25
+ "hawkejs" : "~2.2.18",
26
26
  "jsondiffpatch" : "~0.4.1",
27
27
  "mime" : "~3.0.0",
28
28
  "minimist" : "~1.2.5",
@@ -31,7 +31,7 @@
31
31
  "mongodb" : "~3.6.6",
32
32
  "ncp" : "~2.0.0",
33
33
  "postcss" : "~8.4.6",
34
- "protoblast" : "~0.7.21",
34
+ "protoblast" : "~0.7.24",
35
35
  "semver" : "~7.3.5",
36
36
  "socket.io" : "~2.4.0",
37
37
  "@11ways/socket.io-stream" : "~0.9.2",
@@ -45,7 +45,7 @@
45
45
  "index.js"
46
46
  ],
47
47
  "optionalDependencies": {
48
- "janeway" : "~0.3.5",
48
+ "janeway" : "~0.3.6",
49
49
  "less" : "~4.1.1",
50
50
  "sass" : "~1.53.0",
51
51
  "sass-embedded" : "~1.53.0",