rasti 2.0.0-alpha.4 → 2.0.0-alpha.6
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/dist/rasti.js +2 -2
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +95 -52
- package/es/View.js +22 -22
- package/lib/Component.js +95 -52
- package/lib/View.js +22 -22
- package/package.json +1 -1
- package/src/Component.js +87 -47
- package/src/View.js +20 -21
package/lib/Component.js
CHANGED
|
@@ -21,7 +21,7 @@ var componentOptions = {
|
|
|
21
21
|
* @param text {string} html text
|
|
22
22
|
* @return {object} Object with keys / values representing attributes.
|
|
23
23
|
*/
|
|
24
|
-
var
|
|
24
|
+
var extractAttributes = function extractAttributes(text) {
|
|
25
25
|
var attributes = {};
|
|
26
26
|
var re = /(\w+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;
|
|
27
27
|
var result;
|
|
@@ -113,29 +113,79 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
113
113
|
_proto.ensureElement = function ensureElement() {
|
|
114
114
|
// If el is provided, delegate events.
|
|
115
115
|
if (this.el) this.delegateEvents();
|
|
116
|
-
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Find view's element on parent node, using id.
|
|
120
|
+
*/;
|
|
117
121
|
_proto.findElement = function findElement(parent) {
|
|
118
122
|
return (parent || document).querySelector("#" + this.id);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
/*
|
|
122
|
-
*
|
|
126
|
+
* Eval attributes expressions.
|
|
123
127
|
*/;
|
|
124
|
-
_proto.
|
|
128
|
+
_proto.getAttributes = function getAttributes() {
|
|
125
129
|
var _this2 = this;
|
|
130
|
+
var add = {};
|
|
131
|
+
var remove = {};
|
|
132
|
+
var attrs = [];
|
|
133
|
+
Object.keys(this.attributes).forEach(function (key) {
|
|
134
|
+
if (key === 'id') return;
|
|
135
|
+
// Evaluate attribute value.
|
|
136
|
+
var value = evalExpression(_this2.attributes[key], _this2, _this2);
|
|
137
|
+
// Transform bool attribute values
|
|
138
|
+
if (value === false) {
|
|
139
|
+
remove[key] = true;
|
|
140
|
+
} else if (value === true) {
|
|
141
|
+
add[key] = '';
|
|
142
|
+
attrs.push(key);
|
|
143
|
+
} else {
|
|
144
|
+
add[key] = value;
|
|
145
|
+
attrs.push(key + "=\"" + value + "\"");
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
add.id = this.id;
|
|
149
|
+
attrs.push("id=\"" + this.id + "\"");
|
|
150
|
+
return {
|
|
151
|
+
add: add,
|
|
152
|
+
remove: remove,
|
|
153
|
+
html: attrs.join(' ')
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* Used internally on the render process.
|
|
159
|
+
* Attach the view to the dom element.
|
|
160
|
+
*/;
|
|
161
|
+
_proto.hydrate = function hydrate(parent) {
|
|
162
|
+
var _this3 = this;
|
|
126
163
|
this.el = this.findElement(parent);
|
|
127
164
|
this.delegateEvents();
|
|
128
165
|
this.children.forEach(function (child) {
|
|
129
|
-
return child.hydrate(
|
|
166
|
+
return child.hydrate(_this3.el);
|
|
130
167
|
});
|
|
131
|
-
this.onRender.call(this);
|
|
168
|
+
this.onRender.call(this, 'hydrate');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/*
|
|
172
|
+
* Used internally on the render process.
|
|
173
|
+
* Reuse a view that has `key` when its parent is rendered.
|
|
174
|
+
*/;
|
|
175
|
+
_proto.recycle = function recycle(parent) {
|
|
176
|
+
// Find element to be replaced. It has same id.
|
|
177
|
+
var toBeReplaced = this.findElement(parent);
|
|
178
|
+
// Replace it with this.el.
|
|
179
|
+
toBeReplaced.replaceWith(this.el);
|
|
180
|
+
// Call `onRender` lifecycle method.
|
|
181
|
+
this.onRender.call(this, 'recycle');
|
|
132
182
|
}
|
|
133
183
|
|
|
134
184
|
/*
|
|
135
185
|
* Override. Add some custom logic to super `destroy` method.
|
|
136
186
|
*/;
|
|
137
|
-
_proto.destroy = function destroy(
|
|
138
|
-
_View.prototype.destroy.
|
|
187
|
+
_proto.destroy = function destroy() {
|
|
188
|
+
_View.prototype.destroy.apply(this, arguments);
|
|
139
189
|
// Stop listening to `change`.
|
|
140
190
|
// Set destroyed flag to prevent a last render after destroyed. TODO: Review
|
|
141
191
|
if (this.model) this.model.off('change', this.onChange);
|
|
@@ -155,9 +205,9 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
155
205
|
* This method should be extended with custom logic.
|
|
156
206
|
* Maybe comparing new attributes with previous ones and calling
|
|
157
207
|
* render when needed. Or doing some dom transformation.
|
|
158
|
-
* @param model {Rasti.Model}
|
|
159
|
-
* @param key {string}
|
|
160
|
-
* @param value {any}
|
|
208
|
+
* @param model {Rasti.Model} The model that emitted the event.
|
|
209
|
+
* @param key {string} The key that changed.
|
|
210
|
+
* @param value {any} The new value.
|
|
161
211
|
*/;
|
|
162
212
|
_proto.onChange = function onChange() {
|
|
163
213
|
this.render();
|
|
@@ -165,11 +215,13 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
165
215
|
|
|
166
216
|
/**
|
|
167
217
|
* Lifecycle method. Called when the view is rendered.
|
|
218
|
+
* @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
|
|
168
219
|
*/;
|
|
169
220
|
_proto.onRender = function onRender() {}
|
|
170
221
|
|
|
171
222
|
/**
|
|
172
223
|
* Lifecycle method. Called when the view is destroyed.
|
|
224
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
173
225
|
*/;
|
|
174
226
|
_proto.onDestroy = function onDestroy() {}
|
|
175
227
|
|
|
@@ -177,11 +229,11 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
177
229
|
* Replace expressions.
|
|
178
230
|
*/;
|
|
179
231
|
_proto.replaceExpressions = function replaceExpressions(string, addChild) {
|
|
180
|
-
var
|
|
232
|
+
var _this4 = this;
|
|
181
233
|
return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)'), 'g'), function (match) {
|
|
182
|
-
var expression = getExpression(match,
|
|
234
|
+
var expression = getExpression(match, _this4.template.expressions);
|
|
183
235
|
// Eval expression. Pass view as argument.
|
|
184
|
-
var result = evalExpression(expression,
|
|
236
|
+
var result = evalExpression(expression, _this4, _this4);
|
|
185
237
|
// Treat all expressions as arrays.
|
|
186
238
|
var results = result instanceof Array ? result : [result];
|
|
187
239
|
// Replace expression with the result of the evaluation.
|
|
@@ -213,58 +265,49 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
213
265
|
* Treat the whole view as a HTML string.
|
|
214
266
|
*/;
|
|
215
267
|
_proto.toString = function toString() {
|
|
216
|
-
var
|
|
268
|
+
var _this5 = this;
|
|
217
269
|
// Normally there won't be any children, but if there are, destroy them.
|
|
218
270
|
this.destroyChildren();
|
|
219
|
-
// Add id to template including root element (this.el) as part of it.
|
|
220
|
-
var tpl = this.attributes.id ? this.template.outer : this.template.outer.replace(/^<([a-z]+)/, "<$1 id=\"" + this.id + "\"");
|
|
221
271
|
// Replace expressions.
|
|
222
|
-
|
|
272
|
+
var inner = this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
|
|
223
273
|
// Add child component.
|
|
224
|
-
return
|
|
274
|
+
return _this5.addChild(component);
|
|
225
275
|
});
|
|
276
|
+
// Generate outer template.
|
|
277
|
+
return inner ? "<" + this.tag + " " + this.getAttributes().html + ">" + inner + "</" + this.tag + ">" : "<" + this.tag + " " + this.getAttributes().html + " />";
|
|
226
278
|
}
|
|
227
279
|
|
|
228
280
|
/*
|
|
229
281
|
* View render method.
|
|
230
282
|
*/;
|
|
231
283
|
_proto.render = function render() {
|
|
232
|
-
var
|
|
284
|
+
var _this6 = this;
|
|
233
285
|
// Prevent a last re render if view is already destroyed.
|
|
234
286
|
if (this.destroyed) return this;
|
|
235
|
-
// If
|
|
236
|
-
// create a new element according "this.tag"
|
|
237
|
-
// and "this.attributes".
|
|
287
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
238
288
|
if (!this.el) {
|
|
239
289
|
this.el = this.createElement(this.tag);
|
|
240
290
|
this.delegateEvents();
|
|
241
291
|
}
|
|
242
292
|
// Set `this.el` attributes.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
var value = evalExpression(_this5.attributes[key], _this5, _this5);
|
|
248
|
-
// Transform bool attribute values
|
|
249
|
-
if (value === false) {
|
|
250
|
-
// Remove false attributes.
|
|
251
|
-
_this5.el.removeAttribute(key);
|
|
252
|
-
} else {
|
|
253
|
-
if (value === true) value = '';
|
|
254
|
-
_this5.el.setAttribute(key, value);
|
|
255
|
-
}
|
|
293
|
+
var attributes = this.getAttributes();
|
|
294
|
+
// Remove attributes.
|
|
295
|
+
Object.keys(attributes.remove).forEach(function (key) {
|
|
296
|
+
_this6.el.removeAttribute(key);
|
|
256
297
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
298
|
+
// Add attributes.
|
|
299
|
+
Object.keys(attributes.add).forEach(function (key) {
|
|
300
|
+
_this6.el.setAttribute(key, attributes.add[key]);
|
|
301
|
+
});
|
|
302
|
+
// Check for `template.inner` to see if view has innerHTML.
|
|
260
303
|
if (this.template.inner) {
|
|
261
304
|
var previousChildren = this.children;
|
|
262
305
|
this.children = [];
|
|
263
306
|
var nextChildren = [];
|
|
264
307
|
var recycledChildren = [];
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// Set html
|
|
308
|
+
// Store active element.
|
|
309
|
+
var activeElement = document.activeElement;
|
|
310
|
+
// Replace expressions. Set html inside of `this.el`.
|
|
268
311
|
this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {
|
|
269
312
|
var out = component;
|
|
270
313
|
// Check if child already exists.
|
|
@@ -287,20 +330,24 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
287
330
|
});
|
|
288
331
|
// Add new children. Hydrate them.
|
|
289
332
|
nextChildren.forEach(function (nextChild) {
|
|
290
|
-
|
|
333
|
+
_this6.addChild(nextChild).hydrate(_this6.el);
|
|
291
334
|
});
|
|
292
335
|
// Replace children root elements with recycled components.
|
|
293
336
|
recycledChildren.forEach(function (recycledChild) {
|
|
294
|
-
|
|
337
|
+
_this6.addChild(recycledChild).recycle(_this6.el);
|
|
295
338
|
});
|
|
296
339
|
// Destroy unused children.
|
|
297
340
|
previousChildren.forEach(function (previousChild) {
|
|
298
341
|
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
299
342
|
if (!found) previousChild.destroy();
|
|
300
343
|
});
|
|
344
|
+
// Restore focus.
|
|
345
|
+
if (this.el.contains(activeElement)) {
|
|
346
|
+
activeElement.focus();
|
|
347
|
+
}
|
|
301
348
|
}
|
|
302
349
|
// Call onRender lifecycle method.
|
|
303
|
-
this.onRender.call(this);
|
|
350
|
+
this.onRender.call(this, 'render');
|
|
304
351
|
// Return this for chaining.
|
|
305
352
|
return this;
|
|
306
353
|
}
|
|
@@ -340,7 +387,6 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
340
387
|
var hydrate = arguments.length > 2 ? arguments[2] : undefined;
|
|
341
388
|
// Instantiate view.
|
|
342
389
|
var view = new this(options);
|
|
343
|
-
|
|
344
390
|
// If `el` is passed, mount component.
|
|
345
391
|
if (el) {
|
|
346
392
|
if (hydrate) {
|
|
@@ -354,6 +400,7 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
354
400
|
}
|
|
355
401
|
view.hydrate(el);
|
|
356
402
|
}
|
|
403
|
+
// Return view instance.
|
|
357
404
|
return view;
|
|
358
405
|
}
|
|
359
406
|
/**
|
|
@@ -390,10 +437,8 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
390
437
|
var main = parts.join('').trim().replace(/\n/g, '');
|
|
391
438
|
// Extract outer tag, attributes and inner html.
|
|
392
439
|
var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
393
|
-
// Remove events listeners.
|
|
394
|
-
var string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g, '');
|
|
395
440
|
// Parse attributes from html text into an object.
|
|
396
|
-
var attributes =
|
|
441
|
+
var attributes = extractAttributes(result[2]);
|
|
397
442
|
// Events to be delegated.
|
|
398
443
|
var events = {};
|
|
399
444
|
// Filter events. To generate events object.
|
|
@@ -423,10 +468,8 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
423
468
|
events: events,
|
|
424
469
|
// Set attributes.
|
|
425
470
|
attributes: attributes,
|
|
426
|
-
// Set template
|
|
471
|
+
// Set template.
|
|
427
472
|
template: {
|
|
428
|
-
// Template including root element (this.el) as part of the template.
|
|
429
|
-
outer: string,
|
|
430
473
|
// Template for innerHTML of root element.
|
|
431
474
|
inner: result[3],
|
|
432
475
|
// Template expressions.
|
package/lib/View.js
CHANGED
|
@@ -72,7 +72,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
72
72
|
// Useful to generate elements ids.
|
|
73
73
|
_this.uid = "uid" + ++View.uid;
|
|
74
74
|
// Store delegated events listeners,
|
|
75
|
-
// so they can be
|
|
75
|
+
// so they can be unbound later.
|
|
76
76
|
_this.delegatedEventListeners = [];
|
|
77
77
|
// Store child views,
|
|
78
78
|
// so they can be destroyed.
|
|
@@ -116,27 +116,24 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
116
116
|
/**
|
|
117
117
|
* Destroy the view.
|
|
118
118
|
* Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
119
|
-
* Pass options.remove as true to remove the view's root element (`this.el`) from the DOM.
|
|
120
|
-
* @param {object} options.remove Remove the view's root element (`this.el`) from the DOM.
|
|
121
119
|
*/;
|
|
122
120
|
_proto.destroy = function destroy() {
|
|
123
|
-
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
124
|
-
remove = _ref.remove;
|
|
125
121
|
// Call destroy on children.
|
|
126
122
|
this.destroyChildren();
|
|
127
123
|
// Undelegate `this.el` event listeners
|
|
128
124
|
this.undelegateEvents();
|
|
129
125
|
// Unbind `this` events.
|
|
130
126
|
this.off();
|
|
131
|
-
// Remove `this.el` if "options.remove" is true.
|
|
132
|
-
if (remove) this.removeElement();
|
|
133
127
|
// Call onDestroy lifecycle method
|
|
134
128
|
this.onDestroy.apply(this, arguments);
|
|
129
|
+
// Return `this` for chaining.
|
|
130
|
+
return this;
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
/**
|
|
138
134
|
* `onDestroy` lifecycle method is called after view is destroyed.
|
|
139
135
|
* Override with your code. Useful to stop listening to model's events.
|
|
136
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
140
137
|
*/;
|
|
141
138
|
_proto.onDestroy = function onDestroy() {}
|
|
142
139
|
|
|
@@ -226,10 +223,12 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
226
223
|
// Store events by type i.e.: "click", "submit", etc.
|
|
227
224
|
var eventTypes = {};
|
|
228
225
|
Object.keys(events).forEach(function (key) {
|
|
229
|
-
var keyParts = key.split(' ')
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
var keyParts = key.split(' ');
|
|
227
|
+
var type = keyParts.shift();
|
|
228
|
+
var selector = keyParts.join(' ');
|
|
229
|
+
var listener = events[key];
|
|
230
|
+
// Listener may be a string representing a method name on the view,
|
|
231
|
+
// or a function.
|
|
233
232
|
listener = (typeof listener === 'string' ? _this2[listener] : listener).bind(_this2);
|
|
234
233
|
if (!eventTypes[type]) eventTypes[type] = [];
|
|
235
234
|
eventTypes[type].push({
|
|
@@ -237,16 +236,14 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
237
236
|
listener: listener
|
|
238
237
|
});
|
|
239
238
|
});
|
|
240
|
-
var indexOf = Array.prototype.indexOf;
|
|
241
239
|
Object.keys(eventTypes).forEach(function (type) {
|
|
242
|
-
|
|
240
|
+
// Listener for the type of event.
|
|
243
241
|
var typeListener = function typeListener(event) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
242
|
+
// Iterate and run every individual listener if the selector matches.
|
|
243
|
+
eventTypes[type].forEach(function (_ref) {
|
|
244
|
+
var selector = _ref.selector,
|
|
245
|
+
listener = _ref.listener;
|
|
246
|
+
if (!selector || event.target.closest(selector)) listener(event, _this2);
|
|
250
247
|
});
|
|
251
248
|
};
|
|
252
249
|
_this2.delegatedEventListeners.push({
|
|
@@ -255,6 +252,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
255
252
|
});
|
|
256
253
|
_this2.el.addEventListener(type, typeListener);
|
|
257
254
|
});
|
|
255
|
+
// Return `this` for chaining.
|
|
258
256
|
return this;
|
|
259
257
|
}
|
|
260
258
|
|
|
@@ -264,12 +262,13 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
264
262
|
*/;
|
|
265
263
|
_proto.undelegateEvents = function undelegateEvents() {
|
|
266
264
|
var _this3 = this;
|
|
267
|
-
this.delegatedEventListeners.forEach(function (
|
|
268
|
-
var type =
|
|
269
|
-
listener =
|
|
265
|
+
this.delegatedEventListeners.forEach(function (_ref2) {
|
|
266
|
+
var type = _ref2.type,
|
|
267
|
+
listener = _ref2.listener;
|
|
270
268
|
_this3.el.removeEventListener(type, listener);
|
|
271
269
|
});
|
|
272
270
|
this.delegatedEventListeners = [];
|
|
271
|
+
// Return `this` for chaining.
|
|
273
272
|
return this;
|
|
274
273
|
}
|
|
275
274
|
|
|
@@ -284,6 +283,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
284
283
|
*/;
|
|
285
284
|
_proto.render = function render() {
|
|
286
285
|
if (this.template) this.el.innerHTML = this.template(this.model);
|
|
286
|
+
// Return `this` for chaining.
|
|
287
287
|
return this;
|
|
288
288
|
};
|
|
289
289
|
return View;
|
package/package.json
CHANGED
package/src/Component.js
CHANGED
|
@@ -14,7 +14,7 @@ const componentOptions = {
|
|
|
14
14
|
* @param text {string} html text
|
|
15
15
|
* @return {object} Object with keys / values representing attributes.
|
|
16
16
|
*/
|
|
17
|
-
const
|
|
17
|
+
const extractAttributes = text => {
|
|
18
18
|
const attributes = {};
|
|
19
19
|
const re = /(\w+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;
|
|
20
20
|
|
|
@@ -106,25 +106,72 @@ export default class Component extends View {
|
|
|
106
106
|
if (this.el) this.delegateEvents();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/*
|
|
110
|
+
* Find view's element on parent node, using id.
|
|
111
|
+
*/
|
|
109
112
|
findElement(parent) {
|
|
110
113
|
return (parent || document).querySelector(`#${this.id}`);
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
/*
|
|
114
|
-
*
|
|
117
|
+
* Eval attributes expressions.
|
|
118
|
+
*/
|
|
119
|
+
getAttributes() {
|
|
120
|
+
const add = {};
|
|
121
|
+
const remove = {};
|
|
122
|
+
const attrs = [];
|
|
123
|
+
|
|
124
|
+
Object.keys(this.attributes).forEach(key => {
|
|
125
|
+
if (key === 'id') return;
|
|
126
|
+
// Evaluate attribute value.
|
|
127
|
+
let value = evalExpression(this.attributes[key], this, this);
|
|
128
|
+
// Transform bool attribute values
|
|
129
|
+
if (value === false) {
|
|
130
|
+
remove[key] = true;
|
|
131
|
+
} else if (value === true) {
|
|
132
|
+
add[key] = '';
|
|
133
|
+
attrs.push(key);
|
|
134
|
+
} else {
|
|
135
|
+
add[key] = value;
|
|
136
|
+
attrs.push(`${key}="${value}"`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
add.id = this.id;
|
|
141
|
+
attrs.push(`id="${this.id}"`);
|
|
142
|
+
|
|
143
|
+
return { add, remove, html : attrs.join(' ') };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* Used internally on the render process.
|
|
148
|
+
* Attach the view to the dom element.
|
|
115
149
|
*/
|
|
116
150
|
hydrate(parent) {
|
|
117
151
|
this.el = this.findElement(parent);
|
|
118
152
|
this.delegateEvents();
|
|
119
153
|
this.children.forEach(child => child.hydrate(this.el));
|
|
120
|
-
this.onRender.call(this);
|
|
154
|
+
this.onRender.call(this, 'hydrate');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* Used internally on the render process.
|
|
159
|
+
* Reuse a view that has `key` when its parent is rendered.
|
|
160
|
+
*/
|
|
161
|
+
recycle(parent) {
|
|
162
|
+
// Find element to be replaced. It has same id.
|
|
163
|
+
const toBeReplaced = this.findElement(parent);
|
|
164
|
+
// Replace it with this.el.
|
|
165
|
+
toBeReplaced.replaceWith(this.el);
|
|
166
|
+
// Call `onRender` lifecycle method.
|
|
167
|
+
this.onRender.call(this, 'recycle');
|
|
121
168
|
}
|
|
122
169
|
|
|
123
170
|
/*
|
|
124
171
|
* Override. Add some custom logic to super `destroy` method.
|
|
125
172
|
*/
|
|
126
|
-
destroy(
|
|
127
|
-
super.destroy(
|
|
173
|
+
destroy() {
|
|
174
|
+
super.destroy.apply(this, arguments);
|
|
128
175
|
// Stop listening to `change`.
|
|
129
176
|
// Set destroyed flag to prevent a last render after destroyed. TODO: Review
|
|
130
177
|
if (this.model) this.model.off('change', this.onChange);
|
|
@@ -144,9 +191,9 @@ export default class Component extends View {
|
|
|
144
191
|
* This method should be extended with custom logic.
|
|
145
192
|
* Maybe comparing new attributes with previous ones and calling
|
|
146
193
|
* render when needed. Or doing some dom transformation.
|
|
147
|
-
* @param model {Rasti.Model}
|
|
148
|
-
* @param key {string}
|
|
149
|
-
* @param value {any}
|
|
194
|
+
* @param model {Rasti.Model} The model that emitted the event.
|
|
195
|
+
* @param key {string} The key that changed.
|
|
196
|
+
* @param value {any} The new value.
|
|
150
197
|
*/
|
|
151
198
|
onChange() {
|
|
152
199
|
this.render();
|
|
@@ -154,11 +201,13 @@ export default class Component extends View {
|
|
|
154
201
|
|
|
155
202
|
/**
|
|
156
203
|
* Lifecycle method. Called when the view is rendered.
|
|
204
|
+
* @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
|
|
157
205
|
*/
|
|
158
206
|
onRender() {}
|
|
159
|
-
|
|
207
|
+
|
|
160
208
|
/**
|
|
161
209
|
* Lifecycle method. Called when the view is destroyed.
|
|
210
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
162
211
|
*/
|
|
163
212
|
onDestroy() {}
|
|
164
213
|
|
|
@@ -205,15 +254,15 @@ export default class Component extends View {
|
|
|
205
254
|
toString() {
|
|
206
255
|
// Normally there won't be any children, but if there are, destroy them.
|
|
207
256
|
this.destroyChildren();
|
|
208
|
-
// Add id to template including root element (this.el) as part of it.
|
|
209
|
-
const tpl = this.attributes.id ?
|
|
210
|
-
this.template.outer :
|
|
211
|
-
this.template.outer.replace(/^<([a-z]+)/, `<$1 id="${this.id}"`);
|
|
212
257
|
// Replace expressions.
|
|
213
|
-
|
|
258
|
+
const inner = this.template.inner && this.replaceExpressions(this.template.inner, (component) => {
|
|
214
259
|
// Add child component.
|
|
215
260
|
return this.addChild(component);
|
|
216
261
|
});
|
|
262
|
+
// Generate outer template.
|
|
263
|
+
return inner ?
|
|
264
|
+
`<${this.tag} ${this.getAttributes().html}>${inner}</${this.tag}>` :
|
|
265
|
+
`<${this.tag} ${this.getAttributes().html} />`;
|
|
217
266
|
}
|
|
218
267
|
|
|
219
268
|
/*
|
|
@@ -222,40 +271,32 @@ export default class Component extends View {
|
|
|
222
271
|
render() {
|
|
223
272
|
// Prevent a last re render if view is already destroyed.
|
|
224
273
|
if (this.destroyed) return this;
|
|
225
|
-
// If
|
|
226
|
-
// create a new element according "this.tag"
|
|
227
|
-
// and "this.attributes".
|
|
274
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
228
275
|
if (!this.el) {
|
|
229
276
|
this.el = this.createElement(this.tag);
|
|
230
277
|
this.delegateEvents();
|
|
231
278
|
}
|
|
232
279
|
// Set `this.el` attributes.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
let value = evalExpression(this.attributes[key], this, this);
|
|
238
|
-
// Transform bool attribute values
|
|
239
|
-
if (value === false) {
|
|
240
|
-
// Remove false attributes.
|
|
241
|
-
this.el.removeAttribute(key);
|
|
242
|
-
} else {
|
|
243
|
-
if (value === true) value = '';
|
|
244
|
-
this.el.setAttribute(key, value);
|
|
245
|
-
}
|
|
280
|
+
const attributes = this.getAttributes();
|
|
281
|
+
// Remove attributes.
|
|
282
|
+
Object.keys(attributes.remove).forEach(key => {
|
|
283
|
+
this.el.removeAttribute(key);
|
|
246
284
|
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
285
|
+
// Add attributes.
|
|
286
|
+
Object.keys(attributes.add).forEach(key => {
|
|
287
|
+
this.el.setAttribute(key, attributes.add[key]);
|
|
288
|
+
});
|
|
289
|
+
// Check for `template.inner` to see if view has innerHTML.
|
|
250
290
|
if (this.template.inner) {
|
|
251
291
|
const previousChildren = this.children;
|
|
292
|
+
|
|
252
293
|
this.children = [];
|
|
253
294
|
|
|
254
295
|
const nextChildren = [];
|
|
255
296
|
const recycledChildren = [];
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// Set html
|
|
297
|
+
// Store active element.
|
|
298
|
+
const activeElement = document.activeElement;
|
|
299
|
+
// Replace expressions. Set html inside of `this.el`.
|
|
259
300
|
this.el.innerHTML = this.replaceExpressions(this.template.inner, (component) => {
|
|
260
301
|
let out = component;
|
|
261
302
|
// Check if child already exists.
|
|
@@ -283,16 +324,20 @@ export default class Component extends View {
|
|
|
283
324
|
});
|
|
284
325
|
// Replace children root elements with recycled components.
|
|
285
326
|
recycledChildren.forEach(recycledChild => {
|
|
286
|
-
this.
|
|
327
|
+
this.addChild(recycledChild).recycle(this.el);
|
|
287
328
|
});
|
|
288
329
|
// Destroy unused children.
|
|
289
330
|
previousChildren.forEach(previousChild => {
|
|
290
331
|
const found = recycledChildren.indexOf(previousChild) > -1;
|
|
291
332
|
if (!found) previousChild.destroy();
|
|
292
333
|
});
|
|
334
|
+
// Restore focus.
|
|
335
|
+
if (this.el.contains(activeElement)) {
|
|
336
|
+
activeElement.focus();
|
|
337
|
+
}
|
|
293
338
|
}
|
|
294
339
|
// Call onRender lifecycle method.
|
|
295
|
-
this.onRender.call(this);
|
|
340
|
+
this.onRender.call(this, 'render');
|
|
296
341
|
// Return this for chaining.
|
|
297
342
|
return this;
|
|
298
343
|
}
|
|
@@ -329,7 +374,6 @@ export default class Component extends View {
|
|
|
329
374
|
static mount(options = {}, el, hydrate) {
|
|
330
375
|
// Instantiate view.
|
|
331
376
|
const view = new this(options);
|
|
332
|
-
|
|
333
377
|
// If `el` is passed, mount component.
|
|
334
378
|
if (el) {
|
|
335
379
|
if (hydrate) {
|
|
@@ -343,7 +387,7 @@ export default class Component extends View {
|
|
|
343
387
|
}
|
|
344
388
|
view.hydrate(el);
|
|
345
389
|
}
|
|
346
|
-
|
|
390
|
+
// Return view instance.
|
|
347
391
|
return view;
|
|
348
392
|
}
|
|
349
393
|
/**
|
|
@@ -381,10 +425,8 @@ export default class Component extends View {
|
|
|
381
425
|
const main = parts.join('').trim().replace(/\n/g, '');
|
|
382
426
|
// Extract outer tag, attributes and inner html.
|
|
383
427
|
const result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
384
|
-
// Remove events listeners.
|
|
385
|
-
const string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g, '');
|
|
386
428
|
// Parse attributes from html text into an object.
|
|
387
|
-
let attributes =
|
|
429
|
+
let attributes = extractAttributes(result[2]);
|
|
388
430
|
// Events to be delegated.
|
|
389
431
|
let events = {};
|
|
390
432
|
// Filter events. To generate events object.
|
|
@@ -415,12 +457,10 @@ export default class Component extends View {
|
|
|
415
457
|
events,
|
|
416
458
|
// Set attributes.
|
|
417
459
|
attributes,
|
|
418
|
-
// Set template
|
|
460
|
+
// Set template.
|
|
419
461
|
template : {
|
|
420
|
-
// Template including root element (this.el) as part of the template.
|
|
421
|
-
outer: string,
|
|
422
462
|
// Template for innerHTML of root element.
|
|
423
|
-
inner: result[3],
|
|
463
|
+
inner : result[3],
|
|
424
464
|
// Template expressions.
|
|
425
465
|
expressions,
|
|
426
466
|
},
|