add actual components folder

This commit is contained in:
Scott J. Miles
2014-11-17 10:38:22 -08:00
parent 8d2bcbf60a
commit 55fb21352d
146 changed files with 57440 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
<svg><defs>
<g id="apps"><path d="M4,8h4V4H4V8z M10,20h4v-4h-4V20z M4,20h4v-4H4V20z M4,14h4v-4H4V14z M10,14h4v-4h-4V14z M16,4v4h4V4H16z M10,8h4V4h-4V8z M16,14h4v-4h-4V14z M16,20h4v-4h-4V20z"/></g>
<g id="archive"><path d="M20.5,5.2l-1.4-1.7C18.9,3.2,18.5,3,18,3H6C5.5,3,5.1,3.2,4.8,3.5L3.5,5.2C3.2,5.6,3,6,3,6.5V19c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V6.5C21,6,20.8,5.6,20.5,5.2z M12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5z M5.1,5l0.8-1h12l0.9,1H5.1z"/></g>
<g id="check-box-outline-blank"><path d="M19,5v14L5,19V5H19 M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3L19,3z"/></g>
<g id="check-box-outline"><path d="M7.9,10.1l-1.4,1.4L11,16L21,6l-1.4-1.4L11,13.2L7.9,10.1z M19,19L5,19V5h10V3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-8h-2V19z"/></g>
<g id="arrow-drop-down"><polygon points="7,10 12,15 17,10 "/></g>
<g id="info-outline"><path d="M11,17h2v-6h-2V17z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z M11,9h2V7h-2V9z"/></g>
<g id="search"><path d="M15.5,14h-0.8l-0.3-0.3c1-1.1,1.6-2.6,1.6-4.2C16,5.9,13.1,3,9.5,3C5.9,3,3,5.9,3,9.5S5.9,16,9.5,16c1.6,0,3.1-0.6,4.2-1.6l0.3,0.3v0.8l5,5l1.5-1.5L15.5,14z M9.5,14C7,14,5,12,5,9.5S7,5,9.5,5C12,5,14,7,14,9.5S12,14,9.5,14z"/></g>
<g id="arrow-back"><path d="M20,11H7.8l5.6-5.6L12,4l-8,8l8,8l1.4-1.4L7.8,13H20V11z"/></g>
<g id="schedule"><path fill-opacity="0.9" d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/><polygon fill-opacity="0.9" points="12.5,7 11,7 11,13 16.2,16.2 17,14.9 12.5,12.2 "/></g>
<g id="close"><polygon points="19,6.4 17.6,5 12,10.6 6.4,5 5,6.4 10.6,12 5,17.6 6.4,19 12,13.4 17.6,19 19,17.6 13.4,12 "/></g>
<g id="create"><path d="M3,17.2V21h3.8L17.8,9.9l-3.8-3.8L3,17.2z M20.7,7c0.4-0.4,0.4-1,0-1.4l-2.3-2.3c-0.4-0.4-1-0.4-1.4,0l-1.8,1.8l3.8,3.8L20.7,7z"/></g>
<g id="settings"><path d="M19.4,13c0-0.3,0.1-0.6,0.1-1s0-0.7-0.1-1l2.1-1.7c0.2-0.2,0.2-0.4,0.1-0.6l-2-3.5C19.5,5.1,19.3,5,19,5.1l-2.5,1c-0.5-0.4-1.1-0.7-1.7-1l-0.4-2.6C14.5,2.2,14.2,2,14,2h-4C9.8,2,9.5,2.2,9.5,2.4L9.1,5.1C8.5,5.3,8,5.7,7.4,6.1L5,5.1C4.7,5,4.5,5.1,4.3,5.3l-2,3.5C2.2,8.9,2.3,9.2,2.5,9.4L4.6,11c0,0.3-0.1,0.6-0.1,1s0,0.7,0.1,1l-2.1,1.7c-0.2,0.2-0.2,0.4-0.1,0.6l2,3.5C4.5,18.9,4.7,19,5,18.9l2.5-1c0.5,0.4,1.1,0.7,1.7,1l0.4,2.6c0,0.2,0.2,0.4,0.5,0.4h4c0.2,0,0.5-0.2,0.5-0.4l0.4-2.6c0.6-0.3,1.2-0.6,1.7-1l2.5,1c0.2,0.1,0.5,0,0.6-0.2l2-3.5c0.1-0.2,0.1-0.5-0.1-0.6L19.4,13z M12,15.5c-1.9,0-3.5-1.6-3.5-3.5s1.6-3.5,3.5-3.5s3.5,1.6,3.5,3.5S13.9,15.5,12,15.5z"/></g>
<g id="delete"><path d="M6,19c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V7H6V19z M19,4h-3.5l-1-1h-5l-1,1H5v2h14V4z"/></g>
<g id="menu"><path d="M3,18h18v-2H3V18z M3,13h18v-2H3V13z M3,6v2h18V6H3z"/></g>
</defs></svg>
<script>
var doc = window.import;
var cloneIcon = function(id, size) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
var icon = doc.querySelector(id);
if (icon) {
svg.style.pointerEvents = 'none';
svg.style.display = 'block';
svg.setAttribute('height', '100%');
svg.setAttribute('width', '100%');
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute('viewBox', '0 0 24 24');
svg.appendChild(icon.cloneNode(true));
}
return svg;
};
var icons = {
apps: cloneIcon('#apps'),
archive: cloneIcon('#archive'),
box: cloneIcon('#check-box-outline-blank'),
checked: cloneIcon('#check-box-outline'),
drop: cloneIcon('#arrow-drop-down'),
'info-outline': cloneIcon('#info-outline'),
search: cloneIcon('#search'),
back: cloneIcon('#arrow-back'),
schedule: cloneIcon('#schedule'),
create: cloneIcon('#create'),
close: cloneIcon('#close'),
delete: cloneIcon('#delete'),
schedule: cloneIcon('#schedule'),
settings: cloneIcon('#settings'),
menu: cloneIcon('#menu')
};
</script>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,252 @@
<link rel="import" href="polymer/polymer.html">
<template>
<style>
:host {
display: block;
font-family: sans-serif;
font-size: 1.4em;
}
iframe {
position: absolute;
border: 0;
width: 1920px;
height: 1080px;
visibility: hidden;
xdisplay: none;
}
o, n {
display: inline-block;
xwidth: 24px;
margin: 2px;
text-align: right;
font-family: monospace;
}
o {
color: rgba(255, 0, 0, 0.5);
}
n {
display: inline-block;
color: green;
font-weight: bold;
}
</style>
<div></div>
<iframe id="frame"></iframe>
</template><script>
Polymer({
name: 'frame-tester',
base: '',
runs: 25,
published: [
'tests'
],
created: function() {
this.frame = this.$('#frame');
this.strategy = this.strategies.minimum;
this.attributeChangedCallback('runs');
this.attributeChangedCallback('base');
addEventListener('message', this.scoreMessage.bind(this));
},
attributeChangedCallback: function(name) {
var value = this.getAttribute(name);
switch(name) {
case 'runs':
this.runs = value || this.runs;
break;
case 'base':
this.base = value || this.base;
break;
case 'strategy':
this.strategy = this.strategies[strategy] || this.strategy;
break;
}
},
testsChanged: function() {
this.go();
},
shuffle: function(tests) {
var shuffled = [];
var ordered = tests.slice(0);
var count = ordered.length;
for (var i=0, j; i<count; i++) {
j = Math.floor(Math.random()*count);
// TODO(sjmiles): this is an easy but poorly randomized distribution
for (; !ordered[j]; j = (j + 1) % count);
shuffled.push(j);
ordered[j] = null;
}
return shuffled;
},
go: function() {
this.count = 0;
this.total = [];
this.times = [];
for (var i=0; i<this.tests.length; i++) {
this.total[i] = 0;
this.times[i] = [];
}
this.startRun();
},
startRun: function() {
this.shuffled = this.shuffle(this.tests);
this.index = -1;
//console.group('run', this.count);
this.nextTest();
},
nextTest: function() {
// last test in this run?
if (++this.index === this.tests.length) {
//console.groupEnd();
// report results
++this.count;
this.report();
// more runs?
if (this.count < this.runs) {
this.startRun();
} else {
// all done!
this.fire('done');
}
return;
}
// test order is randomized
this.test = this.shuffled[this.index];
this.frame.src = this.base + this.tests[this.test];
// it's possible for a test to end before the load event fires,
// so assume the frame loads immediately and start waiting
// for a result.
this.load();
},
load: function() {
// frame is loaded, measure the time, then proceed
this.measure(function(time) {
this.record(time);
this.nextTest();
});
},
measure: function(next) {
this.afterScore = next;
},
scoreMessage: function(e) {
if (this.afterScore) {
var time = e.data;
if (time.slice(-2) === 'ms') {
time = Number(time.slice(0, -2));
this.afterScore(time);
}
}
},
record: function(time) {
//console.log('index [%d], test [%d] time [%d]', this.index, this.test, time);
this.times[this.test].push(time);
this.total[this.test] += time;
},
report: function() {
var info = '<br>Runs: ' + this.count + '/' + this.runs
+ '<br><br>';
//
for (var i=0; i<this.tests.length; i++) {
var url = this.tests[i],
total = this.total[i],
times = this.times[i],
stats = this.stats(times);
//
var time = this.strategy.score(times, stats);
//
info += ''
+ ' <b>' + (time).toFixed(1) + 'ms' + '</b>'
//+ ' [stddev: ' + stats.deviation.toFixed(2) + ']'
+ ' &nbsp;&nbsp;&nbsp;<a href="' + this.base + url + '" target="_blank">' + url + '</a>'
+ '<br>'
;
//
info += '<span style="font-size: 8px">';
for (var j=0, v; v=times[j]; j++) {
var o = stats.outlier(v);
info += (o ? '<o>' : '<n>') + v.toFixed(0) + (o ? '</o>' : '</n>') + '|';
}
info += '</span>';
info += '<hr>';
}
//
this.$('div').innerHTML = info;
},
stats: function(a) {
var r = {mean: 0, variance: 0, deviation: 0}, t = a.length;
for (var m, s = 0, l = t; l--; s += a[l]);
for (m = r.mean = s / t, l = t, s = 0; l--; s += Math.pow(a[l] - m, 2));
r.outlier = this.strategy.outlier;
return r.deviation = Math.sqrt(r.variance = s / t), r;
},
//
// selectable statical strategies
//
strategies: {
// This strategy selects the minimum timing for score.
minimum: {
score: function(times, stats) {
var min = Number.MAX_VALUE;
for (var j=0, v; v=times[j]; j++) {
min = Math.min(v, min);
}
stats.score = min;
return min;
},
// called in stats context
outlier: function(value) {
return value > this.score;
}
},
// This strategy selects the mean of all times not more than one stddev
// away from the total sampling mean.
onedev: {
score: function(times, stats) {
var cleaned = [];
for (var j=0, v; v=times[j]; j++) {
if (!stats.outlier(v)) {
cleaned.push(v);
}
}
return this.stats(cleaned).mean;
},
// called in stats context
outlier: function(value) {
return Math.abs(value - this.mean) > (1 * this.deviation);
}
}
}
});
</script>

View File

@@ -0,0 +1,40 @@
// x-browser compat.
if (!window.performance) {
var start = Date.now();
// only at millisecond precision
window.performance = {now: function(){ return Date.now() - start }};
}
console.perf = function() {
if (console.timeline) {
console.timeline();
}
console.profile();
console.perf.time = performance.now();
};
console.perfEnd = function() {
if (window.WebComponents) {
// TODO(sjmiles): we need some kind of 'whenReady' or other signal
// that will work if this function is called after the event has fired
addEventListener('WebComponentsReady', function() {
console._perfEnd();
});
} else {
console._perfEnd();
}
};
console._perfEnd = function() {
// force layout
document.body.offsetWidth;
var time = performance.now() - console.perf.time;
console.profileEnd();
if (console.timeline) {
console.timelineEnd();
}
document.title = time.toFixed(1) + 'ms: ' + document.title;
if (window.top !== window) {
window.top.postMessage(time + 'ms', '*');
}
};

View File

@@ -0,0 +1,126 @@
<script>
/*
Scans a template to produce an annotation map that stores expression metadata
and information that can be used to associate that metadata with the
corresponding nodes in a template instance.
Supported annotations are:
* binding annotations in text nodes
* double-mustache expressions: {{expression}}
* double-bracket expressions: [[expression]]
* binding annotations in attributes
* attribute-bind expressions: name={{expression}} || [[expression]]
* property-bind expressions: name*={{expression}} || [[expression]]
* event annotations
* event delegation directives: on-<eventName>="expression"
Generated data-structure:
[
{
bindings: [
{
kind: ['event'|'text'|'attribute'|'property'],
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}
],
// TODO(sjmiles): confusingly, this is annotation-parent, not node-parent
parent: <reference to parent annotation>,
index: <integer index in parent's childNodes collection>
},
...
]
TODO(sjmiles): this module should produce either syntactic metadata
(e.g. double-mustache, double-bracket, star-attr), or semantic metadata
(e.g. manual-bind, auto-bind, property-bind). Right now it's half and half.
*/
Base.features.push({
findAnnotatedNode: function(root, annote) {
return !annote.parent ? root :
this.findAnnotatedNode(root, annote.parent).childNodes[annote.index];
},
parseTemplateAnnotations: function(template) {
// TODO(sjmiles): it's not a map, per se
var map = [];
this._parseTemplateNode(template.content, map);
if (map.length) {
template.map = map;
}
},
_parseTemplateNode: function(node, map) {
return node.nodeType === Node.TEXT_NODE ?
this._parseTemplateTextNode(node, map) :
this._parseTemplateElement(node, map);
},
_parseTemplateTextNode: function(node, map) {
var v = node.textContent, escape = v.slice(0, 2);
if (escape === '{{' || escape === '[[') {
var annotation = {
bindings: [{
kind: 'text',
mode: escape === '{{' ? 'auto' : '',
value: v.slice(2, -2)
}]
};
map.push(annotation);
return annotation;
}
},
_parseTemplateElement: function(node, map) {
var annotations = {
bindings: []
};
this._parseTemplateNodeAnnotations(node, annotations, map);
this._parseTemplateChildNodes(node, annotations, map);
if (annotations.bindings.length) {
map.push(annotations);
}
return annotations;
},
_parseTemplateNodeAnnotations: function(node, annotation) {
if (node.attributes) {
for (var i=0, a; (a=node.attributes[i]); i++) {
var n = a.name, v = a.value, escape = v.slice(0, 2);
if (escape === '{{' || escape === '[[') {
var kind = (n[n.length-1]) === '*' ? 'property' : 'attribute';
if (kind === 'property') {
n = n.slice(0, -1);
}
annotation.bindings.push({
kind: kind,
mode: escape === '{{' ? 'auto' : '',
name: n,
value: v.slice(2, -2)
});
} else if (n.slice(0, 3) === 'on-') {
annotation.bindings.push({
kind: 'event',
name: n.slice(3),
value: v
});
}
}
}
},
_parseTemplateChildNodes: function(root, annotations, map) {
if (root.firstChild) {
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var annotation = this._parseTemplateNode(node, map);
if (annotation) {
annotation.parent = annotations;
annotation.index = i;
}
}
}
}
});
</script>

View File

@@ -0,0 +1,82 @@
<script>
var attributes = function(node, attr$) {
attr$.split(' ').forEach(function(a) {
node.setAttribute(a, '');
});
};
Base = {
// (semi-)pluggable features for Base
features: [],
installFeatures: function() {
// simple engine to modularize features
Base.features.forEach(function(f) {
extend(Base, f);
});
delete Base.init;
delete Base.installFeatures;
},
registerCallback: function() {
// context is a prototype, not an instance
var prototype = this;
prototype._template = window.import.querySelector('template');
if (prototype._template) {
// requires annotations feature
prototype.parseTemplateAnnotations(prototype._template);
}
// make a list of names that have properties of the form <name>Changed.
// DataClient can use this list to efficiently associate *Changed
// methods with published properties.
// TODO(sjmiles): maybe remove this feature completely in favor of
// explicit annotations in the prototype.
//prototype.precomputeNotifierCandidates();
},
createdCallback: function() {
this.initFeatures();
this.createRoot();
//if (!this.deferStamp) {
this.stampTemplate();
//}
this.installAttributes();
this.created();
// requires bindings feature
this.updateBindings();
},
created: function() {
},
initFeatures: function() {
Base.features.forEach(function(f) {
f.init && f.init.call(this);
}, this);
},
//
// TODO(sjmiles): all the following should be 'features'?
//
createRoot: function(){
// TODO(sjmiles): ad hoc to switch on _template existence here
this.root = this._template ? this.createShadowRoot() : this;
},
stampTemplate: function() {
if (this._template && !this._stamped) {
this.root.appendChild(this.instanceTemplate(this._template));
this._stamped = true;
// requires bindings feature
this.marshalBindings();
// TODO(sjmiles): we don't necessarily want to do this here
//if (this.deferBindings) {
// this.updateBindings();
//}
}
},
installAttributes: function() {
if (this._attributes) {
attributes(this, this._attributes);
}
},
instanceTemplate: function(template) {
return document.importNode(template.content, true);
}
};
</script>

View File

@@ -0,0 +1,147 @@
<script>
//
// `marshalBindings` consumes template annotations to:
//
// 1. set up event delegation machinery
// 2. set up binding machinery and instance data
//
// `updateBindings` can be called to perform propagation of model data to DOM
// on demand
Base.features.push({
init: function() {
this._model = this;
this._bindings = [];
},
// instance binding data
marshalBindings: function() {
if (this._template.map) {
this._template.map.forEach(function(annotation) {
// locate instance node
var node = this.findAnnotatedNode(this.root, annotation);
// implement data-binding on the instance node
this.marshalNodeBindings(node, annotation);
}, this);
}
},
// TODO(sjmiles): most of these methods should probably be prefixed with _
marshalNodeBindings: function(node, annotation) {
for (var i=0, fn, b; b=annotation.bindings[i]; i++) {
if (b.kind === 'event') {
this.implementListener(node, b.name, b.value);
} else {
this._bindings.push({
node: node,
property: b.value,
fn: this.implementBinding(node, b.kind, b.name, b.value, b.mode)
});
}
}
},
implementListener: function(node, name, method) {
// TODO(sjmiles): perhaps implementListener should
// be factored even further (i.e. into a separate module/feature?)
// because it's conceptually separable from the details of template
// annotation processing.
var ctrlr = this;
node.addEventListener(name, function(e) {
if (ctrlr[method]) {
Polymer.log.events && console.log('[event-binding]: %s -> %s.%s', name, ctrlr.localName, method)
ctrlr[method](e, e.detail);
}
});
},
implementBinding: function(node, kind, name, property, mode) {
// TODO(sjmiles): perhaps implementBinding should
// be factored even further (i.e. into a separate module/feature?)
// because it's conceptually separable from the details of template
// annotation processing.
switch(kind) {
case 'text':
// TODO(sjmiles): baby-step toward path support
// - only text bindings
// - support exactly one dot (foo.bar)
// - generated fn binds to this, derefs path from model
// (ignores argument)
/*
if (property.indexOf('.') >= 0) {
var path = property.split('.');
var object = path[0];
var property = path[1];
// TODO(sjmiles): tortured attempt to avoid nerfing performance
// - binder does nothing until target object becomes available
// then memoizes it for all time
var work = function() {
var obj = this[object];
if (obj) {
object = obj;
work = postWork;
work();
}
}.bind(this);
var postWork = function() {
node.data = object[property];
};
var fn = function() {
work();
};
//console.log('[implementBinding]: path detected: [%s]', property);
break;
}
*/
var fn = function(value) {
node.data = value;
};
break;
case 'attribute':
var fn = function(value) {
if (typeof value === 'boolean') {
node[value ? 'setAttribute' : 'removeAttribute'](name, '');
} else {
node.setAttribute(name, value);
}
};
break;
case 'property':
// this is a fourth type of binding that utilizes shared storage to
// avoid the need to observe and propagate changes
// TODO(sjmiles): ad-hoc DataClient detection
if (node._data && (name in node._data)) {
this.publishProperty(property);
this.bindProperty(property, name, node);
return null;
}
// generic property binding
var fn = function(value) {
node[name] = value;
};
break;
}
if (mode === 'auto') {
// create an observable property
this.publishProperty(property);
// update the binding when changes are observed
this.watch(property, fn);
}
return fn;
},
updateBindings: function() {
var model = this._model;
this._bindings.forEach(function(b) {
// TODO(sjmiles): the `fn` are structured so as to be called directly
// from `watch` which supplies a `value` argument. We would instead
// compose the `fn` to include the model dereference instead of taking
// an argument.
// The conceptual propriety is unclear to me at the moment.
// Needing to encode `property` in `b` is a cost to the current method.
// Doing an extra lookup in `fn` when called from watch (or having a
// conditional to avoid doing that) would be a cost the other way.
if (b.fn) {
b.fn(model[b.property]);
}
});
}
});
</script>

View File

@@ -0,0 +1,322 @@
<script>
// TODO(sjmiles): when binding properties that are themselves bound,
// we need to do a merge, which increases the complexity considerably.
// Is there a different way to frame this problem?
// TODO(sjmiles): implement dispose
/*
* Datum wraps a concrete data value and provides:
*
* - persistent shareable reference
* - synchronous notifications on mutations (via method-traps)
*
* Multiple DataClients can refer to a single Datum instance. If any of the
* clients change the concrete value, the new value is immediately available
* to all the others (single source of truth).
*
* e.g. given
* foo.datum = datum1
* bar.datum = datum1
* then obviously:
* foo.datum.value == bar.datum.value
*
* But now foo and bar can construct accessors such that:
* foo.baz => get() { return this.datum.value };
* bar.zot => get() { return this.datum.value };
* now, less obviously:
* foo.baz === bar.zot
*
* This is the data-binding gambit.
*
* The `watch` method registers a callback function to be invoked
* synchronously on data mutations.
*
* Watch callbacks are triggered when:
* - data is changed via setValue
* - any of various Array methods are called value (when value is Array)
* - `notify` method is called directly
*
* `merge` method collapses a DataClient into this one, combining
* all watchers and referers.
*/
function Datum(value) {
this.value = value;
// we require back references so we can merge datums
// back references will require dispose pattern for GC,
// but then we already have this problem wrt watchers
// a 'referer' must be a DataClient
this.referers = [];
}
Datum.prototype = {
addReferer: function(client, name) {
// TODO(sjmiles): `client` has to be a DataClient right now, could be
// generalized.
// TODO(sjmiles): storing this data seems wrong, the Datum doesn't need
// to know these details except for merging. Perhaps we should take
// a function argument, and let the caller supply a closure with the
// necessary bookkeeping.
this.referers.push({client: client, name: name});
},
watch: function(notifier) {
if (!this.watchers) {
this.watchers = [];
}
this.watchers.push(notifier);
},
// TODO(sjmiles): use accessors?
// TODO(sjmiles): factor out Array decoration
setValue: function(value) {
var old = this.value;
if (old !== value) {
// TODO(sjmiles): experimental
if (value instanceof Array) {
//console.log('decorating Array');
// TODO(sjmiles): how costly is this?
extend(value, ArrayTrap);
// TODO(sjmiles): binding client this way is problematic
value.client = this;
}
this.value = value;
this.notify(old);
}
},
notify: function(old) {
if (this.watchers) {
var value = this.value;
this.watchers.forEach(function(w) {
w(value, old);
});
}
},
merge: function(other) {
// transfer all watchers of `other` to `this`
if (other.watchers) {
this.watchers = (this.watchers || []).concat(other.watchers);
other.watchers = null;
}
// redirect all referers of `other` to `this`
var referers = other.referers;
if (referers.length) {
referers.forEach(function(r) {
r.client._refer(r.name, this);
}, this);
other.referers = null;
}
// `other` should be free to GC at this point
}
};
ArrayTrap = Object.create(null);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(function(n) {
ArrayTrap[n] = function() {
console.log('Array.%s notify', n);
Array.prototype[n].apply(this, arguments);
this.client.notify(this);
};
});
/*
* DataClient implements a data-store whose properties can be:
*
* - shared among DataClients (aka _bound_)
* - observed for mutations
* - computed from dependent properties
*
* `Publishing` a property on a DataClient:
*
* - creates a Datum instance mapped to the property name
* - installs (as needed) get/set accessors (on the prototype) for the
* property name that reference the mapped Datum instance.
* - if a method called `<name>Changed` exists, use this
* method as a `watch` callback.
*
* A sugaring system is implemented to set up relationships at
* init-time from meta-data declarations on the instance.
*
* // `published` identifies property names to publish at init-time.
* // TODO(sjmiles): use a map to support initial values
* published: [
* 'foo',
* 'bar'
* ],
* // `watched` object maps properties to names of methods to use for `watch`
* // callbacks. Compound watches are supported.
* published: [
* foo: 'doUpdates',
* 'foo bar baz': 'otherUpdates'
* ],
* // `computed` object maps properties to faux method-invocations. The
* // right-side is parsed to construct a CompoundWatch whose notify callback
* // computes the value of the left-side.
* computed: [
* foo: 'computeFoo(bar)',
* baz: 'computeBaz(foo, zot, narg)'
* ]
*
*/
DataClient = {
published: [],
watched: {},
computed: {},
init: function() {
this._data = {};
this.publishProperties(this.published);
this.watchProperties(this.watched);
this.defineComputedProperties(this.computed);
},
publishProperties: function(properties) {
properties.forEach(this.publishProperty, this)
},
publishProperty: function(property) {
this._defineProperty(property);
// auto-watch feature
// TODO(sjmiles): consider eliminating this feature in favor
// of explicit annotation
var watchName = property + 'Changed';
/*
if (this._hasNotifier) {
if (!this._hasNotifier[property]) {
return;
}
} else {
*/
// TODO(sjmiles): either test is extremely slow
// TODO(sjmiles): slowness of this test was exaggerated by red-herring:
// some last-gen elements (x-repeater) binding *Changed methods manually
// causing double updates.
//if (!(watchName in this)) {
if (!this[watchName]) {
return;
}
//}
this.watch(property, this[watchName]);
},
precomputeNotifierCandidates: function() {
// TODO(sjmiles): implemented due to bogus results as described above
/*
this._hasNotifier = {};
// TODO(sjmiles): seems simple, fraught with performance danger
for (var n in this) {
if (n.slice(-7) === 'Changed') {
this._hasNotifier[n.slice(0, -7)] = true;
}
}
*/
},
notifyChange: function(name) {
var datum = this._data[name];
datum.notify(datum.value);
},
watch: function(name, notify) {
this._data[name].watch(notify.bind(this));
},
watchProperties: function(properties) {
for (var n in properties) {
this.compoundWatch(n.split(' '), this[properties[n]]);
}
},
defineComputedProperties: function(computed) {
for (var n in computed) {
this.defineComputedProperty(n, computed[n]);
}
},
defineComputedProperty: function(name, expression) {
this.publishProperty(name);
var parts = expression.match(/(.*)\((.*)\)/);
var method = this[parts[1]];
var args = parts[2].replace(/ /g, '').split(',');
this.compoundWatch(args, function() {
Polymer.log.watches && console.log('[defineComputedProperty]: computing [%s]', name, arguments);
this[name] = method.apply(this, arguments);
});
},
compoundWatch: function(names, notify) {
// TODO(sjmiles): needs factoring
//
// fallback to normal watch if we are not truly compound
if (names.length === 1) {
this.watch(names[0], notify);
return;
}
var debouncing = false,
data = this._data,
async = this.async.bind(this),
client = this
;
names.forEach(function(n) {
data[n].watch(function(value) {
Polymer.log.watches && console.log('[compoundWatch]: debounce [%s]', n);
if (!debouncing) {
debouncing = true;
// TODO(sjmiles): if a property in `name` is itself computed
// we fail to debounce properly.
// I don't see how we can debounce properly unless we study
// the dependency graph. :(
async(function() {
debouncing = false;
var args = [];
names.forEach(function(n) {
args.push(client[n])
});
Polymer.log.watches && console.log('[compoundWatch]: async-notify ', names, args);
notify.apply(client, args);
});
}
});
});
},
// cause this[name] and target[targetName] to refer to the same datum
// TODO(sjmiles): change call signature to (name, target[, targetName])
bindProperty: function(name, targetName, target) {
//
// TODO(sjmiles): which datum is 'old' and which is 'new' is arbitrary atm
// (one value is discarded)
//
// merge old into new
target._data[targetName].merge(this._data[name], name);
},
// TODO(sjmiles): general-purpose utility method should be implemented
// elsewhere?
async: function(method) {
var handled = false;
var handle = function() {
if (!handled) {
handled = true;
method.call(this);
}
}.bind(this);
// minimize latency by racing requests
setTimeout(handle);
requestAnimationFrame(handle);
},
_defineProperty: function(name) {
if (!(name in this._data)) {
this._refer(name, new Datum(null));
// install accessors on our prototype (if needed)
Object.getPrototypeOf(this)._defineAccessors(name);
}
},
_defineAccessors: function(name) {
if (!this.hasOwnProperty(name)) {
Object.defineProperty(this, name, {
get: function() {
return this._data[name].value;
},
set: function(value) {
this._data[name].setValue(value);
}
});
}
},
_refer: function(name, datum) {
this._data[name] = datum;
datum.addReferer(this, name);
}
};
Base.features.push(DataClient);
</script>

View File

@@ -0,0 +1,38 @@
<script>
Base.features.push({
listeners: {},
init: function() {
this.listenListeners();
},
listenListeners: function() {
for (var n in this.listeners) {
this.listen(n, this.listeners[n]);
}
},
listen: function(eventName, methodName) {
this.addEventListener(eventName, function(e) {
this[methodName](e, e.detail);
}.bind(this));
},
fire: function(type, detail, onNode, bubbles, cancelable) {
var node = onNode || this;
var detail = (detail === null || detail === undefined) ? {} : detail;
var event = new CustomEvent(type, {
bubbles: bubbles !== undefined ? bubbles : true,
cancelable: cancelable !== undefined ? cancelable : true,
detail: detail
});
node.dispatchEvent(event);
return event;
},
eventBind: function(eventName, node, targetValue, model, property) {
node.addEventListener(eventName, function(e) {
model[property] = e.target[targetValue];
Polymer.log.events && console.log('[eventBind]: %s.%s = %s',
e.target.localName, targetValue, e.target[targetValue]);
});
}
});
</script>

View File

@@ -0,0 +1,4 @@
<script>
</script>

View File

@@ -0,0 +1,41 @@
<script>
// a tiny bit of sugar for `document.currentScript.ownerDocument`
// sadly `import` is reserved, so we need another name or
// you have to refer to this value `window.import`
Object.defineProperty(window, 'import', {
enumerable: true,
configurable: true,
get: function() {
var script = document._currentScript || document.currentScript;
return script.ownerDocument;
}
});
// copy own properties from 'api' to 'prototype, with name hinting for 'super'
function extend(prototype, api) {
if (prototype && api) {
// use only own properties of 'api'
Object.getOwnPropertyNames(api).forEach(function(n) {
// acquire property descriptor
var pd = Object.getOwnPropertyDescriptor(api, n);
if (pd) {
// clone property via descriptor
Object.defineProperty(prototype, n, pd);
// cache name-of-method for 'super' engine
if (typeof pd.value == 'function') {
// hint the 'super' engine
pd.value.nom = n;
}
}
});
}
return prototype;
};
Event.prototype.keys = {
ESC_KEY: 27,
ENTER_KEY: 13
};
</script>

View File

@@ -0,0 +1,278 @@
<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<style shim-shadowdom>
/*******************************
Flex Layout
*******************************/
html /deep/ [layout][horizontal], html /deep/ [layout][vertical] {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
html /deep/ [layout][horizontal][inline], html /deep/ [layout][vertical][inline] {
display: -ms-inline-flexbox;
display: -webkit-inline-flex;
display: inline-flex;
}
html /deep/ [layout][horizontal] {
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
}
html /deep/ [layout][horizontal][reverse] {
-ms-flex-direction: row-reverse;
-webkit-flex-direction: row-reverse;
flex-direction: row-reverse;
}
html /deep/ [layout][vertical] {
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
}
html /deep/ [layout][vertical][reverse] {
-ms-flex-direction: column-reverse;
-webkit-flex-direction: column-reverse;
flex-direction: column-reverse;
}
html /deep/ [layout][wrap] {
-ms-flex-wrap: wrap;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
}
html /deep/ [layout][wrap-reverse] {
-ms-flex-wrap: wrap-reverse;
-webkit-flex-wrap: wrap-reverse;
flex-wrap: wrap-reverse;
}
html /deep/ [flex] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
html /deep/ [flex][auto] {
-ms-flex: 1 1 auto;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
}
html /deep/ [flex][none] {
-ms-flex: none;
-webkit-flex: none;
flex: none;
}
html /deep/ [flex][one] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
html /deep/ [flex][two] {
-ms-flex: 2;
-webkit-flex: 2;
flex: 2;
}
html /deep/ [flex][three] {
-ms-flex: 3;
-webkit-flex: 3;
flex: 3;
}
html /deep/ [flex][four] {
-ms-flex: 4;
-webkit-flex: 4;
flex: 4;
}
html /deep/ [flex][five] {
-ms-flex: 5;
-webkit-flex: 5;
flex: 5;
}
html /deep/ [flex][six] {
-ms-flex: 6;
-webkit-flex: 6;
flex: 6;
}
html /deep/ [flex][seven] {
-ms-flex: 7;
-webkit-flex: 7;
flex: 7;
}
html /deep/ [flex][eight] {
-ms-flex: 8;
-webkit-flex: 8;
flex: 8;
}
html /deep/ [flex][nine] {
-ms-flex: 9;
-webkit-flex: 9;
flex: 9;
}
html /deep/ [flex][ten] {
-ms-flex: 10;
-webkit-flex: 10;
flex: 10;
}
html /deep/ [flex][eleven] {
-ms-flex: 11;
-webkit-flex: 11;
flex: 11;
}
html /deep/ [flex][twelve] {
-ms-flex: 12;
-webkit-flex: 12;
flex: 12;
}
/* alignment in cross axis */
html /deep/ [layout][start] {
-ms-flex-align: start;
-webkit-align-items: flex-start;
align-items: flex-start;
}
html /deep/ [layout][center], html /deep/ [layout][center-center] {
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
html /deep/ [layout][end] {
-ms-flex-align: end;
-webkit-align-items: flex-end;
align-items: flex-end;
}
/* alignment in main axis */
html /deep/ [layout][start-justified] {
-ms-flex-pack: start;
-webkit-justify-content: flex-start;
justify-content: flex-start;
}
html /deep/ [layout][center-justified], html /deep/ [layout][center-center] {
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
html /deep/ [layout][end-justified] {
-ms-flex-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
}
html /deep/ [layout][around-justified] {
-ms-flex-pack: around;
-webkit-justify-content: space-around;
justify-content: space-around;
}
html /deep/ [layout][justified] {
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
}
/* self alignment */
html /deep/ [self-start] {
-ms-align-self: flex-start;
-webkit-align-self: flex-start;
align-self: flex-start;
}
html /deep/ [self-center] {
-ms-align-self: center;
-webkit-align-self: center;
align-self: center;
}
html /deep/ [self-end] {
-ms-align-self: flex-end;
-webkit-align-self: flex-end;
align-self: flex-end;
}
html /deep/ [self-stretch] {
-ms-align-self: stretch;
-webkit-align-self: stretch;
align-self: stretch;
}
/*******************************
Other Layout
*******************************/
html /deep/ [block] {
display: block;
}
/* ie support for hidden */
html /deep/ [hidden] {
display: none !important;
}
html /deep/ [relative] {
position: relative;
}
html /deep/ [fit] {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
body[fullbleed] {
margin: 0;
height: 100vh;
}
/*******************************
Other
*******************************/
html /deep/ [segment], html /deep/ segment {
display: block;
position: relative;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
margin: 1em 0.5em;
padding: 1em;
background-color: white;
-webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
border-radius: 5px 5px 5px 5px;
}
</style>

View File

@@ -0,0 +1,26 @@
<link rel="import" href="lang.html">
<link rel="import" href="layout.html">
<link rel="import" href="base.html">
<!-- pluggable features for Base-->
<link rel="import" href="utils.html">
<link rel="import" href="data.html">
<link rel="import" href="annotations.html">
<link rel="import" href="bindings.html">
<link rel="import" href="events.html">
<link rel="import" href="experimental.html">
<script>
Base.installFeatures();
Base.__proto__ = HTMLElement.prototype;
var Polymer = function(prototype) {
prototype.__proto__ = Base;
prototype.registerCallback();
document.registerElement(prototype.name, {prototype: prototype});
}
Polymer.log = {
};
</script>

View File

@@ -0,0 +1,83 @@
<script>
var instanceTemplate = function(template) {
return document.importNode(template.content, true);
};
var preprocessTemplate = function(template) {
var map = [];
var a = _preprocess(template.content, map);
if (a) {
a.map = map;
//console.log(a.map);
}
template.map = map;
};
var _preprocess = function(node, map) {
return node.nodeType === Node.TEXT_NODE ? preprocessTextNode(node, map) : preprocessNode(node, map);
};
var preprocessTextNode = function(node, map) {
var t = node.textContent;
if (t.slice(0, 2) === '{{' || t.slice(0, 2) === '[[') {
var annotations = Object.create(null);
annotations.name = 'text';
annotations.value = t;
map.push(annotations);
return annotations;
}
return null;
};
var preprocessNode = function(node, map) {
var annotations = Object.create(null);
parseAnnotations(node, annotations, map);
preprocessChildNodes(node, annotations, map);
return annotations;
};
var parseAnnotations = function(node, annotations, map) {
var bound = false;
if (node.attributes) {
var b$ = annotations.bindings = Object.create(null);
var e$ = annotations.events = Object.create(null);
for (var i=0, a; a=node.attributes[i]; i++) {
var n = a.name, v = a.value;
if (v.slice(0, 2) === '{{') {
b$[n] = {
raw: v,
prop: v.slice(2, -2)
};
bound = true;
}
if (n.slice(0, 3) === 'on-') {
e$[n.slice(3)] = v;
bound = true;
}
}
}
if (bound) {
map.push(annotations);
}
annotations.name = node.localName || 'template';
};
var preprocessChildNodes = function(root, annotations, map) {
if (root.firstChild) {
var c$ = annotations.children = [];
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var ann = _preprocess(node, map);
if (ann) {
ann.parent = annotations;
ann.index = i;
}
c$.push(ann);
}
}
};
var mapFind = function(root, n) {
return n.parent ? mapFind(root, n.parent).childNodes[n.index] : root;
};
</script>

View File

@@ -0,0 +1,36 @@
<script>
// a tiny bit of sugar for `document.currentScript.ownerDocument`
// sadly `import` is reserved, so we need another name or
// you have to refer to this value `window.import`
Object.defineProperty(window, 'import', {
enumerable: true,
configurable: true,
get: function() {
var script = document._currentScript || document.currentScript;
return script.ownerDocument;
}
});
// copy own properties from 'api' to 'prototype, with name hinting for 'super'
function extend(prototype, api) {
if (prototype && api) {
// use only own properties of 'api'
Object.getOwnPropertyNames(api).forEach(function(n) {
// acquire property descriptor
var pd = Object.getOwnPropertyDescriptor(api, n);
if (pd) {
// clone property via descriptor
Object.defineProperty(prototype, n, pd);
// cache name-of-method for 'super' engine
if (typeof pd.value == 'function') {
// hint the 'super' engine
pd.value.nom = n;
}
}
});
}
return prototype;
};
</script>

View File

@@ -0,0 +1,14 @@
<script>
Base.features.push({
$: function(slctr) {
return this.root.querySelector(slctr);
},
bindModel: function(model, properties) {
properties.forEach(function(p) {
this.bindProperty(p, p, model);
}, this);
}
});
</script>

View File

@@ -0,0 +1 @@
Mutation-2: 01: As with 00, but eschew normative ShadowDOM.

View File

@@ -0,0 +1,56 @@
<svg><defs>
<g id="apps"><path d="M4,8h4V4H4V8z M10,20h4v-4h-4V20z M4,20h4v-4H4V20z M4,14h4v-4H4V14z M10,14h4v-4h-4V14z M16,4v4h4V4H16z M10,8h4V4h-4V8z M16,14h4v-4h-4V14z M16,20h4v-4h-4V20z"/></g>
<g id="archive"><path d="M20.5,5.2l-1.4-1.7C18.9,3.2,18.5,3,18,3H6C5.5,3,5.1,3.2,4.8,3.5L3.5,5.2C3.2,5.6,3,6,3,6.5V19c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V6.5C21,6,20.8,5.6,20.5,5.2z M12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5z M5.1,5l0.8-1h12l0.9,1H5.1z"/></g>
<g id="check-box-outline-blank"><path d="M19,5v14L5,19V5H19 M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3L19,3z"/></g>
<g id="check-box-outline"><path d="M7.9,10.1l-1.4,1.4L11,16L21,6l-1.4-1.4L11,13.2L7.9,10.1z M19,19L5,19V5h10V3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-8h-2V19z"/></g>
<g id="arrow-drop-down"><polygon points="7,10 12,15 17,10 "/></g>
<g id="info-outline"><path d="M11,17h2v-6h-2V17z M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z M11,9h2V7h-2V9z"/></g>
<g id="search"><path d="M15.5,14h-0.8l-0.3-0.3c1-1.1,1.6-2.6,1.6-4.2C16,5.9,13.1,3,9.5,3C5.9,3,3,5.9,3,9.5S5.9,16,9.5,16c1.6,0,3.1-0.6,4.2-1.6l0.3,0.3v0.8l5,5l1.5-1.5L15.5,14z M9.5,14C7,14,5,12,5,9.5S7,5,9.5,5C12,5,14,7,14,9.5S12,14,9.5,14z"/></g>
<g id="arrow-back"><path d="M20,11H7.8l5.6-5.6L12,4l-8,8l8,8l1.4-1.4L7.8,13H20V11z"/></g>
<g id="schedule"><path fill-opacity="0.9" d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/><polygon fill-opacity="0.9" points="12.5,7 11,7 11,13 16.2,16.2 17,14.9 12.5,12.2 "/></g>
<g id="close"><polygon points="19,6.4 17.6,5 12,10.6 6.4,5 5,6.4 10.6,12 5,17.6 6.4,19 12,13.4 17.6,19 19,17.6 13.4,12 "/></g>
<g id="create"><path d="M3,17.2V21h3.8L17.8,9.9l-3.8-3.8L3,17.2z M20.7,7c0.4-0.4,0.4-1,0-1.4l-2.3-2.3c-0.4-0.4-1-0.4-1.4,0l-1.8,1.8l3.8,3.8L20.7,7z"/></g>
<g id="settings"><path d="M19.4,13c0-0.3,0.1-0.6,0.1-1s0-0.7-0.1-1l2.1-1.7c0.2-0.2,0.2-0.4,0.1-0.6l-2-3.5C19.5,5.1,19.3,5,19,5.1l-2.5,1c-0.5-0.4-1.1-0.7-1.7-1l-0.4-2.6C14.5,2.2,14.2,2,14,2h-4C9.8,2,9.5,2.2,9.5,2.4L9.1,5.1C8.5,5.3,8,5.7,7.4,6.1L5,5.1C4.7,5,4.5,5.1,4.3,5.3l-2,3.5C2.2,8.9,2.3,9.2,2.5,9.4L4.6,11c0,0.3-0.1,0.6-0.1,1s0,0.7,0.1,1l-2.1,1.7c-0.2,0.2-0.2,0.4-0.1,0.6l2,3.5C4.5,18.9,4.7,19,5,18.9l2.5-1c0.5,0.4,1.1,0.7,1.7,1l0.4,2.6c0,0.2,0.2,0.4,0.5,0.4h4c0.2,0,0.5-0.2,0.5-0.4l0.4-2.6c0.6-0.3,1.2-0.6,1.7-1l2.5,1c0.2,0.1,0.5,0,0.6-0.2l2-3.5c0.1-0.2,0.1-0.5-0.1-0.6L19.4,13z M12,15.5c-1.9,0-3.5-1.6-3.5-3.5s1.6-3.5,3.5-3.5s3.5,1.6,3.5,3.5S13.9,15.5,12,15.5z"/></g>
<g id="delete"><path d="M6,19c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V7H6V19z M19,4h-3.5l-1-1h-5l-1,1H5v2h14V4z"/></g>
<g id="schedule"><path fill-opacity="0.9" d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10c5.5,0,10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8c4.4,0,8,3.6,8,8S16.4,20,12,20z"/><polygon fill-opacity="0.9" points="12.5,7 11,7 11,13 16.2,16.2 17,14.9 12.5,12.2 "/></g>
<g id="menu"><path d="M3,18h18v-2H3V18z M3,13h18v-2H3V13z M3,6v2h18V6H3z"/></g>
</defs></svg>
<script>
var doc = window.import;
var cloneIcon = function(id, size) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
var icon = doc.querySelector(id);
if (icon) {
svg.style.pointerEvents = 'none';
svg.style.display = 'block';
svg.setAttribute('height', '100%');
svg.setAttribute('width', '100%');
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute('viewBox', '0 0 24 24');
svg.appendChild(icon.cloneNode(true));
}
return svg;
};
var icons = {
apps: cloneIcon('#apps'),
archive: cloneIcon('#archive'),
box: cloneIcon('#check-box-outline-blank'),
checked: cloneIcon('#check-box-outline'),
drop: cloneIcon('#arrow-drop-down'),
'info-outline': cloneIcon('#info-outline'),
search: cloneIcon('#search'),
back: cloneIcon('#arrow-back'),
schedule: cloneIcon('#schedule'),
create: cloneIcon('#create'),
close: cloneIcon('#close'),
delete: cloneIcon('#delete'),
schedule: cloneIcon('#schedule'),
settings: cloneIcon('#settings'),
menu: cloneIcon('#menu')
};
</script>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

View File

@@ -0,0 +1,2 @@
START /B vulcanize polymer.html --inline -output dist/polymer.html
START /B vulcanize data.html --inline -output dist/data.html

View File

@@ -0,0 +1,4 @@
<link rel="import" href="src/features/bind.html">
<link rel="import" href="src/features/annotations-bind.html">
<link rel="import" href="src/features/computed.html">
<link rel="import" href="src/features/bind-effects.html">

592
components/polymer/dist/data.html vendored Normal file
View File

@@ -0,0 +1,592 @@
<script>
/*
* Needs new name.
*
* Provides a data-binding API, by which a getter/setter pair
* can be constructed in one of two imperative modes:
*
* bindMethod(property, methodName): constructs a getter/setter pair and a
* backing store for the given property; calls the method when the setter
* is invoked and the value has changed from what is in the backing store.
*
* bindProperty(property, path): constructs a getter/setter pair that
* forwards data access to a property on another object.
*
* This feature also supports a `bind` object, which contains expressions
* that are deconstructed into `bindMethod` or `bindProperty` calls, or
* into a `multiBinding` construct. `multiBinding` constructs support
* multiple side-effects.
*
* bind {
* // if `method` is the name of a method on the current object, a
* // `bindMethod` call is made to define `property` as described above.
* property: 'method'
* // if the value is not the name of a method, it's assumed to be the
* // name in the `$` hash that maps to an element.
* // If no target property is specified, `textContent` is assumed to
* // be the backing-store for `property2` accessors.
* property2: 'elementId'
* // If a path is provided, that element is dereferenced from $ as before,
* // but the full path is used for the backing-store.
* // This declaration binds property3 to $.elementId.value
* property3: 'elementId.value'
* // If the specified property is also `published`, a multi-binding
* // construct is created which sends a change notification in addition
* // to whatever user side-effect is specified.
* publishedProperty: <method name or element property>
* // Specify multiple side-effects directly as an array. Only one
* // callback method is allowed.
* property4: [
* 'nameOfMethod',
* 'elementId',
* 'elementId.property',
* ...
* ]
* }
*
* Methods bound into multi-bind contexts support a validation feature. If
* the method returns a value that does not === undefined side-effects are
* prevented, and the triggering property is set to the returned value, and
* a new round of side-effects is initiated.
*
* Multi-bind = multiple side-effects for one signal.
* Note: `signal` today is `set-trap`, should we generalize?
* Side-effects can be registered by multiple subsystems:
* - bind feature
* - bind-annotations feature
* - computed feature
* - published feature
* We need to accumulate all side-effects for a particular property
* before constructing the handler.
*
*/
Base.addFeature({
// per prototype
// TODO(sjmiles): initialization of `_propertyEffects` and the
// `addPropertyEffect` itself are really the domain of bind-effects
// but these things needs to happen before bind-effects itself initializes.
// We need to factor bind-effects into before and after features instead
// and let this feature be for dealing with `bind` object.
register: function(prototype) {
prototype._addPropertyBindEffects();
},
// TODO(sjmiles): really ad hoc self-modifying code
// to resolve initialization ordering around optional
// module
addPropertyEffect: function(property, kind, effect) {
// prepare storage on first invocation
this._propertyEffects = {};
// add the effect
this._addPropertyEffect(property, kind, effect);
// subsequent invocations skip preparation step implementation
this.addPropertyEffect = this._addPropertyEffect;
},
_addPropertyEffect: function(property, kind, effect) {
var fx = this._propertyEffects[property];
if (!fx) {
fx = this._propertyEffects[property] = [];
}
fx.push({
kind: kind,
effect: effect
});
},
_addPropertyBindEffects: function() {
for (var n in this.bind) {
var bind = this.bind[n];
if (typeof bind === 'object') {
// multiplexed definition
for (var nn in bind) {
this._addPropertyBindEffect(n, bind[nn]);
}
} else {
// single definition
this._addPropertyBindEffect(n, bind);
}
}
},
_addPropertyBindEffect: function(property, bindEffect) {
this.addPropertyEffect(property, 'bind', bindEffect);
}
});
// TODO(sjmiles): this code was ported from an earlier mutation and needs
// a cleanup to cleave closer to neoprene MO
/*
Scans a template to produce an annotation map that stores expression metadata
and information that can be used to associate that metadata with the
corresponding nodes in a template instance.
Supported annotations are:
* id attributes
* binding annotations in text nodes
* double-mustache expressions: {{expression}}
* double-bracket expressions: [[expression]]
* binding annotations in attributes
* attribute-bind expressions: name="{{expression}} || [[expression]]"
* property-bind expressions: name*="{{expression}} || [[expression]]"
* property-bind expressions: name:="expression"
* event annotations
* event delegation directives: on-<eventName>="expression"
Generated data-structure:
[
{
id: '<id>',
events: [
{
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
bindings: [
{
kind: ['text'|'attribute'|'property'],
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
// TODO(sjmiles): confusingly, this is annotation-parent, not node-parent
parent: <reference to parent annotation>,
index: <integer index in parent's childNodes collection>
},
...
]
TODO(sjmiles): this module should produce either syntactic metadata
(e.g. double-mustache, double-bracket, star-attr), or semantic metadata
(e.g. manual-bind, auto-bind, property-bind). Right now it's half and half.
*/
Base.addFeature({
// instance-time
findAnnotatedNode: function(root, annote) {
if (!annote.parent) {
return root;
}
var parent = this.findAnnotatedNode(root, annote.parent);
// enforce locality.
var nodes = (parent === this) ? parent.childNodes :
(parent.lightChildren || parent.childNodes);
return nodes[annote.index];
},
// registration-time
register: function(prototype) {
if (prototype._template) {
prototype.parseAnnotations(prototype._template)
}
},
parseAnnotations: function(template) {
// TODO(sjmiles): it's not a map, per se
var map = [];
this._parseNodeAnnotations(template.content, map);
if (map.length) {
template.map = map;
}
return template.map;
},
_parseNodeAnnotations: function(node, map) {
return node.nodeType === Node.TEXT_NODE ?
this._parseTextNodeAnnotation(node, map) :
this._parseElementAnnotations(node, map);
},
_parseTextNodeAnnotation: function(node, map) {
var v = node.textContent, escape = v.slice(0, 2);
if (escape === '{{' || escape === '[[') {
var annotation = {
bindings: [{
kind: 'text',
mode: escape === '{{' ? 'auto' : '',
value: v.slice(2, -2)
}]
};
map.push(annotation);
return annotation;
}
},
_parseElementAnnotations: function(node, map) {
var annote = {
bindings: [],
events: []
};
this._parseChildNodesAnnotations(node, annote, map);
if (node.attributes) {
this._parseNodeAttributeAnnotations(node, annote, map);
}
if (annote.bindings.length || annote.events.length || annote.id) {
map.push(annote);
}
return annote;
},
_parseChildNodesAnnotations: function(root, annotation, map) {
if (root.firstChild) {
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var childAnnotation = this._parseNodeAnnotations(node, map);
if (childAnnotation) {
childAnnotation.parent = annotation;
childAnnotation.index = i;
}
}
}
},
_parseNodeAttributeAnnotations: function(node, annotation) {
for (var i=0, a; (a=node.attributes[i]); i++) {
var n = a.name, v = a.value;
// id
if (n === 'id') {
annotation.id = v;
}
// on-* (event)
else if (n.slice(0, 3) === 'on-') {
annotation.events.push({
name: n.slice(3),
value: v
});
}
// other attribute
else {
var b = this._parseNodeAttributeAnnotation(node, n, v);
if (b) {
annotation.bindings.push(b);
}
}
}
},
_parseNodeAttributeAnnotation: function(node, n, v) {
var escape = v.slice(0, 2), lastChar = n[n.length-1];
var kind = 'attribute', mode = '';
if (lastChar === '*' || lastChar === ':') {
n = n.slice(0, -1);
kind = 'property';
mode = 'auto';
}
if (escape === '{{') {
mode = 'auto';
v = v.slice(2, -2);
}
if (escape === '[[') {
mode = 'manual';
v = v.slice(2, -2);
}
if (mode) {
if (n === 'style') {
kind = 'style';
}
return {
kind: kind,
mode: mode,
name: n,
value: v
};
}
}
});
/*
* Parses the annotations map created by `annotations` features to perform
* declarative desugaring.
*
* Depends on `annotations` feature and `bind` feature.
*
* Two tasks are supported:
*
* - nodes with 'id' are described in a virtual annotation map at
* registration time. This map is then concretized per instance.
*
* - Simple mustache expressions consisting of a single property name
* in a `textContent` context are bound using `bind` features
* `bindMethod`. In this mode, the bound method is constructed at
* registration time, so marshaling is done done via the concretized
* `_nodes` at every access.
*
* TODO(sjmiles): ph3ar general confusion between registration and
* instance time tasks. Is there a cleaner way to disambiguate?
*/
Base.addFeature({
// registration-time
register: function(prototype) {
if (prototype._template && prototype._template.map) {
this._preprocessBindAnnotations(prototype, prototype._template.map);
}
},
// construct binding meta-data at *registration* time
_preprocessBindAnnotations: function(prototype, map) {
// create a virtual annotation map, must be concretized at instance time
prototype._nodes = [];
// process annotations that have been parsed from template
map.forEach(function(annotation) {
// where to find the node in the concretized map
var index = prototype._nodes.push(annotation) - 1;
// TODO(sjmiles): we need to support multi-bind, right now you only get
// one (not including kind === `id`)
annotation.bindings.forEach(function(binding) {
prototype._bindAnnotationBinding(binding, index);
});
});
},
_bindAnnotationBinding: function(binding, index) {
// add to the list of property side-effects
binding.index = index;
this.addPropertyEffect(binding.value, 'annotation', binding);
}
});
Base.addFeature({
/* computed property feature */
computed: {
},
register: function(prototype) {
prototype.defineComputedProperties(prototype.computed);
},
defineComputedProperties: function(computed) {
for (var n in computed) {
this.defineComputedProperty(n, computed[n]);
}
},
defineComputedProperty: function(name, expression) {
var index = expression.indexOf('(');
var method = expression.slice(0, index);
var args = expression.slice(index + 1, -1).replace(/ /g, '').split(',');
console.log('%c on [%s] compute [%s] via [%s]', 'color: green', args[0], name, method);
this.addPropertyEffect(args[0], 'compute', {
property: name,
method: method
});
/*
this.compoundWatch(args, function() {
Polymer.log.watches && console.log('[compute] [%s]', name, arguments);
this[name] = method.apply(this, arguments);
});
*/
}
});
Base.addFeature({
// per instance
init: function() {
this._data = Object.create(null);
},
_setupBindListeners: function() {
var bl = this._bindListeners;
for (var n in bl) {
bl[n].targets.forEach(function(target) {
this._setupBindListener(n, target);
}, this);
}
},
_setupBindListener: function(property, target) {
//console.log('[bind]: [%s][%s] listening for [%s][%s-changed]', this.localName, property, target.id || target.index, target.property);
var host = this, property;
var node = target.id ? this.$[target.id] : this._nodes[target.index];
node.addEventListener(target.property + '-changed', function(e) {
//console.log('[bind]:[%s] heard [%s-changed] this.[%s] = [%s]', host.localName, source, property, e.detail);
host[property] = e.detail;
});
},
_notifyChange: function(property) {
this.fire(property + '-changed', this[property], null, false);
},
_setData: function(property, value) {
var old = this._data[property];
if (old !== value) {
this._data[property] = value;
}
return old;
},
// per prototype
register: function(prototype) {
prototype._bindListeners = {};
prototype._createBindings();
},
_createBindings: function() {
//console.group(this.name);
var fx = this._propertyEffects;
for (var n in fx) {
//console.group(n);
var compiledEffects = fx[n].map(function(x) {
return this._buildEffect(n, x);
}, this);
this._bindPropertyEffects(n, compiledEffects);
//console.log(fxt.join('\n'));
//console.groupEnd();
}
//console.groupEnd();
},
_buildEffect: function(property, fx) {
return this['_' + fx.kind + 'EffectBuilder'](property, fx.effect);
},
_bindEffectBuilder: function(source, effect) {
// TODO(sjmiles): validation system requires a blessed
// validator effect which needs to be processed first.
/*
if (typeof this[effect] === 'function') {
return [
'var validated = this.' + effect + '(value, old)',
'if (validated !== undefined) {',
' // recurse',
' this[property] = validated;',
' return;',
'}'
].join('\n');
}
*/
//
// TODO(sjmiles): try/catch is temporary
//try {
if (typeof this[effect] === 'function') {
return 'this.' + effect + '(this._data.' + source + ', old);'
}
//} catch(x) {}
//
var paths = effect.split('.');
var id = paths.shift();
var property = paths.join('.');
//
if (property) {
// TODO(sjmiles): awkward: store data for instance-time listeners.
// _addBindListener is in bind.html, if we did the path processing
// in that module we could contain all the listener logic there too.
this._addBindListener(source, id, property);
} else {
property = 'textContent';
}
//
return 'this.$.' + id + '.' + property + ' = '
+ 'this._data.' + source + ';'
},
_bindPropertyEffects: function(property, effects) {
var defun = {
get: function() {
return this._data[property];
}
}
if (effects.length) {
// combine effects
effects = effects.join('\n\t\t');
// construct effector
var effector = '_' + property + 'Effector';
this[effector] = new Function('old', effects);
// construct setter body
var body = '\tvar old = this._setData(\'' + property + '\', value);\n'
+ '\tif (value !== old) {\n'
+ '\t\tthis.' + effector + '(old);\n'
+ '\t}';
var setter = new Function('value', body);
// ReadOnly properties have a private setter only
if (this.isReadOnlyProperty(property)) {
this['_set_' + property] = setter;
}
// other properties have a proper setter
else {
defun.set = setter;
}
}
Object.defineProperty(this, property, defun);
//var prop = Object.getOwnPropertyDescriptor(this, property);
//console.log(prop.set ? prop.set.toString() : '(read-only)');
},
_notifyEffectBuilder: function(source) {
return 'this._notifyChange(\'' + source + '\')';
},
_computeEffectBuilder: function(source, effect) {
return 'this.' + effect.property
+ ' = this.' + effect.method + '(this._data.' + source + ');';
},
_annotationEffectBuilder: function(source, binding) {
var target = binding.name || 'textContent';
if (binding.kind !== 'text' && binding.kind !== 'attribute') {
console.warn(binding.kind);
return;
}
if (target !== 'textContent') {
this._addAnnotatedListener(source, binding.index, target);
}
return this._bindAnnotationProperty(source, target, binding.index);
},
_bindAnnotationProperty: function(source, target, index) {
return 'this._nodes[' + index + '].' + target
+ ' = this._data.' + source + ';';
},
_addBindListener: function(source, id, property) {
var bl = this._requireBindListeners(source);
bl.targets.push({
id: id,
property: property
});
},
_addAnnotatedListener: function(source, index, property) {
var bl = this._requireBindListeners(source);
bl.targets.push({
index: index,
property: property
});
},
_requireBindListeners: function(source) {
var bl = this._bindListeners[source];
if (!bl) {
bl = this._bindListeners[source] = {targets: []};
}
return bl;
}
});
</script>

BIN
components/polymer/dist/data.html.gz vendored Normal file

Binary file not shown.

589
components/polymer/dist/data.js vendored Normal file
View File

@@ -0,0 +1,589 @@
/*
* Needs new name.
*
* Provides a data-binding API, by which a getter/setter pair
* can be constructed in one of two imperative modes:
*
* bindMethod(property, methodName): constructs a getter/setter pair and a
* backing store for the given property; calls the method when the setter
* is invoked and the value has changed from what is in the backing store.
*
* bindProperty(property, path): constructs a getter/setter pair that
* forwards data access to a property on another object.
*
* This feature also supports a `bind` object, which contains expressions
* that are deconstructed into `bindMethod` or `bindProperty` calls, or
* into a `multiBinding` construct. `multiBinding` constructs support
* multiple side-effects.
*
* bind {
* // if `method` is the name of a method on the current object, a
* // `bindMethod` call is made to define `property` as described above.
* property: 'method'
* // if the value is not the name of a method, it's assumed to be the
* // name in the `$` hash that maps to an element.
* // If no target property is specified, `textContent` is assumed to
* // be the backing-store for `property2` accessors.
* property2: 'elementId'
* // If a path is provided, that element is dereferenced from $ as before,
* // but the full path is used for the backing-store.
* // This declaration binds property3 to $.elementId.value
* property3: 'elementId.value'
* // If the specified property is also `published`, a multi-binding
* // construct is created which sends a change notification in addition
* // to whatever user side-effect is specified.
* publishedProperty: <method name or element property>
* // Specify multiple side-effects directly as an array. Only one
* // callback method is allowed.
* property4: [
* 'nameOfMethod',
* 'elementId',
* 'elementId.property',
* ...
* ]
* }
*
* Methods bound into multi-bind contexts support a validation feature. If
* the method returns a value that does not === undefined side-effects are
* prevented, and the triggering property is set to the returned value, and
* a new round of side-effects is initiated.
*
* Multi-bind = multiple side-effects for one signal.
* Note: `signal` today is `set-trap`, should we generalize?
* Side-effects can be registered by multiple subsystems:
* - bind feature
* - bind-annotations feature
* - computed feature
* - published feature
* We need to accumulate all side-effects for a particular property
* before constructing the handler.
*
*/
Base.addFeature({
// per prototype
// TODO(sjmiles): initialization of `_propertyEffects` and the
// `addPropertyEffect` itself are really the domain of bind-effects
// but these things needs to happen before bind-effects itself initializes.
// We need to factor bind-effects into before and after features instead
// and let this feature be for dealing with `bind` object.
register: function(prototype) {
prototype._addPropertyBindEffects();
},
// TODO(sjmiles): really ad hoc self-modifying code
// to resolve initialization ordering around optional
// module
addPropertyEffect: function(property, kind, effect) {
// prepare storage on first invocation
this._propertyEffects = {};
// add the effect
this._addPropertyEffect(property, kind, effect);
// subsequent invocations skip preparation step implementation
this.addPropertyEffect = this._addPropertyEffect;
},
_addPropertyEffect: function(property, kind, effect) {
var fx = this._propertyEffects[property];
if (!fx) {
fx = this._propertyEffects[property] = [];
}
fx.push({
kind: kind,
effect: effect
});
},
_addPropertyBindEffects: function() {
for (var n in this.bind) {
var bind = this.bind[n];
if (typeof bind === 'object') {
// multiplexed definition
for (var nn in bind) {
this._addPropertyBindEffect(n, bind[nn]);
}
} else {
// single definition
this._addPropertyBindEffect(n, bind);
}
}
},
_addPropertyBindEffect: function(property, bindEffect) {
this.addPropertyEffect(property, 'bind', bindEffect);
}
});
// TODO(sjmiles): this code was ported from an earlier mutation and needs
// a cleanup to cleave closer to neoprene MO
/*
Scans a template to produce an annotation map that stores expression metadata
and information that can be used to associate that metadata with the
corresponding nodes in a template instance.
Supported annotations are:
* id attributes
* binding annotations in text nodes
* double-mustache expressions: {{expression}}
* double-bracket expressions: [[expression]]
* binding annotations in attributes
* attribute-bind expressions: name="{{expression}} || [[expression]]"
* property-bind expressions: name*="{{expression}} || [[expression]]"
* property-bind expressions: name:="expression"
* event annotations
* event delegation directives: on-<eventName>="expression"
Generated data-structure:
[
{
id: '<id>',
events: [
{
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
bindings: [
{
kind: ['text'|'attribute'|'property'],
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
// TODO(sjmiles): confusingly, this is annotation-parent, not node-parent
parent: <reference to parent annotation>,
index: <integer index in parent's childNodes collection>
},
...
]
TODO(sjmiles): this module should produce either syntactic metadata
(e.g. double-mustache, double-bracket, star-attr), or semantic metadata
(e.g. manual-bind, auto-bind, property-bind). Right now it's half and half.
*/
Base.addFeature({
// instance-time
findAnnotatedNode: function(root, annote) {
if (!annote.parent) {
return root;
}
var parent = this.findAnnotatedNode(root, annote.parent);
// enforce locality.
var nodes = (parent === this) ? parent.childNodes :
(parent.lightChildren || parent.childNodes);
return nodes[annote.index];
},
// registration-time
register: function(prototype) {
if (prototype._template) {
prototype.parseAnnotations(prototype._template)
}
},
parseAnnotations: function(template) {
// TODO(sjmiles): it's not a map, per se
var map = [];
this._parseNodeAnnotations(template.content, map);
if (map.length) {
template.map = map;
}
return template.map;
},
_parseNodeAnnotations: function(node, map) {
return node.nodeType === Node.TEXT_NODE ?
this._parseTextNodeAnnotation(node, map) :
this._parseElementAnnotations(node, map);
},
_parseTextNodeAnnotation: function(node, map) {
var v = node.textContent, escape = v.slice(0, 2);
if (escape === '{{' || escape === '[[') {
var annotation = {
bindings: [{
kind: 'text',
mode: escape === '{{' ? 'auto' : '',
value: v.slice(2, -2)
}]
};
map.push(annotation);
return annotation;
}
},
_parseElementAnnotations: function(node, map) {
var annote = {
bindings: [],
events: []
};
this._parseChildNodesAnnotations(node, annote, map);
if (node.attributes) {
this._parseNodeAttributeAnnotations(node, annote, map);
}
if (annote.bindings.length || annote.events.length || annote.id) {
map.push(annote);
}
return annote;
},
_parseChildNodesAnnotations: function(root, annotation, map) {
if (root.firstChild) {
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var childAnnotation = this._parseNodeAnnotations(node, map);
if (childAnnotation) {
childAnnotation.parent = annotation;
childAnnotation.index = i;
}
}
}
},
_parseNodeAttributeAnnotations: function(node, annotation) {
for (var i=0, a; (a=node.attributes[i]); i++) {
var n = a.name, v = a.value;
// id
if (n === 'id') {
annotation.id = v;
}
// on-* (event)
else if (n.slice(0, 3) === 'on-') {
annotation.events.push({
name: n.slice(3),
value: v
});
}
// other attribute
else {
var b = this._parseNodeAttributeAnnotation(node, n, v);
if (b) {
annotation.bindings.push(b);
}
}
}
},
_parseNodeAttributeAnnotation: function(node, n, v) {
var escape = v.slice(0, 2), lastChar = n[n.length-1];
var kind = 'attribute', mode = '';
if (lastChar === '*' || lastChar === ':') {
n = n.slice(0, -1);
kind = 'property';
mode = 'auto';
}
if (escape === '{{') {
mode = 'auto';
v = v.slice(2, -2);
}
if (escape === '[[') {
mode = 'manual';
v = v.slice(2, -2);
}
if (mode) {
if (n === 'style') {
kind = 'style';
}
return {
kind: kind,
mode: mode,
name: n,
value: v
};
}
}
});
/*
* Parses the annotations map created by `annotations` features to perform
* declarative desugaring.
*
* Depends on `annotations` feature and `bind` feature.
*
* Two tasks are supported:
*
* - nodes with 'id' are described in a virtual annotation map at
* registration time. This map is then concretized per instance.
*
* - Simple mustache expressions consisting of a single property name
* in a `textContent` context are bound using `bind` features
* `bindMethod`. In this mode, the bound method is constructed at
* registration time, so marshaling is done done via the concretized
* `_nodes` at every access.
*
* TODO(sjmiles): ph3ar general confusion between registration and
* instance time tasks. Is there a cleaner way to disambiguate?
*/
Base.addFeature({
// registration-time
register: function(prototype) {
if (prototype._template && prototype._template.map) {
this._preprocessBindAnnotations(prototype, prototype._template.map);
}
},
// construct binding meta-data at *registration* time
_preprocessBindAnnotations: function(prototype, map) {
// create a virtual annotation map, must be concretized at instance time
prototype._nodes = [];
// process annotations that have been parsed from template
map.forEach(function(annotation) {
// where to find the node in the concretized map
var index = prototype._nodes.push(annotation) - 1;
// TODO(sjmiles): we need to support multi-bind, right now you only get
// one (not including kind === `id`)
annotation.bindings.forEach(function(binding) {
prototype._bindAnnotationBinding(binding, index);
});
});
},
_bindAnnotationBinding: function(binding, index) {
// add to the list of property side-effects
binding.index = index;
this.addPropertyEffect(binding.value, 'annotation', binding);
}
});
Base.addFeature({
/* computed property feature */
computed: {
},
register: function(prototype) {
prototype.defineComputedProperties(prototype.computed);
},
defineComputedProperties: function(computed) {
for (var n in computed) {
this.defineComputedProperty(n, computed[n]);
}
},
defineComputedProperty: function(name, expression) {
var index = expression.indexOf('(');
var method = expression.slice(0, index);
var args = expression.slice(index + 1, -1).replace(/ /g, '').split(',');
console.log('%c on [%s] compute [%s] via [%s]', 'color: green', args[0], name, method);
this.addPropertyEffect(args[0], 'compute', {
property: name,
method: method
});
/*
this.compoundWatch(args, function() {
Polymer.log.watches && console.log('[compute] [%s]', name, arguments);
this[name] = method.apply(this, arguments);
});
*/
}
});
Base.addFeature({
// per instance
init: function() {
this._data = Object.create(null);
},
_setupBindListeners: function() {
var bl = this._bindListeners;
for (var n in bl) {
bl[n].targets.forEach(function(target) {
this._setupBindListener(n, target);
}, this);
}
},
_setupBindListener: function(property, target) {
//console.log('[bind]: [%s][%s] listening for [%s][%s-changed]', this.localName, property, target.id || target.index, target.property);
var host = this, property;
var node = target.id ? this.$[target.id] : this._nodes[target.index];
node.addEventListener(target.property + '-changed', function(e) {
//console.log('[bind]:[%s] heard [%s-changed] this.[%s] = [%s]', host.localName, source, property, e.detail);
host[property] = e.detail;
});
},
_notifyChange: function(property) {
this.fire(property + '-changed', this[property], null, false);
},
_setData: function(property, value) {
var old = this._data[property];
if (old !== value) {
this._data[property] = value;
}
return old;
},
// per prototype
register: function(prototype) {
prototype._bindListeners = {};
prototype._createBindings();
},
_createBindings: function() {
//console.group(this.name);
var fx = this._propertyEffects;
for (var n in fx) {
//console.group(n);
var compiledEffects = fx[n].map(function(x) {
return this._buildEffect(n, x);
}, this);
this._bindPropertyEffects(n, compiledEffects);
//console.log(fxt.join('\n'));
//console.groupEnd();
}
//console.groupEnd();
},
_buildEffect: function(property, fx) {
return this['_' + fx.kind + 'EffectBuilder'](property, fx.effect);
},
_bindEffectBuilder: function(source, effect) {
// TODO(sjmiles): validation system requires a blessed
// validator effect which needs to be processed first.
/*
if (typeof this[effect] === 'function') {
return [
'var validated = this.' + effect + '(value, old)',
'if (validated !== undefined) {',
' // recurse',
' this[property] = validated;',
' return;',
'}'
].join('\n');
}
*/
//
// TODO(sjmiles): try/catch is temporary
//try {
if (typeof this[effect] === 'function') {
return 'this.' + effect + '(this._data.' + source + ', old);'
}
//} catch(x) {}
//
var paths = effect.split('.');
var id = paths.shift();
var property = paths.join('.');
//
if (property) {
// TODO(sjmiles): awkward: store data for instance-time listeners.
// _addBindListener is in bind.html, if we did the path processing
// in that module we could contain all the listener logic there too.
this._addBindListener(source, id, property);
} else {
property = 'textContent';
}
//
return 'this.$.' + id + '.' + property + ' = '
+ 'this._data.' + source + ';'
},
_bindPropertyEffects: function(property, effects) {
var defun = {
get: function() {
return this._data[property];
}
}
if (effects.length) {
// combine effects
effects = effects.join('\n\t\t');
// construct effector
var effector = '_' + property + 'Effector';
this[effector] = new Function('old', effects);
// construct setter body
var body = '\tvar old = this._setData(\'' + property + '\', value);\n'
+ '\tif (value !== old) {\n'
+ '\t\tthis.' + effector + '(old);\n'
+ '\t}';
var setter = new Function('value', body);
// ReadOnly properties have a private setter only
if (this.isReadOnlyProperty(property)) {
this['_set_' + property] = setter;
}
// other properties have a proper setter
else {
defun.set = setter;
}
}
Object.defineProperty(this, property, defun);
//var prop = Object.getOwnPropertyDescriptor(this, property);
//console.log(prop.set ? prop.set.toString() : '(read-only)');
},
_notifyEffectBuilder: function(source) {
return 'this._notifyChange(\'' + source + '\')';
},
_computeEffectBuilder: function(source, effect) {
return 'this.' + effect.property
+ ' = this.' + effect.method + '(this._data.' + source + ');';
},
_annotationEffectBuilder: function(source, binding) {
var target = binding.name || 'textContent';
if (binding.kind !== 'text' && binding.kind !== 'attribute') {
console.warn(binding.kind);
return;
}
if (target !== 'textContent') {
this._addAnnotatedListener(source, binding.index, target);
}
return this._bindAnnotationProperty(source, target, binding.index);
},
_bindAnnotationProperty: function(source, target, index) {
return 'this._nodes[' + index + '].' + target
+ ' = this._data.' + source + ';';
},
_addBindListener: function(source, id, property) {
var bl = this._requireBindListeners(source);
bl.targets.push({
id: id,
property: property
});
},
_addAnnotatedListener: function(source, index, property) {
var bl = this._requireBindListeners(source);
bl.targets.push({
index: index,
property: property
});
},
_requireBindListeners: function(source) {
var bl = this._bindListeners[source];
if (!bl) {
bl = this._bindListeners[source] = {targets: []};
}
return bl;
}
});

1
components/polymer/dist/data.min.html vendored Normal file

File diff suppressed because one or more lines are too long

1
components/polymer/dist/data.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
components/polymer/dist/data.min.js.gz vendored Normal file

Binary file not shown.

275
components/polymer/dist/layout.css vendored Normal file
View File

@@ -0,0 +1,275 @@
/*******************************
Flex Layout
*******************************/
[layout][horizontal], [layout][vertical] {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
[layout][horizontal][inline], [layout][vertical][inline] {
display: -ms-inline-flexbox;
display: -webkit-inline-flex;
display: inline-flex;
}
[layout][horizontal] {
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
}
[layout][horizontal][reverse] {
-ms-flex-direction: row-reverse;
-webkit-flex-direction: row-reverse;
flex-direction: row-reverse;
}
[layout][vertical] {
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
}
[layout][vertical][reverse] {
-ms-flex-direction: column-reverse;
-webkit-flex-direction: column-reverse;
flex-direction: column-reverse;
}
[layout][wrap] {
-ms-flex-wrap: wrap;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
}
[layout][wrap-reverse] {
-ms-flex-wrap: wrap-reverse;
-webkit-flex-wrap: wrap-reverse;
flex-wrap: wrap-reverse;
}
[flex] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
[flex][auto] {
-ms-flex: 1 1 auto;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
}
[flex][none] {
-ms-flex: none;
-webkit-flex: none;
flex: none;
}
[flex][one] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
[flex][two] {
-ms-flex: 2;
-webkit-flex: 2;
flex: 2;
}
[flex][three] {
-ms-flex: 3;
-webkit-flex: 3;
flex: 3;
}
[flex][four] {
-ms-flex: 4;
-webkit-flex: 4;
flex: 4;
}
[flex][five] {
-ms-flex: 5;
-webkit-flex: 5;
flex: 5;
}
[flex][six] {
-ms-flex: 6;
-webkit-flex: 6;
flex: 6;
}
[flex][seven] {
-ms-flex: 7;
-webkit-flex: 7;
flex: 7;
}
[flex][eight] {
-ms-flex: 8;
-webkit-flex: 8;
flex: 8;
}
[flex][nine] {
-ms-flex: 9;
-webkit-flex: 9;
flex: 9;
}
[flex][ten] {
-ms-flex: 10;
-webkit-flex: 10;
flex: 10;
}
[flex][eleven] {
-ms-flex: 11;
-webkit-flex: 11;
flex: 11;
}
[flex][twelve] {
-ms-flex: 12;
-webkit-flex: 12;
flex: 12;
}
/* alignment in cross axis */
[layout][start] {
-ms-flex-align: start;
-webkit-align-items: flex-start;
align-items: flex-start;
}
[layout][center], [layout][center-center] {
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
[layout][end] {
-ms-flex-align: end;
-webkit-align-items: flex-end;
align-items: flex-end;
}
/* alignment in main axis */
[layout][start-justified] {
-ms-flex-pack: start;
-webkit-justify-content: flex-start;
justify-content: flex-start;
}
[layout][center-justified], [layout][center-center] {
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
[layout][end-justified] {
-ms-flex-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
}
[layout][around-justified] {
-ms-flex-pack: around;
-webkit-justify-content: space-around;
justify-content: space-around;
}
[layout][justified] {
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
}
/* self alignment */
[self-start] {
-ms-align-self: flex-start;
-webkit-align-self: flex-start;
align-self: flex-start;
}
[self-center] {
-ms-align-self: center;
-webkit-align-self: center;
align-self: center;
}
[self-end] {
-ms-align-self: flex-end;
-webkit-align-self: flex-end;
align-self: flex-end;
}
[self-stretch] {
-ms-align-self: stretch;
-webkit-align-self: stretch;
align-self: stretch;
}
/*******************************
Other Layout
*******************************/
[block] {
display: block;
}
/* ie support for hidden */
[hidden] {
display: none !important;
}
[invisible] {
visibility: hidden !important;
}
[relative] {
position: relative;
}
[fit] {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
body[fullbleed] {
margin: 0;
height: 100vh;
}
[scroll] {
overflow: auto;
}
/*******************************
Other
*******************************/
[segment], segment {
display: block;
position: relative;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
margin: 1em 0.5em;
padding: 1em;
background-color: white;
-webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
border-radius: 5px 5px 5px 5px;
}

BIN
components/polymer/dist/layout.css.gz vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<link rel="stylesheet" href="layout.css">
<script src="polymer.min.js><script>
<script src="data.min.js><script>

631
components/polymer/dist/polymer.html vendored Normal file
View File

@@ -0,0 +1,631 @@
<script>
Object.defineProperty(window, 'import', {
enumerable: true,
configurable: true,
get: function() {
return (document._currentScript || document.currentScript).ownerDocument;
}
});
function extend(prototype, api) {
if (prototype && api) {
Object.getOwnPropertyNames(api).forEach(function(n) {
var pd = Object.getOwnPropertyDescriptor(api, n);
if (pd) {
Object.defineProperty(prototype, n, pd);
}
});
}
return prototype;
};
Event.prototype.keys = {
ESC_KEY: 27,
ENTER_KEY: 13
};
Base = {
_features: [],
addFeature: function(feature) {
this._features.push(feature);
extend(Base, feature);
delete Base.init;
delete Base.register;
},
registerCallback: function() {
var prototype = this;
this.registerFeatures(prototype);
this.registered(prototype);
},
registered: function(prototype) {
},
registerFeatures: function(prototype) {
var f$ = this._features;
for (var i=0, n=f$.length; i<n && (f=f$[i]); i++) {
f.register && f.register(prototype);
}
},
createdCallback: function() {
this.root = this;
this.beforeCreated();
this.initFeatures();
this.created();
this.afterCreated();
},
beforeCreated: function() {
},
initFeatures: function() {
var f$ = this._features;
for (var i=0, n=f$.length; i<n && (f=f$[i]); i++) {
f.init && f.init.call(this);
}
},
created: function() {
},
afterCreated: function() {
},
attachedCallback: function() {
this.attached();
},
attached: function() {
},
detachedCallback: function() {
this.detached();
},
detached: function() {
},
attributeChangedCallback: function() {
this.attributeChanged.apply(this, arguments);
},
attributeChanged: function() {
}
};
Base.__proto__ = HTMLElement.prototype;
Polymer = function(prototype) {
prototype.__proto__ = Base;
prototype.registerCallback();
document.registerElement(prototype.name, {prototype: prototype});
};
Polymer.log = {
};
Base.addFeature({
log: function() {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = '[%s]: ' + args[0];
args.splice(1, 0, this.localName);
console.log.apply(console, args);
}
});
Base.addFeature({
published: {
},
nob: Object.create(null),
register: function(prototype) {
if (prototype.addPropertyEffect) {
for (var n in prototype.published) {
if (prototype.isNotifyProperty(n)) {
prototype.addPropertyEffect(n, 'notify');
}
}
}
},
getPublishInfo: function(property) {
var p = this.published[property];
if (typeof(p) === 'function') {
p = this.published[property] = {
type: p
};
}
return p || Base.nob;
},
getPublishedPropertyType: function(property) {
return this.getPublishInfo(property).type;
},
isReadOnlyProperty: function(property) {
return this.getPublishInfo(property).readOnly;
},
isNotifyProperty: function(property) {
return this.getPublishInfo(property).notify;
},
isReflectedProperty: function(property) {
return this.getPublishInfo(property).reflect;
}
});
Base.addFeature({
init: function() {
if (this.hostAttributes) {
this.cloneAttributes(this, this.hostAttributes);
}
},
cloneAttributes: function(node, attr$) {
attr$.split(' ').forEach(function(a) {
node.setAttribute(a, '');
});
}
});
Base.addFeature({
takeAttributes: function() {
for (var n in this.published) {
this.attributeChanged(n);
}
},
attributeChanged: function(name) {
var type = this.getPublishedPropertyType(name);
if (type) {
this.deserialize(name, type);
}
},
deserialize: function(name, type) {
var value = this.getAttribute(name);
switch(type) {
case Number:
value = Number(value) || this[name];
break;
case Boolean:
value = this.hasAttribute(name);
break;
case Object:
case Array:
try {
value = JSON.parse(value);
} catch(x) {
return;
}
break;
case Date:
value = Date.parse(value);
break;
case String:
default:
break;
}
this[name] = value;
}
});
Base.addFeature({
register: function(prototype) {
var script = (document._currentScript || document.currentScript);
var prev = script.previousElementSibling;
if (prev && prev.localName === 'template') {
prototype._template = prev;
}
},
stampTemplate: function(template) {
this._stampTemplate(template || this._template, this.root);
if (window.CustomElements && CustomElements.upgradeSubtree) {
CustomElements.upgradeSubtree(this.root);
}
},
_stampTemplate: function(template, target) {
target.insertBefore(this.instanceTemplate(template),
target.firstElementChild);
},
instanceTemplate: function(template) {
return document.importNode(template.content, true);
}
});
Base.addFeature({
isHost: true,
register: function(prototype) {
var t = prototype._template;
prototype._useContent = Boolean(t && t.content.querySelector('content'));
},
poolContent: function() {
var pool = document.createDocumentFragment();
while (this.firstChild) {
pool.appendChild(this.firstChild);
}
this.contentPool = pool;
this.lightChildren =
Array.prototype.slice.call(this.contentPool.childNodes, 0);
},
distributeContent: function() {
var content, pool = this.contentPool;
while (content = this.querySelector('content')) {
var select = content.getAttribute('select');
var frag = pool;
if (select) {
frag = document.createDocumentFragment();
var nodes = pool.querySelectorAll(select);
for (var i=0, l=nodes.length; i<l; i++) {
frag.appendChild(nodes[i]);
}
}
content.parentNode.replaceChild(frag, content);
}
}
});
Base.addFeature({
findAnnotatedNode: function(root, annote) {
if (!annote.parent) {
return root;
}
var parent = this.findAnnotatedNode(root, annote.parent);
var nodes = (parent === this) ? parent.childNodes :
(parent.lightChildren || parent.childNodes);
return nodes[annote.index];
},
register: function(prototype) {
if (prototype._template) {
prototype.parseAnnotations(prototype._template)
}
},
parseAnnotations: function(template) {
var map = [];
this._parseNodeAnnotations(template.content, map);
if (map.length) {
template.map = map;
}
return template.map;
},
_parseNodeAnnotations: function(node, map) {
return node.nodeType === Node.TEXT_NODE ?
this._parseTextNodeAnnotation(node, map) :
this._parseElementAnnotations(node, map);
},
_parseTextNodeAnnotation: function(node, map) {
var v = node.textContent, escape = v.slice(0, 2);
if (escape === '{{' || escape === '[[') {
var annotation = {
bindings: [{
kind: 'text',
mode: escape === '{{' ? 'auto' : '',
value: v.slice(2, -2)
}]
};
map.push(annotation);
return annotation;
}
},
_parseElementAnnotations: function(node, map) {
var annote = {
bindings: [],
events: []
};
this._parseChildNodesAnnotations(node, annote, map);
if (node.attributes) {
this._parseNodeAttributeAnnotations(node, annote, map);
}
if (annote.bindings.length || annote.events.length || annote.id) {
map.push(annote);
}
return annote;
},
_parseChildNodesAnnotations: function(root, annotation, map) {
if (root.firstChild) {
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var childAnnotation = this._parseNodeAnnotations(node, map);
if (childAnnotation) {
childAnnotation.parent = annotation;
childAnnotation.index = i;
}
}
}
},
_parseNodeAttributeAnnotations: function(node, annotation) {
for (var i=0, a; (a=node.attributes[i]); i++) {
var n = a.name, v = a.value;
if (n === 'id') {
annotation.id = v;
}
else if (n.slice(0, 3) === 'on-') {
annotation.events.push({
name: n.slice(3),
value: v
});
}
else {
var b = this._parseNodeAttributeAnnotation(node, n, v);
if (b) {
annotation.bindings.push(b);
}
}
}
},
_parseNodeAttributeAnnotation: function(node, n, v) {
var escape = v.slice(0, 2), lastChar = n[n.length-1];
var kind = 'attribute', mode = '';
if (lastChar === '*' || lastChar === ':') {
n = n.slice(0, -1);
kind = 'property';
mode = 'auto';
}
if (escape === '{{') {
mode = 'auto';
v = v.slice(2, -2);
}
if (escape === '[[') {
mode = 'manual';
v = v.slice(2, -2);
}
if (mode) {
if (n === 'style') {
kind = 'style';
}
return {
kind: kind,
mode: mode,
name: n,
value: v
};
}
}
});
Base.addFeature({
$$: function(slctr) {
return this.root.querySelector(slctr);
},
_marshalNodeReferences: function() {
this.$ = {};
var map = this._template && this._template.map;
if (map) {
map.forEach(function(annotation) {
var id = annotation.id;
if (id) {
this.$[id] = this.findAnnotatedNode(this.root, annotation);
}
}, this);
}
},
_marshalAnnotatedNodes: function() {
if (this._nodes) {
this._nodes = this._nodes.map(function(a) {
return this.findAnnotatedNode(this.root, a);
}, this);
}
}
});
Base.addFeature({
listeners: {},
init: function() {
},
listenListeners: function() {
for (var key in this.listeners) {
var node = this, name = key;
if (name.indexOf('.') >= 0) {
name = name.split('.');
node = this.$[name[0]];
name = name[1];
}
this.listen(node, name, this.listeners[key]);
}
},
listen: function(node, eventName, methodName) {
node.addEventListener(eventName, function(e) {
this[methodName](e, e.detail);
}.bind(this));
},
fire: function(type, detail, onNode, bubbles, cancelable) {
var node = onNode || this;
var detail = (detail === null || detail === undefined) ? {} : detail;
var event = new CustomEvent(type, {
bubbles: bubbles !== undefined ? bubbles : true,
cancelable: cancelable !== undefined ? cancelable : true,
detail: detail
});
node.dispatchEvent(event);
return event;
}
});
Base.addFeature({
keyPresses: {},
listenKeyPresses: function() {
for (var n in this.keyPresses) {
this.addEventListener('keypress', this.keyPressesFeatureHandler);
for (n in this.keyPresses) {
if (typeof n === 'string') {
this.keyPresses[Event.prototype.keys[n]] = this.keyPresses[n];
}
}
break;
}
},
keyPressesFeatureHandler: function(e) {
var method = this.keyPresses[e.keyCode];
if (method && this[method]) {
return this[method](e.keyCode, e);
}
}
});
Base.addFeature({
_setupAnnotatedListeners: function() {
var map = this._template.map;
if (map) {
map.forEach(function(annotation) {
var events = annotation.events;
if (events && events.length) {
var node = this.findAnnotatedNode(this.root, annotation);
events.forEach(function(e) {
this.listen(node, e.name, e.value);
}, this)
}
}, this);
}
}
});
Base.addFeature({
async: function(method) {
var handled = false;
var handle = function() {
if (!handled) {
handled = true;
method.call(this);
}
}.bind(this);
setTimeout(handle);
requestAnimationFrame(handle);
},
toggleAttribute: function(name, value) {
this[value ? 'setAttribute' : 'removeAttribute'](name, '');
},
attributeFollows: function(name, neo, old) {
if (old) {
old.removeAttribute(name);
}
if (neo) {
neo.setAttribute(name, '');
}
}
});

BIN
components/polymer/dist/polymer.html.gz vendored Normal file

Binary file not shown.

812
components/polymer/dist/polymer.js vendored Normal file
View File

@@ -0,0 +1,812 @@
// a tiny bit of sugar for `document.currentScript.ownerDocument`
// sadly `import` is reserved, so we need another name or
// you have to refer to this value `window.import`
Object.defineProperty(window, 'import', {
enumerable: true,
configurable: true,
get: function() {
return (document._currentScript || document.currentScript).ownerDocument;
}
});
// copy own properties from 'api' to 'prototype, with name hinting for 'super'
function extend(prototype, api) {
if (prototype && api) {
// use only own properties of 'api'
Object.getOwnPropertyNames(api).forEach(function(n) {
// acquire property descriptor
var pd = Object.getOwnPropertyDescriptor(api, n);
if (pd) {
// clone property via descriptor
Object.defineProperty(prototype, n, pd);
// cache name-of-method for 'super' engine
/*
if (typeof pd.value == 'function') {
// hint the 'super' engine
pd.value.nom = n;
}
*/
}
});
}
return prototype;
};
Event.prototype.keys = {
ESC_KEY: 27,
ENTER_KEY: 13
};
Base = {
// (semi-)pluggable features for Base
_features: [],
addFeature: function(feature) {
this._features.push(feature);
extend(Base, feature);
delete Base.init;
delete Base.register;
},
registerCallback: function() {
// `this` context is a prototype, not an instance
var prototype = this;
this.registerFeatures(prototype);
this.registered(prototype);
},
registered: function(prototype) {
// for overriding
},
registerFeatures: function(prototype) {
var f$ = this._features;
for (var i=0, n=f$.length; i<n && (f=f$[i]); i++) {
f.register && f.register(prototype);
}
},
createdCallback: function() {
this.root = this;
this.beforeCreated();
this.initFeatures();
this.created();
this.afterCreated();
},
beforeCreated: function() {
// for overriding
},
initFeatures: function() {
var f$ = this._features;
for (var i=0, n=f$.length; i<n && (f=f$[i]); i++) {
f.init && f.init.call(this);
}
},
created: function() {
// for overriding
},
afterCreated: function() {
// for overriding
},
attachedCallback: function() {
// reserved for canonical behavior
this.attached();
},
attached: function() {
// for overriding
},
detachedCallback: function() {
// reserved for canonical behavior
this.detached();
},
detached: function() {
// for overriding
},
attributeChangedCallback: function() {
// reserved for canonical behavior
this.attributeChanged.apply(this, arguments);
},
attributeChanged: function() {
// for overriding
}
};
Base.__proto__ = HTMLElement.prototype;
Polymer = function(prototype) {
prototype.__proto__ = Base;
prototype.registerCallback();
document.registerElement(prototype.name, {prototype: prototype});
};
Polymer.log = {
};
Base.addFeature({
log: function() {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = '[%s]: ' + args[0];
args.splice(1, 0, this.localName);
console.log.apply(console, args);
}
});
/*
* Define public property API.
*
* published: {
* <property>: <Type || Object>,
* ...
*
* // `foo` property can be assigned via attribute, will be deserialized to
* // the specified data-type. All `published` properties have this behavior.
* foo: String,
*
* // `bar` property has additional behavior specifiers.
* // type: type for (de-)serialization
* // notify: true to send a signal when a value is set to this property
* // reflect: true to serialize the property to an attribute
* // readOnly: if true, the property has no setter
* bar: {
* type: Boolean,
* notify: true
* }
* }
*
*/
Base.addFeature({
published: {
},
nob: Object.create(null),
register: function(prototype) {
// TODO(sjmiles): improve layering
if (prototype.addPropertyEffect) {
for (var n in prototype.published) {
if (prototype.isNotifyProperty(n)) {
prototype.addPropertyEffect(n, 'notify');
}
}
}
},
getPublishInfo: function(property) {
var p = this.published[property];
if (typeof(p) === 'function') {
p = this.published[property] = {
type: p
};
}
return p || Base.nob;
},
getPublishedPropertyType: function(property) {
return this.getPublishInfo(property).type;
},
isReadOnlyProperty: function(property) {
return this.getPublishInfo(property).readOnly;
},
isNotifyProperty: function(property) {
return this.getPublishInfo(property).notify;
},
isReflectedProperty: function(property) {
return this.getPublishInfo(property).reflect;
}
});
/*
* Support for `hostAttributes` property.
*
* `hostAttributes` is a space separated string of attributes to
* install on every instance.
*
* There is room for addition `attributes` features, namely:
*
* - potentially automatic handling of attributeChanged
* - capturing initial configuration values from attributes
*
*/
Base.addFeature({
init: function() {
if (this.hostAttributes) {
this.cloneAttributes(this, this.hostAttributes);
}
},
cloneAttributes: function(node, attr$) {
attr$.split(' ').forEach(function(a) {
node.setAttribute(a, '');
});
}
});
/*
* Support for `published` property.
*
* `published` object maps the names of attributes that the user
* wants mapped as inputs to properties to the data-type of that property.
*
* This feature overwrites `attributeChanged` to support automatic
* propagation of attribute values at run-time.
*
* Static values in attributes at creation time can be captured by
* `takeAttributes`.
*
* Example:
*
* published: {
* // values set to index attribute are converted to Number and propagated
* // to index property
* index: Number,
* // values set to label attribute are propagated to index property
* label: String
* }
*
* Supported types:
*
* - Number
* - Boolean
* - String
* - Object (JSON)
* - Array (JSON)
* - Date
*
*/
Base.addFeature({
/* attribute publishing feature, requires `published` feature */
takeAttributes: function() {
for (var n in this.published) {
this.attributeChanged(n);
}
},
attributeChanged: function(name) {
var type = this.getPublishedPropertyType(name);
if (type) {
this.deserialize(name, type);
}
},
deserialize: function(name, type) {
var value = this.getAttribute(name);
switch(type) {
case Number:
value = Number(value) || this[name];
break;
case Boolean:
value = this.hasAttribute(name);
break;
case Object:
case Array:
try {
value = JSON.parse(value);
} catch(x) {
return;
}
break;
case Date:
value = Date.parse(value);
break;
case String:
default:
break;
}
this[name] = value;
}
});
Base.addFeature({
register: function(prototype) {
var script = (document._currentScript || document.currentScript);
var prev = script.previousElementSibling;
if (prev && prev.localName === 'template') {
prototype._template = prev;
// TODO(sjmiles): probably should be it's own feature
//this.decorateTemplateNodes(prototype._template.content,
//prototype.name);
}
},
/*decorateTemplateNodes: function(root, name) {
for (var node = root.firstElementChild; node;
node = node.nextElementSibling) {
node.setAttribute(name, '');
this.decorateTemplateNodes(node, name);
}
},*/
stampTemplate: function(template) {
this._stampTemplate(template || this._template, this.root);
// TODO(sjmiles): hello prollyfill
if (window.CustomElements && CustomElements.upgradeSubtree) {
CustomElements.upgradeSubtree(this.root);
}
},
_stampTemplate: function(template, target) {
// TODO(sorvell): light dom children will invalidate annotations.
target.insertBefore(this.instanceTemplate(template),
target.firstElementChild);
},
instanceTemplate: function(template) {
return document.importNode(template.content, true);
}
});
Base.addFeature({
// TODO(sjmiles): ad-hoc signal for `ShadowDOM-lite-enhanced` nodes
isHost: true,
register: function(prototype) {
var t = prototype._template;
// TODO(sorvell): is qsa is wrong here due to distribution?
// TODO(sjmiles): No element should ever actually stamp a <content> node
// into it's composed tree, so I believe this is actually correct.
// However, I wonder if it's more efficient to capture during annotation
// parsing, since the parse step does a tree walk in any case, and the
// tree is smaller before element expansion.
prototype._useContent = Boolean(t && t.content.querySelector('content'));
},
poolContent: function() {
// pool the light dom
var pool = document.createDocumentFragment();
while (this.firstChild) {
pool.appendChild(this.firstChild);
}
this.contentPool = pool;
// capture lightChildren to help reify dom scoping
this.lightChildren =
Array.prototype.slice.call(this.contentPool.childNodes, 0);
},
distributeContent: function() {
var content, pool = this.contentPool;
// replace <content> with nodes teleported from pool
while (content = this.querySelector('content')) {
var select = content.getAttribute('select');
var frag = pool;
if (select) {
frag = document.createDocumentFragment();
// TODO(sjmiles): diverges from ShadowDOM spec behavior: ShadowDOM
// only selects top level nodes from pool. Iterate children and match
// manually instead.
var nodes = pool.querySelectorAll(select);
for (var i=0, l=nodes.length; i<l; i++) {
frag.appendChild(nodes[i]);
}
}
// content self-destructs
content.parentNode.replaceChild(frag, content);
}
}
});
// TODO(sjmiles): this code was ported from an earlier mutation and needs
// a cleanup to cleave closer to neoprene MO
/*
Scans a template to produce an annotation map that stores expression metadata
and information that can be used to associate that metadata with the
corresponding nodes in a template instance.
Supported annotations are:
* id attributes
* binding annotations in text nodes
* double-mustache expressions: {{expression}}
* double-bracket expressions: [[expression]]
* binding annotations in attributes
* attribute-bind expressions: name="{{expression}} || [[expression]]"
* property-bind expressions: name*="{{expression}} || [[expression]]"
* property-bind expressions: name:="expression"
* event annotations
* event delegation directives: on-<eventName>="expression"
Generated data-structure:
[
{
id: '<id>',
events: [
{
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
bindings: [
{
kind: ['text'|'attribute'|'property'],
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
// TODO(sjmiles): confusingly, this is annotation-parent, not node-parent
parent: <reference to parent annotation>,
index: <integer index in parent's childNodes collection>
},
...
]
TODO(sjmiles): this module should produce either syntactic metadata
(e.g. double-mustache, double-bracket, star-attr), or semantic metadata
(e.g. manual-bind, auto-bind, property-bind). Right now it's half and half.
*/
Base.addFeature({
// instance-time
findAnnotatedNode: function(root, annote) {
if (!annote.parent) {
return root;
}
var parent = this.findAnnotatedNode(root, annote.parent);
// enforce locality.
var nodes = (parent === this) ? parent.childNodes :
(parent.lightChildren || parent.childNodes);
return nodes[annote.index];
},
// registration-time
register: function(prototype) {
if (prototype._template) {
prototype.parseAnnotations(prototype._template)
}
},
parseAnnotations: function(template) {
// TODO(sjmiles): it's not a map, per se
var map = [];
this._parseNodeAnnotations(template.content, map);
if (map.length) {
template.map = map;
}
return template.map;
},
_parseNodeAnnotations: function(node, map) {
return node.nodeType === Node.TEXT_NODE ?
this._parseTextNodeAnnotation(node, map) :
this._parseElementAnnotations(node, map);
},
_parseTextNodeAnnotation: function(node, map) {
var v = node.textContent, escape = v.slice(0, 2);
if (escape === '{{' || escape === '[[') {
var annotation = {
bindings: [{
kind: 'text',
mode: escape === '{{' ? 'auto' : '',
value: v.slice(2, -2)
}]
};
map.push(annotation);
return annotation;
}
},
_parseElementAnnotations: function(node, map) {
var annote = {
bindings: [],
events: []
};
this._parseChildNodesAnnotations(node, annote, map);
if (node.attributes) {
this._parseNodeAttributeAnnotations(node, annote, map);
}
if (annote.bindings.length || annote.events.length || annote.id) {
map.push(annote);
}
return annote;
},
_parseChildNodesAnnotations: function(root, annotation, map) {
if (root.firstChild) {
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var childAnnotation = this._parseNodeAnnotations(node, map);
if (childAnnotation) {
childAnnotation.parent = annotation;
childAnnotation.index = i;
}
}
}
},
_parseNodeAttributeAnnotations: function(node, annotation) {
for (var i=0, a; (a=node.attributes[i]); i++) {
var n = a.name, v = a.value;
// id
if (n === 'id') {
annotation.id = v;
}
// on-* (event)
else if (n.slice(0, 3) === 'on-') {
annotation.events.push({
name: n.slice(3),
value: v
});
}
// other attribute
else {
var b = this._parseNodeAttributeAnnotation(node, n, v);
if (b) {
annotation.bindings.push(b);
}
}
}
},
_parseNodeAttributeAnnotation: function(node, n, v) {
var escape = v.slice(0, 2), lastChar = n[n.length-1];
var kind = 'attribute', mode = '';
if (lastChar === '*' || lastChar === ':') {
n = n.slice(0, -1);
kind = 'property';
mode = 'auto';
}
if (escape === '{{') {
mode = 'auto';
v = v.slice(2, -2);
}
if (escape === '[[') {
mode = 'manual';
v = v.slice(2, -2);
}
if (mode) {
if (n === 'style') {
kind = 'style';
}
return {
kind: kind,
mode: mode,
name: n,
value: v
};
}
}
});
// depends on `annotations` feature
Base.addFeature({
$$: function(slctr) {
return this.root.querySelector(slctr);
},
// construct $ map (id based)
_marshalNodeReferences: function() {
this.$ = {};
var map = this._template && this._template.map;
if (map) {
map.forEach(function(annotation) {
var id = annotation.id;
if (id) {
this.$[id] = this.findAnnotatedNode(this.root, annotation);
}
}, this);
}
},
// concretize `_nodes` map (annotation based)
_marshalAnnotatedNodes: function() {
if (this._nodes) {
this._nodes = this._nodes.map(function(a) {
return this.findAnnotatedNode(this.root, a);
}, this);
}
}
});
Base.addFeature({
listeners: {},
init: function() {
},
// TODO(sjmiles): support for '.' notation requires 'nodes' feature
listenListeners: function() {
for (var key in this.listeners) {
var node = this, name = key;
if (name.indexOf('.') >= 0) {
name = name.split('.');
node = this.$[name[0]];
name = name[1];
}
this.listen(node, name, this.listeners[key]);
}
},
listen: function(node, eventName, methodName) {
node.addEventListener(eventName, function(e) {
this[methodName](e, e.detail);
}.bind(this));
},
// TODO(sjmiles): use a dictionary for options after `detail`
fire: function(type, detail, onNode, bubbles, cancelable) {
var node = onNode || this;
var detail = (detail === null || detail === undefined) ? {} : detail;
var event = new CustomEvent(type, {
bubbles: bubbles !== undefined ? bubbles : true,
cancelable: cancelable !== undefined ? cancelable : true,
detail: detail
});
node.dispatchEvent(event);
return event;
}
});
Base.addFeature({
keyPresses: {},
listenKeyPresses: function() {
// for..in here to gate empty keyPresses object (iterates once or never)
for (var n in this.keyPresses) {
// only get here if there is something in keyPresses
this.addEventListener('keypress', this.keyPressesFeatureHandler);
// map string keys to numeric codes
for (n in this.keyPresses) {
if (typeof n === 'string') {
this.keyPresses[Event.prototype.keys[n]] = this.keyPresses[n];
}
}
break;
}
},
keyPressesFeatureHandler: function(e) {
var method = this.keyPresses[e.keyCode];
if (method && this[method]) {
return this[method](e.keyCode, e);
}
}
});
/*
* Parses the annotations map created by `annotations` features to support
* declarative events.
*
* Depends on `annotations` and `events` features.
*
*/
Base.addFeature({
// instance-time
_setupAnnotatedListeners: function() {
var map = this._template.map;
if (map) {
map.forEach(function(annotation) {
var events = annotation.events;
if (events && events.length) {
var node = this.findAnnotatedNode(this.root, annotation);
events.forEach(function(e) {
//console.log('[%s] listening for [%s] on [%s]', e.value, e.name, node.localName);
this.listen(node, e.name, e.value);
}, this)
}
}, this);
}
}
});
Base.addFeature({
async: function(method) {
var handled = false;
var handle = function() {
if (!handled) {
handled = true;
method.call(this);
}
}.bind(this);
// minimize latency by racing requests
setTimeout(handle);
requestAnimationFrame(handle);
},
toggleAttribute: function(name, value) {
this[value ? 'setAttribute' : 'removeAttribute'](name, '');
},
attributeFollows: function(name, neo, old) {
if (old) {
old.removeAttribute(name);
}
if (neo) {
neo.setAttribute(name, '');
}
}
});
// TODO(sjmiles): hack
Base.originalInitFeatures = Base.initFeatures;
Base.addFeature({
initFeatures: function() {
this.originalInitFeatures(this);
this.features();
},
features: function() {
this.defaultFeatures();
},
defaultFeatures: function() {
if (this._useContent) {
this.poolContent();
}
if (this._template) {
this.stampTemplate();
this._marshalNodeReferences();
this._marshalAnnotatedNodes();
this._setupAnnotatedListeners();
if (this._setupBindListeners) {
this._setupBindListeners();
}
}
this.listenListeners();
this.listenKeyPresses();
if (this._useContent) {
this.distributeContent();
}
this.takeAttributes();
}
});
Polymer.noFeatures = function() {
};
Polymer.defaultFeatures = Base.defaultFeatures;

278
components/polymer/dist/polymer.min.html vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,47 @@
<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<title>x-doc-viewer</title>
<script src="../perf-lib/perf.js"></script>
<link rel="import" href="../elements/x-doc-viewer/x-doc-viewer.html">
<link rel="import" href="../assets/icons.html">
<style>
html, body {
font-family: Arial, sans-serif;
margin: 0;
}
</style>
</head>
<body fullbleed vertical layout>
<script>console.perf();</script>
<x-doc-viewer flex sources='[
"src/features/bind.html",
"src/features/published.html",
"src/features/annotations.html",
"../elements/x-doc-viewer/x-doc-viewer.html"
]'></x-doc-viewer>
<script>console.perfEnd();</script>
</body>
</html>

View File

@@ -0,0 +1,323 @@
# Polymer (Neoprene)
## Raw Custom Elements
Custom Elements are a powerful emerging web standard that allows developers to create their own elements by attaching a class to a tag-name.
### document.registerElement
The native API is very simple, it looks something like this:
```js
document.registerElement(<name String>, {prototype: Object[, extends: String]});
```
### Typical Boilerplate
There is a little bit of work one has to do to set up the class with the right prototypes and so on to construct a Custom Element. Here is an typical example (using ES5 syntax):
```js
var ctor = function() {
return document.createElement('x-custom');
};
ctor.prototype = Object.create(HTMLElement.prototype);
ctor.prototype.constructor = ctor;
ctor.prototype.createdCallback = function() {
this.innerHTML = 'Hello World, I am a <b>Custom Element!</b>';
}
document.registerElement('x-custom', ctor);
```
### Reluctant Polymer() Abstraction
By principle, Polymer team tries to avoid abstracting DOM APIs, especially new ones. But in this case we finally decided the ergonomic benefit was worth it. By wrapping `registerElement` in our own function, we can reduce the above boilerplate to:
```js
var ctor = Polymer({
name: 'x-custom',
created: function() {
this.innerHTML = 'Hello World, I am a <b>Custom Element!</b>';
}
});
```
### Polymer() Does a Bit More
You might notice the `Polymer()` invocation defines `created` instead of `createdCallback`. This is a feature of `Polymer.Base`, a tiny prototype that `Polymer()` adds to your prototype chain as it's handling the boilerplate above. `Polymer.Base` hooks the standard Custom Element lifecycle callbacks to provide helper implementations. The hooks in turn call shorter-named lifecycle methods on your prototype.
- `created` instead of `createdCallback`
- `attached` instead of `attachedCallback`
- `detached` instead of `detachedCallback`
- `attributeChanged` instead of `attributeChangedCallback`
You can always fallback to using the low-level methods if you wish (iow, you could simply implement `createdCallback` in your prototype).
`Polymer.Base` also implements `registerCallback` on your prototype. `Polymer()` calls `registerCallback` which allows `Polymer.Base` to supply a layering system for Polymer abstractions so that no element needs to pay for features it doesn't use.
## Features
By default, the default Polymer distribution include several features. Although `Polymer.Base` itself is tiny, if you examine `Polymer.Base` you will probably see several methods that have been plugged-in to that prototype by feature definitions. The next few sections will explain these features and why we include them in the default set. Keep in mind that it's entirely possible to construct custom feature sets, or even use a trivial, featureless form of `Polymer()`.
### Feature: _published_
The first feature implements support for the `published` property. By placing a object-valued `published` property on your prototype, let's you define various aspects of your custom-elements public API.
By itself, the `published` feature **doesn't do anything**. It only provides API for asking questions about these special properties (see [link to docs] for details).
```js
Polymer({
name: 'x-custom',
published: {
user: String,
isHappy: Boolean,
count: {
type: Number,
readOnly: true,
notify: true
}
},
created: function() {
this.innerHTML = 'Hello World, I am a <b>Custom Element!</b>';
}
});
```
Remember that the fields assigned to `count`, such as `readOnly` and `notify` don't do anything by themselves, it requires other features to give them life.
### Feature: _attributes_
Many custom elements want to support configuration using HTML attributes. Custom Elements provides the `attributeChanged` callback gives us the raw API for this ability, but then we have to deal with initialization and type conversion (attributes are always strings). Here is an example of a custom element that supports a `user` attribute using the raw API.
```js
Polymer({
name: 'x-custom',
created: function() {
// handle any initial value
this.attributeChanged('user');
// render
this.innerHTML = 'Hello World, my user is ' + (this.user || 'nobody') + '.';
},
attributeChanged: function(name) {
switch(name) {
case 'user':
// pretty easy since user is a String, for other types
// we have to do more work
if (this.hasAttribute('user')) {
this.user = this.getAttribute('user');
}
break;
}
}
});
```
Although it's relatively simple, having to write this code becomes annoying when working with multiple attributes or non-String types. It's also not very DRY.
Instead, Polymer's `attributes` feature handles this work for you (using the `published` feature data). If an attribute is set that matches a property listed in the `published` object, the value is captured into the matching property. Strings are automatically converted to the published type.
The type system includes support for Object values expressed as JSON, or Date objects expressed as any Date-parsable string representation. Boolean properties are mapped to Boolean attributes, in other words, if the attribute exists at all, it's value is true, regardless of it's string-value (and the value is only false if the attribute does not exist).
Here is the equivalent of the above code, taking advantage of the `attributes` feature.
```html
<script>
Polymer({
name: 'x-custom',
published: {
user: String
},
created: function() {
// render
this.innerHTML = 'Hello World, my user is ' + (this.user || 'nobody') + '.';
}
});
</script>
<x-custom user="Scott"></x-custom>
```
### [ToDoc] attributes:hostAttributes
### Feature: _template_
HTML templates are an emerging web standard that we like to consider part of the Web Components family. Templates are a great way to provide archetypal DOM content for your custom element, and this is where the `template` feature comes in.
As usual, we started by writing basic template support by hand. It generally looks something like this:
```html
<template>
Hello World from x-custom!
</template>
<script>
Polymer({
name: 'x-custom',
created: function() {
var template = <find the template somehow>;
var instance = document.importNode(template.content, true);
this.appendChild(instance);
}
});
</script>
```
Again, it's simple, but it's a common pattern, so the `template` feature does it automatically. By default it looks for a template as the first element before the script, so our code can look like this:
```html
<template>
Hello World from x-custom!
</template>
<script>
Polymer({
name: 'x-custom'
});
</script>
```
### Feature: _annotations_
Most elements need to customize the DOM instanced from a template. For this reason, it's handy to encode markers into your template to indicate special nodes, attributes, or text. Polymer calls these markers _annotations_. The `annotations` feature scans the template (once per element, at registration time) and builds a data-structure into the prototype that identifies markers it finds in the DOM (see [link to docs] for details). Normally you do not need to work with this data directly, Polymer does it for you.
### Feature: _annotations-nodes_
Traditionally, modifying DOM is done by querying for elements to manipulate. Here is an example:
```html
<template>
Hello World from <span id="name"></span>!
</template>
<script>
Polymer({
name: 'x-custom',
created: function() {
this.querySelector("#name").textContent = this.name;
}
});
</script>
```
This example is very simple. But in real projects, repeating queries is inefficient, so query results are often stored (memoized). Also, as DOM composition becomes more tricky, crafting correct queries can be difficult. For these reasons, automatically capturing nodes makes a good feature.
The `annotations-nodes` feature builds a map of instance nodes by `id` in `this.$` (using the `annotations` feature data). Here is how the `annotations-nodes` feature simplifies the above example.
```html
<template>
Hello World from <span id="name"></span>!
</template>
<script>
Polymer({
name: 'x-custom',
created: function() {
this.$.name.textContent = this.name;
}
});
</script>
```
### Feature: _annotations-events_
Most elements also need to listen for events. The standard DOM method `addEventListener` provides the low-level support:
```html
<template>
<button id="button">Kick Me</button>
</template>
<script>
Polymer({
name: 'x-custom',
created: function() {
this.$.button.addEventListener('click', function() {
alert('Ow!');
});
}
});
</script>
```
Again, this is pretty simple, but it's so common that it's worth making even simpler. The `annotations-events` feature supports declaring event listeners directly in our template.
Declaring listeners in the template is convenient, and also helps us decouple view from behavior.
```html
<template>
<button on-click="kickAction">Kick Me</button>
</template>
<script>
Polymer({
name: 'x-custom',
kickAction: function() {
alert('Ow!');
}
});
</script>
```
Notice that the `kickAction` method doesn't know anything about `button`. If we decided that kicking should be performed by a key-press, or a menu-item, the element code doesn't need to know. We can change the UI however we want. Also notice that by attaching the event declaratively, we have removed the need to give the button an id.
### [ToDoc] events feature
### [ToDoc] keys feature
### [ToDoc] content feature

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

View File

@@ -0,0 +1,41 @@
<!doctype html>
<html>
<head>
<title>Explainer Samples</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="import" href="../polymer.html">
<style>
body {
font-family: sans-serif;
font-size: 15px;
}
</style>
</head>
<body>
<script>
Polymer({
name: 'x-custom',
published: {
user: String
},
created: function() {
this.innerHTML = 'Hello World, my user is ' + (this.user || 'nobody') + '.';
}
});
</script>
<x-custom user="Scott"></x-custom>
</body>
</html>

View File

@@ -0,0 +1,61 @@
<link rel="import" href="src/polymer.html">
<link rel="import" href="src/features/log.html">
<link rel="import" href="src/features/published.html">
<link rel="import" href="src/features/attributes.html">
<link rel="import" href="src/features/template.html">
<link rel="import" href="src/features/content.html">
<link rel="import" href="src/features/annotations.html">
<link rel="import" href="src/features/annotations-nodes.html">
<link rel="import" href="src/features/events.html">
<link rel="import" href="src/features/keys.html">
<link rel="import" href="src/features/annotations-events.html">
<link rel="import" href="src/features/utils.html">
<link rel="import" href="src/features/layout.html">
<script>
// TODO(sjmiles): hack
Base.originalInitFeatures = Base.initFeatures;
Base.addFeature({
initFeatures: function() {
// TODO(sjmiles): hack to make sure this feature goes last
this.originalInitFeatures(this);
this.features();
},
features: function() {
this.defaultFeatures();
},
defaultFeatures: function() {
if (this._useContent) {
this.poolContent();
}
if (this._template) {
this.stampTemplate();
this._marshalNodeReferences();
this._marshalAnnotatedNodes();
this._setupAnnotatedListeners();
if (this._setupBindListeners) {
this._setupBindListeners();
}
}
this.listenListeners();
this.listenKeyPresses();
if (this._useContent) {
this.distributeContent();
}
this.takeAttributes();
}
});
Polymer.noFeatures = function() {
};
Polymer.defaultFeatures = Base.defaultFeatures;
</script>

View File

@@ -0,0 +1,89 @@
<script>
Base = {
// (semi-)pluggable features for Base
_features: [],
addFeature: function(feature) {
this._features.push(feature);
extend(Base, feature);
delete Base.init;
delete Base.register;
},
registerCallback: function() {
// `this` context is a prototype, not an instance
var prototype = this;
this.registerFeatures(prototype);
this.registered(prototype);
},
registered: function(prototype) {
// for overriding
},
registerFeatures: function(prototype) {
var f$ = this._features;
for (var i=0, n=f$.length; i<n && (f=f$[i]); i++) {
f.register && f.register(prototype);
}
},
createdCallback: function() {
this.root = this;
this.beforeCreated();
this.initFeatures();
this.created();
this.afterCreated();
},
beforeCreated: function() {
// for overriding
},
initFeatures: function() {
var f$ = this._features;
for (var i=0, n=f$.length; i<n && (f=f$[i]); i++) {
f.init && f.init.call(this);
}
},
created: function() {
// for overriding
},
afterCreated: function() {
// for overriding
},
attachedCallback: function() {
// reserved for canonical behavior
this.attached();
},
attached: function() {
// for overriding
},
detachedCallback: function() {
// reserved for canonical behavior
this.detached();
},
detached: function() {
// for overriding
},
attributeChangedCallback: function() {
// reserved for canonical behavior
this.attributeChanged.apply(this, arguments);
},
attributeChanged: function() {
// for overriding
}
};
</script>

View File

@@ -0,0 +1,66 @@
<link rel="import" href="annotations.html">
<script>
/*
* Parses the annotations map created by `annotations` features to perform
* declarative desugaring.
*
* Depends on `annotations` feature and `bind` feature.
*
* Two tasks are supported:
*
* - nodes with 'id' are described in a virtual annotation map at
* registration time. This map is then concretized per instance.
*
* - Simple mustache expressions consisting of a single property name
* in a `textContent` context are bound using `bind` features
* `bindMethod`. In this mode, the bound method is constructed at
* registration time, so marshaling is done done via the concretized
* `_nodes` at every access.
*
* TODO(sjmiles): ph3ar general confusion between registration and
* instance time tasks. Is there a cleaner way to disambiguate?
*/
Base.addFeature({
// registration-time
register: function(prototype) {
if (prototype._template && prototype._template.map) {
this._preprocessBindAnnotations(prototype, prototype._template.map);
}
},
// construct binding meta-data at *registration* time
_preprocessBindAnnotations: function(prototype, map) {
// create a virtual annotation map, must be concretized at instance time
prototype._nodes = [];
// process annotations that have been parsed from template
map.forEach(function(annotation) {
// where to find the node in the concretized map
var index = prototype._nodes.push(annotation) - 1;
// TODO(sjmiles): we need to support multi-bind, right now you only get
// one (not including kind === `id`)
annotation.bindings.forEach(function(binding) {
prototype._bindAnnotationBinding(binding, index);
});
});
},
// _nodes[index][<binding.name=>]{{binding.value}}
_bindAnnotationBinding: function(binding, index) {
// capture the node index
binding.index = index;
// discover top-level property (model) from path
var path = binding.value;
var i = path.indexOf('.');
// [name=]{{model[.subpath]}}
var model = (i >= 0) ? path.slice(0, i) : path;
// add 'annotation' binding effect for property 'model'
this.addPropertyEffect(model, 'annotation', binding);
}
});
</script>

View File

@@ -0,0 +1,34 @@
<link rel="import" href="annotations.html">
<script>
/*
* Parses the annotations map created by `annotations` features to support
* declarative events.
*
* Depends on `annotations` and `events` features.
*
*/
Base.addFeature({
// instance-time
_setupAnnotatedListeners: function() {
var map = this._template.map;
if (map) {
map.forEach(function(annotation) {
var events = annotation.events;
if (events && events.length) {
var node = this.findAnnotatedNode(this.root, annotation);
events.forEach(function(e) {
//console.log('[%s] listening for [%s] on [%s]', e.value, e.name, node.localName);
this.listen(node, e.name, e.value);
}, this)
}
}, this);
}
}
});
</script>

View File

@@ -0,0 +1,36 @@
<script>
// depends on `annotations` feature
Base.addFeature({
$$: function(slctr) {
return this.root.querySelector(slctr);
},
// construct $ map (id based)
_marshalNodeReferences: function() {
this.$ = {};
var map = this._template && this._template.map;
if (map) {
map.forEach(function(annotation) {
var id = annotation.id;
if (id) {
this.$[id] = this.findAnnotatedNode(this.root, annotation);
}
}, this);
}
},
// concretize `_nodes` map (annotation based)
_marshalAnnotatedNodes: function() {
if (this._nodes) {
this._nodes = this._nodes.map(function(a) {
return this.findAnnotatedNode(this.root, a);
}, this);
}
}
});
</script>

View File

@@ -0,0 +1,226 @@
<script>
/**
* Scans a template (once per prototype) to produce an annotation map that
* stores expression metadata and information to associate the metadata with
* nodes in an instance.
*
* Supported expressions include:
*
* Double-mustache annotations in text content. The annotation must be the only
* content in the tag, compound expressions are not supported.
*
* <[tag]>{{path.to.host.property}}<[tag]>
*
* Double-mustache annotations in an attribute.
*
* <[tag] someAttribute="{{path.to.host.property}}"><[tag]>
*
* Only immediate host properties can automatically trigger side-effects.
* Setting `host.path` in the example above triggers the binding, setting
* `host.path.to.host.property` does not.
*
* `on-` style event declarations.
*
* <[tag] on-<event-name>="{{hostMethodName}}"><[tag]>
*
* Note that the `annotations` feature does not actually implement the behaviors
* associated with these expressions, it only captures the data. Other
* `annotations-*` features contain the actual implementations.
*
* @class feature: annotations
*/
/*
Scans a template to produce an annotation map that stores expression metadata
and information that associates the metadata to nodes in a template instance.
Supported annotations are:
* id attributes
* binding annotations in text nodes
* double-mustache expressions: {{expression}}
* double-bracket expressions: [[expression]]
* binding annotations in attributes
* attribute-bind expressions: name="{{expression}} || [[expression]]"
* property-bind expressions: name*="{{expression}} || [[expression]]"
* property-bind expressions: name:="expression"
* event annotations
* event delegation directives: on-<eventName>="expression"
Generated data-structure:
[
{
id: '<id>',
events: [
{
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
bindings: [
{
kind: ['text'|'attribute'|'property'],
mode: ['auto'|''],
name: '<name>'
value: '<expression>'
}, ...
],
// TODO(sjmiles): confusingly, this is annotation-parent, not node-parent
parent: <reference to parent annotation>,
index: <integer index in parent's childNodes collection>
},
...
]
TODO(sjmiles): this module should produce either syntactic metadata
(e.g. double-mustache, double-bracket, star-attr), or semantic metadata
(e.g. manual-bind, auto-bind, property-bind). Right now it's half and half.
*/
Base.addFeature({
// instance-time
findAnnotatedNode: function(root, annote) {
if (!annote.parent) {
return root;
}
var parent = this.findAnnotatedNode(root, annote.parent);
// enforce locality.
var nodes = (parent === this) ? parent.childNodes :
(parent.lightChildren || parent.childNodes);
return nodes[annote.index];
},
// registration-time
register: function(prototype) {
if (prototype._template) {
prototype.parseAnnotations(prototype._template)
}
},
parseAnnotations: function(template) {
// TODO(sjmiles): it's not a map, per se
var map = [];
this._parseNodeAnnotations(template.content, map);
if (map.length) {
template.map = map;
}
return template.map;
},
_parseNodeAnnotations: function(node, map) {
return node.nodeType === Node.TEXT_NODE ?
this._parseTextNodeAnnotation(node, map) :
this._parseElementAnnotations(node, map);
},
_parseTextNodeAnnotation: function(node, map) {
var v = node.textContent, escape = v.slice(0, 2);
if (escape === '{{' || escape === '[[') {
var annotation = {
bindings: [{
kind: 'text',
mode: escape === '{{' ? 'auto' : '',
value: v.slice(2, -2)
}]
};
node.textContent = '';
map.push(annotation);
return annotation;
}
},
_parseElementAnnotations: function(node, map) {
var annote = {
bindings: [],
events: []
};
this._parseChildNodesAnnotations(node, annote, map);
if (node.attributes) {
this._parseNodeAttributeAnnotations(node, annote, map);
}
if (annote.bindings.length || annote.events.length || annote.id) {
map.push(annote);
}
return annote;
},
_parseChildNodesAnnotations: function(root, annotation, map) {
if (root.firstChild) {
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var childAnnotation = this._parseNodeAnnotations(node, map);
if (childAnnotation) {
childAnnotation.parent = annotation;
childAnnotation.index = i;
}
}
}
},
_parseNodeAttributeAnnotations: function(node, annotation) {
for (var i=0, a; (a=node.attributes[i]); i++) {
var n = a.name, v = a.value;
// id
if (n === 'id') {
annotation.id = v;
}
// on-* (event)
else if (n.slice(0, 3) === 'on-') {
i--;
node.removeAttribute(n);
annotation.events.push({
name: n.slice(3),
value: v
});
}
// other attribute
else {
var b = this._parseNodeAttributeAnnotation(node, n, v);
if (b) {
i--;
annotation.bindings.push(b);
}
}
}
},
_parseNodeAttributeAnnotation: function(node, n, v) {
var escape = v.slice(0, 2), lastChar = n[n.length-1];
var kind = 'attribute', mode = '';
if (lastChar === '*' || lastChar === ':') {
n = n.slice(0, -1);
kind = 'property';
mode = 'auto';
}
if (escape === '{{') {
mode = 'auto';
v = v.slice(2, -2);
}
if (escape === '[[') {
mode = 'manual';
v = v.slice(2, -2);
}
if (mode) {
if (n === 'style') {
kind = 'style';
}
node.removeAttribute(n);
return {
kind: kind,
mode: mode,
name: n,
value: v
};
}
}
});
</script>

View File

@@ -0,0 +1,115 @@
<script>
/*
* Support for `hostAttributes` property.
*
* `hostAttributes` is a space separated string of attributes to
* install on every instance.
*
* There is room for addition `attributes` features, namely:
*
* - potentially automatic handling of attributeChanged
* - capturing initial configuration values from attributes
*
*/
Base.addFeature({
init: function() {
if (this.hostAttributes) {
this.cloneAttributes(this, this.hostAttributes);
}
},
cloneAttributes: function(node, attr$) {
attr$.split(' ').forEach(function(a) {
node.setAttribute(a, '');
});
}
});
/*
* Support for `published` property.
*
* `published` object maps the names of attributes that the user
* wants mapped as inputs to properties to the data-type of that property.
*
* This feature overwrites `attributeChanged` to support automatic
* propagation of attribute values at run-time.
*
* Static values in attributes at creation time can be captured by
* `takeAttributes`.
*
* Example:
*
* published: {
* // values set to index attribute are converted to Number and propagated
* // to index property
* index: Number,
* // values set to label attribute are propagated to index property
* label: String
* }
*
* Supported types:
*
* - Number
* - Boolean
* - String
* - Object (JSON)
* - Array (JSON)
* - Date
*
*/
Base.addFeature({
/* attribute publishing feature, requires `published` feature */
takeAttributes: function() {
for (var name in this.published) {
var type = this.getPublishedPropertyType(name);
if (type === Boolean || this.hasAttribute(name)) {
this.attributeChanged(name, type);
}
}
},
attributeChanged: function(name, type) {
var type = type || this.getPublishedPropertyType(name);
if (type) {
this[name] = this.deserialize(name, this.getAttribute(name), type);
}
},
deserialize: function(name, value, type) {
switch(type) {
case Number:
value = Number(value);
break;
case Boolean:
value = this.hasAttribute(name);
break;
case Object:
case Array:
try {
value = JSON.parse(value);
} catch(x) {
value = '[invalid JSON]';
}
break;
case Date:
value = Date.parse(value);
break;
case String:
default:
break;
}
return value;
}
});
</script>

View File

@@ -0,0 +1,216 @@
<script>
Base.addFeature({
// per instance
init: function() {
this._data = Object.create(null);
},
_setupBindListeners: function() {
this._bindListeners.forEach(this._setupBindListener, this);
},
_setupBindListener: function(info) {
// <node>.on.<property>-changed: <path]> = e.detail.value
//console.log('[_setupBindListener]: [%s][%s] listening for [%s][%s-changed]', this.localName, info.path, info.id || info.index, info.property);
var node = info.id ? this.$[info.id] : this._nodes[info.index];
var fn = new Function('e', 'this.' + info.path + ' = e.detail.value;');
node.addEventListener(info.property + '-changed', fn.bind(this));
},
_notifyChange: function(property) {
this.fire(property + '-changed', {value: this[property]}, null, false);
},
_setDataCalls: 0,
_setData: function(property, value) {
Base._setDataCalls++;
var old = this._data[property];
if (old !== value) {
this._data[property] = value;
}
return old;
},
// per prototype
register: function(prototype) {
prototype._bindListeners = [];
prototype._createBindings();
},
_createBindings: function() {
var fx$ = this._propertyEffects;
if (fx$) {
//console.group(this.name);
for (var n in fx$) {
//console.group(n);
var fx = fx$[n];
fx.sort(this._sortPropertyEffects);
//console.log(fx);
var compiledEffects = fx.map(function(x) {
return this._buildEffect(n, x);
}, this);
this._bindPropertyEffects(n, compiledEffects);
//console.log(fxt.join('\n'));
//console.groupEnd();
}
//console.groupEnd();
}
},
_sortPropertyEffects: function(a, b) {
switch (a.kind) {
case 'compute':
return b.kind === 'compute' ? 0 : -1;
case 'notify':
return b.kind === 'notify' ? 0 : 1;
default:
return 0;
}
},
_buildEffect: function(property, fx) {
return this['_' + fx.kind + 'EffectBuilder'](property, fx.effect);
},
_methodEffectBuilder: function(source, effect) {
// TODO(sjmiles): validation system requires a blessed
// validator effect which needs to be processed first.
/*
if (typeof this[effect] === 'function') {
return [
'var validated = this.' + effect + '(value, old)',
'if (validated !== undefined) {',
' // recurse',
' this[property] = validated;',
' return;',
'}'
].join('\n');
}
*/
//
return 'this.' + effect + '(this._data.' + source + ', old);'
},
// basic modus operandi
//
// <hostPath> %=% <targetPath>
// (node = <$.id | nodes[index]>)
// <model[.path]> %=% node.<property>
//
// flow-up:
// set(model): node.<property> = <model[.path]>
//
// flow-down:
// node.on.<property>-changed: <model[.path]> = e.detail.value
_bindEffectBuilder: function(hostProperty, targetPath) {
var parts = targetPath.split('.');
var id = parts[0], property = parts[1];
if (!property) {
property = 'textContent';
// textContent never flows-up
} else {
// flow-up
this._bindListeners.push({
id: id,
property: property,
path: hostProperty
});
}
//
// flow-down
//
//console.log('[_bindEffectBuilder]: [%s] %=% [%s].[%s]', hostProperty, id, property);
return 'this.$.' + id + '.' + property + ' = '
+ 'this._data.' + hostProperty + ';'
},
_notifyEffectBuilder: function(source) {
return 'this._notifyChange(\'' + source + '\')';
},
_computeEffectBuilder: function(source, effect) {
return 'this.' + effect.property
+ ' = this.' + effect.method + '(this._data.' + source + ');';
},
// implement effect directives from template annotations
// _nodes[info.index][info.name] = {{info.value}}
_annotationEffectBuilder: function(hostProperty, info) {
var property = info.name || 'textContent';
if (property !== 'textContent') {
// <node>.on.<property>-changed: <path> = e.detail.value
this._addAnnotatedListener(info.index, property, info.value);
}
//
// flow-down
//
// construct the effect to occur when [property] changes:
// set nodes[index][name] to this[value]
//
//console.log('[_annotationEffectBuilder]: [%s] %=% [%s].[%s]', info.value, info.index, property);
return 'this._nodes[' + info.index + '].' + property
+ ' = this._data.' + info.value + ';';
},
_addAnnotatedListener: function(index, property, path) {
// <node>.on.<property>-changed: <path> = e.detail.value
this._bindListeners.push({
index: index,
property: property,
path: path
});
},
_bindAnnotationProperty: function(name, path, index) {
return 'this._nodes[' + index + '].' + name
+ ' = this._data.' + path + ';';
},
_addBindListener: function(property, path, id) {
var bl = this._requireBindListeners(property);
bl.targets.push({
id: id,
path: path
});
},
// create accessors that implement effects
_bindPropertyEffects: function(property, effects) {
var defun = {
get: function() {
return this._data[property];
}
}
if (effects.length) {
// combine effects
effects = effects.join('\n\t\t');
// construct effector
var effector = '_' + property + 'Effector';
this[effector] = new Function('old', effects);
// construct setter body
var body = '\tvar old = this._setData(\'' + property + '\', value);\n'
+ '\tif (value !== old) {\n'
+ '\t\tthis.' + effector + '(old);\n'
+ '\t}';
var setter = new Function('value', body);
// ReadOnly properties have a private setter only
if (this.isReadOnlyProperty(property)) {
this['_set_' + property] = setter;
}
// other properties have a proper setter
else {
defun.set = setter;
}
}
Object.defineProperty(this, property, defun);
//console.log(prop.set ? prop.set.toString() : '(read-only)');
}
});
</script>

View File

@@ -0,0 +1,115 @@
<script>
/**
* Needs new name.
*
* Support for the declarative property sugaring via a `bind` object
* on the prototype.
*
* Building applications by hand, a pattern emerges: operations such as
* data propagation need to trigger effects. For example, changes to data
* need to be reflected into DOM, or trigger additional value computations.
*
* This module provides an API for registering effects against properties.
* The effect data is consumed by the `bind-effects` module which compiles
* the effects into efficient JavaScript that is triggered, e.g., when a
* property is set to a new value.
*
* Property effects can be created imperatively, by template-annotations
* (e.g. mustache notation), or by declaration in the `bind` object.
*
* The bind object syntax is as follows:
*
* bind {
* // if `method` is the name of a method on the current object, the
* // method is invoked with the property changes. The method is provided
* // arguments as follows: `method(value, oldValue)`
* property: 'method'
*
* // if the value is not the name of a method, it's assumed to be a
* // id of an element in the `$` hash. Remember that when using a
* // template, `$` maps element ids to elements. By default, changes in
* // the named property are sent to the target element's `textContent`.
* // In this case, when `property2` changes, it's value is set to
* // `this.$.myId.textContent`.
* property2: 'myId'
*
* // A target property other than `textContent` can be specified using
* // dot notation. In this case, when `property3` changes, it's value is
* // set to `this.$.myId.value`.
* property3: 'myId.value'
*
* // To have a property modification trigger multiple side effects, use
* // an array.
* property4: [
* 'property4Changed',
* 'myId.data',
* 'otherId.value'
* ]
* }
*
* @class feature: bind
*/
Base.addFeature({
// per prototype
// TODO(sjmiles): initialization of `_propertyEffects` and the
// `addPropertyEffect` itself are really the domain of bind-effects
// but these things needs to happen before bind-effects itself initializes.
// We need to factor bind-effects into before and after features instead
// and let this feature be for dealing with `bind` object.
register: function(prototype) {
prototype._addPropertyBindEffects();
},
// TODO(sjmiles): really ad hoc self-modifying code
// to resolve initialization ordering around optional
// module
addPropertyEffect: function(property, kind, effect) {
// prepare storage on first invocation
this._propertyEffects = {};
// add the effect
this._addPropertyEffect(property, kind, effect);
// subsequent invocations skip preparation step implementation
this.addPropertyEffect = this._addPropertyEffect;
},
_addPropertyEffect: function(property, kind, effect) {
var fx = this._propertyEffects[property];
if (!fx) {
fx = this._propertyEffects[property] = [];
}
fx.push({
kind: kind,
effect: effect
});
},
_addPropertyBindEffects: function() {
for (var n in this.bind) {
var bind = this.bind[n];
if (typeof bind === 'object') {
// multiplexed definition
for (var nn in bind) {
this._addPropertyBindEffect(n, bind[nn]);
}
} else {
// single definition
this._addPropertyBindEffect(n, bind);
}
}
},
_addPropertyBindEffect: function(property, bindEffect) {
var kind = 'bind';
if (typeof this[bindEffect] === 'function') {
kind = 'method';
}
this.addPropertyEffect(property, kind, bindEffect);
}
});
</script>

View File

@@ -0,0 +1,39 @@
<script>
Base.addFeature({
/* computed property feature */
computed: {
},
register: function(prototype) {
prototype.defineComputedProperties(prototype.computed);
},
defineComputedProperties: function(computed) {
for (var n in computed) {
this.defineComputedProperty(n, computed[n]);
}
},
defineComputedProperty: function(name, expression) {
var index = expression.indexOf('(');
var method = expression.slice(0, index);
var args = expression.slice(index + 1, -1).replace(/ /g, '').split(',');
console.log('%c on [%s] compute [%s] via [%s]', 'color: green', args[0], name, method);
this.addPropertyEffect(args[0], 'compute', {
property: name,
method: method
});
/*
this.compoundWatch(args, function() {
Polymer.log.watches && console.log('[compute] [%s]', name, arguments);
this[name] = method.apply(this, arguments);
});
*/
}
});
</script>

View File

@@ -0,0 +1,54 @@
<script>
Base.addFeature({
// TODO(sjmiles): ad-hoc signal for `ShadowDOM-lite-enhanced` nodes
isHost: true,
register: function(prototype) {
var t = prototype._template;
// TODO(sorvell): is qsa is wrong here due to distribution?
// TODO(sjmiles): No element should ever actually stamp a <content> node
// into it's composed tree, so I believe this is actually correct.
// However, I wonder if it's more efficient to capture during annotation
// parsing, since the parse step does a tree walk in any case, and the
// tree is smaller before element expansion.
prototype._useContent = Boolean(t && t.content.querySelector('content'));
},
poolContent: function() {
// pool the light dom
var pool = document.createDocumentFragment();
while (this.firstChild) {
pool.appendChild(this.firstChild);
}
this.contentPool = pool;
// capture lightChildren to help reify dom scoping
this.lightChildren =
Array.prototype.slice.call(this.contentPool.childNodes, 0);
},
distributeContent: function() {
var content, pool = this.contentPool;
// replace <content> with nodes teleported from pool
while (content = this.querySelector('content')) {
var select = content.getAttribute('select');
var frag = pool;
if (select) {
frag = document.createDocumentFragment();
// TODO(sjmiles): diverges from ShadowDOM spec behavior: ShadowDOM
// only selects top level nodes from pool. Iterate children and match
// manually instead.
var nodes = pool.querySelectorAll(select);
for (var i=0, l=nodes.length; i<l; i++) {
frag.appendChild(nodes[i]);
}
}
// content self-destructs
content.parentNode.replaceChild(frag, content);
}
}
});
</script>

View File

@@ -0,0 +1,44 @@
<script>
Base.addFeature({
listeners: {},
init: function() {
},
// TODO(sjmiles): support for '.' notation requires 'nodes' feature
listenListeners: function() {
for (var key in this.listeners) {
var node = this, name = key;
if (name.indexOf('.') >= 0) {
name = name.split('.');
node = this.$[name[0]];
name = name[1];
}
this.listen(node, name, this.listeners[key]);
}
},
listen: function(node, eventName, methodName) {
node.addEventListener(eventName, function(e) {
this[methodName](e, e.detail);
}.bind(this));
},
// TODO(sjmiles): use a dictionary for options after `detail`
fire: function(type, detail, onNode, bubbles, cancelable) {
var node = onNode || this;
var detail = (detail === null || detail === undefined) ? {} : detail;
var event = new CustomEvent(type, {
bubbles: bubbles !== undefined ? bubbles : true,
cancelable: cancelable !== undefined ? cancelable : true,
detail: detail
});
node.dispatchEvent(event);
return event;
}
});
</script>

View File

@@ -0,0 +1,31 @@
<script>
Base.addFeature({
keyPresses: {},
listenKeyPresses: function() {
// for..in here to gate empty keyPresses object (iterates once or never)
for (var n in this.keyPresses) {
// only get here if there is something in keyPresses
this.addEventListener('keypress', this.keyPressesFeatureHandler);
// map string keys to numeric codes
for (n in this.keyPresses) {
if (typeof n === 'string') {
this.keyPresses[Event.prototype.keys[n]] = this.keyPresses[n];
}
}
break;
}
},
keyPressesFeatureHandler: function(e) {
var method = this.keyPresses[e.keyCode];
if (method && this[method]) {
return this[method](e.keyCode, e);
}
}
});
</script>

View File

@@ -0,0 +1,287 @@
<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<style>
/*******************************
Flex Layout
*******************************/
[layout][horizontal], [layout][vertical] {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
[layout][horizontal][inline], [layout][vertical][inline] {
display: -ms-inline-flexbox;
display: -webkit-inline-flex;
display: inline-flex;
}
[layout][horizontal] {
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
}
[layout][horizontal][reverse] {
-ms-flex-direction: row-reverse;
-webkit-flex-direction: row-reverse;
flex-direction: row-reverse;
}
[layout][vertical] {
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
}
[layout][vertical][reverse] {
-ms-flex-direction: column-reverse;
-webkit-flex-direction: column-reverse;
flex-direction: column-reverse;
}
[layout][wrap] {
-ms-flex-wrap: wrap;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
}
[layout][wrap-reverse] {
-ms-flex-wrap: wrap-reverse;
-webkit-flex-wrap: wrap-reverse;
flex-wrap: wrap-reverse;
}
[flex] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
[flex][auto] {
-ms-flex: 1 1 auto;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
}
[flex][none] {
-ms-flex: none;
-webkit-flex: none;
flex: none;
}
[flex][one] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
[flex][two] {
-ms-flex: 2;
-webkit-flex: 2;
flex: 2;
}
[flex][three] {
-ms-flex: 3;
-webkit-flex: 3;
flex: 3;
}
[flex][four] {
-ms-flex: 4;
-webkit-flex: 4;
flex: 4;
}
[flex][five] {
-ms-flex: 5;
-webkit-flex: 5;
flex: 5;
}
[flex][six] {
-ms-flex: 6;
-webkit-flex: 6;
flex: 6;
}
[flex][seven] {
-ms-flex: 7;
-webkit-flex: 7;
flex: 7;
}
[flex][eight] {
-ms-flex: 8;
-webkit-flex: 8;
flex: 8;
}
[flex][nine] {
-ms-flex: 9;
-webkit-flex: 9;
flex: 9;
}
[flex][ten] {
-ms-flex: 10;
-webkit-flex: 10;
flex: 10;
}
[flex][eleven] {
-ms-flex: 11;
-webkit-flex: 11;
flex: 11;
}
[flex][twelve] {
-ms-flex: 12;
-webkit-flex: 12;
flex: 12;
}
/* alignment in cross axis */
[layout][start] {
-ms-flex-align: start;
-webkit-align-items: flex-start;
align-items: flex-start;
}
[layout][center], [layout][center-center] {
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
[layout][end] {
-ms-flex-align: end;
-webkit-align-items: flex-end;
align-items: flex-end;
}
/* alignment in main axis */
[layout][start-justified] {
-ms-flex-pack: start;
-webkit-justify-content: flex-start;
justify-content: flex-start;
}
[layout][center-justified], [layout][center-center] {
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
[layout][end-justified] {
-ms-flex-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
}
[layout][around-justified] {
-ms-flex-pack: around;
-webkit-justify-content: space-around;
justify-content: space-around;
}
[layout][justified] {
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
}
/* self alignment */
[self-start] {
-ms-align-self: flex-start;
-webkit-align-self: flex-start;
align-self: flex-start;
}
[self-center] {
-ms-align-self: center;
-webkit-align-self: center;
align-self: center;
}
[self-end] {
-ms-align-self: flex-end;
-webkit-align-self: flex-end;
align-self: flex-end;
}
[self-stretch] {
-ms-align-self: stretch;
-webkit-align-self: stretch;
align-self: stretch;
}
/*******************************
Other Layout
*******************************/
[block] {
display: block;
}
/* ie support for hidden */
[hidden] {
display: none !important;
}
[invisible] {
visibility: hidden !important;
}
[relative] {
position: relative;
}
[fit] {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
body[fullbleed] {
margin: 0;
height: 100vh;
}
[scroll] {
-webkit-overflow-scrolling: touch;
overflow: auto;
}
/*******************************
Other
*******************************/
[segment], segment {
display: block;
position: relative;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
margin: 1em 0.5em;
padding: 1em;
background-color: white;
-webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
border-radius: 5px 5px 5px 5px;
}
</style>

View File

@@ -0,0 +1,12 @@
<script>
Base.addFeature({
log: function() {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = '[%s]: ' + args[0];
args.splice(1, 0, this.localName);
console.log.apply(console, args);
}
});
</script>

View File

@@ -0,0 +1,90 @@
<script>
/**
* Define public property API.
*
* published: {
* <property>: <Type || Object>,
* ...
*
* // `foo` property can be assigned via attribute, will be deserialized to
* // the specified data-type. All `published` properties have this behavior.
* foo: String,
*
* // `bar` property has additional behavior specifiers.
* // type: as above, type for (de-)serialization
* // notify: true to send a signal when a value is set to this property
* // reflect: true to serialize the property to an attribute
* // readOnly: if true, the property has no setter
* bar: {
* type: Boolean,
* notify: true
* }
* }
*
* By itself the published feature doesn't do anything but provide property
* information. Other features use this information to control behavior.
*
* The `type` information is used by the `attributes` feature to convert
* String values in attributes to properties.
*
* The `bind-effects` feature uses property information to control property
* access.
*
* Marking a property as `notify` causes a change in the property to
* fire a non-bubbling event called `<property>-changed`. Elements that
* have enabled two-way binding to the property use this event to
* observe changes.
*
* `readOnly` properties have a getter, but no setter. To set a read-only
* property, use the private setter method `_set_<property>(value)`.
*
* @class feature: published
*/
Base.addFeature({
published: {
},
nob: Object.create(null),
register: function(prototype) {
// TODO(sjmiles): move to a different module
if (prototype.addPropertyEffect) {
for (var n in prototype.published) {
if (prototype.isNotifyProperty(n)) {
prototype.addPropertyEffect(n, 'notify');
}
}
}
},
getPublishInfo: function(property) {
var p = this.published[property];
if (typeof(p) === 'function') {
p = this.published[property] = {
type: p
};
}
return p || Base.nob;
},
getPublishedPropertyType: function(property) {
return this.getPublishInfo(property).type;
},
isReadOnlyProperty: function(property) {
return this.getPublishInfo(property).readOnly;
},
isNotifyProperty: function(property) {
return this.getPublishInfo(property).notify;
},
isReflectedProperty: function(property) {
return this.getPublishInfo(property).reflect;
}
});
</script>

View File

@@ -0,0 +1,38 @@
<script>
Base.addFeature({
register: function(prototype) {
var script = (document._currentScript || document.currentScript);
var prev = script.previousElementSibling;
if (prev && prev.localName === 'template') {
prototype._template = prev;
}
},
stampTemplate: function(template) {
this._stampTemplate(template || this._template, this.root);
// TODO(sjmiles): hello prollyfill
if (window.CustomElements && CustomElements.upgradeSubtree) {
CustomElements.upgradeSubtree(this.root);
}
},
_stampTemplate: function(template, target) {
// TODO(sorvell): light dom children will invalidate annotations.
// TODO(sjmiles): ^ why?
var instance = this.instanceTemplate(template);
// identify host
for (var e = instance.firstElementChild; e; e=e.nextElementSibling) {
e.host = this;
}
target.insertBefore(instance, target.firstElementChild);
},
instanceTemplate: function(template) {
return document.importNode(template.content, true);
}
});
</script>

View File

@@ -0,0 +1,42 @@
<script>
Base.addFeature({
async: function(method) {
var handled = false;
var handle = function() {
if (!handled) {
handled = true;
method.call(this);
}
}.bind(this);
// minimize latency by racing timeout against rAF
setTimeout(handle);
requestAnimationFrame(handle);
},
toggleAttribute: function(name, value, node) {
(node || this)[value ? 'setAttribute' : 'removeAttribute'](name, '');
},
attributeFollows: function(name, neo, old) {
if (old) {
old.removeAttribute(name);
}
if (neo) {
neo.setAttribute(name, '');
}
},
queryHost: function(node) {
return this.host || this._queryHost(this);
},
_queryHost: function(node) {
return node &&
(node.host || (node.host = this._queryHost(node.parentNode)));
}
});
</script>

View File

@@ -0,0 +1,42 @@
<script>
// a tiny bit of sugar for `document.currentScript.ownerDocument`
// sadly `import` is reserved, so we need another name or
// you have to refer to this value `window.import`
Object.defineProperty(window, 'import', {
enumerable: true,
configurable: true,
get: function() {
return (document._currentScript || document.currentScript).ownerDocument;
}
});
// copy own properties from 'api' to 'prototype, with name hinting for 'super'
function extend(prototype, api) {
if (prototype && api) {
// use only own properties of 'api'
Object.getOwnPropertyNames(api).forEach(function(n) {
// acquire property descriptor
var pd = Object.getOwnPropertyDescriptor(api, n);
if (pd) {
// clone property via descriptor
Object.defineProperty(prototype, n, pd);
// cache name-of-method for 'super' engine
/*
if (typeof pd.value == 'function') {
// hint the 'super' engine
pd.value.nom = n;
}
*/
}
});
}
return prototype;
};
Event.prototype.keys = {
ESC_KEY: 27,
ENTER_KEY: 13
};
</script>

View File

@@ -0,0 +1,118 @@
<script>
(function() {
var originalSetupBindListeners = Base._setupBindListeners;
Base.addFeature({
// TODO(sorvell): hax to automate path listener setup; figure out how to
// integrate this better.
_setupBindListeners: function() {
originalSetupBindListeners.call(this);
this._setupPathListeners();
},
/*
Enables structured data flow. Looks at bindings and adds an event listener
in two cases:
1. the binding path is compound (contains a `.`).
2. the binding property is an object valued published property.
*/
_setupPathListeners: function() {
var $b = this._bindListeners;
for (var i=0, l=$b.length, info; (i<l) && (info=$b[i]); i++) {
var node = this._nodeForBinding(info);
var model = modelForPath(info.path);
var event;
// determine if and what event we need to listen on
if (node._isPublishedObject &&
node._isPublishedObject(info.property)) {
event = info.property + EVENT_PROP_CHANGED;
} else if (model !== info.path) {
event = info.property + EVENT_CHANGED;
}
// listen iff we need to
if (event) {
this._setupPathListener(node, event, model, info.path);
}
}
},
// TODO(sorvell): at one point the listener had to be async to propagate
// changes correctly; this no longer seems to be necessary but need
// to keep an eye on it until it's understood fully.
_setupPathListener: function(node, event, model, path) {
var self = this;
node.addEventListener(event, function(e) {
self.notifyPropertyChange(model, path);
});
},
/**
Notify that a property has changed. This method can be used to
flow data through the system when a path in an Object valued property
changes. For example:
this.item.user.name = 'Bob';
this.notifyPropertyChange('item', 'user.name');
*/
notifyPropertyChange: function(property, path) {
this._forcePropertyEffect(property);
this._firePropertyChange(property, path);
},
// Force property side effects.
// 1. calls the _propertyEffector
// 2. for bindings to Object valued properties, we need to cascade
// recursively through dirty check prevention
// (e.g. if foo.item = this.item is dirty checked away, we ensure
// foo.item's side effects run)
_forcePropertyEffect: function(property) {
this['_' + property + 'Effector']();
// if the property is bound to other Object valued properties,
// force those properties to effect changes.
var $b = this._bindListeners;
for (var i=0, l=$b.length, b; (i<l) && (b=$b[i]); i++) {
this._forceBoundPropertyEffect(property, b);
}
},
_forceBoundPropertyEffect: function(property, binding) {
var node = this._nodeForBinding(binding);
var model = modelForPath(binding.path);
if ((model === property) && node._isPublishedObject(binding.property)) {
node._forcePropertyEffect(binding.property);
}
},
_firePropertyChange: function(modelProperty, path) {
var detail = {
property: this[modelProperty],
path: path
};
this.fire(modelProperty + '-property-changed', detail, null, false);
},
_nodeForBinding: function(info) {
return info.id ? this.$[info.id] : this._nodes[info.index];
},
_isPublishedObject: function(property) {
return this.published[property] &&
(this.published[property].type === Object);
}
});
function modelForPath(path) {
return path.split('.').shift();
}
var EVENT_CHANGED = '-changed';
var EVENT_PROP_CHANGED = '-property' + EVENT_CHANGED;
})();
</script>

View File

@@ -0,0 +1,63 @@
<script>
(function() {
/**
Provides basic inheritance capability via the `extends` property.
*/
Base.addFeature({
register: function(prototype) {
if (prototype.extends) {
prototype.__proto__ = getExtendsPrototype(prototype.extends);
}
}
});
// TODO(sorvell): requires redefining `Polymer`.
Polymer = function(prototype) {
prototype.__proto__ = Base;
prototype.registerCallback();
var config = {prototype: prototype};
if (prototype.extends && !isCustomElementName(prototype.extends)) {
config.extends = prototype.extends;
}
Polymer[prototype.name] = document.registerElement(prototype.name, config);
};
Polymer.log = {
};
function getExtendsPrototype(name) {
var ctor = Polymer[name];
if (ctor) {
return ctor.prototype;
} else {
if (isCustomElementName(name)) {
console.warn('Cannot find extended prototype named', name);
} else {
return getExtendedNativePrototype(name);
}
}
}
function isCustomElementName(name) {
return name.indexOf('-') >= 0;
}
var nativePrototypes = {};
function getExtendedNativePrototype(name) {
if (!nativePrototypes[name]) {
var proto = Object.getPrototypeOf(document.createElement(name));
extend(proto, Base);
nativePrototypes[name] = proto;
}
return nativePrototypes[name];
}
})();
</script>

View File

@@ -0,0 +1,23 @@
<link rel="import" href="../polymer.html">
<link rel="import" href="../features/template.html">
<!-- shadow-styler must be before annotations! -->
<link rel="import" href="shadow-styler.html">
<!-- load normal polymer, imports takes care of de-duping -->
<link rel="import" href="../../polymer.html">
<link rel="import" href="shadow.html">
<link rel="import" href="shadow-layout.html">
<!-- here we want to change the basic behavior for all elements, but in general
this could be a setting. -->
<script>
Base.features = function() {
this._useContent = false;
if (this._template) {
this.createShadow();
}
this.defaultFeatures();
}
</script>

View File

@@ -0,0 +1,120 @@
<script>
(function() {
/**
Provides `ready` lifecycle callback which is called parent to child.
Allows initial property values to be set on the prototype and applied
iff no other input value is given via attribute literal or binding.
*/
// TODO(sorvell): monkey path for now.
var originalAttachedCallback = Base.attachedCallback;
var originalCreateBindings = Base._createBindings;
Base.addFeature({
attachedCallback: function() {
this._checkReady();
originalAttachedCallback.call(this);
},
// Checks if this element is inside another "readyable" element. If so,
// then pushes this element into an array of `_needsReady` elements; if
// not, starts the ready cascade.
_checkReady: function() {
if (!this._readied) {
var host = this._findHostParent();
if (host) {
host._registerForReady(this);
} else {
this._cascadeReady();
}
}
},
_registerForReady: function(element) {
if (!this._needsReady) {
this._needsReady = [];
}
this._needsReady.push(element);
},
_findHostParent: function() {
var n = this;
while (n = n.parentNode) {
if (n.isHost) {
return n;
}
}
},
_cascadeReady: function() {
//console.group('[' + this.name + '#' + this.id + ']', 'ready');
this._readyCallback();
var n$ = this._needsReady;
if (n$) {
for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
if (n._cascadeReady) {
n._cascadeReady();
}
}
}
this._needsReady = null;
//console.groupEnd('[' + this.name + '#' + this.id + ']', 'ready');
},
_readyCallback: function() {
this.ready();
this._applyInitialValues();
this._readied = true;
},
// user extension point.
ready: function() {},
// overridden to allow recording of initial values
_createBindings: function() {
this._recordInitialValues();
originalCreateBindings.call(this);
},
_recordInitialValues: function() {
this._initialValues = {};
var fx$ = this._propertyEffects;
if (fx$) {
for (var n in fx$) {
var v = this[n];
if (v !== undefined) {
this._initialValues[n] = v;
}
}
}
},
// TODO(sorvell): preferrably we would squelch method side effects here
_applyInitialValues: function() {
for (var i in this._initialValues) {
this.setDefaultValue(i, this._initialValues[i]);
}
this._initialValues = null;
},
/**
Ensures `property` has a value. If it does not, then the property is
set to `defaultValue`.
*/
setDefaultValue: function(property, defaultValue) {
if (this[property] === undefined) {
this[property] = defaultValue;
}
}
});
})();
</script>

View File

@@ -0,0 +1,286 @@
<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<style>
/*******************************
Flex Layout
*******************************/
html /deep/ [layout][horizontal], html /deep/ [layout][vertical] {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
html /deep/ [layout][horizontal][inline], html /deep/ [layout][vertical][inline] {
display: -ms-inline-flexbox;
display: -webkit-inline-flex;
display: inline-flex;
}
html /deep/ [layout][horizontal] {
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
}
html /deep/ [layout][horizontal][reverse] {
-ms-flex-direction: row-reverse;
-webkit-flex-direction: row-reverse;
flex-direction: row-reverse;
}
html /deep/ [layout][vertical] {
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
}
html /deep/ [layout][vertical][reverse] {
-ms-flex-direction: column-reverse;
-webkit-flex-direction: column-reverse;
flex-direction: column-reverse;
}
html /deep/ [layout][wrap] {
-ms-flex-wrap: wrap;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
}
html /deep/ [layout][wrap-reverse] {
-ms-flex-wrap: wrap-reverse;
-webkit-flex-wrap: wrap-reverse;
flex-wrap: wrap-reverse;
}
html /deep/ [flex] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
html /deep/ [flex][auto] {
-ms-flex: 1 1 auto;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
}
html /deep/ [flex][none] {
-ms-flex: none;
-webkit-flex: none;
flex: none;
}
html /deep/ [flex][one] {
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}
html /deep/ [flex][two] {
-ms-flex: 2;
-webkit-flex: 2;
flex: 2;
}
html /deep/ [flex][three] {
-ms-flex: 3;
-webkit-flex: 3;
flex: 3;
}
html /deep/ [flex][four] {
-ms-flex: 4;
-webkit-flex: 4;
flex: 4;
}
html /deep/ [flex][five] {
-ms-flex: 5;
-webkit-flex: 5;
flex: 5;
}
html /deep/ [flex][six] {
-ms-flex: 6;
-webkit-flex: 6;
flex: 6;
}
html /deep/ [flex][seven] {
-ms-flex: 7;
-webkit-flex: 7;
flex: 7;
}
html /deep/ [flex][eight] {
-ms-flex: 8;
-webkit-flex: 8;
flex: 8;
}
html /deep/ [flex][nine] {
-ms-flex: 9;
-webkit-flex: 9;
flex: 9;
}
html /deep/ [flex][ten] {
-ms-flex: 10;
-webkit-flex: 10;
flex: 10;
}
html /deep/ [flex][eleven] {
-ms-flex: 11;
-webkit-flex: 11;
flex: 11;
}
html /deep/ [flex][twelve] {
-ms-flex: 12;
-webkit-flex: 12;
flex: 12;
}
/* alignment in cross axis */
html /deep/ [layout][start] {
-ms-flex-align: start;
-webkit-align-items: flex-start;
align-items: flex-start;
}
html /deep/ [layout][center], html /deep/ [layout][center-center] {
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
html /deep/ [layout][end] {
-ms-flex-align: end;
-webkit-align-items: flex-end;
align-items: flex-end;
}
/* alignment in main axis */
html /deep/ [layout][start-justified] {
-ms-flex-pack: start;
-webkit-justify-content: flex-start;
justify-content: flex-start;
}
html /deep/ [layout][center-justified], html /deep/ [layout][center-center] {
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
html /deep/ [layout][end-justified] {
-ms-flex-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
}
html /deep/ [layout][around-justified] {
-ms-flex-pack: around;
-webkit-justify-content: space-around;
justify-content: space-around;
}
html /deep/ [layout][justified] {
-ms-flex-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
}
/* self alignment */
html /deep/ [self-start] {
-ms-align-self: flex-start;
-webkit-align-self: flex-start;
align-self: flex-start;
}
html /deep/ [self-center] {
-ms-align-self: center;
-webkit-align-self: center;
align-self: center;
}
html /deep/ [self-end] {
-ms-align-self: flex-end;
-webkit-align-self: flex-end;
align-self: flex-end;
}
html /deep/ [self-stretch] {
-ms-align-self: stretch;
-webkit-align-self: stretch;
align-self: stretch;
}
/*******************************
Other Layout
*******************************/
html /deep/ [block] {
display: block;
}
/* ie support for hidden */
html /deep/ [hidden] {
display: none !important;
}
html /deep/ [invisible] {
visibility: hidden !important;
}
html /deep/ [relative] {
position: relative;
}
html /deep/ [fit] {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
body[fullbleed] {
margin: 0;
height: 100vh;
}
html /deep/ [scroll] {
overflow: auto;
}
/*******************************
Other
*******************************/
html /deep/ [segment], html /deep/ segment {
display: block;
position: relative;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
margin: 1em 0.5em;
padding: 1em;
background-color: white;
-webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
border-radius: 5px 5px 5px 5px;
}
</style>

View File

@@ -0,0 +1,63 @@
<script>
/* Configures elemnent styles to stamped into ShadowDOM.
NOTE: using this feature will currently change all templates to be configured
to be styled for ShadowDOM.
* If an element has a template, style is placed in the template and element
name is replaced with `:host`.
* If an element does not have a template, style remains in place, but rules
are prepended with `html /deep/`.
*/
(function() {
XStyles = window.XStyles || {};
Base.addFeature({
register: function(prototype) {
var template = prototype._template;
var hook = template ||
(document._currentScript || document.currentScript);
var styles = [];
var prev = hook.previousElementSibling;
if (prev && prev.localName === 'style') {
styles.push(prev);
}
styles = styles.concat(XStyles[prototype.name] || []);
prototype._styles = styles;
if (!styles.length) {
return;
}
// use :host or /deep/ depending on if the element has a template and
// therefore will have a shadowRoot.
var selector = template ? ':host' : 'html /deep/ $&';
this._processStyles(prototype._styles, prototype.name, selector);
if (template) {
this.insertStyles(prototype._styles, template.content);
}
},
_processStyles: function(styles, name, selector) {
var re = new RegExp(name, 'g');
for (var i=0, l=styles.length, style; (i<l) && (style=styles[i]); i++) {
style.textContent = style.textContent.replace(re, selector);
}
},
insertStyles: function(styles, root) {
var ref = root.firstChild;
for (var i=0, l=styles.length, style, n; (i<l) && (style=styles[i]); i++) {
n = document.createElement('style');
n.textContent = style.textContent;
ref = root.insertBefore(n,
ref.nextElementSibling);
}
}
});
})();
</script>

View File

@@ -0,0 +1,9 @@
<script>
Base.addFeature({
createShadow: function(template) {
this.root = this.createShadowRoot();
}
});
</script>

View File

@@ -0,0 +1,17 @@
<link rel="import" href="lang.html">
<link rel="import" href="base.html">
<script>
Base.__proto__ = HTMLElement.prototype;
Polymer = function(prototype) {
prototype.__proto__ = Base;
prototype.registerCallback();
document.registerElement(prototype.name, {prototype: prototype});
};
Polymer.log = {
};
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,5 @@
<style>
.beaker {
background-image: url('Beaker2.jpg');
}
</style>

View File

@@ -0,0 +1,310 @@
<link rel="import" href="../../polymer/polymer.html">
<!--
Below, some elements add additional features to Base.
Ideally we Just Work under this scenario, but we need to take care
because we are sharing a base class and there could be name-collisions.
-->
<!-- raw -->
<style>
x-trivial {
display: block;
padding: 8px;
}
</style>
<script>
Polymer({
name: 'x-trivial',
// stubbing out `features` gives us maximum vanilla
features: function() {
},
// These lifecycle callbacks are non-standard: the standard callbacks are
// all suffixed by `Callback`. We reserve the native callbacks for system
// processing in Base and supply the shorter names for individual elements
// to override as necessary.
created: function() {
this.innerHTML = '<i>Hey</i>, is this script <b>on</b>?';
},
// these lifecycle callbacks are also available
attached: function() {
},
detached: function() {
},
attributeChanged: function(name) {
}
});
</script>
<!-- using a template -->
<style>
x-template {
display: block;
padding: 8px;
}
</style>
<template>
<i>Hey</i>, is this template <b>on</b>?
</template>
<script>
Polymer({
name: 'x-template',
features: function() {
// use template feature
this.stampTemplate();
}
});
</script>
<!-- inexpensive `nodes` feature -->
<style>
x-cheapgood-nodes {
display: block;
padding: 8px;
}
</style>
<template>
<i id="exclaim"></i>, is the node feature <b>on</b>?
</template>
<script>
Polymer({
name: 'x-cheapgood-nodes',
features: function() {
// use template feature
this.stampTemplate();
// use `nodes` feature
this.marshalNodeReferences();
},
created: function() {
// `nodes` features populates `$` map from id's in the template
this.$.exclaim.textContent = 'Hey';
}
});
</script>
<!-- inexpensive `listeners` feature -->
<style>
x-cheapgood-listeners {
display: block;
padding: 8px;
}
</style>
<template>
<i id="exclaim">Hey</i>, are listeners <b>on</b>?
</template>
<script>
Polymer({
name: 'x-cheapgood-listeners',
listeners: {
click: 'clickAction',
'exclaim.click': 'exclaimClickAction'
},
features: function() {
// use template feature
this.stampTemplate();
// use `nodes` feature
this.marshalNodeReferences();
// use `listeners` feature
this.listenListeners();
},
clickAction: function() {
this.style.backgroundColor = 'lightblue';
},
exclaimClickAction: function(e) {
e.stopPropagation();
this.style.backgroundColor = 'orange';
}
});
</script>
<!-- uses standard features (template, nodes, listeners) -->
<style>
x-simple {
display: block;
padding: 8px;
}
</style>
<template>
<i id="exclaim"></i>, is simple mode <b>on</b>?
</template>
<script>
Polymer({
name: 'x-simple',
listeners: {
click: 'clickAction',
'exclaim.click': 'exclaimClickAction'
},
created: function() {
this.$.exclaim.textContent = 'Hey';
},
clickAction: function() {
this.style.backgroundColor = 'lightblue';
},
exclaimClickAction: function(e) {
e.stopPropagation();
this.style.backgroundColor = 'orange';
}
});
</script>
<!--
Can stop here if you want clean-and-simple.
This smoke test goes on to import and exercise some optional features
to make sure everything plays well together.
-->
<!-- import and use `bind` feature -->
<link rel="import" href="../../polymer/more-features/bind.html">
<style>
x-fancy-bind {
display: block;
padding: 8px;
}
</style>
<template>
<i id="exclaim"></i>, is fancy bind mode <b id="state"></b>?
</template>
<script>
Polymer({
name: 'x-fancy-bind',
listeners: {
click: 'clickAction'
},
bind: {
// property: target (set property is pushed to $.<target>[.textContent])
exclaim: 'exclaim',
state: 'state'
},
created: function() {
// NOTE: binding feature turns on automatically at prototype level, if
// you include a `bind` object in your prototype.
// These properties automatically propagate to DOM as defined
// by the `bind` object.
this.exclaim = 'Hey';
this.state = 'on';
},
clickAction: function() {
this.state = 'LIVE';
}
});
</script>
<!-- template annotations feature -->
<link rel="import" href="../../polymer/more-features/annotations.html">
<link rel="import" href="../../polymer/more-features/bind-annotations.html">
<style>
x-fancy-annotations {
display: block;
padding: 8px;
}
</style>
<template>
<i>{{exclaim}}</i>, is fancy annotations mode <b>{{state}}</b>?
</template>
<script>
Polymer({
name: 'x-fancy-annotations',
created: function() {
// NOTE: annotations feature turns on automatically at prototype level,
// need to gate this as it's affecting all elements with templates
// now.
// Create bindings from annotations.
this.marshalBoundNodes();
// These properties automatically propagate to DOM as defined
// by the template annotations.
this.exclaim = 'Hey';
this.state = 'on';
}
});
</script>

View File

@@ -0,0 +1,23 @@
<script src="//www.polymer-project.org/components/platform/platform.js"></script>
<link rel="import" href="polymer-smoke-elements.html">
<x-trivial></x-trivial>
<!-- using a template -->
<x-template></x-template>
<!-- inexpensive `nodes` feature -->
<x-cheapgood-nodes></x-cheapgood-nodes>
<!-- inexpensive `listeners` feature -->
<x-cheapgood-listeners></x-cheapgood-listeners>
<!-- uses standard features (shadow, template, nodes, listeners) -->
<x-simple></x-simple>
<!-- import and use `bind` feature -->
<x-fancy-bind></x-fancy-bind>
<!-- template annotations feature -->
<x-fancy-annotations></x-fancy-annotations>

View File

@@ -0,0 +1,155 @@
<link rel="import" href="../polymer.html">
<link rel="import" href="../elements/x-toolbar.html">
<link rel="import" href="../elements/x-overlay.html">
<link rel="import" href="../elements/x-icon.html">
<link rel="import" href="../elements/x-icon-button.html">
<link rel="import" href="../elements/x-pages.html">
<link rel="import" href="../elements/x-selector.html">
<link rel="import" href="../elements/x-search-input.html">
<link rel="import" href="../elements/x-drop-button.html">
<link rel="import" href="../elements/paper-button.html">
<link rel="import" href="../assets/icons.html">
<style>
body {
font-family: sans-serif;
}
x-icon-button {
height: 38px;
}
#overlay {
width: 500px;
height: 200px;
background-color: white;
border: 1px outset silver;
padding: 24px;
}
#overlay2 {
width: 200px;
height: 600px;
background-color: white;
border: 1px outset silver;
padding: 24px;
}
#overlay3 {
xwidth: 100px;
background-color: white;
border: 1px outset silver;
}
#overlay3 [selected] {
background-color: lightblue;
}
#overlay3 h3 {
padding: 8px 24px;
margin: 0;
}
#overlay3 [tools] {
padding: 4px;
}
paper-button {
background-color: lightblue;
}
page {
display: block;
}
#one {
background-color: whitesmoke;
}
#two {
background-color: #FFDCCA;
}
x-drop-button {
xdisplay: block;
border: 1px dotted silver;
padding: 4px;
}
</style>
<body fullbleed vertical layout>
<x-toolbar>
<x-icon-button icon="menu"></x-icon-button>
<h2>Hello World</h2>
<x-search-input></x-search-input>
<span flex></span>
<x-icon-button icon="schedule" onclick="pages.selected = 1"></x-icon-button>
<x-icon-button icon="settings" onclick="pages.selected = 0"></x-icon-button>
<span flex five></span>
</x-toolbar>
<x-pages id="pages" flex scroll>
<page id="one">
<div style="height: 500px; padding: 16px;">
<x-drop-button onclick="overlay3.open(this.getBoundingClientRect(), true);">Foo</x-drop-button>
</div>
<paper-button onclick="overlay.open();">Dialog</paper-button>
<paper-button onclick="overlay3.open(this.getBoundingClientRect(), true);">Pop</paper-button>
<div style="height: 2500px;"></div>
</page>
<page id="two">
<br>
<h2>Two</h2>
<br>
</page>
</x-pages>
<x-overlay id="overlay" vertical layout>
<div horizontal end-justified layout>
<paper-button onclick="overlay2.open();">Nest</paper-button>
</div>
<div flex></div>
<div horizontal end-justified layout>
<paper-button onclick="overlay.close();">Close</paper-button>
</div>
</x-overlay>
<x-overlay id="overlay2" vertical layout>
<div horizontal end-justified layout>
<paper-button onclick="overlay3.open(this.getBoundingClientRect());">Nest 2</paper-button>
</div>
<div flex></div>
<div horizontal end-justified layout>
<paper-button onclick="overlay2.close();">Close</paper-button>
</div>
</x-overlay>
<x-overlay id="overlay3" vertical layout>
<x-selector block flex>
<h3>Alpha</h3>
<h3>Beta</h3>
<h3>Gamma</h3>
</x-selector>
<div tools horizontal end-justified layout>
<paper-button onclick="overlay3.close();">Close</paper-button>
</div>
</x-overlay>
</body>

View File

@@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="../../web-component-tester/browser.js"></script>
</head>
<body>
<script>
WCT.loadSuites([
'unit/base.html',
]);
</script>
</body>
</html>

View File

@@ -0,0 +1,337 @@
<link rel="import" href="../polymer.html">
<!--
Below, some elements add additional features to Base.
Ideally we Just Work under this scenario, but we need to take care
because we are sharing a base class and there could be name-collisions.
-->
<!-- raw -->
<x-trivial></x-trivial>
<style>
x-trivial {
display: block;
padding: 8px;
}
</style>
<script>
Polymer({
name: 'x-trivial',
// stubbing out `features` gives us maximum vanilla
//features: Polymer.noFeatures,
features: function() {
},
// These lifecycle callbacks are non-standard: the standard callbacks are
// all suffixed by `Callback`. We reserve the native callbacks for system
// processing in Base and supply the shorter names for individual elements
// to override as necessary.
created: function() {
this.innerHTML = '<i>Hey</i>, is this script <b>on</b>?';
},
// these lifecycle callbacks are also available
attached: function() {
},
detached: function() {
},
attributeChanged: function(name) {
}
});
</script>
<!-- using a template -->
<x-template></x-template>
<style>
x-template {
display: block;
padding: 8px;
}
</style>
<template>
<i>Hey</i>, is this template <b>on</b>?
</template>
<script>
Polymer({
name: 'x-template',
features: function() {
// use template feature
this.stampTemplate();
}
});
</script>
<!--
Without platform support for tree-scoping (yay ShadowDOM), robust composition
support requires careful processing of nodes at the javascript level.
For this reason, the very next feature we require is template annotation.
When platforms support tree-scoping, we can defer the expense of template
annotation processing for more advanced features.
-->
<x-template-annote></x-template-annote>
<style>
x-template-annote {
display: block;
padding: 8px;
}
</style>
<template>
<i id="exclaim"></i>, is the annotation feature <b>on</b>?
</template>
<script>
Polymer({
name: 'x-template-annote',
features: function() {
this.stampTemplate();
},
created: function() {
// locate the first annotated node, which we happen to know
this.$ = {
exclaim: this.findAnnotatedNode(this.root, this._template.map[0])
};
this.$.exclaim.textContent = 'Hey';
}
});
</script>
<!--
Once we have enabled parsing template annotations, we can use it to
extract nodes with ids (and be safe around simple composition [but not
yet projection-safe])
-->
<x-cheapgood-nodes></x-cheapgood-nodes>
<style>
x-cheapgood-nodes {
display: block;
padding: 8px;
}
</style>
<template>
<i id="exclaim"></i>, is the nodes feature <b>on</b>?
</template>
<script>
Polymer({
name: 'x-cheapgood-nodes',
features: function() {
this.stampTemplate();
// use `nodes` feature
this.marshalNodeReferences();
},
created: function() {
this.$.exclaim.textContent = 'Hey';
}
});
</script>
<!-- inexpensive `listeners` feature -->
<x-cheapgood-listeners></x-cheapgood-listeners>
<style>
x-cheapgood-listeners {
display: block;
padding: 8px;
cursor: pointer;
}
</style>
<template>
<i id="exclaim">Hey</i>, are listeners <b>on</b>?
</template>
<script>
Polymer({
name: 'x-cheapgood-listeners',
listeners: {
click: 'clickAction',
'exclaim.click': 'exclaimClickAction'
},
features: function() {
this.stampTemplate();
this.marshalNodeReferences();
// use `listeners` feature
this.listenListeners();
},
clickAction: function() {
this.style.backgroundColor = 'lightblue';
},
exclaimClickAction: function(e) {
e.stopPropagation();
this.style.backgroundColor = 'orange';
}
});
</script>
<!-- `bind` feature -->
<x-dom-bind></x-dom-bind>
<style>
x-dom-bind {
display: block;
padding: 8px;
}
</style>
<template>
<i id="exclaim"></i>, is dom-bind mode <b id="state"></b>?
</template>
<script>
Polymer({
name: 'x-dom-bind',
listeners: {
click: 'clickAction'
},
bind: {
// property: target (set property is pushed to $.<target>[.textContent])
exclaim: 'exclaim',
state: 'state'
},
// just use the standard set
// features: Polymer.defaultFeatures
created: function() {
// NOTE: binding feature turns on automatically at prototype level, if
// you include a `bind` object in your prototype.
// These properties automatically propagate to DOM as defined
// by the `bind` object.
this.exclaim = 'Hey';
this.state = 'on';
},
clickAction: function() {
this.state = 'LIVE';
}
});
</script>
<!-- annotation-binding feature -->
<x-bind-annotations></x-bind-annotations>
<style>
x-bind-annotations {
display: block;
padding: 8px;
}
</style>
<template>
<i>{{exclaim}}</i>, are bind-annotations mode <b>{{state}}</b>?
</template>
<script>
Polymer({
name: 'x-bind-annotations',
created: function() {
// These properties automatically propagate to DOM as defined
// by the template annotations.
this.exclaim = 'Hey';
this.state = 'on';
}
});
</script>
<!-- projection feature -->
<x-project>Heya</x-project>
<style>
x-project {
display: block;
padding: 8px;
}
</style>
<template>
<i><content></content></i>, are bind-annotations mode <b>on</b>?
</template>
<script>
Polymer({
name: 'x-project'
});
</script>

View File

@@ -0,0 +1,6 @@
<link rel="import" href="assets/test-style-path.html">
<body class="beaker">
</body>

View File

@@ -0,0 +1,171 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../src/lang.html">
<link rel="import" href="../../src/base.html">
</head>
<body>
<script>
var OrigBase = window.Base;
var Base;
var Child;
var instance;
beforeEach(function() {
// Ensure a clean environment for each test.
Base = extend({}, OrigBase);
Base._features = [];
Child = Object.create(Base);
instance = Object.create(Child);
});
suite('addFeature', function() {
test('mixes the feature into Base', function() {
assert.notOk(Base.someProperty);
Base.addFeature({someProperty: 123});
assert.equal(Base.someProperty, 123);
});
// TODO(nevir): What's up with this behavior?
test('kills init', function() {
Base.init = function() {};
Base.addFeature({});
assert.notOk(Base.init);
});
// TODO(nevir): Ditto above.
test('kills register', function() {
Base.register = function() {};
Base.addFeature({});
assert.notOk(Base.register);
});
});
suite('registerCallback', function() {
test('calls register() for any features', function() {
var called = [];
Base.addFeature({register: function() {called.push('one')}});
Base.addFeature({register: function() {called.push('two')}});
assert.deepEqual(called, []);
Child.registerCallback();
assert.includeMembers(called, ['one', 'two']);
});
test('passes the prototype to feature register()', function() {
Base.addFeature({register: function(prototype) {
assert.equal(prototype, Child);
}});
Child.registerCallback();
});
// TODO(nevir): Pull sinon into WCT to make this sort of test cleaner.
test('calls registered() after features', function() {
var lastCalled = null;
Base.addFeature({register: function() {
assert.equal(lastCalled, null);
lastCalled = 'feature';
}});
Child.registered = function() {
assert.equal(lastCalled, 'feature');
lastCalled = 'registered';
};
Child.registerCallback(Base);
assert.equal(lastCalled, 'registered');
});
});
suite('createdCallback', function() {
// TODO(nevir): sinonify.
test('calls lifecycle events in the proper order', function() {
var lastCalled = null;
Child.beforeCreated = function() {
assert.equal(lastCalled, null);
lastCalled = 'beforeCreated';
};
Base.addFeature({init: function() {
assert.equal(lastCalled, 'beforeCreated');
lastCalled = 'feature';
}});
Child.created = function() {
assert.equal(lastCalled, 'feature');
lastCalled = 'created';
};
Child.afterCreated = function() {
assert.equal(lastCalled, 'created');
lastCalled = 'afterCreated';
};
instance.createdCallback();
assert.equal(lastCalled, 'afterCreated');
});
test('calls init() for any features', function() {
var called = [];
Base.addFeature({init: function() {called.push('one')}});
Base.addFeature({init: function() {called.push('two')}});
assert.deepEqual(called, []);
instance.createdCallback();
assert.includeMembers(called, ['one', 'two']);
});
test('calls feature init() with the correct `this`', function() {
Base.addFeature({init: function() {
assert.equal(this, instance);
}});
instance.createdCallback();
});
});
suite('attachedCallback', function() {
test('calls attached()', function() {
var called = false;
Child.attached = function() {called = true};
instance.attachedCallback();
assert.isTrue(called);
});
});
suite('detachedCallback', function() {
test('calls detached()', function() {
var called = false;
Child.detached = function() {called = true};
instance.detachedCallback();
assert.isTrue(called);
});
});
suite('attributeChangedCallback', function() {
test('calls attributeChanged()', function() {
var args = null;
Child.attributeChanged = function() {args = arguments};
instance.attributeChangedCallback('attr', null, 1, 'stuff');
assert.equal(args.length, 4);
assert.equal(args[0], 'attr');
assert.equal(args[1], null);
assert.equal(args[2], 1);
assert.equal(args[3], 'stuff');
});
});
</script>
</body>
</html>

1
components/vulcanize/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env node
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
var path = require('path');
var fs = require('fs');
var nopt = require('nopt');
var vulcan = require('../lib/vulcan.js');
var pkg = require('../package.json');
var help = [
'vulcanize: Concatenate a set of Web Components into one file',
'',
'Usage:',
' vulcanize [OPTIONS] <html file>*',
'',
'Options:',
' --output, -o: Output file name (defaults to vulcanized.html)',
' --verbose, -v: More verbose logging',
' --help, -h, -?: Print this message',
' --config: Read the given config file',
' --strip, -s: Remove comments and empty text nodes',
' --abspath, -p: Specify site root. Resolve paths to absolute paths based on site root',
' --csp: Extract inline scripts to a separate file (uses <output file name>.js)',
' --inline: The opposite of CSP mode, inline all assets (script and css) into the document',
' --csp --inline: Bundle all javascript (inline and external) into <output file name>.js',
' --version, -V: print version information',
' --no-strip-excludes: Keep imports excluded from inlining',
' --no-update-notifier: disable "update vulcanize" checks',
'',
'Config:',
' JSON file for additional options',
'',
' {',
' "excludes": {',
' "imports": [ "regex-to-exclude" ],',
' "styles": [ "regex-to-exclude" ],',
' "scripts": [ "regex-to-exclude" ],',
' }',
' }'
];
function printHelp() {
console.log(help.join('\n'));
process.exit(0);
}
var options = nopt(
{
'config': path,
'csp': Boolean,
'help': Boolean,
'inline': Boolean,
'output': path,
'abspath': path,
'strip': Boolean,
'verbose': Boolean,
'version': Boolean
},
{
'?': ['--help'],
'h': ['--help'],
'o': ['--output'],
'p': ['--abspath'],
's': ['--strip'],
'v': ['--verbose'],
'V': ['--version']
}
);
if (options.help || process.argv.length === 2) {
printHelp();
}
if (options.version) {
console.log('vulcanize %s', pkg.version);
process.exit(0);
}
if (options['update-notifier'] !== false) {
(function() {
try {
require('update-notifier')({
packageName: pkg.name,
packageVersion: pkg.version
}).notify();
} catch(_) {}
})();
}
var argv = options.argv.remain;
if (argv[0]) {
options.input = path.resolve(argv[0]);
}
vulcan.setOptions(options, function(err) {
if (err) {
console.error(err);
process.exit(1);
}
vulcan.processDocument();
});

View File

@@ -0,0 +1,2 @@
bower_components
vulcanized*

View File

@@ -0,0 +1,12 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="http://example.com/foo/bar.html">
<link rel="import" href="http://example.com/foo/fizz/../bar.html">
<link rel="import" href="/foo/bar.html">

View File

@@ -0,0 +1,5 @@
{
"excludes": {
"imports": ["polymer.html$"]
}
}

View File

@@ -0,0 +1,10 @@
/*
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

View File

@@ -0,0 +1,15 @@
/*
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
:host([type="platform"]) { background-color: red; }
:host([type="core"]) { background-color: red; }
:host([type="elements"]) { background-color: red; }
polyfill-next-selector { content: ':host header'; }
polyfill-next-selector { content: 'I WIN'; }

View File

@@ -0,0 +1,58 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="sub-import/sub-import.html">
<polymer-element name="x-import">
<template>
<link rel="stylesheet" href="import-test.css" shim-shadowdom>
[Imported: <content></content>]
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 630 630" fill="#fff" width="630" height="630" flex?="{{mode !== 'cover'}}">
<rect x="0" y="0" width="630" height="630" fill="#3c790a"/>
<path d="m212 497c11 17 20 31 40 31 19 0 32-8 32-37v-201h59v202c0 61-36 89-88 89-47 0-75-25-89-54M423 492c13 21 29 36 58 36 25 0 40-12 40-29 0-20-16-27-43-39l-15-6c-43-18-71-41-71-89 0-44 34-78 87-78 38 0 65 13 84 47l-46 30c-10-18-21-25-38-25-17 0-28 11-28 25 0 18 11 25 36 36l15 6c50 22 79 44 79 93 0 53-42 82-98 82-55 0-91-26-108-60"/>
<polygon />
<path />
<polygon/>
<path />
</svg>
<ceci-definition>
{
"name": "Signal Transformer",
"thumbnail": "./thumbnail.png",
"description": "Transforms an incoming signal to a value you set and broadcasts it.",
"broadcasts": {
"transformedMessage": {
"label": "Transformed Message"
}
},
"listeners": {
"incomingMessage": {
"label": "Incoming Message",
"default" : true
}
},
"attributes": {
"message": {
"label": "Message",
"editable": "Text",
"default" : true
}
}
}
</ceci-definition>
&nbsp; &quot; entities! &quot; &lt; &gt;
<span>é</span>
</template>
<script>
Polymer('x-import');
</script>
<script type="application/fbs">
WHAT GOES HERE?
</script>
</polymer-element>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vulcanizer Test</title>
<script src="bower_components/platform/platform.js"></script>
<link rel="import" href="absolutes.html">
<link rel="import" href="import-test.html" />
<link rel="stylesheet" href="empty.css">
</head>
<body>
<x-import>Hello Import!</x-import>
<y-import></y-import>
<polymer-element name="x-foo" attributes="">
<script>Polymer()</script>
</polymer-element>
<polymer-element name="x-bar" attributes="">
<script>Polymer({})</script>
</polymer-element>
<script>
console.log('&lt;hi&gt;');
</script>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="y-import">
<template>
<style>
:host{
background: url(../foo.jpg);
}
</style>
<a href="mailto:azakus@users.noreply.github.com">REPORT A BUG</a>
</template>
<script>
Polymer('y-import');
</script>
</polymer-element>

View File

@@ -0,0 +1,32 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
var JS = 'script:not([type]), script[type="text/javascript"]';
var URL_ATTR = ['href', 'src', 'action', 'style'];
module.exports = {
EOL: require('os').EOL,
ELEMENTS: 'polymer-element:not([assetpath])',
ELEMENTS_NOSCRIPT: 'polymer-element[noscript]',
ABS_URL: /(^data:)|(^http[s]?:)|(^\/)|(^mailto:)/,
REMOTE_ABS_URL: /(^http[s]?\:)|(^\/\/)/,
IMPORTS: 'link[rel="import"][href]',
URL: /url\([^)]*\)/g,
URL_ATTR: URL_ATTR,
URL_ATTR_SEL: '[' + URL_ATTR.join('],[') + ']',
URL_TEMPLATE: '{{.*}}',
JS: JS,
JS_SRC: JS.split(',').map(function(s){ return s + '[src]'; }).join(','),
JS_INLINE: JS.split(',').map(function(s) { return s + ':not([src])'; }).join(','),
CSS: 'style:not([type]), style[type="text/css"]',
// Output match is [ 'Polymer(', NAME_OF_ELEMENT OR undefined, ',', { or ) ]
POLYMER_INVOCATION: /Polymer\(([^,{]+)?(,\s*)?({|\))/,
NEOPRENE_INVOCATION: /name:\s*['"]([^'"]*)['"]/
};

View File

@@ -0,0 +1,104 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
var fs = require('fs');
var path = require('path');
var CONSTANTS = require('./constants.js');
var ABS_URL = CONSTANTS.ABS_URL;
var REMOTE_ABS_URL = CONSTANTS.REMOTE_ABS_URL;
var DEFAULT = 'vulcanized.html';
// validate options with boolean return
function processOptions(options, callback) {
var config = {};
var excludes = {
imports: [],
scripts: [],
styles: []
};
options = options || Object.create(null);
if (options.config) {
var configBlob;
try {
// TODO(dfreedm): Make this async
configBlob = fs.readFileSync(options.config, 'utf8');
} catch(e) {
return callback('Config file not found!');
}
try {
config = JSON.parse(configBlob);
} catch(e) {
return callback('Malformed config JSON!');
}
}
options.input = options.input || config.input;
if (!options.input) {
return callback('No input file given!');
}
options.excludes = options.excludes || config.excludes;
if (options.excludes) {
var e = options.excludes;
try {
if (e.imports) {
e.imports.forEach(function(r) {
excludes.imports.push(new RegExp(r));
});
}
if (e.scripts) {
e.scripts.forEach(function(r) {
excludes.scripts.push(new RegExp(r));
});
}
if (e.styles) {
e.styles.forEach(function(r) {
excludes.styles.push(new RegExp(r));
});
}
} catch(_) {
return callback('Malformed import exclude config');
}
}
options.output = options.output || config.output;
if (!options.output) {
options.output = path.resolve(path.dirname(options.input), DEFAULT);
}
options.outputDir = path.dirname(options.output);
options.csp = options.csp || config.csp;
if (options.csp) {
options.csp = options.output.replace(/\.html$/, '.js');
}
options.abspath = options.abspath || config.abspath;
if (options.abspath) {
options.abspath = path.resolve(options.abspath);
excludes.imports.push(REMOTE_ABS_URL);
excludes.scripts.push(REMOTE_ABS_URL);
excludes.styles.push(REMOTE_ABS_URL);
} else {
excludes.imports.push(ABS_URL);
excludes.scripts.push(ABS_URL);
excludes.styles.push(ABS_URL);
}
options.excludes = excludes;
options.keepExcludes = options['strip-excludes'] === false || config['strip-excludes'] === false;
callback(null, options);
}
exports.processOptions = processOptions;

View File

@@ -0,0 +1,84 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
var path = require('path');
var constants = require('./constants.js');
var utils = require('./utils.js');
var setTextContent = utils.setTextContent;
var getTextContent = utils.getTextContent;
function resolvePaths($, input, output, abspath) {
var assetPath;
if (abspath) {
assetPath = rebasePath(input, abspath);
} else {
assetPath = path.relative(output, input);
}
// make sure assetpath is a folder, but not root!
if (assetPath) {
assetPath = utils.unixPath(assetPath) + '/';
}
// resolve attributes
$(constants.URL_ATTR_SEL).each(function() {
var el = $(this);
constants.URL_ATTR.forEach(function(a) {
var val = el.attr(a);
if (val) {
if (val.search(constants.URL_TEMPLATE) < 0) {
if (a === 'style') {
el.attr(a, rewriteURL(input, output, val, abspath));
} else {
el.attr(a, rewriteRelPath(input, output, val, abspath));
}
}
}
});
});
$(constants.CSS).each(function() {
var el = $(this);
var text = rewriteURL(input, output, getTextContent(el), abspath);
setTextContent(el, text);
});
$(constants.ELEMENTS).each(function() {
$(this).attr('assetpath', assetPath);
});
}
function rebasePath(absolutePath, baselinePath) {
var absBase = new RegExp('^' + utils.escapeForRegExp(baselinePath));
return absolutePath.replace(absBase, '');
}
function rewriteRelPath(inputPath, outputPath, rel, abspath) {
if (constants.ABS_URL.test(rel)) {
return rel;
}
var abs = path.resolve(inputPath, rel);
if (abspath) {
return utils.unixPath(rebasePath(abs, abspath));
}
var relPath = path.relative(outputPath, abs);
return utils.unixPath(relPath);
}
function rewriteURL(inputPath, outputPath, cssText, abspath) {
return cssText.replace(constants.URL, function(match) {
var path = match.replace(/["']/g, "").slice(4, -1);
path = rewriteRelPath(inputPath, outputPath, path, abspath);
return 'url("' + path + '")';
});
}
exports.resolvePaths = resolvePaths;
exports.rewriteRelPath = rewriteRelPath;
exports.rewriteURL = rewriteURL;

View File

@@ -0,0 +1,63 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
var path = require('path');
module.exports = {
// directly update the textnode child of <style>
// equivalent to <style>.textContent
setTextContent: function(node, text) {
var unwrapped = node.get(0);
var child = unwrapped.children[0];
if (child) {
child.data = text;
} else {
unwrapped.children[0] = {
data: text,
type: 'text',
next: null,
prev: null,
parent: unwrapped
};
}
},
getTextContent: function(node) {
var unwrapped = node.get(0);
var child = unwrapped.children[0];
return child ? child.data : '';
},
// escape a string to be used in new RegExp
escapeForRegExp: function(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
},
unixPath: function(inpath, optSep) {
var sep = optSep || path.sep;
if (sep !== '/') {
inpath = inpath.split(sep).join('/');
}
return inpath;
},
processPolymerInvocation: function(elementName, invocation) {
var name = invocation[1] || '';
var split = invocation[2] || '';
var trailing = invocation[3];
var nameIsString = /^['"]/.test(name);
if (!split) {
// assume "name" is actually the prototype if it is not a string literal
if (!name || (name && !nameIsString)) {
trailing = name + trailing;
name = '\'' + elementName + '\'';
}
if (trailing !== ')') {
split = ',';
}
}
return 'Polymer(' + name + split + trailing;
}
};

View File

@@ -0,0 +1,308 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
// jshint node: true
var cleancss = require('clean-css');
var fs = require('fs');
var path = require('path');
var uglify = require('uglify-js');
var url = require('url');
var whacko = require('whacko');
var constants = require('./constants.js');
var optparser = require('./optparser.js');
var pathresolver = require('./pathresolver');
var utils = require('./utils');
var setTextContent = utils.setTextContent;
var getTextContent = utils.getTextContent;
var read = {};
var options = {};
// validate options with boolean return
function setOptions(optHash, callback) {
optparser.processOptions(optHash, function(err, o) {
if (err) {
return callback(err);
}
options = o;
callback();
});
}
function exclude(regexes, href) {
return regexes.some(function(r) {
return r.test(href);
});
}
function excludeImport(href) {
return exclude(options.excludes.imports, href);
}
function excludeScript(href) {
return exclude(options.excludes.scripts, href);
}
function excludeStyle(href) {
return exclude(options.excludes.styles, href);
}
function readFile(file) {
var content = fs.readFileSync(file, 'utf8');
return content.replace(/^\uFEFF/, '');
}
// inline relative linked stylesheets into <style> tags
function inlineSheets($, inputPath, outputPath) {
$('link[rel="stylesheet"]').each(function() {
var el = $(this);
var href = el.attr('href');
if (href && !excludeStyle(href)) {
var rel = href;
var inputPath = path.dirname(options.input);
if (constants.ABS_URL.test(rel)) {
var abs = path.resolve(inputPath, path.join(options.abspath, rel));
rel = path.relative(options.outputDir, abs);
}
var filepath = path.resolve(options.outputDir, rel);
// fix up paths in the stylesheet to be relative to the location of the style
var content = pathresolver.rewriteURL(path.dirname(filepath), outputPath, readFile(filepath));
var styleEl = whacko('<style>' + content + '</style>');
// clone attributes
styleEl.attr(el.attr());
// don't set href or rel on the <style>
styleEl.attr('href', null);
styleEl.attr('rel', null);
el.replaceWith(whacko.html(styleEl));
}
});
}
function inlineScripts($, dir) {
$(constants.JS_SRC).each(function() {
var el = $(this);
var src = el.attr('src');
if (src && !excludeScript(src)) {
var rel = src;
var inputPath = path.dirname(options.input);
if (constants.ABS_URL.test(rel)) {
var abs = path.resolve(inputPath, path.join(options.abspath, rel));
rel = path.relative(options.outputDir, abs);
}
var filepath = path.resolve(dir, rel);
var content = readFile(filepath);
// NOTE: reusing UglifyJS's inline script printer (not exported from OutputStream :/)
content = content.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
el.replaceWith('<script>' + content + '</script>');
}
});
}
function concat(filename) {
if (!read[filename]) {
read[filename] = true;
var $ = whacko.load(readFile(filename));
var dir = path.dirname(filename);
pathresolver.resolvePaths($, dir, options.outputDir, options.abspath);
processImports($);
inlineSheets($, dir, options.outputDir);
return $;
} else if (options.verbose) {
console.log('Dependency deduplicated');
}
}
function processImports($, mainDoc) {
var bodyContent = [];
$(constants.IMPORTS).each(function() {
var el = $(this);
var href = el.attr('href');
if (!excludeImport(href)) {
var rel = href;
var inputPath = path.dirname(options.input);
if (constants.ABS_URL.test(rel)) {
var abs = path.resolve(inputPath, path.join(options.abspath, rel));
rel = path.relative(options.outputDir, abs);
}
var $$ = concat(path.resolve(options.outputDir, rel));
if (!$$) {
// remove import link
el.remove();
return;
}
// append import document head to main document head
el.replaceWith($$('head').html());
var bodyHTML = $$('body').html();
// keep the ordering of the import body in main document, before main document's body
bodyContent.push(bodyHTML);
} else if (!options.keepExcludes) {
// if the path is excluded for being absolute, then the import link must remain
var absexclude = options.abspath ? constants.REMOTE_ABS_URL : constants.ABS_URL;
if (!absexclude.test(href)) {
el.remove();
}
}
});
// prepend the import document body contents to the main document, in order
var content = bodyContent.join('\n');
// hide import body content in the main document
if (mainDoc && content) {
content = '<div hidden>' + content + '</div>';
}
$('body').prepend(content);
}
function findScriptLocation($) {
var pos = $('body').last();
if (!pos.length) {
pos = $.root();
}
return pos;
}
// arguments are (index, node), where index is unnecessary
function isCommentOrEmptyTextNode(_, node) {
if (node.type === 'comment'){
return true;
} else if (node.type === 'text') {
// return true if the node is only whitespace
return !((/\S/).test(node.data));
}
}
function compressJS(content, inline) {
var ast = uglify.parse(content);
return ast.print_to_string({inline_script: inline});
}
function removeCommentsAndWhitespace($) {
$(constants.JS_INLINE).each(function() {
var el = $(this);
var content = getTextContent(el);
setTextContent(el, compressJS(content, true));
});
$(constants.CSS).each(function() {
var el = $(this);
var content = getTextContent(el);
setTextContent(el, new cleancss({noAdvanced: true}).minify(content));
});
$('*').contents().filter(isCommentOrEmptyTextNode).remove();
}
function writeFileSync(filename, data, eop) {
if (!options.outputSrc) {
fs.writeFileSync(filename, data, 'utf8');
} else {
options.outputSrc(filename, data, eop);
}
}
function handleMainDocument() {
// reset shared buffers
read = {};
var content = options.inputSrc ? options.inputSrc.toString() : readFile(options.input);
var $ = whacko.load(content);
var dir = path.dirname(options.input);
pathresolver.resolvePaths($, dir, options.outputDir, options.abspath);
processImports($, true);
if (options.inline) {
inlineSheets($, dir, options.outputDir);
}
if (options.inline) {
inlineScripts($, options.outputDir);
}
$(constants.JS_INLINE).each(function() {
var el = $(this);
var content = getTextContent(el);
// find ancestor polymer-element node
var templateElement = el.prev('template').get(0);
if (templateElement) {
var match = constants.NEOPRENE_INVOCATION.exec(content);
var elementName = $(templateElement).attr('id', match[1]);
}
});
// strip noscript from elements, and instead inject explicit Polymer() invocation
// script, so registration order is preserved
$(constants.ELEMENTS_NOSCRIPT).each(function() {
var el = $(this);
var name = el.attr('name');
if (options.verbose) {
console.log('Injecting explicit Polymer invocation for noscript element "' + name + '"');
}
el.append('<script>Polymer(\'' + name + '\');</script>');
el.attr('noscript', null);
});
// strip scripts into a separate file
if (options.csp) {
if (options.verbose) {
console.log('Separating scripts into separate file');
}
// CSPify main page by removing inline scripts
var scripts = [];
$(constants.JS_INLINE).each(function() {
var el = $(this);
var content = getTextContent(el);
scripts.push(content);
el.remove();
});
// join scripts with ';' to prevent breakages due to EOF semicolon insertion
var scriptName = path.basename(options.output, '.html') + '.js';
var scriptContent = scripts.join(';' + constants.EOL);
if (options.strip) {
scriptContent = compressJS(scriptContent, false);
}
writeFileSync(path.resolve(options.outputDir, scriptName), scriptContent);
// insert out-of-lined script into document
findScriptLocation($).append('<script src="' + scriptName + '"></script>');
}
deduplicateImports($);
if (options.strip) {
removeCommentsAndWhitespace($);
}
writeFileSync(options.output, $.html(), true);
}
function deduplicateImports($) {
var imports = {};
$(constants.IMPORTS).each(function() {
var el = $(this);
var href = el.attr('href');
// TODO(dfreedm): allow a user defined base url?
var abs = url.resolve('http://', href);
if (!imports[abs]) {
imports[abs] = true;
} else {
if(options.verbose) {
console.log('Import Dependency deduplicated');
}
el.remove();
}
});
}
exports.processDocument = handleMainDocument;
exports.setOptions = setOptions;

View File

@@ -0,0 +1,41 @@
{
"name": "vulcanize",
"version": "0.6.1",
"description": "Process Web Components into one output file",
"main": "lib/vulcan.js",
"bin": {
"vulcanize": "bin/vulcanize"
},
"dependencies": {
"clean-css": "^2.2.11",
"nopt": "^3.0.1",
"uglify-js": "^2.4.15",
"whacko": "^0.17.1"
},
"devDependencies": {
"jshint": "^2.5.6",
"mocha": "^2.0.1"
},
"scripts": {
"test": "node_modules/.bin/jshint --verbose lib/*.js bin/* test/*.js && node_modules/.bin/mocha"
},
"author": "Daniel Freedman",
"license": "BSD",
"directories": {
"test": "test"
},
"keywords": [
"web components",
"polymer"
],
"repository": {
"type": "git",
"url": "git://github.com/Polymer/vulcanize.git"
},
"bugs": {
"url": "https://github.com/Polymer/vulcanize/issues"
},
"optionalDependencies": {
"update-notifier": "^0.2.2"
}
}

View File

@@ -0,0 +1,3 @@
{
input: "index.html",
}

View File

@@ -0,0 +1,6 @@
{
"csp": true,
"excludes": {
"imports": [".*"]
}
}

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="import" href="imports/simple-import.html">
<link rel="import" href="imports/simple-import.html">
<link rel="import" href="http://example.com/foo/bar.html">
<title>Sample Build</title>
</head>
<body>
<my-element></my-element>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<polymer-element name="my-element" noscript>
<template>
<link rel="stylesheet" href="simple-style.css" shim-shadowdom>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 630 630">
<rect x="0" y="0" width="630" height="630" fill="#3c790a"/>
<path d="m212 497c11"/>
<polygon />
<path />
<polygon/>
<path />
</svg>
</template>
</polymer-element>

View File

@@ -0,0 +1,14 @@
/*
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
:host([type="platform"]) { background-color: red; }
:host([type="core"]) { background-color: red; }
:host([type="elements"]) { background-color: red; }
polyfill-next-selector { content: ':host header'; }
polyfill-next-selector { content: 'I WIN'; }

View File

@@ -0,0 +1,2 @@
--ui tdd
--slow 300

View File

@@ -0,0 +1,451 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
// jshint node: true
var assert = require('assert');
var path = require('path');
assert.AssertionError.prototype.showDiff = true;
suite('constants', function() {
var constants = require('../lib/constants.js');
suite('URLs', function() {
test('absolute urls', function() {
var abs = constants.ABS_URL;
assert(abs.test('data:charset=utf8,'), 'data urls');
assert(abs.test('http://foo.com'), 'http');
assert(abs.test('https://foo.com'), 'https');
assert(abs.test('mailto:foo@bar.com'), 'mailto');
assert(abs.test('//foo.com'), 'protocol-free');
assert(abs.test('/components/'), '/');
assert(!abs.test('../foo/bar.html'), '../');
assert(!abs.test('bar.html'), 'sibling dependency');
});
test('remote absolute urls', function() {
var rabs = constants.REMOTE_ABS_URL;
assert(rabs.test('http://foo.com'), 'http');
assert(rabs.test('https://foo.com'), 'https');
assert(rabs.test('//foo.com'), 'protocol-free');
assert(!rabs.test('../foo/bar.html'), '../');
assert(!rabs.test('bar.html'), 'sibling dependency');
assert(!rabs.test('/components/'), '/');
});
test('CSS URLs', function() {
var url = constants.URL;
assert('url(foo.html)'.match(url), 'naked');
assert('url(\'foo.html\')'.match(url), 'single quote');
assert('url("foo.html")'.match(url), 'double quote');
});
});
test('Polymer Invocation', function() {
var polymer = constants.POLYMER_INVOCATION;
function test(invocation, msg) {
var matches = polymer.exec(invocation);
assert(matches, 'polymer invocation found', msg);
}
test('Polymer(\'core-input\', {})', 'full');
test('Polymer(\'core-input\')', 'name-only');
test('Polymer()', 'none');
test('Polymer({})', 'object-only');
test('Polymer(p)', 'indirect');
});
});
suite('Path Resolver', function() {
var pathresolver = require('../lib/pathresolver.js');
var inputPath = '/foo/bar/my-element';
var outputPath = '/foo/bar';
test('Rewrite URLs', function() {
var css = [
'x-element {',
' background-image: url(foo.jpg);',
'}',
'x-bar {',
' background-image: url(data:xxxxx);',
'}',
'x-quuz {',
' background-image: url(\'https://foo.bar/baz.jpg\');',
'}',
].join('\n');
var expected = [
'x-element {',
' background-image: url("my-element/foo.jpg");',
'}',
'x-bar {',
' background-image: url("data:xxxxx");',
'}',
'x-quuz {',
' background-image: url("https://foo.bar/baz.jpg");',
'}',
].join('\n');
var actual = pathresolver.rewriteURL(inputPath, outputPath, css);
assert.equal(actual, expected);
});
test('Rewrite Paths', function() {
function testPath(val, expected, abs, msg) {
var actual = pathresolver.rewriteRelPath(inputPath, outputPath, val, abs);
assert.equal(actual, expected, msg);
}
testPath('biz.jpg', 'my-element/biz.jpg', null, 'local');
testPath('http://foo/biz.jpg', 'http://foo/biz.jpg', null, 'remote');
testPath('biz.jpg', 'bar/my-element/biz.jpg', '/foo/', 'build path');
});
test('Resolve Paths', function() {
var html = [
'<link rel="import" href="../polymer/polymer.html">',
'<link rel="stylesheet" href="my-element.css">',
'<polymer-element name="my-element">',
'<template>',
'<style>:host { background-image: url(background.svg); }</style>',
'<script>Polymer()</script>',
'</template>',
'</polymer-element>'
].join('\n');
var expected = [
'<html><head><link rel="import" href="polymer/polymer.html">',
'<link rel="stylesheet" href="my-element/my-element.css">',
'</head><body><polymer-element name="my-element" assetpath="my-element/">',
'<template>',
'<style>:host { background-image: url("my-element/background.svg"); }</style>',
'<script>Polymer()</script>',
'</template>',
'</polymer-element></body></html>'
].join('\n');
var expected2 = [
'<html><head><link rel="import" href="/bar/polymer/polymer.html">',
'<link rel="stylesheet" href="/bar/my-element/my-element.css">',
'</head><body><polymer-element name="my-element" assetpath="/bar/my-element/">',
'<template>',
'<style>:host { background-image: url("/bar/my-element/background.svg"); }</style>',
'<script>Polymer()</script>',
'</template>',
'</polymer-element></body></html>'
].join('\n');
var actual;
var whacko = require('whacko');
var $ = whacko.load(html);
pathresolver.resolvePaths($, inputPath, outputPath);
actual = $.html();
assert.equal(actual, expected, 'relative');
$ = whacko.load(html);
pathresolver.resolvePaths($, inputPath, outputPath, '/foo');
actual = $.html();
assert.equal(actual, expected2, 'absolute');
});
});
suite('Utils', function() {
var constants = require('../lib/constants.js');
var utils = require('../lib/utils.js');
test('getTextContent', function() {
var whacko = require('whacko');
var divEl = whacko('<div>some text!</div>');
assert.equal(utils.getTextContent(divEl), 'some text!', 'a textnode child');
var blankEl = whacko('<div></div>');
assert.equal(utils.getTextContent(blankEl), '', 'no textnode children');
});
test('setTextContent', function() {
var whacko = require('whacko');
var divEl = whacko('<div></div>');
utils.setTextContent(divEl, 'some text!');
assert.equal(utils.getTextContent(divEl), 'some text!', 'create text node');
utils.setTextContent(divEl, 'some text 2!');
assert.equal(utils.getTextContent(divEl), 'some text 2!', 'override text node');
});
test('unixPath', function() {
var pp = ['foo', 'bar', 'baz'];
var p = pp.join('/');
var actual = utils.unixPath(p);
assert.equal(actual, p, 'started unix');
var p2 = pp.join('\\');
actual = utils.unixPath(p2, '\\');
assert.equal(actual, p, 'windows path');
});
test('escapeForRegExp', function() {
var actual = utils.escapeForRegExp('foo-bar');
assert.equal(actual, 'foo\\-bar', 'element name');
actual = utils.escapeForRegExp('foo/bar/baz');
assert.equal(actual, 'foo\\/bar\\/baz', 'absolute path');
});
test('Polymer Invocation', function() {
var polymer = constants.POLYMER_INVOCATION;
function test(invocation, expected, msg) {
var matches = polymer.exec(invocation);
assert(matches, 'polymer invocation found');
var replacement = utils.processPolymerInvocation('core-input', matches);
var actual = invocation.replace(matches[0], replacement);
assert.strictEqual(actual, expected, msg);
}
test('Polymer(\'core-input\', {})', 'Polymer(\'core-input\', {})', 'full');
test('Polymer(\'core-input\')', 'Polymer(\'core-input\')', 'name-only');
test('Polymer()', 'Polymer(\'core-input\')', 'none');
test('Polymer({})', 'Polymer(\'core-input\',{})', 'object-only');
test('Polymer(p)', 'Polymer(\'core-input\',p)', 'indirect');
});
test('#82', function() {
var constants = require('../lib/constants.js');
var whacko = require('whacko');
var $ = whacko.load('<polymer-element name="paper-button-base"><script>(function(){ Polymer(p);}()</script></polymer-element>');
$(constants.JS_INLINE).each(function() {
var el = $(this);
var content = utils.getTextContent(el);
assert(content);
var parentElement = el.closest('polymer-element').get(0);
if (parentElement) {
var match = constants.POLYMER_INVOCATION.exec(content);
var elementName = $(parentElement).attr('name');
if (match) {
var invocation = utils.processPolymerInvocation(elementName, match);
content = content.replace(match[0], invocation);
utils.setTextContent(el, content);
}
}
assert.equal(utils.getTextContent(el), '(function(){ Polymer(\'paper-button-base\',p);}()');
});
});
});
suite('Optparser', function() {
var path = require('path');
var optParser = require('../lib/optparser.js');
var constants = require('../lib/constants.js');
var ABS_URL = constants.ABS_URL;
var REMOTE_ABS_URL = constants.REMOTE_ABS_URL;
function optParserTest(fn, opts, skipFail) {
if (typeof opts === 'undefined') {
opts = {input: path.resolve('index.html')};
}
optParser.processOptions(opts, function(err, options) {
if (!skipFail) {
assert.equal(err, null);
}
fn(err, options);
});
}
test('Error on no input', function(done) {
optParserTest(function(err, options) {
assert.equal(err, 'No input file given!');
done();
}, null, true);
});
test('Defaults', function(done) {
optParserTest(function(err, options) {
assert.equal(options.input, path.resolve('index.html'));
assert.equal(options.output, path.resolve('vulcanized.html'));
assert.equal(options.outputDir, path.dirname(path.resolve('vulcanized.html')));
assert(!options.csp);
assert(!options.abspath);
assert.deepEqual(options.excludes, {imports:[ABS_URL], scripts:[ABS_URL], styles:[ABS_URL]});
done();
});
});
test('CSP', function(done) {
optParserTest(function(err, options) {
assert.equal(options.csp, path.resolve('vulcanized.js'));
done();
}, {input: 'index.html', csp: true});
});
test('output', function(done) {
optParserTest(function(err, options) {
assert.equal(options.output, path.resolve('build.html'));
assert.equal(options.csp, path.resolve('build.js'));
done();
}, {input: path.resolve('index.html'), output: path.resolve('build.html'), csp: true});
});
test('abspath', function(done) {
optParserTest(function(err, options) {
assert.equal(options.abspath, path.resolve('../'));
assert.deepEqual(options.excludes, {imports:[REMOTE_ABS_URL], scripts:[REMOTE_ABS_URL], styles:[REMOTE_ABS_URL]});
done();
}, {input: path.resolve('index.html'), abspath: path.resolve('../')});
});
test('excludes', function(done) {
var excludes = {
imports: [
'.*'
]
};
var expected = [/.*/, ABS_URL];
optParserTest(function(err, options) {
assert.deepEqual(options.excludes.imports, expected);
done();
}, {input: path.resolve('index.html'), excludes: excludes});
});
test('config file', function(done) {
optParserTest(function(err, options) {
assert.equal(options.input, path.resolve('index.html'));
assert.equal(options.output, path.resolve('build.html'));
assert.equal(options.csp, path.resolve('build.js'));
assert(!options.abspath);
assert.deepEqual(options.excludes, {imports:[/.*/, ABS_URL], scripts:[ABS_URL], styles:[ABS_URL]});
done();
}, {config: path.resolve('test/config.json'), input: path.resolve('index.html'), output: path.resolve('build.html'), csp: true});
});
test('report broken config file', function(done) {
optParserTest(function(err, options) {
assert.equal(err, 'Malformed config JSON!');
done();
}, {config: path.resolve('test/broken_config.json')}, true);
});
});
suite('Vulcan', function() {
var vulcan = require('../lib/vulcan.js');
var outputPath = path.resolve('test/html/actual.html');
var inputPath = path.resolve('test/html/default.html');
test('set options', function(done) {
var options = {
input: 'index.html'
};
vulcan.setOptions(options, done);
});
function process(options, fn) {
var outputs = Object.create(null);
options.outputSrc = function(name, data, eop) {
if (!data) {
throw new Error("Writing empty data");
}
outputs[name] = data;
};
vulcan.setOptions(options, function(err) {
assert(!err);
vulcan.processDocument();
Object.keys(outputs).forEach(function(o) {
assert.equal(typeof outputs[o], 'string', 'all buffers are closed');
});
fn(outputs);
});
}
test('defaults', function(done) {
var getTextContent = require('../lib/utils.js').getTextContent;
process({input: inputPath, output: outputPath}, function(outputs) {
assert.equal(Object.keys(outputs).length, 1);
var vulcanized = outputs[outputPath];
assert(vulcanized);
var $ = require('whacko').load(vulcanized);
assert.equal($('body > div[hidden]').length, 1, 'only one div[hidden]');
assert.equal($('head > link[rel="import"]:not([href^="http://"])').length, 0, 'all relative imports removed');
assert.equal($('polymer-element').length, 1, 'imports were deduplicated');
assert.equal($('polymer-element').attr('noscript'), null, 'noscript removed');
assert.equal(getTextContent($('polymer-element > script')), 'Polymer(\'my-element\');', 'polymer script included');
assert.equal($('polymer-element > template > link').length, 0, 'external styles removed');
assert.equal($('polymer-element > template > style').length, 1, 'styles inlined');
assert.equal($('polymer-element > template > svg > *').length, 6, 'svg children propery nested');
assert.equal($('polymer-element').attr('assetpath'), 'imports/', 'assetpath set');
done();
});
});
test('CSP', function(done) {
process({input: inputPath, output: outputPath, csp: true}, function(outputs) {
assert.equal(Object.keys(outputs).length, 2);
var vulcanized = outputs[outputPath];
var vulcanizedJS = outputs[path.resolve(outputPath, '../actual.js')];
assert(vulcanized);
assert(vulcanizedJS);
var $ = require('whacko').load(vulcanized);
assert($('body > script[src="actual.js"]'), 'vulcanized script in body');
assert.equal($('body script:not([src])').length, 0, 'inline scripts removed');
assert.equal(vulcanizedJS, 'Polymer(\'my-element\');', 'csp element script');
done();
});
});
test('exclude', function(done) {
var i = 3;
function reallyDone() {
if (--i === 0) {
done();
}
}
process({input: inputPath, output: outputPath, excludes: {imports: ['simple-import']}}, function(outputs) {
var vulcanized = outputs[outputPath];
assert(vulcanized);
var $ = require('whacko').load(vulcanized);
assert.equal($('head > link[href="imports/simple-import.html"]').length, 0, 'import excluded');
assert.equal($('head > link[rel="stylesheet"][href="imports/simple-style.css"]').length, 0, 'import content excluded');
assert.equal($('head > link[href="http://example.com/foo/bar.html"]').length, 1, 'external import is not excluded');
reallyDone();
});
process({input: inputPath, output: outputPath, excludes: {styles: ['simple-style']}}, function(outputs) {
var vulcanized = outputs[outputPath];
assert(vulcanized);
var $ = require('whacko').load(vulcanized);
assert.equal($('polymer-element[name="my-element"] > template > link[href="imports/simple-style.css"]').length, 1, 'style excluded');
reallyDone();
});
process({input: inputPath, output: outputPath, excludes: {imports: ['simple-import']}, 'strip-excludes': false}, function(outputs) {
var vulcanized = outputs[outputPath];
assert(vulcanized);
var $ = require('whacko').load(vulcanized);
assert.equal($('link[href="imports/simple-import.html"]').length, 1, 'excluded import not stripped');
reallyDone();
});
});
});

View File

@@ -0,0 +1,23 @@
#!/bin/bash
#
# @license
# Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
# This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
# The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
# The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
# Code distributed by Google as part of the polymer project is also
# subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
#
# tags sorted semver style
TAGS=($(git tag -l | sort -k1,1r -k2,2r -k3,3r -t.))
TO=(${TAGS[@]})
FROM=(${TAGS[@]:1})
FROM+=(`git rev-list --max-parents=0 HEAD`)
for i in ${!FROM[@]}; do
echo "### ${TO[$i]}"
git log ${FROM[$i]}..${TO[$i]} --pretty="- %s"
done

Some files were not shown because too many files have changed in this diff Show More