Skip to content

Instantly share code, notes, and snippets.

@ryanjduffy
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanjduffy/310cfff06b7d9901e6cd to your computer and use it in GitHub Desktop.
Save ryanjduffy/310cfff06b7d9901e6cd to your computer and use it in GitHub Desktop.
enyo.attach prototype

Overview

Exercise to allow an enyo kind to "attach" to existing markup for progressive enhancement, web component-style integration, or preference for markup over enyo-style components[] block.

View it in action on JSFiddle

Attaching a kind to a control

Define it and call enyo.attach with a reference to the kind and target node

enyo.kind({name: <kind name>});
enyo.attach(
    <kind or kind name>,
    <node, id, or document.body>
);

Call enyo.attach with a config object and a reference to the target node and it will define and create the kind

enyo.attach(
    <config>,
    <node, id, or document.body>
);

Same as before except that the target node included in the config object on the attachTo property

enyo.attach(<config with attachTo property>);

Manually add the mixin and attachTo property and instantiate the object. Effectively the same as the first option but not recommended because it would potentially short-circuit any future logic that might be added to enyo.attach()

enyo.kind({
    name: <kind name>,
    mixins: [enyo.Attachable],
    attachTo: <node, id, or document.body>
});
new <kind>();

Same as above except skipping the definition of the kind and just create it

enyo.singleton({ mixins, attachTo });
.enyo-panels > * {
background: #f8f8f8;
width: 320px;
margin: 1em;
padding: 1em;
}
<div id="panels" class="enyo-fit" data-kind="enyo.Panels" arranger-kind="enyo.CarouselArranger" wrap>
<div>
<label data-kind="onyx.InputDecorator">
<input name="email" placeholder="Email Address" data-kind="onyx.Input" />
</label>
</div>
<div>
<label name="second" data-kind="onyx.InputDecorator">
<input name="noKind" type="checkbox"/>
Here's the content
</label>
<button data-kind="onyx.Button" ontap="buttonTapped">I'm a button</button>
</div>
<div>
I'm the third panel
</div>
</div>
enyo.kind({
name: "enyo.TextNode",
kind: "enyo.Control",
tag: null,
setAttribute: enyo.nop,
getAttribute: enyo.nop,
renderContent: function() {
if(this.node) {
this.node.nodeValue = this.content || "";
}
}
});
(function(enyo) {
var attributeImportMap = {
"class": "classes",
// name and id are explicitly imported
"name": false,
"id": false
};
// special kinds that don't match their element name
var elementToKind = {
"a": "enyo.Anchor",
"img": "enyo.Image",
"textarea": "enyo.TextArea"
};
function parseKinds(root) {
var children = [];
for(var i=0, l=root.childNodes.length; i<l; i++) {
var config = parseNode(root.childNodes.item(i));
config && children.push(config);
}
return children;
}
function parseNode(k) {
var config;
// ignore whitespace text nodes
if(k.nodeType === 3) {
if(/\S/.test(k.nodeValue)) {
config = {
kind: "enyo.TextNode",
content: k.nodeValue,
node: k,
generated: true
};
}
} else if(k.nodeType === 1) {
var comps = parseKinds(k);
var content;
if(comps.length === 1 && comps[0].kind == "enyo.TextNode") {
content = comps[0].content;
comps = null;
}
config = {
id: k.id,
name: k.getAttribute("name") || k.id,
kind: determineKind(k),
node: k,
generated: true,
components: comps,
content: content,
};
importAttributes(k, config)
}
return config;
}
function importAttributes(node, config) {
var attr = node.attributes;
for(var i=0, l=attr.length; i<l; i++) {
var a = attr.item(i);
var isData = a.name.substring(0, 5) == "data-";
var map = attributeImportMap[a.name];
if(!(isData || map === false)) {
// if the attr is not in the map or is true,
// use the attr name as the property
var name = (!map || map === true)? a.name : map;
// hyphen to camelCase
name = name.replace(/-([a-z])/gi, function(s, group1) {
return group1.toUpperCase();
});
var value = a.value;
if(value === "") {
value = true;
}
config[name] = a.value;
}
}
}
function determineKind(k) {
var kind = k.getAttribute("data-kind");
if(!kind) {
var nodeName = k.nodeName.toLowerCase();
kind = enyo.cap(nodeName);
if(nodeName === "input") {
if(k.getAttribute("type") === "checkbox") {
kind = "enyo.Checkbox";
} else {
kind = "enyo.Input";
}
} else if(enyo[kind]) {
kind = "enyo." + kind;
} else {
kind = elementToKind[nodeName];
}
}
return kind || "enyo.Control";
}
var attached = [];
enyo.ready(function() {
enyo.forEach(attached, function(a) {
a.attachToNode();
});
});
enyo.Attachable = {
constructed: enyo.inherit(function(sup) {
return function() {
sup.apply(this, arguments);
attached.push(this);
};
}),
attached: enyo.inherit(function(sup) {
return function() {
sup.apply(this, arguments);
this.rendered();
};
}),
attachToNode: function(node) {
if(!node && this.attachTo) {
node = this.attachTo;
}
if(enyo.isString(node)) {
// special case: if the code is loaded in HEAD,
// document.body isn't defined yet so let's provide
// a way to specify it without assigning it an ID
if(node === "document.body") {
node = document.body;
} else {
node = document.getElementById(node);
}
}
if(!node) {
console.error("Node does not exist");
return;
}
var config = parseNode(node);
var keys = enyo.keys(config);
enyo.forEach(keys, function(prop) {
if(prop === "components") {
this.createComponents(config.components, {owner: this});
} else {
this.set(prop, config[prop]);
}
}, this);
node.enyoInstance = this;
this.attached();
},
configureComponents: function(config) {
enyo.forEach(config, function(c) {
var name = c.name;
var control = name && this.$[name];
if(control) {
enyo.forEach(enyo.keys(c), function(prop) {
control.set(prop, c[prop]);
});
}
}, this);
}
};
enyo.attach = function(kind, node) {
if(!enyo.isFunction(kind)) {
if(enyo.isString(kind)) {
kind = enyo.constructorForKind(kind);
} else {
// allow node to override attachTo but honor
// attachTo if node is undefined
if(!node && kind.attachTo) {
node = kind.attachTo;
delete kind.attachTo;
}
// the kind will be an anonymous singleton
// accessible from the return value of enyo.attach
// or via node.enyoInstance
delete kind.name;
kind = enyo.kind(kind);
}
}
config = {
mixins: [enyo.Attachable],
attachTo: node
}
return new kind(config);
}
})(enyo);
var App = enyo.attach({
attached: enyo.inherit(function(sup) {
return function() {
sup.apply(this, arguments);
this.configureComponents([
{name: "panels", arrangerKind: "enyo.CarouselArranger", narrowFit: false},
{name: "button", ontap: "buttonTapped"}
]);
// Panels doesn't like this approach but a flow
// seems to coax it into compliance. More specifically,
// Arranger sets up a cache of the panels in this.c$ during
// flow() which is required for arranger animations
this.$.panels.flow();
};
}),
buttonTapped: function(inSender, inEvent) {
alert("email = " + this.$.email.getValue());
this.$.panels.set("index", 2);
}
}, "document.body");
name: enyo.attach prototype
description: Exercise to allow an enyo kind to "attach" to existing markup for progressive enhancement, web component-style integration, or preference for markup over enyo-style components[] block
authors:
- Ryan Duffy
normalize_css: no
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment