rasti 2.0.0-alpha.0 → 2.0.0-alpha.10
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.
- package/README.md +9 -6
- package/dist/rasti.js +5 -5
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +205 -113
- package/es/Model.js +51 -12
- package/es/View.js +24 -23
- package/es/index.js +4 -4
- package/lib/Component.js +205 -114
- package/lib/Emitter.js +2 -3
- package/lib/Model.js +53 -15
- package/lib/View.js +25 -25
- package/lib/index.js +4 -4
- package/package.json +1 -1
- package/src/Component.js +200 -118
- package/src/Model.js +49 -12
- package/src/View.js +22 -22
- package/src/index.js +4 -4
package/es/Component.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
2
2
|
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
|
|
3
3
|
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
4
|
-
import View from './View';
|
|
4
|
+
import View from './View.js';
|
|
5
5
|
|
|
6
6
|
// This options keys will be extended on view instance.
|
|
7
7
|
var componentOptions = {
|
|
@@ -17,16 +17,24 @@ var componentOptions = {
|
|
|
17
17
|
* @param text {string} html text
|
|
18
18
|
* @return {object} Object with keys / values representing attributes.
|
|
19
19
|
*/
|
|
20
|
-
var
|
|
20
|
+
var extractAttributes = function extractAttributes(text) {
|
|
21
21
|
var attributes = {};
|
|
22
|
-
var re = /([
|
|
22
|
+
var re = /([\w|data-]+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;
|
|
23
23
|
var result;
|
|
24
24
|
while ((result = re.exec(text)) !== null) {
|
|
25
|
-
attributes[result[1]] = result[2] ? result[2]
|
|
25
|
+
attributes[result[1]] = typeof result[2] === 'undefined' ? true : result[2];
|
|
26
26
|
}
|
|
27
27
|
return attributes;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/*
|
|
31
|
+
* Helper function. If first arg is a placeholder for an expression, return the expression.
|
|
32
|
+
*/
|
|
33
|
+
var getExpression = function getExpression(placeholder, expressions) {
|
|
34
|
+
var match = placeholder && placeholder.match && placeholder.match(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)')));
|
|
35
|
+
return match && match[1] ? expressions[match[1]] : placeholder;
|
|
36
|
+
};
|
|
37
|
+
|
|
30
38
|
/*
|
|
31
39
|
* Helper function. If expression is a function, call it with context and args.
|
|
32
40
|
*/
|
|
@@ -41,14 +49,21 @@ var evalExpression = function evalExpression(expression, context) {
|
|
|
41
49
|
* Components are a special kind of `View` that is designed to be easily composable,
|
|
42
50
|
* making it simple to add child views and build complex user interfaces.<br />
|
|
43
51
|
* Unlike views, which are render-agnostic, components have a specific set of rendering
|
|
44
|
-
* guidelines that allow for a more declarative development style
|
|
52
|
+
* guidelines that allow for a more declarative development style.<br />
|
|
53
|
+
* Components are defined with the `create` static method, which takes a tagged template.
|
|
45
54
|
* @module
|
|
55
|
+
* @extends Rasti.View
|
|
46
56
|
* @param {object} options Object containing options. The following keys will be merged to `this`: model, state, key, onDestroy, onRender, onCreate, onChange.
|
|
57
|
+
* @property {string} key A unique key to identify the component. Used to recycle child components.
|
|
58
|
+
* @property {object} model A `Rasti.Model` or any emitter object containing data and business logic.
|
|
59
|
+
* @property {object} state A `Rasti.Model` or any emitter object containing data and business logic, to be used as internal state.
|
|
47
60
|
* @example
|
|
48
61
|
* import { Component, Model } from 'rasti';
|
|
49
62
|
* // Create Timer component.
|
|
50
63
|
* const Timer = Component.create`
|
|
51
|
-
*
|
|
64
|
+
* <div>
|
|
65
|
+
* Seconds: <span>${({ model }) => model.seconds}</span>
|
|
66
|
+
* </div>
|
|
52
67
|
* `;
|
|
53
68
|
* // Create model to store seconds.
|
|
54
69
|
* const model = new Model({ seconds: 0 });
|
|
@@ -72,8 +87,9 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
72
87
|
// Ensure id.
|
|
73
88
|
_this.id = _this.attributes.id ?
|
|
74
89
|
// If id is provided, evaluate it.
|
|
75
|
-
evalExpression(_this.attributes.id, _assertThisInitialized(_this), _assertThisInitialized(_this)) :
|
|
76
|
-
|
|
90
|
+
evalExpression(_this.attributes.id, _assertThisInitialized(_this), _assertThisInitialized(_this)) :
|
|
91
|
+
// Generate a unique id and set it as id attribute.
|
|
92
|
+
Component.ID_TEMPLATE(_this.uid);
|
|
77
93
|
// Bind onChange to this to be used as listener.
|
|
78
94
|
// Store bound version, so it can be removed on onDestroy method.
|
|
79
95
|
_this.onChange = _this.onChange.bind(_assertThisInitialized(_this));
|
|
@@ -93,29 +109,81 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
93
109
|
_proto.ensureElement = function ensureElement() {
|
|
94
110
|
// If el is provided, delegate events.
|
|
95
111
|
if (this.el) this.delegateEvents();
|
|
96
|
-
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* Find view's element on parent node, using id.
|
|
116
|
+
*/;
|
|
97
117
|
_proto.findElement = function findElement(parent) {
|
|
98
118
|
return (parent || document).querySelector("#" + this.id);
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
/*
|
|
102
|
-
*
|
|
122
|
+
* Eval attributes expressions.
|
|
103
123
|
*/;
|
|
104
|
-
_proto.
|
|
124
|
+
_proto.getAttributes = function getAttributes() {
|
|
105
125
|
var _this2 = this;
|
|
126
|
+
var add = {};
|
|
127
|
+
var remove = {};
|
|
128
|
+
var attrs = [];
|
|
129
|
+
Object.keys(this.attributes).forEach(function (key) {
|
|
130
|
+
if (key === 'id') return;
|
|
131
|
+
// Evaluate attribute value.
|
|
132
|
+
var value = evalExpression(_this2.attributes[key], _this2, _this2);
|
|
133
|
+
|
|
134
|
+
// Transform bool attribute values
|
|
135
|
+
if (value === false) {
|
|
136
|
+
remove[key] = true;
|
|
137
|
+
} else if (value === true) {
|
|
138
|
+
add[key] = '';
|
|
139
|
+
attrs.push(key);
|
|
140
|
+
} else {
|
|
141
|
+
if (value === null || typeof value === 'undefined') value = '';
|
|
142
|
+
add[key] = value;
|
|
143
|
+
attrs.push(key + "=\"" + value + "\"");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
add.id = this.id;
|
|
147
|
+
attrs.push("id=\"" + this.id + "\"");
|
|
148
|
+
return {
|
|
149
|
+
add: add,
|
|
150
|
+
remove: remove,
|
|
151
|
+
html: attrs.join(' ')
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
* Used internally on the render process.
|
|
157
|
+
* Attach the view to the dom element.
|
|
158
|
+
*/;
|
|
159
|
+
_proto.hydrate = function hydrate(parent) {
|
|
160
|
+
var _this3 = this;
|
|
106
161
|
this.el = this.findElement(parent);
|
|
107
162
|
this.delegateEvents();
|
|
108
163
|
this.children.forEach(function (child) {
|
|
109
|
-
return child.hydrate(
|
|
164
|
+
return child.hydrate(_this3.el);
|
|
110
165
|
});
|
|
111
|
-
this.onRender.call(this);
|
|
166
|
+
this.onRender.call(this, 'hydrate');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
* Used internally on the render process.
|
|
171
|
+
* Reuse a view that has `key` when its parent is rendered.
|
|
172
|
+
*/;
|
|
173
|
+
_proto.recycle = function recycle(parent) {
|
|
174
|
+
// Find element to be replaced. It has same id.
|
|
175
|
+
var toBeReplaced = this.findElement(parent);
|
|
176
|
+
// Replace it with this.el.
|
|
177
|
+
toBeReplaced.replaceWith(this.el);
|
|
178
|
+
// Call `onRender` lifecycle method.
|
|
179
|
+
this.onRender.call(this, 'recycle');
|
|
112
180
|
}
|
|
113
181
|
|
|
114
182
|
/*
|
|
115
183
|
* Override. Add some custom logic to super `destroy` method.
|
|
116
184
|
*/;
|
|
117
|
-
_proto.destroy = function destroy(
|
|
118
|
-
_View.prototype.destroy.
|
|
185
|
+
_proto.destroy = function destroy() {
|
|
186
|
+
_View.prototype.destroy.apply(this, arguments);
|
|
119
187
|
// Stop listening to `change`.
|
|
120
188
|
// Set destroyed flag to prevent a last render after destroyed. TODO: Review
|
|
121
189
|
if (this.model) this.model.off('change', this.onChange);
|
|
@@ -135,9 +203,9 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
135
203
|
* This method should be extended with custom logic.
|
|
136
204
|
* Maybe comparing new attributes with previous ones and calling
|
|
137
205
|
* render when needed. Or doing some dom transformation.
|
|
138
|
-
* @param model {Rasti.Model}
|
|
139
|
-
* @param
|
|
140
|
-
* @param
|
|
206
|
+
* @param model {Rasti.Model} The model that emitted the event.
|
|
207
|
+
* @param changed {object} Object containing keys and values that has changed.
|
|
208
|
+
* @param [...args] {any} Any extra arguments passed to set method.
|
|
141
209
|
*/;
|
|
142
210
|
_proto.onChange = function onChange() {
|
|
143
211
|
this.render();
|
|
@@ -145,131 +213,139 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
145
213
|
|
|
146
214
|
/**
|
|
147
215
|
* Lifecycle method. Called when the view is rendered.
|
|
216
|
+
* @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
|
|
148
217
|
*/;
|
|
149
218
|
_proto.onRender = function onRender() {}
|
|
150
219
|
|
|
151
220
|
/**
|
|
152
221
|
* Lifecycle method. Called when the view is destroyed.
|
|
222
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
153
223
|
*/;
|
|
154
224
|
_proto.onDestroy = function onDestroy() {}
|
|
155
225
|
|
|
156
226
|
/*
|
|
157
227
|
* Replace expressions.
|
|
158
228
|
*/;
|
|
159
|
-
_proto.replaceExpressions = function replaceExpressions(string,
|
|
160
|
-
var
|
|
161
|
-
return string.replace(
|
|
162
|
-
|
|
163
|
-
var idx = match.match(/{(\d+)}/)[1];
|
|
229
|
+
_proto.replaceExpressions = function replaceExpressions(string, addChild) {
|
|
230
|
+
var _this4 = this;
|
|
231
|
+
return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)'), 'g'), function (match) {
|
|
232
|
+
var expression = getExpression(match, _this4.template.expressions);
|
|
164
233
|
// Eval expression. Pass view as argument.
|
|
165
|
-
var result =
|
|
234
|
+
var result = evalExpression(expression, _this4, _this4);
|
|
166
235
|
// Treat all expressions as arrays.
|
|
167
236
|
var results = result instanceof Array ? result : [result];
|
|
168
237
|
// Replace expression with the result of the evaluation.
|
|
169
|
-
return results.reduce(function (out,
|
|
238
|
+
return results.reduce(function (out, result) {
|
|
239
|
+
var parsed;
|
|
240
|
+
// If result is true, replace it with a placeholder.
|
|
241
|
+
if (result === true) parsed = Component.TRUE_PLACEHOLDER;
|
|
242
|
+
// If result is false, replace it with a placeholder.
|
|
243
|
+
else if (result === false) parsed = Component.FALSE_PLACEHOLDER;
|
|
244
|
+
// Replace null or undefined with empty string.
|
|
245
|
+
else if (result === null || typeof result === 'undefined') parsed = '';
|
|
246
|
+
// If result is a view, call addChild callback.
|
|
247
|
+
else if (result && typeof result.render === 'function') parsed = addChild(result);
|
|
248
|
+
// Return expression itself.
|
|
249
|
+
else parsed = result;
|
|
170
250
|
// Concatenate expressions.
|
|
171
|
-
out
|
|
172
|
-
return out;
|
|
251
|
+
return out + parsed;
|
|
173
252
|
}, '');
|
|
174
253
|
})
|
|
175
254
|
// Replace `attribute="true"` with `attribute`
|
|
176
|
-
.replace(
|
|
177
|
-
// Replace `attribute="false"` with
|
|
178
|
-
.replace(
|
|
255
|
+
.replace(new RegExp("([a-z]+)=[\"|']" + Component.TRUE_PLACEHOLDER + "[\"|']", 'g'), '$1')
|
|
256
|
+
// Replace `attribute="false"` with empty string.
|
|
257
|
+
.replace(new RegExp("([a-z]+)=[\"|']" + Component.FALSE_PLACEHOLDER + "[\"|']", 'g'), '')
|
|
258
|
+
// Replace rest of false expressions with empty string.
|
|
259
|
+
.replace(new RegExp(Component.FALSE_PLACEHOLDER, 'g'), '');
|
|
179
260
|
}
|
|
180
261
|
|
|
181
262
|
/*
|
|
182
263
|
* Treat the whole view as a HTML string.
|
|
183
264
|
*/;
|
|
184
265
|
_proto.toString = function toString() {
|
|
185
|
-
var
|
|
266
|
+
var _this5 = this;
|
|
186
267
|
// Normally there won't be any children, but if there are, destroy them.
|
|
187
268
|
this.destroyChildren();
|
|
188
|
-
// Add id to template including root element (this.el) as part of it.
|
|
189
|
-
var tpl = this.attributes.id ? this.template.outer : this.template.outer.replace(/^<([a-z]+)/, "<$1 id=\"" + this.id + "\"");
|
|
190
269
|
// Replace expressions.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (result === null || typeof result === 'undefined') out = '';
|
|
195
|
-
// If result is a view, add it to children array.
|
|
196
|
-
else if (result && typeof result.render === 'function') {
|
|
197
|
-
// Add child component.
|
|
198
|
-
_this4.addChild(result);
|
|
199
|
-
}
|
|
200
|
-
return out;
|
|
270
|
+
var inner = this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
|
|
271
|
+
// Add child component.
|
|
272
|
+
return _this5.addChild(component);
|
|
201
273
|
});
|
|
274
|
+
// Generate outer template.
|
|
275
|
+
return inner ? "<" + this.tag + " " + this.getAttributes().html + ">" + inner + "</" + this.tag + ">" : "<" + this.tag + " " + this.getAttributes().html + " />";
|
|
202
276
|
}
|
|
203
277
|
|
|
204
278
|
/*
|
|
205
279
|
* View render method.
|
|
206
280
|
*/;
|
|
207
281
|
_proto.render = function render() {
|
|
208
|
-
var
|
|
282
|
+
var _this6 = this;
|
|
209
283
|
// Prevent a last re render if view is already destroyed.
|
|
210
284
|
if (this.destroyed) return this;
|
|
211
|
-
// If
|
|
212
|
-
// create a new element according "this.tag"
|
|
213
|
-
// and "this.attributes".
|
|
285
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
214
286
|
if (!this.el) {
|
|
215
287
|
this.el = this.createElement(this.tag);
|
|
216
288
|
this.delegateEvents();
|
|
217
289
|
}
|
|
218
290
|
// Set `this.el` attributes.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
var value = evalExpression(_this5.attributes[key], _this5, _this5);
|
|
224
|
-
// Transform bool attribute values
|
|
225
|
-
if (value === false) {
|
|
226
|
-
// Remove false attributes.
|
|
227
|
-
_this5.el.removeAttribute(key);
|
|
228
|
-
} else {
|
|
229
|
-
if (value === true) value = '';
|
|
230
|
-
_this5.el.setAttribute(key, value);
|
|
231
|
-
}
|
|
291
|
+
var attributes = this.getAttributes();
|
|
292
|
+
// Remove attributes.
|
|
293
|
+
Object.keys(attributes.remove).forEach(function (key) {
|
|
294
|
+
_this6.el.removeAttribute(key);
|
|
232
295
|
});
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
var
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
var
|
|
248
|
-
|
|
296
|
+
// Add attributes.
|
|
297
|
+
Object.keys(attributes.add).forEach(function (key) {
|
|
298
|
+
_this6.el.setAttribute(key, attributes.add[key]);
|
|
299
|
+
});
|
|
300
|
+
// Check for `template.inner` to see if view has innerHTML.
|
|
301
|
+
if (this.template.inner) {
|
|
302
|
+
var previousChildren = this.children;
|
|
303
|
+
this.children = [];
|
|
304
|
+
var nextChildren = [];
|
|
305
|
+
var recycledChildren = [];
|
|
306
|
+
// Store active element.
|
|
307
|
+
var activeElement = document.activeElement;
|
|
308
|
+
// Replace expressions. Set html inside of `this.el`.
|
|
309
|
+
this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {
|
|
310
|
+
var out = component;
|
|
311
|
+
// Check if child already exists.
|
|
312
|
+
var found = component.key && previousChildren.find(function (previousChild) {
|
|
313
|
+
return previousChild.key === component.key;
|
|
249
314
|
});
|
|
250
315
|
if (found) {
|
|
251
|
-
|
|
316
|
+
// If child already exists, replace it html by its root element.
|
|
317
|
+
out = "<" + found.tag + " id=\"" + found.id + "\"></" + found.tag + ">";
|
|
318
|
+
// Add child to recycled children.
|
|
252
319
|
recycledChildren.push(found);
|
|
253
|
-
|
|
320
|
+
// Destroy new child component. Use recycled one instead.
|
|
321
|
+
component.destroy();
|
|
254
322
|
} else {
|
|
255
|
-
|
|
323
|
+
// Not found. Add new child component.
|
|
324
|
+
nextChildren.push(component);
|
|
256
325
|
}
|
|
326
|
+
// Component html.
|
|
327
|
+
return out;
|
|
328
|
+
});
|
|
329
|
+
// Add new children. Hydrate them.
|
|
330
|
+
nextChildren.forEach(function (nextChild) {
|
|
331
|
+
_this6.addChild(nextChild).hydrate(_this6.el);
|
|
332
|
+
});
|
|
333
|
+
// Replace children root elements with recycled components.
|
|
334
|
+
recycledChildren.forEach(function (recycledChild) {
|
|
335
|
+
_this6.addChild(recycledChild).recycle(_this6.el);
|
|
336
|
+
});
|
|
337
|
+
// Destroy unused children.
|
|
338
|
+
previousChildren.forEach(function (previousChild) {
|
|
339
|
+
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
340
|
+
if (!found) previousChild.destroy();
|
|
341
|
+
});
|
|
342
|
+
// Restore focus.
|
|
343
|
+
if (this.el.contains(activeElement)) {
|
|
344
|
+
activeElement.focus();
|
|
257
345
|
}
|
|
258
|
-
|
|
259
|
-
});
|
|
260
|
-
nextChildren.forEach(function (nextChild) {
|
|
261
|
-
_this5.addChild(nextChild).hydrate(_this5.el);
|
|
262
|
-
});
|
|
263
|
-
recycledChildren.forEach(function (recycledChild) {
|
|
264
|
-
_this5.el.replaceChild(_this5.addChild(recycledChild).el, recycledChild.findElement(_this5.el));
|
|
265
|
-
});
|
|
266
|
-
previousChildren.forEach(function (previousChild) {
|
|
267
|
-
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
268
|
-
if (!found) previousChild.destroy();
|
|
269
|
-
});
|
|
270
|
-
|
|
346
|
+
}
|
|
271
347
|
// Call onRender lifecycle method.
|
|
272
|
-
this.onRender.call(this);
|
|
348
|
+
this.onRender.call(this, 'render');
|
|
273
349
|
// Return this for chaining.
|
|
274
350
|
return this;
|
|
275
351
|
}
|
|
@@ -301,7 +377,7 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
301
377
|
* @param {object} options The view options.
|
|
302
378
|
* @param {node} el Dom element to append the view element.
|
|
303
379
|
* @param {boolean} hydrate If true, the view will use existing html.
|
|
304
|
-
* @return {Rasti.
|
|
380
|
+
* @return {Rasti.Component}
|
|
305
381
|
*/;
|
|
306
382
|
Component.mount = function mount() {
|
|
307
383
|
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
@@ -309,7 +385,6 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
309
385
|
var hydrate = arguments.length > 2 ? arguments[2] : undefined;
|
|
310
386
|
// Instantiate view.
|
|
311
387
|
var view = new this(options);
|
|
312
|
-
|
|
313
388
|
// If `el` is passed, mount component.
|
|
314
389
|
if (el) {
|
|
315
390
|
if (hydrate) {
|
|
@@ -323,12 +398,23 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
323
398
|
}
|
|
324
399
|
view.hydrate(el);
|
|
325
400
|
}
|
|
401
|
+
// Return view instance.
|
|
326
402
|
return view;
|
|
327
403
|
}
|
|
328
404
|
/**
|
|
329
|
-
*
|
|
330
|
-
* and returns a `Component
|
|
405
|
+
* Takes a tagged template containing an HTML string,
|
|
406
|
+
* and returns a new `Component` class.
|
|
407
|
+
* - The template outer tag and attributes will be used to create the view's root element.
|
|
408
|
+
* - Boolean attributes should be passed in the form of `attribute="${() => true}"`.
|
|
409
|
+
* - Event handlers should be passed, at the root element, in the form of `onEventName=${{'selector' : listener }}`. Where `selector` is a css selector. The event will be delegated to the view's root element.
|
|
410
|
+
* - The template inner HTML will be used as the view's template.
|
|
411
|
+
* - Template interpolations that are functions will be evaluated on the render process. Receiving the view instance as argument. And being bound to it.
|
|
412
|
+
* - If the function returns `null`, `undefined`, `false` or empty string, the interpolation won't render any content.
|
|
413
|
+
* - If the function returns a component instance, it will be added as a child component.
|
|
414
|
+
* - If the function returns an array, each item will be evaluated as above.
|
|
331
415
|
* @static
|
|
416
|
+
* @param {string} HTML template for the component.
|
|
417
|
+
* @return {Rasti.Component}
|
|
332
418
|
*/;
|
|
333
419
|
Component.create = function create(strings) {
|
|
334
420
|
for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
@@ -342,25 +428,26 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
342
428
|
parts.push(string);
|
|
343
429
|
// Add expression placeholder for later or expression eval.
|
|
344
430
|
if (expressions[i]) {
|
|
345
|
-
parts.push(typeof expressions[i] === 'function' || typeof expressions[i] === 'object' ?
|
|
431
|
+
parts.push(typeof expressions[i] === 'function' || typeof expressions[i] === 'object' ? Component.EXPRESSION_PLACEHOLDER_TEMPLATE(i) : expressions[i]);
|
|
346
432
|
}
|
|
347
433
|
});
|
|
348
434
|
// Create output text for main template.
|
|
349
435
|
var main = parts.join('').trim().replace(/\n/g, '');
|
|
350
436
|
// Extract outer tag, attributes and inner html.
|
|
351
|
-
var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/);
|
|
352
|
-
// Remove events listeners.
|
|
353
|
-
var string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g, '');
|
|
437
|
+
var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
354
438
|
// Parse attributes from html text into an object.
|
|
355
|
-
var attributes =
|
|
439
|
+
var attributes = extractAttributes(result[2]);
|
|
440
|
+
// Events to be delegated.
|
|
356
441
|
var events = {};
|
|
357
|
-
// Filter events.
|
|
442
|
+
// Filter events. To generate events object.
|
|
443
|
+
// Generate attributes object, replace placeholders with expressions.
|
|
358
444
|
attributes = Object.keys(attributes).reduce(function (out, key) {
|
|
359
|
-
|
|
360
|
-
var
|
|
445
|
+
// Is Event?
|
|
446
|
+
var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
|
|
447
|
+
// Is placeholder for function or object?
|
|
361
448
|
// Get expression or value.
|
|
362
|
-
var value =
|
|
363
|
-
// Is event handler.
|
|
449
|
+
var value = getExpression(attributes[key], expressions);
|
|
450
|
+
// Is event handler. Add to events object.
|
|
364
451
|
if (matchKey && matchKey[1]) {
|
|
365
452
|
var eventType = matchKey[1].toLowerCase();
|
|
366
453
|
Object.keys(value).forEach(function (selector) {
|
|
@@ -368,22 +455,19 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
368
455
|
});
|
|
369
456
|
return out;
|
|
370
457
|
}
|
|
371
|
-
// Is
|
|
458
|
+
// Is attribute. Add to attributes object.
|
|
372
459
|
out[key] = value;
|
|
373
460
|
return out;
|
|
374
461
|
}, {});
|
|
375
462
|
var Current = this;
|
|
376
|
-
|
|
377
463
|
// Create subclass for this component.
|
|
378
464
|
return Current.extend({
|
|
379
465
|
// Set events.
|
|
380
466
|
events: events,
|
|
381
467
|
// Set attributes.
|
|
382
468
|
attributes: attributes,
|
|
383
|
-
// Set template
|
|
469
|
+
// Set template.
|
|
384
470
|
template: {
|
|
385
|
-
// Template including root element (this.el) as part of the template.
|
|
386
|
-
outer: string,
|
|
387
471
|
// Template for innerHTML of root element.
|
|
388
472
|
inner: result[3],
|
|
389
473
|
// Template expressions.
|
|
@@ -395,4 +479,12 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
395
479
|
};
|
|
396
480
|
return Component;
|
|
397
481
|
}(View);
|
|
398
|
-
export { Component as default };
|
|
482
|
+
export { Component as default };
|
|
483
|
+
Component.ID_TEMPLATE = function (uid) {
|
|
484
|
+
return "rasti-component-" + uid;
|
|
485
|
+
};
|
|
486
|
+
Component.EXPRESSION_PLACEHOLDER_TEMPLATE = function (idx) {
|
|
487
|
+
return "__RASTI_EXPRESSION{" + idx + "}";
|
|
488
|
+
};
|
|
489
|
+
Component.TRUE_PLACEHOLDER = '__RASTI_TRUE';
|
|
490
|
+
Component.FALSE_PLACEHOLDER = '__RASTI_FALSE';
|
package/es/Model.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
2
2
|
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
|
|
3
3
|
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
4
|
-
import Emitter from './Emitter';
|
|
4
|
+
import Emitter from './Emitter.js';
|
|
5
5
|
/**
|
|
6
6
|
* - Orchestrates data and business logic.
|
|
7
7
|
* - Emits events when data changes.
|
|
@@ -15,6 +15,7 @@ import Emitter from './Emitter';
|
|
|
15
15
|
* from `this.attributes`, and a setter is created to set the model property in `this.attributes` and emit `change`
|
|
16
16
|
* and `change:attribute` events.
|
|
17
17
|
* @module
|
|
18
|
+
* @extends Rasti.Emitter
|
|
18
19
|
* @param {object} attrs Object containing model attributes to extend `this.attributes`. Getters and setters are generated for `this.attributtes`, in order to emit `change` events.
|
|
19
20
|
* @example
|
|
20
21
|
* import { Model } from 'rasti';
|
|
@@ -100,7 +101,7 @@ var Model = /*#__PURE__*/function (_Emitter) {
|
|
|
100
101
|
|
|
101
102
|
/**
|
|
102
103
|
* Set an attribute into `this.attributes`.
|
|
103
|
-
* Emit `change` and `change:attribute` if value change.
|
|
104
|
+
* Emit `change` and `change:attribute` if a value change.
|
|
104
105
|
* Could be called in two forms, `this.set('key', value)` and
|
|
105
106
|
* `this.set({ key : value })`.
|
|
106
107
|
* This method is called internally by generated setters.
|
|
@@ -111,19 +112,57 @@ var Model = /*#__PURE__*/function (_Emitter) {
|
|
|
111
112
|
* @emits change:attribute
|
|
112
113
|
*/;
|
|
113
114
|
_proto.set = function set(key, value) {
|
|
114
|
-
var
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
var _this3 = this;
|
|
116
|
+
var attrs, args;
|
|
117
|
+
// Handle both `"key", value` and `{key: value}` style arguments.
|
|
118
|
+
for (var _len = arguments.length, rest = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
|
|
119
|
+
rest[_key - 2] = arguments[_key];
|
|
120
|
+
}
|
|
121
|
+
if (typeof key === 'object') {
|
|
122
|
+
attrs = key;
|
|
123
|
+
args = [value].concat(rest);
|
|
124
|
+
} else {
|
|
125
|
+
var _attrs;
|
|
126
|
+
attrs = (_attrs = {}, _attrs[key] = value, _attrs);
|
|
127
|
+
args = rest;
|
|
128
|
+
}
|
|
129
|
+
// Are we in a nested `set` call?
|
|
130
|
+
// Calling a `set` inside a `change:attribute` or `change` event listener
|
|
131
|
+
var changing = this._changing;
|
|
132
|
+
this._changing = true;
|
|
133
|
+
// Store changed attributes.
|
|
134
|
+
var changed = {};
|
|
135
|
+
// Store previous attributes.
|
|
136
|
+
if (!changing) {
|
|
137
|
+
this.previous = Object.assign({}, this.attributes);
|
|
138
|
+
}
|
|
139
|
+
// Set attributes.
|
|
117
140
|
Object.keys(attrs).forEach(function (key) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (changed) {
|
|
123
|
-
_this3.emit('change', _this3, key, attrs[key]);
|
|
124
|
-
_this3.emit("change:" + key, _this3, attrs[key]);
|
|
141
|
+
// Use equality to determine if value changed.
|
|
142
|
+
if (attrs[key] !== _this3.attributes[key]) {
|
|
143
|
+
changed[key] = attrs[key];
|
|
144
|
+
_this3.attributes[key] = attrs[key];
|
|
125
145
|
}
|
|
126
146
|
});
|
|
147
|
+
var changedKeys = Object.keys(changed);
|
|
148
|
+
// Pending `change` event arguments.
|
|
149
|
+
if (changedKeys.length) this._pending = ['change', this, changed].concat(args);
|
|
150
|
+
// Emit `change:attribute` events.
|
|
151
|
+
changedKeys.forEach(function (key) {
|
|
152
|
+
_this3.emit.apply(_this3, ["change:" + key, _this3, attrs[key]].concat(args));
|
|
153
|
+
});
|
|
154
|
+
// Don't emit `change` event until the end of the nested
|
|
155
|
+
// `set` calls inside `change:attribute` event listeners.
|
|
156
|
+
if (changing) return this;
|
|
157
|
+
// Emit `change` events, that might be nested.
|
|
158
|
+
while (this._pending) {
|
|
159
|
+
var pendingChange = this._pending;
|
|
160
|
+
this._pending = null;
|
|
161
|
+
this.emit.apply(this, pendingChange);
|
|
162
|
+
}
|
|
163
|
+
// Reset flags.
|
|
164
|
+
this._pending = null;
|
|
165
|
+
this._changing = false;
|
|
127
166
|
return this;
|
|
128
167
|
}
|
|
129
168
|
|