2012.12.14
Sencha Advent Calendar 2012 – 12月14日 BlogViewerやってくよー!
Sencha Advent Calendar 2012 12月14日分です :)
SenchaUG 勉強会 第2回@東京の使い回しになっちゃいますが、WordPressの簡易スマホ化をおさらいします ;)
Sencha SDK Tools
今回はプロジェクトを作成する際に、SenchaSDKToolsを使用します
$ cd SenchaTouchのSDKへのPATH
$ sencha app create アプリ名 アプリを作成したいフォルダへのPATH

こんな感じのプロジェクトが出来ます :)
今回のプロジェクトで作るファイル
- app.js [既存ファイルを編集]
- /app/controller/Article.js [全体のController]
- /app/model/Article.js [ブログデータのModel]
- /app/store/Articles.js [Modelを格納するStore]
- /app/view/Detail.js [ブログ詳細ページ表示用のView]
- /app/view/List.js [ブログ一覧ページ表示用のView]
- /app/view/Main.js [既存ファイルを編集]
app.js
app.js内は非常にシンプルで、今回のアプリケーションに利用する各種部品の登録を行います。
viewportの設定はやっておくと、ブラウザで表示した際に領域稼げるので良いと思います。
ただ、もしかするとviewportの設定をしておくとブラウザからUA偽装してアプリケーションを表示する際に、Console.jsでエラー吐いて止まるかもしれないです。 :(
Ext.application({
name: 'SenchaUG', // アプリケーションのネームスペース
viewport: {
autoMaximize: true // ブラウザで表示する際に、ヘッダーの検索バーを隠す
},
controllers: ['Article'], // Controllerの設定
views: ['Main', 'List', 'Detail'], // アプリケーションで使用するViewの設定
stores: ['Articles'], // アプリケーションで使用するStoreの設定
models: ['Article'], // アプリケーションで使用するModelの設定
launch: function() {
Ext.fly('appLoadingIndicator').destroy();
Ext.Viewport.add(Ext.create('SenchaUG.view.Main'));
}
});
WordPress側に少し細工 :)
今回はWordPressにJSONを吐き出すように設定を行い、そのJSONを利用してブログデータの生成を行います。
WordPressの/wp-content/themes/テーマ名/functions.phpを編集して下記を追加すると、http://tnker.com?json=true というとうな形でJSONデータを取得することが可能になります。
global $wp;
$wp -> add_query_var('json');
function json_format($wp)
{
if (get_query_var('json') != 'true') return;
global $posts;
header('Content-Type: application/json');
echo json_encode($posts);
exit;
}
add_action('wp','json_format');
/app/model/Article.js
上記でJSONを取得出来た際に確認すると分かるのですが、このような形で1つ1つ記事データのオブジェクトが配列に入った状態で渡されます。この配列の中に入っているオブジェクトがModel、このオブジェクトを格納している配列がStoreというとイメージしやすいかもしれませんね。

今回のBlogViewerで利用したいプロパティを名を確認してモデルを作成していきます :]
JSONデータから利用するのは、id, name, post_date, post_title, post_content を利用していきます。内容的には通常利用されるid、あとはどれも基本的なものですね。
Ext.define('SenchaUG.model.Article', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'id', type: 'int'},
{name: 'name', type: 'string'},
{name: 'post_date', type: 'date'},
{name: 'post_title', type: 'string'},
{name: 'post_content', type: 'string'}
],
proxy: {
// 実際はここがWP側の記事一覧などの取得
type: 'ajax',
url: 'data/data.json',
reader: {
type: 'json'
}
}
}
});
データの取得を行うにはproxyプロパティを設定する必要があります。
proxyのurlにローカルデータが指定してありますが、実際はjsonを取得するAPIのurlを指定します。
proxy: {
// 実際はここがWP側の記事一覧などの取得
type: 'ajax',
url: 'data/data.json', → 'http://tnker.com?json=true',
/app/store/Articles.js
取得したデータ(モデル)を格納しておくStoreを用意します。configプロパティで、このStoreで利用するModelのクラスを定義します。
また、autoLoadプロパティをtrueに設定しておくことにより、このストアがインスタンス化された際に自動的にデータの取得を行ってくれます。
Ext.define('SenchaUG.store.Articles', {
extend: 'Ext.data.Store',
config: {
model: 'SenchaUG.model.Article',
},
autoLoad: true
});
/app/view/List.js
ブログ一覧のViewを作成します。
aliasプロパティを利用してxtypeを指定します。これは後ほど、アプリケーション全体のViewへこのListlViewを登録する際に、xtypeを利用して登録するため。
ここで使っているExt.Listは、Storeを利用して表示します。Store内にデータがある場合そのデータを利用して、指定したテンプレート内(itemTpl)に表示したいデータを指定します。(下記参照)
Ext.define('SenchaUG.view.List', {
extend: 'Ext.List',
alias: 'widget.articlelist',
config: {
itemId: 'articleList',
store: 'Articles',
cls: 'article-list',
itemTpl: [
'<div class="article-div">',
'<h1>{post_title:ellipsis(28)}</h1>',
'<div class="value">{post_content:ellipsis(100)}</div>',
'</div>'
]
}
});
{post_title:ellipsis(28)}は、記事のタイトルデータの文字列を28バイトでトリムしています。このitemTplはXTemplateで構成されており、このほかにも様々なフォーマットの書式が用意されています。
また、このフォーマット用の関数などを自分で作ることも可能です。:) Ext.util.Format
/app/view/Detail.js
ブログ詳細のViewを作成します。
aliasプロパティを利用してxtypeを指定します。これは後ほど、アプリケーション全体のViewへこのDetailViewを登録する際に、xtypeを利用して登録するため。
ここは、ListViewから渡された記事データの詳細表示に使用するViewになっています。
{post_content}が記事の詳細ページのコンテンツになっている。
Ext.define('SenchaUG.view.Detail', {
extend: 'Ext.Container',
alias: 'widget.articledetail',
config: {
cls: 'article-detail',
itemId: 'articleDetail',
scrollable: {
direction: 'vertical'
},
tpl: [
'<div class="detail-div">',
'<h2>{post_title}</h2>',
'<div class="value">{post_content}</div>',
'</div>'
]
}
});
/app/view/Main.js
ListViewおよびDetailViewで指定したxtypeをここで利用していきます。
items内でアプリケーション全体のツールバーを設置し、指定したxtypeを設定します。
Ext.define("SenchaUG.view.Main", {
extend: 'Ext.Container',
fullscreen: true,
config: {
layout: {
type: 'card',
animation: {type: 'slide'}
},
itemId: 'main',
items: [{
docked: 'top',
xtype : 'titlebar',
title : 'SenchaUG',
items: [{
xtype: 'button',
itemId: 'backButton',
text: '戻る',
ui: 'back',
align: 'left',
hidden: true
}]
},
{xtype: 'articlelist'},
{xtype: 'articledetail'}
]
}
});
MainViewはアプリケーション全体のViewとなっており、前半で作ったパーツをこのMainViewにはめ込んでいくだけでアプリケーションのViewが出来ちゃいます。今回は、MainViewのヘッダー部分にToolbarと前半で作成したListViewおよびDetailViewを、MainViewのitemsに設定します。
ここでポイントなのが、MainViewのlayoutプロパティをtype: 'card'にしているところです。
cardレイアウトは、itemsに指定したViewを順に切り替えて表示させるようなレイアウト方式です。
今回のMainViewをサンプルに見てみると、MainViewのitemsには3つのオブジェクトが指定されており、一番最初に設定されているオブジェクトは、MainViewで使用するxtype:'titlebar'を設定しており、残りの2つについてはListView(一覧ページ)、DetailView(詳細ページ)を設定しています。


ListViewに設定しているStoreのデータのロードが完了すると上記左画像の2番のように、ブログ記事の一覧が表示される。そして、その一覧からデータを選択することによりDetailViewにそのデータを渡し詳細表示を行います。
/app/controller/Article.js
最後に、ここまでで組み合わせてきたViewを動作させる為のControllerを作成していきます。
他のソースコードと比較するとControllerのコードは少し長めになっています。実際に処理の実装などがあるのである程度は仕方ないのですが、全部Controllerに詰め込むみたいな書き方はしないように注意しましょう。
Ext.define('SenchaUG.controller.Article', {
extend: 'Ext.app.Controller',
config: {
activeIndex: 0,
refs: {
mainPanel: '#main',
list: '#main #articleList',
detail: '#main #articleDetail',
backButton: '#main #backButton'
},
control: {
list: {
itemtap: 'showDetail'
},
backButton: {
tap: 'back'
}
}
},
showDetail: function(view, index, target, record) {
var me = this,
detailPanel = me.getDetail();
detailPanel.setData(record.data);
me.getBackButton().show();
me.getMainPanel().setActiveItem(detailPanel);
me.setActiveIndex(1);
},
back: function() {
var me = this,
index = me.getActiveIndex() - 1;
if (index === 0) {
me.getBackButton().hide();
}
me.getMainPanel().animateActiveItem(index, {
type: 'slide',
reverse: true
});
me.setActiveIndex(index);
me.getList().deselectAll();
}
});
コントローラーでのポイントは、コントローラーから各 items/Components にアクセスする際に、どのような手法でアクセスするかについてです。Controllerにはrefsコンフィグというものがあり、このrefsにComponentQuery式を用いてリテラルを記載しておくと、プロパティ名を元にそのコンポーネントのゲッターを自動生成してくれるという非常に優れた機能です。ちょっと抜粋して説明すると
refs: {
mainPanel: '#main', // app/view/Main.jsのitemIdを指定
list: '#main #articleList', // app/view/Main.jsの子供でitemIdがarticleListのもの
detail: '#main #articleDetail', // app/view/Main.jsの子供でitemIdがdetailListのもの
backButton: '#main #backButton' // app/view/Main.jsの子供でitemIdがbackButtonのもの
},
このような形で指定し、対象のコンポーネントの取得を行います。
簡単にどのコンポーネントを対象に取得しているか書いてみると
refs: {
mainPanel: 全体のMainView
list: ブログ記事一覧のListView
detail: ブログ詳細のDetailView
backButton: ブログ詳細を表示した際に表示する為の戻るボタン
},
というような感じになります。
Controllerにはcontrolというコンフィグがあり、このcontrolコンフィグに対象となるコンポーネントとそのコンポーネントが発火させるイベント名、そして最後にそのイベントが発火した際に呼ばれるメソッドを設定することで、イベントのリッスンが行えます。
このcontrolコンフィグに 対象となるコンポーネント を設定する際に、先ほど出てきたrefsで指定したものが利用できます。今回の書き方を再度抜粋してみると
control: {
list: {
// list: '#main #articleList'
// listのアイテム一覧からitemtapメソッドの実行
itemtap: 'showDetail'
},
backButton: {
// backButton: '#main #backButton'
// backButtonがtapされた際にbackメソッドの実行
tap: 'back'
}
}
このような形でコンポーネントを選択・メソッドの登録を行えます。
そして最後に、Controllerで行っている処理について軽く説明していきます。
Controllerでリスナーに登録している処理は、showDetailとbackの2つのメソッドです。
showDetailメソッド
ListViewで表示されているリスト(ブログの記事一覧)のitemtapが起きた際に流れる処理がshowDetailです。中でやっている処理は単純で、refsで定義したdetailのgetterを利用して詳細画面のインスタンスを取得し、ListViewで選択された記事データを詳細画面のDetailViewへ適応した後に画面遷移させているってだけです。
showDetail: function(view, index, target, record) {
var me = this,
detailPanel = me.getDetail();
detailPanel.setData(record.data);
me.getBackButton().show();
me.getMainPanel().setActiveItem(detailPanel);
me.setActiveIndex(1);
},
backメソッド
backメソッドは詳細画面を表示した際に、一覧画面へ戻る場合の処理に使っています
back: function() {
var me = this,
index = me.getActiveIndex() - 1;
if (index === 0) {
me.getBackButton().hide();
}
me.getMainPanel().animateActiveItem(index, {
type: 'slide',
reverse: true
});
me.setActiveIndex(index);
me.getList().deselectAll();
}
1つ1つは比較的軽量な感じで、それぞれをブロックのように組み合わせながら作っていくことができるのが、Senchaのアーキテクチャの面白いところですね。ちょっと今回遅れてしまったので、次回もう一度今度は準備して書きたいと思います!;)
今回のソースコードはGithubにあります:tnker/senchaug.002

