Skip to content
Browse files

Added todo list.

  • Loading branch information...
0 parents commit cf52af554e350924a03514461cfaeaa30a50b561 @borismus committed
Showing with 470 additions and 0 deletions.
  1. +6 −0 todo/README.md
  2. +226 −0 todo/components-polyfill.js
  3. +29 −0 todo/index.html
  4. +37 −0 todo/news-component.html
  5. +48 −0 todo/reset.css
  6. +52 −0 todo/todo-item-component.html
  7. +72 −0 todo/todo-list-component.html
6 todo/README.md
@@ -0,0 +1,6 @@
+# A canonical TODO MVC sample built with Web Components
+
+Components:
+
+- todo list (w/ input box, items remaining)
+- todo item
226 todo/components-polyfill.js
@@ -0,0 +1,226 @@
+(function(scope) {
+
+var scope = scope || {};
+
+var SCRIPT_SHIM = ['(function(){\n', 1, '\n}).call(this.element);'];
+
+if (!window.WebKitShadowRoot) {
+ console.error('Shadow DOM support is required.');
+ return;
+}
+
+
+scope.HTMLElementElement = function(name, tagName, declaration) {
+ this.name = name;
+ this.extends = tagName;
+ this.lifecycle = this.lifecycle.bind(declaration);
+}
+
+scope.HTMLElementElement.prototype = {
+ __proto__: HTMLElement.prototype,
+ lifecycle: function(dict) {
+ this.created = dict.created || nil;
+ this.inserted = dict.inserted || nil;
+ this.attributeChanged = dict.attributeChanged || nil;
+
+ // TODO: Implement remove lifecycle methods.
+ //this.removed = dict.removed || nil;
+ }
+};
+
+
+scope.Declaration = function(name, tagName) {
+ this.elementPrototype = Object.create(this.prototypeFromTagName(tagName));
+ this.element = new scope.HTMLElementElement(name, tagName, this);
+ this.element.generatedConstructor = this.generateConstructor();
+ // Hard-bind the following methods to "this":
+ this.morph = this.morph.bind(this);
+}
+
+scope.Declaration.prototype = {
+
+ generateConstructor: function() {
+ var tagName = this.element.extends;
+ var created = this.created;
+ var extended = function() {
+ var element = document.createElement(tagName);
+ extended.prototype.__proto__ = element.__proto__;
+ element.__proto__ = extended.prototype;
+ created.call(element);
+ }
+ extended.prototype = this.elementPrototype;
+ return extended;
+ },
+
+ evalScript: function(script) {
+ //FIXME: Add support for external js loading.
+ SCRIPT_SHIM[1] = script.textContent;
+ eval(SCRIPT_SHIM.join(''));
+ },
+
+ addTemplate: function(template) {
+ this.template = template;
+ },
+
+ morph: function(element) {
+ // FIXME: We shouldn't be updating __proto__ like this on each morph.
+ this.element.generatedConstructor.prototype.__proto__ = document.createElement(this.element.extends);
+ element.__proto__ = this.element.generatedConstructor.prototype;
+ var shadowRoot = this.createShadowRoot(element);
+
+ // Fire created event.
+ this.created && this.created.call(element, shadowRoot);
+ this.inserted && this.inserted.call(element, shadowRoot);
+
+ // Setup mutation observer for attribute changes.
+ if (this.attributeChanged) {
+ var observer = new WebKitMutationObserver(function(mutations) {
+ mutations.forEach(function(m) {
+ this.attributeChanged(m.attributeName, m.oldValue,
+ m.target.getAttribute(m.attributeName));
+ }.bind(this));
+ }.bind(this));
+
+ // TOOD: spec isn't clear if it's changes to the custom attribute
+ // or any attribute in the subtree.
+ observer.observe(shadowRoot.host, {
+ attributes: true,
+ attributeOldValue: true
+ });
+ }
+ },
+
+ createShadowRoot: function(element) {
+ if (!this.template) {
+ return;
+ }
+
+ var shadowRoot = new WebKitShadowRoot(element);
+ [].forEach.call(this.template.childNodes, function(node) {
+ shadowRoot.appendChild(node.cloneNode(true));
+ });
+
+ return shadowRoot;
+ },
+
+ prototypeFromTagName: function(tagName) {
+ return Object.getPrototypeOf(document.createElement(tagName));
+ }
+}
+
+
+scope.DeclarationFactory = function() {
+ // Hard-bind the following methods to "this":
+ this.createDeclaration = this.createDeclaration.bind(this);
+}
+
+scope.DeclarationFactory.prototype = {
+ // Called whenever each Declaration instance is created.
+ oncreate: null,
+
+ createDeclaration: function(element) {
+ var name = element.getAttribute('name');
+ if (!name) {
+ // FIXME: Make errors more friendly.
+ console.error('name attribute is required.')
+ return;
+ }
+ var tagName = element.getAttribute('extends');
+ if (!tagName) {
+ // FIXME: Make it work with any element.
+ // FIXME: Make errors more friendly.
+ console.error('extends attribute is required.');
+ return;
+ }
+ var constructorName = element.getAttribute('constructor');
+ var declaration = new scope.Declaration(name, tagName, constructorName);
+ if (constructorName) {
+ window[constructorName] = declaration.element.generatedConstructor;
+ }
+
+ [].forEach.call(element.querySelectorAll('script'), declaration.evalScript,
+ declaration);
+ var template = element.querySelector('template');
+ template && declaration.addTemplate(template);
+ this.oncreate && this.oncreate(declaration);
+ }
+}
+
+
+scope.Parser = function() {
+ this.parse = this.parse.bind(this);
+}
+
+scope.Parser.prototype = {
+ // Called for each element that's parsed.
+ onparse: null,
+
+ parse: function(string) {
+ var doc = document.implementation.createHTMLDocument();
+ doc.body.innerHTML = string;
+ [].forEach.call(doc.querySelectorAll('element'), function(element) {
+ this.onparse && this.onparse(element);
+ }, this);
+ }
+}
+
+
+scope.Loader = function() {
+ this.start = this.start.bind(this);
+}
+
+scope.Loader.prototype = {
+ // Called for each loaded declaration.
+ onload: null,
+ onerror: null,
+
+ start: function() {
+ [].forEach.call(document.querySelectorAll('link[rel=components]'), function(link) {
+ this.load(link.href);
+ }, this);
+ },
+
+ load: function(url) {
+ var request = new XMLHttpRequest();
+ var loader = this;
+
+ request.open('GET', url);
+ request.addEventListener('readystatechange', function(e) {
+ if (request.readyState === 4) {
+ if (request.status >= 200 && request.status < 300 || request.status === 304) {
+ loader.onload && loader.onload(request.response);
+ } else {
+ loader.onerror && loader.onerror(request.status, request);
+ }
+ }
+ });
+ request.send();
+ }
+}
+
+scope.run = function() {
+ var loader = new scope.Loader();
+ document.addEventListener('DOMContentLoaded', loader.start);
+ var parser = new scope.Parser();
+ loader.onload = parser.parse;
+ loader.onerror = function(status, resp) {
+ console.error("Unable to load component: Status " + status + " - " +
+ resp.statusText);
+ };
+
+ var factory = new scope.DeclarationFactory();
+ parser.onparse = factory.createDeclaration;
+ factory.oncreate = function(declaration) {
+ [].forEach.call(document.querySelectorAll(
+ declaration.element.extends + '[is=' + declaration.element.name +
+ ']'), declaration.morph);
+ }
+}
+
+if (!scope.runManually) {
+ scope.run();
+}
+
+function nil() {}
+
+})(window.__exported_components_polyfill_scope__);
29 todo/index.html
@@ -0,0 +1,29 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Simple Web Components Example</title>
+ <link rel="components" href="todo-item-component.html">
+ <link rel="components" href="todo-list-component.html">
+ <script src="components-polyfill.js"></script>
+ <style>
+ /** This needs to go inside the main CSS file. */
+ ul { margin: 0; padding: 0; }
+ li { margin: 0; padding: 0; list-style-type: none; }
+ </style>
+</head>
+<body>
+
+ <div class="container">
+ <h1>Todos</h1>
+ <h2>(now with Web Components)</h2>
+
+ <ul is="todo-list">
+ <li is="todo-item">Create a TodoMVC template</li>
+ <li is="todo-item">Eat your lunch</li>
+ </ul>
+
+ </div>
+
+</body>
+</html>
37 todo/news-component.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>News Component</title>
+ <link rel="stylesheet" type="text/css" href="../../src/debug.css">
+</head>
+<body>
+<element name="news" extends="ul">
+ <template>
+ <style scoped>
+ div.breaking {
+ color: Red;
+ font-size: 20px;
+ border: 1px dashed Purple;
+ }
+ div.other {
+ padding: 2px 0 0 0;
+ border: 1px solid Cyan;
+ }
+ </style>
+
+ <div class="breaking">
+ <h2>Breaking Stories</h2>
+ <ul>
+ <content select=".breaking"></content>
+ </ul>
+ </div>
+ <div class="other">
+ <h2>Other News</h2>
+ <ul>
+ <content></content>
+ </ul>
+ </div>
+ </template>
+</element>
+</body>
+</html>
48 todo/reset.css
@@ -0,0 +1,48 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+v2.0 | 20110126
+License: none (public domain)
+ */
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}-
52 todo/todo-item-component.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Todo Item Component</title>
+ </head>
+ <body>
+ <element name="todo-item" extends="li" constructor="TodoItem">
+ <template>
+ <style scoped>
+ * {
+ list-bullet-style: none;
+ }
+ :root {
+ padding: 12px 20px 11px 0;
+ position: relative;
+ font-size: 24px;
+ line-height: 1.1em;
+ border-bottom: 1px solid #cccccc;
+ }
+ :root:after {
+ content: "\0020";
+ display: block;
+ height: 0;
+ clear: both;
+ overflow: hidden;
+ visibility: hidden;
+ }
+ </style>
+
+ <div class="todo">
+ <div class="display">
+ <input class="check" type="checkbox">
+ <label class="todo-content"><content></content></label>
+ <span class="todo-destroy"></span>
+ </div>
+ </div>
+ </template>
+ <script>
+ var root = null;
+ this.lifecycle({
+ created: function(r) {
+ root = r;
+ },
+ });
+
+ TodoItem.prototype.setTitle = function(title) {
+ root.querySelector('.todo-content').innerText = title;
+ };
+ </script>
+ </element>
+ </body>
+</html>
72 todo/todo-list-component.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Todo Component</title>
+ <link rel="stylesheet" href="todo-list-component.css">
+ </head>
+ <body>
+ <element name="todo-list" extends="ul" constructor="TodoList">
+ <template>
+ <style scoped>
+ ul { margin: 0; padding: 0; }
+ #create-todo {
+ position: relative;
+ }
+ #create-todo input {
+ width: 466px;
+ font-size: 24px;
+ font-family: inherit;
+ line-height: 1.4em;
+ border: 0;
+ outline: none;
+ padding: 6px;
+ border: 1px solid #999999;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ }
+
+ #create-todo span {
+ position: absolute;
+ z-index: 999;
+ width: 170px;
+ left: 50%;
+ margin-left: -85px;
+ }
+
+ #todo-list {
+ margin-top: 10px;
+ }
+ </style>
+
+ <div id="create-todo">
+ <input id="new-todo" placeholder="What needs to be done?" type="text">
+ <span class="ui-tooltip-top" style="display:none;">Press Enter to save this task</span>
+ </div>
+ <div id="todos">
+ <input class="check mark-all-done" type="checkbox">
+ <label for="check-all">Mark all as complete</label>
+ <ul id="todo-list">
+ <content></content>
+ </ul>
+ </div>
+ </template>
+
+ <script>
+ var root = null;
+ this.lifecycle({
+ created: function(r) {
+ root = r;
+ }
+ });
+
+ TodoList.prototype.addItem = function(title) {
+ var item = new TodoItem();
+ item.setTitle(title);
+ root.appendChild(item);
+ };
+ </script>
+ </element>
+ </body>
+</html>

0 comments on commit cf52af5

Please sign in to comment.
Something went wrong with that request. Please try again.