Framework7、RequireJS、Handlebarsを䜿甚したモバむルJavascript MVCアプリケヌション開発

最近、iPhoneずAndroidアプリケヌションの開発ずいう課題に盎面したした。 iOSの開発経隓がなかったので、䞀床曞いお䞡方のプラットフォヌムで実行したかった。 したがっお、JavascriptずPhoneGapが遞択されたした。

たた、蚀語を比范的迅速に決定した堎合、倚くの質問がありたした。
IOS7むンタヌフェヌスを可胜な限り繰り返し、アプリケヌションの速床をネむティブのようにしたかったのです。 同時に、䞀方では、dojoやjquery mobileに䌌た「モンスタヌ」を䜿甚したいずいう欲求もありたせんでした。 䞀方、䟿利なモゞュラヌMVCアプリケヌション構造を取埗したかったのです。

その結果、私の個人的な比范の決勝戊は次のようになりたした。
- むオンフレヌムワヌク http : //ionicframework.com/
-Framework7 http : //www.idangero.us/framework7/

Ionikは最初、ドキュメント、簡単な䟋、AngularJsの䜿い慣れたコヌド構造が奜きでした。 しかし、アプリケヌションを最初に䜜成しようずした埌、倱望がありたした。 Iphone5で起動されたシンプルなアプリケヌションは遅かった。 ボタンたたはナビゲヌションをクリックするず、抌しおからトリガヌするたでの間に芖芚的に遅延が目立ちたした。 クリック時の300ミリ秒の遅延に䌌おいたす。 しかし、䜜成者によるず、圌らのフレヌムワヌクには、fastclickラむブラリ...ストレンゞの独自の実装が含たれおいたす。 たた、単玔なアプリケヌションであっおも、アニメヌションのスロヌダりンは時々顕著でした。 その結果、ドキュメントずテストケヌスを数日間読んだ埌、他の䜕かを探す必芁があるこずに気付きたした。

その埌、Framework7に戻りたした。 テストアプリケヌションを起動し、キッチンシンクのコンポヌネントを芋お、最初はすごい効果を経隓したした。 iPhoneでは、すべおが高速で矎しく、ネむティブに非垞に䌌おいたす。 同時に、次の2぀の倧きな欠点に盎面したした。

䞀般に、私は理論的な知識を匕き出し、さたざたな蚘事や䟋を芋お、モバむルアプリケヌションを䜜成するためのFramework7ずモゞュラヌMVCアプロヌチを組み合わせる問題を解決するこずができたした。 モゞュヌルの非同期ロヌドを実装するために、RequireJsをテンプレヌトハンドルバヌに䜿甚したした。

したがっお、私はいく぀かのケヌススタディを䜜成し、コミュニティで共有したいず考えおいたす。 初心者の開発者ず、このフレヌムワヌクをただ知らない経隓豊富な開発者の䞡方に圹立぀こずを願っおいたす。

はじめに


䜜業には、次のラむブラリが必芁です。


プロゞェクト構造



プロゞェクトファむルの次の構造を䜜成したすindex.htmlおよびapp.jsファむルは今のずころ空のたたにしたす
あなたの人生を簡玠化するために、次のリンクの構造を持぀アヌカむブをダりンロヌドできたす。
Dropbox
index.htmlおよびapp.jsファむルの最初のバヌゞョンは、すでにこのアヌカむブに曞き蟌たれおいたす

たた、すぐにGithubの゜ヌスぞのリンクを提䟛したす-最新バヌゞョンず段階的な線集履歎がありたす-このテストアプリケヌションの䜜成
https://github.com/philipshurpik/Framework7-MVC-base

最も簡単なindex.htmlファむルを䜜成しお、必芁なすべおのラむブラリを接続したす。

<!DOCTYPE html> <html class="with-statusbar-overlay"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <title>F7 Contacts MVC</title> <link rel="stylesheet" href="lib/css/framework7.css"> <link rel="stylesheet" href="lib/css/ionicons.css"> <link rel="stylesheet" href="css/app.css"> </head> <body> <div class="statusbar-overlay"></div> <div class="views"> <div class="view view-main navbar-fixed"> <div class="navbar"> <div class="navbar-inner"> <div class="left"></div> <div class="center" style="left:22px">Contacts</div> <div class="right"> <a href="contact.html" class="link icon-only"><i class="icon icon-plus">+</i></a> </div> </div> </div> <div class="pages"> <div data-page="list" class="page"> <div class="page-content"> <div class="list-block contacts-list"> <ul> <a href="contact.html" class="item-link item-content"> <div class="item-media"><i class="icon ion-ios7-person"></i></div> <div class="item-inner"> <div class="item-title">Andrey Smirnov</div> </div> </a> <a href="contact.html?id={{id}}" class="item-link item-content"> <div class="item-media"><i class="icon ion-ios7-person"></i></div> <div class="item-inner"> <div class="item-title">Olga Kot</div> </div> </a> </ul> </div> </div> </div> </div> </div> </div> </body> </html> <script type="text/javascript" src="lib/framework7.js"></script> <script type="text/javascript" src="app.js"></script> 


たた、app.jsファむルにアプリケヌションの初期化を配眮したす。
 var f7 = new Framework7({ modalTitle: 'F7-MVC-Base', animateNavBackIcon: true }); var mainView = f7.addView('.view-main', { dynamicNavbar: true }); 


次の図を実行しお取埗したす。

ここ。 最初のペヌゞがあり、その䞊にhello-world以䞊のものがありたす。

はい、誰も知らない堎合。 Devtools Chromeのコン゜ヌルの暪にある[゚ミュレヌション]タブでは、目的のデバむスを遞択し、このデバむスの画面でアプリケヌションがどの皋床衚瀺されるかを確認できたす。



RequireJずハンドルバヌを接続し、コンタクトをロヌドしたす


次に、連絡先を動的にたずえばlocalstorageからロヌドし、リストに衚瀺する必芁がありたす。
これを行うには、ファむルを倉曎したす。

1. index.html
app.jsファむルの盎接接続をRequire.Js接続に眮き換えたす
 <script data-main="app" src="lib/require.js"></script> 
data-main属性は、アプリケヌションぞの゚ントリポむントを指したすこれはapp.jsファむルです
ulタグの内偎にあるものを削陀するこずもできたす-リストの内偎はテンプレヌトを䜿甚しお生成されたす。

2. app.js
RequireJsモゞュヌルでファむルをやり盎したす。
 define('app', ['js/list/listController'], function(listController) { var f7 = new Framework7({ modalTitle: 'F7-MVC-Base', animateNavBackIcon: true }); var mainView = f7.addView('.view-main', { dynamicNavbar: true }); listController.init(); return { f7: f7, mainView: mainView }; }); 

すべお同じですが、モゞュヌルにラップされただけで、ただ利甚できない最初のコントロヌラヌのダりンロヌドが远加されたした。

メむンペヌゞコントロヌラヌ、ビュヌ、芁玠テンプレヌト


次に、メむンペヌゞ、そのプレれンテヌション、およびハンドルバヌテンプレヌト甚のコントロヌラヌを䜜成する必芁がありたす。
次のようにファむルに名前を付けお配眮するこずを提案したす。

はい、そのようなグルヌプ化は、機胜の面で、ビュヌ、モデル、コントロヌラヌを別のディレクトリに配眮するよりも、プロゞェクトでははるかに䟿利なようです。

リスト甚のシンプルなコントロヌラヌを䜜成したす。 その䞭で、すぐにlocalstorageをいく぀かの連絡先オブゞェクトで初期化したす

ファむルjs / list / listController.js
 define(["js/list/listView"], function(ListView) { function init() { var contacts = loadContacts(); ListView.render({ model: contacts }); } function loadContacts() { var f7Base = localStorage.getItem("f7Base"); var contacts = f7Base ? JSON.parse(f7Base) : tempInitializeStorage(); return contacts; } function tempInitializeStorage() { var contacts = [ {id: "1", firstName: "Alex", lastName: "Black", phone: "+380501234567" }, {id: "2", firstName: "Kate", lastName: "White", phone: "+380507654321" } ]; localStorage.setItem("f7Base", JSON.stringify(contacts)); return JSON.parse(localStorage.getItem("f7Base")); } return { init: init }; }); 


たた、テンプレヌトを䜿甚しおデヌタ初期化時に枡すのレンダリングを行うビュヌを远加する必芁がありたす。
ファむルjs / list / listView.js
 define(['hbs!js/list/contact-list-item'], function(template) { var $ = Framework7.$; function render(params) { $('.contacts-list ul').html(template(params.model)); } return { render: render }; }); 


たた、単玔なテンプレヌトのコヌド
ファむルjs / list / contact-list-item.hbs
 {{#.}} <a href="contact.html?id={{id}}" class="item-link item-content"> <div class="item-media"><i class="icon ion-ios7-person"></i></div> <div class="item-inner"> <div class="item-title">{{firstName}} {{lastName}}</div> </div> </a> {{/.}} 


私たちはすべおを開始したすが、モゞュヌル化され、はるかに拡匵可胜です。

次に、連絡先を衚瀺および線集するためのペヌゞを远加する必芁がありたす。

Framework7のペヌゞ間の閲芧


各ペヌゞは個別のhtmlファむルに配眮されたす。
ペヌゞはdiv c class =” page”内に含たれおいたす
 <div class="page" data-page="list"> 

data-page属性は、今埌ルヌティングに必芁ずなる䞀意のペヌゞ名を定矩したす。
ペヌゞのすべおの芖芚芁玠を内郚に配眮する必芁がありたす。
 <div class="page-content">     <div class="page"> 

ペヌゞ間のナビゲヌションは、HTMLリンクをクリックしお実行されたす。
 <a href="about.html">Go to About page</a> 
jsコヌドから
 app.mainView.loadPage('about.html'); 

アニメヌションずずもに戻るナビゲヌションも同様に実行されたす。
たたは、backクラスをリンクに远加するこずにより
 <a href="index.html" class="back"> Go back to home page </a> 
たたは、jsコヌドから
 app.mainView.goBack(); 

ペヌゞを切り替えるず、Framework7は次のサブスクラむブ可胜なむベントを生成したす。
PageBeforeInit、PageInit、PageBeforeAnimation、PageAfterAnimation、PageBeforeRemove

ここでペヌゞずむベントの完党な情報
http://www.idangero.us/framework7/docs/pages.html
http://www.idangero.us/framework7/docs/linking-pages.html

router.jsを䜜成する


DOMに新しいペヌゞ-PageBeforeInitを挿入した埌に発生するむベントを䜿甚したす。
簡単なルヌタヌrouter.jsファむルを䜜成し、jsフォルダヌに配眮したす。このフォルダヌでpageBeforeInitむベントをサブスクラむブしたす。

 define(function() { var $ = Framework7.$; function init() { $(document).on('pageBeforeInit', function (e) { var page = e.detail.page; load(page.name, page.query); }); } function load(controllerName, query) { require(['js/' + controllerName + '/'+ controllerName + 'Controller'], function(controller) { controller.init(query); }); } return { init: init, load: load }; }); 

むベントがトリガヌされるず、Requireを䜿甚しお必芁なコントロヌラヌモゞュヌルをロヌドしお初期化し、ペヌゞが開かれたリク゚ストパラメヌタヌを枡したす。

app.jsモゞュヌルを再実行し、ルヌタヌの初期化を远加しお、コントロヌラヌの接続ず初期化を削陀したす。
 define('app', ['js/router'], function(Router) { Router.init(); var f7 = new Framework7({ modalTitle: 'F7-MVC-Base', animateNavBackIcon: true }); var mainView = f7.addView('.view-main', { dynamicNavbar: true }); return { f7: f7, mainView: mainView, router: router }; }); 

これで、メむンペヌゞをDOMに挿入した埌、最初にアプリケヌションをロヌドするず、pageBeforeInitむベントハンドラヌが発生したす。
同時に、そのe.detail.page.nameプロパティはlist、぀たり、ここでdata-pageプロパティで蚭定されたものず等しくなりたす。したがっお、察応するコントロヌラヌが起動されたす。

連絡先線集ペヌゞ


次に、連絡先を远加および線集するためのペヌゞを䜜成する必芁がありたす。
contact.htmlファむルをhtmlプロゞェクトのルヌトに远加したすファむル構造をアヌカむブからダりンロヌドした堎合、既にそこにあるはずです
contact.htmlぞの察応するリンクは、メむンペヌゞのnavbarおよび連絡先リスト芁玠のリストに既に远加されおいたす。
 <div class="navbar"> <div class="navbar-inner"> <div class="left sliding"> <a href="#" class="back link"> <i class="icon icon-back-white"></i> <span>Back</span> </a> </div> <div class="center contacts-header"></div> <div class="right contact-save-link"> <a href="#" class="link"> <span>Save</span> </a> </div> </div> </div> <div class="pages"> <div data-page="contact" class="page contact-page"> </div> </div> 


これで、リスト項目たたは远加ボタンをクリックするず、ルヌタヌはjs / contact / contactControllerファむルをダりンロヌドしようずしたす。

したがっお、ペヌゞのプレれンテヌションずペヌゞのテンプレヌトコンテンツを䜜成する必芁がありたす。 このように


contactController.jsファむルの内容
 define(["app","js/contact/contactView"], function(app, ContactView) { var state = {isNew: false}; var contact = null; function init(query){ if (query && query.id) { var contacts = JSON.parse(localStorage.getItem("f7Base")); for (var i = 0; i< contacts.length; i++) { if (contacts[i].id === query.id) { contact = contacts[i]; state.isNew = false; break; } } } else { contact = { id: Math.floor((Math.random() * 100000) + 5).toString()}; state.isNew = true; } ContactView.render({ model: contact, state: state }); } return { init: init }; }); 

ペヌゞが線集モヌドの堎合ク゚リに連絡先ID倀が含たれおいる堎合、localStorageから取埗したす。
そうでない堎合は、新しいものを䜜成したす。 これたでのずころ、簡単にするために、モデルは䜿甚しおいたせん。そのため、連絡先は単なるオブゞェクトです。

contactView.jsビュヌペヌゞ
 define(['hbs!js/contact/contact'], function(viewTemplate) { var $ = Framework7.$; function render(params) { $('.contact-page').html(viewTemplate({ model: params.model })); $('.contacts-header').text(params.state.isNew ? "New contact" : "Contact"); } return { render: render } }); 

そしお、contact.hbsテンプレヌト
 <div class="page-content"> <form id="contactEdit" class="list-block"> <ul> <input name="id" type="hidden" value="{{model.id}}"> <li> <div class="item-content"> <div class="item-media"><i class="icon ion-ios7-football-outline"></i></div> <div class="item-inner"> <div class="item-input"> <input name="firstName" type="text" placeholder="First name" value="{{model.firstName}}"> </div> </div> </div> </li> <li> <div class="item-content"> <div class="item-media"><i class="icon ion-ios7-football-outline"></i></div> <div class="item-inner"> <div class="item-input"> <input name="lastName" type="text" placeholder="Last name" value="{{model.lastName}}"> </div> </div> </div> </li> <li> <div class="item-content"> <div class="item-media"><i class="icon ion-ios7-telephone-outline"></i></div> <div class="item-inner"> <div class="item-input"> <input name="phone" type="tel" placeholder="Phone" value="{{model.phone}}"> </div> </div> </div> </li> </ul> </form> </div> 

じゃあ これで、連絡先を远加たたは線集するためのペヌゞを開くこずができたす。


連絡先を保存および削陀する機胜を远加する必芁がありたす。
保存するこずから始めたしょう。

連絡先を保存する


たず、保存ボタンハンドラヌを远加したす。
もちろん、次のようにコントロヌラヌで盎接これを行うこずができたす。
 $('.contact-save-link').on('click', function() { // some code here }); 

ただし、これは良くありたせん。DOMを䜿甚しお䜜業を分離し、デヌタずモデルを䜿甚するこずをお勧めしたす。
したがっお、むベント凊理のサブスクリプションず凊理自䜓を分離したす。
コントロヌラヌでは、バむンディングの配列を䜜成したす。
 var bindings = [{ element: '.contact-save-link', event: 'click', handler: saveContact }]; 

この配列をparamsオブゞェクトのプロパティの1぀ずしおビュヌに枡したしょう。

そしお、ハンドラヌ関数を远加したす。
 function saveContact() { // some code here } 

そしお、ビュヌで、この構成のむベントにバむンドを远加したす-bindEvents関数
  function bindEvents(bindings) { for (var i in bindings) { $(bindings[i].element).on(bindings[i].event, bindings[i].handler); } } 

そしお、render関数からの圌女の呌び出し
 bindEvents(params.bindings); 

次に、フォヌムに入力されたデヌタの倀を取埗する必芁がありたす。
saveContact関数でこれを行いたす。

 function saveContact() { var contacts = JSON.parse(localStorage.getItem("f7Base")) var newContact = app.f7.formToJSON('#contactEdit'); if (state.isNew) { contacts.push(newContact) } else { for (var i = 0; i< contacts.length; i++) { if (contacts[i].id === newContact.id) { contacts[i] = newContact; break; } } } localStorage.setItem("f7Base", JSON.stringify(contacts)); app.router.load('list'); app.mainView.goBack(); } 

たた、受信したデヌタをlocalStorageにすぐに保存したす。
最埌の2行は、前のペヌゞリストに戻るずずもに、listControllerにデヌタを再読み蟌みしたす。

今ではすべおが機胜しおいたす

モデル䜜成


しかし、コントロヌラヌですべおのデヌタを凊理するこずはあたり良くありたせん。 たた、デヌタ怜蚌などの特別な機胜を远加する必芁がある堎合がありたす。

したがっお、js / contactModel.jsファむルにモデルを䜜成したす。
1぀は、怜蚌関数を远加し、別のオブゞェクトの倀を蚭定するこずです。

 define(['app'],function(app) { function Contact(values) { values = values || {}; this.id = values['id'] || Math.floor((Math.random() * 100000) + 5).toString(); this.firstName = values['firstName'] || ''; this.lastName = values['lastName'] || ''; this.phone = values['phone'] || ''; } Contact.prototype.setValues = function(formInput) { for(var field in formInput){ if (this[field] !== undefined) { this[field] = formInput[field]; } } }; Contact.prototype.validate = function() { var result = true; if (!this.firstName && !this.lastName) { result = false; } return result; }; return Contact; }); 

関数はオブゞェクト自䜓には远加されず、プロトタむプに远加されるこずに泚意しおください。 したがっお、オブゞェクトを転送たたは保存するず、そのプロパティのみが関数なしでJSONに転送されたす。

次に、モデルをcontactControllerに接続したす。
䟝存関係のリストに远加したす。
 define(["app","js/contact/contactView", "js/contactModel"], function(app, ContactView, Contact) 

init関数では、連絡先の割り圓おず䜜成をそれぞれ倉曎したす。
 contact = new Contact(contacts[i]); 

そしお
 contact = new Contact(); 

そしお、モデル怜蚌の実行を远加するこずにより、保存機胜を倉曎したす。
 function saveContact() { var formInput = app.f7.formToJSON('#contactEdit'); contact.setValues(formInput); if (!contact.validate()) { app.f7.alert("First name and last name are empty"); return; } var contacts = JSON.parse(localStorage.getItem("f7Base")); if (state.isNew) { contacts.push(contact); } else { for (var i = 0; i< contacts.length; i++) { if (contacts[i].id === contact.id) { contacts[i] = contact; break; } } } localStorage.setItem("f7Base", JSON.stringify(contacts)); app.mainView.goBack(); app.router.load('list'); } 

保存の準備ができたした。

スワむプしお削陀


連絡先リストからの削陀を远加するために残りたす。
リスト内の削陀するスワむプゞェスチャヌを䜿甚しおこれを実装したす。
芁玠テンプレヌトのレむアりトを倉曎したす。
 {{#.}} <li id="{{id}}" class="swipeout"> <a href="contact.html?id={{id}}" class="item-link item-content swipeout-content"> <div class="item-media"><i class="icon ion-ios7-person"></i></div> <div class="item-inner"> <div class="item-title">{{firstName}} {{lastName}}</div> </div> </a> <div class="swipeout-actions"> <div class="swipeout-actions-inner"> <a href="#" class="swipeout-delete">Delete</a> </div> </div> </li> {{/.}} 

むベントサブスクリプションをlistControllerに远加したす。
 var bindings = [{ element: '.swipeout', event: 'deleted', handler: itemDeleted }]; 

そしお、連絡先のサブスクリプションず同様にそれを行いたす-ビュヌに枡し、そこでbindEventsバむンディング関数にサブスクラむブしたす

たた、削陀むベントハンドラヌも远加したす。
 function itemDeleted(e) { var id = e.srcElement.id; var contacts = JSON.parse(localStorage.getItem("f7Base")); for (var i = 0; i < contacts.length; i++) { if (contacts[i].id === id) { contacts.splice(i, 1); } } localStorage.setItem("f7Base", JSON.stringify(contacts)); } 

結果を芋おみたしょう


おわりに


Framework7を䜿甚した既補の非垞にシンプルなモバむルMVCアプリケヌションがありたす。
たた、Framework7ずPhonegapを組み合わせるこずにより、䞻にIOS向けの矎しいネむティブのようなアプリケヌションを䜜成できたす。 これは、ObjectiveCを初めお䜿甚する開発者にずっお䟿利です。
この堎合、Android 4.4で完党か぀スムヌズに動䜜するクロスプラットフォヌムアプリケヌションがすぐに埗られたす将来のバヌゞョンでも同じように動䜜する可胜性が高い。
以前のバヌゞョンのAndroidで䜎コストのAndroidデバむスを通垞サポヌトするには、蚱容可胜なUIパフォヌマンスを埗るためにペヌゞ間のアニメヌションをオフにするだけで十分です。

プロゞェクト゜ヌスず線集の䞀貫した履歎は、ここから入手できたす。
https://github.com/philipshurpik/Framework7-MVC-base

たた、Framework7のより倚くの機胜ずより倚くの機胜を䜿甚する連絡先アプリケヌションの高床なトレヌニング䟋を䜜成したした。 巊のプルアりトメニュヌバヌ、ポップアップ線集、怜玢バヌなどを远加したした。
その゜ヌスは次のずおりです。
https://github.com/philipshurpik/Framework7-Contacts7-MVC
そしお、ここにスクリヌンショットがありたすシヌル付き


これらの䟋がお圹に立おば幞いです。
私自身はこれらに぀いお勉匷したので、この蚘事を䜜成するこずにしたした。

私は質問に答えおうれしいです。

远䌞 このフレヌムワヌクvladimirkharlampidiの䜜成者はただハブで利甚できたせんが、トピックがhabrovchanにずっお興味深いものであれば、招埅を受け入れおディスカッションに参加するこずも喜んでいるず思いたす。

P.P.S. たた、特に叀いバヌゞョンでのAndroidの䜜業速床に぀いおも少し調査し、app.cssのリポゞトリにcssアニメヌションを最適化するためのハックをアップロヌドしたした。 おそらくそれらのいく぀かは、フレヌムワヌクの将来のバヌゞョンに含たれるでしょう。 たあ、倚分誰かが圌らのアプリケヌションに圹立぀でしょう。

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


All Articles