Skip to content

Commit

Permalink
use template-binding to set up event listener delegations; remove mas…
Browse files Browse the repository at this point in the history
…sive amounts of old event binding code
  • Loading branch information
Scott J. Miles committed Oct 19, 2013
1 parent 00e2982 commit 7fd66eb
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 167 deletions.
70 changes: 3 additions & 67 deletions src/declaration/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

// imports

var EVENT_PREFIX = scope.api.instance.events.EVENT_PREFIX;
var api = scope.api.instance.events;
var log = window.logFlags || {};

// polymer-element declarative api: events feature
Expand All @@ -24,57 +24,12 @@
// for each attribute
for (var i=0, a; a=this.attributes[i]; i++) {
// does it have magic marker identifying it as an event delegate?
if (hasEventPrefix(a.name)) {
if (api.hasEventPrefix(a.name)) {
// if so, add the info to delegates
delegates[removeEventPrefix(a.name)] = a.value;
delegates[api.removeEventPrefix(a.name)] = a.value;
}
}
},
parseLocalEvents: function() {
// extract data from all templates into delegates
var t$ = this.querySelectorAll('template');
for (var i=0, l=t$.length, t; (i<l) && (t=t$[i]); i++) {
// store delegate information directly on template
t.delegates = {};
// acquire delegates from entire subtree at t
this.accumulateTemplatedEvents(t, t.delegates);
log.events && console.log('[%s] parseLocalEvents:', this.attributes.name.value, t.delegates);
};
},
accumulateTemplatedEvents: function(node, events) {
if (node.localName === 'template') {
var content = getTemplateContent(node);
if (content) {
this.accumulateChildEvents(content, events);
}
}
},
accumulateChildEvents: function(node, events) {
var n$ = node.childNodes;
for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
this.accumulateEvents(n, events);
}
},
accumulateEvents: function(node, events) {
this.accumulateAttributeEvents(node, events);
this.accumulateChildEvents(node, events);
this.accumulateTemplatedEvents(node, events);
return events;
},
accumulateAttributeEvents: function(node, events) {
var a$ = node.attributes;
if (a$) {
for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) {
if (hasEventPrefix(a.name)) {
this.accumulateEvent(removeEventPrefix(a.name), events);
}
}
}
},
accumulateEvent: function(name, events) {
name = this.event_translations[name] || name;
events[name] = events[name] || 1;
},
event_translations: {
webkitanimationstart: 'webkitAnimationStart',
webkitanimationend: 'webkitAnimationEnd',
Expand All @@ -84,25 +39,6 @@
}
};

var prefixLength = EVENT_PREFIX.length;

function hasEventPrefix(n) {
return n.slice(0, prefixLength) === EVENT_PREFIX;
}

function removeEventPrefix(n) {
return n.slice(prefixLength);
}

// TODO(sorvell): Currently in MDV, there is no way to get a template's
// effective content. A template can have a ref property
// that points to the template from which this one has been cloned.
// Remove this when the MDV api is improved
// (https://github.com/polymer-project/mdv/issues/15).
function getTemplateContent(template) {
return template.ref ? template.ref.content : template.content;
}

// exports

scope.api.declaration.events = events;
Expand Down
2 changes: 1 addition & 1 deletion src/declaration/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
}
},
requireProperties: function(properties, prototype, base) {
// ensure a prototype value for each one
// ensure a prototype value for each property
for (var n in properties) {
if (prototype[n] === undefined && base[n] === undefined) {
prototype[n] = properties[n];
Expand Down
2 changes: 0 additions & 2 deletions src/declaration/prototype.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@
this.accumulateInstanceAttributes();
// parse on-* delegates declared on `this` element
this.parseHostEvents();
// parse on-* delegates declared in templates
this.parseLocalEvents();
// install external stylesheets as if they are inline
this.installSheets();
// TODO(sorvell): install a helper method this.resolvePath to aid in
Expand Down
2 changes: 0 additions & 2 deletions src/instance/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@
shadowRootReady: function(root, template) {
// locate nodes with id and store references to them in this.$ hash
this.marshalNodeReferences(root);
// add local events of interest...
this.addInstanceListeners(root, template);
// set up pointer gestures
PointerGestures.register(root);
},
Expand Down
114 changes: 25 additions & 89 deletions src/instance/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@
// instance events api

var events = {
// magic words
// read-only
EVENT_PREFIX: EVENT_PREFIX,
// event name utilities
hasEventPrefix: function (n) {
return n.slice(0, prefixLength) === EVENT_PREFIX;
},
removeEventPrefix: function(n) {
return n.slice(prefixLength);
},
// event listeners on host
addHostListeners: function() {
var events = this.eventDelegates;
log.events && (Object.keys(events).length > 0) && console.log('[%s] addHostListeners:', this.localName, events);
this.addNodeListeners(this, events, this.hostEventListener);
},
// event listeners inside a shadow-root
addInstanceListeners: function(root, template) {
var events = template.delegates;
if (events) {
log.events && (Object.keys(events).length > 0) && console.log('[%s:root] addInstanceListeners:', this.localName, events);
this.addNodeListeners(root, events, this.instanceEventListener);
}
},
addNodeListeners: function(node, events, listener) {
// note: conditional inside loop as optimization
// for empty 'events' object
Expand Down Expand Up @@ -75,69 +74,26 @@
Platform.flush();
}
},
instanceEventListener: function(event) {
listenLocal(this, event);
}
};

// TODO(sjmiles): much of the below privatized only because of the vague
// notion this code is too fiddly and we need to revisit the core feature

function listenLocal(host, event) {
if (!event.cancelBubble) {
event.on = EVENT_PREFIX + event.type;
log.events && console.group("[%s]: listenLocal [%s]", host.localName, event.on);
if (!event.path) {
_listenLocalNoEventPath(host, event);
} else {
_listenLocal(host, event);
prepareBinding: function(path, name, node) {
// if lhs an event prefix,
if (events.hasEventPrefix(name)) {
// provide an event-binding callback
return function(model, name, node) {
console.log('event: [%s].%s => [%s].%s()"', node.localName, name, model.localName, name);
node.addEventListener(events.removeEventPrefix(name),
function(event) {
var ctrlr = findController(node);
if (ctrlr && ctrlr.dispatchMethod) {
ctrlr.dispatchMethod(ctrlr, path, [event, event.detail, node]);
}
}
);
};
}
log.events && console.groupEnd();
}
}
};

function _listenLocal(host, event) {
var c = null;
// use `some` to stop iterating after the first matching target
Array.prototype.some.call(event.path, function(t) {
// if we hit host, stop
if (t === host) {
return true;
}
// find a controller for target `t`, unless we already found `host`
// as a controller
c = (c === host) ? c : findController(t);
// if we have a controller, dispatch the event, return 'true' if
// handler returns true
if (c && handleEvent(c, t, event)) {
return true;
}
}, this);
}

// TODO(sorvell): remove when ShadowDOM polyfill supports event path.
// Note that findController will not return the expected
// controller when when the event target is a distributed node.
// This because we cannot traverse from a composed node to a node
// in shadowRoot.
// This will be addressed via an event path api
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066
function _listenLocalNoEventPath(host, event) {
log.events && console.log('event.path() not supported for', event.type);
var t = event.target, c = null;
// if we hit dirt or host, stop
while (t && t != host) {
// find a controller for target `t`, unless we already found `host`
// as a controller
c = (c === host) ? c : findController(t);
// if we have a controller, dispatch the event, return 'true' if
// handler returns true
if (c && handleEvent(c, t, event)) {
return true;
}
t = t.parentNode;
}
}
var prefixLength = EVENT_PREFIX.length;

function findController(node) {
while (node.parentNode) {
Expand All @@ -146,26 +102,6 @@
return node.host;
};

function handleEvent(ctrlr, node, event) {
var h = node.getAttribute && node.getAttribute(event.on);
if (h && handleIfNotHandled(node, event)) {
log.events && console.log('[%s] found handler name [%s]', ctrlr.localName, h);
ctrlr.dispatchMethod(node, h, [event, event.detail, node]);
}
return event.cancelBubble;
};

function handleIfNotHandled(node, event) {
var list = event[HANDLED_LIST];
if (!list) {
list = event[HANDLED_LIST] = [];
}
if (list.indexOf(node) < 0) {
list.push(node);
return true;
}
}

// exports

scope.api.instance.events = events;
Expand Down
24 changes: 18 additions & 6 deletions src/instance/mdv.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,27 @@
// imports

var log = window.logFlags || 0;
var events = scope.api.instance.events;

// use an MDV syntax
// expressionista

var mdv_syntax = new PolymerExpressions();
var syntax = new PolymerExpressions();

var expressionista = {
// <[node] [name] = {{path}}>
prepareBinding: function(path, name, node) {
// if not an event, delegate to the standard syntax
return events.prepareBinding(path, name, node)
|| syntax.prepareBinding(path, name, node);
}
};

// element api supporting mdv

var mdv = {
syntax: expressionista,
instanceTemplate: function(template) {
return template.createInstance(this, mdv_syntax);
return template.createInstance(this, this.syntax);
},
bind: function(name, model, path) {
// note: binding is a prepare signal. This allows us to be sure that any
Expand All @@ -26,7 +37,10 @@
this.prepareElement();
}
var property = this.propertyForAttribute(name);
if (property) {
if (!property) {
return this.super(arguments);
} else {
// clean out the closets
this.unbind(name);
// use n-way Polymer binding
var observer = this.bindProperty(property, model, path);
Expand All @@ -37,8 +51,6 @@
// does not update due to not changing.
this.reflectPropertyToAttribute(property);
return this.bindings[name] = observer;
} else {
return this.super(arguments);
}
},
asyncUnbindAll: function() {
Expand Down

0 comments on commit 7fd66eb

Please sign in to comment.