この投稿は、 ここから始まったシリーズの続きです (前の部分を読む必要があります)。 今日は、いわゆる「to-doリスト」( TodoMVCプロジェクトのTodoリスト)を作成します。 Angularで作成されたバージョンに基づいて、 ダービーで機能を再作成してください。
作業オプションを調べる
それでは、Angularバージョンの内容とその仕組みを見てみましょう(機能を理解するために5分かかります)。
- 新しいタスクが上部の入力フィールドに入力され、
enter
キーを押すとリストに移動します。 - リスト内のタスクは、タスクの右側にある「十字」をクリックして削除できます(タスクにカーソルを合わせると表示されます)。
- タスクの左側にある「チェックマーク」をクリックして、タスクを「完了」とマークできます(チェックボックスをオフにすることができます)。
- タスクをダブルクリックすると、編集モードになり、右、Enterキーを押します-タスクが更新されます。
- タスクを完了した場合、右下に「完了完了」ボタンが表示され、クリックすると、完了したタスクが削除されます。
- 完了したアクティブなタスクをカウント(および表示)します。 ステータスバーの下部。
- また、下のステータス行には3つのリンク(すべて、アクティブ、完了、URLをそれぞれ「#/」、「#/アクティブ」、「#/完了」に変更)があり、それらをクリックするとタスクフィルターが変更されます:すべてが表示されますアクティブなタスク(完了していない)または完了したタスク。
基礎となるもの
(より良いderbyjsを学習するための)目標に基づいて、ここでスタイルを考え出すことはしません。スタイルはすでに記述されており、ほとんどのTodoMVC実装で変更なしで使用されます。 cssファイルを取得します 。 それをざっと見てみると、背景bg.pngの写真も撮る必要があることがわかります。 また、angularによって生成されたhtmlをフレームワークとして使用します(ブラウザーの開発者ツールを使用してコピーし、Angularディレクティブから少し削除しました)。
基本的なhtmlコード
<section id="todoapp"> <header id="header"> <h1>todos</h1> <form id="todo-form"> <input id="new-todo" placeholder="What needs to be done?" autofocus> </form> </header> <section id="main"> <input id="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul id="todo-list"> <li> <div class="view"> <input class="toggle" type="checkbox"> <label>hello</label> <button class="destroy"> </button> </div> <form > <input class="edit"> </form> </li> </ul> </section> <footer id="footer"> <span id="todo-count"><strong>0</strong> <span>items left</span> </span> <ul id="filters"> <li><a href="/" class="selected">All</a></li> <li><a href="/active">Active</a></li> <li><a href="/completed">Completed</a></li> </ul> <button id="clear-completed">Clear completed (0)</button> </footer> </section>
ご覧のとおり、このhtmlは3つのメインブロックで構成されています。
- ヘッダー-ここがメイン入力です。 新しいタスクを導入するために必要です。
- main-メインブロック。タスクリスト自体はここに格納されます。
- フッター-ステータス行、ここの情報、フィルターと「クリア完了」ボタンの切り替え
プロジェクト構造
それでは、理由を考えましょう。 私たちのプロジェクトには何がありますか? スタイルシートがあり、静的データ(背景画像)が返され、htmlテンプレートがあり、少なくとも2つのファイルがあります-アプリケーションダービーの2つの部分(サーバー部分とダービーアプリケーション自体)。 このすべてに基づいて、私はそのようなアプリケーションファイル構造を投げました(あなたは他のものをすることができます):
public/ bg.png app # - views/ index.html css/ index.css index.js # - server.js # package.json
cssファイルは、パブリックフォルダー内ではなく、アプリフォルダー内にあることに注意してください。 これは、ダービーが特別な方法でスタイルを操作するためです。 その結果、Googleの調査(derbyの作成者による)で示されているように、スタイルタグのページの先頭に直接挿入されます。これは、スタイルを高速に配置するための最良の方法です。
したがって、前回のレッスンで述べたように、アプリフォルダー内のすべては同形のダービーアプリケーションです。 「同形」という言葉は好きではないので省略します。「ダービーのサーバー部分」ではなく、ダービーアプリケーションとだけ言います。 ここでのポイントは、これらすべてのファイル(アプリ内のすべて)を1つにまとめて、クライアントブラウザーに1つのバンドル(ピース)を提供するため、それらをまとめたものです。
一般的に(将来)、プロジェクトを複数のダービーアプリケーション(クライアントパートや管理パネルなど)に分割できます。 これは、不要なデータ(テンプレート、スタイル、コード)を提供しないことと、接続性を減らすことの2つの理由で正当化されます。 つまり、次のようになります。プロジェクトには、1つのサーバーパーツと複数のダービーアプリケーション(この場合は2つ)があります。
package.jsonファイルでは、依存関係は同じ2つのモジュール:derby@0.6.0-alpha5とderby-starterになります。
はじめに
ファイル構造を作成します。 最初に示したリンクから背景画像とスタイルをダウンロードし、
npm init
を使用してpackage.jsonを作成し
npm init
(前のレッスンで確認できます)。
前の例のように、Htmlは少し調整されています。まず、定義済みの
Body:
テンプレートにある必要があります
Body:
次に、ヘッダー、メイン、フッターを別々のダービーテンプレートに配置します。
最終的なindex.html
<Body:> <section id="todoapp"> <view name="header"/> <view name="main"/> <view name="footer"/> </section> <header:> <header id="header"> <h1>todos</h1> <form id="todo-form"> <input id="new-todo" placeholder="What needs to be done?" autofocus> </form> </header> <main:> <section id="main"> <input id="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul id="todo-list"> <li> <div class="view"> <input class="toggle" type="checkbox"> <label>hello</label> <button class="destroy"> </button> </div> <form > <input class="edit"> </form> </li> </ul> </section> <footer:> <footer id="footer"> <span id="todo-count"><strong>0</strong> <span>items left</span> </span> <ul id="filters"> <li><a href="/" class="selected">All</a></li> <li><a href="/active">Active</a></li> <li><a href="/completed">Completed</a></li> </ul> <button id="clear-completed">Clear completed (0)</button> </footer>
お気づきかもしれませんが、独自のテンプレートの呼び出しは、
view
タグを使用して行われ
view
。viewタグでは、テンプレートの名前によってテンプレートの名前が設定されます。
まず、ブラウザで結果を確認して機能を強化できるように、最小限の作業コードを作成します。
前の例のserver.jsファイルは、プロジェクトの構造を考慮して静的ファイルをレンダリングするためにわずかに拡張されています。
server.js
var server = require('derby-starter'); var appPath = __dirname + '/app'; var options = { static: __dirname + '/public' }; server.run(appPath, options);
プロジェクトの教育的性質により、derby-starterモジュールをサーバー部分として使用していることを思い出させてください。 内側を見ると、静的ファイルの戻りは、express-static static-middlwareの古典的な使用法です。 自分で見てください 。
最小index.js:
var derby = require('derby'); var app = module.exports = derby.createApp('todos', __filename); // app , // ( ) global.app = app; app.loadViews (__dirname+'/views'); app.loadStyles(__dirname+'/css'); app.get('/', getTodos); function getTodos(page, model){ page.render(); }
すべて、npm start(または直接ノードserver.js)を起動し、ブラウザにhttp:// localhost:3000 /結果が表示されます:
レイアウトのスタイルが追いついた。 開始されました。
デザインURL
前回のレッスンでは、ダービー開発者はプロジェクトをURLに分割して開発を開始する必要があると述べました。 これは、検索エンジンが好むクライアントとサーバーの両方でページを生成するダービーの能力によるものです。 そのため、Angularバージョンの学習中に、フッターにURLを変更する3つのリンクがあり、それに応じてタスクごとのフィルターがあることに気付きました。 ここでは、アプリケーションに3つのget要求ハンドラーが必要であることを理解しています。 次のようなもの:
app.get('/', getAllTodos); app.get('/active', getActiveTodos); app.get('/completed', getCompletedTodos);
これらのページがすべて異なる場合、これは正当化されますが、私たちにとって、それらの間の唯一の違いはフィルターであるため、コードを最小限に複製しようとします。
設計データ
タスク自体は、
todos
コレクションに保存されます。 各タスクは2つのフィールドで表されます。
- テキスト-タスクの説明
- completed-タスクが完了したことの兆候
これに、もちろん各タスクにもidフィールドがあることを追加する必要があります-ダービーはコレクションにアイテムを追加するときに自動的に追加します。
したがって、ダービーの方法論に従って、コントローラー(urlへの要求を処理する関数)で、renderを呼び出す前に、データを準備し、データを更新するためのサブスクリプションを登録する必要があります。 ハンドラーは、概略的に次のようになります。
function getTodos(page, model){ model.subscribe('todos', function(){ page.render(); }); }
これはほぼ同じですが、先に進む前に(3つのクエリすべてに対して1つのコントローラーを作成し、タスクに対して異なるフィルターのみを使用するために)、ダービーモデルについていくつかのことを学ぶ必要があります。
- アンダースコアで始まる「パス」(「_session」、「_ page」など)
- 「_page」の機能は何ですか
- ダービーフィルターとは
- コレクション内の特定のデータへの参照は何ですか
前回のレッスンでは、いわゆる「パス」について話しました。 モデルの操作でそれらを使用します。 たとえば、データをサブスクライブする場合:model.subscribe( 'path')、モデルのデータを受信および書き込む場合:
model.get('')
、
model.set('', )
。 パスの例:
- 'todos'-todosコレクション全体へのリンク
- 'users.42'-id = 42のユーザーコレクションのエントリを参照します
だからここに。 ご存じのとおり、パスの最初のセグメントはコレクションの名前です。 このダービー名は、ラテン文字、または文字$または_で始めることができます。 $および_で始まるすべてのコレクションは特別であり、サーバーと同期していません(これらはモデルに対してローカルであり、1つのモデルのみがダービーアプリケーションで作成されます)。 $で始まるコレクションは、独自のニーズのためにダービーを予約しました。 開発者は、アンダースコアで始まるコレクションを使用します。
少し実験してみましょう。 ブラウザーで開発者コンソールを開き、「
app.model.get()
と入力して、出力を表示します。
「_」コレクションには特別なものが1つあり
_page
は、URLが
_page
れるたびに
_page
れ
_page
これにより、あらゆる種類の作業データを保存するのに非常に便利です。 このレッスンでは、より多くの例を見ることができます。
フィルターに移りましょう。 モデルのドキュメントを読むと、ダービーにはリアクティブデータの処理を容易にするさまざまなメカニズムがあることがわかります。 これは、たとえば、リアクティブ関数、データで発生するさまざまなイベントのサブスクリプション、データフィルター、データソーターなどです。
フィルターについて説明しましょう。 たとえば、アクティブなタスクのみを表示するフィルターを実装するにはどうすればよいですか:
特定の名前でフィルター関数を登録します(名前はバンドルでのシリアル化に必要です)。 ドキュメントには、それらを厳密に
app.on('model')
に登録する必要があると書かれています
app.on('model', function(model) { model.fn('completed', function(item) { return item.completed; }); });
さらにコントローラーで、このフィルターを使用してtodosコレクションをフィルター処理します。
function getPage(page, model){ model.subscribe('todos', function() { var filter = model.filter('todos', 'completed') filter.ref('_page.todos'); page.render(); }); }
ここで、行
filter.ref('_page.todos');
は非常に重要です
filter.ref('_page.todos');
、その中で、フィルタリングされた「todos」が
_page.todos
パスで利用可能になります。 すべてをまとめると、コントローラーを使用したフィルターコードをここに提案します。
app.on('model', function(model) { model.fn('all', function(item) { return true; }); model.fn('completed', function(item) { return item.completed;}); model.fn('active', function(item) { return !item.completed;}); }); app.get('/', getPage('all')); app.get('/active', getPage('active')); app.get('/completed', getPage('completed')); function getPage(filter){ return function(page, model){ model.subscribe('todos', function() { model.filter('todos', filter).ref('_page.todos'); page.render(); }); } }
お気づきかもしれませんが、すべてを統一するために、偽のフィルターを「すべて」作成する必要がありましたが、これはテイクの欠如にとって大きな料金ではないと思います。
さて、少し気が散りました。 アプリケーションを復活させましょう。
タスクの追加とリスト
レイアウトのデータ入力の入力は次のようになります。
<form id="todo-form"> <input id="new-todo" placeholder="What needs to be done?" autofocus> </form>
ダービーの古典的なパターン(多くの最新のフレームワークと同様)は、リアクティブバインディングです。 入力で入力した値を
_page
パスに
_page
ます。
enter
クリックを処理するために、フォームの
submit
イベントハンドラーも登録し
enter
。
<form id="todo-form" on-submit="addTodo(_page.newTodo)"> <input id="new-todo" placeholder="What needs to be done?" autofocus value="{{_page.newTodo}}"> </form>
on-submit
はなく、自然に
on-click
、
on-keyup
、
on-focus
記述
on-click
ことができ
on-click
-つまり、これはダービーでイベントを処理する標準的な方法です。
app.proto
ハンドラーを配置し
app.proto
(ダービーコンポーネントについて説明するときに、各コンポーネントがそのハンドラーをそれ自体に格納することがわかりますが、今のところこれを行います)。
app.proto.addTodo = function(newTodo){ if (!newTodo) return; this.model.add('todos', { text: newTodo, completed: false }); this.model.set('_page.newTodo', ''); };
テキストが空かどうかを確認し、タスクをコレクションに追加して、
input
クリアし
input
。 ハンドラーにパラメーターが1つしかないことに気づいたかもしれません。何らかの理由でイベントオブジェクトまたはhtml要素自体への参照が必要な場合は、次のようにhtmlに明示的に記述する必要があります。
on-submit="addTodo(_page.newTodo, $event, $element)"
、
$event
および
$element
はダービー自体によって埋められる特別なパラメーターです。
フィルタリングされたタスクリストの出力
ul
要素を編集します。
<ul id="todo-list"> {{each _page.todos as #todo, #index}} <li class="{{if #todo.completed}}completed{{/}}"> <div class="view"> <input class="toggle" type="checkbox" checked="{{#todo.completed}}"> <label>{{#todo.text}}</label> <button class="destroy"> </button> </div> <form> <input class="edit"> </form> </li> {{/each}} </ul>
だから何をした:
- すべてのToDoをループします(フィルター済み)-それらの要素を作成します-li
-
lable
タスクの説明を印刷する -
checkbox
をtodo.completed
- タスクが完了した場合、
li
タグのcompleted
クラスを設定します。
アイテムを削除する
それは基本的に行われます:
<button class="destroy" on-click="delTodo(#todo.id)"> </button>
app.proto.delTodo = function(todoId){ this.model.del('todos.' + todoId); };
そして、さらに短くすることもできました。
<button class="destroy" on-click="model.del('todos.' + #todo.id)"> </button>
すべての「完了」タスクの削除も同様です(「完了完了」ボタンは右下にあります)。
<button id="clear-completed" on-click="clearCompleted()"> Clear completed (0) </button>
app.proto.clearCompleted = function(){ var todos = this.model.get('todos'); for (var id in todos) { if (todos[id].completed) this.model.del('todos.'+id); } }
要素を編集する
ダブルクリックすると、タスクは編集モードになります。 レイアウトから判断すると、このモードに切り替えるときは、対応する
li
要素に
editing
クラスを追加する必要があります。 また、途中で、ダブルクリックして発生する選択を取り除き、必要な入力に正しくフォーカスを合わせる必要があります。
次のようにそれを行うことを提案します:パス-_page.editを使用して、編集したタスクに関する情報を保存します。 そこで、編集したタスクのIDとテキストを保存します。
テキストを個別に保存するのはなぜですか、すでにタスク自体に保存されていますか?
それはすべて目標に依存します。 テキストをタスクから直接入力に接続した場合、ユーザーはデータベース内の要素を直接編集します。 つまり、その編集(ボタンを押すたびに)は、ブラウザーで他のユーザーに即座に表示されます。 さらに、複数のユーザーが同時にテキストを編集し、すべての変更を表示できますが、これは必要なものではありません。 通常のシナリオは、最終的に編集されたデータのデータベースにコミットするか、コミットを拒否することです。つまり、ユーザーが
キーを押した場合にのみ、すべてのユーザーに対してすべてを更新する必要があり
。
enter
キーを押した場合にのみ、すべてのユーザーに対してすべてを更新する必要があり
enter
。
そのため、次のすべてを実装します。
<ul id="todo-list"> {{each _page.todos as #todo}} <li class="{{if #todo.completed}}completed{{/}} {{if _page.edit.id === #todo.id}}editing{{/}}"> <div class="view"> <input class="toggle" type="checkbox" checked="{{#todo.completed}}"> <label on-dblclick="editTodo(#todo)">{{#todo.text}}</label> <button class="destroy" on-click="delTodo(#todo.id)"> </button> </div> <form on-submit="doneEditing(_page.edit)"> <input id="{{#todo.id}}" class="edit" value="{{_page.edit.text}}" on-keyup="cancelEditing($event)"> </form> </li> {{/each}} </ul>
app.proto.editTodo = function(todo){ this.model.set('_page.edit', { id: todo.id, text: todo.text }); window.getSelection().removeAllRanges(); document.getElementById(todo.id).focus() } app.proto.doneEditing = function(todo){ this.model.set('todos.'+todo.id+'.text', todo.text); this.model.set('_page.edit', { id: undefined, text: '' }); } app.proto.cancelEditing = function(e){ // 27 = ESQ-key if (e.keyCode == 27) { this.model.set('_page.edit.id', undefined); } }
ダブルクリックすると、 editTodo関数がトリガーされ 、その中に_path.editを入力し、不要な選択を削除し、必要な入力にフォーカスを切り替えます(ここでは、入力
id = todo.id
を指定して少し浮気しました)。
編集が終了したら、enterまたはesqを押します。 したがって、
doneEditing
、
cancelEditing
の2つのハンドラーのいずれかが起動します。 コードを学ぶ-新しいことは何もない。
アクティブおよび完了したタスクの数-リアクティブ機能
したがって、最後に行うことは、アクティブなタスクと完了したタスクの数をフッターに出力することです。 これは、リアクティブ関数とは何かを説明する正当な理由です。
プロジェクトのアーキテクチャに関する小さな発言
私が選択したアプリケーション実装オプションが唯一のものではないことに注意してください。 これを考えると、特定のプロジェクトがすぐにlive-queryを使用して頭に浮かびます-これは、データベースに対してmongo-requestを行うことができるもう1つの素晴らしいダービーメカニズムであり、その結果は事後的に更新されます。 もちろん、クエリでは、さまざまな選択、ソート、数量制限(
、
、
)を使用できます。 コレクション内の要素の数を(任意の選択を含めて)返すクエリを作成することもできます-これはまさに私たちの場合です。 次の投稿のいずれかで「ライブ」クエリを調査しますが、実際のアプリケーションでもよく使用されるリアクティブ関数による実装を示すことが適切であると考えました。
$limit
、
$skip
、
$orderby
)を使用できます。 コレクション内の要素の数を(任意の選択を含めて)返すクエリを作成することもできます-これはまさに私たちの場合です。 次の投稿のいずれかで「ライブ」クエリを調査しますが、実際のアプリケーションでもよく使用されるリアクティブ関数による実装を示すことが適切であると考えました。
したがって、リアクティブ関数は、データが変更されるたびにトリガーされる関数です。 つまり、この特定のリアクティブ関数がこれらの特定のデータの変化を監視することを示す必要があります。 このデータは、パラメーターとしてこの関数に入力されます。 次に、彼女は何かを計算し、結果を返します。 その結果は、特定の「パス」に関連付けられています...
さて、これはすべて抽象的であり、したがって読みにくいです。 例を使用しましょう。 アクティブなタスクと完了したタスクを含むTodoのコレクションがあります。 コレクションの変更時に、どこか(たとえば、
_page.counters
パスに沿って)でアクティブなタスクと完了したタスクのカウンターにアクセスできると
_page.counters
です。 次のようなもの:
_page.counters = { active: 2, completed: 3 }
次に、このデータをフッターに簡単に表示できます。
これらのカウンターを取得する1つのオプションは、リアクティブ関数を使用することです。 それらはフィルターと同じ方法で登録されます:
app.on('model', function(model) { model.fn('all', function(item) { return true; }); model.fn('completed', function(item) { return item.completed;}); model.fn('active', function(item) { return !item.completed;}); model.fn('counters', function(todos){ var counters = { active: 0, completed: 0 }; for (var id in todos) { if(todos[id].completed) counters.completed++; else counters.active++; } return counters; }) });
これが
counters
機能の登録方法ですが、それだけではありません。 適切なタイミングで起動し、パスに結び付ける必要があります。 これは、
model.start
関数を使用してコントローラーで実行されます。
model.subscribe('todos', function () { model.filter('todos', filter).ref('_page.todos'); model.start('_page.counters', 'todos', 'counters'); page.render(); });
これで、テンプレートでカウンターを使用できるようになりました。 フッターを完成させています:
<footer:> <footer id="footer"> <span id="todo-count"><strong>{{_page.counters.active}} </strong> <span>items left</span> </span> <ul id="filters"> <li><a href="/" class="{{if $render.url==='/' }}selected{{/}}">All</a></li> <li><a href="/active" class="{{if $render.url==='/active' }}selected{{/}}">Active</a></li> <li><a href="/completed" class="{{if $render.url==='/completed'}}selected{{/}}">Completed</a></li> </ul> <button id="clear-completed" on-click="clearCompleted()" class="{{if _page.counters.completed==0}}hidden{{/}}"> Clear completed ({{_page.counters.completed}}) </button> </footer>
完了したタスクがない場合は、必要なカウンターを表示すると同時に、「クリア完了」ボタンを非表示にしました。 また、ブラウザーコンソールで
app.model.get()
調査中に取得した情報を使用して、
selected
クラスをアクティブなリンクに追加しました。 はい、予約済みの
$render
コレクションには、さまざまな有用な情報、特にレンダリングに
url
れる
url
ています。 もう一度コンソールを見てください。
まとめ
何が起こったのかを試し、いくつかのタブを開いて、すべてが同期されていることを確認します。
コードを比較したい場合のgithubプロジェクト。
PS
次のderbyjsの記事を見逃したくない場合は、プロファイルの更新を購読してください: zag2art 。 私は自分でやっています-ハブでは、特定の(非常に興味深い)ハブをトラッカーに追加する方法がないため、見逃すことはありません。
derbyjs — github