Skip to content

Instantly share code, notes, and snippets.

@KTKate
Last active August 29, 2015 14:24
Show Gist options
  • Save KTKate/78e91a6fe73837ff98aa to your computer and use it in GitHub Desktop.
Save KTKate/78e91a6fe73837ff98aa to your computer and use it in GitHub Desktop.
todos
export default Ember.TextField.extend({
didInsertElement() {
this.$().focus();
this.$().addClass('focus'); // headless testing is brittle
}
});
<b class="foo">{{yield}}</b>
{{#if isEditing}}
{{edit-todo class='edit' value=todo.title
focus-out='save'
insert-newline='save'}}
{{else}}
{{input type='checkbox' checked=todo.isCompleted class='toggle'}}
<label {{action 'editTodo' on='doubleClick'}}>{{todo.title}}</label>
<button {{action 'removeTodo'}} class='destroy'></button>
{{/if}}
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['todo.isCompleted:completed', 'isEditing:editing'],
init() {
this._super(...arguments);
this.set('isEditing', false);
},
actions: {
editTodo() {
this.set('isEditing', true);
},
removeTodo() {
var todo = this.get('todo');
todo.deleteRecord();
todo.save();
},
save() {
this.set('isEditing', false);
this.get('todo').save();
}
},
});
{{#each todos as |todo|}}
{{todo-item todo=todo}}
{{/each}}
export default Ember.Component.extend({
tagName: 'ul',
});
<section class='todoapp'>
<header id='header'>
<h1>todos</h1>
{{input type='text'
class='new-todo'
placeholder='What needs to be done?'
value=newTitle
enter='createTodo'}}
</header>
<section class='main'>
{{todos-list todos=filtered class='todo-list'}}
{{input type='checkbox'
class='toggle-all'
checked=allAreDone}}
</section>
<footer class='footer'>
<span class='todo-count'>
<strong>{{active.length}}</strong> {{inflection}} left
</span>
<ul class='filters'>
<li> {{link-to 'All' 'index' (query-params state='all') activeClass='selected'}} </li>
<li> {{link-to 'Active' 'index' (query-params state='active') activeClass='selected'}} </li>
<li> {{link-to 'Completed' 'index' (query-params state='completed') activeClass='selected'}} </li>
</ul>
{{#if completed}}
<button class='clear-completed' {{action 'clearCompleted'}}>
Clear completed ({{completed.length}})
</button>
{{/if}}
</footer>
</section>
<footer class='info'>
<p>Double-click to edit a todo</p>
</footer>
var isEmpty = Ember.isEmpty;
var filterBy = Ember.computed.filterBy;
var computed = Ember.computed;
var service = Ember.inject.service;
export default Ember.Component.extend({
store: service(),
filtered: computed('todos.@each.isCompleted', 'filter', function() {
var filter = this.get('filter');
var all = this.get('todos');
if (filter === 'all') { return all; }
return all.filterBy('isCompleted', filter === 'completed');
}),
completed: filterBy('todos', 'isCompleted', true),
active: filterBy('todos', 'isCompleted', false),
inflection: computed('active.[]', function() {
var active = this.get('active.length');
return active === 1 ? 'item' : 'items';
}).readOnly(),
allAreDone: computed('filtered.@each.isCompleted', {
get() {
return !isEmpty(this) && this.get('todos.length') === this.get('completed.length');
},
set(key, value) {
// TODO: use action instead of a 2 way CP
var todos = this.get('todos');
todos.setEach('isCompleted', value);
todos.invoke('save');
return value;
}
}),
actions: {
createTodo() {
const store = this.get('store');
// Get the todo title set by the "New Todo" text field
var title = this.get('newTitle');
if (title && !title.trim()) {
this.set('newTitle', '');
return;
}
// Create the new Todo model
var todo = store.createRecord('todo', {
title: title
});
// Clear the "New Todo" text field
this.set('newTitle', '');
// Save the new model
todo.save();
},
clearCompleted() {
var completed = this.get('completed');
completed.toArray(). // clone the array, so it is not bound while we iterate over and delete.
invoke('deleteRecord').
invoke('save');
}
},
});
export default Ember.Controller.extend({
state: 'all',
queryParams: [
'state'
],
});
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean', { defaultValue: false })
});
export default Ember.Route.extend({
queryParams: {
state: { refreshModel: true }
},
model(params) {
return this.store.findAll('todo').then((todos) => {
return {
all: todos,
filter: params.state
};
});
}
});
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
}
button,
input[type="checkbox"] {
outline: none;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
label[for='toggle-all'] {
display: none;
}
.toggle-all {
position: absolute;
top: -55px;
left: -12px;
width: 60px;
height: 34px;
text-align: center;
border: none; /* Mobile Safari */
}
.toggle-all:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
.todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
.todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
visibility: hidden;
position: relative;
}
.clear-completed::after {
visibility: visible;
content: 'Clear completed';
position: absolute;
top: 0;
right: 0;
white-space: nowrap;
}
.clear-completed:hover::after {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
.toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
{{todos-route todos=model
filter=model.filter}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment