Skip to content

Commit

Permalink
Merge pull request #23 from sjmiles/master
Browse files Browse the repository at this point in the history
MDV sugaring
  • Loading branch information
Steve Orvell committed Oct 26, 2012
2 parents 2958f61 + 6673c11 commit 6696b85
Showing 1 changed file with 196 additions and 27 deletions.
223 changes: 196 additions & 27 deletions src/g-component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
// conventional names have implicit bindings
var conventions = {
PROPERTY_CHANGED_SUFFIX: "Changed",
CUSTOM_EVENT_PREFIX: "at"
CUSTOM_EVENT_PREFIX: "on-"
};

// polyfill for DOMTokenList features: list of classes in add/remove;
// toggle method.
// polyfill for DOMTokenList features:
// add/remove multiple classes
// arity-2 toggle

(function() {
'use strict';
var add = DOMTokenList.prototype.add;
Expand All @@ -44,6 +46,12 @@
(function(){

// attribute bindings
//
// <element attributes="attr1, attr2..."
//
// create matching observed properties attr1, attr2, etc.
// and attaches MutationObserver so that changes in the attributes
// are reflected in the matching properties

var bindAttrs = function(inAttributes) {
var attrs = this.boundAttributes = [];
Expand All @@ -58,6 +66,11 @@
};

// event bindings
//
// <element events="onclick: click, onkeypress: keypress, ..."
//
// create listeners on instances that map named events to
// methods on the instance

var bindEvents = function(inEvents) {
if (inEvents) {
Expand All @@ -73,9 +86,45 @@
}, this);
}
};

// model bindings
//
// convert {{macro}} strings in markup into MDV bindings
//
// MDV usually does this work but requires an additional
// nested template and functions asynchronously

var bindModel = function(inNodes) {
var m = bindModel.mustache;
Array.prototype.forEach.call(inNodes, function(n) {
n = n.baby || n;
if (n.nodeName == "#text") {
if (n.textContent.search(m) >= 0) {
//console.log('addBinding', n.textContent, n);
n.addBinding(n.textContent);
}
} else if (n.attributes && n.tagName !== "TEMPLATE") {
// attribute bindings
Array.prototype.forEach.call(n.attributes, function(a) {
if (a.value.search(m) >= 0) {
//console.log('addBinding', a.name, a.value, n);
n.addBinding(a.name, a.value);
}
});
bindModel(n.childNodes);
}
});
};
bindModel.mustache = /\{\{([^{}]*)}}/g;

// property bindings

//
// sets up observable properties
//
// an observable property with <name> will automatically call
// <name>Changed method when the property value changes
// (if <name>Changed exists)

var propertyChanged = function(inName, inOld) {
var fn = inName + conventions.PROPERTY_CHANGED_SUFFIX;
if (this[fn]) {
Expand Down Expand Up @@ -108,7 +157,7 @@
// set default value in a property already bound via attrs
setPropertySilently.call(this, inName, value);
} else {
console.log('binding', inName, this)
//console.log('binding', inName, this)
var value = inValue;
var sideEffect = sideEffectFactory(inName);
Object.defineProperty(this, inName, {
Expand All @@ -134,6 +183,11 @@
}
};

// node bindings
//
// local nodes that have ID and store references to them in
// this.$ hash

var deref = function(inNode) {
return inNode && (inNode.baby || inNode);
};
Expand All @@ -148,7 +202,73 @@
}, this);
}
};

// data bindings
//
// <name>: ['<path dependency>', '<path dependency>'...]
//
// bind MDV macros of the form {{<name>}} to formatting methods
// in inNode using MDV delegates
//

var bindDelegates = function(inDelegates) {
if (inDelegates) {
var node = this;
this.modelDelegate = function(inBinding) {
if (inDelegates[inBinding]) {
return [
inDelegates[inBinding],
function(/*inValues*/) {
return delegateBinding(node, inBinding + "Out", arguments);
},
function(/*inValues*/) {
return delegateBinding(node, inBinding + "In", arguments);
}
];
}
};
}
};

var delegateBinding = function(inNode, inDelegate, inValues) {
var fn = inNode[inDelegate];
var value = fn ? fn.apply(inNode, inValues) : undefined;
if (value === undefined) {
value = inValues[0];
}
//console.log(inDelegate, inValues, value);
return value;
};

var deref = function(inNode) {
return inNode && (inNode.baby || inNode);
};

var establishNodeReferences = function(inRoot) {
this.$ = this.$ || {};
// search the LOCAL tree
if (inRoot) {
var nodes = ShadowDOM.localQueryAll(inRoot, "[id]");
Array.prototype.forEach.call(nodes, function(n) {
this.$[n.id] = deref(n);
}, this);
}
};

/*
var $$ = function(inSlctr) {
var s = this.shadow;
while(s){
var n = s.querySelector(inSlctr);
if (n) {
return n;
}
s = s.olderSubtree;
}
return s;
};
*/

// attribute mutations

var deserializeValue = function(inValue) {
Expand All @@ -165,6 +285,10 @@
return isNaN(n) ? inValue : n;
};

// propagate initial bound-attribute values into properties
// as a batch, invoking side-effects only after all
// properties are initialized

var takeAttributes = function() {
var changed = [];
try {
Expand All @@ -188,8 +312,7 @@
};

var attributeChanged = function(inName) {
var value = this.getAttribute(inName);
this[inName] = deserializeValue(value);
this[inName] = deserializeValue(this.getAttribute(inName));
};

var handleMutations = function(inMxns) {
Expand All @@ -212,20 +335,43 @@

// lifecycle

var automate = function(inAttributes, inPublished) {
bindAttrs.call(this, inAttributes.attributes);
bindEvents.call(this, inAttributes.handlers);
bindProperties.call(this, inPublished);
};

// per-root
var initialize = function(inRoot, inUber) {
establishNodeReferences.call(this, inRoot);
if (inUber.shadowRootCreated) {
inUber.shadowRootCreated.call(this, inRoot);
}
bindAllCustomEvents(inRoot);
if (inRoot) {
bindModel(inRoot.childNodes);
}
};

// per-instance
var automate = function(inAttributes, inPublished, inDelegates) {
bindAttrs.call(this, inAttributes.attributes);
bindEvents.call(this, inAttributes.handlers);
bindProperties.call(this, inPublished);
bindDelegates.call(this, inDelegates);
};


// fireEvent impl

var fireEvent = function(inType, inDetail) {
this.asyncMethod("dispatchEvent", [
new CustomEvent(inType, {bubbles: true, detail: inDetail})
]);
};

// asyncMethod impl

var asyncMethod = function(inMethod, inArgs) {
window.setTimeout(function() {
this[inMethod].apply(this, inArgs);
}.bind(this), 0);
};

// decorate HTMLElementElement with toolkit API

HTMLElementElement.prototype.component = function(inUber) {
Expand All @@ -235,8 +381,9 @@
initialize.call(this, inRoot, inUber);
},
created: function() {
//console.log("created", this);
this.controller = this;
automate.call(this, attributes, inUber.published);
automate.call(this, attributes, inUber.published, inUber.delegates);
takeAttributes.call(this);
if (inUber.created) {
inUber.created.call(this);
Expand All @@ -250,11 +397,14 @@
// probably better to insert another link in the prototype chain
p.utils = utils;
p.setPropertySilently = setPropertySilently;
p.fireEvent = fireEvent;
p.asyncMethod = asyncMethod;
//p.$$ = $$;
// install our prototype
this.generatedConstructor.prototype = p;
};

// utility methods
// utility methods (utils)

// job

Expand All @@ -277,26 +427,41 @@

// target finding

var findDistributedTarget = function(inTarget, inItems) {
// find ancestor of target (including himself) that
// is in our item list, if any
var findDistributedTarget = function(inTarget, inNodes) {
// find first ancestor of target (including itself) that
// is in inNodes, if any
var n = inTarget;
while (n && n != this) {
var i = inItems.indexOf(n);
var i = inNodes.indexOf(n);
if (i >= 0) {
return i;
}
n = n.parentNode;
}
};

// string interpolation
var macroize = function(inText, inMap, inPattern) {
var result = inText, pattern = inPattern || macroize.pattern;
var fn = function(macro, name) {
var v = inMap[name];
return (v === undefined || v === null) ? macro : v;
};
result = result.replace(pattern, fn);
return result;
};
// Matches macros of the form {{name}}.
macroize.pattern = /\{{([^{}]*)\}}/g;

// collect utils

var utils = {
job: job,
macroize: macroize,
findDistributedTarget: findDistributedTarget
};

/*
// code below provides a shim for declarative event handlers
// (aka 'x', as in onclick="x('click')")
// it's only really for evaluating syntax, and not
Expand Down Expand Up @@ -350,7 +515,8 @@
owner[inHandler](event);
}
};

*/

// newer experimental event handler

var findController = function(inNode) {
Expand All @@ -373,6 +539,10 @@
};

// automagic event mapping
//
// locate attributes of the form <prefix><eventName>="<methodName>" and
// map <eventName> events to method <methodName> to inNode's controller
//

var bindCustomEvent = function(inNode, inEventName, inHandler) {
var h = inNode.__athandlers = inNode.__athandlers || {};
Expand All @@ -386,11 +556,12 @@
};

var bindCustomEvents = function(inNode) {
var prefix = conventions.CUSTOM_EVENT_PREFIX;
var a$ = inNode.attributes;
if (a$) {
for (var i=0, a; a=a$[i]; i++) {
if (a.name.slice(0, 2) == conventions.CUSTOM_EVENT_PREFIX) {
bindCustomEvent(inNode, a.name.slice(2), a.value);
if (a.name.slice(0, prefix.length) == prefix) {
bindCustomEvent(inNode, a.name.slice(prefix.length), a.value);
}
}
}
Expand All @@ -408,12 +579,9 @@
}
};

var __bindAllCustomEvents = function(inNode) {
_bindAllCustomEvents(inNode);
};

var eventsObserver = function(inNode) {
new WebKitMutationObserver(__bindAllCustomEvents.bind(this, inNode))
// TODO(sjmiles): optimize so we only bind new nodes
new WebKitMutationObserver(_bindAllCustomEvents.bind(this, inNode))
.observe(inNode, {
childList: true,
subTree: true
Expand All @@ -423,6 +591,7 @@
var bindAllCustomEvents = function(inNode) {
if (inNode) {
_bindAllCustomEvents(inNode);
// we need this for custom events in iterated templates
eventsObserver(inNode);
}
};
Expand Down

0 comments on commit 6696b85

Please sign in to comment.