rasti 2.0.0 → 2.1.0-alpha.0

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/es/Component.js CHANGED
@@ -94,11 +94,22 @@ var Component = /*#__PURE__*/function (_View) {
94
94
  }
95
95
 
96
96
  /*
97
- * Override. We don't want to ensure an element on instantiation.
98
- * We will provide it later.
99
- */
97
+ * Tell if component is a container.
98
+ * In which case, it will not have an element by itself.
99
+ * It will render a single expression which is expected to return a single component as child.
100
+ * `this.el` will be a reference to that child component's element.
101
+ * @return {boolean}
102
+ */
100
103
  _inheritsLoose(Component, _View);
101
104
  var _proto = Component.prototype;
105
+ _proto.isContainer = function isContainer() {
106
+ return !!(!this.tag && this.template && this.template.inner && this.template.expressions && this.template.expressions.length === 1);
107
+ }
108
+
109
+ /*
110
+ * Override. We don't want to ensure an element on instantiation.
111
+ * We will provide it later.
112
+ */;
102
113
  _proto.ensureElement = function ensureElement() {
103
114
  // If el is provided, delegate events.
104
115
  if (this.el) {
@@ -106,7 +117,7 @@ var Component = /*#__PURE__*/function (_View) {
106
117
  this.id = this.el.id;
107
118
  }
108
119
  // Ensure id.
109
- if (!this.id) {
120
+ if (!this.isContainer() && !this.id) {
110
121
  this.id = this.attributes && this.attributes.id ?
111
122
  // If id is provided, evaluate it.
112
123
  evalExpression(this.attributes.id, this, this) :
@@ -117,6 +128,8 @@ var Component = /*#__PURE__*/function (_View) {
117
128
 
118
129
  /*
119
130
  * Find view's element on parent node, using id.
131
+ * @param parent {node} The parent node.
132
+ * @return {node} The component's element.
120
133
  */;
121
134
  _proto.findElement = function findElement(parent) {
122
135
  return (parent || document).querySelector("#" + this.id);
@@ -124,6 +137,7 @@ var Component = /*#__PURE__*/function (_View) {
124
137
 
125
138
  /*
126
139
  * Eval attributes expressions.
140
+ * @return {object} Object containing add, remove and html properties.
127
141
  */;
128
142
  _proto.getAttributes = function getAttributes() {
129
143
  var _this2 = this;
@@ -161,40 +175,57 @@ var Component = /*#__PURE__*/function (_View) {
161
175
  /*
162
176
  * Used internally on the render process.
163
177
  * Attach the view to the dom element.
178
+ * @param parent {node} The parent node.
179
+ * @return {Rasti.Component} The component instance.
164
180
  */;
165
181
  _proto.hydrate = function hydrate(parent) {
166
182
  var _this3 = this;
167
- this.el = this.findElement(parent);
168
- this.delegateEvents();
169
- this.children.forEach(function (child) {
170
- return child.hydrate(_this3.el);
171
- });
183
+ if (!this.isContainer()) {
184
+ this.el = this.findElement(parent);
185
+ this.delegateEvents();
186
+ this.children.forEach(function (child) {
187
+ return child.hydrate(_this3.el);
188
+ });
189
+ } else {
190
+ this.children[0].hydrate(parent);
191
+ this.el = this.children[0].el;
192
+ }
193
+ // Call `onRender` lifecycle method.
172
194
  this.onRender.call(this, 'hydrate');
195
+ // Return `this` for chaining.
196
+ return this;
173
197
  }
174
198
 
175
199
  /*
176
200
  * Used internally on the render process.
177
201
  * Reuse a view that has `key` when its parent is rendered.
202
+ * @param parent {node} The parent node.
203
+ * @return {Rasti.Component} The component instance.
178
204
  */;
179
205
  _proto.recycle = function recycle(parent) {
180
206
  // Find element to be replaced. It has same id.
181
- var toBeReplaced = this.findElement(parent);
207
+ var toBeReplaced = (this.isContainer() ? this.children[0] : this).findElement(parent);
182
208
  // Replace it with this.el.
183
209
  toBeReplaced.replaceWith(this.el);
184
210
  // Call `onRender` lifecycle method.
185
211
  this.onRender.call(this, 'recycle');
212
+ // Return `this` for chaining.
213
+ return this;
186
214
  }
187
215
 
188
216
  /*
189
217
  * Override. Add some custom logic to super `destroy` method.
218
+ * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
190
219
  */;
191
220
  _proto.destroy = function destroy() {
192
221
  _View.prototype.destroy.apply(this, arguments);
193
222
  // Stop listening to `change`.
194
- // Set destroyed flag to prevent a last render after destroyed. TODO: Review
195
223
  if (this.model && this.model.off) this.model.off('change', this.onChange);
196
224
  if (this.state && this.state.off) this.state.off('change', this.onChange);
225
+ // Set destroyed flag to prevent a last render after destroyed.
197
226
  this.destroyed = true;
227
+ // Return `this` for chaining.
228
+ return this;
198
229
  }
199
230
 
200
231
  /**
@@ -257,12 +288,12 @@ var Component = /*#__PURE__*/function (_View) {
257
288
  return out + parsed;
258
289
  }, '');
259
290
  })
260
- // Replace `attribute="true"` with `attribute`
261
- .replace(new RegExp("([a-z]+)=[\"|']" + Component.TRUE_PLACEHOLDER + "[\"|']", 'g'), '$1')
262
- // Replace `attribute="false"` with empty string.
263
- .replace(new RegExp("([a-z]+)=[\"|']" + Component.FALSE_PLACEHOLDER + "[\"|']", 'g'), '')
264
- // Replace rest of false expressions with empty string.
265
- .replace(new RegExp(Component.FALSE_PLACEHOLDER, 'g'), '');
291
+ // Replace `attribute="true"` with `attribute` and `attribute="false"` with empty string.
292
+ .replace(new RegExp("([\\w|data-]+)=([\"'])?(" + Component.TRUE_PLACEHOLDER + "|" + Component.FALSE_PLACEHOLDER + ")\\2", 'g'), function (match, attribute, quote, placeholder) {
293
+ return placeholder === Component.TRUE_PLACEHOLDER ? attribute : '';
294
+ })
295
+ // Replace rest of true or false expressions with empty string.
296
+ .replace(new RegExp(Component.TRUE_PLACEHOLDER + "|" + Component.FALSE_PLACEHOLDER, 'g'), '');
266
297
  }
267
298
 
268
299
  /*
@@ -272,17 +303,20 @@ var Component = /*#__PURE__*/function (_View) {
272
303
  var _this5 = this;
273
304
  // Normally there won't be any children, but if there are, destroy them.
274
305
  this.destroyChildren();
275
- // Get tag name.
276
- var tag = this.tag || 'div';
277
- // Get attributes.
278
- var attributes = this.getAttributes().html;
279
306
  // Replace expressions of inner template.
280
- var inner = this.template && this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
307
+ var close = this.template && this.template.inner;
308
+ var inner = close && this.replaceExpressions(this.template.inner, function (component) {
281
309
  // Add child component.
282
310
  return _this5.addChild(component);
283
311
  });
312
+ // If component is a container, return inner which will be an expression thats return a single component.
313
+ if (this.isContainer()) return inner;
314
+ // Get tag name.
315
+ var tag = this.tag || 'div';
316
+ // Get attributes.
317
+ var attributes = this.getAttributes().html;
284
318
  // Generate outer template.
285
- return inner ? "<" + tag + " " + attributes + ">" + inner + "</" + tag + ">" : "<" + tag + " " + attributes + " />";
319
+ return close ? "<" + tag + " " + attributes + ">" + inner + "</" + tag + ">" : "<" + tag + " " + attributes + " />";
286
320
  }
287
321
 
288
322
  /*
@@ -292,31 +326,33 @@ var Component = /*#__PURE__*/function (_View) {
292
326
  var _this6 = this;
293
327
  // Prevent a last re render if view is already destroyed.
294
328
  if (this.destroyed) return this;
295
- // If `this.el` is not present, create a new `this.tag` element.
296
- if (!this.el) {
297
- this.el = this.createElement(this.tag);
298
- this.delegateEvents();
329
+ if (!this.isContainer()) {
330
+ // If `this.el` is not present, create a new `this.tag` element.
331
+ if (!this.el) {
332
+ this.el = this.createElement(this.tag);
333
+ this.delegateEvents();
334
+ }
335
+ // Set `this.el` attributes.
336
+ var attributes = this.getAttributes();
337
+ // Remove attributes.
338
+ Object.keys(attributes.remove).forEach(function (key) {
339
+ _this6.el.removeAttribute(key);
340
+ });
341
+ // Add attributes.
342
+ Object.keys(attributes.add).forEach(function (key) {
343
+ _this6.el.setAttribute(key, attributes.add[key]);
344
+ });
299
345
  }
300
- // Set `this.el` attributes.
301
- var attributes = this.getAttributes();
302
- // Remove attributes.
303
- Object.keys(attributes.remove).forEach(function (key) {
304
- _this6.el.removeAttribute(key);
305
- });
306
- // Add attributes.
307
- Object.keys(attributes.add).forEach(function (key) {
308
- _this6.el.setAttribute(key, attributes.add[key]);
309
- });
310
346
  // Check for `template.inner` to see if view has innerHTML.
311
347
  if (this.template && this.template.inner) {
312
- var previousChildren = this.children;
313
- this.children = [];
314
- var nextChildren = [];
315
- var recycledChildren = [];
316
348
  // Store active element.
317
349
  var activeElement = document.activeElement;
350
+ var nextChildren = [];
351
+ var recycledChildren = [];
352
+ var previousChildren = this.children;
353
+ this.children = [];
318
354
  // Replace expressions. Set html inside of `this.el`.
319
- this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {
355
+ var inner = this.replaceExpressions(this.template.inner, function (component) {
320
356
  var out = component;
321
357
  // Check if child already exists.
322
358
  var found = component.key && previousChildren.find(function (previousChild) {
@@ -338,14 +374,32 @@ var Component = /*#__PURE__*/function (_View) {
338
374
  // Component html.
339
375
  return out;
340
376
  });
341
- // Add new children. Hydrate them.
342
- nextChildren.forEach(function (nextChild) {
343
- _this6.addChild(nextChild).hydrate(_this6.el);
344
- });
345
- // Replace children root elements with recycled components.
346
- recycledChildren.forEach(function (recycledChild) {
347
- _this6.addChild(recycledChild).recycle(_this6.el);
348
- });
377
+ if (this.isContainer()) {
378
+ if (nextChildren[0]) {
379
+ var fragment = this.createElement('template');
380
+ fragment.innerHTML = inner;
381
+ // Add new child to dom fragment and hydrate it.
382
+ this.addChild(nextChildren[0]).hydrate(fragment.content);
383
+ // Get next child element.
384
+ var nextEl = fragment.content.children[0];
385
+ // If `this.el` is present, replace it with nextEl.
386
+ if (this.el) this.el.replaceWith(nextEl);
387
+ // Set `this.el` to nextEl.
388
+ this.el = nextEl;
389
+ } else if (recycledChildren[0]) {
390
+ this.addChild(recycledChildren[0]);
391
+ }
392
+ } else {
393
+ this.el.innerHTML = inner;
394
+ // Add new children. Hydrate them.
395
+ nextChildren.forEach(function (nextChild) {
396
+ _this6.addChild(nextChild).hydrate(_this6.el);
397
+ });
398
+ // Replace children root elements with recycled components.
399
+ recycledChildren.forEach(function (recycledChild) {
400
+ _this6.addChild(recycledChild).recycle(_this6.el);
401
+ });
402
+ }
349
403
  // Destroy unused children.
350
404
  previousChildren.forEach(function (previousChild) {
351
405
  var found = recycledChildren.indexOf(previousChild) > -1;
@@ -399,11 +453,11 @@ var Component = /*#__PURE__*/function (_View) {
399
453
  if (hydrate) {
400
454
  view.toString();
401
455
  } else {
402
- var fragment = document.createElement('template');
456
+ var fragment = this.prototype.createElement('template');
403
457
  // Add html text into element inner html.
404
458
  fragment.innerHTML = view;
405
459
  // Add to dom.
406
- el.appendChild(fragment.content);
460
+ el.appendChild(fragment.content.children[0]);
407
461
  }
408
462
  view.hydrate(el);
409
463
  }
@@ -423,13 +477,19 @@ var Component = /*#__PURE__*/function (_View) {
423
477
  * - If the function returns a component instance, it will be added as a child component.
424
478
  * - If the function returns an array, each item will be evaluated as above.
425
479
  * @static
426
- * @param {string} HTML template for the component.
480
+ * @param {string} HTML template for the component. Or a function that mounts a sub component.
427
481
  * @return {Rasti.Component}
428
482
  */;
429
483
  Component.create = function create(strings) {
430
484
  for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
431
485
  expressions[_key2 - 1] = arguments[_key2];
432
486
  }
487
+ // Containers can be created using create as a functions instead of a tagged template.
488
+ if (typeof strings === 'function') {
489
+ expressions = [strings];
490
+ strings = ['', ''];
491
+ }
492
+ var tag, attributes, inner, events;
433
493
  var parts = [];
434
494
  // Replace functions and objects interpolations with `{number}`.
435
495
  // Where `number` is the index on expressions array.
@@ -444,31 +504,35 @@ var Component = /*#__PURE__*/function (_View) {
444
504
  // Create output text for main template.
445
505
  var main = parts.join('').trim().replace(/\n/g, '');
446
506
  // Extract outer tag, attributes and inner html.
447
- var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
448
- // Parse attributes from html text into an object.
449
- var attributes = extractAttributes(result[2]);
450
- // Events to be delegated.
451
- var events = {};
452
- // Filter events. To generate events object.
453
- // Generate attributes object, replace placeholders with expressions.
454
- attributes = Object.keys(attributes).reduce(function (out, key) {
455
- // Is Event?
456
- var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
457
- // Is placeholder for function or object?
458
- // Get expression or value.
459
- var value = getExpression(attributes[key], expressions);
460
- // Is event handler. Add to events object.
461
- if (matchKey && matchKey[1]) {
462
- var eventType = matchKey[1].toLowerCase();
463
- Object.keys(value).forEach(function (selector) {
464
- return events["" + eventType + (selector === '&' ? '' : " " + selector)] = value[selector];
465
- });
507
+ var result = main.match(/^<([a-z]+[1-6]?)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
508
+ if (result) {
509
+ tag = result[1];
510
+ inner = result[3];
511
+ // Parse attributes from html text into an object.
512
+ attributes = extractAttributes(result[2]);
513
+ // Events to be delegated.
514
+ events = {};
515
+ // Filter events. To generate events object.
516
+ // Generate attributes object, replace placeholders with expressions.
517
+ attributes = Object.keys(attributes).reduce(function (out, key) {
518
+ // Is Event?
519
+ var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
520
+ // Is placeholder for function or object?
521
+ // Get expression or value.
522
+ var value = getExpression(attributes[key], expressions);
523
+ // Is event handler. Add to events object.
524
+ if (matchKey && matchKey[1]) {
525
+ var eventType = matchKey[1].toLowerCase();
526
+ Object.keys(value).forEach(function (selector) {
527
+ return events["" + eventType + (selector === '&' ? '' : " " + selector)] = value[selector];
528
+ });
529
+ return out;
530
+ }
531
+ // Is attribute. Add to attributes object.
532
+ out[key] = value;
466
533
  return out;
467
- }
468
- // Is attribute. Add to attributes object.
469
- out[key] = value;
470
- return out;
471
- }, {});
534
+ }, {});
535
+ } else inner = main;
472
536
  var Current = this;
473
537
  // Create subclass for this component.
474
538
  return Current.extend({
@@ -479,12 +543,12 @@ var Component = /*#__PURE__*/function (_View) {
479
543
  // Set template.
480
544
  template: {
481
545
  // Template for innerHTML of root element.
482
- inner: result[3],
546
+ inner: inner,
483
547
  // Template expressions.
484
548
  expressions: expressions
485
549
  },
486
550
  // Set root element tag.
487
- tag: result[1]
551
+ tag: tag
488
552
  });
489
553
  };
490
554
  return Component;
package/es/View.js CHANGED
@@ -111,6 +111,7 @@ var View = /*#__PURE__*/function (_Emitter) {
111
111
  /**
112
112
  * Destroy the view.
113
113
  * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
114
+ * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
114
115
  * @return {Rasti.View} Return `this` for chaining.
115
116
  */;
116
117
  _proto.destroy = function destroy() {