中安拓也のブログ

プログラミングについて書くブログ

【JavaScript】Ionicで架空のECアプリを作成する #10 - 完成

前回の記事はこちら

ハイブリットモバイルアプリフレームワークのIonicを使って、架空のアパレルショップにおける注文アプリを作成していました。

最低限の機能は、実装できたかな?というところまで来たので、今回で一旦完成ということにします。

下記のURLから実際にさわれます。

https://l08084.github.io/ionic-sample-shopping-app/www

後からリポジトリを見直した時になんのシステムかわかるように、README.mdを書く アンド GitHub Pagesにプッシュして完了にしたいと思います。

  • GitHubリポジトリ

github.com

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っています

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

README.mdを書く

README.mdの構成ですが、Getting Started(ローカルでのアプリの動かし方)とApp Preview(各画面のスクリーンショット)だけ書いて終わりにします。

各画面のスクショをとる

$ ionic serve --labを使うと、各種OS(iOS, Android, Windows)の動作確認を一度に実行することができるので、便利です。

f:id:l08084:20180512192139p:plain
商品リスト画面のスクショ

f:id:l08084:20180512193014p:plain
商品詳細画面のスクショ

f:id:l08084:20180512193139p:plain
カート画面のスクショ

撮ったスクリーンショットをresources/screenshots/配下に配置して、README.mdから参照できるようにする。

README.md:

## App Preview

[Try it live](https://l08084.github.io/ionic-sample-shopping-app/www)

* Home Page

  <img src="resources/screenshots/HomePage.png">

* Detail Page

  <img src="resources/screenshots/DetailPage.png">

* Cart Page

  <img src="resources/screenshots/CartPage.png">

MacPCを使っていて、かつエディタがVSCodeのかたは、キーボードで「command + shift + v」と打つことで、マークダウンのプレビューをみることが出来るので、便利ですよ

GitHub Pagesに配置する

IonicアプリをGitHub Pagesで公開する - 中安拓也のブログ

IonicアプリをGitHub Pages上で公開する方法については、上記記事を参考にしてください。

本アプリについては、https://l08084.github.io/ionic-sample-shopping-app/www に公開しました。

以上

IonicアプリをGitHub Pagesで公開する

Ionic3を使って作成したアプリを、GitHub Pagesで公開する方法について

バージョン情報

  • ionic-angular@3.9.2
  • Angular@5.2.10

GitHub Pagesで公開する

GitHubの設定を変更する

f:id:l08084:20180512173432p:plain

GitHubにアクセスして、GitHub Pagesで公開したいアプリのリポジトリの設定ボタンをクリックする

f:id:l08084:20180512173530p:plain

GitHub PagesのSourceを、Noneからmaster branchに変更する

Ionicアプリをビルドする

ターミナルを開いて、下記のビルドコマンドを実行する

$ ionic build --prod

出来上がったwww/フォルダを丸ごとGitHubにプッシュする

なお、.gitignorewww/が含まれたままだとプッシュできないので、事前に削除しておくのを忘れないように注意

これで、GitHub Pagesへの公開が完了する🍺🍺🍺

実際に参照してみる

GitHub PagesにプッシュしたIonicアプリを参照するには、https://[GitHubアカウント名].github.io/[リポジトリ名]/wwwのURLにアクセスすれば良い。

※ビルドした資源は、www/ 配下にあるため、URLに /www をつけるのを忘れないように

今回は、GitHubアカウント: l08084 リポジトリ名: ionic-sample-shopping-app のIonicアプリをGitHub Pagesにプッシュしたので、下記URLを参照すれば、実際の画面をみることができる。

https://l08084.github.io/ionic-sample-shopping-app/www

Ionicで戻るボタンのラベルテキストを設定する

f:id:l08084:20180511165352p:plain Ionicのヘッダーについている戻るボタンのテキストの設定方法について

参考サイト

javascript - How to change the label from back button in Ionic 2? - Stack Overflow

バージョン情報

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

設定方法

戻るボタンのラベルについては、@NgModuleimports: []配列にIonicModule.forRoot(MyApp, { backButtonText: [追加したいテキスト文] })を追加してあげることで、設定できます。

src/app/app.module.ts:

@NgModule({
  declarations: [MyApp, HomePage, TabsPage, DetailPage, CartPage],
  imports: [
    BrowserModule,
    // add this!
    IonicModule.forRoot(MyApp, { backButtonText: "Backします" }),
    HttpClientModule,
    IonicStorageModule.forRoot()
  ],

上のコードでは、戻るボタンのラベルを、Backしますに設定しています。

動作確認

上記のコードをブラウザ上で動かしてみます。

f:id:l08084:20180511165352p:plain
iOSで動かした場合の表示

f:id:l08084:20180511165901p:plain
Androidでの表示

【JavaScript】Ionicで架空のECアプリを作成する #9 - スワイプするとボタンが表示されるリスト

前回の記事はこちら

ハイブリットモバイルアプリフレームワークのIonicを使って、架空のアパレルショップにおける注文アプリを作成していきます。

第9回目である今回は、ショッピングカート画面のリストを、スライドすると削除ボタンが表示されるリストに変更します。

  • デモ

こちらのURLで、完成版のアプリを実際に操作することができます

https://l08084.github.io/ionic-sample-shopping-app/www

  • GitHubリポジトリ

github.com

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っています

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

今回やること

f:id:l08084:20180510231720p:plain
今回やることのイメージ

前回は、ショッピングカートに入れた全ての商品を削除する「空にする」ボタンを実装しました。今回は、IonicのItemSlidingを使って、カートに入れた商品を一列ずつ削除するボタンを実装していきます。

スライドするとボタンが表示されるようにする

リストを、スライドするとボタンが表示されるItemSlidingに変えます。

  • ポイント
    • ItemSlidingにしたい部分を<ion-item-sliding>で囲む
    • 削除ボタンを<ion-item-options>で囲む
    • 削除ボタンをクリックすると、remove()メソッドがコールされる

カート画面のテンプレート(src/pages/cart/cart.html):

    <ion-list>
      <!-- ItemSlidingにしたいリストの部分を'ion-item-sliding'で囲む -->
      <ion-item-sliding *ngFor="let product of productList; let i = index">
        <ion-item>
          <div class="quantity">{{product.quantity}}</div>
          <div class="product">{{product.name}}</div>
          <div class="price">¥{{product.price * product.quantity}}</div>
        </ion-item>
        <!-- スライドした時に表示したいアイテムはここに書く -->
        <ion-item-options>
          <button ion-button (click)="remove(i)" color="danger">アイテムを削除する</button>
        </ion-item-options>
      </ion-item-sliding>

以上


次回の記事

【JavaScript】Ionicで架空のECアプリを作成する #8 - ヘッダーにボタンをつける

前回の記事はこちら

ハイブリットモバイルアプリフレームワークのIonicを使って、架空のアパレルショップにおける注文アプリを作成します。

第8回目である今回は、ショッピングカート画面のヘッダーに「空にする」ボタンをつけます

  • デモ

こちらのURLで、完成版のアプリを実際に操作することができます

https://l08084.github.io/ionic-sample-shopping-app/www

  • GitHubリポジトリ

github.com

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っています

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

あらすじ

ショッピングカート画面に、「カート内に入れたアイテムを削除」する機能をつけるのをすっかり忘れていたのだった(全部買わない限り、カートを空にできない)。

今回やること

f:id:l08084:20180506173409p:plain
今回やることのイメージ

カート画面に「ショッピングカート内に入っているアイテムを全部削除する」ボタンをつけます

ヘッダーの右側にボタンを配置する

カート画面に、Ionicのボタンを配置します。<ion-buttons>タグにendをつけることで、ヘッダーの右端にボタンが配置されます。

カート画面のテンプレートファイル(src/pages/cart/cart.html):

<ion-header>
  <ion-navbar>
    <ion-title>
      カート
    </ion-title>
    <!-- add this! -->
    <!-- endをつけることで、右側に配置される -->
    <ion-buttons end>
      <button ion-button (click)="allRemove()">空にする</button>
    </ion-buttons>

  </ion-navbar>
</ion-header>

<!-- ...長いので省略 -->

ショッピングカートを空にする処理

「空にする」ボタンをクリックすると呼び出されるallRemove()メソッドを実装します。

カート画面のコンポーネントファイル(src/pages/cart/cart.ts):

  /**
   * ショッピングカートの中を空にする
   *
   * @memberof CartPage
   */
  allRemove() {
    this.storage.clear().then(() => {
      // タブのバッジを更新するために、
      // カート情報が更新されたことを出版している
      this.events.publish("cart:updated", 0);
      // ストレージの再読み込みを行う
      this.init();
    });
  }
}
  • ポイント
    • ionic-storageclear()メソッドを呼び出すことでIonicストレージを空にしている
    • clear()メソッドは処理が完了するとPromiseを返すので、thenでキャッチしている

以上


次回の記事

【JavaScript】Ionicで架空のECアプリを作成する #7 - 同じ商品を買った場合は、まとめて表示したい

前回の記事はこちら

ハイブリットモバイルアプリフレームワークのIonicを使って、架空のアパレルショップにおける注文アプリを作成する。

第7回目である今回は、同じ商品を二つ買った場合でも、まとめて表示されない問題について解決します。

  • デモ

こちらのURLで、完成版のアプリを実際に操作することができます

https://l08084.github.io/ionic-sample-shopping-app/www

  • GitHubリポジトリ

github.com

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っている

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

解決したい問題

f:id:l08084:20180504161319p:plain
今回することのイメージ

現在のアプリ(AS IS)だと、同一の商品を3個購入した場合は、そのままレジ画面に3列分表示されてしまうという問題があります。それを、同一の商品を複数件購入した場合は、1列でまとめて表示するように、コードを改修します。

実装

複数個の商品オブジェクトをまとめて一つにする、groupByメソッドを以前の回で作ったサービスクラスProductProviderに追加します。

src/providers/product/product.ts:

  // ...長いので省略

  /**
   * 同一の商品(idが同じ)を一つのオブジェクトにまとめる
   *
   * @param {Product[]} productList カート内の商品リスト
   * @returns {Product[]} (同一の商品ついては、一つにまとめた状態の)カート内の商品リスト
   * @memberof ProductProvider
   */
  groupBy(productList: Product[]): Product[] {
    const group = productList.reduce((result: Product[], current: Product) => {
      const element = result.find(p => p.id === current.id);
      if (element) {
        // 同一の商品がある場合
        // 商品の数量をインクリメントする
        element.quantity += 1;
      } else {
        result.push(current);
      }
      return result;
    }, []);
    return group;
  }
}

上記のgroupByメソッドをカート画面の初期表示時に呼び出す

src/pages/cart/cart.ts:

// add this!
// 同一の商品(idが同じ)を一つのオブジェクトにまとめる
this.productList = this.productProvider.groupBy(result);

商品が複数個になった場合の価格の表示については、カート画面のテンプレートファイル側で「商品の1個あたりの値段 * 数量」の計算をすることで対応している(あまりテンプレート側で計算とかをするのはよくないと思うので、今後改善していきたい....)

src/pages/cart/cart.html:

<div class="price">¥{{product.price * product.quantity}}</div>

ここまで書くと最初の図のTO BEのイメージ通り、同じ商品を複数購入した場合は、まとまって表示されるはずである。

余談

今回のような、”あるプロパティが同じ複数のオブジェクトをまとめて一つにする処理”は、自分にとっては鬼門で、本件と同様の問題が発生した時にまずいコードを書いてしまったことがある(以前書いたまずいコードについては、こちら)。

今回は、下記のサイトを参考にメソッドを書いた。(「SQLのgroup byのように集計する」という的確な表現だったり、説明がわかりやすい記事でとても参考にさせていただいた)

参考サイト

JavaScript オブジェクト配列をsqlのgroup byのように集計する


次回の記事

【JavaScript】Ionicで架空のECアプリを作成する #6 - タブバーのアイコンにバッジをつける

前回の記事はこちら

ハイブリットモバイルアプリフレームワークのIonicを使って、架空のアパレルショップにおける注文アプリを作成する。

第6回目である今回は、カートアイコンにショッピングカート内の商品数を表示するバッジをつけるところまで

  • デモ

こちらのURLで、完成版のアプリを実際に操作することができます

https://l08084.github.io/ionic-sample-shopping-app/www

  • GitHubリポジトリ

github.com

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っている

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

タブバーにバッジを表示する

Ionicのタブには、インプットプロパティtabBadgeがあるため、タブバーのアイコンにバッジを表示するだけなら、簡単にすみます。

src/pages/tabs/tab.html:

<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="商品" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="カート" tabIcon="cart" tabBadge="4" tabBadgeStyle="danger"></ion-tab>
</ion-tabs>

上記のコードでは、カートアイコン上にバッジで「4」を表示しています

f:id:l08084:20180503183243p:plain
動作確認イメージ

バッジ上の数値とカート内の商品数を同期させる

このままではバッチに固定の数値が表示されているだけで意味がないので、ショッピングカート内の商品数とバッジ上で表示されている数値が常に一致するようにしてあげる必要があります。

IonicのEventsを使う

カート内の商品数とバッジ上の数値を同期させる件ですが、IonicのEventsを使って実現します。Eventsはいわゆる出版-購読型モデルを実現するIonicのAPIになります。 今回は、商品をカートに入れるなど、カート内の商品数が更新されるタイミングでPublish(出版)を実施し、タブ側のコンポーネントではSubscribe(購読)を行うことによって、タブ側で常に最新のカート内の商品数を取得することができる...といった風に使います。

タブ側でSubscribeを行う

実際にコードを書いていきます。まずは、タブバーコンポーネントの方から。

src/pages/tabs/tab.ts:

import { Component } from "@angular/core";

import { HomePage } from "../home/home";
import { CartPage } from "../cart/cart";
import { Events } from "ionic-angular";

@Component({
  templateUrl: "tabs.html"
})
export class TabsPage {
  public tab1Root = HomePage;
  public tab2Root = CartPage;
  public itemCount = 0;

  constructor(public events: Events) {}

  ionViewDidLoad() {
    this.events.subscribe(
      "cart:updated",
      count => (this.itemCount = count ? count : 0)
    );
  }
}

上記のコードでは、タブバーコンポーネント初期作成時(ionViewDidLoad())に、トピック"cart:updated"とアロー関数を引数に、Subscribe(購読)を行なっています。こうすることによって、同一トピック(つまり"cart:updated")のPublish(出版)が行われる度に、アロー関数が実行されるため、バッジの値itemCountは常に最新の値になります。

ちなみにタブバーのテンプレートファイル

src/pages/tabs/tab.html:

<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="商品" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="カート" tabIcon="cart" tabBadge="{{itemCount}}" tabBadgeStyle="danger"></ion-tab>
</ion-tabs>

カート内の商品数が変更されるタイミングでPublishを行う

あとは、ショッピングカート内の商品数が更新されるタイミングでPublish(出版)を行いさえすれば、カートアイコンのバッジ上に正しい商品数が表示される。

  • カート内の商品数が更新されるタイミング
    • アプリの初期表示時
    • カートに商品を追加した時
    • 注文を確定して、カートの中身が空になった時
アプリの初期表示時

前回起動時に、商品をカートに入れていた場合は、ストレージの機能により、カート内に商品が残ったままであるため、アプリ起動時には必ず商品数が0になる、というわけではない。

なので、アプリ起動時にも、ストレージの中身をチェックして、商品の数をPublishしてあげる必要がある。

タブ(src/pages/tabs/tab.ts):

import { Component } from "@angular/core";

import { HomePage } from "../home/home";
import { CartPage } from "../cart/cart";
import { Events } from "ionic-angular";
import { Storage } from "@ionic/storage";

@Component({
  templateUrl: "tabs.html"
})
export class TabsPage {
  public tab1Root = HomePage;
  public tab2Root = CartPage;
  // カート内の商品の数
  public itemCount = 0;

  constructor(private events: Events, private storage: Storage) {}

  /**
   * Ionicのライフサイクルメソッド
   * タブ画面の作成時に呼び出される
   *
   * @memberof TabsPage
   */
  ionViewDidLoad() {
    // トピック`cart:updated`を購読している
    // 最新のカート内の商品数を取得して、バッジの数値を更新する
    this.events.subscribe(
      "cart:updated",
      count => (this.itemCount = count ? count : 0)
    );

    // add this!
    // アプリの初期表示時のタイミング
    // 前回起動時にカート内に商品を入れた場合を考慮して、
    // ストレージの読み込みを実施している
    this.storage
      .length()
      .then(result => {
        if (result !== 0) {
          this.storage
            .get("items")
            .then(items => {
              // 現在のカート内の商品の数を取得
              const count = items.length;
              // トピック`cart:updated`で出版
              // eventDataとして、カート内の商品の数を渡している
              this.events.publish("cart:updated", count);
            })
            .catch(err => console.log(`storage error: ${err}`));
        }
      })
      .catch(err => console.log(`storage error: ${err}`));
  }
}
カートに商品を追加した時

ショッピングカートに商品を追加したタイミングでも、当然カート内の商品の数が変わるため、Publishしてあげる必要がある。

商品詳細画面(src/pages/detail/detail.ts):

import { Component } from "@angular/core";
import { NavController, NavParams, Events } from "ionic-angular";
import { Storage } from "@ionic/storage";
import { Product } from "../../model/product.model";

@Component({
  selector: "page-detail",
  templateUrl: "detail.html"
})
export class DetailPage {
  product: Product;

  constructor(
    private navCtrl: NavController,
    private navParams: NavParams,
    private storage: Storage,
    private events: Events
  ) {}

  ngOnInit() {
    // 商品リスト画面でタップした商品情報を受け取る
    this.product = this.navParams.get("product");
  }

  /**
   * ショッピングカートに商品を追加する
   *
   * @param {Product} product
   * @memberof DetailPage
   */
  addItem(product: Product) {
    let itemList = [];
    this.storage
      .length()
      .then(result => {
        // カート内の商品が0個だった場合
        if (result === 0) {
          itemList = [];
          itemList.push(product);
          // ストレージにボタンを押下した商品を追加する
          this.storage.set("items", itemList);
          // add this!
          // トピック`cart:updated`で出版
          // eventDataとして、カート内の商品の数を渡している
          this.events.publish("cart:updated", 1);
        } else {
          // カート内にすでに商品があった場合
          this.storage
            .get("items")
            .then(items => {
              items.push(product);
              const count = items.length;
              // ストレージにボタンを押下した商品を追加する
              this.storage.set("items", items);
              // add this!
              // トピック`cart:updated`で出版
              // eventDataとして、カート内の商品の数を渡している
              this.events.publish("cart:updated", count);
            })
            .catch(err => console.log(`storage error: ${err}`));
        }
      })
      .catch(err => console.log(`storage error: ${err}`));
    // 商品リスト画面に戻る
    this.navCtrl.pop();
  }
}
注文を確定して、カートの中身が空になった時

注文を確定して、未決済の商品(つまりカート内の商品)が0件になったときも、Publishして購読者に最新の商品数を送ってあげる必要がある。

カート画面(src/pages/cart/cart.ts):

// ...長いので省略

  /**
   * 「注文する」ボタン押下時に呼び出し
   *  Confirm Alertを表示し、OKを選択すると、
   *  注文を確定して、ストレージをクリアする
   *
   * @memberof CartPage
   */
  order() {
    const confirm = this.alertCtrl.create({
      title: "注文を確定しますか?",
      buttons: [
        {
          text: "キャンセル",
          handler: () => {}
        },
        {
          text: "OK",
          handler: () => {
            let alert = this.alertCtrl.create({
              title: "ご注文を受け付けました!",
              subTitle: "ご指定の住所までのお届け時間は、20-30分程です",
              buttons: ["OK"]
            });
            alert.present();
            this.storage.clear();
            this.productList = [];
            this.isEmpty = true;
            // add this!
            // トピック`cart:updated`で出版
            // eventDataとして、カート内の商品の数を渡している
            this.events.publish("cart:updated", 0);
          }
        }
      ]
    });
    confirm.present();
  }

// ...長いので省略

ここまで書くと、ショッピングカート内の商品数と、バッジの表示が同期されます。


次回の記事