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