# 1hで半分わかった気になる[Alpine.js](https://alpinejs.dev/) 概要 > 今ホットなwebフレームワークのひとつである Alpine.js を使って、カレンダーツールを作ってみましょう! > Alpine.js は非常に小さく、特別なインストール操作などをせずに開発できるフレームワークです。 > 実装されている15個の属性、6個のプロパティ、2個の関数のうち、半分を1hで一緒にコーディングしながら学びます。 ## Alpine.js とは? - シンプルで軽量でパワフルなJavaScriptフレームワーク - 15のアトリビュート(属性) - HTMLタグのあとに続いて書くもの - 例) imgタグの src 属性 - `<img src="example.png"/>` - 6のプロパティ(値) - Alpine.jsで定義済みの便利な値を利用できる - 2のメソッド(ほぼ関数) - JavaScriptから利用できる処理 →合わせて23個覚えればほぼマスター! ## 【前編】 使ってみよう Alpine.js 1. [codepen](https://codepen.io/pen/) を開く 2. CDNインポートでAlpine.jsを利用できるようにする - ↓をHTMLのエディタの右上の歯車から設定画面を開いて`Stuff for head`に追加する - `<script src="//unpkg.com/alpinejs" defer></script>` ### HTMLを書いて試してみる #### ボタンでテキストの表示を制御できるようにしてみる ```htmlembedded= <div x-data="{ flag: false }"> <button @click="flag = !flag">toggle</button> <span x-show="flag" x-transition>hello, Alpine.js !</span> </div> ``` ##### 使った機能 - **x-data** - この属性が指定された要素以下で、Alpine.jsでの制御が可能になる - 属性に渡すオブジェクトの中身 (↑の例では`{ flag: false }`) に自身と子孫要素からアクセスできる  - **x-on:\<event\> (@\<event\>)** - 要素のeventに対してイベントハンドラを設定する - ↑の例では`@click="flag = !flag"` のことで、`x-on:click="flag = !flag"` としても同義 - **x-show** - 要素の表示・非表示を制御する - 渡す値がtruthyなら表示、 falsyなら非表示 - **x-transition** - 要素が表示されるときにトランジション(エフェクト)がかかる ##### おまけ - codepenを保存してリロードしてみよう - 一瞬だけ最初見えてほしくない `hello, Alpine.js !` が見えてしまうかも - ↑はAlpine.jsによる初期化より前にブラウザで描画されてしまうせい - これを解決するには、 div要素に `x-cloak` を追加する - **x-cloak** - Alpine.jsによる初期化が終わるまで要素を隠す ### 値を表示してみる #### ボタンを押した回数をカウントして表示する ```htmlembedded= <div x-cloak x-data="{ count: 0 }"> <button x-on:click="count++">click me!</button> <span x-text="count"></span> </div> ``` ##### 使った機能 - **x-text** - 渡された値を属性が指定された要素のinnerTextに設定する #### 現在時刻を表示する ```htmlembedded= <div x-cloak x-data x-init="now = new Date()"> <span x-text="now.toLocaleString()"></span> </div> ``` ##### 使った機能 - **x-init** - Alpine.jsが要素を初期化するときに渡された処理を実行する ### 要素の表示をもっと制御する #### ボタンで要素の表示を切り替える ```htmlembedded= <div x-data="{ flag: false }"> <button @click="flag = !flag">toggle</button> <template x-if="flag"> <span>hello, Alpine.js !<span> </template> </div> ``` ##### 使った機能 - **x-if** - templateタグにのみ使用できる - x-showと表示上は似て見えるが、内部的に以下の差がある - x-showはCSSで `display: none` している - x-ifはtemplateタグの中身そのものをhtmlに追加したり削除したりして制御している - x-ifには `x-transition` が使えない #### 要素を繰り返し作成する ```htmlembedded= <div x-data> <template x-for="i in 5"> <div> <span x-text="i"></span> </div> </template> <template x-for="i in ['あ', 'い', 'う', 'え', 'お']"> <div> <span x-text="i"></span> </div> </template> </div> ``` ##### 使った機能 - **x-for** - templateタグにのみ使用できる - `x-for="i in 5"` のようにすると`i`は`1~5`になり、`x-for=""['あ', 'い', 'う', 'え', 'お']"` のようにすると配列の要素を一つずつ`i`として要素を作成する ## 【後編】 カレンダー作るぞ :muscle: ### 下準備 - このあとの作業を楽にしたいので、CSSに↓を追加します ```css= * { box-sizing: border-box; margin: 0; border: none; padding: 0; background-color: transparent; } html, body { height: 100%; width: 100%; } ``` - 色を選ぶタイミングがいくつかあるので↓のページを開いておくといいです - https://developer.mozilla.org/ja/docs/Web/CSS/named-color ### 見た目を作る `x-for`を利用して7日×6週分の欄を用意する → 日曜始まりカレンダーのとき、1日が土曜になると30,31が6週目になるので html ```htmlembedded= <main x-cloak x-data="calenderData()"> <div class="month"> <span x-text="getMonthText()"></span> </div> <div class="calender"> <template x-for="i in 7*6"> <div class=cell> <span class="day" x-text="getDateText(i)" ></span> </div> </template> </div> </main> ``` css ```css= * { box-sizing: border-box; margin: 0; border: none; padding: 0; background-color: transparent; } html, body, main { height: 100%; width: 100%; } .month { height: 40px; width: 100%; font-size: 32px; fort-weight: bold; display: grid; place-content: center; } .calender { height: calc(100% - 40px); width: 100%; padding: 8px; display: grid; background-color: ghostwhite; grid-template-rows: repeat(6, 1fr); grid-template-columns: repeat(7, 1fr); gap: 8px; } .calender .cell { border-radius: 8px; padding: 8px; background-color: white; } .calender .cell:nth-child(7n+1) { background-color: aliceblue; color: midnightblue; } .calender .cell:nth-child(7n+2) { background-color: lavenderblush; color: darkred; } ``` js ```javascript= const daysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; const calenderData = () => ({ currentMonth: null, init() { this.currentMonth = new Date(); this.currentMonth.setDate(1); }, getDateText(i) { const date = i - this.currentMonth.getDay() if (date < 1) { return '-' } else if (date > daysOfMonth[this.currentMonth.getMonth()]) { return '-' } else { return date } }, getMonthText() { // 0~11 で帰ってくるので +1 する return (this.currentMonth.getMonth() + 1).toString() + '月' }, }); ``` ↑のコードを書いたあと↓ ![](https://i.imgur.com/Z5XaX0E.png) ### クリックした日付がわかるようにする html ```htmlembedded=7 <div class=cell @click="onClickCell($el)"> ``` css ```css=49 .calender .cell.clicked { box-shadow: 0px 0px 10px gray; } ``` js ```javascript=26 onClickCell(el) { if (this.clickedCell) { this.clickedCell.classList.remove('clicked') if(this.clickedCell.isEqualNode(el)) { this.clickedCell = null return } } this.clickedCell = el this.clickedCell.classList.add('clicked') }, ``` ##### 使った機能 - **$el** - 現在のDOMへの参照 - ↑の例では関数に渡して `document.querySelector~~~...` のような面倒な処理をしなくてもいいようにしています ### 予定を入れられるようにする html ```htmlembedded=7 <div class=cell @click="onClickCell($el, getDateText(i))"> <span class="day" x-text="getDateText(i)" ></span> <br> <span class="schedule-detail" x-text="$store.schedules[getDateText(i)]" ></span> </div> ``` ```htmlembedded=20 <div class="popup" x-cloak x-show="$store.popupData && $store.popupData.date" x-data="popup"> <span class="title" x-text="$store.popupData.date + '日の予定'"></span> <button class="close" @click="close()">close</button> <button class="save" @click="save($store.schedules)">save</button> <textarea class="schedule" x-model="$store.schedules[$store.popupData.date]" type="text"> </textarea> </div> ``` css ```css=53 .popup { position: absolute; top: 0; left: 0; width: 80%; height: 80%; margin: 10%; border-radius: 16px; padding: 16px; box-shadow: 0px 0px 10px tomato; background-color: white; display: grid; grid-template: 'title save-button close-button' 24px 'schedule schedule schedule' 1fr / 1fr 80px 80px; gap: 8px; } .popup span.title { grid-area: title; } .popup button.close { grid-area: close-button; width: 100%; height: 100%; border-radius: 24px; background-color: lightgray; font-size: 20px; } .popup button.save { grid-area: save-button; width: 100%; height: 100%; border-radius: 24px; background-color: lightgray; font-size: 20px; } .popup textarea.schedule { grid-area: schedule; border: 2px solid lightgray; border-radius: 16px; padding: 16px; } ``` js ```javascript= const daysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; const calenderData = () => ({ currentMonth: null, clickedCell: null, openPopuo: false, init() { this.currentMonth = new Date(); this.currentMonth.setDate(1); Alpine.store('schedules', JSON.parse(window.localStorage.getItem('schedules') ?? '{}')) }, getDateText(i) { const date = i - this.currentMonth.getDay() if (date < 1) { return '-' } else if (date > daysOfMonth[this.currentMonth.getMonth()]) { return '-' } else { return date } }, getMonthText() { // 0~11 で帰ってくるので +1 する return (this.currentMonth.getMonth() + 1).toString() + '月' }, onClickCell(el, date) { if (date !== '-') { Alpine.store('popupData', { 'date': date }) Alpine.store('schedule', { date: '' }) } if (this.clickedCell) { this.clickedCell.classList.remove('clicked') if(this.clickedCell.isEqualNode(el)) { this.clickedCell = null Alpine.store('popupData', { 'date': null }) return } } this.clickedCell = el this.clickedCell.classList.add('clicked') } }); const popup = () => ({ close() { Alpine.store('popupData', { 'date': null }) }, save(schedules) { Alpine.store('schedule', schedules) window.localStorage.setItem('schedules', JSON.stringify(schedules)) Alpine.store('popupData', { 'date': null }) } }); ``` ##### 使った機能 - **x-model** - input要素やtextarea要素のような入力を受け付ける要素に指定できるアトリビュート - 入力された値を属性に渡された変数にバインド(同期)する - **$store** - テンプレート(HTML)から`Alpine.store()`で保持している値にアクセスするためのプロパティ - **Alpine.store()** - テンプレートから`$store`でアクセスできるようにしたい値を流す関数 - alpineの機能じゃないけど[*localStorage*](https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage) - 値をブラウザに保存しておける標準API ## まとめ 出来あがったものは[こんな感じ](https://codepen.io/yamaji-toshiyuki/pen/XWPbNQy)になっていると思います。 Alpine.jsの今日使った機能の一覧は↓ - Attributes - `x-data` - `x-on:<event> (@)` - `x-show` - `x-transition` - `x-cloak` - `x-text` - `x-init` - `x-if` - `x-for` - `x-model` - Properties - `$el` - `$store` - Methods - `Alpine.store()` の13個でした〜! [Alpine.jsの公式トップ](https://alpinejs.dev)に書いてある数は 15+6+2 = 23 なので、これで半分わかった気になれましたね! Alpine.jsは軽量でCDNインポートなどで手軽に利用できるため、様々な場面で活用してもらえると思います! 勉強会の内容を生かして、まずは今回作ったカレンダーツールを改善してみてはいかがでしょうか? :smile: