-
Notifications
You must be signed in to change notification settings - Fork 0
Overview
Phalanxは、Viewの役割を分割することに注力したライブラリですが、薄いラッパーとしてまとめているため、フルスタック系の機能は備えていません。
###Phalanx.Layout
Layoutは、WebMVCにおけるControllerに似た振る舞いをもったViewの単位です。Viewを継承しているため、Viewとしての基本機能もすべて備えています。次に挙げるregions
を利用するほどでもない簡単なページであれば、Layoutのみで完結することも可能です。
####regions
Layoutには、regions
と呼ばれるViewの管理領域が存在します。HTML側で、管理領域の対象となる要素に、id
を割り当てます。
<body>
<header id="js-reg-header">
<!-- HEADER -->
</header>
<div id="js-reg-content">
<!-- MAIN CONTENT -->
</div>
<footer id="js-reg-footer">
<!-- FOOTER -->
</footer>
</body>
クラスの宣言時にregions
プロパティに、名前とセレクタのペアで管理領域を定義します。Layoutをnewしたあと、それぞれの管理領域にViewをassign
することで、Layout内に表示すべき要素を配置していきます。
var AcmeLayout = Phalanx.Layout.extend({
regions: {
header : '#js-reg-header',
content: '#js-reg-content',
footer : '#js-reg-footer'
},
onChange: function(regionName, newView, oldView) {
// Called when regions view was changed.
}
});
var layout = new AcmeLayout({el: 'body'});
layout.assign('header', new HeaderView());
layout.assign('content', new ContentView());
layout.assign('footer', new FooterView());
Layoutでは、管理領域(region)にViewを割り当てる(assign)する際、$.ajax()
やBackbone.Model.fetch()
などで取得したデータオブジェクトを、各Viewに渡すことで表示データについても管理を行います。
LayoutとViewの関係は言い換えれば、MainViewとSubViewのような関係と考えても良いでしょう。
###Phalanx.View
一般的なViewの拡張です。components
とlisteners
という宣言用のプロパティをもちますが、これらは後述するPhalanx.Component
と関連する機能であるため、次項で説明します。
Viewの中にある特定の要素を取得する際のショートハンドとして、ui
プロパティによる宣言が用意されています。
<section id="js-list">
<div data-ui="list">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
</section>
data-ui="list"
という属性で、listという識別子を与えます。
var ListView = Phalanx.View.extend({
ui {
list: null
},
onAfterRender: function() {
var ul = this.ui.list; // DOMElement
var $ul = this.$ui.list; // jQuery|Zepto
}
});
var listView = new ListView({el: '#js-list'});
JS側でもui
プロパティにlistを宣言しておくことで、this.ui.list
またはthis.$ui.list
とアクセスできるようになります。
本来、lookupUiメソッドは、Backbone.setElement
相当の処理が行われる際に自動で実行されていますが、JavaScriptでテンプレーティングを行ったあとは、別途lookupUiを実行して要素を拾い直す必要があります。
###Phalanx.Component
ComponentはViewのような振る舞いを持ちますが、やや特殊な単位として扱われます。実体としてもComponentは独自にクラス定義されており、Backbone.View
を継承していないため、似ているだけで別のものです。
目安として下記のような要素をComponentとして扱います。
- ページ内に不特定多数しうる要素
- ユーザーアクションによって通信処理などを伴い、ステートの変化によって表示状態も変わる要素
- ViewModelのような振る舞いを持ちうる要素
Componentのクラス定義においては、events
を利用して、Component内のイベントを定義することができます。ただし、ここでのイベントは、Componentを管理するView(View側の記述は後述)のeventsとマージされて、View.elの委譲イベントとして管理されます。そのため、ひとつのViewで複数のComponentを管理する際、指定されたセレクタがView自体のeventsや他のComponentのeventsと衝突すると、正常に動作しません。
var LikeBtnComponent = Phalanx.Component.extend({
// Events are delegated to the View.
events: {
'click .js-like': 'doLike'
},
// Elements with the data-ui are auto stored when create component instance.
ui: {
count: null
},
// Increment like count when POST completed.
doLike: function() {
$.post('/api/like', {id: this.id}, function() {
this.$ui.count.text(parseInt(this.$ui.count, 10) + 1);
}.bind(this));
}
});
Componentを利用するViewでは次のように定義します。ここでは、components
にlikeBtn
という名前でLikeBtnComponent
クラスを指定しています。
var AcmeListView = Phalanx.View.extend({
// Specified name here, will specify as data-component attr in the HTML.
components: {
'likeBtn': LikeBtnComponent
}
});
var listView = new AcmeListView({el: '#js-list'});
HTMLは次のようになります。
<section id="js-list">
<ul>
<li>
<a>screen_name</a>
<p>Lorem ipsum dolor sit amet, consectetur adipisici…</p>
<!-- Events that have been delegated to occur, creation of component is delayed. -->
<div data-component="likeBtn">
<button class="js-like"><button>
<span data-ui="count">3</span>
</div>
</li>
...
..
.
</ul>
</section>
Viewのcomponents
に指定したコンポ—メント名(エイリアス)をdata-component
属性として、要素に指定します。それによって指定された要素が、Componentとして管理されるようになります。
Componentは遅延してインスタンスが生成されます。上記の例では、click .js-like
が最初に発生したときに初めて、その要素のComponentが生成されます。他のViewから生成済みまたは生成前のComponentのインスタンスにアクセスする用途は想定されていません。ViewとはComponentからtriggerされるイベントによってメッセージングするようにします。
なお、例のHTMLのようにリストアイテムにおけるいいねボタンのように、ひとつのViewの中で繰り返し出現する要素をComponentで管理した場合は、要素ごとに個別のComponentインスタンスが生成されるため、要素とComponentインスタンスは常に1:1の関係になります。
イベントメッセージングは、Componentがtriggerしたものを、Viewがlisteners
プロパティの宣言で受け取ります。
var ReadMoreBtnComponent = Phalanx.Component.extend({
events: {
'click [data-ui="btn"]': 'onClickBtn'
},
onClickBtn: function(evt) {
var href = evt.currentTarget.getAttribute('href');
$.get(href, function(resp) {
this.trigger('success', resp);
}.bind(this));
}
});
ここではReadMoreBtnComponent
が、もっと見るAPIへのリクエストに成功した際に、success
イベントを発信するようなイメージで実装されています。
これを、View側では次のようにlisteners
プロパティに'{イベント名} {コンポーネントエイリアス}': '{リスナーメソッド名}'
を指定することで、任意のComponentが発したイベントを、View側で受け取れるようになります。内部的には、view.listenTo(component, 'event', view.listenerMethod
が処理されるので、view.stopListening()
を手動で実行した場合はイベントのリスニングが止まる点に注意してください。
var ListView = Phalanx.View.extend({
components: {
'moreBtn': ReadMoreBtnComponent
},
listeners: {
'success moreBtn': 'renderMore'
},
ui {
list null
}
renderMore: function(html) {
this.$ui.list.append(html);
}
});
var listView = new ListView({el: '#js-list'});
Componentの操作結果がComponent要素の外側に干渉する場合は、属しているViewにイベントを通じて処理を委譲するように実装すべきです。
なお、Componentはevents
のほか、el
・$el
・ui
・$ui
についてViewと同様の設定を行うことができます。