31
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ElmAdvent Calendar 2019

Day 15

Elm と Ionic でモバイルアプリっぽい UI を作る

Posted at

はじめに

Ionic を使い Elm でもモバイルアプリっぽい UI を作りたいと思って試してみました!どのように動くか気になる方はこちらのデモを見てください。この記事では Elm 上で Ionic を使う方法、その上でできること、できないことをまとめます。

Ionic とは

Ionic は、iOS や Android、Web などクロスプラットフォームでアプリを開発するための UI フレームワークです。以前は Angular でしか使うことができませんでしたが Stencil という Ionic チームが開発したフレームワークを使うことで React や Angular、Vue などでも使うことができるようになりました。どのようなコンポーネントが用意されているのか、また使い方などは UI Components - Ionic Documentation を見るとおおよそわかると思います。

Stencil とは

Stencil は全てのブラウザで実行できる、標準仕様に従った Web Components を生成するためのフレームワークです。Ionic はこのフレームワークを使って開発されています。個人的にプロパティやイベントを含めドキュメントを自動生成できるところが、ドキュメントの手動更新による間違いを減らすことができて便利そうでした。

Docs Readme Auto-Generation - Stencil

Stencil is able to auto-generate readme.md files in markdown. This is an opt-in feature and will save the readme files as a sibling to the component within the same directory. When this feature is used it can be useful for others to easily find and read formatted docs about one component.

Web Components とは

Elm と他のフレームワークを組み合わせる - Qiita

Web Components は,再利用可能なカスタム要素を作成し,ブラウザ上で利用するための技術,HTML Templates や Custom Elements,Shadow DOM をまとめた総称です.これまで React や Angular,Vue などのフレームワークを使用して実現していたカスタムコンポーネントや Scoped CSS などの機能を,標準の機能のみで実現することができます.

Ionic を使うための準備をする

Ionic を React や Angular、Vue 以外で使う場合は CDN、jsDelivr から配信されるファイルを使う、または npm で @ionic/core を追加することになります。ここで @ionic/core を使う場合、基本的には webpack などのバンドラーが必要になります。もし試すだけであれば CDN から配信されるファイルを使う方が簡単かもしれませんね。

CDN から配信されるファイルを使う

ここでは CDN から配信されるファイルを linkscript タグを使って HTML ファイルに追加する必要があります。そのため elm make--output オプションを使い JavaScript ファイルとして出力するようにします。次にドキュメントに従って次のタグを HTML ファイルに追加します。

It's recommended to use jsdelivr to access the Framework from a CDN. To get the latest version, add the following inside the <head> element in an HTML file, or where external assets are included in the online code editor:

<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css"/>

@ionic/core を使う

外部の CDN に依存したくない場合、バンドラーを使って自前で @ionic/core をバンドルする必要があります。これは少し面倒です。特に理由がないのであれば CDN を使う方法をオススメします。

まず、エントリーファイルに @ionic/core から必要となるファイルをインポートします。ここでは CSS ファイルを読み込めるようにする必要があり、webpack であれば css-loader を使う、または copy-webpack-plugin を使ってコピーするのもありかもしれません。

import { defineCustomElements } from "@ionic/core/loader";
import "@ionic/core/css/ionic.bundle.css";
import "ionicons/icons";

defineCustomElements(window);

また、Ionic には Ionicons というものがあり Ionic 上で使うアイコンを SVG として管理しています。そのため ion-icon などのタグを使いたい場合、適切な場所に SVG ファイルを配置する必要があります。import "ionicons/icons" すると SVG ファイルがインポートされるのでバンドラーのファイル出力先に svg フォルダを作り、その下に SVG ファイルを配置するようにします。例として webpack では次のように設定します。

{
  test: /\.svg$/,
  loader: [
    {
      loader: 'file-loader',
      options: { name: 'svg/[name].[ext]' }
    }
  ]
}

CDN から配信されるファイルを使う場合と比べるとちょっと面倒ですね!

Elm 上で Ionic のコンポーネントを扱えるようにする

node 関数や on 関数を使って Ionic のタグやプロパティ、イベントを Elm 上でも使えるようにします。これは使いたいコンポーネント毎に node 関数を記述していく必要があります。

import Html exposing (node)
import Html exposing (Attribute, attribute)
import Html.Events exposing (on, targetValue)

button =
  node "ion-button"

datetime =
  node "ion-datetime"

displayFormat =
  attribute "display-format"

onChange : (String -> msg) -> Attribute msg
onChange handler =
  on "ionChange" (Json.map handler targetValue)

タグやプロパティ、イベント毎に分けて作っておくと Html モジュールと同じように使えて便利です。

.
├── Ionic
│   ├── Attributes.elm
│   └── Events.elm
└── Ionic.elm

1 directory, 3 files

これで Ionic を使う準備が整いました!

使ってみる

基本的には Elm の Html と同じように記述します。

type alias Model =
  { datetime : String }

type Msg
  = DateTimeChanged String

view : Model -> Html Msg
view model =
  div
    []
    [ button [] [ text "Hello World" ]
    , datetime
        [ onChange DateTimeChanged
        , displayFormat "YYYY/MM/DD"
        , value model.datetime
        ]
        []
    ]

ここで使った ion-datetimeion-button 以外にも ion-refresherion-selection-toggle など、便利なコンポーネントが沢山用意されていて面白いです!

ion-action-sheet や ion-alert を使う

Ionic には画面にオーバーレイを表示し、ユーザが選択肢をタップすることで処理が先に進む ion-action-sheetion-alert といったコンポーネントも用意されています。これらは仕組み上、Elm だけでは表示することはできません。Elm の Ports を使って JavaScript 側でオーバーレイを表示、特定の選択肢が選択された場合に Elm 側に Ports を使って情報を送り返すといった処理が必要になります。また、次の例では ion-alert にはない create メソッドを使うため ion-alert ではなく ion-alert-controller を使用しています。

port createAlert : { header : String, message : String } -> Cmd msg

port onClickOkButton : (() -> msg) -> Sub msg

subscriptions : Model -> Sub Msg
subscriptions model =
  onClickOkButton (\_ -> OKButtonClicked)
ports.createAlert.subscribe(async ({ header, message }) => {
  const alertController = document.querySelector("ion-alert-controller");
  const alert = await alertController.create({
    header,
    message,
    buttons: [{
      text: "OK",
      handler: handler: () => ports.onClickOkButton.send(null)
    }]
  });
  await alert.present();
});

ページ遷移時のアニメーションが使えない

Ionic にはページ遷移時のアニメーションも用意されています。具体的には ion-routerion-route というコンポーネントが用意されていて、このコンポーネントに url 属性を指定することでルーティング、そして component 属性に HTML タグ名を指定することで、ページ遷移時にモバイルアプリの画面遷移のようなアニメーションが発火します。とても便利なコンポーネントですが、この component 属性に指定できるのは特定の Ionic コンポーネントと HTML タグ名だけで id や class などを指定することができず、Elm から使う方法はありません。

しかし @ionic/react など、内部的に customElements.define を使っていないフレームワークへの実装ではページ遷移を実現しています。どのようにページ遷移を実現しているのか @ionic/react-router のソースコードを読んでみたところ、まずルーター自体をそのフレームワークに合わせたもの、ここでは react-router で実装し直し、アニメーション用の関数を React コンポーネントに対して使っているようです。もし Elm で使う場合、Elm のページ遷移が発生したら Ports を介して JavaScript 側でページ遷移のアニメーションを行うことで実現できそう。これはまた別の機会に!試してみたいですね!

JavaScript のみで実装された ion-routerion-route のサンプルはあまり見かけないので例として、次のようなページ毎に customElements.define をしているソースコードはアニメーションも含めページ遷移が正しく動作します。

<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css"/>

<script>
customElements.define('page-a', class extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
<ion-content padding>
  <ion-button href="#/b">Go to Page-B</ion-button>
</ion-content>
`;
  }
});

customElements.define('page-b', class extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
<ion-content padding>
  <ion-button href="#/">Go to Page-A</ion-button>
</ion-content>
`;
  }
});
</script>

<ion-app>
  <ion-router>
    <ion-route url="/" component="page-a"></ion-route>
    <ion-route url="/b" component="page-b"></ion-route>
  </ion-router>
  <ion-nav></ion-nav>
</ion-app>

まとめ

まだちょっと問題はありますが、Elm でも Ionic を使ってモバイルアプリっぽい UI を作ることができました。簡単なものであれば十分使うことができそうで、特に PWA にしてフルスクリーンで起動できるととても面白そうですね。この記事で行ったこととは逆に Elm を Web Components として React や Angular、Vue などで使う方法もあります。気になる方は Elm と他のフレームワークを組み合わせる - Qiita を見てください!

31
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?