knockoutjsで複雑なアプリケーションを作成する

このようなライブラリknockout.jsがあります。 それは初心者向けの良いチュートリアルとたくさんの明確な実用例で他とは異なります。 細いMVVMモデル、宣言関係などもあります。



要するに、私のように、あなたがこのライブラリで遊んで、美しい型を書いて、それを気に入ったなら、あなたはこのすべてを実際のプロジェクトで使いたかったのです。 そしてここに問題があります-実際のプロジェクトには複数の型があります。 そして、もしそのようなツールなら、あなたは単一のウェブページアプリケーションが欲しいだけです。 また、1つのコントローラーを作成し、1つのページですべてのテンプレートを完成させることも愚かであり、ブレーキがかかります。



猫の下で、複雑なアプリケーションの基礎を説明します。 それ自体はまったく複雑ではありませんが、モジュール式で拡張可能であり、テンプレートとモデルは動的にロードされます。 アイデアはこのプレゼンテーションで見張られました-http : //www.knockmeout.net/2012/08/thatconference-2012-session.html、プレゼンテーションコードはgithub- https://github.com/rniemeyer/SamplePresentationで利用可能-これに基づいてコードを書きます。



リトリート


最初に、1ページに複数のフォームをまとめることについて言及しました。これは私たちの方法ではなく、完全に通常の解決策です。 2つの置換可能なテンプレートの小さな単一ページアプリケーションの例は、ホームサイトのチュートリアル( http://learn.knockoutjs.com/#/?tutorial=webmail)にあります。



別の隠れ家


data-bind = "..."属性でhtmlテンプレートで直接リンクを宣言するというアイデアのために、その構文をforるノックアウトがあります。 これは、onclick = ".."にJavaScriptコードを挿入して90年代に戻るようなものです。 はい、すべてはevalによって機能します。 クレームは正当化されます-型バインディングのデバッグで問題が発生する可能性があります
<div data-bind=”value: name, event: { focus: function() { viewModel.selectItem($data); }, blur: function() { viewModel.selectItem(null); }”></div>
      
      



htmlコードのクリーンさのための戦いは、この記事( http://www.knockmeout.net/2011/08/simplifying-and-cleaning-up-views-in.html)で詳細に議論されています 。 dependentObservableを使用し、カスタムバインドを行い、匿名関数を回避する必要があります。 bindingProviderを記述するか、このhttps://github.com/rniemeyer/knockout-classBindingProviderを使用できます



目的


ノックアウトの例をベースとして実際のアプリケーションを作成すると、巨大なモノリシックモデルが得られ、それらをどのように開発およびデバッグするかが明確でない場合があります。 私の例の主な目標は、コードを目に見える部分に分割する1つの方法を示すことです。



最後に何ができるかを説明します。 templatesフォルダーのhtmlファイルにテンプレートがあり、modulesフォルダーの対応するファイルにknockout-jsバインディングがあります。 特定のアクションでは、メソッドが起動され、require.jsを使用して目的のdivにテンプレートとコードがロードされます。 この例の最終コードはhttps://github.com/Kasheftin/ko-testにあります



StringTemplateEngine


Knockoutjsはそのままで、 匿名と名前付きの2つのテンプレート操作方法をサポートしています。 例:
 //  <div data-bind="foreach: items"> //   <li> <span data-bind="name"></span> <span data-bind="price"></span> </li> </div> //  <div data-bind="template: {name:'person-template',data:person}"></div> <script type="text/html" id="person-template"> //   <h3 data-bind="text: name"></h3> <p>Credits: <span data-bind="text: credits"></span></p> </script>
      
      





すべての場合において、テンプレートは既存のdomツリーの一部です。 私たちの場合、コードはサーバーから文字列として送られ、最も有機的な解決策はテンプレートエンジンを書くことです。 この記事http://www.knockmeout.net/2011/10/ko-13-preview-part-3-template-sources.htmlから理論を学ぶことができます 。 おそらく、優れたターンキーソリューションhttps://github.com/ifandelse/Knockout.js-External-Template-Engineがありますが、最初に書いたプレゼンテーションに基づいて独自のソリューションを作成します。



これがプレゼンテーションのstringTemplateEngineコードです-https ://github.com/rniemeyer/SamplePresentation/blob/master/js/stringTemplateEngine.js 好きではないもの:グローバル配列ko.templatesが使用され、そこにダウンロードされたテンプレートが書き込まれ、テンプレートはそれらが呼び出される名前を考え出す必要があります。 require.jsはキャッシュを処理するため、この配列は使用しません。 stringTemplateEngineは次のように呼び出されます。
 <div data-bind="with: currentState"> <div data-bind="template: {html:html,data:data}"></div> </div>
      
      



つまり、htmlプロパティが指定されている場合はstringTemplateEngineが呼び出され、そうでない場合は実行のために標準のノックアウトに戻ります。 currentStateは、htmlコードを含むhtmlプロパティと、おそらくモジュールオブジェクトを含むデータを持つオブジェクトです。



したがって、新しいtemplateSourceを作成します。
 ko.templateSources.stringTemplate = function(element,html) { this.domElement = element; this.html = ko.utils.unwrapObservable(html); } ko.templateSources.stringTemplate.prototype.text = function() { if (arguments.length == 0) return this.html; this.html = ko.utils.unwrapObservable(arguments[0]); }
      
      





そして、nativeTemplateEngineオブジェクトからmakeTemplateSourceメソッドを再定義します。 今のところバイクはありません-makeTemplateSourceの再定義はドキュメントに書かれています。 ただし、組み込みのmakeTemplateSourceは、入力としてtemplateとtemplateDocumentのみを受け入れます。templateは、テンプレートの名前(存在する場合)、および別の場合には現在のdomへのリンクです。 型の混乱は良い解決策ではありません。 さらに、StringTemplateEngineを接続するには、name属性ではなく、html属性を確認する必要があります。 そのようなデータはありませんが、renderTemplateメソッドに渡されるため、それも再定義します。
 var engine = new ko.nativeTemplateEngine(); //   renderTemplate -   makeTemplateSource    engine.renderTemplate = function(template,bindingContext,options,templateDocument) { var templateSource = this.makeTemplateSource(template, templateDocument, bindingContext, options); return this.renderTemplateSource(templateSource, bindingContext, options); } //  ,   2  engine.makeTemplateSource = function(template, templateDocument, bindingContext, options) { //  engine  knockout- if (typeof template == "string") { templateDocument = templateDocument || document; var elem = templateDocument.getElementById(template); if (!elem) throw new Error("Cannot find template with ID " + template); return new ko.templateSources.domElement(elem); } //  stringTemplateEngine,  options else if (options && options.html) { return new ko.templateSources.stringTemplate(template,options.html); } else if ((template.nodeType == 1) || (template.nodeType == 8)) { //  engine   knockout- return new ko.templateSources.anonymousTemplate(template); } else throw new Error("Unknown template type: " + template); } ko.setTemplateEngine(engine);
      
      





renderTemplateをオーバーライドしても、makeTemplateSourceはその中と、ここで説明する別のrewriteTemplateメソッドでのみ呼び出されるため、ノックアウトを中断しません: https : //github.com/SteveSanderson/knockout/blob/master/src/templating/templateEngine.js ただし、nativeTemplateEngineでallowTemplateRewriting = falseが設定されているため、後者は呼び出されません。



stringTemplateEngineの完全なコードは、 https//github.com/Kasheftin/ko-test/blob/master/js/stringTemplateEngine.jsにあります



State.js


次に、 state.jsを記述します。これは、初期化されると、指定されたテンプレートとモジュールをロードするオブジェクトです。 状態は相互にネストされるため、アプリケーション自体も状態になります。状態は、フォームに他の状態をロードするメニューで囲まれます。



 define(["knockout","text"],function(ko) { return function(file,callback) { var s = this; s.callback = callback; s.data = ko.observable(null); s.html = ko.observable(null); require(["/js/modules/" + file + ".js","text!/js/templates/" + file + ".html"],function(Module,html) { s.data(typeof Module === "function" ? new Module(s) : Module); s.html(html); if (s.callback && typeof s.callback === "function") s.callback(s); }); s.setVar = function(i,v) { var data = s.data(); data[i] = v; s.data(data); } } });
      
      





これがすべてのコードです。 AMDスクリプト、knockoutおよびrequire.jsテキストプラグインを使用してhtmlテンプレートをロードします。 入力は、stringTemplateEngineで必要な2つの監視可能な変数dataとhtml内のファイル名とコールバックメソッドです。 setVarメソッドも作成されました。複数の状態が同時にページ上に存在し、データを交換する必要があります。 通常、ルート状態への参照はsetVarに渡され、そこからすべてが取得されます。



Main.js
メインページのHTMLコードは、2、3行で構成されています。
 <body> <div class="container" data-bind="template:{html:html,data:data}"></div> <script type="text/javascript" data-main="/js/main" src="/lib/require/require.js"></script> </body>
      
      





ページがロードされてrequire.jsが実行された後に実行されるメインファイルを作成します。
 require(["knockout","state","stringTemplateEngine"], function(ko,State) { var sm = new State("app",function(state) { ko.applyBindings(state); }); });
      
      







App.js、App.html


アプリケーション自体も状態であるとすでに書きました。 すべてが互いに埋め込まれています。 ページはメニューとコンテンツで構成されます。 そのため、ページレイアウトのhtmlコードはテンプレート/ app.htmlにあり、メニューとコンテンツの初期化はモジュール/ app.jsにあります。
 // templates/app.html: <div class="row"> <div data-bind="with:menu"><div class="span3 menu" data-bind="template:{html:html,data:data}">Menu</div></div> <div data-bind="with:currentState"><div class="span9 content" data-bind="template:{html:html,data:data}"></div></div> </div>
      
      



 // modules/app.js: define(["knockout","state"],function(ko,State) { return function() { var app = this; this.menu = new State("menu",function(state) { // ,  callback-,    app,  app       state- state.setVar("app",app); }); this.currentState = ko.observable(null); } });
      
      







Menu.js、Menu.html


メニューの別の例を挙げます。 リンクをクリックすると、別の状態のコンテンツ、stateアプリにあるcurrentState変数が変更されます。 これへのアクセスは、メニューが初期化されたときにアプリがsetVarに送信されたためです。
 // menu.html <ul class="nav nav-list"> <li><a href="javascript:void(0);" data-bind="click:gotoSample" data-id="1">Hello World</a></li> <li><a href="javascript:void(0);" data-bind="click:gotoSample" data-id="2">Click counter</a></li> <li><a href="javascript:void(0);" data-bind="click:gotoSample" data-id="3">Simple list</a></li> ...
      
      



 // menu.js: define(["jquery","knockout","state"],function($,ko,State) { return function() { var menu = this; this.gotoSample = function(obj,e) { var sampleId = $(e.target).attr("data-id"); var newState = new State("samples/sample" + sampleId,function(state) { state.setVar("app",menu.app); //     app.currentState, ..   observable- currentState,     menu.app.currentState(state); }); } } });
      
      





以上です。 コードはすでにモジュールに分割されています。 異なる缶のサンプルのページは、 ライブのサンプルからコピー&ペーストされ、amd-formでのみ装飾されています。 その後、すべての初期化ajax-sがロードされますが、これらはすでにstate-sにある「ローカル」な詳細です。



もう一度、サンプルの最終コードhttps://github.com/Kasheftin/ko-testへのリンクを提供します



All Articles