JSFiddle - React, Tailwind, and code Playground

by StevenSanderson

HTML

<script src="http://jshost.googlecode.com/hg/knockout-latest.js"></script>
<div id="todoapp">

    <h1>Todos</h1>

    <form id="create-todo" data-bind="submit: addItem">
      <input placeholder="What needs to be done?" data-bind="value: itemToAdd, valueUpdate: 'afterkeydown'" />
    </form>

    <ul data-bind="foreach: items" id="todo-list">
      <li data-bind="css: { editing: editing, done: done }">
          <div class="display">
            <input data-bind="checked: done" type="checkbox" />
            <div data-bind="text: content, event: { dblclick: edit }" class="todo-content"></div>
            <span data-bind="click: function() { window.todoViewModel.removeItem($data) }" class="todo-destroy"></span>
          </div>
          <form class="edit" data-bind="submit: save">
            <input data-bind="value: content, hasfocus: editing, valueUpdate: 'afterkeydown'" />
          </form>
      </li>    
    </ul>
    
    <div id="todo-stats">
        <span data-bind="visible: items().length > 0">
          <b data-bind="text: remaining().length"></b>
          <span data-bind="text: remaining().length == 1 ? 'item' : 'items'"></span> left.
        </span>

        <span data-bind="visible: done().length > 0" class="todo-clear">
            <a data-bind="click: clearCompleted" href="#">
            Clear <b data-bind="text: done().length" class="number-done"></b>
            completed <span data-bind="text: done().length == 1 ? 'item' : 'items'"></span>
            </a>
        </span>
    </div>

</div>

<ul id="instructions">
  <li>Double-click to edit a todo.</li>
</ul>

CSS

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, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  font-weight: inherit;
  font-style: inherit;
  font-size: 100%;
  font-family: inherit;
  vertical-align: baseline;
}
body {
  line-height: 1;
  color: black;
  background: white;
}
ol, ul {
  list-style: none;
}
a img {
  border: none;
}

html {
  background: #eeeeee;
}
  body {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 14px;
    line-height: 1.4em;
    background: #eeeeee;
    color: #333333;
  }

#todoapp {
  width: 480px;
  margin: 0 auto 40px;
  background: white;
  padding: 20px;
  -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
  -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
  -o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
  box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
}
  #todoapp h1 {
    font-size: 36px;
    font-weight: bold;
    text-align: center;
    padding: 20px 0 30px 0;
    line-height: 1;
  }

#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 input::-webkit-input-placeholder {
      font-style: italic;
    }
  #create-todo span {
    position: absolute;
    z-index: 999;
    width: 170px;
    left: 50%;
    margin-left: -85px;
  }

#todo-list {
  margin-top: 10px;
}
  #todo-list li {
   ...

JavaScript

function Todo (content, done) {
    this.content = ko.observable(content);
    this.done = ko.observable(done);
    this.editing = ko.observable(false);

    this.edit = function () { this.editing(true) };
    this.save = function () { this.editing(false) };
}

function TodoList (todos) {
    this.items = ko.observableArray(todos);
    this.itemToAdd = ko.observable('');

    this.addItem = function () {
        if (this.itemToAdd()) {
            this.items.push(new Todo(this.itemToAdd(), false));
            this.itemToAdd('');
        }
    };

    this.removeItem = function (item) {
        this.items.remove(item);
    }
    
    this.done = ko.dependentObservable(function () {
        return this.items().filter(function (todo) { return todo.done(); });
    }, this);

    this.remaining = ko.dependentObservable(function () {
        return this.items().filter(function (todo) { return !todo.done(); });
    }, this);

    this.clearCompleted = function () {
        this.items(this.remaining());
    };
}

// Get initial state from local storage
var initialTodos = $.map(JSON.parse(localStorage.getItem("Todos") || "[]"), function(obj) {
    return new Todo(obj.content, obj.done);
});
window.todoViewModel = new TodoList(initialTodos);
ko.applyBindings(window.todoViewModel);

// Whenever the items change, save to local storage
ko.dependentObservable(function () {
    localStorage.setItem("Todos", ko.toJSON(window.todoViewModel.items))
});