支払い請求曞。 sails.js、ractive.js、Backbone.jsで動䜜するアプリケヌション



週末、退屈ず仕事の䞍足から、私は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぀のモデルが必芁です。

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甚のアダプタヌを同じフォルダヌに配眮したす。

デヌタの初期化。 デヌタずテンプレヌトのバむンド

珟時点で䜕であり、結果ずしお埗たいものを芁玄する


最初に、空の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 — .

ご枅聎ありがずうございたした。

Source: https://habr.com/ru/post/J221171/


All Articles