目次


MEAN をマスターする

MEAN とレスポンシブ Web デザインを適用した UGLI CRUD

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: MEAN をマスターする

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:MEAN をマスターする

このシリーズの続きに乞うご期待。

この連載を読んでいる方は、すでに MEAN アプリケーションの仕組みを理解していることと思うので、いよいよ連載の 1 回目で作成して 2 回目でその詳細を探った MEAN.JS アプリケーションをカスタマイズします。この 3 回目では、アプリケーションの基本的な CRUD 機能をデモンストレーションします。また、レスポンシブ Web デザインと Bootstrap についても概説します。

この連載の残りで作成するアプリケーションは、親しみを込めて UGLI と呼びます (User Group List and Information アプリケーション)。私は 2010年から HTML5 Denver User Group (その前は Boulder Java User Group、さらにその前は Denver Java User Group) を運営しているので、ローカル・ユーザー・グループの大ファンであると言っても驚くに当たらないでしょう。そんな私が意外に思っているのは、ユーザー・グループを運営するための専用のソフトウェアがないことです (靴屋の子供がいつも裸足でいるのと同じことだと思いませんか)。そろそろ、この問題に対処するときがきました。

多くのユーザー・グループが、Meetup.com にオンラインの拠点を見つけています。私が目標とするのは、この MEAN と UGLI のアプリケーションを Meetup.com に代わるものにすることではなく、むしろ、Meetup.com に密接に統合することです。Meetup.com は、ユーザー・グループを順調に運営するために必要なコア機能 (新規ユーザーの登録、ミーティングの詳細の公開、RSVP の処理など) のほとんどにおいて優れていますが、プレゼンターのリストの管理やパワーポイント資料集へのリンクなど、ユーザー・グループのリーダーにとって重要となる機能のいくつかがまだ欠けています。そのギャップを、UGLI で埋めたいと思います (完全なサンプル・コードを入手するには、「ダウンロード」を参照してください)。

ブランド名の表示を調整する

アプリケーションを UGLI にする際の最初のタスクは、アプリケーションのブランド名を調整することです。変更の一部は、アプリケーションのサーバー・サイドの config ディレクトリーや app ディレクトリーで行い、その他の変更はクライアント・サイドの public ディレクトリーで行う必要があります。

まず始めに、config/env/all.js に格納されているメタデータから手を付けます。タイトルを「HTML5 Denver」(または任意のユーザー・グループ) に変更し、説明を「HTML5 Denver User Group」に変更してください (リスト 1 を参照)。

リスト 1. config/env/all.js
'use strict';

module.exports = {
    app: {
        title: 'HTML5 Denver',
        description: 'HTML5 Denver User Group',
        keywords: 'MongoDB, Express, AngularJS, Node.js'
    },

config/env/development.js でもタイトルを変更する必要があります (リスト 2 を参照)。前回説明したように development.js と all.js は実行時に結合されます。

リスト 2. config/env/development.js
'use strict';

module.exports = {
    db: 'mongodb://localhost/test-dev',
    app: {
        title: 'HTML5 Denver'
    },

次に、ナビゲーション・バーの左上隅に表示されるブランド名を変更します。それには、public/modules/core/views/header.client.view.html を編集します。9 行目あたりで、クラスが navbar-brand に設定されたアンカー・タグを見つけて、そのアンカー・タグの対象コンテンツを「HTML5 Denver」に変更します (リスト 3 を参照)。

リスト 3. public/modules/core/views/header.client.view.html
<div class="container" data-ng-controller="HeaderController">
    <div class="navbar-header">
        <button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
        </button>
        <a href="/#!/" class="navbar-brand">HTML5 Denver</a>
    </div>

    <!-- ...snip... -->
</div>

変更を確認するには、コマンド・ラインで「mongod」と入力して MongoDB を起動してから、「grunt」と入力してアプリケーションを起動します。ブラウザーで Web アプリケーションを表示し、変更後のブランド名がメニューとタイトル・バーに表示されていることを確認します。

ブランド名の変更を完了するには、ホーム・ページの本体に表示される public/modules/core/views/home.client.view.html 内のボイラープレート・テキストを置換する必要があります。後で必要に応じて参照できるように、home.client.view.html.original という名前のコピーを作成してください。

このファイルは、Web サイトが初めからモバイル対応になるように、Bootstrap フレームワークの力を借ります。作業を先に進める前に、Bootstrap が提供する 12 カラムのグリッド・レイアウトについて理解しておくと役立ちます。

Bootstrap とレスポンシブ Web デザインの概要

印刷された新聞や雑誌を見ると、カラムが使用されていることがわかるはずです。場合によっては、特徴的なデザインにするために画像や見出しが複数のカラムにまたがっていることもありますが、印刷されるほぼすべてのページの基盤となっているのは、基本的な縦欄式レイアウトです。

これは、Web ページにしても同じことです。一例として、TIME の Web サイトにアクセスすると、最初にカラム・ベースのレイアウトが表示されますが、全画面表示のブラウザーの幅を非常に狭くするとどうなるかを見てください。表示されるカラムの数は、ウィンドウを小さくすると減り、ウィンドウを大きくすると増えていきます。

Web ページが要求側の端末に応じて (respond)、そのデザインを端末の画面サイズに合うように調整することから、この効果はレスポンシブ (responsive) Web デザインと呼ばれています。近頃の Web 開発者は、最も小さい携帯端末からデスクトップや壁掛け式の最も大きい画面にまでシームレスに対応する Web サイトを作成します。スマートフォン、タブレット、ノート PC などに応じ、(http://m.* および http://www.* という別々の URL を使用して) それぞれに異なる別々の Web サイトを進んで作成するのは、もはや時代遅れとなった 21 世紀の戦略です。

レスポンシブ Web デザインは、万能の戦略ではありません。正しく言えば、「1 つの Web サイトのルック・アンド・フィールをあらゆる端末に適応させる」戦略と言えます。Web サイトにアクセスするユーザーが使用する端末の種類を Web サイトの作成側で指定することはできないため、Web サイトのデザインに柔軟性を持たせ、デザイン自体を端末に合わせて調整することが重要です。

人気を集めている (Facebook や Instagram を含む) 多くの Web サイトへのアクセス手段としては、従来型のコンピューターよりもモバイル端末のほうがより多く使われています。Twitter のユーザー・ベースは、主にモバイル・ユーザーです。Twitter ではそのレスポンシブ Web デザイン戦略を標準化し、Bootstrap というオープンソースにしました。Bootstrap が採用している 12 カラムのレイアウトは、カラムを定義するために使用される CSS クラスに基づいて拡大/縮小することができます。

MEAN.JS サンプル・アプリケーションのホーム・ページには、MongoDB、Express、AngularJS、および Node.js 用に 4 つのカラムがあることに注目してください (図 1 を参照)。

図 1. Bootstrap の縦欄式レイアウトの例
Bootstrap の縦欄式レイアウトの例のスクリーンショット
Bootstrap の縦欄式レイアウトの例のスクリーンショット

public/modules/core/views/home.client.view.html のソース (リスト 4 を参照) を調べると、Bootstrap の 12 カラムのレイアウトが実際に使われているのを確認することができます。

リスト 4. public/modules/core/views/home.client.view.html
<div class="row">
    <div class="col-md-3">
        <h2><strong>M</strong>ongoDB</h2>
    </div>
    <div class="col-md-3">
        <h2><strong>E</strong>xpress</h2>
    </div>
    <div class="col-md-3">
        <h2><strong>A</strong>ngularJS</h2>
    </div>
    <div class="col-md-3">
        <h2><strong>N</strong>ode.js</h2>
    </div>
</div>

divclass="row" を追加すれば、子 divclass="col-xx-N" 属性を追加してカラムに分割することができます。N の値は 1 から 12 までの数値にする必要があります。xx の値は、以下のようにレイアウトを最適化する対象の端末のサイズによって決まります。

  • xs: 超小型 (幅 768 ピクセル未満) の端末
  • sm: 小型 (幅 768 ピクセルから 991 ピクセル) の端末
  • md: 中型 (幅 992 ピクセルから 1,199 ピクセル) の端末
  • lg: 大型 (幅 1,200 ピクセル以上) の端末

詳しくは、Bootstrap CSS ドキュメントの「Grid system」セクションを参照してください。

リスト 4 の各カラムは中型 (md) の端末用に最適化されているため、このページに画面の幅が 992 ピクセルより小さい端末でアクセスすると、カラムは横方向ではなく縦方向に配列されます。この変化が生じるところまで、ブラウザーのウィンドウ幅を狭くしてください (図 2 を参照)。

図 2. モバイル端末でのレスポンシブ Web デザインの例
モバイル端末でのレスポンシブ Web デザインの例のスクリーンショット
モバイル端末でのレスポンシブ Web デザインの例のスクリーンショット

ここで、これまでに学んだことを活かして、home.client.view.html のボイラープレート・テキストを UGLI 固有のテキストに置き換える作業に移ります。

まずは、W3C HTML5 ロゴ・ページから 256 ピクセルの HTML5 ロゴをダウンロードして、それを public/modules/core/img/brand/HTML5_Logo_256.png にコピーします。次に、public/modules/core/views/home.client.view.html 内の既存の HTML をリスト 5 に示すソースに置き換えます。

リスト 5. public/modules/core/views/home.client.view.html
<section data-ng-controller="HomeController">
    <div class="jumbotron text-center">
        <div class="row">
            <div class="col-md-4">
                <img alt="HTML5" class="img-responsive center-block" 
                src="modules/core/img/brand/HTML5_Logo_256.png" />
            </div>
            <div class="col-md-8">
                <h1>The HTML story is still being written.</h1> 
                <h2><em>Come hear the latest chapter at the HTML5 Denver User Group.</em></h2>
            </div>
        </div>
    </div>
</section>

幅を広くしたブラウザー・ウィンドウで Web サイトを表示すると、テキストの横に HTML5 ロゴが表示されます (図 3 を参照)。

図 3. 新しい UGLI ホーム・ページ
新しい UGLI ホーム・ページの画面のスクリーンショット
新しい UGLI ホーム・ページの画面のスクリーンショット

ブラウザー・ウィンドウの幅を狭くすると、ロゴはテキストの上に表示されます (図 4 を参照)。

図 4. モバイル端末に表示されるであろう新しい UGLI ホーム・ページ
モバイル端末に表示されるであろう新しい UGLI ホーム・ページのスクリーンショット
モバイル端末に表示されるであろう新しい UGLI ホーム・ページのスクリーンショット

Bootstrap では、どれほど簡単に Web サイトをモバイル対応にできるかがわかりましたか?私が顧客用に作成する新しい Web サイトは、すべて Bootstrap がベースになっています。

今度は、MEAN スタックの CRUD に取り組みます。

基本的な CRUD

Meetup.com は、私がユーザー・グループのイベントを管理する上で大いに役立っています。しかしイベントが終了すると、そのイベントの時間的な側面は、当日の夜に行われた講演ほど重要でなくなります。

別の言葉に置き換えると、この Web サイトでのユーザー・ストーリーの 1 つは、「次のミーティングでの議題は何か?」というものです。このユーザー・ストーリーは、現状の Meetup.com で完全に対応することができます。

UGLI アプリケーションで解決しようと目指しているのは、「いつ開催されたかに関わらず、MEAN スタック関連のすべての講演を表示する」という別のユーザー・ストーリーです。このストーリーを実装するには、Talk という新規モデル・オブジェクトを中心とした CRUD インフラストラクチャーを作成する必要があります。幸い、このインフラストラクチャーを導入するには、Yeoman ジェネレーターを利用することができます。

アプリケーションのルート・ディレクトリーで「yo meanjs:crud-module talks」と入力し、プロンプトに応じて以下の操作を行ってください。

  1. 4 つの補助フォルダー (css、img、directives、filters) のすべてを選択します。
  2. メニューに CRUD モジュールのリンクを追加するかどうかという質問に対し、「Yes」と答えます。
  3. どのメニューを使用するかをジェネレーターから尋ねられたら、デフォルト (topbar) を受け入れます。

リスト 6 に、この対話型コマンド・ライン・シーケンスを記載します。

リスト 6. Yeoman ジェネレーターで新規 CRUD モジュールを生成する
$ yo meanjs:crud-module talks
[?] Which supplemental folders would you like to include in your angular module? 
css, img, directives, filters
[?] Would you like to add the CRUD module links to a menu? Yes
[?] What is your menu identifier? topbar
   create app/controllers/talks.server.controller.js
   create app/models/talk.server.model.js
   create app/routes/talks.server.routes.js
   create app/tests/talk.server.model.test.js
   create public/modules/talks/config/talks.client.routes.js
   create public/modules/talks/controllers/talks.client.controller.js
   create public/modules/talks/services/talks.client.service.js
   create public/modules/talks/tests/talks.client.controller.test.js
   create public/modules/talks/config/talks.client.config.js
   create public/modules/talks/views/create-talk.client.view.html
   create public/modules/talks/views/edit-talk.client.view.html
   create public/modules/talks/views/list-talks.client.view.html
   create public/modules/talks/views/view-talk.client.view.html
   create public/modules/talks/talks.client.module.js

リスト 6 には、ジェネレーターによって、ルート、コントローラー、モデル、ユニット・テストからなる必要なサーバー・サイドのインフラストラクチャーが作成されることが示されています (これらは、app ディレクトリーに保管されます)。また、クライアント・サイドのすべての成果物も、public/modules/talks ディレクトリーに作成されます。

この後 Talk オブジェクトにカスタム・フィールドを追加しますが、その前に、ブラウザーで Web サイトにアクセスして、デフォルトで表示される内容を確認してください。

右上隅にある「Signin (サインイン)」リンクをクリックし、連載で前に作成したユーザー名とパスワードを入力します。あるいは、「Signup (サインアップ)」をクリックして新しい資格情報一式を作成しても構いません。

ログインすると、左上に「Talks (講演)」メニューが表示されていることがわかります。このメニューから「New Talk (新規講演)」を選択して HTML フォームを開きます。このフォームに表示されるフィールドは、今のところ「Name (名前)」だけです (図 5 を参照)。

図 5. カスタマイズする前の「New Talk (新規講演)」フォーム
カスタマイズする前の「New Talk (新規講演)」フォームのスクリーンショット
カスタマイズする前の「New Talk (新規講演)」フォームのスクリーンショット

出発点としては申し分ないフォームですが、単純なテキスト・フィールドだけでは Talk オブジェクトのすべての属性を取り込むことができません。

パーシスタンス用の新規フィールドを追加する

新しいフィールドを Talk に追加するには、以下の 6 つのファイルを編集する必要があります。このうち 4 つは表示用、残りの 2 つはパーシスタンス用です。

  • app/models/talk.server.model.js
  • public/modules/controllers/talks.client.controller.js
  • public/modules/talks/views/create-talk.client.view.html
  • public/modules/talks/views/edit-talk.client.view.html
  • public/modules/talks/views/view-talk.client.view.html
  • public/modules/talks/views/list-talks.client.view.html

最初にパーシスタンスから取り掛かります。このソリューションの半分はサーバー・サイドにあり、もう半分はクライアント・サイドにあります。

app/models/talk.server.model.js で定義されたサーバー・サイドのモデルが、このアプリケーションの真実を語ります。このファイルで、フィールドに名前を付け、データ型を指定し、検証ルールを追加するなどの作業を行います。

public/modules/controllers/talks.client.controller.js で定義されたクライアント・サイドのコントローラーは、ユーザーからのデータ入力を収集し、そのデータを HTTP リクエストを介してサーバーにプッシュします。また、送られてきた JSON データを取り込んで表示するためにビューに渡すのも、このコントローラーの役目です。

このアーキテクチャーの興味深い点は、モデル・オブジェクトがサーバーを離れることは決してないことです。代わりに、モデル・オブジェクトはクライアントから送信されたデータを使用して具体化され、HTTP レスポンス内では JSON にシリアライズされます。

このアプリケーションには、サーバー・サイド用とクライアント・サイド用に合計 2 つのコントローラーがありますが、興味深いのはクライアント・サイドのコントローラーのみです。サーバー・サイドのコントローラーは受信される JSON をモデル・オブジェクトに注入するだけに過ぎないため、モデルにフィールドを追加するとしても、サーバー・サイドのコントローラーを調整する必要はありません。クライアント・サイドのコントローラーには、新規フィールドに対応させるための調整が多少必要です。

app/models/talk.server.model.js を開いて、サーバー・サイドのモデルに新規フィールドを追加します。リスト 7 に示されているように、要求される name フィールド (図 5 を参照) と併せて、さらに createduser という 2 つのメタデータ・フィールドが定義されていることがわかります。

リスト 7. app/models/talk.server.model.js
/**
 * Talk Schema
 */
var TalkSchema = new Schema({
    name: {
        type: String,
        default: '',
        required: 'Please fill Talk name',
        trim: true
    },
    created: {
        type: Date,
        default: Date.now
    },
    user: {
        type: Schema.ObjectId,
        ref: 'User'
    }
});

JSON ベースのスキーマは、ほぼ見てのとおりの内容です。新規フィールドを定義するときには、データ型、デフォルト値、必須フィールドに関して表示するエラー・メッセージの他、さまざまな詳細を指定することができます。詳しくは、Mongoose のドキュメントを参照してください。

さらに、descriptionpresenter、および slidesUrl を新規フィールドとして追加します (リスト 8 を参照)。この場合、descriptionpresenter はどちらも必須フィールドで、slidesUrl フィールドはオプションです。

リスト 8. app/models/talk.server.model.js
/**
 * Talk Schema
 */
var TalkSchema = new Schema({
    name: {

        type: String,
        default: '',
        required: 'Please fill Talk name',
        trim: true
    },
    description: {
        type: String,
        default: '',
        required: 'Please fill Talk description',
        trim: true
    },  
    presenter: {
        type: String,
        default: '',
        required: 'Please fill Talk presenter',
        trim: true
    },
    slidesUrl: {
        type: String,
        default: '',
        trim: true
    },
    created: {
        type: Date,
        default: Date.now
    },
    user: {
        type: Schema.ObjectId,
        ref: 'User'
    }
});

これで、サーバー・サイドのバックエンドは新規フィールドを受け入れられるようになりました。次は、クライアント・サイドのコントローラーに取り組む必要があります。public/modules/controllers/talks.client.controller.js を開いて、リスト 9 に示す新規フィールドを追加してください。

リスト 9. public/modules/controllers/talks.client.controller.js
// Create new Talk
$scope.create = function() {
    // Create new Talk object
    var talk = new Talks ({
        name: this.name,
        description: this.description,
        presenter: this.presenter,
        slidesUrl: this.slidesUrl
    });

    // Redirect after save
    talk.$save(function(response) {
        $location.path('talks/' + response._id);
    }, function(errorResponse) {
        $scope.error = errorResponse.data.message;
    });

    // Clear form fields
    this.name = '';
    this.description = '';
    this.presenter = '';
    this.slidesUrl = '';
};

$scope.create 関数で、フォーム・フィールドが JSON オブジェクトに集約されて、パーシスタンスのためにサーバーに送信できる状態になります。モデルからコントローラーに対応するフィールドを追加し終わったら、ストーリーのパーシスタンスの部分は完成です。

今度はプレゼンテーション層に焦点を移し、ユーザーが新しいフィールドを表示して操作できるようにします。

表示用の新規フィールドを追加する

public/modules/talks/views/ に、CRUD ライフサイクルに関連する以下の 4 つのファイルがあります。

  • create-talk.client.view.html
  • edit-talk.client.view.html
  • view-talk.client.view.html
  • list-talks.client.view.html

create-talk.client.view.html を開いてください (リスト 10 を参照)。

リスト 10. 生成された create-talk.client.view.html
<section data-ng-controller="TalksController">
  <div class="page-header">
    <h1>New Talk</h1>
  </div>
  <div class="col-md-12">
    <form class="form-horizontal" data-ng-submit="create()" novalidate>
      <fieldset>
        <div class="form-group">
          <label class="control-label" for="name">Name</label>
          <div class="controls">
            <input type="text" data-ng-model="name" id="name" class="form-control" 
            placeholder="Name" required>
          </div>
        </div>
        <div class="form-group">
          <input type="submit" class="btn btn-default">
        </div>
        <div data-ng-show="error" class="text-danger">
          <strong data-ng-bind="error"></strong>
        </div>
      </fieldset>
    </form>
  </div>
</section>

DescriptionPresenter、および slidesUrl をサポートするために、Name フィールドに関連するコード・ブロックを 3 回コピーします (リスト 11 を参照)。注意する点として、私は Description フィールドを単純なテキスト・フィールドではなく textarea にしました。また、slidesUrl フィールドから required 属性を除去し、input type を “text” から “url” に変更しています。

リスト 11. 更新後の create-talk.client.view.html
<section data-ng-controller="TalksController">
  <div class="page-header">
    <h1>New Talk</h1>
  </div>
  <div class="col-md-12">
    <form class="form-horizontal" data-ng-submit="create()" novalidate>
      <fieldset>
        <div class="form-group">
          <label class="control-label" for="name">Name</label>
          <div class="controls">
            <input type="text" data-ng-model="name" id="name" class="form-control" 
            placeholder="Name" required>
          </div>
        </div>
        <div class="form-group">
          <label class="control-label" for="description">Description</label>
          <div class="controls">
            <textarea data-ng-model="description" id="description" class="form-control" 
            placeholder="Description" required></textarea>
          </div>
        </div>
        <div class="form-group">
          <label class="control-label" for="presenter">Presenter</label>
          <div class="controls">
            <input type="text" data-ng-model="presenter" id="presenter" class="form-control" 
            placeholder="Presenter" required>
          </div>
        </div>
        <div class="form-group">
          <label class="control-label" for="slidesUrl">Slides</label>
          <div class="controls">
            <input type="url" data-ng-model="slidesUrl" id="slidesUrl" class="form-control" 
            placeholder="Slides Url">
          </div>
        </div>                        
        <div class="form-group">
          <input type="submit" class="btn btn-default">
        </div>
        <div data-ng-show="error" class="text-danger">
          <strong data-ng-bind="error"></strong>
        </div>
      </fieldset>
    </form>
  </div>
</section>

新しく変更された「New Talk (新規講演)」ページは、Web ブラウザーで図 6 のように表示されます。

図 6. カスタマイズ後の「New Talk (新規講演)」フォーム
カスタマイズ後の「New Talk (新規講演)」フォームのスクリーンショット
カスタマイズ後の「New Talk (新規講演)」フォームのスクリーンショット

変更内容に満足したら、edit-talk.client.view.html を開いて、このファイルにも同じ変更を加えます (リスト 12 を参照)。

リスト 12. edit-talk.client.view.html
<div class="col-md-12">
    <form class="form-horizontal" data-ng-submit="update()" novalidate>
      <fieldset>
        <div class="form-group">
          <label class="control-label" for="name">Name</label>
          <div class="controls">
            <input type="text" data-ng-model="talk.name" id="name" class="form-control" 
            placeholder="Name" required>
          </div>
        </div>
        <div class="form-group">
          <label class="control-label" for="description">Description</label>
          <div class="controls">
            <textarea data-ng-model="talk.description" id="description" class="form-control" 
            placeholder="Description" required></textarea>
          </div>
        </div>
        <div class="form-group">
          <label class="control-label" for="presenter">Presenter</label>
          <div class="controls">
            <input type="text" data-ng-model="talk.presenter" id="name" class="form-control" 
            placeholder="Presenter" required>
          </div>
        </div>
        <div class="form-group">
          <label class="control-label" for="slidesUrl">Slides</label>
          <div class="controls">
            <input type="url" data-ng-model="talk.slidesUrl" id="name" class="form-control" 
            placeholder="Slides Url">
          </div>
        </div>
        <div class="form-group">
          <input type="submit" value="Update" class="btn btn-default">
        </div>
        <div data-ng-show="error" class="text-danger">
          <strong data-ng-bind="error"></strong>
        </div>
      </fieldset>
    </form>
</div>

編集用の HTML は、先ほど変更した作成フォームとわずかに異なることに注意してください。編集する際に Talk オブジェクトはすでに存在するため、data-ng-model 属性は完全修飾の形でフィールドを参照します。例えば、name ではなく talk.name で参照するなどです。Web ブラウザーで変更を確認してください (図 7 を参照)。

図 7. カスタマイズ後の「Edit Talk (講演の編集)」フォーム
カスタマイズ後の「Edit Talk (講演の編集)」フォームのスクリーンショット
カスタマイズ後の「Edit Talk (講演の編集)」フォームのスクリーンショット

view-talk.client.view.html ページは、オブジェクトの読み取り専用ビューです。ユーザーが新規 Talk を保存した後や、既存の Talk を更新した後、あるいはリスト・ページから Talk を選択した後、ユーザーはこのビューにリダイレクトされます。このページに対して、リスト 13 に記載する変更を行います。

リスト 13. edit-talk.client.view.html
<div class="page-header">
  <h1 data-ng-bind="talk.name"></h1>
  <h2><em>by {{talk.presenter}} 
    <span ng-if="talk.slidesUrl !== '' ">[<a href="{{talk.slidesUrl}}">slides</a>]</span></em></h2>
  <p>{{talk.description}}</p>              
</div>

slidesUrl フィールドはオプションであることを思い出してください。このビュー・ページでは、ng-if ディレクティブを使用して、フィールドが入力されているかどうかの条件によってフィールドを表示しています。この動作を確認するには、ブラウザーでページを表示します (図 8 を参照)。

図 8. カスタマイズ後の「View Talk (講演の表示)」フォーム
カスタマイズ後の「View Talk (講演の表示)」フォームのスクリーンショット
カスタマイズ後の「View Talk (講演の表示)」フォームのスクリーンショット

微調整が必要な最後のビューは、リスト・ビューです。list-talks.client.view.html を開いて、リスト 14 に示す調整を行います。

リスト 14. list-talks.client.view.html
<div class="list-group">
    <a data-ng-repeat="talk in talks" data-ng-href="#!/talks/{{talk._id}}" class="list-group-item">
    <h4 class="list-group-item-heading" data-ng-bind="talk.name"></h4>
        <p><em>by {{talk.presenter}}</em></p>
    </a>
</div>

注意する点として、サーバーから返された講演のリストに各講演を表示するために、data-ng-repeat ディレクティブを使用しています。ブラウザーで結果を確認してください (図 9 を参照)。

図 9. カスタマイズ後の「List Talks (講演の一覧表示)」フォーム
カスタマイズ後の「List Talks (講演の一覧表示)」フォームのスクリーンショット
カスタマイズ後の「List Talks (講演の一覧表示)」フォームのスクリーンショット

まとめ

読者の皆さんは、この時点で MEAN スタックを構成するさまざまな要素が互いにやりとりする仕組みを十分に理解できたはずです。今回は、基本的なキーとマウスを備えた従来型の端末だけでなく、あらゆる端末で Web サイトが見栄え良く表示されるようにするために、Bootstrap のレスポンシブ Web デザイン機能を使用しました。また、新しい CRUD モジュールをアプリケーションに追加するために Yeoman ジェネレーターを使用する強力さと便利さも体験しました。このジェネレーターによって、適切なディレクトリーにある未加工の成果物のすべてが収集されるため、私たちに残されるのは、それらの成果物をわずかにカスタマイズするタスクのみです。

次回は、リモート・ソースのデータをアプリケーションに取り込むのがいかに簡単であるかを説明します。具体的には、Meetup の RESTful API を使用して、Meetup.com のイベント・データを直接取り込む作業に取り掛かります。それまでは、楽しみながら MEAN をマスターしてください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source
ArticleID=990399
ArticleTitle=MEAN をマスターする: MEAN とレスポンシブ Web デザインを適用した UGLI CRUD
publish-date=11272014