HotMilk-Moustacheテンプレートを便利に整理するためのライブラリ

今週中の投稿^HabréのJavaScriptの月の西。

1ページのWebアプリケーションの開発に関する記事を読んだ後、手が届くとICanHazライブラリをブックマークし、 それを引き裂いて少し仕上げました。 そして、いつものように、長い箱に入れておきます。

そして今、先週末の直前に風邪をひいたため、コンピューターで2日間の無料休暇を予期せず受け取ったので、私はこのベンチャーに戻りました。 その結果、化粧品のドーピングの代わりに、モーター付きのささやかな自転車を手に入れました。

ICanHazは、ブラウザでJavaScriptが使用する小さな口ひげテンプレートを簡単に整理できることを思い出してください。 このライブラリを使用したテンプレートのレンダリングは、単純な関数呼び出しになります。 また、テンプレートの半分をシールドする必要がなくなりました。なぜなら、 そのテキストは、HTML <script>タグに直接書き込むことができます



なんで?


実際には、ICanHazは単純です。テンプレートエンジン(mustache.js)があり、ID属性の名前を持つ<script>タグのHTMLコードに直接記述されたテンプレート(完全および部分)があり、ページがロードされるときにグローバルichオブジェクトがありますテンプレート名を持つメソッドが追加されます。 さらに、オブジェクトを対応するメソッドに渡すと、出力はテキストとしてレンダリングされます。
$('#myDiv').html(ich.myTemplateName(objModel)); 

テンプレートのもう少し繊細な^ W組織が欲しかった。 たとえば、最も単純なオンラインストアには、いくつかの種類の商品があります(書籍、雑誌、映画、音楽など)。 それらのそれぞれに対して、リストテンプレート、各リストに対してアイテム、アイテムに対して価格が割り当てられた通貨(価格)を定義する必要があります。 そして、右側に割引列を追加し、そこに少し異なる価格を表示したい...

通常、テンプレートのフラットなコレクションを使用することはもちろん可能ですが、何らかの方法でテンプレートを階層に整理したり、部分的な継承であっても(たとえば、すべての種類の商品の価格形式を決定したり、ブックテンプレート-著者の名前をフォーマットする方法)。 また、<script>ブロックをコピーせずに1つのテンプレートを複数の場所に追加できることは素晴らしいことです(たとえば、年の週番号を含む雑誌や新聞のリリース日テンプレートを作成し、書籍や映画の場合は1年のみ)。

これらのアイデアから何かが生まれました。

HotMilkの説明


このライブラリは、 CoffeeScript Moustache実装であるMilkテンプレートエンジンに基づいています。 牛乳はずっと前に選ばれましたが、Mustache仕様へのより良いサポートのために、どのような理由で覚えているかはわかりません。 名前はそれぞれ、Milkから来て行きました(さらにHTMLに組み込まれたテンプレートのヒント)。

jQuery、MooTools、およびフレームワークのないブラウザー用のHotMilkバージョン、およびDOMからテンプレートをロードしない基本バージョン(必要に応じて少なくともサーバー上で使用可能)がアセンブルされています。

ICanHazと同様に、使用を開始する前に、テンプレートライブラリに記入する必要があります。 これは、$ addTemplateメソッドを使用して行われます。
  HotMilk.$addTemplate('path/to/template', 'template {{text}}'); HotMilk.$addTemplate('path/to#partial', '...'); 

テンプレートは任意の順序で追加および削除できるため、その場合はサーバーから非同期にプルできます。 テンプレートは、ページの読み込みの最後に<script>タグから自動的に組み立てることもできます。 テンプレートをロードするには、「text / x-mustache-template」に設定し、data-hotmilk-path属性を指定する必要があります(ちなみに、HTML5仕様ではスクリプトのdata- *属性の使用が禁止されていないため、ここでも妥当性が考慮されます):
  <script type="text/x-mustache-template" data-hotmilk-path="books/list#item"> <a href="books/{{id}}"><b>{{title}}</b> by {{#author}}{{>author}}{{/author}}</a> </script> 

例のテンプレートは部分的です。 「books / list」テンプレートをレンダリングする際の一部として使用されます。

data-hotmilk-path属性では、コロンで区切られた複数のパスを追加できます(いくつかの独立したコピーが作成されます)。

テンプレートを整理するすべての原則は、いくつかの簡単な点で説明できます。


githubのHotMilkリポジトリに小さなデモがあります

誰かが突然実際にライブラリを使用したい場合は、少なくともテストの健全なセットが存在せず、おそらく長期間はないので、バグがあるかもしれないことに注意してください。

内部:実装機能


いくつかのコメントを付けて、ソースの一部について説明します。

部分的なコレクションクラス。 この設計により、親サブパターンのテンプレートによる継承が実装されています。
  PartialsCollection = function(parentPartialsCollection) { var ctor = function() {}; ctor.prototype = parentPartialsCollection || PartialsCollection.prototype; return new ctor(); }; 

単純に機能します。呼び出しごとに、以前のpartial'ovのセットをプロトタイプとして使用して新しいクラスを作成します。 最初の起動時に、プロトタイプを取得します。 したがって、この方法で作成された各オブジェクトは、PartialsCollectionクラスのインスタンスになり、さらに、親チェーン内のすべてのコレクションのプロパティを保持します。
  var a = new PartialsCollection(); a.t1 = "template 1" // a.t1 === 'template 1'; a.hasOwnProperty('t1') === true; var b = new PartialsCollection(a); // b.t1 === 'template 1'; b.hasOwnProperty('t1') === false; 

テンプレート関数のファクトリ。 ライブラリのほぼ中心。 PartialCollectionクラスのテンプレートとインスタンスを取得し、モデルを取得してレンダリングされた文字列を返す関数を返します。
  var createTemplatingFunction = function(template, partialsCollection) { return function(data) { return Milk.render(template, data, function(partialName) { if(partialsCollection[partialName] && partialsCollection[partialName].$value != null) { return partialsCollection[partialName].$value; } else { throw new Error("Unknown partial: " + partialName); } }); }; }; 

テンプレート、モデル、および名前でサブパターンを検索する機能は、コレクションにそのようなフィールドがあるかどうか、および$値があるかどうかを確認するだけで、Milk.render関数に渡されます。

そして、この関数ファクトリーの助けを借りて、ツリーの完全なノードが作成されます:
  var createTemplateNode = function(template, partialsCollection) { partialsCollection = partialsCollection || new PartialsCollection(); var templatingFunction = createTemplatingFunction(template, partialsCollection); templatingFunction.$ = partialsCollection; //    Function.prototype      ... templatingFunction.$addTemplate = addTemplate; templatingFunction.$removeTemplate = removeTemplate; return templatingFunction; }; 

テンプレートの追加をランダムな順序で実装するには、別のトリックを実装する必要がありました。 たとえば、「path / to#partial」の一部が追加されたが、テンプレートがまだ存在せず、「to」がテンプレートであるか別のグループであるかが不明な場合があります。 この状況を解決するために、パスは常にグループから構築され、テンプレートをアタッチする必要があることが判明した場合、ノードは保存されている部分的なものに置き換えられます。
  var addNormalTemplate = function(root, path, template) { if(path.length === 0) { throw new Error("Couldn't create template: name must not be empty"); } var node = nodeBuildPath(root, path.slice(0,-1)), name = path[path.length - 1]; if(hasOwnProperty(node, name)) { //   ,       , //  ,   partial' if(node[name] instanceof GroupNode && nodeIsEmpty(node[name])) { node[name] = createTemplateNode(template, node[name].$); } else { //  -  (   ) throw new Error("Couldn't add template: node " + path.join('/') + " already exists"); } } else { //    ,      , //      node[name] = createTemplateNode(template, new PartialsCollection(node.$)); } }; 

簡単なサブパターンで:
  var addPartialTemplate = function(root, path, partialName, template) { //   ,   var node = nodeNavigatePath(root, path) || nodeBuildPath(root, path); if(hasOwnProperty(node.$, partialName)) { throw new Error("Couldn't add partial: node " + path.join('/') + "#" + partialName + " already exists"); } // , : .. partial     , //       , //        node.$[partialName] = createPartialTemplate(template, node.$); }; 

後続の置換でグループを作成してテンプレートを作成する場合、削除は反対のアクションです:最初に、テンプレートが保存されたサブパターンのコレクションを持つグループに置換され、次にパスが最後からクリアされます(テンプレートとpartial'ovが残されていないグループは削除されます)。

熱心な読者は、hasOwnPropertyを呼び出す曲線的な方法に気付いています。 hasOwnPropertyという名前のテンプレートから外れないように、これは再保険のために再度行われます。 もちろん、状況は妄想ですが、まあ、同時に、これは圧縮にプラスの効果があるはずです。
  var hasOwnProperty = function(obj, propName) { return Object.prototype.hasOwnProperty.call(obj, propName); }; 

一般的に、考慮される主なポイントは、希望する人はgithubで残りの部分を学習できます。 同じ場所で既製のアセンブリをダウンロードできます。

誰かが役に立つといいな。 ご清聴ありがとうございました!

参照資料




UPD:記事を書く価値がありましたが、Functionから継承しようとしたときに本当に台無しになったことが明らかになりました(これも可能ですか?)。
一般に、修正されました。 編集後、機能は影響を受けず、内部のみがやり直され、影響を受ける場所の投稿が更新されました。

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


All Articles