alchemymvc 1.1.5 → 1.1.9

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.
@@ -1,5 +1,3 @@
1
- var slug = alchemy.use('mollusc');
2
-
3
1
  /**
4
2
  * The Sluggable Behaviour class
5
3
  *
@@ -293,7 +291,7 @@ Sluggable.setMethod(function createSlug(record, new_value, callback) {
293
291
  *
294
292
  * @author Jelle De Loecker <jelle@develry.be>
295
293
  * @since 0.1.0
296
- * @version 1.1.0
294
+ * @version 1.1.8
297
295
  *
298
296
  * @param {String} title
299
297
  * @param {String} path
@@ -336,7 +334,7 @@ Sluggable.setMethod(function generateSlug(title, key, record, callback) {
336
334
  return callback();
337
335
  }
338
336
 
339
- baseSlug = slug(title, {replacement: that.replacement || '-'}).toLowerCase();
337
+ baseSlug = title.slug(that.replacement || '-').toLowerCase();
340
338
  current = baseSlug;
341
339
  model = Model.get(this.model.name);
342
340
  count = 1;
@@ -1,193 +1,174 @@
1
- /**
2
- * The Http Conduit Class
3
- *
4
- * @author Jelle De Loecker <jelle@develry.be>
5
- * @since 0.2.0
6
- * @version 1.1.3
7
- *
8
- * @param {IncomingMessage} req
9
- * @param {ServerResponse} res
10
- * @param {Router} router
11
- */
12
- var HttpConduit = Function.inherits('Alchemy.Conduit.Conduit', function Http(req, res, router) {
13
-
14
- // Initialize basic conduit values
15
- Http.super.call(this);
16
-
17
- this.initHttp(req, res, router);
18
- });
19
-
20
- /**
21
- * Has the client aborted the request?
22
- *
23
- * @author Jelle De Loecker <jelle@develry.be>
24
- * @since 1.1.0
25
- * @version 1.1.0
26
- *
27
- * @type {Boolean}
28
- */
29
- HttpConduit.setProperty(function aborted() {
30
-
31
- if (this.request && this.request.aborted != null) {
32
- return this.request.aborted;
33
- }
34
-
35
- return false;
36
- });
37
-
38
- /**
39
- * Return the IP address
40
- *
41
- * @author Jelle De Loecker <jelle@develry.be>
42
- * @since 0.2.0
43
- * @version 1.1.0
44
- */
45
- HttpConduit.setProperty(function ip() {
46
-
47
- var req = this.request;
48
-
49
- if (!req) {
50
- return null;
51
- }
52
-
53
- let forwarded_for = req.headers['x-forwarded-for'] || req.headers['x-real-ip'];
54
-
55
- if (forwarded_for) {
56
-
57
- // Forwarded for can contain multiple ip addresses,
58
- // return the first one
59
- if (forwarded_for.indexOf(',') > -1) {
60
- forwarded_for = forwarded_for.before(',');
61
- }
62
-
63
- return forwarded_for;
64
- }
65
-
66
- let remote_address;
67
-
68
- if (req.connection) {
69
- remote_address = req.connection.remoteAddress;
70
- }
71
-
72
- if (!remote_address && req.socket) {
73
- remote_address = req.socket.remoteAddress;
74
- }
75
-
76
- if (!remote_address && req.connection && req.connection.socket) {
77
- remote_address = req.connection.socket.remoteAddress;
78
- }
79
-
80
- return remote_address;
81
- });
82
-
83
- /**
84
- * Get a simple fingerprint of the client
85
- * based on ip, accept-language & user-agent
86
- *
87
- * @author Jelle De Loecker <jelle@develry.be>
88
- * @since 1.1.0
89
- * @version 1.1.0
90
- *
91
- * @type {String}
92
- */
93
- HttpConduit.enforceProperty(function fingerprint() {
94
-
95
- let result,
96
- language = this.headers['accept-language'] || 'all',
97
- ua = this.headers['user-agent'] || 'unknown',
98
- ip = this.ip || '';
99
-
100
- result = Object.checksum(ip + '_' + language + '_' + ua);
101
-
102
- return result;
103
- });
104
-
105
- /**
106
- * Init
107
- *
108
- * @author Jelle De Loecker <jelle@kipdola.be>
109
- * @since 0.3.3
110
- * @version 1.1.0
111
- *
112
- * @param {IncomingMessage} req
113
- * @param {ServerResponse} res
114
- * @param {Router} router
115
- */
116
- HttpConduit.setMethod(async function initHttp(req, res, router) {
117
-
118
- if (req != null) {
119
- // Make conduit available in req
120
- req.conduit = this;
121
-
122
- // Basic HTTP objects
123
- this.request = req;
124
-
125
- // The HTTP request headers
126
- this.headers = req.headers;
127
-
128
- // Parse the original URL without host
129
- this.original_url = new RURL(req.url);
130
-
131
- // Is this an AJAX request?
132
- this.ajax = null;
133
- }
134
-
135
- if (res != null) {
136
- this.response = res;
137
- }
138
-
139
- if (router) {
140
- this.router = router;
141
- }
142
-
143
- // The HTTP status
144
- this.status = 200;
145
-
146
- this.debugMark('Parse request');
147
-
148
- // Parse the request, get the correct routes and such
149
- await this.parseRequest();
150
-
151
- if (this.halt_request) {
152
- return;
153
- }
154
-
155
- if (alchemy.settings.debug || alchemy.settings.environment != 'live') {
156
- this.setHeader('X-Robots-Tag', 'none');
157
- }
158
-
159
- this.debugMark(false);
160
-
161
- // Call the middleware, which will call the handler afterwards
162
- this.callMiddleware();
163
- });
164
-
165
- /**
166
- * Get the original url path
167
- *
168
- * @author Jelle De Loecker <jelle@kipdola.be>
169
- * @since 1.1.0
170
- * @version 1.1.0
171
- *
172
- * @type {String}
173
- */
174
- HttpConduit.setProperty(function original_path() {
175
- return this.original_url.path;
176
- }, function setPath(value) {
177
- return this.original_url.path = value;
178
- });
179
-
180
- /**
181
- * Get the original url pathname
182
- *
183
- * @author Jelle De Loecker <jelle@kipdola.be>
184
- * @since 1.1.0
185
- * @version 1.1.0
186
- *
187
- * @type {String}
188
- */
189
- HttpConduit.setProperty(function original_pathname() {
190
- return this.original_url.pathname;
191
- }, function setPathname(value) {
192
- return this.original_url.pathname = value;
1
+ /**
2
+ * The Http Conduit Class
3
+ *
4
+ * @author Jelle De Loecker <jelle@develry.be>
5
+ * @since 0.2.0
6
+ * @version 1.1.3
7
+ *
8
+ * @param {IncomingMessage} req
9
+ * @param {ServerResponse} res
10
+ * @param {Router} router
11
+ */
12
+ var HttpConduit = Function.inherits('Alchemy.Conduit.Conduit', function Http(req, res, router) {
13
+
14
+ // Initialize basic conduit values
15
+ Http.super.call(this);
16
+
17
+ this.initHttp(req, res, router);
18
+ });
19
+
20
+ /**
21
+ * Has the client aborted the request?
22
+ *
23
+ * @author Jelle De Loecker <jelle@develry.be>
24
+ * @since 1.1.0
25
+ * @version 1.1.0
26
+ *
27
+ * @type {Boolean}
28
+ */
29
+ HttpConduit.setProperty(function aborted() {
30
+
31
+ if (this.request && this.request.aborted != null) {
32
+ return this.request.aborted;
33
+ }
34
+
35
+ return false;
36
+ });
37
+
38
+ /**
39
+ * Return the IP address
40
+ *
41
+ * @author Jelle De Loecker <jelle@develry.be>
42
+ * @since 0.2.0
43
+ * @version 1.1.0
44
+ */
45
+ HttpConduit.setProperty(function ip() {
46
+
47
+ var req = this.request;
48
+
49
+ if (!req) {
50
+ return null;
51
+ }
52
+
53
+ let forwarded_for = req.headers['x-forwarded-for'] || req.headers['x-real-ip'];
54
+
55
+ if (forwarded_for) {
56
+
57
+ // Forwarded for can contain multiple ip addresses,
58
+ // return the first one
59
+ if (forwarded_for.indexOf(',') > -1) {
60
+ forwarded_for = forwarded_for.before(',');
61
+ }
62
+
63
+ return forwarded_for;
64
+ }
65
+
66
+ let remote_address;
67
+
68
+ if (req.connection) {
69
+ remote_address = req.connection.remoteAddress;
70
+ }
71
+
72
+ if (!remote_address && req.socket) {
73
+ remote_address = req.socket.remoteAddress;
74
+ }
75
+
76
+ if (!remote_address && req.connection && req.connection.socket) {
77
+ remote_address = req.connection.socket.remoteAddress;
78
+ }
79
+
80
+ return remote_address;
81
+ });
82
+
83
+ /**
84
+ * Get a simple fingerprint of the client
85
+ * based on ip, accept-language & user-agent
86
+ *
87
+ * @author Jelle De Loecker <jelle@develry.be>
88
+ * @since 1.1.0
89
+ * @version 1.1.0
90
+ *
91
+ * @type {String}
92
+ */
93
+ HttpConduit.enforceProperty(function fingerprint() {
94
+
95
+ let result,
96
+ language = this.headers['accept-language'] || 'all',
97
+ ua = this.headers['user-agent'] || 'unknown',
98
+ ip = this.ip || '';
99
+
100
+ result = Object.checksum(ip + '_' + language + '_' + ua);
101
+
102
+ return result;
103
+ });
104
+
105
+ /**
106
+ * Init
107
+ *
108
+ * @author Jelle De Loecker <jelle@kipdola.be>
109
+ * @since 0.3.3
110
+ * @version 1.2.0
111
+ *
112
+ * @param {IncomingMessage} req
113
+ * @param {ServerResponse} res
114
+ * @param {Router} router
115
+ */
116
+ HttpConduit.setMethod(async function initHttp(req, res, router) {
117
+
118
+ this.setReqRes(req, res);
119
+
120
+ if (router) {
121
+ this.router = router;
122
+ }
123
+
124
+ // The HTTP status
125
+ this.status = 200;
126
+
127
+ this.debugMark('Parse request');
128
+
129
+ // Parse the request, get the correct routes and such
130
+ await this.parseRequest();
131
+
132
+ if (this.halt_request) {
133
+ return;
134
+ }
135
+
136
+ if (alchemy.settings.debug || alchemy.settings.environment != 'live') {
137
+ this.setHeader('X-Robots-Tag', 'none');
138
+ }
139
+
140
+ this.debugMark(false);
141
+
142
+ // Call the middleware, which will call the handler afterwards
143
+ this.callMiddleware();
144
+ });
145
+
146
+ /**
147
+ * Get the original url path
148
+ *
149
+ * @author Jelle De Loecker <jelle@kipdola.be>
150
+ * @since 1.1.0
151
+ * @version 1.1.0
152
+ *
153
+ * @type {String}
154
+ */
155
+ HttpConduit.setProperty(function original_path() {
156
+ return this.original_url.path;
157
+ }, function setPath(value) {
158
+ return this.original_url.path = value;
159
+ });
160
+
161
+ /**
162
+ * Get the original url pathname
163
+ *
164
+ * @author Jelle De Loecker <jelle@kipdola.be>
165
+ * @since 1.1.0
166
+ * @version 1.1.0
167
+ *
168
+ * @type {String}
169
+ */
170
+ HttpConduit.setProperty(function original_pathname() {
171
+ return this.original_url.pathname;
172
+ }, function setPathname(value) {
173
+ return this.original_url.pathname = value;
193
174
  });
@@ -76,9 +76,9 @@ LoopConduit.setMethod(function copyParentProperties(conduit) {
76
76
  /**
77
77
  * Set the loopback options
78
78
  *
79
- * @author Jelle De Loecker <jelle@develry.be>
79
+ * @author Jelle De Loecker <jelle@elevenways.be>
80
80
  * @since 1.1.3
81
- * @version 1.1.5
81
+ * @version 1.1.7
82
82
  *
83
83
  * @param {Object} options
84
84
  * @param {Function} callback
@@ -93,6 +93,9 @@ LoopConduit.setMethod(function setOptions(options, callback) {
93
93
  info,
94
94
  key;
95
95
 
96
+ // Always clone the options
97
+ options = JSON.clone(options);
98
+
96
99
  for (key in options) {
97
100
  let set_method = false;
98
101
 
@@ -113,17 +116,32 @@ LoopConduit.setMethod(function setOptions(options, callback) {
113
116
  }
114
117
  }
115
118
 
116
- if (!this.method) {
117
- this.method = 'get';
119
+ if (options.method) {
120
+ this.method = options.method;
118
121
  }
119
122
 
120
123
  if (options.name) {
121
124
  // @TODO: what about path sections?
122
- //route = this.getRouteByName(options.name);
125
+ route = this.getRouteByName(options.name);
126
+
127
+ if (route && !this.method) {
128
+ if (route.methods.length == 1) {
129
+ this.method = route.methods[0];
130
+ } else if (options.body && route.methods.indexOf('post') > -1) {
131
+ this.method = 'post';
132
+ } else {
133
+ this.method = route.methods[0];
134
+ }
135
+ }
123
136
 
124
137
  // @WARNING: It's best to just generate the URL
125
138
  // and let it parse all the information that way
126
139
  options.href = alchemy.routeUrl(options.name, options.params, {extra_get_parameters: false});
140
+ // @TODO: alchemy.routeUrl will ignore the `keep_get_parameters` option
141
+ }
142
+
143
+ if (!this.method) {
144
+ this.method = 'get';
127
145
  }
128
146
 
129
147
  if (options.href) {
@@ -83,18 +83,21 @@ TimeAgo.setMethod(function connected() {
83
83
  *
84
84
  * @author Jelle De Loecker <jelle@develry.be>
85
85
  * @since 0.1.0
86
- * @version 0.1.0
86
+ * @version 0.1.7
87
87
  *
88
88
  * @param {Boolean} reset_timer
89
89
  */
90
90
  TimeAgo.setMethod(function refresh(reset_timer) {
91
91
 
92
- var that = this,
92
+ let that = this,
93
93
  counter,
94
94
  timer,
95
+ text = this.value,
95
96
  diff;
96
97
 
97
- this.innerHTML = this.value;
98
+ if (this.textContent != text) {
99
+ this.textContent = text;
100
+ }
98
101
 
99
102
  if (!reset_timer || Blast.isNode) {
100
103
  return;
@@ -106,7 +109,7 @@ TimeAgo.setMethod(function refresh(reset_timer) {
106
109
  }
107
110
 
108
111
  if (this.date) {
109
- diff = Date.now() - this.date;
112
+ diff = Math.abs(Date.now() - this.date);
110
113
  counter = 0;
111
114
 
112
115
  if (diff < 1000 * 29) {
@@ -15,7 +15,7 @@ var Remote = Function.inherits('Alchemy.Datasource', function Remote(name, optio
15
15
  *
16
16
  * @author Jelle De Loecker <jelle@develry.be>
17
17
  * @since 1.1.0
18
- * @version 1.1.0
18
+ * @version 1.1.7
19
19
  */
20
20
  Remote.setMethod(async function doServerCommand(action, model, data, callback) {
21
21
 
@@ -55,7 +55,8 @@ Remote.setMethod(async function doServerCommand(action, model, data, callback) {
55
55
 
56
56
  conduit.loopback({
57
57
  name : route_name,
58
- params : data
58
+ method : 'post',
59
+ body : data
59
60
  }, function gotResult(err, result) {
60
61
 
61
62
  if (err) {
@@ -20,6 +20,27 @@ var StringField = Function.inherits('Alchemy.Field', function String(schema, nam
20
20
  */
21
21
  StringField.setDatatype('string');
22
22
 
23
+ /**
24
+ * Cast the given value to this field's type for search in a db
25
+ *
26
+ * @author Jelle De Loecker <jelle@develry.be>
27
+ * @since 1.1.7
28
+ * @version 1.1.7
29
+ *
30
+ * @param {Mixed} value
31
+ * @param {Array} field_paths The path to the field
32
+ *
33
+ * @return {Mixed}
34
+ */
35
+ StringField.setMethod(function _castCondition(value, field_paths) {
36
+
37
+ if (typeof value == 'object' && RegExp.isRegExp(value)) {
38
+ return value;
39
+ }
40
+
41
+ return this.cast(value, true);
42
+ });
43
+
23
44
  /**
24
45
  * Cast the given value to this field's type
25
46
  *
@@ -277,7 +277,12 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) {
277
277
 
278
278
  name = this.name + 'FieldModel';
279
279
  Dummy = alchemy.getModel('Model', false);
280
- Dummy = new Dummy({name: name});
280
+
281
+ Dummy = new Dummy({
282
+ root_model : this.root_model,
283
+ name : name
284
+ });
285
+
281
286
  item = {};
282
287
 
283
288
  item[name] = value;
@@ -338,7 +343,7 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) {
338
343
 
339
344
  if (field != null) {
340
345
  tasks[field_name] = function doToDatasource(next) {
341
- field.toApp({}, {}, field_value, next);
346
+ field.toApp({}, {_root_data: options._root_data}, field_value, next);
342
347
  };
343
348
  }
344
349
  });
@@ -510,6 +510,9 @@ Criteria.setMethod(function getCriteriaForAssociation(name, item) {
510
510
  assoc_crit.where(assoc_key).equals(value);
511
511
  }
512
512
 
513
+ assoc_crit.setOption('assoc_key', assoc_key);
514
+ assoc_crit.setOption('assoc_value', value);
515
+
513
516
  // Take over the locale option
514
517
  if (options.locale) {
515
518
  assoc_crit.setOption('locale', options.locale);
@@ -1,13 +1,15 @@
1
1
  /**
2
- * The FieldConfig class
2
+ * Alchemy.Criteria.FieldConfig:
3
+ * Configuration on what to do with a certain field during a database query
4
+ * or anywhere data is represented
3
5
  *
4
6
  * @constructor
5
7
  *
6
8
  * @author Jelle De Loecker <jelle@elevenways.be>
7
9
  * @since 1.1.3
8
- * @version 1.1.4
10
+ * @version 1.1.7
9
11
  *
10
- * @param {String} path
12
+ * @param {string} path
11
13
  * @param {Object} options
12
14
  */
13
15
  const FieldConfig = Fn.inherits('Alchemy.Base', 'Alchemy.Criteria', function FieldConfig(path, options) {
@@ -54,7 +56,7 @@ FieldConfig.setStatic(function unDry(obj) {
54
56
  * @since 1.1.3
55
57
  * @version 1.1.3
56
58
  *
57
- * @type {String}
59
+ * @type {string}
58
60
  */
59
61
  FieldConfig.setProperty(function title() {
60
62
 
@@ -107,7 +109,7 @@ FieldConfig.setMethod(function toJSON() {
107
109
  * @since 1.1.3
108
110
  * @version 1.1.3
109
111
  *
110
- * @param {String} path
112
+ * @param {string} path
111
113
  */
112
114
  FieldConfig.setMethod(function parsePath(path) {
113
115
 
@@ -160,4 +162,34 @@ FieldConfig.setMethod(function getValueIn(data) {
160
162
  }
161
163
 
162
164
  return Object.path(data, this.path);
165
+ });
166
+
167
+ /**
168
+ * Get the display value for the value in the given object
169
+ *
170
+ * @author Jelle De Loecker <jelle@elevenways.be>
171
+ * @since 1.1.7
172
+ * @version 1.1.7
173
+ *
174
+ * @param {Object} data
175
+ *
176
+ * @return {string}
177
+ */
178
+ FieldConfig.setMethod(function getDisplayValueIn(data) {
179
+
180
+ let value = this.getValueIn(data);
181
+
182
+ if (value == null) {
183
+ return '';
184
+ }
185
+
186
+ if (typeof value == 'object') {
187
+ if (Date.isDate(value)) {
188
+ value = value.format('y-m-d H:i:s');
189
+ }
190
+
191
+ return value;
192
+ }
193
+
194
+ return '' + value;
163
195
  });
@@ -96,12 +96,18 @@ Model.setProperty(function table() {
96
96
  *
97
97
  * @author Jelle De Loecker <jelle@develry.be>
98
98
  * @since 1.0.0
99
- * @version 1.1.0
99
+ * @version 1.1.7
100
100
  *
101
101
  * @type {Datasource}
102
102
  */
103
103
  Model.setProperty(function datasource() {
104
104
 
105
+ if (Blast.isNode) {
106
+ // We use the "remote" type datasource, which will make loopback
107
+ // requests on the server side
108
+ return Datasource.get({name: 'loopback', type: 'remote'});
109
+ }
110
+
105
111
  if (!fallback_datasource) {
106
112
  fallback_datasource = new Blast.Classes.Alchemy.Datasource.Fallback('default', {
107
113
  upper: {
@@ -1052,6 +1058,26 @@ Model.setMethod(function addAssociatedDataToRecord(criteria, item, callback) {
1052
1058
  return;
1053
1059
  }
1054
1060
 
1061
+ // SchemaFields use a dummy Model name, so get the root_model in that case
1062
+ let root_model = (that.options && that.options.root_model) || that.name;
1063
+
1064
+ // Make sure references to the same model don't cause a deadlock
1065
+ // (Can happen when a model refers to itself)
1066
+ if (association.modelName == root_model && criteria.options._root_data) {
1067
+ let assoc_key = assoc_crit.options.assoc_key,
1068
+ assoc_value = assoc_crit.options.assoc_value,
1069
+ root = criteria.options._root_data[root_model];
1070
+
1071
+ if (root && root[assoc_key] && Object.alike(root[assoc_key], assoc_value)) {
1072
+
1073
+ aliases[alias] = nextAlias => {
1074
+ nextAlias(null, root[assoc_key]);
1075
+ };
1076
+
1077
+ return;
1078
+ }
1079
+ }
1080
+
1055
1081
  aliases[alias] = function aliasRecordTask(nextAlias) {
1056
1082
 
1057
1083
  var key;