JSFiddle - React, Tailwind, and code Playground

HTML

<script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/jquery-ui.min.js"></script>
<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular-route.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<body ng-app="app">
    <div ng-controller="todoCtrl">
        <form name="form1">
            <p>【新規登録】</p>
            <input type="text" name="newTitle" ng-model="newTitle" ng-init="" required></input>
            <button ng-click="addNewTask()" ng-disabled="form1.$invalid">追加</button>
        </form>
        <div style="margin-top:50px;">
            <p>【タスク一覧】</p>
            <ul style="padding-left:5px;list-style-type:none;">
                <li ng-repeat="item in items" style="hover: background:red;">
                    <p todo-editable class="todo-item-{{item.finished ? 'finished' : 'working'}}">{{item.id}} - {{item.title}} / {{item.finished ? '完了' : '未完了'}}</p>
                </li>
            </ul>
        </div>
    </div>
    <div id="dialog-form" title="タスクの編集">
        <p>
            <input type="text" name="title" />
        </p>
        <p>
            <input type="radio" name="status" id="task-finished" value="true">完了</input>
            <input type="radio" name="status" id="task-working" value="false">未完了</input>
        </p>
    </div>
</body>

CSS

li:hover {
    background : #CEF2F5;
    cursor : pointer;
}
.todo-item-finished {
    color : green;
}
.todo-item-working {
    color : red;
}

JavaScript

var services = {};


/**
 * Todoサービス
 */
services.TodoService = (function () {
    var Todo = function () {
        this._items = [{
            id: 1,
            title: "タスク1",
            finished: false
        }, {
            id: 2,
            title: "タスク2",
            finished: true
        }, {
            id: 3,
            title: "タスク3",
            finished: false
        }, {
            id: 4,
            title: "タスク4",
            finished: true
        }, {
            id: 5,
            title: "タスク5",
            finished: false
        }];
    };

    Todo.prototype.getAll = function () {
        return this._items;
    };

    var _add = function (title) {
        this._items.push({
            id: this._items.length + 1,
            title: title,
            finished: false
        });
    };

    var _update = function (item) {
        var updateTgt = _.find(this._items, function (i) {
            return i.id === item.id;
        });
        if (updateTgt === undefined) {
            _add.call(this, item.title);
        } else {
            updateTgt.title = item.title;
            updateTgt.finished = item.finished;
        }
    };

    Todo.prototype.update = function (item) {
        _.isString(item) ? _add.call(this, item) : _update.call(this, item);
    };

    Todo.prototype.delete = function (item) {
        //todo:実装
    };

    return Todo;
})();


/**
 * 非同期Todoサービス
 */
services.AsyncTodoService = (function () {

    //memo:定義場所について。
    //このサービスはAngularJSの$timeoutサービスに依存している。
    //ファクトリの登録関数内で定義するのが正解かもしれない。

    var AsyncTodo = function ($timeout) {
        this.$timeout = $timeout || function (action) {
            action()
        };
        this.todo = new services.TodoService();
    };

    AsyncTodo.prototype.getAll = function () {
        return this.todo.getAll();
    };

    var _applyCurrying = function (me, fnc) {
        return function () {
            return fnc.apply(me, arguments);
        }
    };

   ...