Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows passing down children components through soy template #66

Merged
merged 10 commits into from
Mar 5, 2015
3 changes: 2 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"evil": false,
"forin": false,
"globals": {
"lfr": true
"lfr": true,
"soy": true
},
"immed": true,
"indent": 2,
Expand Down
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ var registerTasks = require('alloyui-tasks');

registerTasks({
bundleFileName: 'aui.js',
corePathFromSoy: '../',
pkg: pkg
});
6 changes: 4 additions & 2 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ module.exports = function(config) {

jspm: {
// ES6 files need to go through jspm for module loading.
loadFiles: ['src/**/*.js', 'test/**/*.js']
loadFiles: ['test/**/*.js'],
serveFiles: ['src/**/*.js']
},

files: [
'test/html/fixture/*.html',
'node_modules/closure-templates/soyutils.js',
'test/html/fixture/*.html'
],

preprocessors: {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
},
"keywords": [],
"devDependencies": {
"alloyui-tasks": "git://github.com/alloyui/tasks",
"alloyui-tasks": "git://github.com/mairatma/alloyui-tasks#templates",
"closure-templates": "^20141017.0.0",
"gulp": "^3.8.11",
"isparta": "git://github.com/douglasduteil/isparta",
"karma": "^0.12.31",
Expand Down
15 changes: 15 additions & 0 deletions src/array/array.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
'use strict';

class array {
/**
* Checks if the given arrays have the same content.
* @param {!Array<*>} arr1
* @param {!Array<*>} arr2
* @return {boolean}
*/
static equal(arr1, arr2) {
for (var i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return arr1.length === arr2.length;
}

/**
* Returns the first value in the given array that isn't undefined.
* @param {!Array} arr
Expand Down
31 changes: 27 additions & 4 deletions src/component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -649,10 +649,10 @@ class Component extends Attribute {
var surface = this.getSurface(surfaceId);
var cacheState = this.computeSurfaceCacheState_(content);

if (cacheState === Component.Cache.NOT_INITIALIZED ||
surface.cacheMiss = cacheState === Component.Cache.NOT_INITIALIZED ||
cacheState === Component.Cache.NOT_CACHEABLE ||
cacheState !== surface.cacheState) {

cacheState !== surface.cacheState;
if (surface.cacheMiss) {
this.replaceSurfaceContent_(surfaceId, content);
}
surface.cacheState = cacheState;
Expand Down Expand Up @@ -708,10 +708,22 @@ class Component extends Attribute {
dom.addClasses(this.element, classesToAdd);
}

/**
* Validator logic for `children` element.
* @param {*} val
* @return {boolean}
* @protected
*/
validatorChildrenFn_(val) {
return (val instanceof Array) && val.every(function(component) {
return component instanceof Component;
});
}

/**
* Validator logic for element attribute.
* @param {string|Element} val
* @return {Boolean} True if val is a valid element.
* @return {boolean} True if val is a valid element.
* @protected
*/
validatorElementFn_(val) {
Expand Down Expand Up @@ -763,6 +775,17 @@ class Component extends Attribute {
* @static
*/
Component.ATTRS = {
/**
* Child components passed to this component.
* @type {Array<Component>}
*/
children: {
validator: 'validatorChildrenFn_',
valueFn: function() {
return [];
}
},

/**
* Component element bounding box.
* @type {Element}
Expand Down
163 changes: 163 additions & 0 deletions src/component/ComponentCollector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
'use strict';

import dom from '../dom/dom';
import object from '../object/object';
import ComponentRegistry from '../component/ComponentRegistry';
import Disposable from '../disposable/Disposable';

class ComponentCollector extends Disposable {
constructor() {
super();

/**
* Holds the extracted components, indexed by ref.
* @type {!Object<string, !Component>}
* @protected
*/
this.components_ = {};

/**
* Holds the main extracted components (that is, components that are
* not children of other extracted components), indexed by ref.
* @type {!Object<string, !Component>}
* @protected
*/
this.mainComponents_ = {};
}

/**
* Creates a child component and renders it inside the specified parent.
* @param {string} ref The component ref.
* @param {string} name The component name.
* @param {!Object} data The component config data.
* @param {Element} parent The component's parent element.
* @protected
*/
createComponent_(ref, name, data, parent) {
var ConstructorFn = ComponentRegistry.getConstructor(name);
this.components_[ref] = new ConstructorFn(data).render(parent.parentNode, parent);
this.mainComponents_[ref] = this.components_[ref];
parent.parentNode.removeChild(parent);
}

/**
* Gets all the extracted components.
* @return {!Array<!Component>}
*/
getComponents() {
return this.components_;
}

/**
* Gets all the main extracted components.
* @return {!Array<!Component>}
*/
getMainComponents() {
return this.mainComponents_;
}

/**
* Handles the child component with the given ref, creating it for the first
* time or updating it in case it doesn't exist yet.
* @param {!Object} data The child component's template call data.
* @return {!Component} The child component's instance.
* @protected
*/
extractChild_(data) {
var component = this.components_[data.ref];
if (component) {
component.setAttrs(data.data);
} else {
var ConstructorFn = ComponentRegistry.getConstructor(data.name);
component = new ConstructorFn(data.data);
this.components_[data.ref] = component;
delete this.mainComponents_[data.ref];
}
return component;
}

/**
* Handles the given array of rendered child soy templates, converting them to
* component instances.
* @param {string} children Rendered children.
* @param {string} ref The parent component's ref.
* @param {!Object<string, !Object>} componentData An object with creation
* data for components that may be found inside the element, indexed by
* their ref strings.
*/
extractChildren(children, parentRef, componentData) {
var parentData = componentData[parentRef];
parentData.data = object.mixin({}, parentData.data);
parentData.data.children = [];

var frag = dom.buildFragment(children);
for (var i = 0; i < frag.childNodes.length; i++) {
var ref = frag.childNodes[i].getAttribute('data-ref');
var data = componentData[ref];
if (data.children) {
this.extractChildren(data.children.content, ref, componentData);
}
parentData.data.children.push(this.extractChild_(data));
}
}

/**
* Extracts a component from the given element.
* @param {!Element} element Element that represents a component that should
* be extracted.
* @param {string} ref The component's ref.
* @param {!Object<string, !Object>} componentData An object with creation
* data for components that may be found inside the element, indexed by
* their ref strings.
* @protected
*/
extractComponent_(element, ref, componentData) {
var data = componentData[ref];
if (!data) {
return;
}

if (data.children) {
this.extractChildren(data.children.content, ref, componentData);
}

if (this.components_[data.ref]) {
this.updateComponent_(data.ref, data.data, element);
} else {
this.createComponent_(data.ref, data.name, data.data, element);
}
}

/**
* Extracts components from the given element.
* @param {!Element} element
* @param {!Object<string, !Object>} componentData An object with creation
* data for components that may be found inside the element, indexed by
* their ref strings.
* @return {!Object<string, !Object>} The original `componentData` object.
*/
extractComponents(element, componentData) {
if (element.hasAttribute && element.hasAttribute('data-component')) {
var ref = element.getAttribute('data-ref');
this.extractComponent_(element, ref, componentData);
}

return componentData;
}

/**
* Updates a child component's data and parent.
* @param {string} ref The component's ref.
* @param {!Object} data The component's data.
* @param {Element} parent The component's parent element.
* @protected
*/
updateComponent_(ref, data, parent) {
var component = this.components_[ref];
parent.parentNode.insertBefore(component.element, parent);
parent.parentNode.removeChild(parent);
component.setAttrs(data);
}
}

export default ComponentCollector;
47 changes: 47 additions & 0 deletions src/component/ComponentRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

/**
* The component registry is used to register components, so they can
* be accessible by name.
* @type {Object}
*/
class ComponentRegistry {
/**
* Gets the constructor function for the given component name, or
* undefined if it hasn't been registered yet.
* @param {string} name The component's name.
* @return {?function}
* @static
*/
static getConstructor(name) {
return ComponentRegistry.components_[name];
}

/**
* Registers a component.
* @param {string} name The component's name.
* @param {string} constructorFn The component's constructor function.
* @static
*/
static register(name, constructorFn) {
ComponentRegistry.components_[name] = constructorFn;
}
}

/**
* Holds all registered components, indexed by their names.
* @type {!Object<string, function()>}
* @protected
* @static
*/
ComponentRegistry.components_ = {};

/**
* Holds all registered component templates, indexed by component names.
* Soy files automatically add their templates to this object when imported.
* @type {!Object<string, !Object<string, !function()>>}
* @static
*/
ComponentRegistry.Templates = {};

export default ComponentRegistry;
Loading