20210118のJavaScriptに関する記事は29件です。

俺の論理演算子、条件演算子

演算子

javaScriptで定義されている演算子は様々あります。
JavaScript primer演算子

その中でReactでよく用いる演算子をまとめました。

論理演算子

AND演算子(&&)

左辺がtrueなら右辺を。
左辺がfalseならそのまま左辺を返します。

OR演算子(||)

&&の逆で左辺がtrueなら左辺を。falseなら右辺を返します。

条件演算子

条件式 ? trueの時の評価式 :flseの時の評価式

const a = 3;
const b = 7;
const inNum = true;
console.log(inNum ? a : b); // aが評価されて「3」出力

!! tspeScriptのオプショナル用の打ち消し

!!を付ける事で boolean | undefinedbooleanにすることが出来る。
オプショナルでa?:boolean;と定義してエラーが出ている時などは、これで対応できます。


フリーランスでフロントエンドエンジニアをしています。
お仕事のご相談こちらまで
gunners6518@gmail.com

技術ブログ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ml5.jsでPoseNetで手旗信号読取りWebを作る

demo.gif

はじめに

前回 TensorFlow.js version of PoseNetで手旗信号読取りサイト というPoseNet(機械学習によって姿勢を推定できる技術)を使った手旗信号読取りWebサイトを作った話を書きました。
記事中にもありますが、推定された肩や肘の角度から判定しているのですが、実測値を見ながら手動で判定式を書いた感じなので精度に納得がいっていないんです。

せっかくTensorFlowとか使ってるなら手旗信号のポーズ自体も学習させた方が良いな…とか何となしには思っていたのですが、そんなスキルがあるわけでもなく。

と半ば諦めつつもググっていると、

https://thecodingtrain.com/learning/ml5/7.2-pose-classifier.html

いや、まさにコレじゃないすか!

というわけでこのml5.jsとPoseNetを使った手旗信号読取りWeb 機械学習Verの作成を紹介します。
結果から言うと読取り精度のイマイチさはあまり変わっていません?ただ学習方法の最適化など、まだ精度向上の余地はあるな、という感じなので前向きです。

参考にしたもの

ちなみにわたくしのバックグラウンドはフロントエンドエンジニアとか機械学習専門とかではないので、全て勉強しながらの備忘録という感じです。

PoseNet

前回と同じです。前回の記事参考にしていただければと思います。

ml5.js

https://ml5js.org/

TensorFlow.jsをベースにした機械学習用のライブラリです。アーティストやクリエイティブ、学生向け、とありますね。
このライブラリを使って手旗信号を学習させて、得られたモデルを使って判定させる、といった具合です。

p5.js

はじめは前回のコードの判定部分のみを上記ml5.jsによる判定に置き換えようと思ったのですが、描画の部分が上手く動かないため、先のCoding Trainのダニエル先生が使っている

https://p5js.org/

というライブラリをそのまま使うことにしました。
Processingという映像や音楽に使われているライブラリをJavaScript用のライブラリにしたものだそうです。

実装!

ぶっちゃけ手旗信号の原画の判定は、条件付きであればCoding Trainサイトのサンプルのみで実現できます。
条件というのは、画面の大きさが固定だったり画面内の位置に縛りがあったりとか、その辺の課題への対応を中心に作業の流れを書きます。

姿勢データの収集

まず手旗信号の各原画1~14の17姿勢(詳しくは前記事参照)のデータを収集します。

Coding Trainの「Data Collection」というサンプルがこれにあたるのですが、こちらではPoseNetを使って、判定したい姿勢の17ポイントの座標を収集して、jsonファイルで出力されます。
操作方法としては、「poseNet ready」という表示後、アルファベットのキー("s"と"t"以外)を押下すと5秒後「collecting」となって収集を開始します。10秒後「not collecting」に表示が変わると押したキーの文字に割り当てられるデータの収集終了です。これを必要な姿勢分実行し、最後に"s"を押すと結果がjsonファイルに出力されます。

ですがこれで得られる座標が絶対座標というか、収集する時の画面の座標なので、これが画面を変えたときに判定が渋い原因なのかなと。
ですので下図の通り、身体の中心(赤点)と鼻を結んだ距離(緑線)を単位距離とし、鼻、上半身の中心それぞれのポイントから各関節の距離(黄線)を正規化した値を収集することにしました。
boyscouts_man_.png

flag_sem_util.js
const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
function pose_normalize(keypoints){
  inputs = [];

  let x0 = keypoints[NOSE].position.x, y0 = keypoints[NOSE].position.y;
  let x1 = getCenterCoord(keypoints).x, y1 = getCenterCoord(keypoints).y;
  if(x1 == undefined || y1 == undefined){
    return null;
  }
  let basedist = distance(x0, y0, x1, y1);
  if(basedist == float.NaN || basedist == 0){
    return null;
  }

  for (let i = RIGHTEYE; i <= RIGHTHIP; i++) {
    let l0 = distance(x0, y0, keypoints[i].position.x, keypoints[i].position.y) / basedist;
    let l1 = distance(x1, y1, keypoints[i].position.x, keypoints[i].position.y) / basedist;
    inputs.push(l0);
    inputs.push(l1);
  }
  return inputs;
}

Github

上記のソースコードで手旗原画の17姿勢を収集しjsonファイルが得られます。

姿勢データを学習させる

これは簡単です。上記収集で得られたjsonファイルをml5のニューラルネットワークオブジェクトに食わせるだけです。
Coding Trainのサンプルをほぼそのまま使えるのですが、読み込ませるポイント数と出力数を変更する必要があります。

今回は2つの距離を正規化したものと、上半身の判定に使う関節分12ポイントなのでinputsを24、17姿勢なのでoutputsを17にしました。

loading_data/sketchs.js
let options = {
    inputs: 24,
    outputs: 17,
    task: 'classification',
    debug: true
}

これを実行し学習が終了するとモデルファイルが出力されます。

  • model.json: //モデルのデータ
  • model_meta.json: //メタデータ(モデルの付随情報)
  • model.weights.bin: //読み取った姿勢と正解の結びつきの重みバイナリデータ(多分)

姿勢判定

学習して得られた3つのモデルファイルをml5に読み込ませます。

flag_semaphore_reading.js
  brain = ml5.neuralNetwork(options);
  const modelInfo = {
    model: 'model/model.json',
    metadata: 'model/model_meta.json',
    weights: 'model/model.weights.bin',
  };
  brain.load(modelInfo, brainLoaded);

あとはclassifyPose()をコールバックしてリアルタイムに読み取り距離で正規化したデータで分類判定させます。
判定された原画でカナを判定するのは前回のロジックをそのまま使いました。

まとめ

ソースコード

Github

読取りサイト

https://hiroshi32yoshida.github.io/flag_semaphore_reading/

課題

心なしか前回版より精度は上がっている気がしないでもないですが…うーーん…気のせいだなぁ?

スマホで使えない

ml5.jsの影響かな?
私の環境iPhone8+Safariでは動きませんでした。
そもそもAppleはブラウザからカメラの使用は推奨しないという噂もどこかで見ましたが…前回版は動くんだよなぁ

手旗を持っていると読み取れない

手旗信号なのに!
関節が手旗に隠れてしまっているとPoseNetの姿勢推定が渋くなるみたいです。
ですので原画「6」とか手旗を持っていなくても場合によって読取りが渋いです。「11」ももちろんですが、「9」とかも。

画面の端に近いと読取りが上手くいかない

このための対策をしているはずなんだけどな…

誤判定

似ている原画や体格の違いやカメラのアングルによってまだまだ不安定です。
学習させるパラメータを増やすとか、子供のデータを学習させるとか何か方法はあると思います。勉強します。

さいごに

(日本の)手旗信号を考えた人に言いたいこと

何で「11」をジェスチャーにしたの!?

全国のボーイスカウト指導者のみなさん

練習程度には使えると思います。おうちスカウティングなどに是非!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GASでシフト自動作成ソフト作ってみた

作成したスプレッドシート↓↓
https://docs.google.com/spreadsheets/d/1D1Shq5GaVnSMqiSf3hxXynWdYd79-gQXjZP221GnVQ4/edit?usp=sharing

-仕様-

従業員は
R(レギュラー)、H(派遣)、P(パート)、A(アルバイト)の4種類
Rは基本営業時間内でシフトタイム、それ以外は希望時間内で早(早番)、中(中番)、遅(遅番)に振り分ける

それぞれに希望休(日)、希望休(曜)、有給、週何回出勤、勤務時間の入力欄を準備その入力内容をもとにシフトを作っていく。

シート(月予定表)の「シフト作成ボタン」を押して月の出勤日を作成、
その後ドロップダウンリストで作成したい日数分(最大7日)の日にちを選択し「日にちシフト作成ボタン」を押して日にちごとのシフトを作成。

-改善点-
少人数であれば問題なく動くが人数が多くなるにつれてエラーが出やすくなる。 → 元々のアルゴリズムを考え直さないといけない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「lazyload 対応」スムーズスクロールの書き方

どうも7noteです。lazyloadを入れていてもスムーズにページ内リンクできる方法

以前表示速度が少し心配なサイトがあり、lazyload属性を使いつつもでもページ内リンクをさせたい場面がありました。

もし対策せずにどちらも入れてしまうと、画像が読み込まれていないかページ内リンクの高さ取得が上手くできず
画像が後から読み込まれることで高さが変わってしまい、本来の位置よりも上の方に移動してしまいます。

ですがjQueryのwhenを使うことでこの問題を解決することができます。

lazyload対応スムーズスクロールの書き方

※jQueryを使用しています。jQueryってなんだという方はこちら

$(function () {
    $('a[href^=#]').click(function(e) {
        var headerHight = 50;   /* ヘッダーの高さ(50px) */
        var href = $(this).attr("href");
        var target = $(href == "#" || href == "" ? 'html' : href);
        var position = target.offset().top - headerHight;

        $.when(
            $("html, body").animate({
                scrollTop: position
            }, 400, "swing"),
            e.preventDefault(),
        ).done(function() {
            var diff = target.offset().top - headerHight;
            if (diff === position) {
            } else {
                $("html, body").animate({
                    scrollTop: diff
                }, 10, "swing");
            }
        });
    });
});

詳しい動きの解説は参考にさせていただいたページの書き方に載っていますので省略させていただきます。

参考:https://kaiteki-chokin.com/anchor-link-lazy-loading/

whenについて簡単に解説

whenを使うことで、複数の非同期処理が全部終わったら、続く処理を行うことができます。
今回の処理ではこれを利用し、画像がlazyloadでちゃんと読み込まれた後に正しい位置までのスクロール処理を行なっています。

まとめ

jsは様々な便利なライブラリやソースがネットにあるので1つのページに様々な種類のソースを書くことは珍しくありません。
ただ、その動き方やソースの内容や特性をしっかり理解できないと相性が悪く上手く動かない事もあります。

なので自分がソースを書くことも大事ですが、他人のソースを読めることも大事だと思います。

おそまつ

~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React-Calendarで超簡単にカレンダーを作ってみた

会社のイベントとして、2020年のアドベントカレンダーで記事を書いたのですが、個人のQiitaでも同じものを公開したいな〜と思ったので、ちょっとだけ変えて公開します!

去年の案件で急にReactでカレンダーを作ることになり、有識者もいない中、四苦八苦したのですが、ライブラリって便利だな〜と感じたので、Reactのreact-calendarを使ってカレンダーを作成したときのことを書きます。

※環境構築とかは端折ります。

react-calendarとは

カレンダー実装だともっとメジャーなライブラリがあるかも知れないんのですが、今回、私はreact-calendarを使ってみたのですが、簡単にReactでカレンダーを実装できちゃいました。

条件を絞って、日付ごとに任意の内容を表示させたり、スタイルのカスタマイズなど 柔軟に対応できます。

公式:react-calendar

react-calendarで実装してみる

react-calendarをインストール

yarnで「react-calendar」をインストールします。

yarn add react-calendar

react-calendarをimportする

react-calendarをimportしてCalendarコンポーネントを呼び出します。

CalenderCmponent.jsx
import Calendar from 'react-calendar'

export default class Calendar extends Component {

    render() {
        return(
            <Calendar />
        )
    } 
}

これだとまだ動きません!

カレンダーに日付を表示する

カレンダーのタイルに日付(日本時間)を表示します。

CalenderCmponent.jsx
import Calendar from 'react-calendar'

export default class Calendar extends Component {

    render() {
        return(
            <div>
                <Calendar
                    locale="ja-JP"
                    value={this.state.date}
                 />
          </div>
        )
    } 
}

それぞれのPropsについては公式だと以下のように説明されています。(英語のドキュメントのみ、、、)

Description Description Default value Example values
locale Locale that should be used by the calendar. Can be any IETF language tag. User's browser settings "hu-HU"
value Calendar value. Can be either one value or an array of two values. If you wish to use React-Calendar in an uncontrolled way, use defaultValue instead. "n/a" ・Date: new Date()
・An array of dates: [new Date(2017, 0, 1), new Date(2017, 7, 1)]

これで、カレンダーの作成は完了です。とても簡単!!

カレンダーに任意のアイテムを表示してみる

これだけだと寂しいので、日付タイルに任意のアイテムを表示させてみたいと思います。

CalenderCmponent.jsx
import Calendar from 'react-calendar'

export default class Calendar extends Component {

    constructor(props) {
        super(props);
        this.state = {
            date: new Date(),
            //テストデータ
            month_item: {
                2020-12-01: { text: 'work' },
                2020-12-10: { text: 'hangout' },
                2020-12-24: { text: 'Christmas Eve' },
                2020-12-25: { text: 'Christmas' },
            }
        }
    };


     //日付の内容を出力
      getTileContent({ date, view }) {
          if (view === 'month') {
              const targetDate = moment(date).format('YYYY-MM-DD')

             return   month_item[targetDate] && month_item[targetDate].text ?
                 <div>
                        <p>{month_item[targetDate].text}</p>
                 </div>
               : null

          }
      }


    render() {
        return(
            <div>
                <Calendar
                    locale="ja-JP"
                    value={this.state.date}
                    tileContent={this.getTileContent.bind(this)} //ここを追加
                 />
    </div>
        )
    } 
}

tileContentの詳細の説明は以下です。functionを呼び出して、returnされたものを渡せば任意の日付に内容を表示させることができます。

Description Description Default value Example values
tileContent Allows to render custom content within a given calendar item (day on month view, month on year view and so on). n/a ・String: "Sample"
・React element:
・Function: ({ activeStartDate, date, view }) => view === 'month' && date.getDay() === 0 ? It's Sunday! : null

まとめ

Reactって便利〜! 今回の記事では説明していませんが、Reduxと合わせてDBからデータを引っ張ってきてstateで制御すれば、スケジュールを登録したり、表示させたりすることも可能でした!

今回記載している以外にもたくさんPropsの種類もあるので、気になる方はぜひ公式URLから見てみてください!

参考

React-Calendar が便利 | バシャログ。
Reactでカレンダー作成(React-Calendarライブラリ)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポートフォリオサイトの制作

はじめに

自分のスキルや成果物の可視化やGithub以外のアウトプットを一元管理するため、ポートフォリオサイトを作成しました。

今回は作成する上で気をつけたことや使用したツール、ホスティング方法について備忘録としてまとめたいと思います。

URL: https://seiyaiwanabe.github.io/portfolio_site/

ezgif.com-gif-maker (2).gif
ezgif.com-gif-maker (3).gif

目次

1.記載内容
2.使用したサービス
3.Github Pages

1. 記載内容

ポートフォリオサイトを作成した理由は主に企業の採用担当者の方に見てもらうためです。
「誰にポートフォリオサイトを見せたいのか」という要素は最も大事だと思うのでここを決めることでサイト全体の構成も決まってくると思います。

今回は以下の要素を記載しました。

  • 使用した言語(技術)
  • 作品紹介
  • 開発に使用したツール
  • アウトプットへのリンク(今回はQiita)

また工夫した点としてサイト全体にアニメーションを盛り込みました。
JSの練習になるのと少しでもビジュアルを魅力的にするためです。

ただし、多様しすぎるとエゴになってしまうので、線引が難しいですが、
情報を整理するという目的を忘れないことが大事だと思いました。

アプリ自体はRuby on Railsで作成し、インフラにAWSを使ってデプロイしています。

2. 使用したサービス

コーディングはイチからしました。
bootstrapやテンプレートの類は使用していません。
自分の手を動かして少しでもプログラミングに慣れたかったためです。

そのため使用した外部サービスはFontAwesomeFavicon作成ツールにとどまりました。

FontAwesome(ヘッダーの中のアイコンに使用)→https://fontawesome.com/
スクリーンショット 2021-01-18 19.44.42.png

Faviconの素材(159,700のフリーアイコンが集まるサイト)→https://icons8.jp/
スクリーンショット 2021-01-18 19.42.27.png
Faviconは拡張子がjpgやpngでも表示させることが出来ます。
まずダウンロードした素材のファイル名をicon.pngに変更しimagesディレクトリに格納します。
図にするとこのような階層になります。↓

スクリーンショット 2021-01-18 20.02.52.png

あとはhtmlファイルに一行追記するだけです。

index.html
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="style.css">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"> # FontAwesome導入
  <link rel="icon" type="image/x-icon" href="images/icon.png"> # 追記
  <title>Document</title>
</head>

これでアイコンが表示されます。

3. Github Pages

GitHub Pages は、GitHub のリポジトリから HTML、CSS、および JavaScript ファイル を直接取得し、任意でビルドプロセスを通じてファイルを実行し、ウェブサイトを公開できる静的なサイトホスティングサービスです。(Github Dogs)

こちらの引用でも記載してる通り、静的なサイトのためのホスティングサービスなので動的なサイトをホスティングさせることは出来ません。なのでRailsアプリはAWSでデプロイを行っています。

今回のポートフォリオサイトのようにサーバーサイドに関与しないコンテンツを世界に公開する手段としては
Github Pagesは無料で利用できて、プロセスが簡単なのでおすすめです。

おわりに

最後まで読んでいただきありがとうございました!
お疲れさまでした。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでAzure IoT Hubにテレメトリを送信するサンプルコードを動かすメモ

はじめに

デバイスから IoT ハブに利用統計情報を送信してバックエンド アプリケーションで読み取るを参考にサンプルコードを動かしてみます。
Image from Gyazo
接続文字列はAzure CLIを使わずに、コンソールから取得します。

Azure IoTについてはこちらを参照。

準備

  • Node.jsをインストールする(今回はv14.1.0を使用)
  • サンプルコードをダウンロードする
  • 手順を参考に、Iot Hubをデプロイする。(スケールはS1を選択。)

IoTデバイスを登録する

Azure IoT Hubコンソール > サイドバー > IoTデバイス > 新規からデバイスを追加する。

Image from Gyazo

追加できたら、追加したデバイスを選択してプライマリ接続文字列をコピーしておく。

Image from Gyazo

データを送信する

ダウンロードしたサンプルコードから、SimulatedDevice.jsを開き、17行目あたりのconnectionStringに先程コピーしたプライマリ接続文字列を貼り付ける。

コードはこんな感じになる。

var connectionString = '{Your device connection string here}'; // ←ここに`プライマリ接続文字列`を貼り付ける。

var Mqtt = require('azure-iot-device-mqtt').Mqtt;
var DeviceClient = require('azure-iot-device').Client
var Message = require('azure-iot-device').Message;

var client = DeviceClient.fromConnectionString(connectionString, Mqtt);

setInterval(function(){
  // 送信するデータを生成する。(本当はセンサーで測定した値を入れるがシミュレーションのため乱数を使用)
  var temperature = 20 + (Math.random() * 15);
  var message = new Message(JSON.stringify({
    temperature: temperature,
    humidity: 60 + (Math.random() * 20)
  }));

  message.properties.add('temperatureAlert', (temperature > 30) ? 'true' : 'false');

  console.log('Sending message: ' + message.getData());

  // イベントを送信する
  client.sendEvent(message, function (err) {
    if (err) {
      console.error('send error: ' + err.toString());
    } else {
      console.log('message sent');
    }
  });
}, 1000);

以下のコマンドをターミナルで実行する。

$ node SimulatedDevice.js

データが送信されている。

Image from Gyazo

送信したデータを受信する

ダウンロードしたサンプルコードから、ReadDeviceToCloudMessages.jsを開き、38行目あたりのconnectionStringAzure IoT Hubコンソール > サイドバー > 組み込みのエンドポイント > イベントハブ五感エンドポイントの値を貼り付ける。
(26行目あたりにeventHubsCompatibleEndpointなどの変数が用意されていますが、無視します。)

Image from Gyazo

コードはこんな感じになる。

const connectionString = `Endpoint=**********************;SharedAccessKeyName=*****************;EntityPath=********************`;

var printError = function (err) {
  console.log(err.message);
};

var printMessages = function (messages) {
  for (const message of messages) {
    console.log("Telemetry received: ");
    console.log(JSON.stringify(message.body));
    console.log("Properties (set by device): ");
    console.log(JSON.stringify(message.properties));
    console.log("System properties (set by IoT Hub): ");
    console.log(JSON.stringify(message.systemProperties));
    console.log("");
  }
};

async function main() {
  console.log("IoT Hub Quickstarts - Read device to cloud messages.");

  const clientOptions = {
    // webSocketOptions: {
    //   webSocket: WebSocket,
    //   webSocketConstructorOptions: {}
    // }
  };

  const consumerClient = new EventHubConsumerClient("$Default", connectionString, clientOptions);

  // イベントハブのストリームからデータを取得する
  consumerClient.subscribe({
    processEvents: printMessages,
    processError: printError,
  });
}

main().catch((error) => {
  console.error("Error running sample:", error);
});

実行する。

$ node ReadDeviceToCloudMessages.js

受信できた。

Image from Gyazo

まとめ

公式のデバイスから IoT ハブに利用統計情報を送信してバックエンド アプリケーションで読み取るを動かしました。
また、Azure CLIを使わずにコンソールから接続文字列を取得しました。

Azure IoT HubのS1スケールは月に2~3000円ほどかかるので、試し終わったら忘れずに止めておきましょう。

次回は、M5系のデバイスからデータを送信してみます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分の町の避難所情報をGoogle Mapsに表示する

オープンデータの活用とGoogle Maps APIの練習を兼ねて自分の町の避難所情報をGoogle Mapに表示してみました。さらに避難所までのルートも表示させてみます。

避難所情報の取得

まずは大刀洗町オープンデータカタログ https://odcs.bodik.jp/405035/ から避難所情報を取得してみます。
CKAN Data APIのサンプルを参考に以下のようにAPIにアクセスします。

  $.ajax({
    url: 'https://data.bodik.jp/api/3/action/datastore_search',
    data: { resource_id: '1bb3a1f5-db97-488f-92f4-aa72e497e6d1', limit: 5 },
    dataType: 'jsonp',
    cache: true, // これを付けないと引数のタイムスタンプで何故かエラーが出る
    success: function(data) {
      console.log(data.result)
    }
  });

CORSに対応するためにjsonpを使っていたりしますがその辺り今回は割愛。
ブラウザのコンソールにjson情報が表示されたら成功です。

Googleマップに表示させる

取得したjson情報をGoogleマップに表示させてみましょう。
Google Maps APIを使用するには、
https://cloud.google.com/maps-platform?hl=ja
にてAPIキーを取得しなければなりませんが詳細は割愛します。

大刀洗町避難所マップ

緯度と経度を取得してマーカーを立てます。さらにマーカーをクリックしたときに簡単な詳細情報ウィンドウを表示させてみました。

<!DOCTYPE html>
<html>
  <head>
    <title>大刀洗町避難所マップ</title>
    <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script
      src="https://maps.googleapis.com/maps/api/js?key=APIキー&callback=initMap&libraries=&v=weekly"
      defer
    ></script>
    <style type="text/css">
      #map {
        height: 100%;
      }
      html,
      body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
    <script>
      function initMap() {
        const map = new google.maps.Map(document.getElementById("map"), {
          zoom: 14,
          center: { lat: 33.3881, lng: 130.6127},
        });

        // 避難所情報取得
        $.ajax({
          url: 'https://data.bodik.jp/api/3/action/datastore_search',
          data: { resource_id: '1bb3a1f5-db97-488f-92f4-aa72e497e6d1', limit: 30 },
          dataType: 'jsonp',
          cache: true,
          success: function(data) {
            // マーカー
            for (const record of data.result.records) {
              const marker = new google.maps.Marker({
                position: {lat: record.緯度, lng: record.経度},
                map: map,
                title: record.名称,
              });
              marker.addListener('click', function(){
                // 詳細ウィンドウ
                const infoWindow = new google.maps.InfoWindow({
                  content: `<h3>${record.名称}</h3><p>${record.住所表記}<br>${record.避難施設種別}</p>`
                });
                infoWindow.open(map, marker);
              });
            }
          }
        });
      }
    </script>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>

避難所までのルートを表示する

これだけだと物足りないので、避難所までのルートを表示させてみましょう。

ルート表示

ルート表示を行うためには事前にGoogle Map APIのDirections APIを有効化しておく必要があります。
今回の交通手段はWALKING(徒歩)を設定します。

function initMap() {
  var currentPos = { lat: 33.3881, lng: 130.6127 };
  const map = new google.maps.Map(document.getElementById("map"), {
    zoom: 14,
    center: currentPos,
  });
  const directionsService = new google.maps.DirectionsService();
  const directionsDisplay = new google.maps.DirectionsRenderer();
  directionsDisplay.setMap(map);

  $.ajax({
    url: 'https://data.bodik.jp/api/3/action/datastore_search',
    data: { resource_id: '1bb3a1f5-db97-488f-92f4-aa72e497e6d1', limit: 30 },
    dataType: 'jsonp',
    cache: true,
    success: function(data) {
      for (const record of data.result.records) {
        const marker = new google.maps.Marker({
          position: {lat: record.緯度, lng: record.経度},
          map: map, 
          title: record.名称,
        });
        marker.addListener('click', function(){
          const infoWindow = new google.maps.InfoWindow({
            content: `<h3>${record.名称}</h3><p>${record.住所表記}<br>${record.避難施設種別}</p>`
          });
          infoWindow.open(map, marker);

          // ルート情報
          var request = {
              origin: currentPos,
              destination: new google.maps.LatLng(record.緯度, record.経度),
              travelMode: google.maps.TravelMode.WALKING
          }
          directionsService.route(request, function(result, status) {
              if (status == google.maps.DirectionsStatus.OK) {
                  directionsDisplay.setDirections(result);
              }
          });
        });
      }
    }
  });
}

さらなる発展

現在位置の取得(navigator.geolocation.getCurrentPosition)を使うとさらに実用的になるのですが、コードが長くなったのでまたの機会に。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptフレームワークのプロジェクト作成コマンドを列挙した

はじめに

JavaScript のフレームワークには npm や Yarn などのパッケージマネージャーを使い簡単に依存関係を解決し、プロジェクトの雛形を作成してくれるコマンドが用意されていることが多いです。
スクラッチでの作成もできますが、コマンドラインツールによって簡単にプロジェクトの自動作成、開発サーバーでの実行ができ便利だと感じたため、有名な JavaScript フレームワークの雛形作成コマンドを備忘録として挙げてみます。

各コマンドの <project-name> の部分が作成されるディレクトリ名、デフォルトのプロジェクト名になります。作成するプロジェクトに応じて適宜置き換えてください。

公式のドキュメントに沿って基本となるコマンドをまとめています。プロジェクト作成時に対話形式で設定可能な項目については記事内で扱いません。項目の説明や、利用できるオプションについては公式のドキュメントを参照のうえ実行をお願いします。

前提条件

  • node.js がインストールされていること(バージョン 8 以降、最新の LTS バージョンを推奨)
  • npm もしくは Yarn が利用可能であること(npm は node.js にデフォルトで搭載)

Vue

npm

npm install -g @vue/cli

vue create <project-name>
cd <project-name>
npm run serve

Yarn

yarn global add @vue/cli

vue create <project-name>
cd <project-name>
npm run serve

http://localhost:8080 で開発サーバが立ち上がります。

Vue

Nuxt.js

npm

npm init nuxt-app <project-name>
cd <project-name>
npm run dev (yarn dev)

npx

npx create-nuxt-app <project-name>
cd <project-name>
npm run dev

Yarn

yarn create nuxt-app <project-name>
cd <project-name>
yarn dev

http://localhost:3000 で開発サーバーが立ち上がります。

nuxt.js

React

npm

npm init react-app <project-name>
cd <project-name>
npm start

npx

npx create-react-app <project-name>
cd <project-name>
npm start

Yarn

yarn create react-app <project-name>
cd <project-name>
yarn start

npm、npx でインストールする場合は Yarn がインストールされているとデフォルトで Yarn が使用されます。

http://localhost:3000 で開発サーバーが立ち上がります。

react

Next.js

npm

npx create-next-app <project-name>
cd <project-name>
npm run dev

Yarn

yarn create next-app <project-name>
cd <project-name>
yarn dev

React と同じく npm、npx でインストールする場合は Yarn がインストールされているとデフォルトで Yarn が使用されます。

http://localhost:3000 で開発サーバーが立ち上がります。

next.js

Angular

npm

npm install -g @angular/cli

ng new <project-name>
cd <project-name>
ng serve --open

Yarn

npm install -g @angular/cli

ng config -g cli.packageManager yarn
ng new <project-name>
cd <project-name>
ng serve --open

ng serve --openとすることでデフォルトのブラウザで起動します。
http://localhost:4200 で開発サーバーが立ち上がります。

angular.js

Svelte

公式で公開されているテンプレートから新規プロジェクトを作る。

npx

npx degit sveltejs/template <project-name>
cd <project-name>
npm install
npm run dev

http://localhost:5000 で開発サーバーが立ち上がります。

svelte

おわりに

有名な JavaScript フレームワークの雛形プロジェクト作成コマンドを公式ドキュメントをもとに列挙してみました。簡単なコマンド 1 つですぐに開発を始める環境が作成できるのはとても便利です。フレームワークがここまで発展し、多くの人に選ばれる理由わかった気がします。

公式ドキュメントを見ながら記事を執筆する中で得た気づきが多くあるため、今後 JavaScript フレームワークについて更に理解を深めていきたいです。

以上、ここまで読んでいただきありがとうございました。

参考ドキュメント

Creating a Project | Vue CLI
インストール - NuxtJS
新しい React アプリを作る - React
facebook/create-react-app: Set up a modern web app by running one command.
Create Next App | Next.js
Angular 日本語ドキュメンテーション - ローカル環境とワークスペースのセットアップ
【2019 年 5 月】yarn で @angular/cli
The easiest way to get started with Svelte
sveltejs / template Template for building basic applications with Svelte

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React初心者】 #3 ルーティング・ページ遷移を作る! react-router-dom

Reactでreact-router-domのルーティング設定

3回目の今回は、Reactでreact-router-dom
を使用したルーティングを使います。
URLを変えることで、表示するページを変える(レンダリングする)ことができます。

前回までで、

  • Material-UIで最低限のデザインを一から作成
  • データをAPIから取得して表示

しました。コードは前回の続きとなります。

シリーズ記事

やること

  • react-router-domでのReactでのルーティング
  • ルーティングするためのリンクの付け方

react-router-dom導入

まず、インストールが必要です。
今回はreact-router-dom: 5.2.0を使用しています。

$ npm install react-router-dom

TypeScriptの場合はnpm install react-router-dom @types/react-router-domみたいです。

とりあえず、aboutページを作成します。
ただ、文字を表示するだけにします。

ということで、Aboutコンポーネントを作成。

$ touch src/components/About.js

文字を表示するだけになります。

src/components/About.js
import React from 'react'

function About() {
    return (
        <div>
            aboutページ
        </div>
    )
}

export default About

前回までのコードにRouterとSwitch、Routeを足して、
URLによって、レンダリングするコンポーネントを変えています。

material-react/src/App.js
import './App.css';
import { Grid } from '@material-ui/core';
import Header from './components/Header';
import Content from './components/Content';
import About from './components/About';

import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";

function App() {
  return (
    <Grid container direction="column">
      <Grid item>
        <Header />
      </Grid>
      <Grid item container>
        <Grid sm={2} />
        <Grid xs={12} sm={8}>
        <Router>
          <Switch>
            <Route exact path="/">
              <Content />
            </Route>
            <Route path="/about">
              <About />
            </Route>
          </Switch>
        </Router>
        </Grid>
        <Grid sm={2} />
      </Grid>
    </Grid>
  );
}

export default App;

http://localhost:3000/about

とURLに入れると、

スクリーンショット 2021-01-16 5.25.38.png

文字だけのAboutコンポーネントが表示されています。

説明と注意

<Route exact path="/">exactは完全に一致しないとダメという意味です。

一致するURLを上から探すのだけど、このexactをつけないと、http://localhost:3000/jasdklfsとか意味ないものやでもContent`コンポーネントを使うことになります。

というか、http://localhost:3000/移行にURLに、何があろうがContentコンポーネントを使います。

試しに、exactを消して/aboutにアクセスしてみれば、Contentコンポーネントが出るので、試してみてもいいと思います。
<Route path="/"> 変更後、
http://localhost:3000/jasdklfshttp://localhost:3000/about
と書いてみましょう。

どちらも、Contentコンポーネントの内容が表示されます。
「書く順番」を考えましょう。

URLのパラメータを取得して個別記事表示

前回、JSON Placeholderからデータを取得しました。

APIをみると、個別記事の取得もできるので、
記事についている「詳細を見る」ボタンを押したら遷移させます。

個別記事のコンポーネントを作成

前回までで、左側は作りました。
今回は、右側を作ります。
個別記事IDにアクセスしたときにPostContentコンポーネントをレンダリングすることになります。
image.png

$ touch src/components/PostContent.js

/post/1だったら記事1を表示するようにしてみます。とりあえず
idを受け取れるかテスト。
/post/1にアクセスしたらuseParams(){id: 1}のようなデータが入るのでidだけをとるためconst { id }にしています。

そして、個別記事の後に受け取ったIDをくっつけて表示します。

material-react/src/components/PostContent.js
import React from 'react'
import { useParams } from 'react-router-dom'

function PostContent() {
    const { id } = useParams();
    return (
        <div>
            <h1>個別記事 {id}</h1>
        </div>
    )
}

export default PostContent

スクリーンショット 2021-01-16 6.02.07.png

http://localhost:3000/post/1にアクセスしたら1が表示されているのでOK
http://localhost:3000/post/10とかにしたら10に変わりますので試してみてください。

個別記事データをAPIで取得

Getで取得しましょう。URLの最後に記事のIDを入れればデータを取得できます。

では、JSON PlaceholderでAPIのURLは

https://jsonplaceholder.typicode.com/posts/1

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

axiosを使ったAPIのデータ取得は前回やったので簡単に書きます。

material-react/src/components/PostContent.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom'

import { Grid } from '@material-ui/core'
import BodyCard from './BodyCard'


const cardContent = 
    {
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    }

function PostContent() {
    const { id } = useParams();
    const [post, setPosts] = useState([])

    useEffect(() => {
        axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
        .then(res => {
            setPosts(res.data)
        })
    }, [])

    return (
        <Grid container spacing={2}>
            <Grid item xs={12} key={post.id}>
                <BodyCard {...{...post, ...cardContent}} />
            </Grid>
        </Grid>
    )
}

export default PostContent
  • https://jsonplaceholder.typicode.com/posts/${id}でURLから取得したパラメータを埋め込んでGET、APIでの取得
  • useStateを使って、ページを開いた時一度だけ、APIで取得
  • 前回作った、BodyCardコンポーネントに渡してMaterial-UIのカードの形式で作成
    • {...{...post, ...cardContent}}で二つのオブジェクトを結合して渡してます

とりあえず、ページを作り込むより、ルーティングの使い方説明メインなので、

  • 詳細をみるというボタンがあったり、
  • 画像がAPIでランダム取得するので変わってしまう、
  • 画像がhight: 150pxだと大きさおかしい

とかツッコミありますができました。
スクリーンショット 2021-01-16 16.44.21.png

一応、前回の記事から変更はないですが、BodyCardコンポーネントも載せておきます。

BodyCard.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import CardHeader from '@material-ui/core/CardHeader';
import Avatar from '@material-ui/core/Avatar';
import IconButton from '@material-ui/core/IconButton';
import StarBorderOutlinedIcon from '@material-ui/icons/StarBorderOutlined';
import { CardMedia } from '@material-ui/core';

const useStyles = makeStyles({
    bullet: {
      display: 'inline-block',
      margin: '0 2px',
      transform: 'scale(0.8)',
    },
    title: {
      fontSize: 14,
    },
    pos: {
      marginBottom: 12,
    },
    cHeader: {
        height: '50px',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        whiteSpace: 'nowrap',
        "& .MuiCardHeader-content": {
            overflow: 'hidden'
        }
    },
    cContent: {
        height: '200px',
        overflow: 'hidden'
    }
});


function BodyCard(props) {
    const { userId, id, title, body, avatarUrl, imageUrl } = props;
    const classes = useStyles();
    const bull = <span className={classes.bullet}></span>;
    return (
        <Card variant="outlined">
            <CardHeader
                avatar={<Avatar src={avatarUrl} />}
                action={
                <IconButton aria-label="settings">
                    <StarBorderOutlinedIcon />
                </IconButton>
                }
                className={classes.cHeader}
                title={title}
            />
            <CardMedia style={{ height: "150px" }} image={imageUrl} />
            <CardContent className={classes.cContent}>
            <Typography variant="body2" component="p">
                {body}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">詳細をみる</Button>
            </CardActions>
        </Card>
    );
}

export default BodyCard

ボタンにリンクをつける

Material-UIにはButtonにhrefつければ遷移できるようになります。
以下のようにすれば大丈夫。

BodyCard.js
<Button size="small" href={`/post/${id}`}>詳細をみる</Button>

今回、Buttonにリンク機能がついていたので使いましたが、
デザインを自分でやりたい、文字にリンクつけるなどの場合は、

<Link to="/post/1">記事1の詳細</Link>

<Link to=`/post/${id}`>記事{$id}の詳細</Link>

などにすればいいと思います。
react-routerの公式サイトは英語ですが、
コードがみやすいのでみながら試してみてください。

https://reactrouter.com/web/example/basic

まとめ

  • react-router-domでのReactでのルーティング
  • ルーティングするためのリンクの付け方

をやりました。次回はAPIサーバーをPythonのDjangoで自作します。

補足

これまで、RouterをApp.jsに書きました。
ただ、ヘッダーがいらない、デザインを変えたいなどの場合(ログインページ、LP、認証のためのURLとか使う場合など)、

index.jsにrouterを書くこともできるので補足として。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import About from './components/About';
import reportWebVitals from './reportWebVitals';
import { Route, BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <div>
        <Route exact path="/" component={App} />
        <Route exact path="/about" component={About} />
      </div>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React hooks】噛み砕いて解説してみた~useContext編~

前書き

16.8vで追加された機能であるreact hooksを理解を深めるために体系的にまとめました。

以下、本題です

そもそもContextとは

別コンポーネントに値を動的に渡す方法として、Propsがあると思います。Contextも同じように値を動的に渡すことができます。

Contextの存在意義

多重ネストされたコンポーネントに値を渡すときに、下記のようにバケツリレーになってしまうことが多々あると思います。(コードは公式ドキュメントからコピペしました)

<Page user={user} avatarSize={avatarSize} />
// ... Page コンポーネントは以下をレンダー ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... PageLayout コンポーネントは以下をレンダー ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... NavigationBar コンポーネントは以下をレンダー ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

Contextを使うことで、コンポーネントを超えて動的に値を渡すことができます。

実際の使い方(hookなし)

まずはuseContextを使わないパターンを解説していきます。そのほうが理解が深まるからです。(知っているよ!という人は飛ばしてください。)

登場人物

  • createContext()
  • Proveider
  • Consumer

それぞれ軽く説明しておきます。

createContext()

const Context = React.createContext()

この関数でコンテキストオブジェクトが作成されます。このコンテキストオブジェクトは下記のようになっています。

{
  Provider: <>...<>,
  Consumer: <>...<>,
  ...
}

Context.ProviderContext.Consumerのように使われます。実際に見ていきましょう。

Provider

 return (
    <Context.Provider value={resource}>
      <Hoge />
    </Context.Provider>
  )

上記のようにProviderpropsvalueに渡したい値を指定してあげます。

Consumer

const Hoge = () => (
  <Context.Consumer>
    {(resource)=> (
      <h1>{resource.title}</h1>
    )}
  </Context.Consumer>
)

上記のように実際に使いたいコンポーネント(今回はProviderラップしたHogeコンポーネント)でprops.childに対して関数の引数で渡したい値(ここではresource)を入れています。

以上でuseContextを使わないパターンの説明は終了です!

以降からuseContextを使ったパターンの解説をしていきます。

useContextを使う場合

簡潔にお伝えするとConsumerつまり実際に値を呼び出すコンポーネントで使うことができます。useContextを使わない場合は下記のような形ですね。

const Hoge = () => (
  <Context.Consumer>
    {(resource)=> (
      <h1>{resource.title}</h1>
    )}
  </Context.Consumer>
)

useContextを使う場合は下記のようにuseContext()関数の引数にコンテキストオブジェクトを含めてください。

const Hoge = () => {
  const { resource } = useContext(Context)

  return (
    <h1>{resource.title}</h1>
  )
}

props.childが不要になったのでスッキリしましたね!

解説は以上です。お疲れ様でした?‍♂️

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React+reduxで数字をカウントしてみる

index.jsを用意します

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { PersistGate } from 'redux-persist/integration/react'
import './index.css';
import App from './App';


// ステートの値
let state_value = {
    counter:0,
    message:"COUNTER"
}


// レデューサー
function counter(state = state_value, action) {
    switch (action.type) {
        case 'INCREMENT':
        return {
            counter:state.counter + 1,
            message:"INCREMENT"
        };
        case 'DECREMENT':
        return {
            counter:state.counter - 1,
            message:"DECREMENT"
        };
        case 'RESET':
        return {
            counter:0,
            message:"RESET"
        };
        default:
        return state;
    }
}


// Redux Persistの設定
const persistConfig = {
    key: 'root',
    storage,
}


// パーシストレデューサーの作成
const persistedReducer = persistReducer(persistConfig, counter)


// ストア、パーシスターの作成
let store = createStore(persistedReducer)
let pstore = persistStore(store)


// 表示をレンダリング
ReactDOM.render(
    <Provider store={store}>
        <PersistGate loading={<p>loading...</p>}
                persistor={pstore}>
            <App />
        </PersistGate>
    </Provider>,
    document.getElementById('root')
);

ここではパーシストレデューサーをもとに、ストアとパーシスターを作成しました。
次にパーシスターとAPPコンポーネントをJSXで表示します。

APP.jsを作成

import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';


// Appコンポーネント
class App extends Component {


  constructor(props){
    super(props);
  }

  render() {
    return (
      <div>
        <h1>Redux</h1>
        <Message />
        <Button />
      </div>
    );
  }
}
// ストアのコネクト
App = connect()(App);


// メッセージ表示のコンポーネント
class Message extends Component {
  style = {
    fontSize:"20pt",
    padding:"20px 5px"
  }

  render(){
    return (
      <p style={this.style}>
        {this.props.message}: {this.props.counter}
      </p>
    );
  }
}
// ストアのコネクト
Message = connect((state)=>state)(Message);


// ボタンのコンポーネント
class Button extends Component {
  style = {
    fontSize:"16pt",
    padding:"5px 10px"
  }

  constructor(props){
    super(props);
    this.doAction = this.doAction.bind(this);
  }

  // ボタンクリックでディスパッチを実行
  doAction(e){
    if (e.shiftKey){
      this.props.dispatch({ type:'DECREMENT' });
    } else if (e.ctrlKey){
      this.props.dispatch({ type:'RESET' });
    } else {
      this.props.dispatch({ type:'INCREMENT' });
    }
  }


  render(){
    return (
      <button style={this.style}
          onClick={this.doAction}>
        click
      </button>
    );
  }
}
// ストアのコネクト
Button = connect()(Button);


export default App;

APP.js内に画面に表示するMessageコンポーネントとプッシュボタンのButtonコンポーネントを作成します。
Buttonコンポーネント内には、ボタンを押すINCREMENT、Shiftキー押しながらボタンを押すとDECREMENT、ctrlキーを押しながらボタンを押すとRESETのタイプを作りました。ボタンを押すとタイプごとのアクションをRedux送りそのタイプにあった処理が行われます。

image.png

読んでくださりありがとうございます。
初学者なので至らないところがあると思います。
おかしい所があればご指摘願います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】英単語数をカウントする

わりと簡単に作れると思っていましたが、
スペースがあったときに1カウントとすると、スペースが2つ続いたときも1単語としてカウントされてしまうことに頭を悩ませました。

解決策

HTML

<textarea id="count-area"></textarea>
<span id="output"></span>

JavaScript

const input = document.getElementById('count-area');
input.addEventListener('keyup', countWords);

function countWords() {
  // \S+の意味は「空白、タブ、改行以外が1回以上続く」
  const spaces = input.value.match(/\S+/g);

  let words;
  if (spaces) {
    words = spaces.length;
  } else {
    words = 0;
  }

  document.getElementById('output').textContent = words + " words";
}

参考

Count words as user type - Textarea and Javascript (Youtube)
正規表現入門 レッスン7 Space (Youtube)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mapbox入門

Mapboxを使って地図を表示

CDN

<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v/mapbox-gl.css' rel='stylesheet' />

Mapbox GL JS / マイアカウントでアクセストークンを取得

mapboxgl.accessToken = 'hoge';

mapboxglのMapオブジェクトを生成(インスタンス化)

const map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://hoge',
        center: [lon, lat],
        zoom: 14
    });

オプション

container
マウントするdiv要素(not ネスト

<div id="map"></div>

style
マップのスタイル

center

[longitude, latitude]

中心地の緯度経度

zoom
ズーム度

現在地を追跡

map.addControl(
        new mapboxgl.GeolocateControl({
            positionOptions: {
            enableHighAccuracy: true
        },
            trackUserLocation: true
        })
    );

オプション

addControl
UIでmapを操作

GeolocateControl
位置情報を操作(類似:Geolocation API)

ジオロケーション
ユーザーの位置情報を扱う技術

enableHighAccuracy
・false:デフォルト
・true:高精度で現在地を取得

trackUserLocation
詳細不明
trueにするとアニメーションがつく

ギアマークをクリックで現在地まで追跡

next

リアルタイムに自分の居場所を保存し取り出せる
Webページにいる人それぞれの位置をマップに表示

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのreduce()がしていること

配列の合計値を出したいときなどに便利なreduce()
基本的な使い方は以下。

const arr = [1, 2, 3, 4, 5];

console.log(arr.reduce((n, m) => n + m));
// 15

勉強をしていて、「なんとなく合計値を出せるもの」という理解をもう一歩深められましたのでシェアします。

reduce()の構文と処理内容

僕がいつもお世話になっているMDNによると、構文は次の通りです。

arr.reduce(callback( accumulator, currentValue[, index[, array]] ) {
  // return result from executing something for accumulator or currentValue
}[, initialValue]);

参考:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

callback:配列のすべての要素に対して実行される関数
accumulator:reduce()の返り値を記憶する一時的な変数(繰り返しにおける前回の実行結果)
currentValue:現在処理される配列要素

最初の例だとそれぞれが次のように対応しています。
callback(n, m) => n + m
accumulatorn
currentValuem

そして、反復処理の実行手順をひとつひとつ見ていくと以下のようになります。

実行回数 n m 実行結果(次のn)
1回目 1 2 3
2回目 3 3 6
3回目 6 4 10
4回目 10 5 15

こうして簡単に配列の合計値を出すことができました。
昔C言語でint sum = 0;で合計値を保持する変数を用意してforループを回していた時代はなんだったのか…。

どうして要素が5個なのに処理が4回なのか

要素が5つなのに処理が4回である理由は、上で紹介した構文中のinitialValueが指定されていない場合、最初の要素を飛ばしてインデックス番号1から実行されるためです。
配列の要素の合計値を求める場合は0を初期値として指定しておく、といった具合ですね。

MDNのドキュメントでも、通常は初期値を指定するほうが安全だと書いてあります。
より詳しく知りたい方は、ぜひドキュメントを参照してみてください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#description

なぜ名前が"reduce"なのか

多くの人が英単語のreduceの意味を「減らす」で覚えていますよね。
それ自体は正しいのですが、この関数reduce()のイメージとはちょっと合致しないな~と感じます。

そこで、いまいちどreduceの意味を調べてみると、次のような意味が出てきました。

3〔+目的語+to+(代)名詞〕〈ものを〉(整理して)〔簡単な形に〕変える,まとめる.
7【数学】〈…を〉換算する,通分する,約する.
参考:https://ejje.weblio.jp/content/reduce

JavaScriptの関数で使われるreduce()としての意味は上記のものがふさわしそうです。

reduce()に関数を渡し、配列要素に対してその関数を実行していき、最終的に出た結果のみを返す。ふむふむ。
要素ひとつひとつをギュッとまとめてひとつの値のみにするというイメージですかね。

英単語の意味を押さえると、グッとわかりやすくなりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Safariで<input>のonchangeが発火しない場合の対処法

TL;DR

Safariではなぜかinputのchangeを拾えないことがある(リロード時の挙動により差分を検知出来ない事があるんだと思う)
その場合はonclickイベントでinputのvalueを空にしてやればよい
onclickはonchangeよりも先に発火するのでこれで常にnullと比較でき、変更を検知できる

const input = document.createElement('input');
input.type = 'file';
input.onclick = () => {
    this.value = null;
};
input.onchange = () => {
    // なんか処理
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【小ネタ】JavaScriptでPHPのlist()っぽいことをやってみる。

はじめに

ドット連結の文字列を分割してそれぞれを変数にできないかなーって考えたときに、もしかしたらJSでもPHPのlist()みたいなことできるんじゃね?って思ったんですよ。

PHPのlist()とは

右辺が配列になるとき、左辺側に列挙した変数に値を入れれるというやつです。
https://www.php.net/manual/ja/function.list.php

list($jeffy, $tockey, $fagimaru) = ['犬', '猿', '雉'];

echo $jeffy; // 犬
echo $tockey; // 猿
echo $fagimaru; // 雉

実はPHP7.1以降は以下の書き方でもいけるんですよね。全然気づかなかった……

[$jeffy, $tockey, $fagimaru] = ['犬', '猿', '雉'];

echo $jeffy; // 犬
echo $tockey; // 猿
echo $fagimaru; // 雉

JSではどうするの?

実はPHPの省略構文と同じです。

const [jeffy, tockey, fagimaru] = ['', '', ''];

console.log(jeffy); // 犬
console.log(tockey); // 猿
console.log(fagimaru); // 雉

「分割代入」っていうんですね。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Emotion v11とtypescriptが喧嘩する ~jsx設定方法~

emotionの環境構築をおこなったところ、
TypeScript絡みの設定が案外手強かったので備忘録とさせていただきます。

ざっくり環境

React v17.0.1
typescript v4.1.3
@emotion/react v11.1.4
@babel/core v7.12.10

jsx属性認識しない問題

import { jsx } from '@emotion/react'
import { FooterContainer } from '../../../style/components/block/Footer'

export default function Footer() {
  return (
    <div css={FooterContainer}>
      <p>Footer</p>
    </div>
  )
}


emotion v11においては、
上記のように jsxをimportして、reactからやってくるjsxを使わない(reactを直でimportしない)記述となります。
しかし、早速問題が...

スクリーンショット 2021-01-18 11.42.34.png

なんとか型定義をimportしているjsxの方に向かせたいですね。
そもそもreactのJSXの型定義はどこからきているのでしょうか?

答えはTypeScriptのコンパイルのオプションである jsxFactoryからきています。
https://www.typescriptlang.org/tsconfig/#jsxFactory
こちらはデフォルトでは React.createElementになっています。

jsxFactoryをemotionのjsxで呼べるよう変更しておきます。
ちなみにトランスパイルをbabelに任せている場合、 "jsx": "preserve" でOKです。

tsconfig
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxFactory": "jsx",
...
}

これでエラーが解消されます。

別解:プラグマを使う

公式のドキュメントでは以下のようなコメントが差し込まれています。

/** @jsx jsx */

このコメントはプラグマというTSでサポートされている機能になります。
この機能は、ファイル毎にjsxFactoryを変更できる機能です。
こちらを追加するとエラーが解消されます。

/** @jsx jsx */
import { jsx } from '@emotion/react'
import { FooterContainer } from '../../../style/components/block/Footer'

export default function Footer() {
  return (
    <div css={FooterContainer}>
      <p>Footer</p>
    </div>
  )
}

https://github.com/Microsoft/TypeScript/pull/21218

ただ、ファイル毎にプラグマを記述しないといけないのが微妙ですね。

わがまま

jsxを直で上書きしてしまうのはちょっと不安です。
毎度jsxを読まないといけないのもなんか微妙です。
もっといい方法があれば教えていただけると嬉しいです:runner:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptのスコープに関して理解を深める

そもそも、スコープとは

プログラミングにおけるスコープ(英: scope, 可視範囲)とは、ある変数や関数などの名前(識別子)を参照できる範囲のこと。
(参照:wikipedia https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97)

スコープを使うことでできること

・範囲外のものは参照をさせないような設計ができる(変数をスコープの外では変更できないような、安全性を確保)
・スコープがあることにより、関数名や変数名等同じものを使用してもスコープの内外で有効な範囲が変わってくるので、コード全体の命名が簡潔に済む

スコープの種類

グローバル変数とローカル変数の二種類に分かれる。

グローバル変数 ローカル変数
関数の外(トップレベル)で宣言した変数 関数の中で宣言した変数, 関数の仮引数
プログラム全体から参照できる その関数の中でのみ参照できる

グローバルスコープ

プログラムのどこからでもアクセス可能。
JavaScript においては、一般的にすべてのコードが実行されている Web ページがグローバルスコープとなる。

スクリーンショット 2021-01-18 13.30.22.png

aとbは関数からでも、ブロック内でもどこでも呼び出せる。

ローカルスコープ

ローカルスコープは二種類あり、関数スコープブロックスコープに分かれる。

関数スコープ

関数スコープはその名前の通り、関数ごとに作られるスコープ。

スクリーンショット 2021-01-18 13.32.16.png
↑関数内で定義したhelloに対して、関数の外から呼び出しをしようとすると、上記のようなエラーが出てしまう。

スクリーンショット 2021-01-18 13.31.42.png

↑こちらは関数内で定義したものを関数内で呼び出している為、エラーの表示がなく、結果もしっかりと"hello world"の文字列が取得できました。
このように関数内で定義したものは関数内でしか実行できないものを関数スコープと呼ばれています。

ブロックスコープ

ブロックスコープも関数スコープと同じくローカルスコープの仲間で、if文やfor文と組み合わせることが多いです。

スクリーンショット 2021-01-18 13.40.12.png

↑ブロック内で宣言しているinBlockはブロックの外から呼び出してもスコープ外の為、未定義エラーが出てしまう。

スクリーンショット 2021-01-18 13.46.33.png

↑こちらも同様にfor文の外で回数を出力しようとすると、未定義エラーが出てしまう。

上記のスコープで注意する点

letとvarの違い

変数宣言をする際、letとvar(とconst)があるが、それぞれスコープに違いがある。

スクリーンショット 2021-01-18 14.00.06.png

↑このようにブロックスコープを定めても、letはブロックごとに読み取りの制限をすることが出来ているのですが、varを使用している場合ブロック内のfooを読みとってしまっている為、外部から意図しない値に書き変わってしまいバグが発生する可能性がある。

また、varにはホイスティングと呼ばれる機能があり、関数の内部で宣言されている変数は、その関数の先頭で宣言されたものと見なされるというものです。
スクリーンショット 2021-01-18 14.07.52.png

このようなサンプルコードではすぐ近くにあるので気づけますが、関数の行数が多い場合fooがトップレベルのfooと勘違いしてしまう可能性もあり、さらにコードがエラーを出さない為、対処が大変になってしまう可能性があります。

最後に

Javascriptに苦手イメージがあり、ほとんど勉強をしてこなかったのですが、業務で取り扱う機会が多いので基礎から勉強しようと思いスコープを題材に取り上げました。
まだまだ、わからないことだらけなので、整理しながら勉強をしていきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクトのディープコピー

メモとして。

const obj = [
  {
    name: 'taro',
    age: 24
  },

  {
    name: 'sato',
    age: 22
  },
]
const copyObj = obj.map(person => ({name : person.name ,age : person.age}))

for(let i = 0; i < copyObj.length; i++){
  copyObj[i].name = "keiko";
  copyObj[i].age = i + 20;
}

console.log(obj);
console.log(copyObj);

結果

[[object Object] {
  age: 24,
  name: "taro"
}, [object Object] {
  age: 22,
  name: "sato"
}]
[[object Object] {
  age: 20,
  name: "keiko"
}, [object Object] {
  age: 21,
  name: "keiko"
}]

配列も同様

const obj = [
  {
    answer: [
      [1,1,1],
      [1,1,1]
    ],
    remain: [2,2,2]
  }
]

const copyObj = obj.map(list => (
    {
      answer : [...list.answer],
      remain : list.remain
    }
  )
);

copyObj[0].answer[0] = [1,2,3];
copyObj[0].answer[1] = [4,5,6];

console.log(obj);
console.log(copyObj);

結果

[[object Object] {
  answer: [[1, 1, 1], [1, 1, 1]],
  remain: [2, 2, 2]
}]
[[object Object] {
  answer: [[1, 2, 3], [4, 5, 6]],
  remain: [2, 2, 2]
}]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Chartで散布図を描画してみた

はじめに

授業のチーム開発で散布図を作ることがあり、そこの開発に携わっていたので、アウトプットしようと思いました。

成果物

qiitaにあげるやつ.png

このように、散布図を作成することができました。

参考元

Google Charts (Scatter Charts)

また、散布図の元データとして、ぐるなびAPIから得たデータを利用しています。
ぐるなびAPI
※2021/1/15以降、新規利用ができなくなったようです

開発環境

エディタ:vscode

ソースコード全体

index.html
   <div id="target" class="box" style="width: 700px; height: 500px;"></div>

    <script src="https://www.gstatic.com/charts/loader.js"></script>

    <!-- targetタグのdivに散布図を挿入-->
    <script src="js/googleScatter.js"></script>


googleScatter.js
const drawScatter = (result, userLatlng) => {
  google.charts.load('current', { 'packages': ['corechart'] });
  google.charts.setOnLoadCallback(drawChart);

  var mapLatlng = [
  ]

  function drawChart() {
    var data = new google.visualization.DataTable();

    // [距離、金額、店名]のデータテーブルを作成
    data.addColumn('number', '距離');
    data.addColumn('number', '金額');
    data.addColumn({ type: 'string', role: 'tooltip' });

    // ぐるなびAPIから情報を取得し、行を追加
    result.rest.map(item => {
      var latlng = new google.maps.LatLng(item.latitude, item.longitude);
      mapLatlng.push([item.latitude, item.longitude])
      var distance = google.maps.geometry.spherical.computeDistanceBetween(userLatlng, latlng);
      data.addRow([distance, item.budget, item.name]);
    });

    // mouseOnの時に表示される吹き出しを調整
    var formatter = new google.visualization.PatternFormat('店名 {2}  :{0}m :{1} 円 ');
    formatter.format(data, [0, 1, 2], 2);

    // 実際の散布図の設定
    var options = {
      title: '店の距離と金額',
      hAxis: { title: '距離', format: '###m', minValue: 0, maxValue: 15 },
      vAxis: { title: '金額', format: '###円', minValue: 0, maxValue: 15 },
      legend: 'none'
    };

    // コンテナに散布図を挿入
    var chart = new google.visualization.ScatterChart(document.getElementById('target'));

    // 散布図の店が押された時の処理を記入
    google.visualization.events.addListener(chart, 'select', function () {
      var selection = chart.getSelection();
      // console.log(selection[0].row);
      map.panTo(new google.maps.LatLng(mapLatlng[selection[0].row][0], mapLatlng[selection[0].row][1]))
    });

    chart.draw(data, options);
  }
}

index.htmlの中身

  1. 散布図の挿入元となるコンテナを用意
  2. Google Chartsの利用の宣言
  3. googleScatter.jsを呼び出す

をしています。

googleScatter.jsの中身

1. 散布図のデータの元となる表の構造を記述

    var data = new google.visualization.DataTable();

    // [距離、金額、店名]のデータテーブルを作成
    data.addColumn('number', '距離');
    data.addColumn('number', '金額');
    data.addColumn({ type: 'string', role: 'tooltip' });

2. 表にデータを入れていく

    // ぐるなびAPIから情報を取得し、行を追加
    result.rest.map(item => {
      var latlng = new google.maps.LatLng(item.latitude, item.longitude);
      mapLatlng.push([item.latitude, item.longitude])
      var distance = google.maps.geometry.spherical.computeDistanceBetween(userLatlng, latlng);
      data.addRow([distance, item.budget, item.name]);
    });

3. 散布図のプロパティを設定

// mouseOnの時に表示される吹き出しを調整
    var formatter = new google.visualization.PatternFormat('店名 {2}  :{0}m :{1} 円 ');
    formatter.format(data, [0, 1, 2], 2);

    // 実際の散布図の設定
    var options = {
      title: '店の距離と金額',
      hAxis: { title: '距離', format: '###m', minValue: 0, maxValue: 15 },
      vAxis: { title: '金額', format: '###円', minValue: 0, maxValue: 15 },
      legend: 'none'
    };

formatterの部分では、{0}{1}{2}がそれぞれ表しているのは、表の[列番号-1]です。
つまり今回なら、
{0} = 距離
{1} = 金額
{2} = 店名

optionsの部分では、散布図の「タイトル、横軸、縦軸」などを設定します。
また、表の1列目がhAxisの要素、2列目がvAxisの要素になります。

4. コンテナに作成したchartを埋め込む

    // コンテナに散布図を挿入
    var chart = new google.visualization.ScatterChart(document.getElementById('target'));

5. 散布図の点がクリックされた時の処理を記述

    // 散布図の店が押された時の処理を記入
    google.visualization.events.addListener(chart, 'select', function () {
      var selection = chart.getSelection();
      // console.log(selection[0].row);
      map.panTo(new google.maps.LatLng(mapLatlng[selection[0].row][0], mapLatlng[selection[0].row][1]))
    });

引数に「select」を渡すことで、散布図の点がクリックされた時に動くようになっています。

また、変数のselectionでは表のindex部分を参照しています。

6. chartの描画

    chart.draw(data, options);

反省点

  • 変数の宣言部分の順番がわかりづらい

    • targetにchartを入れるというところは、もっと最初の方に記述しておくべき
    • index.htmlのjavascriptを呼び出すところがぐちゃぐちゃ
  • ソースコードの説明の仕方が下手くそ

最後に

今回は、Qiita初投稿でしたが、ソースコードの説明を記述するのがとても難しいということがわかりました。
もう少し、他の投稿者様の書き方を参考にしていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一列で終わるJavascriptとアルゴリズム

一列で終わるJavascript

1loc.dev.comはウェブ開発(JavaScript)に必要な技術が見られるリアルJavaScript「ライブラリー」である。 利用者は無料で資料を見ることができる。
簡単な例もあり、分かりやすい!

1.png

https://1loc.dev/

一列で終わるJavascript

ウーバーの開発者trekhlebGitHubには
アルゴリズムが必要な時に有用な情報があふれている。

2.png

ビッグデータの分析や統計関連数値を予測するプログラムを実装する段階で、trekhleb GitHubを好んで探すなら、特定のアルゴリズムを簡単かつ迅速に検索して使用することができる。

何より、日本語になっているので分かりやすい!

3.png

https://github.com/trekhleb/javascript-algorithms/blob/master/README.ja-JP.md

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロントで電話番号をハイフン付きに変換する

利用するライブラリについて

ユーザの入力した電話番号に対して、視認性を持たせるためハイフン区切りにしたい、もしくはハイフン付きでAPIサーバに送信する必要があるなど、フロントでハイフン付き電話番号に変換する機会があると思います。

GitHubのGoogleリポジトリに電話番号処理ライブラリlibphonenumberがあります。
こちらのJS版のデモページで、電話番号とカントリーコード(JP)を入力することで動作検証ができます。こちらのデモと同様の挙動を実現する想定です。
Phone Number Parser Demo

JS版の使い方を読むと、コンパイルなどの必要があり直接NPMから利用できずとても面倒です。本家の参照しているサードパーティライブラリを使います。

Alternatives to our own versions:
Javascript: If you don't want to use our version, which depends on Closure, there are several other options, including https://github.com/catamphetamine/libphonenumber-js - a stripped-down rewrite, about 110 KB in size - and https://github.com/seegno/google-libphonenumber - a browserify-compatible wrapper around the original unmodified library installable via npm, which packs the Google Closure library, about 420 KB in size.

今回は執筆当時にDL数の多かったgoogle-libphonenumberを利用します。

動作環境

  • TypeScript 3.9
  • google-libphonenumber "3.2.15"
  • NPMから利用
// npm
npm install google-libphonenumber
// Yarn
yarn add google-libphonenumber 

サンプルコード

TypeScriptでサンプルコードを作成しましたが、変数宣言時の型定義を削除すれば、JavaScriptでも動作するかと思います。

formatUtil.ts
import { PhoneNumberUtil, PhoneNumber, PhoneNumberFormat } from 'google-libphonenumber';

export class FormatUtil {
  public static formatTelNumber (value: string) {

    // 日本の国コード
    const region = 'JP';

    const util:PhoneNumberUtil = PhoneNumberUtil.getInstance();

    // 番号と地域を設定
    const number:PhoneNumber = util.parseAndKeepRawInput(value, region);

    // 電話番号の有効性チェック
    if (!util.isValidNumberForRegion(number, region)) {
      return null;
    }

    // ハイフン付きの形式で返却
    return util.format(number, PhoneNumberFormat.NATIONAL);
  }
}

動作結果

formatUtil.spec.ts
import { FormatUtil } from '~/util/FormatUtil';

describe('FormatUtil', () => {
  test('formatTelNumber', () => {
    // 携帯電話番号
    expect(FormatUtil.formatTelNumber('09012345678')).toEqual('090-1234-5678');
    expect(FormatUtil.formatTelNumber('08099998888')).toEqual('080-9999-8888');
    expect(FormatUtil.formatTelNumber('07033336666')).toEqual('070-3333-6666');
    expect(FormatUtil.formatTelNumber('09011111111')).toEqual('090-1111-1111');
    // 大阪
    expect(FormatUtil.formatTelNumber('0611112222')).toEqual('06-1111-2222');
    // 愛知
    expect(FormatUtil.formatTelNumber('0529991111')).toEqual('052-999-1111');
    // 千葉
    expect(FormatUtil.formatTelNumber('0431234567')).toEqual('043-123-4567');
    // 福岡
    expect(FormatUtil.formatTelNumber('0921234567')).toEqual('092-123-4567');
    // 東京
    expect(FormatUtil.formatTelNumber('0312345678')).toEqual('03-1234-5678');
    // IP電話
    expect(FormatUtil.formatTelNumber('05012345678')).toEqual('050-1234-5678');

    // 無効番号
    expect(FormatUtil.formatTelNumber('05012')).toEqual(null);
    expect(FormatUtil.formatTelNumber('09000000000')).toEqual(null);
    expect(FormatUtil.formatTelNumber('99999999999')).toEqual(null);
  });
});

参考

GitHub Google/libphonenumber
GitHub ruimarinho/google-libphonenumber
Qiita JavaScriptで電話番号のバリデーション&自動フォーマット

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

困っている方必見!ページトップからある要素までの高さの取得方法

cssにてpositionを使っているときに、topleftなどを%で指定していると、使っている機種によって%の値が変わってきます。

これは厄介。。。

こんな時に役立つのが、Javascriptを使ってページトップからの高さを取得してしまえば、機種によって%の値を変える必要はありません!

結論から言うと、getBoundingClientRect()pageYOffsetの2つの関数を使います。

それでは使い方を見ていきましょう!!

要素を取得

index.js
const topToElement = document.getElementsByClassName('class-name')[0]

また、document.getElementIdなどで取得してください

ページトップからの高さを取得

index.js
const topToElement = document.getElementsByClassName('class-name')[0]
const topToElementHeight = topToElement.getBoundingClientRect().top + window.pageYOffset
console.log(topToElementHeight)

このようにして高さを取得することができます!

また、これらはVue.jsで使う場合、DOM要素と紐づけられた後のmountedで行ってください。

createdでは取得できないのでご了承ください。

以上、「ページトップからある要素までの高さの取得方法」でした!

良かったら、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AgregoreWeb を使ってみた (-01)

AgregoreWeb を最近知りました。

以下の Live 配信 で 紹介されたのが切欠なのですが、
とても興味深い技術です。

Speakeasy JS - Using Standard Web APIs to Mix P2P Protocols (Mauve)
https://www.pscp.tv/feross/1BRJjBkdqLoJw?t=23m53s

What is AgregoreWeb

AgregoreWeb は 分散Web用のブラウザーです。
AgregorWeb は https://github.com/AgregoreWeb/agregore-browser で ソースが公開されてる OpenSource です。

公式サイトは以下になります。
https://agregore.mauve.moe/

ドキュメントは以下

https://github.com/AgregoreWeb/agregore-browser/tree/master/docs

起動してみた

https://github.com/AgregoreWeb/agregore-browser/releases からダウンロードできます

Screen Shot 2021-01-18 at 7.05.01.png

ブラウザーと同じですね!!

Screen Shot 2021-01-18 at 7.09.35.png

こんな感じでアクセスできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redmineでチケットをコピーした時は開始日と期日をそれぞれ+1日加算したい

同じ開始日と期日でチケット作ることほぼ無いからね

RedmineをTODOっぽく使ってるマンです。
んで、使ってるうちにこんなことを思いました。

  1. 今日のお掃除チケット終了!じゃあ明日用にチケットコピーしようジャマイカ。
  2. あれ?コピーしたはずのチケットが見当たらない…。
  3. あ、開始日と期日を 1日ずらすの忘れてた
  4. 面倒くさいなあ…。

ということで記事タイトルのようなことをやりました。

ビューカスタマイズプラグイン使うよ

ビューカスタマイズプラグインのインストールまでは下記でやってるので、この記事ではハショリます。
CentOS8にインストールしたRedmineにビューカスタマイズプラグイン導入

設定はこんな感じ

パスのパターン で絞り込んでおくのが大事。
『このJSって別のページに悪影響あるかな?』を考えなくてよくなるです。
image.png

コード
$(function(){


    var start_val_ = $('#issue_start_date').val();
    var due_val_ = $('#issue_due_date').val();


    //コピー元の値を確認できた方がいいから。
    $('label[for="issue_start_date"]').text('開始日(元:' + start_val_.replace(/-/g, '/') + '');
    $('label[for="issue_due_date"]').text('期日(元:' + due_val_.replace(/-/g, '/') + '');


    var y_;
    var m_;
    var d_;


    //チケットコピーは日次のTODOが多いから開始日と期日を+1日加算。

    //開始日未設定のチケットをコピーすると、
    //開始日の値は現在日付が自動でセットされるので、
    //開始日の値が無い場合は考えないことにします。


    //開始日
    var start_date_ = new Date(start_val_);
    start_date_.setDate(start_date_.getDate() + 1);

    y_ = start_date_.getFullYear();
    m_ = ("00" + (start_date_.getMonth()+1)).slice(-2);
    d_ = ("00" + start_date_.getDate()).slice(-2);
    $('#issue_start_date').val(y_ + "-" + m_ + "-" + d_);


    //期日未設定のチケットをコピーした時に例外にならないように。
    if(due_val_ == '') return;


    //期日
    var due_date_ = new Date(due_val_);
    due_date_.setDate(due_date_.getDate() + 1);

    y_ = due_date_.getFullYear();
    m_ = ("00" + (due_date_.getMonth()+1)).slice(-2);
    d_ = ("00" + due_date_.getDate()).slice(-2);
    $('#issue_due_date').val(y_ + "-" + m_ + "-" + d_);


})

有効にするとこんな感じ

年またぎと月またぎの時にちゃんと動作するか確認しました。
image.png

蛇足

チケットを『日次』『週次』『月次』『年次』『不定期』に分類して、それぞれに対してチケットコピー時の加算日数を切り替えれば、もうちょっと便利になるかもです。

ビューカスタマイズプラグインはこういうプチ改良が手軽にできて便利です。

バージョン

CentOS Linux release 8.3.2011
Redmine 4.0.6.stable
Chrome バージョン: 87.0.4280.141(Official Build) (64 ビット)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript で文字コードを加減算する方法

自分のサイトがあるのですが、そこを楽曲専用から汎用に大型アップデートしようと思いまして、
そこでリストの番号を

イメージ
  リスト
①text1
②text2
③text3

の様にしたかったんですね。数字が○で囲まれている文字です。
そこで、CSS で

合ってるかは知らないけどこんなの.css
ol li#list1:nth-child(1)::before {
    content: "①";
}
/*    :
      :    */

とか文字列配列作ってインデクスに応じて切り替えたり(?)で出来るんですが、
せっかくなら JavaScript で書きたくなったんですよ。
そのままテキストで①、②…と書けばいいものを…

C/C++なら分かるぞ!

と思ったので一応書いておきます。C/C++ での実装の場合のイメージです。

C/C++の場合.cpp
std::string GetNumByCircle(int num)
{
    std::string res = '①' + num;
    return res;
}
実行結果のイメージ(入力は3)

これで今回の主題は達成するのですが、JavaScript で同じ様に書いてみましょうか。

JavaScriptの場合.js
function getNumByCircle(num)
{
    var res = '' + num;
    return res;
}
実行結果のイメージ(入力は3)
①3

JavaScript では '...' も "..." も文字列で、+ は文字列結合演算子なので文字列結合が発生しました。
これでは、僕が実際に使おうとした方法でやると

他のバグもありますが、こうなりますよね。
...①6①5①4①3①2①1①0

JavaScript で文字コードを加減算

では、JavaScript で「'①' + num」を考えてみましょう。


…分かりましたか?
では、僕が最終的に辿り着いた方法を紹介しましょう。

JavaScriptでのやり方.js
function getNumByCircle(num)
{
    var res = String.fromCharCode(''.charCodeAt(0) + num);
    return res;
}

これで

実行結果のイメージ(入力は3)

となります。長いなぁ…。

解説

解説編です。

何が起こっているか

といえば、

  1. 文字コードを取得
  2. 取得した値に num を加算
  3. その値を文字コードとした文字を取得

とやっているだけです。
因みに C/C++ の場合は、

  1. char 型の整数値(=文字コード)に num を加算

これだけです。

1. 文字コードを取得

この部分.js
/*function getNumByCircle(int num)
{
    var res = String.fromCharCode(*/ ''.charCodeAt(0) /*+ num);
    return res;
}*/

文字列.charCodeAt(index: number)
文字列の index 番目の文字の文字コードを取得するメソッドです。
これで 文字型から整数型に変換するようなことをしています。

2.取得した値に num を加算

この部分.js
/*function getNumByCircle(int num)
{
    var res = String.fromCharCode(*/ ''.charCodeAt(0) + num /*);
    return res;
}*/

文字コード(整数値) + 整数値
書くとすればこういうことになりますね。単純に整数値同士の加算です。
C/C++ の方では「1.char 型の整数値(=文字コード)に num を加算」にあたる式です。

1. 文字コードを取得

この部分.js
/*function getNumByCircle(int num)
{
    var res = */String.fromCharCode(''.charCodeAt(0) + num)/*;
    return res;
}*/

String.fromCharCode(...codes: number[])
文字コード配列に対応する文字列を取得するメソッドです。
これで 整数型配列から文字型配列に変換するようなことをしています。

まとめ

String.fromCharCode(str.charCodeAt(idx) + num)

chr + num の様な事は可能!
…書きながら思いましたが、 str[idx] + num の方が JS に忠実ですね。

おわりに

記事を書いている最中にメソッド名で調べたんですが、
どうやら String.charCodeAt(idx) では値の範囲によっては上手く動かないようです(例えば "?" 等)。
その理由や、どのメソッドを使用すればいいのかは自分で調べてみて下さい。
因みに、 String.fromCodePoint(codes) の方はこれで良いのでしょうか…?

さてさて、執筆はこの辺にして、ちゃんと各 <ol><li>:before に配置されるように修正しないとね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript ベスト・オブ・ザ・イヤー 2020

00.png

JavaScriptライブラリのトレンドを紹介しているbestofjs.orgが、2020年に最もホットであったJavaScriptライブラリのランキングを発表しました。
選考基準は現在のスター数ではなく、『2020年の一年間で増えたスターの数』です。
過去流行っていたけど落ち目となった技術は出てこないので、最近注目されている技術がわかります。

ちなみに2016年の総合ランキング1位はVue.js、2017年の総合ランキング1位はVue.js、2018年の総合ランキング1位はVue.js、2019年の総合ランキング1位はVue.jsです。

以下は2020年のランキング、2020 JavaScript Rising Starsの日本語訳です。

JavaScript ライジングスター 2020

5回目のJavaScript ライジングスターにようこそ!

このランキングのコンセプトは、昨年までと同じです。
すなわち、2020年の一年間でGitHubに追加された☆の数を比較することで、どのプロジェクトが最も注目を集めたかを数字で確認します。

以下のチャートは、2020年の一年間にGitHubで増加したスターの数を比較したものです。
Webプラットフォームに関するベストプロジェクトを集めたリストであるBest of JavaScriptからの分析となります。
各プロジェクトをクリックすると、プロジェクトの詳細を閲覧することができます。

総合ランキング

04.png

1位: Deno
2位: Vue.js
3位: React
4位: Playwright
5位: VS Code
6位: esbuild
7位: Vue Element Admin
8位: eDEX-UI
9位: Next.js
10位: Tailwind CSS

2020年は様々な理由で特別な年になりました。
最も目を引くことは、これまで5年間首位を独走してきたVue.jsを抜き去り、Denoが一位になったことです。

DenoはNode.jsの生みの親Ryan Dahlによる新たなJavaScriptランタイムです。
Node.jsのこれまでの10年間の経験と反省を生かし、多くを改善しているため、Node.jsの後継と思われがちです。
主な機能としては、

  • デフォルトでTypeScript対応。ただしJavaScriptでコードを書くこともできる。
  • 一極集中したパッケージマネージャがなく、任意の依存関係を任意のURLから読み込むことができる。
  • Deno標準ライブラリは、Node.jsでは個別にパッケージをインストールしなければならなかったような一般的な用途のライブラリを最初から提供する。
  • Denoは可能なかぎりWeb標準に従っている。(例:Fetch API)
  • インポートはES Modulesを使用。
  • テストランナーやデバッガーを標準装備。

Denoのエコシステムはまだまだ発展途上ですが、Denoの話題性を考えると、今後大きく変化することが期待されます。

Denoの成長は、2つの大きなトレンドを裏付けています。

  • フロントエンドとクライアントサイドでのTypeScriptの台頭
  • Snowpackなどのソリューションによってオンザフライで提供されるES Modulesの成長。

フロントエンド フレームワーク

1位: Vue.js +22.5k
2位: React +19.8k
3位: Angular +13.3k
4位: Svelte +12.0k
5位: Alpine.js +11.5k
6位: vue-next +5.9k
7位: Solid +3.3k
8位: Preact +3.0k
9位: htmx +2.7k
10位: Stimulus +2.0k

いつものようにVue.jsReactが頂上決戦を繰り広げています。

その後ろでは、2019年に3番手をSvelteに奪われたAngularが、ふたたびその位置を奪還しました。

ベスト5の新顔はAlpine.jsで、これはLaravel LiveWireの作者によって作られたミニマルなリアクティブフレームワークです。
Vue.jsとAngularの両方から、カスタムHTMLディレクティブや双方向バインディングといったアイデアを拝借しています。
HTMLに古き良き<script>タグを追加するだけで簡単に使うことができ、ビルドプロセスも不要で、HTMLマークアップだけで全てを動かすことができます。
本格的なフレームワークを導入することが困難な既存のWebページをさくっと強化する目的については、最も適切なソリューションであるかもしれません。

Webページにインタラクティブ性をもたらすだけの非常に軽量なソリューションであるため、Elixir Phoenixのようなフレームワークともうまく連携して同居できます。
Alpine.jsTailwind CSSを最初からまとめておいたPETALのようなプロジェクトも存在します。
こちらについては後ほど語りましょう。

Node.js フレームワーク

1位: Next.js +15.5k
2位: Strapi +11.8k
3位: Nest +10.3k
4位: Nuxt +8.2k
5位: Blitz +6.0k
6位: Redwood +5.5k
7位: Express +4.6k
8位: Fastify +3.8k
9位: umi +2.9k
10位: Koa +2.3k

Node.jsフレームワークには大きく2つの種類が存在します。

ひとつはNext.jsNuxtのようなフルスタックフレームワークで、ReactやVue.jsなどをサーバサイドに持ってくるアプリケーション構築方法については賛否両論があります。

もうひとつは昨年のチャンピオンNestFastifyなどが属する、サーバ側のみで動作する古典的なフレームワークです。

この分野では2018年にトップだったNext.jsが再びチャンピオンに返り咲きました。
当初はReactをSSRするだけのソリューションとして名を上げましたが、今ではReactでフルスタックWebアプリケーションを構築するソリューションの筆頭になっています。
最新バージョンでは動的ページと静的ページの垣根を取り払うIncremental Static Regenerationにも対応し、多くのユースケースで最適な選択肢になりつつあります。

フルスタックといえば、BlitzRedwoodは、それひとつだけで完全なWebアプリケーションを構築できるという最高の開発者体験を提供することを目的としたプロジェクトです。

それにしても浮沈の激しいJavaScriptの世界で、11年前に誕生したExpressがいまだに一定の地位を保っているのは興味深いですね。

React エコシステム

1位: Next.js +15.5k
2位: REact Query +13.6k
3位: Recoil +11.1k
4位: Ant Design +10.9k
5位: React Hook Form +10.8k
6位: Material UI +10.6k
7位: Create React App +10.1k
8位: Chakra UI +10.0k
9位: swr +8.9k
10位: Gatsby +7.4k

2020年のReactエコシステムのテーマは安定性でした。
React17では破壊的変更を行わず、将来に向けての布石を仕込みました。
それがReact Server Componentsです。

React Server Componentsはクライアントのバンドルサイズを縮小し、起動にかかる時間を改善します。
さらにデータの取得、データベースやファイルシステムなどデータソースへのアクセスも簡単になります。

Next.jsは、Reactアプリケーションを構築するための最も有名なソリューションに成長しました。
React Server Componentsの最初のアプリになることでしょう。
React QueryRecoilReact Hook Formといったサポートライブラリは、hooksを主軸に進化、円熟してきました。
それぞれがReact開発の一部を簡素化してくれます。
これらのコンポーネントライブラリを組み合わせることで、React開発者はこれまで以上に多くのツールを手に入れることができるでしょう。

Vue エコシステム

1位: Vue Element Admin +16.0k
2位: Vite +14.1k
3位: Nuxt +8.2k
4位: Element Plus +7.3k
5位: vue-next +5.9k
6位: Vuetify +5.8k
7位: Wiki.js +5.7k
8位: Element +5.5k
9位: Ant Design Vue +4.2k
10位: Vant +4.1k

2020年のVueコミュニティ最大のニュースは、Vue3のリリースです。

Vue2に存在した幾つかの問題を解決するために、Composition APIという仕組みが導入されました。

  • コンポーネント内の論理的な繋がりによってコードを整理することが難しかった。
  • コンポーネントをまたいだコードの再利用が容易になる。 (Vue2のmixin、mixing factory、scoped slots等では不十分だった)
  • TypeScriptサポートが改善された。

バージョン3で導入された変更については、マイグレーションガイドをチェックしてみてください。

2020年には新たなWeb構築ツール、Viteが誕生しました。
ES modulesに対応し、コマンドラインからVueアプリケーションを構築する最速の方法です。

Angular エコシステム

1位: ngx-admin +2.5k
2位: Material Design for Angular +1.5k
3位: Scully +1.4k
4位: Angular CLI +1.3k
5位: NG-ZORRO +1.2k

Angularのランキングは昨年とあまり変わりませんが、3位に新たなプロジェクトが登場しました。
ScullyはAngularにJamstackをもたらす静的サイトジェネレータです。
このプロジェクトは2019年12月に登場し、そしてわかりやすいドキュメントが存在します。

Angularは2020年に3つのメジャーバージョンがリリースされました。

2月にはバージョン9がリリースされました。
主な変更点はIvyコンパイラの導入で、これによってバンドルサイズが減少し、またビルドプロセスに大きな改善がもたらされました。
さらに年の後半にはバージョン10バージョン11がリリースされました。

Angularチームの2020年後半の主な仕事は、コミュニティの声に耳を傾けることでした。
コミュニティのニーズを理解するために、issueやPRに対応することに大きな努力を行いました。
また、チームが取り組んでいることの共有や、今後のロードマップの公開も行いました。

ビルドツール

1位: esbuild +16.6k
2位: Rome +14.2k
3位: Vite +14.1k
4位: Snowpack +10.1k
5位: Webpack +4.5k

2020年はビルドツールの当たり年で、多くの新しいトレンドが産まれました。

SnowpackViteはES modulesの将来に賭けたアプローチです。
開発中のコードはバンドルせず、プロダクションコードのビルド時のみバンドルする方針で、非常に高速なフィードバックループを持っています。

swcesbuildは、それぞれRustとGoで書かれており、TypeScriptをサポートしていて、そして信じられないほどの高速で動作します。

Webpackは設定が複雑すぎると言われることが多く、よりシンプルに書けるParcelRollupが成熟してきました。
とはいえビルドツールの中心はいまだWebpackであることは変わらず、そしてWebpackの新たなキャッシングレイヤはビルドのパフォーマンスを大幅に改善します。

Monorepoがメインストリームになりつつあります。
YarnとLernaが広く使われ、そしてnpm 7も参加してきました。

個人的に2021年の去就を注目しているのはRomeToastTurborepoです。

CSSフレームワーク

1位: Tailwind CSS +15.5k
2位: Bootstrap +8.2k
3位: Bulma +4.2k
4位: new.css +3.1k
5位: Halfmoon +2.0k

昨年はなかった項目ですが、Tailwind CSSの躍進と、そのユーティリティファーストの姿勢を評してこのセクションを追加しました。

BootstrapBulmaといった既存のCSSフレームワークに比べて、開発者がクラス名を合成してページやコンポーネントをスタイル化するための命名規則を提供しています。
State of CSSのアンケートにおいても、最も満足度の高いフレームワークになっています。
先日バージョン2がリリースされ、ダークモードなど多くの新機能が追加されました。

CSS in JavaScript

1位: Styled Components +4.8k
2位: Twin +2.8k
3位: Emotion +2.5k
4位: Linaria +1.8k
5位: Theme UI +1.8k

テスト

1位: Playwright +19.7k
2位: Storybook +12.3k
3位: Puppeteer +10.6k
4位: Cypress +9.0k
5位: Headless Recorder +6.0k

モバイル

1位: React Native +8.8k
2位: Expo +4.3k
3位: Quasar +4.0k
4位: Ionic +2.8k
5位: Sonar +1.8k

JSコンパイラ

1位: TypeScript +10.4k
2位: swc +3.4k
3位: Babel +2.7k
4位: Reason +818
5位: Flow +799

状態管理ライブラリ

1位: Recoil +11.1k
2位: XState +5.1k
3位: Immer +4.2k
4位: Zustand +3.2k
5位: Redux +3.2k

GraphQL

1位: Gatsby +7.4k
2位: Hasura GraphQL Engine +5.9k
3位: Redwood +5.5k
4位: Prisma +4.0k
5位: Apollo client +2.4k

学習リソース

1位: JS Algorithms & Data Structures +31.9k
2位: Node.js Best Practices +20.2k
3位: You Don't Know JS +18.0k
4位: Clean Code +15.1k
5位: 30 seconds of code +13.3k

まとめ

Best of JSが追跡している多くのカテゴリにおいて、幾つかの新しい潮流が発生し、JavaScriptの世界は今年も素晴らしい年になりました。

バックエンド開発者は今すぐDenoを使って、依存を気にすることなくTypeScriptを楽しむことができます。

フロントエンド開発者はesbuildSnowpack、そしてViteなど、より高速でシンプルなビルドソリューションを手に入れることができました。

ツールにおいては、NPM 7がひとつのリポジトリで複数のパッケージを扱えるようになるworkspacesをリリースしました。
これはライバルであるYarnが先に提供していた大きな利点のひとつです。

スタイルについては、よりシンプルなコンセプトを中心としたエコシステムを構築する、Tailwind CSSのような方向性のソリューションが他にも現れています。

2021年には何が期待できるでしょうか?

React Server Componentsがどのようなものになるかは興味深いところです。

Sebastian McKenzie(BabelやYarnを作った人)がRomeにフルタイムで入っている今、そのJavaScriptツールを統一しようとする試みはどこまで進むでしょうか。
コンパイル、テスト、Lint、その他全て、全てが入ったたったひとつの依存は完成するでしょうか。

我々は、フルスタックフレームワークであるRedwoodにも注目しています。
これはGraphQLと相性が良く、そしてデータハンドリングに"cells"と呼ばれるユニークな仕組みを使っています。

ユーザのフィードバックに基づいた、本調査とは別観点からの結果を見たいのであれば、State of JSも参照してください。

みてくれてありがとう。
また来年会いましょう!

感想

可及的速やかにReactが絶滅しますように。

このランキングが始まって以来4年間トップをひた走っていたVue.jsを抑えて、なんとDenoがトップに立ちました。
ただ、30kの半数近く13.7kは、バージョン1が出た5月の一ヶ月だけで稼いでいます。

01.png

ご祝儀にしても極端すぎるような。
試しに5月を除いてみると7位くらいです。
とはいえ非常に注目されていることには間違いないので、今後の技術の採用基準の視野に入れてもいいかもしれません。

ただし、あくまで☆の増加数であって、ダウンロード数でも実際に運用されているサイト数でもないので、注目されている=最適な技術である、は必ずしも成り立たないことに注意が必要です。
アーリーアダプター()以外の普通の開発者は、普通に枯れた技術を使うのが一番です。

たとえば昨年Angularを抜き去り、すわ新時代の到来かと思われたSvelteも、今年はあっさりAngularに抜き返されました。
Svelteが即座に消え去るとは思いませんが、今後RiotやAureliaのようにフェードアウトしていってもおかしくありません。
もちろん何かの拍子に再躍進する可能性もあるでしょう。
そのような将来が不安定な技術はそういうのが好きな連中に任せておいて、業務に取り入れるのは十分に成熟してからも遅くありません。
だいたいWebサイトを使うユーザは、そのサイトが何の技術でできているかなんて一切興味ありませんからね。
Next.js + TailwindだろうがjQuery + べた書きstyle要素だろうが、見た目が同じならそれは同じものです。

もちろん、そういうのが好きな人はどんどん手を出しましょう。
ちなみにこのJavaScript Rising StarsのサイトはNext.jsでできています。
コマンド幾つか打つだけでローカルサーバが立ち上がるのでとっても楽。
ただ昨年はホットリロード対応してたはずなんだけど今年は手動リロードしないとだめだった。なんでだろう?(調べてない)

Houkago Atelier Toiro ha iizo

02.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

え、意外とみんな知らない!?要素外でクリックしたときにイベントを発火させる方法

皆さんこんにちは!

今サイト制作を行っている際に、ふとドロップダウンメニュー(プルダウンとも呼ぶ)を作っていた時、「要素外でクリックしたときにメニューを閉じたいな~」と思い、1から構築しようとしたけどめちゃだるい!!!

僕がサイト制作を行うときに一番心掛けていることは、「どれだけ楽をして作るか」を日々考えながらやっています。

そんな僕にとって、この機能を1から作るなんて死んでもやりたくもない。。。

Googleで検索したところ、、、

なんとありました!!

パッケージ名は「vue-click-outside」

初めて聞いた

さて、今回はドロップダウンメニューを作りつつ「vue-click-outside」を使っていきたいと思います。

使い方だけ見たいという方は、こちらのサイトからご覧ください。

使い方の例も載っているので、分かりやすいかと。

それでは、順を追って一緒に使い方を見ていきましょう!

パッケージのインストール

npm i vue-click-outside --save-dev

ドロップダウンメニューの作成(リストの作成)

ここからはドロップダウンメニューを作っていきます。

表示したいメニュー項目をmenuListで定義し、isOpendで要素をクリックしたかを判断します。また、menuListのプロパティはお好みで設定してください。

App.vue
<script>
export default{
  data() {
    return {
      isOpend: false,
      menuList: [
        {
          icon: 'user',
          id: 'account?isActive=0',
          labelText: 'アカウント情報'
        },
        {
          icon: 'envelope',
          id: 'email?isActive=1',
          labelText: 'メールアドレス変更'
        },
        {
          icon: 'key',
          id: 'password?isActive=2',
          labelText: 'パスワード変更'
        },
        {
          icon: 'calendar-alt',
          id: 'event?isActive=3',
          labelText: 'イベント'
        },
        {
          icon: 'user-minus',
          id: 'withdrawal?isActive=4',
          labelText: '退会'
        }
      ]
    }
  },
}
</script>

v-forでメニューリストを表示

次は、先ほど作成したオブジェクトを使用し、v-forを使って表示していきます。

また、ドロップダウンメニューを表示したときモーダルウィンドウとして表示するため、Buefyを使っていきます。アイコンもBuefyで表示します。

Buefyの使い方は、こちら以下の記事で詳しく書いているので、興味のある方はぜひご覧ください!

初心者必見!サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介!!
効率的にサイト作り!Buefyでアイコンを表示しよう!!

App.vue
<template>
  <div class="phone-side-menu">
    <div class="drop-down-menu">
      <div class="drop-down-menu-wrapper">
        <b-modal v-model="isOpend">
          <div class="drop-down-menu-list">
            <ul>
              <li v-for="(element, index) in menuList" :key="index">
                <input :id="element.id" name="sideMenuItems" type="radio" />
                <label :for="element.id" class="phone-menu-label">
                  <b-icon pack="fas" :icon="element.icon" size="medium"></b-icon>
                  {{ element.labelText }}
                </label>
              </li>
            </ul>
          </div>
        </b-modal>
      </div>
    </div>
  </div>
</template>
<script>
export default{
  data() {
    return {
      isOpend: false,
      menuList: [
        {
          icon: 'user',
          id: 'account?isActive=0',
          labelText: 'アカウント情報'
        },
        {
          icon: 'envelope',
          id: 'email?isActive=1',
          labelText: 'メールアドレス変更'
        },
        {
          icon: 'key',
          id: 'password?isActive=2',
          labelText: 'パスワード変更'
        },
        {
          icon: 'calendar-alt',
          id: 'event?isActive=3',
          labelText: 'イベント'
        },
        {
          icon: 'user-minus',
          id: 'withdrawal?isActive=4',
          labelText: '退会'
        }
      ]
    }
  },
}
</script>

モーダルウィンドウの表示

次は、クリックされたときにモーダルウィンドウを表示する関数、ボタンを作ります。

App.vue
<template>
  <div class="phone-side-menu">
    <div class="drop-down-menu">
      <div class="drop-down-menu-wrapper">
        <!-- ここから追加  -->
        <b-button type="is-text" @click="toggleMenuList">
            ドロップダウンメニュー
        </b-button>
        <b-modal v-model="isOpend">
          <div class="drop-down-menu-list">
            <ul>
              <li v-for="(element, index) in menuList" :key="index">
                <input :id="element.id" name="sideMenuItems" type="radio" />
                <label :for="element.id" class="phone-menu-label">
                  <b-icon pack="fas" :icon="element.icon" size="medium"></b-icon>
                  {{ element.labelText }}
                </label>
              </li>
            </ul>
          </div>
        </b-modal>
      </div>
    </div>
  </div>
</template>
<script>
export default{
  data() {
    return {
      isOpend: false,
      menuList: [
        {
          icon: 'user',
          id: 'account?isActive=0',
          labelText: 'アカウント情報'
        },
        {
          icon: 'envelope',
          id: 'email?isActive=1',
          labelText: 'メールアドレス変更'
        },
        {
          icon: 'key',
          id: 'password?isActive=2',
          labelText: 'パスワード変更'
        },
        {
          icon: 'calendar-alt',
          id: 'event?isActive=3',
          labelText: 'イベント'
        },
        {
          icon: 'user-minus',
          id: 'withdrawal?isActive=4',
          labelText: '退会'
        }
      ]
    }
  },
  methods: {
    toggleMenuList() {
      this.isOpend = !this.isOpend
    }
}
</script>

ボタンをクリックしたら、toggleMenuListという関数を実行してモーダルウィンドウの表示・非表示を行います。

要素外をクリックしたときの関数を作成

最後に、モーダルウィンドウを表示中に要素外をクリックしたら、モーダルウィンドウを閉じるための関数を作成していきます。

関数名はhideMenuList、先ほどインストールしたパッケージをインストールしscript内でdirectivesを定義、v-click-outsideでイベントの発火

というような仕組みになっております。

App.vue
<template>
  <!-- イベント発火 -->
  <div v-click-outside="hideMenuList" class="phone-side-menu">
    <div class="drop-down-menu">
      <div class="drop-down-menu-wrapper">
        <!-- ここから追加  -->
        <b-button type="is-text" @click="toggleMenuList">
            ドロップダウンメニュー
        </b-button>
        <b-modal v-model="isOpend">
          <div class="drop-down-menu-list">
            <ul>
              <li v-for="(element, index) in menuList" :key="index">
                <input :id="element.id" name="sideMenuItems" type="radio" />
                <label :for="element.id" class="phone-menu-label">
                  <b-icon pack="fas" :icon="element.icon" size="medium"></b-icon>
                  {{ element.labelText }}
                </label>
              </li>
            </ul>
          </div>
        </b-modal>
      </div>
    </div>
  </div>
</template>
<script>
export default{
  // ディレクティブを定義
  directives: {
    ClickOutside
  },
  data() {
    return {
      isOpend: false,
      menuList: [
        {
          icon: 'user',
          id: 'account?isActive=0',
          labelText: 'アカウント情報'
        },
        {
          icon: 'envelope',
          id: 'email?isActive=1',
          labelText: 'メールアドレス変更'
        },
        {
          icon: 'key',
          id: 'password?isActive=2',
          labelText: 'パスワード変更'
        },
        {
          icon: 'calendar-alt',
          id: 'event?isActive=3',
          labelText: 'イベント'
        },
        {
          icon: 'user-minus',
          id: 'withdrawal?isActive=4',
          labelText: '退会'
        }
      ]
    }
  },
  methods: {
    toggleMenuList() {
      this.isOpend = !this.isOpend
    },
    // 関数を定義
    hideMenuList() {
      this.isOpend = false
    }
}
</script>

いかがだったでしょうか?

このようにして、ドロップダウンメニューのモーダルウィンドウを表示、要素外をクリックしたらモーダルウィンドウを閉じるという一連の動作を完成させることができます!

最近、モチベが低下しつつある。。。

それでも僕は頑張ります!

皆さんも一緒に頑張りましょう!!!

以上、「え、意外とみんな知らない!?要素外でクリックしたときにイベントを発火させる方法」でした!

良かったら、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む