Commit 11ba30e8 authored by Sam Saccone's avatar Sam Saccone

🌋 Polymer 1.0 functional changes

* Update for spec complience
* Polymer 0.5 -> 1 changes
parent d5970707
<link rel="import" href="../bower_components/polymer/polymer.html"> <link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="td-input" extends="input" on-keyup="{{keyupAction}}" on-keypress="{{keypressAction}}"> <dom-module id="td-input">
<script> <script>
(function() { (function() {
'use strict';
var ENTER_KEY = 13; var ENTER_KEY = 13;
var ESC_KEY = 27; var ESC_KEY = 27;
Polymer('td-input', { Polymer({
is: 'td-input',
extends: 'input',
listeners: {
'keyup': 'keyupAction',
'keypress': 'keypressAction'
},
keypressAction: function(e, detail, sender) { keypressAction: function(e, detail, sender) {
// Listen for enter on keypress but esc on keyup, because // Listen for enter on keypress but esc on keyup, because
// IE doesn't fire keyup for enter. // IE doesn't fire keyup for enter.
...@@ -21,4 +29,4 @@ ...@@ -21,4 +29,4 @@
}); });
})(); })();
</script> </script>
</polymer-element> </dom-module>
<link rel="import" href="../bower_components/polymer/polymer.html"> <link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="td-input.html"> <link rel="import" href="td-input.html">
<polymer-element name="td-item" extends="li" attributes="item editing" on-blur="{{commitAction}}"> <dom-module id="td-item">
<template> <template>
<div class="view {{ {completed: item.completed, editing: editing} | tokenList }}" hidden?="{{editing}}" on-dblclick="{{editAction}}"> <template is="dom-if" if="{{!editing}}">
<input type="checkbox" class="toggle" checked="{{item.completed}}" on-click="{{itemChangeAction}}"> <div on-dblclick="editAction">
<label>{{item.title}}</label> <input type="checkbox" class="toggle" checked="{{item.completed::change}}" on-click="itemChangeAction">
<button class="destroy" on-click="{{destroyAction}}"></button> <label>{{item.title}}</label>
</div> <button class="destroy" on-click="destroyAction"></button>
<input is="td-input" id="edit" class="edit" value="{{item.title}}" hidden?="{{!editing}}" on-td-input-commit="{{commitAction}}" on-td-input-cancel="{{cancelAction}}"> </div>
</template>
<template is="dom-if" if="{{editing}}">
<input is="td-input" id="edit" class="edit" value$="{{item.title}}" on-td-input-commit="commitAction" on-td-input-cancel="cancelAction" on-blur="onBlur">
</template>
</template> </template>
<script> <script>
(function() { (function() {
Polymer('td-item', { 'use strict';
editing: false,
Polymer({
is: 'td-item',
extends: 'li',
properties: {
editing: {
type: Boolean,
value: false
},
item: {
type: Object,
value: function() {
return {};
}
},
},
observers: ['setRootClass(item.completed, editing)'],
setRootClass: function(completed, editing) {
this.classList[completed ? 'add' : 'remove']('completed');
this.classList[editing ? 'add' : 'remove']('editing');
},
onBlur: function() {
this.commitAction();
this.editing = false;
},
editAction: function() { editAction: function() {
this.editing = true; this.editing = true;
// schedule focus for the end of microtask, when the input will be visible this.async(function() {
this.asyncMethod(function() { var elm = this.querySelector('#edit');
this.$.edit.focus(); // It looks like polymer is trying to be smart here and not updating the
// title attribute on the input when it is changed. To work around this, we manually have
// to set the value again when we go into edit mode.
elm.value = this.item.title;
elm.focus();
}); });
}, },
commitAction: function() { commitAction: function() {
if (this.editing) { if (this.editing) {
this.editing = false; this.editing = false;
this.item.title = this.item.title.trim(); this.set('item.title', this.querySelector('#edit').value.trim());
if (this.item.title === '') { if (this.item.title === '') {
this.destroyAction(); this.destroyAction();
} }
...@@ -34,7 +66,8 @@ ...@@ -34,7 +66,8 @@
cancelAction: function() { cancelAction: function() {
this.editing = false; this.editing = false;
}, },
itemChangeAction: function() { itemChangeAction: function(e, details) {
this.set('item.completed', e.target.checked);
this.fire('td-item-changed'); this.fire('td-item-changed');
}, },
destroyAction: function() { destroyAction: function() {
...@@ -43,4 +76,4 @@ ...@@ -43,4 +76,4 @@
}); });
})(); })();
</script> </script>
</polymer-element> </dom-module>
<link rel="import" href="../bower_components/polymer/polymer.html"> <link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="td-model" attributes="filter items storageId"> <dom-module id="td-model">
<template>
<iron-localstorage name="todos-polymer" value="{{items}}" on-iron-localstorage-load-empty="initializeDefaultTodos"></iron-localstorage>
</template>
<script> <script>
Polymer('td-model', { (function() {
filtered: null, 'use strict';
completedCount: 0,
activeCount: 0, Polymer({
allCompleted: false, is: 'td-model',
ready: function() { properties: {
this.asyncMethod(function() { items: {
this.items = this.storage.value || []; type: Array,
}); notify: true
}, },
filterChanged: function() { filter: String
this.asyncMethod(function() { },
this.filterItems(); initializeDefaultTodos: function() {
}); this.items = [];
}, },
itemsChanged: function() { newItem: function(title) {
this.completedCount = this.items.filter(this.filters.completed).length; title = String(title).trim();
this.activeCount = this.items.length - this.completedCount;
this.allCompleted = this.completedCount && !this.activeCount; if (!title) {
this.filterItems(); return;
if (this.storage) { }
this.storage.value = this.items;
this.storage.save(); this.push('items', {
}
},
storageIdChanged: function() {
this.storage = document.querySelector('#' + this.storageId);
if (this.storage) {
this.items = this.storage.value;
}
},
filterItems: function() {
var fn = this.filters[this.filter];
this.filtered = fn ? this.items.filter(fn) : this.items;
},
newItem: function(title) {
title = String(title).trim();
if (title) {
var item = {
title: title, title: title,
completed: false completed: false
}; });
this.items.push(item); },
this.itemsChanged(); getCompletedCount: function(items) {
} return items === null ? 0 : items.filter(this.filters.completed).length
}, },
destroyItem: function(item) { getActiveCount: function(items) {
var i = this.items.indexOf(item); return items.length - this.getCompletedCount(items);
if (i >= 0) { },
this.items.splice(i, 1); areAllCompleted: function(items) {
} return this.getCompletedCount(items) && !this.getActiveCount(items) ? true : false;
this.itemsChanged(); },
}, matchesFilter: function(item, filter) {
clearItems: function (){ var fn = this.filters[filter];
this.items = this.items.filter(this.filters.active); return this.filtered = fn ? fn(item) : true;
}, },
setItemsCompleted: function(completed) { destroyItem: function(item) {
this.items.forEach(function(item) { var i = this.items.indexOf(item);
item.completed = completed;
}); i !== -1 && this.splice('items', i, 1);
this.itemsChanged(); },
}, clearCompletedItems: function (){
filters: { this.items = this.items.filter(this.filters.active);
active: function(item) { },
return !item.completed; setItemsCompleted: function(completed) {
// Since we are mutating elements in an array here
// and we want everyone to know about it we must go through
// the polymer internal `splice` api. The fix that comes to my mind here
// would be to use a hash or set to represent this structure so that the mutations
// would happening on an object with real key value pairs instead of an object
// nested inside of an array
// Polymer array mutation docs: https://www.polymer-project.org/1.0/docs/devguide/properties.html#array-mutation
this.items.forEach(function(item, i) {
if (this.filter) {
if (this.filters[this.filter](item)) {
this.splice('items', i, 1, {title: item.title, completed: completed});
}
} else {
this.splice('items', i, 1, {title: item.title, completed: completed});
}
}, this);
}, },
completed: function(item) { filters: {
return item.completed; active: function(item) {
return !item.completed;
},
completed: function(item) {
return item.completed;
}
} }
} });
}); })();
</script> </script>
</polymer-element> </dom-module>
<link rel="import" href="../bower_components/polymer/polymer.html"> <link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/flatiron-director/flatiron-director.html"> <link rel="import" href="../bower_components/flatiron-director/flatiron-director.html">
<link rel="import" href="../bower_components/iron-selector/iron-selector.html"> <link rel="import" href="../bower_components/iron-selector/iron-selector.html">
<link rel="import" href="td-input.html"> <link rel="import" href="td-input.html">
<link rel="import" href="td-item.html"> <link rel="import" href="td-item.html">
<polymer-element name="td-todos" attributes="route modelId"> <dom-module id="td-todos">
<template> <template>
<flatiron-director route="{{route}}"></flatiron-director> <flatiron-director id="router" route="{{route}}"></flatiron-director>
<section id="todoapp"> <section id="todoapp">
<header id="header"> <header id="header">
<input is="td-input" id="new-todo" placeholder="What needs to be done?" autofocus on-td-input-commit="{{addTodoAction}}" on-td-input-cancel="{{cancelAddTodoAction}}"> <input is="td-input" id="new-todo" placeholder="What needs to be done?" autofocus on-td-input-commit="addTodoAction" on-td-input-cancel="cancelAddTodoAction">
</header> </header>
<section id="main" hidden?="{{model.items.length == 0}}"> <template is="dom-if" if="{{hasTodos(items.length)}}">
<input id="toggle-all" type="checkbox" on-change="{{toggleAllCompletedAction}}" checked="{{model.allCompleted}}"> <section id="main">
<label for="toggle-all">Mark all as complete</label> <input id="toggle-all" type="checkbox" on-change="toggleAllCompletedAction" checked="[[areAllCompleted]]">
<ul id="todo-list" on-td-item-changed="{{itemChangedAction}}" on-td-destroy-item="{{destroyItemAction}}"> <label for="toggle-all">Mark all as complete</label>
<template repeat="{{model.filtered}}"> <ul id="todo-list" on-td-destroy-item="destroyItemAction">
<li is="td-item" item="{{}}"></li> <template id="todo-list-repeater" is="dom-repeat" items="{{items}}" filter="matchesFilter" observe="completed">
<li is="td-item" item="{{item}}"></li>
</template>
</ul>
</section>
</template>
<template is="dom-if" if="{{hasTodos(items.length)}}">
<footer id="footer">
<span id="todo-count"><strong>{{getActiveCount(items, items.*)}}</strong> <span>{{getItemWord(items, items.*)}}</span> left</span>
<iron-selector on-tap="onTap" id="filters" selected="{{getSelectedRoute(route)}}}}" attr-for-selected="name">
<li name="all">
<a href="#/">All</a>
</li>
<li name="active">
<a href="#/active">Active</a>
</li>
<li name="completed">
<a href="#/completed">Completed</a>
</li>
</iron-selector>
<template is="dom-if" if="{{anyCompleted(items, items.*)}}">
<button id="clear-completed" on-click="clearCompletedAction" style="visibility:visible">Clear completed</button>
</template> </template>
</ul> </footer>
</section> </template>
<footer id="footer" hidden?="{{model.items.length == 0}}">
<span id="todo-count"><strong>{{model.activeCount}}</strong> {{model.activeCount == 1 ? 'item' : 'items'}} left</span>
<core-selector id="filters" selected="{{route || 'all'}}">
<li name="all">
<a href="../#/">All</a>
</li>
<li name="active">
<a href="../#/active">Active</a>
</li>
<li name="completed">
<a href="../#/completed">Completed</a>
</li>
</core-selector>
<button hidden?="{{model.completedCount == 0}}" id="clear-completed" on-click="{{clearCompletedAction}}">Clear completed</button>
</footer>
</section> </section>
</template> </template>
<script> <script>
(function() { (function() {
'use strict';
var ENTER_KEY = 13; var ENTER_KEY = 13;
var ESC_KEY = 27; var ESC_KEY = 27;
Polymer('td-todos', { Polymer({
modelIdChanged: function() { is: 'td-todos',
this.model = document.querySelector('#' + this.modelId); properties: {
}, modelId: String,
routeChanged: function() { areAllCompleted: {
if (this.model) { type: Boolean,
this.model.filter = this.route; computed: 'allCompleted(items.*)'
},
items: {
type: Array
},
model: {
type: Object
},
route: {
type: String,
observer: 'refreshFiltered'
} }
}, },
onTap: function() {
this.setActiveFilterChildClass();
},
setActiveFilterChildClass: function() {
// iron-selector should maybe allow selecting of arbitrary subnodes?
var filters = this.querySelector('#filters');
var prev = filters.querySelector('a.selected');
prev && prev.classList.remove('selected');
this.async(function() {
filters.selectedItem.querySelector('a').classList.add('selected');
});
},
getSelectedRoute: function(route) {
return route || 'all';
},
anyCompleted: function(items) {
return this.model.getCompletedCount(items) > 0;
},
getActiveCount: function(items) {
return this.model.getActiveCount(items);
},
refreshFiltered: function() {
// WAT: So it would be nice if repeat would be able to "observe" and external instance prop
// since it does not we have to "hack" this.
var elm = this.querySelector('#todo-list-repeater');
elm && elm._applyFullRefresh();
},
matchesFilter: function(item) {
return this.model.matchesFilter(item, this.route);
},
attached: function() {
document.querySelector('#router').addEventListener('director-route', this.routeChanged.bind(this));
// get a reference to the "model" which is our datastore interface
this.set('model', document.querySelector('#model'));
// this seems like smell... however I am not sure of a better way
this.addEventListener('dom-change', function() {
if (this.querySelector('#filters') !== null) {
this.setActiveFilterChildClass();
this.removeEventListener('dom-change');
}
});
},
allCompleted: function(items) {
return this.model.areAllCompleted(this.items);
},
getItemWord: function(items) {
return items.length === 1 ? 'item' : 'items';
},
hasTodos: function(todoCount) {
return todoCount > 0;
},
routeChanged: function(e) {
this.model.filter = e.detail;
},
addTodoAction: function() { addTodoAction: function() {
this.model.newItem(this.$['new-todo'].value); this.model.newItem(this.$['new-todo'].value);
// when polyfilling Object.observe, make sure we update immediately // when polyfilling Object.observe, make sure we update immediately
Platform.flush();
this.$['new-todo'].value = ''; this.$['new-todo'].value = '';
}, },
cancelAddTodoAction: function() { cancelAddTodoAction: function() {
this.$['new-todo'].value = ''; this.$['new-todo'].value = '';
}, },
itemChangedAction: function() {
if (this.model) {
this.model.itemsChanged();
}
},
destroyItemAction: function(e, detail) { destroyItemAction: function(e, detail) {
this.model.destroyItem(detail); this.model.destroyItem(detail);
}, },
toggleAllCompletedAction: function(e, detail, sender) { toggleAllCompletedAction: function(e) {
this.model.setItemsCompleted(sender.checked); this.model.setItemsCompleted(e.target.checked);
}, },
clearCompletedAction: function() { clearCompletedAction: function() {
this.model.clearItems(); this.model.clearCompletedItems();
} }
}); });
})(); })();
</script> </script>
</dom-module>
</polymer-element>
...@@ -11,12 +11,15 @@ ...@@ -11,12 +11,15 @@
<link rel="import" href="elements/td-todos.html"> <link rel="import" href="elements/td-todos.html">
</head> </head>
<body> <body>
<header> <div id="todoapp">
<h1>todos</h1> <header>
</header> <h1>todos</h1>
<core-localstorage id="storage" name="todos-polymer"></core-localstorage> </header>
<td-model id="model" storageId="storage"></td-model> <template is="dom-bind" id="root">
<td-todos modelId="model"></td-todos> <td-model id="model" items="{{todos}}"></td-model>
<td-todos items="{{todos}}"></td-todos>
</template>
</div>
<footer id="info"> <footer id="info">
<p>Double-click to edit a todo</p> <p>Double-click to edit a todo</p>
<p>Created by <a href="http://www.polymer-project.org">The Polymer Authors</a></p> <p>Created by <a href="http://www.polymer-project.org">The Polymer Authors</a></p>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment