週末、退屈と仕事の不足から、私は2つのすばらしいライブラリractive.jsとsails.jsの機能を調査するための教材として機能する小さなアプリケーションを作成することで楽しもうと決めました。
問題の声明
仕事の後、多くの場合、次のタスク(私はフリーランサー)を完了した後、サービスの支払いについて顧客に請求する必要があります。 特に法人を扱う場合。 これを行うために、単純なhtmlテンプレートを使用して、データを手で入力し、次のものを修正しました...
こんな感じ
私は、スタイルとレイアウトがfreshbooks.comから盗まれたことを認めています。 残念ながら、ロシアのクライアントにとってそれは私には合わず、単純なhtmlテンプレートで十分でした。
技術の選択
すべてのストライプのjsフレームワークとサーバーサイドのjs開発の人気の現在の傾向では、少し幸福のjsを少しの間維持するために、おいしいものとリアクティブなものを使用したいと思いました...そしてこれらのおもちゃを並行して試してみてください。
いくつかの調査、比較、および直感的な洞察の後、私はsails.jsをサーバーとして使用することに決めました。 私はダービーとセイルのどちらかを選びました-結局、ヨットを選んだのは、主にそのシンプルさ(ドックが読みやすくてすてきだからです)に加えて、箱から出して非常にクールなレストAPIジェネレーターも備えています。 学習という点でのダービーは、より難しく、より怪しいように見えました(この例では、明示的なオーバーヘッド)。
クライアントでは 、 ractive.jsをいじることに決めました。 その後、backbone.jsを接続することが決定されました-主にモデルとの便利な作業のため
この例の前は、 sails.jsとractive.jsの経験がありませんでした 。 作業では、バックボーンのみを使用しました。
始めましょう。
サーバー
この例では、sails v0.10を使用します。これはまだベータ版ですが、現在の安定バージョン0.9.xと比較すると、いくつかの便利な機能があります。 特に、1対多、多対多(およびモデル間のその他の関係)を指定できるモデルアソシエーション 、また0.10では、 うなり声タスクシステムが再設計されました。 0.10のドックでは、すべてがはっきりと書かれています
sails v0.10はnpm経由で配信できます(グローバルにインストールしました)
sudo npm install -g "git://github.com/balderdashy/sails.git#v0.10"
確認する
sails -v
0.10.0-すばらしい
スケルトンのsailsjsアプリケーションの作成
請求書などの新しいアプリケーションを作成し、依存関係を配置します
sails new invoicer cd invoicer npm install
次に、
sails lift
コマンドを実行することにより、組み込みのexpress.jsサーバーを実行できます。
localhost:1337
APIエンティティ(モデル)の作成
アプリケーションには3つのモデルが必要です。
-
user
ユーザー情報を保存します -
invoice
-アカウントのリスト用 -
task
アカウント内のタスク(請求書)
sails generate api <api_name>
API生成
zaebee@zaeboo$ sails generate api user debug: Generated a new model `User` at api/models/User.js! debug: Generated a new controller `user` at api/controllers/UserController.js! info: REST API generated @ http://localhost:1337/user info: and will be available the next time you run `sails lift`. zaebee@zaeboo$ sails generate api invoice debug: Generated a new model `Invoice` at api/models/Invoice.js! debug: Generated a new controller `invoice` at api/controllers/InvoiceController.js! info: REST API generated @ http://localhost:1337/invoice info: and will be available the next time you run `sails lift`. zaebee@zaeboo$ sails generate api task debug: Generated a new controller `task` at api/controllers/TaskController.js! debug: Generated a new model `Task` at api/models/Task.js! info: REST API generated @ http://localhost:1337/task info: and will be available the next time you run `sails lift`.
その後、3つのファイルがapi / controllersフォルダーに表示されます
-rw-r--r-- 1 146 28 17:15 InvoiceController.js -rw-r--r-- 1 143 28 17:15 TaskController.js -rw-r--r-- 1 143 28 17:15 UserController.js
API /モデルも
-rw-r--r-- 1 146 28 17:15 Invoice.js -rw-r--r-- 1 143 28 17:15 Task.js -rw-r--r-- 1 143 28 17:15 User.js
簡単でシンプルな帆は、3つの方法を作成しました
localhost:1337/user
localhost:1337/invoice
localhost:1337/task
CRUD操作をサポートします。 それらのエイリアスもあります。たとえば、
localhost:1337/user/create?name=Andrey&address=Russia
localhost:1337/user/create?name=Andrey&address=Russia
新しいユーザーインスタンスを作成します。 郵便配達員を通して遊ぶことができます
また、コントローラのドキュメントを読むことをお勧めします
ストレージ構成(DB)
作成されたデータはどこに保存されますか? デフォルトでは、ディスクがストレージとして使用されます。これは、
config/connections.js
および
config/models.js
設定で示されます
config / connections.jsコード
module.exports.connections = { localDiskDb: { adapter: 'sails-disk' }, someMysqlServer: { adapter : 'sails-mysql', host : 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS', user : 'YOUR_MYSQL_USER', password: 'YOUR_MYSQL_PASSWORD', database: 'YOUR_MYSQL_DB' }, someMongodbServer: { adapter : 'sails-mongo', host : 'localhost', port : 27017, //user : 'username', //password : 'password', database : 'invoicer' }, somePostgresqlServer: { adapter : 'sails-postgresql', host : 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS', user : 'YOUR_POSTGRES_USER', password : 'YOUR_POSTGRES_PASSWORD', database : 'YOUR_POSTGRES_DB' } };
mongoを使用してレコードを保存します。このため、config / models.jsをわずかに変更します。
config / models.jsコード
/** * Models * (sails.config.models) * * Unless you override them, the following properties will be included * in each of your models. */ module.exports.models = { // Your app's default connection. // ie the name of one of your app's connections (see `config/connections.js`) // // (defaults to localDiskDb) connection: 'someMongodbServer' };
必要なユーザー、請求書、タスクモデルのフィールドについて説明します
api / models / User.js
module.exports = { attributes: { name: 'string', email: 'string', avatar: 'string', address: 'text', account: 'text', invoices: { collection: 'invoice', via: 'owner', } }, };
API /モデル/ Invoice.js
module.exports = { attributes: { total_amount: 'float', name: 'string', address: 'text', owner: { required: false, model: 'user', }, tasks: { required: false, collection: 'task', via: 'invoice', } }, };
api / models / Task.js
module.exports = { attributes: { name: 'string', description: 'text', hours: 'float', rate: 'float', invoice: { required: false, model: 'invoice', via: 'tasks', } }, };
mongoアダプタを使用するには、sails-mongoパッケージをインストールする必要があります
npm install sails-mongo@0.10
コントローラーの「アクション」と、そのテンプレート(ビュー)を追加する
メインタスク(請求書の作成)のページを生成するコントローラーを作成する必要があります。
sails generate controller main generate
新しい
MainController.js
を作成し、1つの
generate
関数、いわゆるアクションを作成します
URLに行くと
localhost:1337/main/generate
localhost:1337/main/generate
generate
関数が返すものを確認します
デフォルトではjsonを返します
return res.json({ todo: 'Not implemented yet!' });
ブラウザーでhtmlページを表示する必要があります。 これを行うには、上記のコードを次のように置き換えます
return res.view()
ブラウザでページを更新するとエラーが表示されます
{ "view": { "name": "main/generate", "root": "/home/zaebee/projects/invoicer/views", "defaultEngine": "ejs", "ext": ".ejs" } }
これは、表示用のテンプレートを作成していないことを意味します。 コントローラーのすべてのhtmlテンプレートはビューフォルダーにあり、次の
views/<controller_name>/<action_name>
を持ちます
空のビューを作成/メイン/テンプレートを生成
zaebee@zaeboo$ mkdir views/main zaebee@zaeboo$ touch views/main/generate.ejs
デフォルトでは、ejsはテンプレートエンジンとして使用されます。 Sailsは多くのテンプレートエンジンをサポートしており、config / views.jsファイルでそれをお好みに変更できます。
ejs, jade, handlebars, mustache
underscore, hogan, haml, haml-coffee, dust
atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS,
swig, templayed, toffee, walrus, & whiskers
注意! セールバージョン0.10では、ライブアウトのサポートはejsでのみ機能します。 要するに、他のすべての
views/layout.ejs
が継承される基本
views/layout.ejs
があり
views/layout.ejs
。 また、テンプレートエンジンを使用する場合、ejs以外の継承はありません。
config/views.js
エンジンオプションを変更すると、Sailsはこれを明確にします
warn: Sails' built-in layout support only works with the `ejs` view engine. warn: You're using `hogan`. warn: Ignoring `sails.config.views.layout`...
お客様
サーバーの準備ができました。請求書アプリケーションのクライアント部分の作成を始めましょう。
静的接続
すべての静的(またはパブリッククライアントコード)は、assetsフォルダーにあります。 新しいファイルをテンプレートに接続するには、それらを適切なフォルダー(アセット/ jsのスクリプト、アセット/スタイルのスタイル、アセット/テンプレートのクライアントテンプレート)に配置するだけです。 .ejs-特別なセクション:
ソースファイル/views/layout.ejsのリスト
<!DOCTYPE html> <html> <head> <title>New Sails App</title> <!-- Viewport mobile tag for sensible mobile support --> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <!--STYLES--> <link rel="stylesheet" href="/styles/importer.css"> <!--STYLES END--> </head> <body> <%- body %> <!--TEMPLATES--> <!--TEMPLATES END--> <!--SCRIPTS--> <script src="/js/dependencies/sails.io.js"></script> <!--SCRIPTS END--> </body> </html>
必要なライブラリ(Jquery、Underscore、Backbone、Ractive)をcdnを介してレイアウトに接続し、
bootstrap.min.css
と完成した
app.css
ファイルを
assets/styles
フォルダーに配置します。 また、必要な追加のjs(
bootstrap.min.css
、
moment.ru.js
および
moment.min.js
moment.ru.js
を
moment.min.js
するためのライブラリ)を
assets/js/vendor
フォルダーに
app.js
し、
assets/js
フォルダーに空の
app.js
ファイルを配置します
assets/js
sails lift
を実行し、ファイル
views/layout.ejs
で現在の内容を確認します
ソースファイル/views/layout.ejsのリスト
<!DOCTYPE html> <html> <head> <title>New Sails App</title> <!-- Viewport mobile tag for sensible mobile support --> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <!--STYLES--> <link rel="stylesheet" href="/styles/app.css"> <link rel="stylesheet" href="/styles/bootstrap.min.css"> <link rel="stylesheet" href="/styles/importer.css"> <!--STYLES END--> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script> <script src="//cdn.ractivejs.org/latest/ractive.min.js"></script> <script src="//api.filepicker.io/v1/filepicker.js"></script </head> <body> <%- body %> <!--TEMPLATES--> <!--TEMPLATES END--> <!--SCRIPTS--> <script src="/js/dependencies/sails.io.js"></script> <script src="/js/app.js"></script> <script src="/js/vendor/bootstrap.min.js"></script> <script src="/js/vendor/moment.min.js"></script> <script src="/js/vendor/moment.ru.js"></script> <!--SCRIPTS END--> </body> </html>
素晴らしい、帆は私たちのためにすべてをしました。 確かに、マイナスが1つあります。ベンダースクリプトはapp.jsの下に接続されています。
tasks/pipeline.js
ファイルを修正し、ベンダーフォルダーを先に接続する必要があることをgruntに伝えます。
tasks / pipeline.jsファイルの一部をリストする
...... // CSS files to inject in order // // (if you're using LESS with the built-in default config, you'll want // to change `assets/styles/importer.less` instead.) var cssFilesToInject = [ 'styles/**/*.css' ]; // Client-side javascript files to inject in order // (uses Grunt-style wildcard/glob/splat expressions) var jsFilesToInject = [ // Dependencies like sails.io.js, jQuery, or Angular // are brought in here 'js/dependencies/**/*.js', 'js/vendor/**/*.js', // vendor // All of the rest of your client-side js files // will be injected here in no particular order. 'js/**/*.js' ]; ........
クライアント部分の準備が完了しました-アプリケーションのビジネスロジックの記述に直接進むことができます。
ページレイアウトスケルトンを作成します。 Ractive.jsテンプレート
もう一度レイアウトを見てみましょう。 その上で、動的データにバインドするブロックを強調しました。
クライアントテンプレートが含まれるファイルビュー/ main / generate.ejsで基本的なマークアップを作成しましょう
ファイルビューのリスト/ main / generate.ejs
<div class="main_bg"> <div class="container primary-content"> <div class="invoice-container rounded-container peel-shadows col-sm-8 col-sm-offset-2"> <h2 style="text-align:center;margin-bottom:30px;"> </h2> <div class="invheader"> <div class="invheader-upper"> <!-- User . : , , --> </div> <div class="invheader-lower"> <!-- Invoice . --> </div> </div> <div class="invbody"> <div class="invbody-tasks"> <!-- Task . --> </div> <div class="clearb" style="height: 1px; overflow: hidden;"></div> <div class="invbody-account"> <!-- User . --> </div> </div> </div> </div> </div>
これで、基本的なマークアップの準備ができました-ractive.jsテンプレートの時間です
ブロックごとにテンプレートを作成し(合計で4つあります)、それらをアセット/テンプレートに配置します
アセット/テンプレート/invheader-upper.htmlのリスト
<div class="invheader-address-account" on-hover="toggleBtn"> <a role="button" title="{{ .editing ? '' : ' ' }}" class="hidden-print btn btn-primary btn-sm hide" on-click="edit"><i class="glyphicon glyphicon-{{ .editing ? 'ok' : 'pencil' }}"></i> </a> <b>:</b> <div class="user-name {{ .editing ? 'editing' : '' }}"> <span>{{^name}} {{/name}}{{name}}</span> {{#.editing}} <div class='edit-container'> <input intro="select" value="{{name}}" class="form-control" placeholder=" "> </div> {{/.editing}} </div> <div class="user-address {{ .editing ? 'editing' : '' }}"> <span>{{^address}} {{/address}}{{{address}}}</span> {{#.editing}} <div class='edit-container'> <textarea value="{{address}}" class='edit form-control' placeholder=" ">{{address}}</textarea> </div> {{/.editing}} </div> </div> <div on-hover="togglePicker" class="invheader-logo-container"> <div class="invheader-logo"> {{#avatar}} <img src="{{avatar}}/convert?h=110&w=250" alt="{{name}}"> {{/avatar}} <div class="hidden-print BoardCreateRep {{ avatar ? 'hide' : '' }}"> <input type="filepicker-dragdrop" data-fp-mimetype="image/png" data-fp-apikey="A3lXl09sRSejY4e0pOOSQz" data-fp-button-class="btn btn-primary hidden-print" data-fp-button-text=" " data-fp-drag-text=" " data-fp-drag-class="hidden-print drop-avatar" onchange="app.user.fire('setAvatar', event)"> </div> </div> </div>
アセット/テンプレート/invheader-lower.htmlのリスト
<div class="invheader-address-client" on-hover="toggleBtn"> <a role="button" title="{{ .editing ? '' : ' ' }}" class="hidden-print btn btn-primary btn-sm hide" on-click="edit"><i class="glyphicon glyphicon-{{ .editing ? 'ok' : 'pencil' }}"></i> </a> <b>:</b> <div class="cleint-name {{ .editing ? 'editing' : '' }}"> <span>{{^invoice.name}} {{/invoice.name}}{{invoice.name}}</span> {{#.editing}} <div class='edit-container'> <input intro="select" value="{{invoice.name}}" class="form-control" placeholder=" "> </div> {{/.editing}} </div> <div class="client-address {{ .editing ? 'editing' : '' }}"> <span>{{^invoice.address}} {{/invoice.address}}{{{invoice.address}}}</span> {{#.editing}} <div class='edit-container'> <textarea value="{{invoice.address}}" class="edit form-control" placeholder=" ">{{invoice.address}}</textarea> </div> {{/.editing}} </div> </div> <div class="invheader-invoicedetails"> <table cellspacing="0"> <tbody> <tr> <th> </th> <td>#{{ lastFour(invoice.id) }}</td> </tr> <tr> <th> </th> <td>{{ date(invoice.createdAt) }}</td> </tr> <tr class="invheader-invoicedetails-balance"> <th><div> </div></th> <td><div> {{^invoice.total_amount}}0.00{{/invoice.total_amount}}{{ invoice.total_amount }} </div></td> </tr> </tbody> </table> </div>
アセット/テンプレート/invbody-tasks.htmlのリスト
<table class="invbody-items" cellspacing="0"> <thead> <tr> <th class="first"><div class="item"> </div></th> <th><div class="description">, </div></th> <th><div class="unitcost"> ()</div></th> <th><div class="quantity"> </div></th> <th class="last"><div class="linetotal"> ()</div></th> </tr> </thead> <tbody> {{#tasks}} <tr> <td style="width: 160px;"> <a on-tap="destroy:{{this}}" role="button" class='hidden-print destroy'></a> <div on-click="edit" class="item">{{name}}</div> <input intro="select" class="form-control hide" value="{{name}}" on-blur-enter="hide:{{this}}"> </td> <td> <div on-click="edit" class="description">{{description}}</div> <textarea class="form-control hide" value="{{description}}" on-blur-enter="hide:{{this}}">{{description}}</textarea> </td> <td style="width: 85px;"> <div on-click="edit" class="unitcost">{{ format(rate) }}</div> <input class="form-control hide" value="{{rate}}" on-blur-enter="hide:{{this}}"> </td> <td style="width: 80px;"> <div on-click="edit" class="quantity">{{ format(hours) }}</div> <input class="form-control hide" value="{{hours}}" on-blur-enter="hide:{{this}}"> </td> <td style="width: 90px;"> <div class="linetotal">{{ format(rate * hours) }}</div> </td> </tr> {{/tasks}} <tr> <td class="hidden-print text-center" colspan="5"> <button on-click="add" class="btn btn-primary btn-sm"><i class="glyphicon glyphicon-plus "></i> </button> </td> </tr> </tbody> </table> <table class="invbody-summary" cellspacing="0"> <tbody> <tr> <td class="invbody-summary-clean"> </td> <td style="width: 150px;"><strong> : </strong></td> <td style="width: 120px;"><strong> {{ total(tasks) }} </strong></td> </tr> <tr class="invbody-summary-paid"> <td class="invbody-summary-clean"> </td> <td style="width: 150px;"> </td> <td style="width: 120px;">-0.00</td> </tr> <tr class="invbody-summary-total"> <td class="invbody-summary-clean"> </td> <td style="width: 150px;"><div><strong> : </strong></div></td> <td style="width: 120px;"><div><strong> {{ total(tasks) }} </strong></div></td> </tr> </tbody> </table>
アセット/テンプレート/invbody-account.htmlのリスト
<div class="invbody-terms" on-hover="toggleBtn"> <a role="button" title="{{ .editing ? '' : ' ' }}" class="hidden-print btn btn-primary btn-sm hide" on-click="edit"><i class="glyphicon glyphicon-{{ .editing ? 'ok' : 'pencil' }}"></i> </a> <b>:</b> <div class="user-account {{ .editing ? 'editing' : '' }}"> <span>{{^account}} {{/account}}{{{account}}}</span> {{#.editing}} <div class='edit-container'> <textarea value="{{account}}" class='edit form-control' placeholder=" ">{{account}}</textarea> </div> {{/.editing}} </div> </div>
一般に、これは通常のhtmlであり、 ractive.jsがデータを挿入する口ひげのようなタグ
{{}}
散在しています。 また、いくつかのディレクティブ
on-click="edit"
気付くかもしれません-クリックによって
edit
メソッドを実行します。
on-hover="toggleBtn"
、
on-tap="destroy:{{this}}"
この点については後で説明します。今のところ、 ractive.jsイベントのドキュメントを学習できます
イベントはプラグインの形式でractiveで接続されます-いわゆるプロキシイベント。 イベントを機能させるには、必要なものをダウンロードして (イベントのすべてのプラグインをダウンロードした)、
assets/js/vendor
フォルダーに配置する必要があります
ractive.jsがバックボーンモデルをデータソースとして使用できるように、 Backbone用のアダプターを同じフォルダーに配置します。
データの初期化。 データとテンプレートのバインド
現時点で何であり、結果として得たいものを要約する
- REST APIを使用して帆サーバー上でユーザー、請求書、タスクを作成できます。 モデルの関連付けを介してそれらを接続します。 データはmongodbデータベースに保存されます
- バックボーンクライアントでは、モデルはユーザーが入力したデータを保存し、rest APIを介してセールサーバーと同期します。
- クライアントでは、ractiveはhtmlテンプレートとバックボーンモデル間の双方向バインディングを実装します( Backboneのアダプターにより)
- ....
- 利益?
最初に、空の
assets/js/app.js
必要なBackboneモデルを作成し
assets/js/app.js
。
アセットのリスト/ js / app.js
var app = app || {}; (function (app) { app.User = Backbone.Model.extend({ urlRoot: '/user', }); app.Invoice = Backbone.Model.extend({ urlRoot: '/invoice', }); app.Task = Backbone.Model.extend({ urlRoot: '/task', }); app.Tasks = Backbone.Collection.extend({ url: '/task', model: app.Task }); })(app);
さて、app.Userモデルにバインドされ、
assets/templates/invheader-upper.html
と
assets/templates/invbody-account.html
assets/templates/invheader-upper.html
をレンダリングするractiveインスタンスを作成します
アセット/ js / user.jsファイルを作成します
アセットのリスト/js/user.js
var app = app || {}; (function (app) { var backboneUser = new app.User; // ractive Ractive.extend // new Ractive({}), 2 var RactiveUser = Ractive.extend({ init: function (options) { this.data = options.data; this.on({ // // `on-click="edit"` edit: function (event) { var editing = this.get('editing'); this.set( 'editing', !editing ); if (editing) { this.data.save(); // } }, // // https://www.inkfilepicker.com // `onchange="app.user.fire('setAvatar', event)"` setAvatar: function (event) { if (event.fpfile) { var url = event.fpfile.url; this.set('avatar', url); } else { this.set('avatar', null); } this.data.save(); // }, // // `on-hover="togglePicker"` togglePicker: function (event) { if (!this.get('avatar')) return; if ( event.hover ) { $(event.node).find('.BoardCreateRep').removeClass('hide'); } else { $(event.node).find('.BoardCreateRep').addClass('hide'); } }, // // `on-hover="toggleBtn"` toggleBtn: function (event) { if ( event.hover ) { $(event.node).find('[role=button]').removeClass('hide'); } else { $(event.node).find('[role=button]').addClass('hide'); } } }); } }); // RactiveUser // `.invheader-upper` app.user = new RactiveUser({ el: '.invheader-upper', template: JST['assets/templates/invheader-upper.html'](), data: backboneUser, adaptors: [ 'Backbone' ], }); // RactiveUser // `.invheader-account` app.account = new RactiveUser({ el: '.invbody-account', template: JST['assets/templates/invbody-account.html'](), data: backboneUser, adaptors: [ 'Backbone' ], }); // Id // id ( ) // app.user.observe('id', function(id){ if (id && app.invoice) { app.invoice.data.invoice.set('owner', id); app.invoice.data.invoice.save(); } }); })(app);
コードは非常に簡単です。 ここで、基本クラス
RactiveUser
を作成します。 通常、
new Ractive({})
を使用してインスタンスを作成できますが、特にここでは、同じモデルに関連付けられ、ほぼ同じイベントをサブスクライブする2つの要素がユーザーに必要です。 イベント自体は、
init
関数の本体で示されます。
さらに進んで、
assets/js/invoice.js
と
assets/js/task.js
類推して作成し
assets/js/invoice.js
アセットのリスト/ js / invoice.js
var app = app || {}; (function (app) { app.invoice = new Ractive({ el: '.invheader-lower', template: JST['assets/templates/invheader-lower.html'](), data: { invoice: new app.Invoice, // Backbone // {{ date(createdAt) }} date: function (date) { return moment(date).format('D MMMM YYYY'); }, // {{ lastFour(id) }} lastFour: function (str) { return str.slice(-4); } }, adaptors: [ 'Backbone' ], transitions: { select: function ( t ) { setTimeout( function () { t.node.select(); t.complete(); }, 200 ); } } }); app.invoice.on({ // // `on-click="edit"` edit: function (event) { console.log(event); var editing = this.get('editing'); this.set( 'editing', !editing ); if (editing) { this.data.invoice.save({owner: app.user.data.id}); } }, // // `on-hover="toggleBtn"` toggleBtn: function (event) { if ( event.hover ) { $(event.node).find('[role=button]').removeClass('hide'); } else { $(event.node).find('[role=button]').addClass('hide'); } } }); // app.invoice.data.invoice.save(); })(app);
アセットのリスト/ js / task.js
var app = app || {}; (function (app) { app.tasks = new Ractive({ el: '.invbody-tasks', template: JST['assets/templates/invbody-tasks.html'](), data: { tasks: new app.Tasks, // Backbone // {{ format(price) }} format: function ( num ) { return num.toFixed( 2 ); }, // {{ total(tasks) }} total: function ( collection ) { var total = collection.reduce(function( sum, el ) { return el.get('rate') * el.get('hours') + sum; }, 0 ); return total.toFixed( 2 ); }, }, adaptors: [ 'Backbone' ], transitions: { select: function ( t ) { setTimeout( function () { t.node.select(); t.complete(); }, 200 ); } } }); app.tasks.on({ // // `on-click="add"` add: function ( event ) { var tasks = this.get('tasks'); var task = new app.Task({ name: ' ', description: ' ', hours: 0, rate: 0, }); tasks.add(task); task.save(null, { // , success: function() { task.set('invoice', app.invoice.data.invoice.id); task.save(); } }); }, // // `on-tap="destroy:{{this}}"` destroy: function ( event, task ) { task.destroy(); }, // // `on-click="edit"` edit: function ( event ) { $(event.node).hide(); $(event.node).next().removeClass('hide').focus().select(); }, // - // `on-blur-enter="hide"` hide: function ( event, task ) { $(event.node).addClass('hide'); $(event.node).prev().show(); task.save({invoice: app.invoice.data.invoice.id}); }, }); // `hours` `rate` // // // TODO app.tasks.observe('tasks.*.hours tasks.*.rate', function(tasks, old, keypath){ var total = this.data.total(this.data.tasks); app.invoice.data.invoice.set('total_amount', total); }); })(app);
ここで、コードは十分に明確であり、イベントにコメントを追加しました。 これは基本的にすべてのクライアントコードです。 また、IDに基づいて静的な請求書を生成する方法を固定することも計画しました
localhost:1337/main/generate/535ea7aa6113230d773fd160
localhost:1337/main/generate/535ea7aa6113230d773fd160
)またはapi pdfcrowd.comを使用します。幸いなことに、URLでpdfを作成できるノード用のモジュールがあります。 今、私はctrp + P(印刷に送信)-> "ファイルに印刷"でPDFを作成しています。 また、不要なhtml要素(ボタンなど)が出ないようにするために、
hidden-print
クラスを追加しました。
サーバーにデプロイする
これでほぼすべてです-アプリケーションの準備ができました。 この例はgithubにあります
サーバーで、リポジトリを複製し、依存関係をインストールし、本番モードで帆を実行します。
node app.js --port=8000 --prod
本番モードで動作するデモを開始しました
まとめ
sailsjsとractiveの両方を使用した結果は非常に満足しています。
Sailsjs-長所:
+セールでAPIが簡単に作成できる
+テンプレートエンジン、データベース、使用されるORMから始まる非常にクールな構成オプション( bookshelfjs.orgを帆にねじ込む予定です)
+私は、prodとmaidの両方のバンドルを生成するのに良い仕事をする既製のうなり声のタスクがあることをとても気に入りました。
+ (
sails www
) — .
+ ( , , )
短所:
— model assocoations (, v0.10 — , v0.9.x — )
— ejs
Ractivejs — :
+ backbone
+ ( )
+ mustache ( ejs — )
+ ,
Ractive — .
ご清聴ありがとうございました。