簡単Stimulus
この記事は ケーシーエスキャロット Advent Calendar 2023 の記事です。
先日お仕事でStimulusを扱う機会があり、 最初こそとっつきにくいと感じましたが、割とスムーズに理解することができました。 ちょうど良い機会なので、Stimulusの使い方をまとめてみようと思います。
と言っても基本的な使い方は Stimulus Handbook にまとまっているので、 この記事では実際に実装しながら使い方を簡単に振り返ってみようと思います。
注意
- この記事ではStimulusの導入方法は扱いません
作るもの
更新ボタンを押すと、APIから現在時刻を取得し、画面に表示する機能をStimulusで実装します。
イメージは以下です。
今回は段階を追って進めていきます。
準備
view と stimulus controller を用意します。
view
こんな感じのviewを用意します。
<div> <button>更新</button> <div> <%= Time.current %> </div> </div>
Stimulus controller
Stimulus の controller は bin/rails g stimulus
コマンドを使うと、Stimulus の controller ファイルの作成と、読込設定まで追加してくれます!(便利)
今回は update-button
という名前の controller を作成するので、コマンドは以下になります。
bin/rails g stimulus update_button
※update-button という命名はあまり適していないような気がしますが、スルーしてください。
作成された app/javascript/controller/update_button_controller.js
はこんな感じになっていると思います。
import { Controller } from "@hotwired/stimulus" // Connects to data-controller="update-button" export default class extends Controller { connect() { } }
1: 更新ボタンにクリックイベントを設定する
いきなり全てを実装するのも難しいので、まずは更新ボタンにクリックイベントを設定するところから。
更新ボタンをクリックすると alert ダイアログを表示するようにしてみます。
view と Stimulus controller を以下のように書き換えます。
<div data-controller="update-button"> <button data-action="click->update-button#update">更新</button> <div> <%= Time.current %> </div> </div>
export default class extends Controller { update() { alert("update!!!") } }
view の この部分。
<div data-controller="update-button"> <button data-action="click->update-button#update">更新</button>
一番上の div の data-controller
で適応させる Stimulus の controller を指定します。
※controller のファイル名は _
だが、controller 名としては -
を使うので注意
また、button には data-action
でイベントと、イベント発火時に実行する処理を設定しています。
この場合は、クリックイベント発火時に、 update-button
controller の update
というメソッドを実行する、という設定になります。
2: 更新ボタンクリック時に時刻表示を書き換えるようにする
次は実際に画面の表示を書き換えられるようにします。
更新ボタンをクリックすると、日時表示の要素の innerHTML
を「更新した!」という文字で更新します。
書き換え先の要素は getElementByID
などを使って取得することもできますが、
Stimulus には要素を参照するための target という属性があるのでこれを使います。
view と Stimulus controller を以下のように書き換えます。
<div data-controller="update-button"> <button data-action="click->update-button#update">更新</button> <!-- ここを書き換える --> <div data-update-button-target="area"> <%= Time.current %> </div> </div>
export default class extends Controller { // area という target を定義 static targets = ["area"] update() { this.areaTarget.innerHTML = "更新した!" } }
書き換え先の要素を area
という名前の target で指定させるようにしました。
この area
はどこを参照するかというのは view で、data-controller名-target
という属性で指定します。
<div data-update-button-target="area"> <%= Time.current %> </div>
area
target は controller 側で this.[target名]Target
とすると参照することができます。
この場合 this.areaTarget
で、area
targetに指定した要素が参照可能です。
3: APIから取得した値で時刻表示を書き換えるようにする
次は実際APIを呼び出して、取得した値で時刻表示を書き換えるようにします。
使用するAPIは、 /api/time/now
で呼び出すと、現在時刻をテキストで返してくれるものとします。
実装はこんな感じ。
class Api::TimeController < ApplicationController def now render plain: Time.current end end
API の URL は Stimulus controller 内に直接定義することもできますが、 またまた Stimulus には value という、値を受け渡すのに便利な属性があるので、 こちらを利用します。
view と Stimulus controller を書き換えます。
<div data-controller="update-button" data-update-button-url-value="/api/time/now"> <button data-action="click->update-button#update">更新</button> <div data-update-button-target="area"> <%= Time.current %> </div> </div>
export default class extends Controller { static targets = ["area"] // url という value を定義 static values = { url: String } update() { fetch(this.urlValue, { method: "GET" }) .then((response) => response.text()) .then((time) => this.areaTarget.innerHTML = time) } }
API の URL を url
というvalueで渡せるようにしました。
value が参照する値の指定も同様に view 側で行います。
値の指定は data-controller名-value名-value
という属性で行います。
<div data-controller="update-button" data-update-button-url-value="/api/time/now">
※ data-controller
と同じ要素に指定する必要があるので注意
url
value は controller 側で this.[value名]Value
とすると参照することができます。
この場合 this.urlValue
で、指定した API の URL が参照できます。
これで完成です!
まとめ
data-controller
で適応させる controller を指定data-action
で要素にイベントを設定できる- 他の参照したい要素は
target
、値を参照した場合はvalue
が使える
WMS の地図画像を Google Map 上に表示する
この記事は ケーシーエスキャロット Advent Calendar の記事です。
WMS とは、地図画像配信方式の一つです。 表示したい地図範囲を指定すると、その範囲を切り取った画像が返ってくるという方式でデータの配信を行います。
これを Google Map 上に表示してみよう、というのが今回の内容です。
WMS の API 仕様
今回利用しようと思っているWMSのAPIは、大体以下のような仕様とします。
- 範囲指定: 左下(南西)と右上(北東)の緯度経度を指定(世界測地系)
- サイズ指定: px単位
Google Map 上に画像を表示する方法
まず Google Map でどのように地図データの画像を表示するかです。
Google Map では 256 × 256 に画像を分割し表示させる、タイル方式という方法で地図画像の表示を行っています。 タイルがどうなっているかは、こちらのサイトを見るとイメージしやすいかと思います。
実際に Google Maps API でタイルを表示する場合、 overlayMapTypes
を用いて、以下のようなコードになります。
コードからなんとなくわかる通り Google Maps API では、
必要なタイル座標とzoomレベルを getTileUrl
に渡し、返ってきたURL(API)から画像を取得して表示するという動きをします。
// オーバーレイオブジェクト作成用 class CoordMapType { constructor(tileSize) { this.tileSize = tileSize; } MapOption() { let layer = new google.maps.ImageMapType({ tileSize: this.tileSize, isPng: true, getTileUrl: function(point, zoom) { // タイル取得のためのAPIを設定 url = "https://XXXXXXXXXX/{x}/{y}/{z}" .replace('{z}', zoom) .replace('{x}', point.x) .replace('{y}', point.y) return url; }, opacity: 0.7 }); return layer; } } // Google Map オブジェクト作成 var map = new google.maps.Map(document.getElementById("map"), { zoom: 10, center: { lat: 35.68, lng: 139.76 }, }); // オーバーレイオブジェクト作成、Google Map に追加 opts = new CoordMapType(new google.maps.Size(256, 256)); map.overlayMapTypes.insertAt(0, opts.MapOption());
つまり WMS を Google Map 上に表示するには?
getTileUrl
で該当タイルに当てはまるWMS の API を返すようにしてあげれば、表示することができそうです。
が、ここで問題があります。
getTileUrl
に引数で渡されるのはタイル座標(X, Y)と zoom レベルなので、
緯度経度の情報を取得するには、タイル座標を緯度経度に変換する必要があるのです。
タイル座標から緯度経度への変換
タイル座標を緯度経度に変換する方法は、以下のサイトで紹介されていました。
TrailNote : 座標の変換(世界座標、ピクセル座標、タイル座標、緯度・経度)
つまり、
というステップでタイル座標→緯度経度の変換ができそうです。
これをこんなかんじでコードにおこしてみます。
// タイル座標を緯度経度に変換する function tilePointToLatLon(x, y, zoom) { // 左下: タイル座標->ピクセル座標 let ldX = x * 256; let ldY = y * 256 + 255; // 左下: ピクセル座標->緯度経度 dl = pixPointToLatLon(ldX, ldY, zoom); // 右上: タイル座標-> ピクセル座標 let urX = x * 256 + 255; let urY = y * 256; // 右上: ピクセル座標->緯度経度 ur = pixPointToLatLon(urX, urY, zoom); return { down_left: dl, up_right: ur }; } // ピクセル座標を緯度経度に変換する function pixPointToLatLon(x, y, zoom) { // 表示可能上限 // L = 180 / PI * asin(tanh(PI)) = 85.05112878 const L = 85.05112878; // 経度 let lon = 180 * ((pixX / Math.pow(2, zoom + 7)) - 1); // 緯度 let lat = 180/Math.PI * (Math.asin(Math.tanh(-Math.PI/Math.pow(2, zoom+7) * pixY + Math.atanh(Math.sin(Math.PI/180 * L))))); return { lat: lat, lon: lon } }
タイル座標→ピクセル座標の箇所で、左下の y座標と右上のx座標に 255 を足しているのは、 単純にタイル座標→ピクセル座標変換すると左上のピクセル座標になってしまうためです。
下の図のように、左上を (0, 0) とすると、タイルの1辺は 256 px なので、左下と右上はそれぞれ (0, 255) と (255, 0) になるので、それぞれ255を足しています。
これでタイル座標からWMS指定に必要な緯度経度が取得できました。
後は、 getTileUrl
にこの変換ロジックを使ってWMSをタイルっぽく取得できるようにしてあげれば表示ができます。
落とし穴…
しかしこれ、実はIEだと動きません。
なぜかというと、緯度経度変換処理の中で使用している Math.tanh()
と Math.atanh()
がIEではサポートされていないからです。(なんてこった)
そのため、IEでも表示させたい場合はそれぞれ代替の計算式を使って、以下のように書き換えてあげると良いと思います。
// ピクセル座標を緯度経度に変換する function pixPointToLatLon(x, y, zoom) { // 表示可能上限 // L = 180 / PI * asin(tanh(PI)) = 85.05112878 const L = 85.05112878; // 経度 let lon = 180 * ((pixX / Math.pow(2, zoom + 7)) - 1); // 緯度 let lat = 180/Math.PI * (Math.asin(tanh(-Math.PI/Math.pow(2, zoom+7) * pixY + atanh(Math.sin(Math.PI/180 * L))))) return { lat: lat, lon: lon } } function tanh(x) { if (Math.tanh) { return Math.tanh(x); } else { var a = Math.exp(+x), b = Math.exp(-x); return a == Infinity ? 1 : b == Infinity ? -1 : (a - b) / (a + b); } } function atanh(x) { if (Math.atanh) { return Math.atanh(x); } else { // |x| < 1 以外の場合は考慮していない if (Math.abs(x) < 1) { return Math.log((1+x)/(1-x)) / 2; } return Nan; } }
感想
これでWMSを Google Map 上に表示させるという目的はクリアできましたが、 実際表示させてみるとめちゃくちゃ表示に時間がかかります。(正直実用的ではなさそう)
とはいえ、久々に数式見たり、頭を悩ませてコードを書いたりして良い経験になりました。
御覧いただきありがとうございました。
自作キーボードを作りました
この記事は ケーシーエスキャロット Advent Calendar 2019 - Qiita の 6日目の記事です。
昨日の記事は ifbun さんの「社会人13年目の夏季集中講座」でした。 学習した内容&感想がいい感じにまとまっていて、すごく良い記事ですね!
さて、実は今年自作キーボードデビュー(といって良いのか)を果たしました。 せっかくなので、この機会にきっかけとか雑感とか振り返ってみようと思います。
きっかけ
もともとセパレートタイプで程よい高さのキーのものはないかと思って探していた*1ときに、ふと、 自作キーボードなら、自分の好みのキーボード見つかるかも?くらいの軽い気持ちで、 色々調べ始めたのがきっかけです。*2
また、自作キーボード専門店がOPENしたというのも影響しています。
購入したもの
Mint60 というキーボードの基本セット(基盤などの基本的なものは入っているが、キースイッチ・キーキャップ・ケーブルなどは入っていない)の マットクリアにしました。
※ なぜこのキーボードにしたかというと、単純に数字列があって、セパレート形式で、row staggered でもともと使っていたノートPCの配列に近いから、という理由です。
あとはリンク先にも書いてあるとおり、
- キースイッチ
- キーキャップ
- ケーブル類
を揃えました。
ちなみにキースイッチは Gateron の青軸*3、キーキャップは DSA*4 という規格のものを購入しました。
制作
基本的にはこちらの記事の通りに組み立てを勧めました。 隣で見ていた emorima さんからの熱い(?)サポートも入りつつ、なんとか組み立てることができました。
ハンダを使うのは中学の授業以来だったので、うまくできるか不安でしたが、結構いい感じにできたと思います!
組み立て途中と完成品の写真がこちら
組み立て後の悲劇
実は組み立て後しばらくはいい感じに使えていたんですが、TRSコネクタ(右と左を繋ぐケーブルを指す部分)の調子が悪くなり、 右側だけ反応しないという状態になりました。 これ、付け替えようとすると、ハンダ付けしたキーをすべて取り除かないといけないと行けないんですね。 なので、泣く泣くキーをすべてはずして組み立て直しました。はんだシュッ太郎は偉大です… www.amazon.co.jp
使い心地
とても気に入っています。 写真からはわからないと思いますが、実は左下の緑のキーが esc キーになっていて、 vimmer の自分的には遠くにあった esc キーが押しやすい位置に来てくれていて嬉しいです。
まとめ
今回作ってみて、たしかに時間もお金もかかるし、組み立ても苦労したけど、 やっと自分好みのキーボードに出会えて良かったなと思います。 キーを打つのがとても快適になりました。
今は Mint60 で満足しているので、別のキーボードのことは考えていませんが、 また魅力的なキーボードが出てきたら作ってみたいですね。
2018年振り返り
この記事は ケーシーエスキャロット Advent Calendar 2018 の最終日の記事になります。
Advent Calendar もついに最終日となりました。
2018年も、あと少しで終わりですね。今年は個人的に、色々な変化があったと感じる年でした。 ということで、2018年を振り返ってみました。
まず、今年は RubyKaigi を始め、Ruby World Conference、VimConf と、いろいろなカンファレンスに参加しました。
Ruby 関連のカンファレンスへの参加が主ですが、今年は前から気になっていたテキストエディタ vim のカンファレンス、VimConf にも参加してみました。 実は vimrc がまっさら&ノープラグインの状態で使っていたので、プラグインのお話されると途端に置いてけぼりをくらうなどしていましたが、 Ruby 系のカンファレンスに参加することが多い身としては、Ruby がマイノリティとなっている環境はとても新鮮でした。
以前もカンファレンスなどは参加したことがありますが、 今までは誰かに言われて参加する、という流れだったのが、今年は自主的に参加を決めたりと、自分的には大きな一歩を踏み出せた感があります。
また、カンファレンス参加にあたり、持ち運びができるPCを新しく購入しました。 (もともと家で使っているノートPCは持ち運びを想定しないで購入したので、15.6型のとても重く、持ち運ぶのも一苦労で・・・)
というのも、カンファレンスとか参加したときに、手ぶらだとすごく手持ち無沙汰! 今聞いた内容をすぐに試して見たい、調べたい!!
ということで、買っちゃいました。Thinkpad の X1Carbon。勢いです。 最初は Mac でも良いかなと思っていたのですが、ちょうどPC買おうと思った時期にセールで安くなっていたのもあり、 X1Carbon の方を購入しました。 使ってみてしばらく経ちますが本体も動作も軽くて快適です。 ちなみに、ubunts と windows10 のデュアルブートで使っています。
今年カンファレンスにたくさん参加しようと思ったのは、このPCを手に入れたという要因も大きいです。
という感じで、今年は外部のお話を聞く機会が多かった年でした。 さきほども書きましたが、外に出ようと思ったのは自分に取ってとても大きな変化でした。
また来年も引き続き RubyKaigi やその他気になるカンファレンスに参加できたらと思います。
Rakeのススメ
この記事は、 株式会社ケーシーエスキャロット アドベントカレンダー の11日目の記事です。
いい機会なので、以前 Rake タスク作成を行ったときに、試行錯誤の結果得たものをまとめてみようと思います。 少しでも 参考になれば幸いです。
Rake タスクとは
Rake タスクとは、Ruby で書く Make のようなものです。 Make だと Makefile に処理を定義していきますが、 Rake も同様に Rakefile というファイルに処理を定義します。
例えば、 Rakefile に以下のように記載すると、 sample
と言うタスクが作成されます。
作成したタスクは rake -T
を実行すると一覧表示されます。
desc 'sample task' task :sample do puts "do sample" end
$ rake -T rake sample # sample task
実行する場合は rake タスク名
で実行します。
$ rake sample do sample
namespace の活用
タスクは namespace をつけることもできるので、ある程度タスクのまとまりが大きくなる場合などは namespace でまとめておくと良いと思います。
require 'fileutils' namespace :workdir do desc 'ワークディレクトリの作成' task :make do if File.exist? 'work' puts "workdir already exist." else FileUtils.mkdir 'work' end end end
$ rake -T rake workdir:make # ワークディレクトリの作成
引数の使い方
タスクに引数を渡すには、タスクごとに受け渡す方法と、環境変数として受け渡す方法があり、 それぞれ実装方法、実行方法が異なります。
desk '引数をタスクで定義する方法' task :sample_1, [:str] do |task, args| puts "input str => #{args[:str]}" end desc '引数を環境変数で定義する方法' task :sample_2 do puts "input str => #{ENV['INPUT_STR']}" end
$ rake -T rake sample_1[str] # 引数をタスクで定義する方法 rake sample_2 # 引数を環境変数で定義する方法 $ rake sample_1['---test---'] input str => ---test--- $ rake sample_2 INPUT_STR='---test---' input str => ---test---
タスクごとに受け渡す場合、受け取った引数は、ブロックの第2引数(上の例だと args
)から取得することができます。
タスク一覧で、引数の情報を確認することができるのが便利です。
環境変数として受け渡す方法は、引数の情報を環境変数 ENV
として定義します。受け渡すと言うよりは、設定すると言う感覚でしょうか。
どのタスクからでも参照可能なので、例えば一回で複数のタスクを実行させる場合には都合がいいです。
こちらは、タスクごとに定義する方法と違い、タスク一覧では引数の情報を確認することができませんので、説明文などに記載しておくと親切です。
個人的には、環境などタスク全体に関わる引数に関しては、環境変数として定義させ、それ以外はタスクごとに定義させると言うように使い分けしています。(できるだけ)
invoke と execute
他のタスクを呼び出す場合は Rake::Task#invoke
、 Rake::Task#execute
は他のタスクを呼び出すときに使います。
使い方は以下になります。
desc 'invoke を使ってタスク呼び出し' task :sample_invoke, [:str] do |task, args| Rake::Task['output'].invoke(args[:str]) end desc 'execute を使ってタスク呼び出し' task :sample_execute, [:str] do |task, args| Rake::Task['output'].execute(args) end # desc のないタスクはタスク一覧に表示されない task :output, [:str] do |task, args| puts "input => #{args[:str]}" end
$ rake sample_execute['aaa'] input => aaa $ rake sample_execute['aaa'] input => aaa
invoke
も execute
も、実行時に引数を渡すことができますが、渡し方がそれぞれ異なります。
引数以外にも、以下にまとめたように挙動が異なるため、使用するケースによってどちらを使うか判断する必要があります。
invoke | execute | |
---|---|---|
引数 | 文字列 or 配列形式で渡して、渡した順番で各引数の項目と値を解釈する | Rake::TaskArguments をそのまま渡す or ハッシュ形式で引数の項目と値を明示的に指定する |
実行回数 | 1回のみ実行可能 | 何回でも実行可能 |
依存タスク | 実行する | 実行しない |
依存タスク
依存タスクは、そのタスク実行時に必ず実行するタスクの定義です。 他のタスクを呼び出す場合はタスク実行時の好きなタイミングで呼び出せますが、依存タスクは必ずタスク実行前に呼び出されますので、タスクの前準備の処理を定義するのに向いています。
依存タスクは以下のように定義します。
desc '依存タスクサンプル' task :sample_izon, [:str] => :header do |task, args| puts "input => #{args[:str]}" end # 依存タスク task :header do puts "##########" end
$ rake sample_izon['aaa'] ########## input => aaa
Rails で Rake タスク作ったことある人は、依存タスクの定義、見たことあるかもしれません。
そう、DB操作系のタスクを作る場合に定義する必要のある environment
です。
task :hoge => :environment do ・・・ end
実はこの environment
も、DB接続の準備をするタスクで、依存タスクとして定義させることで、DB操作が可能になるというわけです。
まとめ
ざざざっとまとめてみました。 Rake タスク便利なので、皆さん作って、 Ruby ライフをエンジョイしてください。