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">
<polymer-element name="td-input" extends="input" on-keyup="{{keyupAction}}" on-keypress="{{keypressAction}}">
<dom-module id="td-input">
<script>
(function() {
'use strict';
var ENTER_KEY = 13;
var ESC_KEY = 27;
Polymer('td-input', {
Polymer({
is: 'td-input',
extends: 'input',
listeners: {
'keyup': 'keyupAction',
'keypress': 'keypressAction'
},
keypressAction: function(e, detail, sender) {
// Listen for enter on keypress but esc on keyup, because
// IE doesn't fire keyup for enter.
......@@ -21,4 +29,4 @@
});
})();
</script>
</polymer-element>
</dom-module>
<link rel="import" href="../bower_components/polymer/polymer.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>
<div class="view {{ {completed: item.completed, editing: editing} | tokenList }}" hidden?="{{editing}}" on-dblclick="{{editAction}}">
<input type="checkbox" class="toggle" checked="{{item.completed}}" on-click="{{itemChangeAction}}">
<template is="dom-if" if="{{!editing}}">
<div on-dblclick="editAction">
<input type="checkbox" class="toggle" checked="{{item.completed::change}}" on-click="itemChangeAction">
<label>{{item.title}}</label>
<button class="destroy" on-click="{{destroyAction}}"></button>
<button class="destroy" on-click="destroyAction"></button>
</div>
<input is="td-input" id="edit" class="edit" value="{{item.title}}" hidden?="{{!editing}}" on-td-input-commit="{{commitAction}}" on-td-input-cancel="{{cancelAction}}">
</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>
<script>
(function() {
Polymer('td-item', {
editing: false,
'use strict';
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() {
this.editing = true;
// schedule focus for the end of microtask, when the input will be visible
this.asyncMethod(function() {
this.$.edit.focus();
this.async(function() {
var elm = this.querySelector('#edit');
// 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() {
if (this.editing) {
this.editing = false;
this.item.title = this.item.title.trim();
this.set('item.title', this.querySelector('#edit').value.trim());
if (this.item.title === '') {
this.destroyAction();
}
......@@ -34,7 +66,8 @@
cancelAction: function() {
this.editing = false;
},
itemChangeAction: function() {
itemChangeAction: function(e, details) {
this.set('item.completed', e.target.checked);
this.fire('td-item-changed');
},
destroyAction: function() {
......@@ -43,4 +76,4 @@
});
})();
</script>
</polymer-element>
</dom-module>
<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>
Polymer('td-model', {
filtered: null,
completedCount: 0,
activeCount: 0,
allCompleted: false,
ready: function() {
this.asyncMethod(function() {
this.items = this.storage.value || [];
});
},
filterChanged: function() {
this.asyncMethod(function() {
this.filterItems();
});
(function() {
'use strict';
Polymer({
is: 'td-model',
properties: {
items: {
type: Array,
notify: true
},
itemsChanged: function() {
this.completedCount = this.items.filter(this.filters.completed).length;
this.activeCount = this.items.length - this.completedCount;
this.allCompleted = this.completedCount && !this.activeCount;
this.filterItems();
if (this.storage) {
this.storage.value = this.items;
this.storage.save();
}
filter: String
},
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;
initializeDefaultTodos: function() {
this.items = [];
},
newItem: function(title) {
title = String(title).trim();
if (title) {
var item = {
if (!title) {
return;
}
this.push('items', {
title: title,
completed: false
};
this.items.push(item);
this.itemsChanged();
}
});
},
getCompletedCount: function(items) {
return items === null ? 0 : items.filter(this.filters.completed).length
},
getActiveCount: function(items) {
return items.length - this.getCompletedCount(items);
},
areAllCompleted: function(items) {
return this.getCompletedCount(items) && !this.getActiveCount(items) ? true : false;
},
matchesFilter: function(item, filter) {
var fn = this.filters[filter];
return this.filtered = fn ? fn(item) : true;
},
destroyItem: function(item) {
var i = this.items.indexOf(item);
if (i >= 0) {
this.items.splice(i, 1);
}
this.itemsChanged();
i !== -1 && this.splice('items', i, 1);
},
clearItems: function (){
clearCompletedItems: function (){
this.items = this.items.filter(this.filters.active);
},
setItemsCompleted: function(completed) {
this.items.forEach(function(item) {
item.completed = completed;
});
this.itemsChanged();
// 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);
},
filters: {
active: function(item) {
......@@ -73,5 +80,6 @@
}
}
});
})();
</script>
</polymer-element>
</dom-module>
<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/iron-selector/iron-selector.html">
<link rel="import" href="td-input.html">
<link rel="import" href="td-item.html">
<polymer-element name="td-todos" attributes="route modelId">
<dom-module id="td-todos">
<template>
<flatiron-director route="{{route}}"></flatiron-director>
<flatiron-director id="router" route="{{route}}"></flatiron-director>
<section id="todoapp">
<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>
<section id="main" hidden?="{{model.items.length == 0}}">
<input id="toggle-all" type="checkbox" on-change="{{toggleAllCompletedAction}}" checked="{{model.allCompleted}}">
<template is="dom-if" if="{{hasTodos(items.length)}}">
<section id="main">
<input id="toggle-all" type="checkbox" on-change="toggleAllCompletedAction" checked="[[areAllCompleted]]">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" on-td-item-changed="{{itemChangedAction}}" on-td-destroy-item="{{destroyItemAction}}">
<template repeat="{{model.filtered}}">
<li is="td-item" item="{{}}"></li>
<ul id="todo-list" on-td-destroy-item="destroyItemAction">
<template id="todo-list-repeater" is="dom-repeat" items="{{items}}" filter="matchesFilter" observe="completed">
<li is="td-item" item="{{item}}"></li>
</template>
</ul>
</section>
<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'}}">
</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>
<a href="#/">All</a>
</li>
<li name="active">
<a href="../#/active">Active</a>
<a href="#/active">Active</a>
</li>
<li name="completed">
<a href="../#/completed">Completed</a>
<a href="#/completed">Completed</a>
</li>
</core-selector>
<button hidden?="{{model.completedCount == 0}}" id="clear-completed" on-click="{{clearCompletedAction}}">Clear completed</button>
</iron-selector>
<template is="dom-if" if="{{anyCompleted(items, items.*)}}">
<button id="clear-completed" on-click="clearCompletedAction" style="visibility:visible">Clear completed</button>
</template>
</footer>
</template>
</section>
</template>
<script>
(function() {
'use strict';
var ENTER_KEY = 13;
var ESC_KEY = 27;
Polymer('td-todos', {
modelIdChanged: function() {
this.model = document.querySelector('#' + this.modelId);
Polymer({
is: 'td-todos',
properties: {
modelId: String,
areAllCompleted: {
type: Boolean,
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';
},
routeChanged: function() {
if (this.model) {
this.model.filter = this.route;
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() {
this.model.newItem(this.$['new-todo'].value);
// when polyfilling Object.observe, make sure we update immediately
Platform.flush();
this.$['new-todo'].value = '';
},
cancelAddTodoAction: function() {
this.$['new-todo'].value = '';
},
itemChangedAction: function() {
if (this.model) {
this.model.itemsChanged();
}
},
destroyItemAction: function(e, detail) {
this.model.destroyItem(detail);
},
toggleAllCompletedAction: function(e, detail, sender) {
this.model.setItemsCompleted(sender.checked);
toggleAllCompletedAction: function(e) {
this.model.setItemsCompleted(e.target.checked);
},
clearCompletedAction: function() {
this.model.clearItems();
this.model.clearCompletedItems();
}
});
})();
</script>
</polymer-element>
</dom-module>
......@@ -11,12 +11,15 @@
<link rel="import" href="elements/td-todos.html">
</head>
<body>
<div id="todoapp">
<header>
<h1>todos</h1>
</header>
<core-localstorage id="storage" name="todos-polymer"></core-localstorage>
<td-model id="model" storageId="storage"></td-model>
<td-todos modelId="model"></td-todos>
<template is="dom-bind" id="root">
<td-model id="model" items="{{todos}}"></td-model>
<td-todos items="{{todos}}"></td-todos>
</template>
</div>
<footer id="info">
<p>Double-click to edit a todo</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