旧gaaamiiのブログ

間違ったことを書いている時があります。コメントやTwitter、ブコメなどでご指摘ください

ブログを blog.gaaamii.jp へ引っ越しました

かれこれ8年くらい続けたこのブログですが、ふと思い立って手作りのブログへ引っ越します。

blog.gaaamii.jp

昨日、引っ越し先のサイトでもRSS配信を始めたので、RSSリーダーで登録していただいてる方は新たに以下を登録してもらえればと思います。

これまでの記事をそちらにexportしたりはどうしようかなと思ったのですが、面倒なのと、URLが変わるのも良くないかなと思い、やめました。

書く内容は特に変わりませんが、せっかく手作りしてドメインもとったので、更新頻度は上げていこうと思っています。

Next.jsのISRのデモがとてもわかりやすい

Next.jsのドキュメント読んでいて、ISR(Incremental Static Regeneration)という言葉を知りました。文章を読めばなるほどという感じです。

普通の動的なコンテンツって、リクエストをもらったらDBなりAPIからデータとってきてHTMLなりJSONなりに埋め込んで返すって感じだと思います。でもISRの場合は、リクエストもらったらデータ埋め込んだページを生成して、その次のリクエストからそいつを返すようにするという感じっぽいです。動的なものなのに静的なHTML返すだけになるなんて、とても速そうですね。

自分の英語力があれなのもあって文章読んだだけだとほんとに理解できたか怪しかったのですが、デモがあったのでそれを見たところ、だいぶ安心できました。

reactions-demo.vercel.app

以下が確認したときの動画です。

vimeo.com

リアクションを押してカウントを 290 -> 291 にしてからページを見ます。そしてページをリロード。すると290のままです。しかしもう一度リロードすると、今度は291が反映されています。めでたしめでたし。

近況

子が生まれたりして急に忙しくなりました。自分の時間の確保が難しい。出来る人は意識的に時間確保して踏ん張ってるイメージだけど、自分は疲れると寝てしまい、全然そういうのができてない。諸々もうちょっとうまくやれるようになりたい。それはそうと、子が可愛くて仕方がない。キャッチボールとかテニスとか一緒にやれる日が楽しみです。

Chrome 86でFile System Access APIが使えるようになったのが嬉しい

題のとおりです。File System Access APIというのが入って、大変うれしいです。

developers.google.com

なぜ嬉しいのか

File System Access APIは、これまでもNative File System APIという名前で提供されていました。ただこれは試験的な機能という位置づけだったので chrome://flags/ から設定を変えないと使えず。趣味でローカルのファイルを読み書きするようなウェブアプリを作ってみても、知り合いに勧めづらい状況でした。

今回のChrome 86のリリースで、これがデフォルト設定で使えるようになったので、ウェブアプリからローカルファイルいじれるようになって嬉しかったのです。

なにができるのか

ファイルの読み書きができるというのはどういう感じでしょうか。こういう感じです。

Editing a file by Nekobito from gaaamii on Vimeo.

これは、趣味で作っているNekobitoというウェブアプリを使っている様子です。 そう、これウェブアプリなんですよ。ネイティブアプリ書いてる人からしたら感動はないのかもしれませんが、趣味でウェブアプリ作ってる者としてはローカルのファイルを普通に読み書きできてるのは、なかなか嬉しいことなのです。

ネイティブアプリのほうがいいよ

ブラウザがどんどんネイティブアプリっぽいことができるようになっているのを見ていると、でも実際ネイティブアプリのほうがいいよという声が(脳内で)聞こえてくることがあります。それはそうです。でもウェブアプリにはネイティブアプリにない雑さというか、検索して出てきたページのリンクをクリックして使い出す、みたいな、ソフトウェアをインストールとか考えずに雑に使い出せるという良さがあると思います。

今後もブラウザに便利APIが増えていくと、それを使う便利ウェブアプリも増えていくだろうし、なんか楽しそうです(語彙力)。


というわけで、便利APIChromeで使えるようになって、趣味開発のやる気が少し復活したという話でした。

Sonic Piというやつをインストールしてみた

なにか面白いプログラミング動画とかはないものかと思って、ScrrencastとかLivecodingみたいな言葉で検索をしていた。

すると、以下の動画が出てきた。

自分が探していたのはコードによる音楽演奏ではなく、コーディング生配信動画みたいなやつだったんだけど...。ライブコーディングっていうのはこういうのを指すのか。

とはいえ、これはこれで面白そうで、ついつい関連動画などを見ているうちに、こういったコーディングに興味が出てきた。自分も遊んでみたい。どうやら、この動画の彼が操っているSonic Piというソフトウェアは普通にMacにインストールして遊べそうだ。*1

sonic-pi.net

インストールしてみると、なにやらチュートリアルが丁寧だ。最初のほうは、ちゃんと日本語化されている。すぐに音の出し方が知れるし、コードはRubyで書ける。

f:id:shgam:20200830155926p:plain
Sonic Piのスクリーンショット

これは楽しい。しばらく触って遊んでみたい。

*1:この動画の彼は、Sonic Piの作者(Sam Aaron氏)らしい。

CSSで雨を表現する

せっかくの4連休ですが、なんの予定もありません。まだまだあちこち外出するのは怖いです。しかも連休初日は雨です。散歩くらいはしようと思ってましたが、この雨だとやる気が削がれますね。せっかく暇で、外は雨が降っているので、雨をCSSで表現できたら良いなと思い、やってみました。

雨を見てみる

雨を見てみると、たくさんの水滴が上から下に落ちています。これをCSSで表現できればよさそうです。

たくさんの水滴

水滴は英語でwater dropのようなので、.drop というdivを用意しました。そしてこれがたくさん必要なのですが、HTMLにたくさん書くのはなんだかなと思って、一つだけ用意して、画面読み込み時にJavaScriptで任意の数にコピーするようにしました。

const createDrops = () => {
  const rain = document.querySelector('.rain')
  const drop = document.querySelector('.drop')
  const numberOfDrops = 150
  for (let i = 0; i < numberOfDrops; i++) {
    rain.appendChild(drop.cloneNode())
  }
}

window.onload = () => {
  createDrops();
}

上から下に落とす

次に、上から下に落とします。.drop要素が、margin-top: 0からmargin-top: 100vhへ移動すれば良さそうです。

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
}
.rain {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: space-between;
  position: fixed;
  opacity: 0.8;
}
.drop {
  width: 1px;
  height: 0vh;
  background: #999;  
  animation: falldown 1s infinite;
  height: 10vh;
}
@keyframes falldown {
  0% {
    margin-top: 0vh;
  }
  100% {
    margin-top: 100vh;
  }
}

水滴には見えませんが、たくさんのなにかが下に落ちているのでよさそうです。

雨粒っぽくする

これで雨です!というのは強引すぎるので、.drop をちゃんと雨粒っぽくしたいです。

上から下へ、揃って落ちていると雨っぽくありません。なのでどうにかしてそれをずらしたい。そこで、animation-delay プロパティを使います。.drop:nth-child(2n)で、偶数番目の.drop要素が落ちるタイミングを0.5秒遅くしてみます。

.drop:nth-child(2n) {
  animation-delay: .5s;
}

落ちるタイミングがずれましたが、まだ雨っぽくはないですね。なので、いっぱいずらします。

.drop:nth-child(2n) {
  animation-delay: .1s;
}
.drop:nth-child(3n) {
  animation-delay: .2s;
}
.drop:nth-child(4n) {
  animation-delay: .3s;
}
.drop:nth-child(5n) {
  animation-delay: .4s;
}
.drop:nth-child(6n) {
  animation-delay: .5s;
}
.drop:nth-child(7n) {
  animation-delay: .6s;
}
.drop:nth-child(8n) {
  animation-delay: .7s;
}
.drop:nth-child(9n) {
  animation-delay: .8s;
}
.drop:nth-child(10n) {
  animation-delay: .9s;
}

少しは雨っぽくなりました。ただ、なんて規則的な降り方をする雨なんだ、という感じがしますね。なので、ちょっと順番をいじくります。

.drop:nth-child(2n) {
  animation-delay: .3s;
}
.drop:nth-child(3n) {
  animation-delay: .9s;
}
.drop:nth-child(4n) {
  animation-delay: .2s;
}
.drop:nth-child(5n) {
  animation-delay: .1s;
}
.drop:nth-child(6n) {
  animation-delay: .5s;
}
.drop:nth-child(7n) {
  animation-delay: .4s;
}
.drop:nth-child(8n) {
  animation-delay: .7s;
}
.drop:nth-child(9n) {
  animation-delay: .8s;
}
.drop:nth-child(10n) {
  animation-delay: .6s;
}

まだまだ規則的ですが、こだわりだすと大変そうなのでこれで次に進みます。

色をつける

白地に黒い線が降っている状態なので、もう少し雨っぽくするため、背景に色をつけます。どんよりした色にしたいので、こんな感じにします。

body {
  background: linear-gradient(#666f6d, #999);
}

おお〜、これはだいぶ雨じゃないでしょうか。

仕上げ

あとは雰囲気でいじっただけなので割愛しますが、雨の落ち方やら色のことがわかっていれば、もっとリアルに雨を表現できそうです。

まとめ

普通のCSSなので、こいつの上に画像でものっけて透明度を調整すれば、雨の街みたいなのもできそうですね。横浜(みなとみらい)の画像をのっけてみるとこんな感じになりました。

See the Pen Rainy Yokohama by gaaamii (@gaaamii) on CodePen.

Flexboxコンテナの左右の余白をいい感じにする

Flexboxコンテナの左右の余白をいい感じにしたい!でもできない!と悩んだので、それを共有します。最終的に、CSSだけで書くのは諦めてJavaScriptを書きました。悔しい!!

なにを実現したいのか

いくつかのアイテムを持つFlexboxコンテナで、左揃えで、画面内におさまるように折り返して、コンテナの左右の余白は均等になっているやつをCSSで書きたかった。

こういうやつです。

やってみよう

こんなん楽勝でしょ。と手を付けたんですが、楽勝ではありませんでした。

折り返す

まずは折返しです。これはもう、flex-wrap: wrap; を指定すればいいだけです。これはすぐできました。

/* 値を設定するだけなら変数にする必要はないのだけど、*/
/* このときはcalc()などを活用してCSSだけでいけると思っていた... */
:root {
  --cardWidth: 240px;
  --cardHeight: 280px;
  --cardMargin: 16px;
}
.container {
  display: flex;
  justify-content: flex-start;
  flex-wrap: wrap;
  border: 1px solid #ccc;
}
.card {
  width: var(--cardWidth);
  height: var(--cardHeight);
  margin: var(--cardMargin);
  border: 1px solid #ccc;
  border-radius: 8px;
  background: #fff;
  box-sizing: border-box;
}

See the Pen Flexboxでレスポンシブなやつ試作その1 by gaaamii (@gaaamii-the-sasster) on CodePen.

折返したのはいいのですが、コンテナの左右の余白がいまいちですね。

左右の余白を均等にする

コンテナの全体の余白を、どう指定するべきか。コンテナに対して margin: 0 auto; で済めば嬉しいのですが、そうはいきませんでした。インスペクタで幅を見てみると、コンテナの幅は、その親要素いっぱいの幅になっていました。

どうしよう。そもそもコンテナの幅があってその中で幅を分配するというのが自然ですが、今回の場合、アイテムとそのパディングが先に決められていて、そこからコンテナの幅が決まってほしいわけです。

今回の場合、コンテナの幅を決めるためには、「一行にいくつのアイテムが並べられているか」がわかればよさそうだと考えました。

アイテムが1行にn個表示されているとしたら、コンテナの幅は、

コンテナの幅 = (アイテムの幅 * n) / 2

です。では、nはどうやって出せば良いのでしょう。

n = コンテナの親要素の幅 / アイテムの幅

です。これくらいの算数なら自分でもできるぞ!さっそく実装してみます。

JavaScriptの力を借りる

CSSは先程と同じです。で、それに加えてこんなJavaScriptを書きました。

const CARD_PADDING = 32 // カード1つにつける余白

const state = {
  container: null, // コンテナ要素への参照
  card: null, // カード要素への参照
  numberOfItems: null, // カードが1行にいくつ表示されているかの状態
}

const initState = () => {
  state.container = document.getElementsByClassName('container')[0]
  state.card = document.querySelector('.card')
}

// 現在のウィンドウ幅にいくつカードが表示できるかを計算
// カードの表示可能な数が変わったら, それをもとにコンテナ要素の幅を更新する
const updateContainerWidth = () => {  
  const cardAllWidth = state.card.offsetWidth + CARD_PADDING
  const numberOfItems = Math.floor(window.innerWidth / cardAllWidth)
  if (state.numberOfItems !== numberOfItems) {
    state.numberOfItems = numberOfItems
    const containerWidth = state.numberOfItems * cardAllWidth
    state.container.setAttribute("style", `width: ${containerWidth}px`)
  }
}

window.onload = () => {
  initState()
  updateContainerWidth()
}

window.onresize = () => {
  updateContainerWidth()
}

できました。実際のアプリケーション開発に取り入れるには、Reactなりなんなりの書き方になおさないといけないですが、できることがわかったのでよしです。

See the Pen Flexboxでレスポンシブなやつ試作その2 by gaaamii (@gaaamii-the-sasster) on CodePen.

なぜCSSだけでできなかったのか

CSSだけでいけそうじゃん!という感じですが、なぜ今回CSSだけでいけなかったのか。書き始めたときは、以下のCSSで実現できると思っていました。上記のJavaScriptでやっていることと同じことを、最初はCSSでやろうとしていたのです。

:root {
  --cardWidth: 240px;
  --cardHeight: 280px;
  --cardMargin: 16px;
  --cardAllWidth: calc(var(--cardWidth) + (var(--cardMargin) * 2));
}
.container {
  --numberOfItems: calc(100vw / var(--cardAllWidth));
  --containerWidth: calc(var(--cardAllWidth) * var(--numberOfItems));
  display: flex;
  justify-content: flex-start;
  flex-wrap: wrap;
  border: 1px solid #ccc;
  width: var(--containerWidth);
}
.card {
  width: var(--cardWidth);
  height: var(--cardHeight);
  margin: var(--cardMargin);
  border: 1px solid #ccc;
  border-radius: 8px;
  background: #fff;
  box-sizing: border-box;
}

ですが、このCSSには問題があります。

CSScalc() では、非number値による除算はできません。

/

右側は <number> でなければなりません。

https://developer.mozilla.org/ja/docs/Web/CSS/calc

なので、実際にはこの行でほしい値は出ませんでした(var(--cardAllWidth)はnumberではなくlength)。

--numberOfItems: calc(100vw / var(--cardAllWidth));

雑感

CSSの変数とcalc()があればなんでもできるだろ!という気持ちで書き始めたのですが、見事に敗北しました。CSS難しい...。もし、CSSだけでも書けるじゃんと思った方いたら、教えていただけると嬉しいです。

BearはやめてNekobitoを使うようにした

f:id:shgam:20200621235902p:plain

以前から、仕事やら生活やらで必要なメモはBearを使って書いていた。使いやすく、Pro(iCloud同期とかできる)でも月額150円と安く、満足していた。けど、これだといつまでたってもNekobito(手作りのMarkdownメモ帳)がよくならない...と思い、思い切ってBearを使うのをやめた。

Nekobitoをどう使うか

ふつうにメモをファイルに保存して管理する。chrome://flags/ でNative FileSystem APIをenabledにしておけば、ファイルを開閉、編集できるようになっている。とはいえ一端末でしかメモが見れないのは不便なので、外部のオンラインストレージサービスに同期するようにはしておく。

Nekobitoをどうしていくか

自分で使っていて困る順に修正を入れていく。せっかくの趣味開発なので、あまり人に勧めたりせず、好き勝手な優先順で改修を繰り返す。いずれは、Bearとかより使いやすいものにできたらいいな。

なぜか急に日記を書きたくなった

急に日記を書きたくなった。どうせ毎日は続かないので、気が向いたときに書ければと思う。公開しているブログだと書けないこと、人に見られてしまったら困るけど頭の中を整理しておきたいことみたいなのを書きたい。ちょうど趣味でつくっているMarkdownエディタがあるので、それを使ってローカルのファイルに書き込んで保存していくだけにする。

Elmでランタイムエラーを起こす方法を教えてもらった

※この記事の検証環境はElm 0.19.1です。

Elmでランタイムエラーを起こす方法をTwitterで尋ねたところ、すぐに教えてもらえました。Elmコミュニティすごい!ありがとうございます!!

確認

以下のコードを書いてみたところちゃんとエラーになりました。

コード

div [ attribute "" "" ] []

出てきたエラー

InvalidCharacterError: Failed to execute 'setAttribute' on 'Element': '' is not a valid attribute name.

エラーになった!やった!

なぜランタイムエラーを起こしたくなったのか

以上でこの記事終わりでいいんですが、どうしてランタイムエラーを起こしたくなったのかというのも書いておきます。

Elm製アプリケーションを運用していて、Sentryの導入を考えていました。で、JavaScript側にSentryをセットアップしたときに、Elm側で起きたエラーも拾ってくれるのだろうか...?という疑問がありました。おもむろにSentryをセットアップして、JavaScriptからエラーが飛ぶことを確認した後に、さてElm側でもエラーを起こして確認だと思ったものの、あれ、Elmってランタイムエラー起きないことを謳ってるよな。起こす方法あるの...?となりました。

なぜattribute関数でランタイムエラーを起こせたのか

これも蛇足ですが、どうしてランタイムエラーを起こせたのでしょうか。

エラーの理由は、エラー文に書いてあるとおりです。setAttributeを不正な引数で呼んだからですね。ただ、今日はせっかくの休日なのでソースコードを見てみたいと思います。https://github.com/elm/html/blob/master/src/Html.elm を見ると、DOMを吐き出す関数はこんな感じです。

node : String -> List (Attribute msg) -> List (Html msg) -> Html msg
node =
  VirtualDom.node

なので、VirtualDom.nodeを見てみます。

node : String -> List (Attribute msg) -> List (Node msg) -> Node msg
node tag =
  Elm.Kernel.VirtualDom.node (Elm.Kernel.VirtualDom.noScript tag)

なるほど...?

var _VirtualDom_nodeNS = F2(function(namespace, tag)
{
    return F2(function(factList, kidList)
    {
        for (var kids = [], descendantsCount = 0; kidList.b; kidList = kidList.b) // WHILE_CONS
        {
            var kid = kidList.a;
            descendantsCount += (kid.__descendantsCount || 0);
            kids.push(kid);
        }
        descendantsCount += kids.length;

        return {
            $: __2_NODE,
            __tag: tag,
            __facts: _VirtualDom_organizeFacts(factList),
            __kids: kids,
            __namespace: namespace,
            __descendantsCount: descendantsCount
        };
    });
});

なるほど!!(わからん)

深入りするとElmのVirtualDom実装理解という別テーマになってしまうので、完全理解は諦めて、このモジュールでsetAttributeしているところを見てみます。

function _VirtualDom_applyAttrs(domNode, attrs)
{
    for (var key in attrs)
    {
        var value = attrs[key];
        typeof value !== 'undefined'
            ? domNode.setAttribute(key, value)
            : domNode.removeAttribute(key);
    }
}

ここだけ見れば難しくないです。domNodeはきっとvirtualdomがつくってくれた生DOMで、attrsはきっと {'class': 'hoge', 'id': 'fuga', ...}みたいな連想配列でしょう。ということは、Elmのdiv [ attribute "" "" ] []というコードを書いたとき、実行時には以下のようなことが起きているはずです。

domNode.setAttribute('', ''); // domNodeはdocument.createElement('div');相当のオブジェクト
Uncaught DOMException: Failed to execute 'setAttribute' on 'Element': '' is not a valid attribute name.
    at <anonymous>:1:4

まとめ

ざっくり追って、どういうことかわかりました。VirtualDomに興味が出てきたので、時間があるときに今度はそちらを深追いしてみたいです。

Scrapboxを使って輪読会をやってみた

Scrapbox上で、みんなでわいわい勉強メモをつけるきまべんという集まりがあるんですが、そこで輪読会を企画してみました。

scrapbox.io

思いついてScrapboxにページだけつくって放置していたところ、id:tkykhk さんや@t__nabeさんがいい感じに取りまとめてくれて、やることになりました。感謝です。今年のGWはコロナ自粛ということもあり、4日間(各1時間)も開催できて、そこそこ進みました。

やっていること

だいたいこんな感じでやっております。

  • 項ごとに音読(全員でローテーション)
  • 1回1時間
  • 都合が合いそうな休みの日の14:00〜に開催
  • 音読したあとに疑問とか補足を雑に話す

輪読会の良いところ

この輪読会の良いなーと感じるところはこんな感じです。

  • さっと読んだだけで自分が理解してなくても、「これってどういうことですかね...?」みたいに止めれば、誰かしら理解してる人が補足してくれるので理解の助けになる
  • 進み悪いかなと思いきや、意外と自分一人で読むときよりもさくさく進む(すぐに飽きがこないからかもしれない)
  • 関連する実体験とかも話せて楽しい。受託だったり自社サービスだったり、クライアントだったりサーバーだったりそれぞれ微妙に違うポジションで働いているので、経験していることも違っていて楽しい。
  • Scrapboxに全部メモが残るので終わったあと復習もできる。

今後

いまはReal World HTTPという本を読んでいるのですが、これ以外にも、1人で読み切る自信がない(けど読みたい)本がたくさんあります。そういう本を提案して、みんなでちまちまと読んでいけたら楽しそうだなと思います。

最近やってみたいこと

やってみたいことが思いついても、忙しいと忘れてしまうのでブログに残しておく。

nekobitoのエディタ部分をいい感じにする

https://github.com/gaaamii/nekobito でひとまずファイル操作ができるようになったので、エディタの部分を使いやすくしたい。

CMS(ブログ)をつくる

ブログ用のCMSをつくりたい。はてなブログはとても使いやすく、ちゃんとProにして使っているのだけど、やっぱエンジニアとしては自分で管理したほうがスキルアップにつながりそうだし、楽しそう。ただ、インフラまで手を出すと厳しそうなので何かしらのPaaSにのせて動かせるようなものをつくるところまでを目標にしてやっていきたい。

UIデザインの勉強

趣味開発で、使いやすいものが作れたら楽しいだろうなと思い、UIデザインをちゃんと勉強しておきたい。あと、デザイナーの人が意図してることをちゃんと理解して実装できるようにもなりたい。

ジェネラティブアートの勉強

なんとなく面白そう。学んだことをベースにCodePenとかに自分のお絵かきみたいなものを公開できたらなお楽しそう。

SQLの勉強

必要な場面がそこそこ多い割にSQLが苦手と言い続けて生きてきたので、そろそろ克服しておきたい。具体的には、SQLグラフ原論という本を読みたい。

YouTubeあるいはPodcast活動

性格的に勉強会でLTとかハードルが高いし面倒に感じてしまう。しかしアウトプット的なことはしていきたいので、YouTubeで一人LTとかスクリーンキャストとか、あるいはPodcastをやればいいんじゃないかという気持ちになってる。Podcastドメインとってるので、ドメイン代もったいないのでなんかしら更新したいというのもある。

方針とか

なるべく楽しそうなことを優先してやっていきたい。仕事を失うと困るのでキャリア戦略とかも大事だけど、そればっかりだと変なことができないので。

プルダウンメニューを再帰的に表示する

プルダウンメニューの実装について。先日の記事でも触れましたが、あれについてもうちょっと書けそうなネタがあったので書いてみます。

つくったもの

こういうものです。

実装

このように使えるように書きました。

let filePulldown =
    { id = "File"
    , label = "File"
    , checked = False
    , children =
        PullDown.Children
            [ { id = "File/New"
              , label = "New"
              , children = PullDown.empty
              , checked = False
              }
            , { id = "File/Open"
              , label = "Open"
              , children = PullDown.empty
              , checked = False
              }
            , { id = "File/Save"
              , label = "Save"
              , children = PullDown.empty
              , checked = False
              }
            ]
    }
in
PullDown.view [ filePullDown ] Pulldown.rootLevel

入れ子構造になっていて、childrenに入れ続ければいくらでもネストした要素を表示できるようにしたかったです。

ネストした構造を再帰的に表示

view : List PullDown -> Int -> Html Msg
view pullDowns level =
    let
        classes =
            classList
                [ ( "app-pulldown--level-" ++ String.fromInt level, True )
                , ( "app-pulldown__children", level > 1 )
                ]
    in
    ul [ classes ] <| List.map (viewPullDown level) pullDowns


viewPullDown : Int -> PullDown -> Html Msg
viewPullDown level pullDown =
    case pullDown.children of
        Children [] ->
            if pullDown.checked then
                li [ classList [ ( "app-pulldown--checked", pullDown.checked ) ] ]
                    [ text pullDown.label
                    , span [ class "material-icons", Html.Attributes.style "font-size" "16px" ] [ text "check" ]
                    ]

            else
                li [ classList [ ( "app-pulldown--checked", pullDown.checked ) ], onClick (OnClick pullDown.id) ]
                    [ text pullDown.label ]

        Children children ->
            if level > 1 then
                li []
                    [ div [ class "app-pulldown__label" ] [ text pullDown.label ]
                    , span [ class "material-icons" ] [ text "arrow_right" ]
                    , view children (level + 1)
                    ]

            else
                li []
                    [ div [ class "app-pulldown__label" ] [ text pullDown.label ]
                    , view children (level + 1)
                    ]

viewPullDown関数が何をしてるかというと、パターンマッチでchildrenを見て以下の処理にしています。

  • 空配列のとき:テキスト要素を返す
  • 配列に中身があるとき:childrenを渡してviewを呼び出す

雑感

実務ではこういう深さが決まってないネストしたものを末端まで処理していくようなやつを書く機会に遭遇してなかった(あってもライブラリを使うとかしてた)ので、楽しかったです。