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/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 getAttributes = function getAttributes(text) {
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
- * Used internally on the render process. Attach the view to the dom element.
126
+ * Eval attributes expressions.
123
127
  */;
124
- _proto.hydrate = function hydrate(parent) {
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(_this2.el);
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(options) {
138
- _View.prototype.destroy.call(this, options);
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 _this3 = this;
232
+ var _this4 = this;
181
233
  return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)'), 'g'), function (match) {
182
- var expression = getExpression(match, _this3.template.expressions);
234
+ var expression = getExpression(match, _this4.template.expressions);
183
235
  // Eval expression. Pass view as argument.
184
- var result = evalExpression(expression, _this3, _this3);
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 _this4 = this;
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
- return this.replaceExpressions(tpl, function (component) {
272
+ var inner = this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
223
273
  // Add child component.
224
- return _this4.addChild(component);
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 _this5 = this;
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 "this.el" is not present,
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
- Object.keys(this.attributes).forEach(function (key) {
244
- // Omit changing id.
245
- if (key === 'id') return;
246
- // Evaluate attribute value.
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
- // Check for `template.inner`.
259
- // Root element may be a self enclosed tag element without innerHTML.
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
- // Replace expressions.
267
- // Set html text inside `this.el`. Make it part of the dom.
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
- _this5.addChild(nextChild).hydrate(_this5.el);
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
- _this5.el.replaceChild(_this5.addChild(recycledChild).el, recycledChild.findElement(_this5.el));
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 = getAttributes(result[2]);
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 data.
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 unbinded later.
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
- type = keyParts.shift(),
231
- selector = keyParts.join(' '),
232
- listener = events[key];
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
- var self = _this2;
240
+ // Listener for the type of event.
243
241
  var typeListener = function typeListener(event) {
244
- eventTypes[type].forEach(function (_ref2) {
245
- var selector = _ref2.selector,
246
- listener = _ref2.listener;
247
- if (indexOf.call(selector ? self.el.querySelectorAll(selector) : [self.el], event.target) > -1) {
248
- listener(event, self);
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 (_ref3) {
268
- var type = _ref3.type,
269
- listener = _ref3.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rasti",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.6",
4
4
  "description": "Minimalistic, ultra-lightweight JavaScript library for building user interfaces.",
5
5
  "main": "lib/index.js",
6
6
  "jsnext:main": "es/index.js",
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 getAttributes = text => {
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
- * Used internally on the render process. Attach the view to the dom element.
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(options) {
127
- super.destroy(options);
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
- return this.replaceExpressions(tpl, (component) => {
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 "this.el" is not present,
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
- Object.keys(this.attributes).forEach(key => {
234
- // Omit changing id.
235
- if (key === 'id') return;
236
- // Evaluate attribute value.
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
- // Check for `template.inner`.
249
- // Root element may be a self enclosed tag element without innerHTML.
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
- // Replace expressions.
258
- // Set html text inside `this.el`. Make it part of the dom.
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.el.replaceChild(this.addChild(recycledChild).el, recycledChild.findElement(this.el));
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 = getAttributes(result[2]);
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 data.
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
  },