20200320のJavaScriptに関する記事は30件です。

Next.jsでCSSとSASS(SCSS)、Bootstrapを使う

Next.jsでCSSとSASS(SCSS)、Bootstrapを使う

next.jsでreactのSPAを作り始めたのですが、css, sass, bootstrapの設定周りですこしつまづいたのでメモ。

Next.jsのプロジェクト作成

プロジェクト作成

yarn craete next-app

これでNext.jsのプロジェクトが作成されます。 yarn dev で起動できます。

CSS, SASSの設定

css, sass(scss)ファイルをimportできるようにします。

まずはパッケージインストールします。

yarn add @zeit/next-css

yarn add @zeit/next-sass node-sass

next.config.jsの作成

プロジェクトのルートに next.config.js ファイルを作成します。これはnext.jsの設定ファイルです。

以下はcssとsassどちらもimportできるような設定です。

next.config.js
const withSass = require('@zeit/next-sass');
const withCSS = require('@zeit/next-css');
module.exports = withCSS(withSass());

Bootstrapの設定

Bootstrapを入れます。

yarn add bootstrap

yarn add react-bootstrap

Bootstrapを適用します。
アプリ全体に適用したいので、グローバルに設定したいです。

まず、/pages/_app.js を作成します。
next.jsでは、全体設定のためのインターフェースとして _app.js で設定できるようになっています。

_app.js でbootstrapをimportすることでアプリ全体に適用していくのです。

/pages/_app.js
import 'bootstrap/dist/css/bootstrap.min.css';

function MyApp ({ Component, pageProps }) {
  return <Component {...pageProps} />
};

export default MyApp;

ページを作ってみる

では正しく設定できているか実際にページを作って確認してみましょう。

/pages/login.js
import {Container, Row, Col, Form, Button} from 'react-bootstrap';

const Login = () => {
  return (
    <Container className='login'>
      <Row>
        <Col className='text-center'>
          <h2>ログイン</h2>
        </Col>
      </Row>
      <Row>
        <Col>
          <Form className='col-6 offset-3'>
            <Form.Group>
              <Form.Control type='text' name='id' />
            </Form.Group>
            <Form.Group>
              <Form.Control type='password' name='password' />
            </Form.Group>
            <Form.Group>
              <Form.Control type='submit' name='submit' value='ログイン' className='btn btn-primary' />
            </Form.Group>
          </Form>
        </Col>
      </Row>
    </Container>
  );
};

export default Login;

確認してみる

起動します

yarn dev

localhost:3000/login にアクセス。

スクリーンショット 2020-03-20 23.55.26.png

わあい

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

particles.jsをさわってみた

particles.jsってなに

一言でいうとパーティクルを実装できるスクリプト!
下のようなものが作れる!
image.png

image.png
Qiitaの記事があんまり見当たらなかったので書いてみました!

とりあえず動かしてみる!

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Particles.js</title>
        <link rel="stylesheet" href="style.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/particles.js/2.0.0/particles.min.js"></script>
    </head>

    <body>
        <div id="particles-js"></div>
        <script src="app.js"></script>
    </body>
</html>
style.css
@charset "UTF-8";
#particles-js {
    width: 100%;
    height: 100%;
    background-color: #000;
}
app.js
particlesJS('particles-js');

はい、これだけ!
とりあえず動かしてみましょう!
image.png
いや、まぁ動いたけどダサいよね。自分の思ってたのとは違うって感じがしますよね。

かっこよくしましょう!(笑)

表示の設定はJavaScriptのObjectを使って書いていきます
基本構造は下のような感じです(長いので折りたたみました)

app.js
app.js
const config = {
  "particles":{
    //--シェイプの設定--
    "number":{
      "value":20, //シェイプの数
      "density":{
        "enable":true, //シェイプの密集度を変更するか否か
        "value_area":200 //シェイプの密集度
      }
    },
    "shape":{
      "type":"circle" //シェイプの形(circle:丸、edge:四角、triangle:三角、polygon:多角形、star:星型、image:画像)
    },
    "color":{
      "value":"#fff" //シェイプの色
    },
    "opacity":{
      "value":0.5 //シェイプの透明度
    },
    "size":{
      "value":3, //シェイプの大きさ
      "random":true, //シェイプの大きさをランダムにするか否か
      "anim":{
        "enable":true, //シェイプの大きさをアニメーションさせるか否か
        "speed":40, //アニメーションのスピード
        "size_min":0.1, //大きさの最小値
        "sync":false //全てのシェイプを同時にアニメーションさせるか否か
      }
    },

    //--線の設定--
    "line_linked":{
      "enable":true, //線を表示するか否か
      "distance":150, //線をつなぐシェイプの間隔
      "color":"#ffffff", //線の色
      "opacity":0.3, //線の透明度
      "width":1 //線の太さ
    },

    //--動きの設定--
    "move":{
      "speed":5, //シェイプの動くスピード
      "straight":false, //個々のシェイプの動きを止めるか否か
      "direction":"none", //エリア全体の動き(none、top、top-right、right、bottom-right、bottom、bottom-left、left、top-leftより選択)
      "out_mode":"out" //エリア外に出たシェイプの動き(out、bounceより選択)
    }
  },


  "interactivity":{
    "detect_on":"canvas",
    "events":{
      //--マウスオーバー時の処理--
      "onhover":{
        "enable":true, //マウスオーバーが有効か否か
        "mode":"repulse" //マウスオーバー時に発動する動き(下記modes内のgrab、repulse、bubbleより選択)
      },
    },

    "modes":{
      //--カーソルとシェイプの間に線が表示される--
      "grab":{
        "distance":400, //カーソルからの反応距離
        "line_linked":{
          "opacity":1 //線の透明度
        }
      },
      //--シェイプがカーソルから逃げる--
      "repulse":{
        "distance":200 //カーソルからの反応距離
      },
      //--シェイプが膨らむ--
      "bubble":{
        "distance":400, //カーソルからの反応距離
        "size":40, //シェイプの膨らむ大きさ
        "opacity":8, //膨らむシェイプの透明度
        "duration":2, //膨らむシェイプの持続時間(onclick時のみ)
        "speed":3 //膨らむシェイプの速度(onclick時のみ)
      },
      //--シェイプが増える--
      "push":{
        "particles_nb":4 //増えるシェイプの数
      },
      //--シェイプが減る--
      "remove":{
        "particles_nb":2 //減るシェイプの数
      }
    }
  },


  "retina_detect":true, //Retina Displayを対応するか否か
  "resize":true //canvasのサイズ変更にわせて拡大縮小するか否か

}

particlesJS('particles-js', config);


このコードを書くと、最初の一枚目の画像のようになります
かなりかっこよくなりました!

少し自分好みにアレンジ

個人的にシェイプがカーソルから逃げるのと、クリックしたときにシェイプが増えるのが嫌だったのでそれを変更してみます
編集する部分はapp.jsの"interactivity"内です

app.js
"interactivity":{
      "detect_on":"canvas",
      "events":{
        "onhover":{
          "enable":false
        },
      },

      "modes":{
        "push":false
      }
},

こう修正することで、カーソルを動かしたりクリックしたりしても何も起こらなくなりました。

最後に

この記事を最後までお読みいただきありがとうございました。
Qiitaの記事が見つからなかったので書かせていただきました。
particles.jsも少しさわっただけなので間違いなどもあるかもしれません。間違いがありましたらコメントの方までお願いします。

参考記事

https://hackable.jp/posts/1401

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

Electronでカレンダーを作る③

前回からの続き

前回はカレンダーの中身が動的に表示されるようにした。

表示する月を変えられるようにする

先月、来月に切り替えられるようにしたい。

先月と来月切り替えボタンを配置する。

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ElectronCalendar</title>
  <script text="text/javascript" src="./js/index.js"></script>
  <link rel="stylesheet" href="./css/index.css">
</head>
<body>
  <div class="calendar-wrapper">
    <div class="preMonthButton">
      <button type="button" id="preMonth"><先月</button>
    </div>
    <div class="nextMonthButton">
      <button type="button" id="nextMonth">来月></button>
    </div>
    <table id="table" class="calendar">
      <caption id="caption"></caption>
      <thead>
        <tr>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr id="row1">
          <td id="cell1"></td>
          <td id="cell2"></td>
          <td id="cell3"></td>
          <td id="cell4"></td>
          <td id="cell5"></td>
          <td id="cell6"></td>
          <td id="cell7"></td>
        </tr>
        <tr id="row2">
          <td id="cell8"></td>
          <td id="cell9"></td>
          ・・・略
          <td id="cell33"></td>
          <td id="cell34"></td>
          <td id="cell35"></td>
        </tr>
        <tr id="row6"> ※
          <td id="cell36"></td>
          <td id="cell37"></td>
          <td id="cell38"></td>
          <td id="cell39"></td>
          <td id="cell40"></td>
          <td id="cell41"></td>
          <td id="cell42"></td>
        </tr>
      </tbody>
    </table>
  </div>
</body>
</html>

※カレンダーに6行目を追加している。

index.js
'use strict';

const moment = require("moment");

window.onload = function() {
    createCallendar(moment());

    //先月ボタン押下時
    document.getElementById('preMonth').onclick = function() {
        createCallendar(moment(this.value));
    }

    //来月ボタン押下時
    document.getElementById('nextMonth').onclick = function() {
        createCallendar(moment(this.value));
    }
}

/**
 * カレンダーを表示する。
 * @param momentオブジェクト 
 */
const createCallendar = function(localMoment) {
    //captionを表示
    document.getElementById('caption').innerText = localMoment.format("YYYY年MM月");

    //カレンダー初期化
    clearCallendar();

    //当月の日数を取得
    const daysOfMonth = localMoment.daysInMonth();

    //月初の曜日を取得(index.htmlと合わせるために+1する)
    const firstDayOfManth = localMoment.startOf('month').day() + 1;

    //カレンダーの各セルに日付を表示させる
    let cellIndex = 0;
    for(let i = 1; i < daysOfMonth + 1; i++) {
        if(i === 1) {
            cellIndex += firstDayOfManth;
        } else {
            cellIndex++;
        }
        document.getElementById("cell" + cellIndex).innerText = i;
    }

    //6行目の第1セルが空白なら6行目自体を非表示にする。
    if(document.getElementById("cell36").innerText === "") {
        document.getElementById('row6').style.visibility = "hidden";
    }

    //先月
    document.getElementById('preMonth').value = localMoment.add(-1,'month').format("YYYY-MM");
    //来月(先月のmomentオブジェクトとなっているので+2ヶ月)
    document.getElementById('nextMonth').value = localMoment.add(2,'month').format("YYYY-MM");
}

/**
 * カレンダーを初期化する
 */
const clearCallendar = function() {
    //6行目を表示させておく
    if(document.getElementById('row6').style.visibility === "hidden") {
        document.getElementById('row6').style.visibility = "visible";
    }
    for(let i = 1; i < 43; i++) {
        document.getElementById("cell" + i).innerText = "";
    }
}

6行目は表示しない場合がある(表示しない場合の方が多い)ので、表示しない場合は隠す。

$ electron .

初期表示
image.png

6行目がある場合
image.png

とりあえず出来たけどボタンがダサい。

TODO

・月を切り替えた場合、同一html内の表示内容の切り替えとなっているため、いちいち初期化が必要で微妙。新しく画面を読み込むようにしたい。
・ボタンの見た目をナウい感じにしたい。

あとがき

Electronの記事じゃ無く、HTMLとJSの記事みたいになってる・・・。

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

Vue.jsでApexchartsを使ってグラフ描画を行う

Vue.jsでのグラフ描画ライブラリの選定について

Apexcharts, vue-apexcharts を使って、Vue.jsで各種グラフの描画をおこなってみます。
当初、vue-chartjs を使っていたものの、柔軟性に欠ける、見た目があまり良くない、コードが複雑になりがち等の理由で乗り換えました。

描画結果

今回、折れ線グラフ円グラフ棒グラフ (横型) の描画を行ってみました。結果は以下。

linechart.png
piechart.png
barchart.png

良い点

  • 色々なカスタマイズ項目があり、細かい要求に対して融通が利きやすい
  • 今回のような単純な例の他、人口ピラミッドヒートマップ地図ローソク足等色々なグラフの描画が可能
  • マウスカーソルをグラフ上にあてると特定の結果をハイライト表示できる
  • 折れ線グラフはブラウザ上で横軸のズームイン、ズームアウトができる
  • 画像としてダウンロード可能
  • トランスパイルすればIE11にも対応

参考: 公式のデモ

インストール

npm install vue-apexcharts

折れ線グラフのコードサンプル

サンプルは色々カスタマイズオプションを設定しているため少し長めですが、大部分は省略可能です。

~略~
<!-- 少し色みがかったグラフにするため今回は type="area"にした。通常は type="line" -->
<apexchart
  type="area"
  height="400"
  :options="chart.options"
  :series="chart.series"
></apexchart>
~略~
<script>
import VueApexCharts from 'vue-apexcharts'
export default {
  components: {
    apexchart: VueApexCharts,
  },

  data: () => ({
    chart: {
      options: {
        chart: {
          zoom: {
            type: 'x', // X軸のズームを可能にする
            enabled: true,
            autoScaleYaxis: true,
          },
          toolbar: {
            autoSelected: 'zoom',
          },
        },
        plotOption: {
          line: {
            curve: 'smooth', // 滑らかなラインにする
          },
        },
        xaxis: {
          type: 'datetime', // category, datetime, numericのいずれか。時系列チャートならズームに強いdatetimeがおすすめ
          title: {
            text: '日付',
            offsetY: 10,
          },
          labels: {
            format: 'yy/MM/dd',
          },
          categories: [ // X軸の値
            new Date('2020/03/01 09:00:00'),
            new Date('2020/03/02 09:00:00'),
            new Date('2020/03/03 09:00:00'),
            new Date('2020/03/04 09:00:00'),
          ],
        },
        yaxis: {
          title: {
            text: '回答数',
          },
        },
        title: {
          text: '回答数推移',
          align: 'center',
        },
        tooltip: {
          x: {
            format: 'yy/MM/dd',
          },
        },
        fill: {
          type: 'gradient', // グラデーションをつける
          gradient: {
            type: 'vertical',
            shadeIntensity: 1,
            inverseColors: false,
            opacityFrom: 0.5,
            opacityTo: 0,
            stops: [0, 90, 100],
          },
        },
      },
      series: [
        {
          name: '回答数',
          data: [10, 3, 8, 2], // Y軸の値
        },
      ],
    },
  }),
}
</script>

時差が考慮されていないのか、9時間進んだ値を入れないとうまく日本時間に合った表示をしてくれませんでした。(私の調査不足かもしれません)

円グラフのコードサンプル

~略~
<apexchart
  type="pie"
  height="400"
  :options="chart.options"
  :series="chart.series"
></apexchart>
~略~
<script>
import VueApexCharts from 'vue-apexcharts'
export default {
  components: {
    apexchart: VueApexCharts,
  },

  data: () => ({
    chart: {
      options: {
        labels: ['20代', '30代', '40代', '50代'],
        title: {
          text: 'あなたの年齢を教えてください。',
          align: 'center',
        },
      },
      series: [5, 8, 3, 2],
    },
  }),
}
</script>

棒グラフのコードサンプル

~略~
<apexchart
  type="bar"
  height="400"
  :options="chart.options"
  :series="chart.series"
></apexchart>
~略~
<script>
import VueApexCharts from 'vue-apexcharts'
export default {
  components: {
    apexchart: VueApexCharts,
  },

  data: () => ({
    chart: {
      options: {
        plotOptions: {
          bar: {
            horizontal: true, // 横型のグラフにする場合
          },
        },
        title: {
          text: '使用ブラウザー',
          align: 'center',
        },
        xaxis: {
          categories: ['Internet Explorer', 'Google Chrome', 'Firefox', 'Safari'],
        },
      },
      series: [
        {
          name: '件数',
          data: [3, 5, 2, 9],
        },
      ],
    },
  }),
}
</script>

グラフのカスタマイズ

オプション項目の一覧はAPEXCHARTS - Optionsを参照。
ただし、オプションを見ただけではイマイチ具体的な設定方法が分からなかったり、
ところどころVue.jsに合わせて勘でアレンジする必要がありました。
VUE CHARTS DEMOSにいくつか実コードが載っているケースもあり、参考になりました。

参考: 棒グラフに件数とパーセンテージを両方表示する例

options: {
  // ~~略~~
  dataLabels: {
    enabled: true,
    formatter: (val, opt) => {
      return (
        val + '件 (' + Math.round((100 * val) / total) + '%)' // total変数に総件数が入っているものとします
      )
    },
  },
  tooltip: {
    y: {
      formatter: val => {
        return (
          val + '件 (' + Math.round((100 * val) / data.total) + '%)'
        )
      },
    },
  // ~~略~~
},

IE11に対応させる

  • promise-polyfill
  • classlist.js
  • findIndex - timeline/rangebarグラフを使う場合必要
  • canvg - PNGでのダウンロードに対応させる場合必要

が必要とのこと。BabelのPolyfillで概ねいけましたが、classlistは別途Polyfillが必要でした。

npm install classlist-polyfill

src/main.js

import 'classlist-polyfill'

Babelについては他の記事をご参照ください。

CSSの微調整

Apexchartsのツールバーのz-indexがイケておらず、私の環境ではCSSの調整が必要でした。(場合によると思われます)

.apexcharts-toolbar {
  z-index: 4 !important;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Fastly] Server-Sent Events (SSE) を Fastly で最強にスケールさせる

はじめに

この記事の内容の多くは、Andrew Betts が書いた Fastly のブログ Server-sent events with Fastly に基づいています。

Server-Sent Events とは?

Server-Sent Events (以降 SSE と呼ぶ) は、いわゆる "リアルタイム" イベントを サーバ -> クライアント に向けてプッシュできる技術です。
リアルタイムイベントというと、Websockets をまず頭に浮かべる人が多いかと思います。確かに Websockets は SSE に比べてより柔軟で強力な機能を持っていますが、実装の複雑さは SSE とは対象的に非常に高コストになりがちです。また大きな違いとして、Websockets では サーバ <-> クライアント 間での双方向のリアルタイム通信が可能ですが、SSE はあくまでも サーバ -> クライアント (一方向) のデータのプッシュのみをサポートしており、Websockets とは想定される利用シーンが少々異なってきます。

例えば下記のようなウェブページには SSE が非常に向いていると言えます。

  • フライト情報をリアルタイムに更新
  • 株価情報
  • ニュースサイトのアラート
  • スコアボード (選挙速報や競技イベント)

上記はいずれもサーバからクライアントに対して一方的にデータをプッシュできればいいため、わざわざ大掛かりな Websockets を導入せずとも SSE で低コストに実現することができます。しかし、SSE はあまり普及していないようです。なぜでしょう。

SSE の基本的な仕組み

SSE はシンプルに既存の HTTP プロトコルの上で通常のリクエストとして動作します。特殊なプロトコルは何も使用していません。
サーバはクライアントからリクエストが来ると、テキストベースのシンプルなストリームデータをクライアントに送信します。この時サーバは、MIME タイプ text/event-stream としてデータを送信する必要があります。
クライアントはデータを受信するとコネクションを閉じず保持し続け、サーバからのデータを待ちます。サーバはこの open 状態にあるコネクションの上で新しい追加のデータをストリームにプッシュし続ける事ができます。

クライアントが SSE のイベントを受信するには、EventSource インターフェイスを利用します。

var evtSource = new EventSource("stream");

インスタンスの作成後、メッセージの受け取りをトリガーに任意の処理を実行できます。

evtSource.onmessage = function(e) {
  var newElement = document.createElement("li");
  var eventList = document.getElementById('list');

  newElement.innerHTML = "message: " + e.data;
  eventList.appendChild(newElement);
}

とりあえず SSE が動いてるか手早く確かめたい場合は、EventSource でインスタンスの作成だけ行い、例えば Chrome の developer tools で下記のように EventStream タブからデータ受信の様子を確認することができます (または当該の URL に直接アクセスすると、データがそのままブラウザに表示され、更新されていく様子が見ることができます)。

Screen Shot 2020-03-20 at 21.15.11.png

非常に簡単であることが分かると思います。
あとは送られてくるデータをクライアント側でどう処理するかを考えればよいだけです。

SSE を使う上での課題

ブラウザのサポート状況

これはあまり問題にはならないでしょう。IE を除く主要なブラウザでサポートされています。

サーバ側のスケーラビリティ

前述したように、クライアントとサーバは SSE のコネクションを一度開くとコネクションを張りっぱなしにします。
これはつまり、大量のクライアントからのアクセスが発生する場合にはサーバが大量のアイドル状態の TCP 接続を開いたままにしておかなければならない事を意味し、サーバまたはネットワーク構成上において何らかの最適化が必要になる可能性があります。

アプリケーションのスケーラビリティ

接続中の複数のクライアントに対して同じデータを同時に提供する必要があるため、接続がリクエスト毎に isolation されるような場合には少々チャレンジングになることがあるかもしれません。

Fastly で全て解決する

前述したスケーラビリティの問題は、Fastly を使うことで劇的に改善することができます。
これを実現するためには、Fastly の下記のような機能が鍵になってきます。

リクエスト共有 (Request collapsing)

リクエスト共有 (Request Collapsing) は、単一の Fastly データセンター内でキャッシュミスが同時に発生する際に、オリジンサーバへのリクエストを 1 つに束ねることを意味します。つまり、キャッシュされていない URL コンテンツに対して複数のクライアントから同時にアクセスが発生したとしても、1番最初に到達したリクエストだけがオリジンへリクエストを送信し、残りのリクエストはこの最初のリクエストのレスポンスがオリジンから返されるまでの間は Fastly 上で待機状態になります。オリジンからレスポンスが返されると、一斉に待機状態になっている接続に対してコンテンツを返します。
これは通常 cache stampede 問題を回避するための重要な機能ですが、SSE の場合にはつまり Fastly <-> オリジン 間の単一のストリームコネクションを複数のユーザーに "fan out" させることができます。なので、オリジンのサーバは非常に低い load で稼働を続けることができ、単に単一ストリームにデータをプッシュすることで、Fastly 上で待機状態になっている複数の接続に対して一斉にデータをプッシュすることができます。

Streaming Miss

通常クライアントが Fastly にリクエストを送り、そのコンテンツがキャッシュされていない場合、Fastly はオリジンサーバにリクエストを送ります。この時、クライアントは Fastly がコンテンツ全体をオリジンから取得し終わるまで待機状態となり、レスポンスを受け取ることができません。Streaming Miss を有効にすることでこの挙動を変更できます。有効にすると、オリジンからの最初のレスポンス (チャンク) が届いた時点ですぐにそれをクライアントにも配信することができます。SSE では当然ストリーム上で任意の間隔でデータのチャンクが送信されるため、オリジンサーバから最初のチャンクが届いた時点ですぐにそれをブラウザに届けることが重要です。

オリジンシールド (Shielding)

Fastly のオリジンシールドを使うと、オリジンに対するシールドとして POP (Point of Presence) をいずれか 1 つ指定できます。シールド POP の指定後は、当該オリジンへのリクエストはすべてシールド POP 経由となるため、キャッシュのヒット率が高くなります。シールドに指定されていない POP がキャッシュを持っていない場合、その POP はオリジンではなくシールド POP へリクエストを行います (ただしシールド POP がダウンしていないことが条件です)。

つまり、デフォルトでは、例えばオリジンサーバが日本にあるとして、アメリカからアクセスするクライアントはアメリカの POP からオリジンに向けてコネクションが、ヨーロッパからアクセスするクライアントはヨーロッパの POP からオリジンに向けてコネクションが張られます。キャッシュオブジェクトは POP 単位で管理されており、全ての POP で共有されている訳ではありません。

そこで、例えば東京の POP をオリジンシールドとして設定するとします。すると、アメリカからのクライアントはアメリカ POP -> 東京の POP に対して接続を、ヨーロッパからのクライアントはヨーロッパ POP -> 東京の POP へ必ずアクセスが中継されることになり、東京 POP にさえキャッシュオブジェクトが存在していれば HIT となり、オリジンへの接続を減らすことができます。

これはまたリクエスト共有とも関連していて、オリジンシールドが有効でない場合は各 POP 単位でリクエスト共有が行われ、例えばアメリカ POP -> オリジン、ヨーロッパ POP -> オリジン、へそれぞれコネクションが1本発生します。オリジンシールドが有効になっていると、更に間に 東京 POP が仲介しているので、同じ URL に対しての世界中からのリクエストに対してリクエスト共有が働き、東京 POP -> オリジンへの1本のリクエストだけの発生に抑えることができます。

Screen Shot 2020-03-20 at 22.39.01.png

HTTP/2

HTTP/2 では1つの TCP コネクション上で複数の HTTP リクエストを多重並列化することができます。これは HTTP/1.1 ではできなかったことです。SSE の場合を考えると、ストリームのコネクションで常時1つのコネクションが張りっぱなしになるため、例えば多くのブラウザで制限されている、1つのドメインに対しての最大同時リクエスト数制限(主に6本であることが多い)、このうち1つを常に消費してしまうことになります。Fastly 上で HTTP/2 を利用することでこの問題を回避できます。

Fastly で SSE を実装する上での注意点

上記で説明した機能を使うに辺り、注意しなければならない点がいくつかあります。

サーバは定期的にストリームコネクションを切断すべし (close interval)

例えば、サーバはストリームのコネクションを open してから XX秒後 (例: 30秒後) にコネクションを強制的に閉じる、といった処理をするべきです。なぜなら、コネクションを明示的に切断しない限り Fastly <-> オリジン 間のコネクションはずっと張りっぱなしになってしまいます。コネクションが切断されないとキャッシュオブジェクトへの書き込みが終わらず、新規に SSE 接続してきたクライアントが初回に受け取るデータ量が増え続けてしまいます。また、Fastly <-> オリジンの最大同時接続数制限に達する可能性があります。

サーバは cacheable なストリーム応答を返すべし

先に説明したように、リクエスト共有の機能は非常に強力で、SSE をスケールさせる上で最も重要なポイントです。
ただしこのリクエスト共有を正しく使うためには、オリジンサーバは必ずストリームのレスポンスを cacheable (キャッシュ可能) なオブジェクトとして返す必要があります。つまり、Cache-Control ヘッダーに任意の TTL を正しく設定する必要があります。

max-age == ストリームの定期切断の interval とせよ

ちょっと分かりにくいのですが、前述の2つの注意点はそれぞれ密接に関係していて、例えばオリジンサーバがストリームコネクションを30秒毎に切断するように構成した場合、Cache-Control レスポンスヘッダーに設定するべき TTL max-age も同じ interval に設定する必要があります。なので、この場合には Cache-control: public, max-age=30 とするのが正しいです。または、時刻同期のズレの可能性を考慮して、Cache-control: public, max-age=29 (TTL を1秒だけ短く) としてもよいでしょう。

こうすることで、オリジンサーバは30秒毎にストリームを切断し、TTL も30秒に設定されているため同時に expire (期限切れ) となり、SSE ストリームの切断を検知したブラウザは自動的にコネクションの再接続を試みるため、新たなコネクションが張られます。

例えば Fastly <-> オリジンサーバ間でストリームを開始して10秒後に新たに接続してきたクライアントは、それまで Fastly がキャッシュしていた10秒間のストリームデータを接続時に一気に取得し、残りの20秒間はリアルタイムにプッシュを受け取ります。

Streaming Miss を有効にする

vcl_fetch
# サーバからのレスポンスが `Content-Type: text/event-stream` (SSE) の場合には Streaming Miss を有効にする
if (beresp.http.Content-Type ~ "^text/event-stream") {
  set beresp.do_stream = true;
}

ブラウザキャッシュを無効にする

vcl_deliver
if (fastly.ff.visits_this_service == 0) {
  set resp.http.Cache-Control = "private, no-store";
}

オリジンサーバは常に ping (空データ) メッセージを一定間隔でストリームに送信するべし

これはちょっとした落とし穴ですが、データのプッシュがどのタイミングで、どのくらいの間隔で発生するのかは完全にオリジンサーバ側のアプリケーションの実装次第です。例えばデータの更新が激しい場合には毎秒データのプッシュが発生するかもしれませんし、更新がない場合には数十秒間プッシュが行われないかもしれません。Fastly を使う上ではここが問題となります。Fastly ではオリジン毎に各種 timeout のしきい値を設定できます。このうち、between byte timeout の値が鍵となります。

これはデフォルトでは 10000 milliseconds (10秒) となっています。つまり、オリジンとストリームのコネクションを張った後、10秒以上データが送信されてこない場合、タイムアウトとしてエラーになってしまい、正しくキャッシュオブジェクトが作成されません。

このため、必ずオリジンサーバは一定間隔で (between byte timeout の設定値よりも短い間隔で) ping メッセージをストリーム上で送信するように構成してください。keep-alive 目的のなようなものです。

参考実装として、Andrew Betts が公開している sse-pubsub npm パッケージを紹介しておきます。

まとめ

説明してきたポイントを正しく設定することで、Fastly を使って SSE を最強にスケールさせることができます。是非チャレンジしてみてはいかがでしょうか。

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

魔法JS☆あれい 第4話「fillも、spliceも、copyWithinもあるんだよ」

登場人物

丹生(にゅう)あれい
魔法少女「魔法(マジカル)JS(女子小学生)☆あれい」として活動中。

イテレー太
正体不明の魔法生物。

fill()

イテレー太「さて、魔法生物である僕が魔法世界から召喚したデータを、魔法少女であるあれいの配列魔法を使って、敵の弱点に合わせた値に変換してreturnする事でダメージを与えて敵を倒す、という設定でやってる戦闘の真っ最中だよ!」
あれい「どれだけ説明的なんだよこの駄犬」
イ「さて、今回僕が魔法世界から召喚したデータは……ああっ! しまった!」
あ「どうした駄犬」
イ「うっかりして、女子小学生に見せたら確実にお縄になるデータを召喚しちゃったよ!」

items = ['', '', '', ''];

あ「何やらかしてんだよポンコツ」
イ「どうしよう! 全国の健全な青少年に深刻な悪影響を与えちゃうよ!」
あ「まずは目の前の子供への悪影響を心配しろよ」
イ「お願い! あれいの配列魔法で、当たり障りのないデータに変換してよ!」
あ「女子小学生に尻拭いさせるんじゃねえ」
イ「頼むよ! 正体不明の魔法生物なのに警察のご厄介になっちゃうよ!」
あ「面倒くせえなあ……」

return items.fill('');
// ['■', '■', '■', '■']

イ「良かった! 女子小学生に見せられないデータが全部黒塗りになったよ! 助かった!」
あ「捕まったら目の部分を黒塗りしてやるよ」

解説

fill() メソッドは、配列中の開始位置から終了位置までの要素を固定値で設定します。その際、終了位置は含まれません。

([MDN]https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fillより)

「fill」とは「満たす」「埋める」というような意味です。
このメソッドは、「値、開始位置、終了位置」という3つの引数を指定できます。1番目の引数で指定した値で、配列の要素を満たすように置き換えます。また、開始位置を指定した場合は開始位置から配列の末尾まで、終了位置も指定した場合は開始位置から終了位置までを置き換えます。
いずれの場合も、配列の要素数が変化することはありません。

splice()

イ「さあ、気を取り直して、次の敵だよ!」
あ「まだ何とも戦ってねえよ」
イ「今回僕が魔法世界から召喚したデータは、これだ!」

items = ['', '', '', ''];

イ「そして、今回の敵の弱点は、『2番目の要素』だよ! さあ、あれい、2番目の要素をreturnして……あああっ!!」
あ「今度は何だよ」
イ「大変だ! 2番目の要素を取り除いたら、とても卑猥な言葉が残っちゃうよ!」
あ「お前絶対ワザとだろ」
イ「頼むよあれい! 取り除いた後の要素を、何か健全な言葉に置き換えておいてよ!」
あ「面倒くせえなあ……」

return items.splice(1, 1, "");
// ["ゃ"]
console.log(items);
// ["ち", "○", "ん", "こ"]

イ「なんだか、余計にイヤらしくなった気がするよ」
あ「うるせえよ」

解説

splice() メソッドは、 (in place で) 既存の要素を取り除いたり、置き換えたり、新しい要素を追加したりすることで、配列の内容を変更します。

MDNより)

「splice」とは、「継ぎ合わせる」「接合する」というような意味があるそうです。配列の要素を足したり引いたりして、新しい配列として継ぎ合わせる……といったイメージでしょうか。なんだか盆栽みたいです。
このメソッドは「開始位置、取り除く要素の数、追加する要素1、追加する要素2…」という引数を指定できます。この引数によって、要素の数を変動させることができます。
また、戻り値は「取り除かれた要素」になります。

copyWithin()

イ「さて、数々の試練を乗り越えたところで、今日最後の敵だよ!」
あ「全部お前が自分で蒔いた種だけどな」
イ「今回僕が魔法世界から召喚したデータは、これだ!」

items = ['', '', '', ''];

あ「すでに嫌な予感しかしないぞ」
イ「さあ、あれい、1番目と2番目の要素を、3番目と4番目の要素に上書きして、敵に見せつけてやってよ!」
あ「お前ついに本性フルオープンだな。そろそろ利用規約の第8条に抵触するぞ」
イ「さ、さあ、あれい! は、早く!!」
あ「何興奮してんだよド変態が」

return items.copyWithin(0, 2, 4);
// ["あ", "つ", "あ", "つ"]

イ「あ、あれ? 注文と違うよ? 熱々の鍋が召喚されたよ?」
あ「これはお前にぶつけるからいいんだよ」
イ「ギャーーー!!!」

解説

copyWithin() メソッドは、サイズを変更せずに、配列の一部を同じ配列内の別の場所にシャローコピーして返します。

MDNより)

「within」とは「…の範囲内で」「……を越えずに」という意味です。その名の通り、配列内の要素を、要素の数が変わらない範囲でコピー・ペーストし、変更後の配列を返すメソッドです。
このメソッドは、「ペーストの開始位置、コピーの開始位置、コピーの終了位置」という引数を指定できます。コピーの終了位置を指定しない場合はコピーの開始位置から末尾までの要素、コピーの開始位置も指定しなかった場合はすべての要素がコピーされます。ペーストされた際、配列の要素数を超えた分は切り捨てられます。


これにて、第1部「ミューテーター・メソッド編」完結です。次回からは、第2部「アクセサ・メソッド編」がスタートします。
さて、魔法JS☆あれいは、魔法(JavaScript)の力でこの世界を守ることが出来るのか!? 次回に続く!

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

【ラジオボタンより簡単!】jQueryで表示・非表示を実装する方法

CSSでうまく行くと思っていたのに...

最初はラジオボタンにdisplay:noneで
コーディングを行っていのですがタブがズレるズレる...
下に行ったかと思えば次は上に行くし、ため息ばかり...

CSSでは歯が立たず『jQuery』で実装を行うことに。

jQueryの方が簡単でした!

mypage.js
$(function(){
  $('.mypage--list--info').click(function () {
    $('.tab--list--info').show();
    $('.info-go-list').show();
    $('.tab--list--todo').hide();
    $('.todo-go-list').hide();
  });

  $('.mypage--list--todo').click(function () {
    $('.tab--list--todo').show();
    $('.todo-go-list').show();
    $('.tab--list--info').hide();
    $('.info-go-list').hide();
  });

  $('.goods-purchase--transaction').click(function () {
    $('.tab-list--transaction').show();
    $('.transaction-go-list').show();
    $('.tab-list--post').hide();
    $('.post-go-list').hide();
  });

  $('.goods-purchase--post').click(function () {
    $('.tab-list--post').show();
    $('.post-go-list').show();
    $('.tab-list--transaction').hide();
    $('.transaction-go-list').hide();
  });
})

うまく表示されました!

481c5c10c2dce6e766a2335c50fddb7c.png

jQueryでのコーティングは単調な繰り返しが多く、簡単に記載することができました。
CSSの方で実装頑張ってた自分がバカだった...1週間無駄にした...

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

HTMLのtableをソートする方法

概要

  • Tableをソートするライブラリを書きました

    • 外部ライブラリなどは不要
    • シンプルなので素のHTMLやBootstrapなど特にフレームワーク縛りなく使える
    • ソートのみが欲しかったので容量小さめ
  • ソースコードはこちらで公開しています

デモ

See the Pen sortable-table (with Bootstrap4) by Tom Misawa (@riversun) on CodePen.

使い方

TableのHTML

  • sortable-tableクラスをもった要素以下にtable theadを置く
  • th要素にdata-id属性でデータとひもづけるidを指定する
  • ソートしたい列にはsortable属性をつける

以下のようになる。

ソートできるテーブルのHTML
<div class="sortable-table">
    <table id="my-table1">
        <thead>
        <tr>
            <th data-id="id" data-header>
                <div>#</div>
            </th>
            <th data-id="name" sortable>
                name
            </th>
            <th data-id="price" sortable>
                price($)
            </th>
            <th data-id="weight" sortable>
                weight(carat)
            </th>
        </tr>
        </thead>
    </table>
</div>

テーブルにデータを入れる

  • 以下のようにテーブルに表示させたいデータを行ごとに配列で指定する
  • 要素名とさきほどHTMLで定義したdata-id属性が紐付く
 const data = [
    {
      id: 0,
      name: 'Diamond',
      weight: 1.0,
      price: 9000,
    },
    {
      id: 1,
      name: 'Amethyst',
      weight: 3.0,
      price: 200,
    },
    {
      id: 2,
      name: 'Emerald',
      weight: 2.5,
      price: 2500,
    },
    {
      id: 3,
      name: 'Ruby',
      weight: 2.0,
      price: 2000,
    },
  ];

  const sortableTable = new SortableTable();
  // table要素を指定する
  sortableTable.setTable(document.querySelector('#my-table1'));
  // テーブルに表示したいデータを指定する
  sortableTable.setData(data);
  • setTableでソートできるようにしたいtable要素を指定する
  • setDataでテーブルに表示したいデータを指定する

See the Pen sortable-table (with plain HTML) by Tom Misawa (@riversun) on CodePen.

ソートした結果を受け取り処理する

ソート結果はgetDataで取得できる。
setDataで元のデータを指定するが、ソートしても元のデータは変更しないようにしているので、ソート済のデータを取得したい場合はgetDataか後述のイベントコールバックから取得する。

const sortedData=sortableTable.getData();

ソートイベントを受け取る

テーブルのヘッダ部分がクリックされ、ソートが実行されるとソートイベントが発火する
以下のようにイベントリスナーを登録するとソートされたときイベントがコールバックされる。

  sortableTable.events()
    .on('sort', (event) => {
      console.log(`[SortableTable#onSort]
      event.colId=${event.colId}
      event.sortDir=${event.sortDir}
      event.data=\n${JSON.stringify(event.data)}`);
    });
  • event.colIdには列のid(=data-id)が格納される
  • event.sortDirにはソート方向(=asc or desc)
  • event.dataにはソート済のデータが格納される

ソートをコードから実行する

ユーザーのクリックではなく、コードからソートを(強制的に)実行する方法は以下の通り

ソートをコードから実行する
sortableTable.sort('price', 'asc');

sortableTable.sort([id], [sortDir]);のように指定する。

sortDirにソート方向を指定する
- asc・・・昇順ソート。昇順とは 1,2,3,4 のようになる
- desc・・・降順ソート。降順とは4,3,2,1 のようになる
- toggle・・・現在のソート方向を反転する。

ソートを初期状態に戻す

最初にデータを指定したときの状態に戻すときはresetData

初期状態に戻す
sortableTable.resetData();

ソートをコードから実行するデモ

https://riversun.github.io/sortable-table/index_with_bootstrap.html

まとめ

  • 拙作のソートライブラリについてお読みいただきありがとうございました
  • 何かのお役にたてますと幸いです

ソースコードについて

npm install @riversun/sortable-table
  • CDNからも利用可能です
<script src="https://cdn.jsdelivr.net/npm/@riversun/sortable-table@1.0.0/lib/sortable-table.js"></script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

consoleのいろんな使い方

Consoleオブジェクトにはいろんなメソッドが提供されています。

console.log(console);

console {debug: ƒ, error: ƒ, info: ƒ, log: ƒ, warn: ƒ, …}
memory: (...)
debug: ƒ debug()
error: ƒ error()
info: ƒ info()
log: ƒ log()
warn: ƒ warn()
dir: ƒ dir()
dirxml: ƒ dirxml()
table: ƒ table()
trace: ƒ trace()
group: ƒ group()
groupCollapsed: ƒ groupCollapsed()
groupEnd: ƒ groupEnd()
clear: ƒ clear()
count: ƒ count()
countReset: ƒ countReset()
assert: ƒ assert()
profile: ƒ profile()
profileEnd: ƒ profileEnd()
time: ƒ time()
timeLog: ƒ timeLog()
timeEnd: ƒ timeEnd()
timeStamp: ƒ timeStamp()
context: ƒ context()
Symbol(Symbol.toStringTag): "Object"
get memory: ƒ ()
set memory: ƒ ()
__proto__: Object

これだけあるにも関わらず、 log() 以外はどうも影が薄いように感じています。
なので、 console に隠された(?)メソッドをいくつかピックアップしてみました。

目的

  • console のいろんなメソッドの使い方を把握する

assert()

第1引数の値がfalseならエラーを出力する。

const method = (num) => {
    console.assert(num > 0 , num + ' is NG.');
}

method(1);
method(0);
method(-1);

image27.png

clear()

コンソールをクリアする。

console.log('hoge');
console.log('hoge');
console.clear();
console.log('fuga');
console.log('fuga');

image28.png

count()

呼び出された回数を出力する。

const method = () => {
    console.count('counter');
}

method();
method();
method();

image29.png

error()

エラーを出力する。

console.error('error');

image26.png

group() / groupEnd()

インデントを開始する。 / インデントを終了する。

console.group('Indent1');
console.log('hoge');
console.log('hoge');
console.group('Indent2');
console.log('fuga');
console.log('fuga');
console.groupEnd();
console.log('hoge');
console.log('hoge');
console.groupEnd();

image30.png

table()

表形式でログを出力する。

const ary = [100,200,300,400];
const obj = {
    'a':100 ,
    "b":200 ,
    "c":300 ,
    'd':400
};
console.table(ary);
console.table(obj);

image31.png

warn()

警告を出力する。

console.warn('warn');

image25.png

まとめ

いかがでしょうか?

Consoleオブジェクトはログを出力するだけの存在ではないことがわかります。
table()group() なんかは使い方次第では log() よりも便利そうです。

console を使いこなせるようにお役に立てれば幸いです。

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

高階関数を書いたら、中級者になれた気がした。

とあるWeb制作会社

社長「おーい、やめ太郎くん」

ワイ「何でっか?」

社長「お仕事を持ってきたで」
社長「今日は↓こんな関数を作ってくれ」

  • 引数として受け取ったHTML要素の高さを100ピクセルにする

社長「関数の名前はsetHeightで頼むわ」
社長「使うときのイメージとしては↓こんな感じや」

// boxと言うIDを持った要素を取得。
const box = document.getElementById("box");

// 関数を呼び出して高さを設定。
setHeight(box);

ワイ「ほうほう」
ワイ「何に使うのかよく分かりまへんけど、簡単ですわ!」
ワイ「お任せくださいやで!」

さっそく作業開始

ワイ「引数としてHTML要素を受け取って」
ワイ「その要素の高さを100ピクセルにするんやから・・・」

const setHeight = element => {
    element.style.height = '100px';
};

ワイ「↑こうやな!」

もう完成

ワイ「社長!」
ワイsetHeight関数、完成しましたやで!」

社長「おお、早いな!」
社長「おおきにやで!」

ワイ「てへへ・・・」

社長「でもスマン・・・」

ワイ「え・・・」

社長「一つだけ要件を言い忘れてたんや・・・」

  • 高さを100ピクセルにして、更に背景色を赤くしたい場合もある

ワイ「Oh...」
ワイ「まあ、やってみますわ・・・」

修正開始

ワイ「背景色を赤くしたい場合もある、か・・・」
ワイ「ほな、使い方としては↓こんな感じのイメージにしてみよか・・・」

// 高さを100ピクセルにするだけの場合
setHeight(box);

// 高さを100ピクセルにして、更に背景を赤くしたい場合
setHeight(box, true);

ワイ「第二引数にtrueを渡すと、背景色も変わるんや」
ワイ「せやから・・・」

const setHeight = (element, toRed) => {
    element.style.height = '100px';

    if(toRed){
        element.style.backgroundColor = 'red';
    }
};

ワイ「↑こんな感じやな!」
ワイ「第二引数のtoRedtruthy1だったら背景色を赤にするんや」

またまた完成

ワイ「社長!」
ワイsetHeight関数、完成しましたやで!」

社長「おお、爆速やんけ!」
社長「天才やな!」

ワイ「てへへ・・・」

社長「でもスマン・・・」

ワイ「ファッ!?

社長「一つだけ要件を言い忘れてたんや・・・」

ワイ「(一つちゃうやん・・・二つですやん・・・)」

社長「高さを100ピクセルにして、背景を赤くして・・・」
社長「更に横幅を200ピクセルまたは300ピクセルに変更したい場合もあるんや」

ワイ「ぐぬぬ・・・」
ワイ「やってみますわ・・・」

またまた修正開始

ワイ「ほな、第三引数に200とか300という数値が入ってきてたら」
ワイ「横幅をそのピクセル数に変更する・・・」
ワイ「そんな感じにすればええな!」

const setHeight = (element, toRed, width) => {
    element.style.height = '100px';
    if(toRed){
        element.style.backgroundColor = 'red';
    }

    // 第三引数のwidthがあったら、要素の横幅を変更する
    if(width){
        element.style.width = width + 'px';
    }
};

ワイ「↑こんな感じや!」

またまたまた完成

ワイ「社長!」
ワイsetHeight関数、完成しましたやで!」

社長「・・・」
社長「一つだけ要件を言い忘れてたんや・・・」

ワイ「またかい!」
ワイ「一つちゃうやんけ!」
ワイ「○すぞ!」

社長「許すってこと?」
社長「ありがとうやで・・・」

ワイ「・・・ギギギィ・・・!」
ワイ「もうええから要件を言うてみい!!!」

社長「高さを100ピクセルにして、背景を赤くして・・・」
社長「横幅を変更して・・・」
社長「更に・・・」

ワイ「更に・・・?」

社長「文字色をにしたい時もあるし、にしたい時もあるし」
社長「文字サイズを大きくしたい時もあれば、小さくしたい時もある」
社長「要素内のテキストを変更したい場合もあんねん」

ワイ「ぐぬぬぬぬぬ・・・」

社長「ただ、高さを100ピクセルにするってことだけはマストや」
社長「その要件だけは固定や

ワイ「はぁ・・・」
ワイ「とりあえずやってみますわ・・・」

社長「すまんな・・・」

またまたまた修正開始

ワイ「ええと、要件をまとめると・・・」

  • 要素の高さを100ピクセルにする(マスト)
  • 背景色を赤くするかもしれない
  • 横幅も変更するかもしれない
  • 文字色も変更するかもしれない
  • 文字サイズも変えるかもしれない
  • 要素内のテキストを変更するかもしれない

ワイ「↑これを全部、引数の渡し方でコントロールすんのかいな・・・」
ワイ「引数、何個必要やねん・・・」

こんな機能があったらいいのに

ワイ「もっとこう、何をどうしたいかを、引数でそのまま渡せたらいいのになぁ・・・」

ハスケル子「できますよ

ワイ「マジかい、ハスケル子ちゃん!?」

ハスケル子「はい」
ハスケル子「高階関数にすればいいんです」

高階関数とは

ワイ「後悔関数!?
ワイ「書いたことを後悔するようなクソコードを書くってことかいな!?」

ハスケル子「私はそんなの書かないです」
ハスケル子「やめ太郎さんじゃないので」

ワイ「(せやな)」

ハスケル子「高階関数、つまり」
ハスケル子「引数として関数を受け取る関数です」

ワイ「引数として、関数を・・・受け取る・・・?」
ワイ「よう分からんわ・・・」

ハスケル子「こういうのは、やってみれば分かるので」
ハスケル子「とりあえずやってみましょう」

まず、コールバック関数を書いてみる

ハスケル子「まず、コールバック関数を書いてみましょう」

ワイ「キックバック関数!?」
ワイ「その関数を誰かに紹介したらいくらかお金がもらえるんか!?

ハスケル子「・・・本当にそう思いますか?

ワイ「いえ、すんません」

ハスケル子「はい」

ワイ「そんで、コールバック関数って何ですか・・・」

ハスケル子「ええと」
ハスケル子「とりあえずですね」

追加でやりたいことを書いてみる

ハスケル子「setHeight関数はまず、要素の高さを100ピクセルに変更する」
ハスケル子「そこはどんな時も変わらない、マストな処理だって話ですよね」

ワイ「せや」

ハスケル子「そして、その後に追加で」
ハスケル子「横幅を変えたり、文字色を変えたり・・・」
ハスケル子「逆に変えなかったり・・・色々あるわけじゃないですか」

ワイ「せやね」

ハスケル子「その追加でやりたい内容を関数にしてみてください」

ワイ「やってみるわ・・・」

const func = () => {
    element.style.color = 'green';
    element.innerHTML = 'こんにちわ!';
};

ワイ「↑こんな感じかな」
ワイ「とりあえず文字色テキストを変更する感じで書いてみたわ」

ハスケル子「いいですね」
ハスケル子「でも、HTML要素を引数にとる感じにしてもらっていいですか?」

ワイ「なるほど・・・」

const func = element => {
    element.style.color = 'green';
    element.innerHTML = 'こんにちわ!';
};

ワイ「↑こんな感じ?」

ハスケル子「OKです」
ハスケル子「で、そのfunc君を、第二引数としてsetHeight関数に渡してやるんです」

setHeight(box, func);

ワイ「↑こんな感じ?」

ハスケル子「そうです」
ハスケル子「引数として渡されるこのfunc君こそが、コールバック関数です」

ワイ「なるほど・・・なんか分かってきたで・・・!」
ワイ「このあとsetHeight関数も編集するんやな?」

ハスケル子「そうです」
ハスケル子「引数として入ってきた関数を」
ハスケル子「setHeight関数の中で使ってあげるんです」

ワイ「ってことは」

const setHeight = (element, callback) => {

ワイ「↑こんな感じで、callbackっていう名前で第二引数を受け取ってやって・・・」

    element.style.height = '100px';
    callback(element);

ワイ「↑こうやな!」
ワイ「要素の高さを100ピクセルに変更したあとで」
ワイ「コールバック関数にelementを渡して実行してやればええんやな!」

ハスケル子「そうです!」
ハスケル子「単純な引数だけでは足りなくなって、もっとこう・・・」
ハスケル子「何をどうしたいのかをそのまま引数で渡したいときに便利なんです」

ワイ「おお・・・」
ワイ「パラメーターいっぱい増やすより、やりたいことをそのまま引数にできる感じで素敵やん・・・!」

ハスケル子「そうです」
ハスケル子「ちなみにfunc君はここでしか使わない関数なので」
ハスケル子「名前をつけないで無名関数にしてもいいかもですね」

ワイ「なるほど」

setHeight(box, element => {
    element.style.color = 'green';
    element.innerHTML = 'こんにちわ!';
});

ワイ「↑こういうことやな」

ハスケル子「ですね」

今度こそ完成

ワイ「社長!」
ワイ「setHeight関数、完成しましたやで!」

社長「一つだけ要件を言い忘れてたんや・・・」
社長「背景画像を変更したい場合もあるんや・・・」

ワイ「もう何でも来いですわ!
ワイ「何をどうしたいかそのまま引数として渡せるようになったワイに」
ワイ「もはや死角なしですわ!!!」

社長「おお、流石やな」

ワイ「高階関数を、コールバック関数に渡すんですわ!」

ハスケル子「やめ太郎さん、逆です
ハスケル子「コールバック関数を、高階関数に渡すんです」
ハスケル子「引数としてコールバック関数を渡す」
ハスケル子「そのコールバック関数を、引数として受け取るのが高階関数です」

ワイ「Oh...」
ワイ「逆やったか」

ハスケル子「Arrayの持っているforEach、map、filterなんかも」
ハスケル子「関数を受け取る関数なので高階関数ですね」

ワイ「なるほどな」

社長「後悔関数!?
社長「書いたことを後悔するような(以下略)」

ハスケル子「(こいつも同じ発想か)」
ハスケル子「(なんだこの時間)」

なんだかんだで終業時刻

ワイ「ハスケル子ちゃん、今日もありがとうな」

ハスケル子「いえいえ」

ワイ「高階関数を書いてみたら、なんだかフロントエンド中級者になったような気がするわ」

ハスケル子「へえ」
ハスケル子「やめ太郎さん、分割代入ってしたことあリますか?」

ワイ「ん?ないけど」

ハスケル子「Webpackの使い方は?」

ワイ「知らん・・・」

ハスケル子「React Hooksは?」

ワイ「何それ・・・」

ハスケル子「ですよね」
ハスケル子「やめ太郎さんは、中級者ですか?」

ワイ「いえ、初級者です・・・
ワイ「申し訳ございませんでした・・・」

結論

中級者になれた気がしただけでした。
気のせいでした。

〜おしまい〜


  1. trueと見なせるような値のこと。 

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

Electronでカレンダーを作る②

前回からの続き

前回は画面まで作成した。
カレンダーの内容を動的に表示する処理を作成していく。

カレンダーの中身を作っていく

日付操作を楽にするためmomentモジュールを使う。

$ npm install -D moment

index.htmlを修正していく。

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ElectronCalendar</title>
  <script text="text/javascript" src="index.js"></script>
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <div class="calendar-wrapper">
    <table class="calendar">
      <caption id="caption"></caption>
      <thead>
        <tr>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td id="cell1"></td>
          <td id="cell2"></td>
          <td id="cell3"></td>
          <td id="cell4"></td>
          <td id="cell5"></td>
          <td id="cell6"></td>
          <td id="cell7"></td>
        </tr>
        ・・・略
          <td id="cell27"></td>
          <td id="cell28"></td>
        </tr>
        <tr>
          <td id="cell29"></td>
          <td id="cell30"></td>
          <td id="cell31"></td>
          <td id="cell32"></td>
          <td id="cell33"></td>
          <td id="cell34"></td>
          <td id="cell35"></td>
        </tr>
      </tbody>
    </table>
  </div>
</body>
</html>

7(曜日) × 5(週)の箱を作って各セルにIDを振っておく。

index.js
'use strict';

const moment = require("moment");

window.onload = function() {
    //captionを表示
    document.getElementById('caption').innerText = moment().format("YYYY年MM月");

    //当月の日数を取得
    const daysOfMonth = moment().daysInMonth();

    //月初の曜日を取得(index.htmlと合わせるために+1する)
    const firstDayOfManth = moment().startOf('month').day() + 1;

    //カレンダーの各セルに日付を表示させる
    let cellIndex = 0;
    for(let i = 1; i < daysOfMonth + 1; i++) {
        if(i === 1) {
            cellIndex += firstDayOfManth;
        } else {
            cellIndex++;
        }
        document.getElementById("cell" + cellIndex).innerText = i;
    }
}

月初の曜日が分かればそれ以降の日付の曜日も一意に決まる。
カレンダーの特性上、1番上の行のセルには値が必ず一個は入るので、cellIndex += firstDayOfManth; で初日のセルの位置を設定して、後は日数分インクリメントしていく。

月初はmomentモジュールのmoment().startOf('month')で楽に取得できる。

$ electron .

image.png

祝日を赤字にするにはどっかでデータを持っておかないと無理そう。

TODO

年月を変更できるようにする。

あとがき

momentモジュールが便利。

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

[js]コールバック関数が分からないから調べてみた

コールバック関数とは

引数に渡す関数のことをコールバック関数という・・・。
文だけだと分かりづらいのでコードで見ていきましょう。

function hello(name) {
 console.log(name());//引数に()をつけることで関数を実行する
}

function getname() {
  return 'code mafia';
}

hello(getname);

動作内容

①hello(getname)でhello関数に引数として(getname)を渡す。

②hello関数は渡された(getname)を(name)として受け取る。
function hello(name)←中身はgetname

③hello関数でconsole.log(name())でgetname関数を実行する。

④getnameの戻り値はcode mafiaなので
console.log(name())

console.log('code mafia')となる

このプログラムでコールバック関数となるのは、function getnameとなります。

なぜ function getnameなのか

最初のコールバック関数の定義を振り返りましょう。
『引数に渡す関数のことをコールバック関数という』

function getname関数はfunction hello関数に引数として渡されていますね!なので、コールバック関数はfunction getnameとなります。

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

Leaflet を使ってみる

Leaflet という地図描画ライブラリ https://leafletjs.com/ を使って、地図上で次のようなことをやってみました。

  • OpenStreetMap を表示する
  • マーカーを置く
  • マーカーをクリックしたときの説明・写真を追加する
  • 円を描く
  • 多角形を描く

上記のようなことであれば、短いコードで表現できるため便利だなと感じました。

ほんとうは、「あるエリア」(たとえば「北海道だけ」とか) を塗りつぶすというのがやりたかったのですが、今回はあきらめてます。ただ、「多角形を描く」の応用のような気がしますので、データさえそろえば達成できそうな感触はつかめました。

また、今回の記事は、次のサイトを参考にしました。大変わかりやすかったです。

OpenStreetMap を表示する

OpenStreetMap https://www.openstreetmap.org/ は、地図データを供給してくれるサイト。Google Map の代替として最近よく目にします。

Leaflet 自体は地図データを持っていないので、OpenStreetMap を地図データの供給元として設定してみる、というのが以下のサンプルコードになります。

image.png

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.0/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.3.0/dist/leaflet.js"></script>
    <script>
      function init() {
        //地図を表示するdiv要素のidを設定
        var map = L.map('mapcontainer', {zoomControl:false});
        // 縮尺表示
        L.control.scale({maxWidth:200,position:'bottomright',imperial:false}).addTo(map);
        // ズームコントロール (マウスのホイールコロコロがあれば不要。デフォルトは左上)
        L.control.zoom({position:'bottomleft'}).addTo(map);


        // オープンストリートマップ
        var osm = L.tileLayer('http://tile.openstreetmap.jp/{z}/{x}/{y}.png',
          {attribution: "<a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors" });
        osm.addTo(map);
        var mpoint = [50.086474, 14.411526]; // プラハ、カレル橋
        map.setView(mpoint, 15);
      }
    </script>
  </head>
  <body onload="init()">
    <div id="mapcontainer" style="position:absolute;top:0;left:0;right:0;bottom:0;"></div> <!-- ページに目いっぱい表示 -->
  </body>
</html>

マーカーを置く

image.png

        var mpoint = [50.086474, 14.411526]; // プラハ、カレル橋
        map.setView(mpoint, 15);

// ★ 以下を追記
        // クリック時の写真を追加 (写真データは tabinaka.co.jp から拝借)
        var pop1 = L.popup({maxWidth:550}).setContent(`カレル橋 (チェコ / プラハ)<br><img src="https://tabinaka.co.jp/magazine/wp-content/uploads/2019/09/shutterstock_369258188.jpg" style="width:240px;">`);
        L.marker(mpoint, {title:"カレル橋"}).bindPopup(pop1).addTo(map);

円や多角形を描く

image.png

        L.marker(mpoint, {title:"カレル橋"}).bindPopup(pop1).addTo(map);

// ★ 以下を追記
        // 円を描く (赤の円。同心円で2つ。内側は「塗り」あり)
        L.circle(mpoint, { radius: 500, color: "red", fill: true, weight: 2 }).addTo(map);
        L.circle(mpoint, { radius: 1000, color: "red", fill: false, weight: 1 }).addTo(map);

        // 多角形を描く (正方形になるかな?と思ったけど、緯度が高いせいか縦長になっている。赤道付近で試すと正方形になった)
        var delta = 0.005;
        var arealatlons = [mpoint, [mpoint[0], mpoint[1]-delta], [mpoint[0]-delta, mpoint[1]-delta], [mpoint[0]-delta, mpoint[1]]];
        L.polygon(arealatlons, { color: 'blue', weight: 2, fill: true, fillColor: 'blue', opacity: 0.5 }).addTo(map);

完成版のソースコード全文

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.0/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.3.0/dist/leaflet.js"></script>
    <script>
      function init() {
        //地図を表示するdiv要素のidを設定
        var map = L.map('mapcontainer', {zoomControl:false});
        // 縮尺表示
        L.control.scale({maxWidth:200,position:'bottomright',imperial:false}).addTo(map);
        // ズームコントロール (マウスのホイールコロコロがあれば不要。デフォルトは左上)
        L.control.zoom({position:'bottomleft'}).addTo(map);


        // オープンストリートマップ
        var osm = L.tileLayer('http://tile.openstreetmap.jp/{z}/{x}/{y}.png',
          {attribution: "<a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors" });
        osm.addTo(map);
        var mpoint = [50.086474, 14.411526]; // プラハ、カレル橋
        map.setView(mpoint, 15);

        // クリック時の写真を追加 (写真データは tabinaka.co.jp から拝借)
        var pop1 = L.popup({maxWidth:550}).setContent(`カレル橋 (チェコ / プラハ)<br><img src="https://tabinaka.co.jp/magazine/wp-content/uploads/2019/09/shutterstock_369258188.jpg" style="width:240px;">`);
        L.marker(mpoint, {title:"カレル橋"}).bindPopup(pop1).addTo(map);

        // 円を描く (赤の円。同心円で2つ。内側は「塗り」あり)
        L.circle(mpoint, { radius: 500, color: "red", fill: true, weight: 2 }).addTo(map);
        L.circle(mpoint, { radius: 1000, color: "red", fill: false, weight: 1 }).addTo(map);

        // 多角形を描く (正方形になるはずだけど、緯度が高いせいか縦長になっている。赤道付近で試すと正方形になった)
        var delta = 0.005;
        var arealatlons = [mpoint, [mpoint[0], mpoint[1]-delta], [mpoint[0]-delta, mpoint[1]-delta], [mpoint[0]-delta, mpoint[1]]];
        L.polygon(arealatlons, { color: 'blue', weight: 2, fill: true, fillColor: 'blue', opacity: 0.5 }).addTo(map);
      }
    </script>
  </head>
  <body onload="init()">
    <div id="mapcontainer" style="position:absolute;top:0;left:0;right:0;bottom:0;"></div> <!-- ページに目いっぱい表示 -->
  </body>
</html>

以上です。

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

JavaScriptのthisとPHPのthis

どんな記事?

JavaScriptとPHPのthisを比べながら理解しよう。

背景

thisがよく分からん!

JavaScriptのthis他の言語と少々異なる動作をするらしい

よろしい、ならば比較だ

PHPのthis

PHPの疑似変数である$thisは、メソッドがオブジェクトコンテキストからコール場合に利用することができる。
$thisは呼び出し元オブジェクトへの参照である。

カレントオブジェクトからそのクラス内の関数や変数にアクセスする場合に$thisを使える。

(コンテキストは直訳だと文脈という意味。
プログラミング用語的には、背景となる情報という言い回しが妥当かな?)

(カレントオブジェクトとは、インスタンスメソッドが起動されたオブジェクトのこと)

<?php
//クラス(Sample)
class Sample
{
  //プロパティ($hoge)宣言
  public $hoge = 'foo';

  //メソッド(display)宣言
  public function display()
  {
    //displayメソッドで$hogeの内容を呼び出す
    echo $this->$hoge;
  }
}

$sample = new Sample;
$sample->display(); //出力結果:foo
?>

なぜ$thisが必要か?

それは、
クラス定義の内部では、それにアクセス可能なオブジェクト名を知ることができない
からである。
上記サンプルでは、Sampleクラスが書かれている時点では、
そのオブジェクトの名前があとで$sampleになるのか、はたまた$showになるのか分からない。

その為、Sampleクラスの中で

$sample->$hoge

と書くことはできないのである。

代わりに、クラスの中から、そのクラス内の関数や変数にアクセスする為に$thisを使う。

JavaScriptのthis

ほとんどの場合、thisの値は、関数の呼ばれ方によって決定されます。これは実行時に割り当てできず、関数が呼び出されるたびに異なる可能性があります。

ほう。

構文

this

値:現在のコードが実行されているJavaScriptコンテキストオブジェクトです

グローバルコンテキスト

JavaScriptにおけるグローバル実行コンテキスト(いずれかの関数の外側)では、
thisはグローバルオブジェクトを参照する。

例文
//whidowはグローバルオブジェクト
//thisはグローバルオブジェクトを示す
console.log(this === window);  // true

//グローバルオブジェクトで宣言
this.hoge = 37;
//windowグローバルオブジェクトから
//thisで宣言した値を呼び出すことができる
console.log(window.hoge); //出力結果:37

あれ、PHPとぜんぜん違いますね(驚愕)

関数コンテキスト

単純な呼び出し

次の例は呼び出し時にthisの値がセットされない為、thisはデフォルトでグローバルオブジェクトとなり、それはブラウザではwindowと同等である。

funcion f1() {
  return this;
}

f1() === window; //出力結果:true

別のコンテキストからthisの値を呼び出す場合はcallもしくはapplyを使用する。

//オブジェクト定義
var obj = {a: 'Custom'};

function whatsThis() {
  return this.a;
}

whatsThis.call(obj); //出力結果:Custom

アロー関数

アロー関数では、thisはそれを囲むレキシカル(静的)なコンテキストのthisの値が設定される。

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); //出力結果:true

オブジェクトのメソッドとして

関数がオブジェクトのメソッドとして呼び出される時、そのthisにはメソッドが呼び出されたオブジェクトが設定される。

次の例ではtest.func()が起動した時、関数内のthisにはtestオブジェクトが関連付けられる。

const test = {
  prop: 42,
  func: function () {
    retrun this.prop;
  },
};

console.log(test.func());
//出力結果: 42

この振る舞いは、関数定義の方法や場所に全く影響を受けない。

オブジェクトのプロトタイプチェーン上のthis

同じ概念が、オブジェクトのプロトタイプチェーンのどこかに定義されたメソッドにもあてはまる。
そのメソッドがオブジェクト上にあるかのように、thisはメソッドを呼び出したオブジェクトを参照する。

var o = {
  f: function() {
    return this.a + this.b
  }
}
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); //出力結果:5

コンストラクタとして

関数がコンストラクタとして(newで生成される)使用される時、そのthisは生成された新しいオブジェクトにバインド(拘束)される

//関数定義
function C() {
  this.a = 37;
}
//コンストラクタ生成
var o = new C();
console.log(o.a); //出力結果:37

DOMイベントハンドラとして

関数がイベントハンドラとして使用される場合、そのthisにはイベントを発火させた要素が設定される

//要素を青色にする関数
function bluify(e) {
  console.log(this === e.currentTarget);
  console.log(this === e.target);
  this.style.backgroundColor = '#A5D9F3';
}
//全ての要素を取得
var elements = document.getElementByTagName('*');
//クリックリスナーとしてbluify関数を設定
//クリックした要素が青色に変わる
for(var i = 0; i < elements.length; i++){
  elements[i].addEventListener('click', bluify, false);
}

比べてみて

JavaScriptのthisは、オブジェクトのメソッドコンストラクトとして使うのであればPHPと同じような理解でいいのかな、と思いました。
DOMイベントハンドラとしてはフロントエンドならでは、という感じですね。

グローバルコンテキスト・関数コンテキストでのthisは、まだ使い道が分からん...

参考

PHP: クラスの基礎 - Manual
PHPで擬似変数$thisって何のためにあるのか?
this - JavaScript|MDN
「コンテキスト」という言葉が何を指しているのかよく分からない
レキシカルスコープとクロージャを理解する

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

Electronでカレンダーを作る①

はじめに

皆さん、Electron触っていますでしょうか?
私は最近Electronの存在を知ったので、JavaScriptの勉強も兼ねてカレンダーアプリを作成してみようと思った次第です。

とりあえずElectronプロジェクトを作る。

$ mkdir ElectronCalender
$ cd ElectronCalender/
$ npm init -y
$ npm install -D electron ※

これでディレクトリ内にElectronを使う準備は整った。
※npm install -D ~ はpackage.jsonに書き込みらしい。

画面を作ってアプリを立ち上げる。

package.jsonにエントリーポイント(main.js)を指定しとく。

package.json
{
  "name": "ElectronCalender",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^8.1.1"
  }
}

main.jsの中身

main.js
'use strict';

// Electronのモジュール
const electron = require("electron");

// アプリケーションをコントロールするモジュール
const app = electron.app;

// ウィンドウを作成するモジュール
const BrowserWindow = electron.BrowserWindow;

// メインウィンドウはGCされないようにグローバル宣言
let mainWindow;

// 全てのウィンドウが閉じたら終了
app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// Electronの初期化完了後に実行
app.on('ready', function() {
  // メイン画面の表示。ウィンドウの幅、高さを指定できる
  mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadURL('file://' + __dirname + '/index.html');

  // ウィンドウが閉じられたらアプリも終了
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});

mainWindow.loadURL('file://' + __dirname + '/index.html'); で表示する画面(htmlファイル)を読み込む。

html,cssはこんな感じ(ほぼ参考書の内容を丸写しした)

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ElectronCalender</title>
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <div class="calendar-wrapper">
    <table class="calendar">
      <caption>2020年3月</caption>
      <thead>
        <tr>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td>2</td>
          <td>3</td>
          <td>4</td>
          <td>5</td>
          <td>6</td>
          <td>7</td>
        </tr>
        <tr>
          <td>8</td>
          <td>9</td>
          <td>10</td>
          <td>11</td>
          ・・・略
        </tr>
      </tbody>
    </table>
  </div>
</body>
</html>
index.css
.calendar-wrapper {
    width: 750px;
}

.calendar {
    table-layout: fixed;
    width: 100%;
    border-collapse: collapse;
    border: 1px solid #ccddcc;
    vertical-align: middle;
}

.calendar th,
.calendar td {
    padding: 10px 0;
    border: 1px solid #cdcdcd;
    text-align: center;
    vertical-align: top;
    font-size: 35px;
}

.calendar th {
    background-color: #dedede;
}

/* 日曜日1行目(見出し) */
.calendar th:first-child {
    background-color: #e05557;
    color: #ffffff;
}

/* 日曜日 */
.calendar td:first-child {
    color: #e05557;
}

/* 土曜日1行目(見出し) */
.calendar th:last-child {
    background-color: #207bcf;
    color: #ffffff;
}

/* 土曜日 */
.calendar td:last-child {
    color: #207bcf;
}

/* 祝日 */
.holiday {
    color: #e05557;
}

.calendar caption {
    margin: 0 0 10px 0;
    padding: 10px;
    font-size: 23px;
    border: 5px solid #dedede;
    border-radius: 30px;
    font-weight: bold;
}

アプリを起動する。

ディレクトリ直下で
$ electron .

image.png

良い感じな気がする。

TODO

日付、祝日をhtml内にベタ書きしているのでjsを使って動的にカレンダーの内容が作成されるようにしたい。

あとがき

作っていて気がついたけどカレンダーのスペルは「calender」じゃなくて「calendar」だった。

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

【JavaScript】小数点以下の不要な0を削除する

小数点の表示で0の場合だけなくすという仕様で困ったことはありませんか?
具体的には以下のような仕様です。

1.000 => 1
1.100 => 1.1
1.110 => 1.11

解決方法

JavaScriptのNumberクラスのtoLocaleStringメソッド(optionのmaximumFractionDigits)を使います。

ブラウザーがlocales引数とoptions引数をサポートサポートされていない可能性がありますのでこちらで確認して下さい。
リファレンス

[数値].toLocaleString( locales, { maximumFractionDigits: [有効にしたい小数点以下の桁数] })

maximumFractionDigitsオプションを省略した場合はデフォルトは3です。(リファレンスにのっていないため自己検証)

コードで仕様説明

スクリーンショット 2020-03-20 13.57.06.png
小数点以下の不要な0が全て切捨てられています。
画像の一番下をみていただくとわかりますがmaximumFractionDigitsが3のため小数点第3位の数字までしか表示されていません。
スクリーンショット 2020-03-20 14.00.57.png
maximumFractionDigitsを4にしているため小数点第4位まで表示されています。

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

【JavaScript】小数点以下の不要な0を削除する toLocaleString

小数点の表示で0の場合だけなくすという仕様で困ったことはありませんか?
具体的には以下のような仕様です。

1.000 => 1
1.100 => 1.1
1.110 => 1.11

解決方法

JavaScriptのNumberクラスのtoLocaleStringメソッド(optionのmaximumFractionDigits)を使います。

ブラウザーがlocales引数とoptions引数をサポートされていない可能性がありますのでこちらで確認して下さい。
リファレンス

[数値].toLocaleString( locales, { maximumFractionDigits: [有効にしたい小数点以下の桁数] })

maximumFractionDigitsオプションを省略した場合はデフォルトは3です。(リファレンスにのっていないため自己検証)

コードで仕様説明

スクリーンショット 2020-03-20 13.57.06.png
小数点以下の不要な0が全て切捨てられています。
画像の一番下をみていただくとわかりますがmaximumFractionDigitsが3のため小数点第3位の数字までしか表示されていません。
スクリーンショット 2020-03-20 14.00.57.png
maximumFractionDigitsを4にしているため小数点第4位まで表示されています。

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

promise勉強

promise理解のステップ

  1. 関数は順番通りに実行される
  2. 順番通りなので、すげえ時間かかるやつがあるとそこで時が止まる(体感上)
  3. 時間かかるやつを置いといて、後続の処理を実行させる技術が編み出される(非同期関数)
  4. 非同期関数のあと、また後でいろいろやってもらうための関数を登録できる(コールバック関数)
const hoge = () => {
    console.log("hello")
}

// 非同期関数自体は時間のかかる処理を実行する際に、途中で処理を止めないためのもの
// 終わったあとにhoge実行している
// このままだとやりたいことを増やすたびにネストが深くなる
console.log(1)
setTimeout(()=>{
    hoge()
    setTimeout(()=>{
        hoge()}), 10}, 10);
console.log(3)

5.後に実行させるやつ多すぎると上みたいなのがもっとエグい感じになる
6. もっと綺麗に書きたい
7. 時間かかる関数が終わったのが分かればネストしなくて済む(プロミスの発明)

// 時間のかかる処理をプロミスでくるむ
const promisedekurumu = () => {
    const promise = new Promise((a, b)=>{
        setTimeout(()=>{
            const result = () => {console.log("jikan")}
            a(result) //ここに処理内容を詰めて返せる
        }, 10);
    })
    console.log(promise)
    return promise
}

const hoge = () => {
    console.log("hello")
}

// thenも非同期関数なので引数には関数を登録する。実行しちゃダメ
// thenは新しいコールバックを返してくれる
console.log(1)
promisedekurumu()
.then((result)=>{
  result();
  hoge()
})
console.log(3)

// 1
// Promise { <pending> }
// 3
// jikan
// hello

8.なんかthenとかいうやつ結構うざいよねってなる。
thenはプロミス実行結果関数を受けて新しいコールバック関数を実行し、さらに新しいプロミスを生成する
9. 数珠つなぎじゃなくて、awaitで受け取るようにする
awaitはメソッドチェーン風に使わないので、非同期関数がより同期的な見た目になる
10. awaitいきなり使いたいのにasync関数の中でしか使えないので、プロミス受け取り用の関数を作る

// 時間のかかる処理をプロミスでくるむ
const promisedekurumu = () => {
    const promise = new Promise((a, b)=>{
        setTimeout(()=>{
            const result = () => {console.log("時間のかかる処理")};
            a(result); //ここに処理内容を詰めて返せる
        }, 10);
    });
    console.log(promise);
    return promise;
};

const hoge = () => {
    console.log("非同期処理を待ってからやりたいこと");
};

// プロミス受取関数とその後続の処理
// thenで数珠つなぎじゃなく、async関数内で同期的な記述が可能になる
const uketori = async () => {
    const result = await promisedekurumu();
    result()
    hoge()
};

// thenと比べると見やすい
// const uketori = () => {
//    promisedekurumu()
//      .then((result) => {result()})
//      .then(()=>{hoge()})
//};

console.log("最初")
uketori()
console.log("先に進んでやっていて欲しいこと")

//output
//最初
//Promise { <pending> }
//先に進んでやっていて欲しいこと
//時間のかかる処理
//非同期処理を待ってからやりたいこと

まとめ

非同期処理の後にやらせたい処理を、いかに綺麗に書けるかっていうところがpromise理解のミソかなと思います

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

永遠に続くクソ計算クイズ3 vue vuexで

成果物

https://amazing-minsky-06ffdf.netlify.com/

リポジトリ

https://github.com/yuzuru2/yuzuru2.github.io/tree/master/neta2

環境

  • node.js 12.14.0
  • parcel 1.12.4
  • typescript 3.8.3
  • vue 2.6.11
  • vuex 3.1.3

UI

無題.png

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

【JavaScript】文字列の中のある文字を別の文字に変換する

基本形

let word = 'ポテチ、ポッキー、クッキー、アイス';
let result = word.replace('アイス', 'チョコ');
console.log(result);
// ポテチ、ポッキー、クッキー、チョコ

replace(置換したい文字、置換後の文字)のように書きます。

この書き方の問題は置換したい文字が複数あったときに、最初の一つしか置換されないことです。

let word = 'ポテチ、アイス、クッキー、アイス';
let result = word.replace('アイス', 'チョコ');
console.log(result);
// ポテチ、チョコ、クッキー、アイス

これを回避するには正規表現を使います。

正規表現を使ってみる

let word = 'ポテチ、アイス、クッキー、アイス';
let result = word.replace(/アイス/g, 'チョコ');
console.log(result);
// ポテチ、チョコ、クッキー、チョコ

まず正規表現を使うよ、という意味を込めてパターン(文字列)の前後に「/」をつけます。

次に「/」後ろにフラグをつけます。

今回だと、置換したい文字が見つかっても最後まで検索してよ、ってことで「g(グローバルマッチ)」をつけました。

次は置換したい文字が複数あった場合の書き方です。

メソッドチェーンを使ってみる

let word = 'ポテチ、アイス、クッキー、アイス';
let result = word.replace(/アイス/g, 'チョコ').replace(/ポテチ/g, 'キャンディ');
console.log(result);
// キャンディ、チョコ、クッキー、チョコ

replace()の後にもう一回replace()を書くことで複数の文字の変換ができます。

普段、Laravelを使うことが多いのですがこれもソッドチェーンみたいです。

$user->where('id', $id)->get();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScript: 38個の具体例から学ぶnamed export

本稿では、TypeScriptのexportの書き方のうち、named exportに限定して、多数の具体例を用意しました。具体例を見ながら、exportの使い方を学ぶという趣旨です。良く使う書き方、あまり使わない書き方、いろいろあります。

具体例を一般化しながら、抽象的な構文知識を得たい人向けです。抽象的な文法から入りたい人は、各公式ドキュメントをご参照ください:

変数のexport

変数の定義と同時に、変数をexportする:

module.ts
export const value1 = 1
export let value2 = 2
export var value3 = 3

// インポート方法: import {value1, value2, value3} from './module'

変数の定義と、そのexportを分ける:

module.ts
const value1 = 1
let value2 = 2
var value3 = 3
export {value1}
export {value2}
export {value3}

// インポート方法: import {value1, value2, value3} from './module'

1回のexportで3つの変数を一気にexportする:

module.ts
const value1 = 1
let value2 = 2
var value3 = 3
export {value1, value2, value3} // エクスポートリストと呼ばれます

// インポート方法: import {value1, value2, value3} from './module'

複数の変数定義と、exportを同時する:

module.ts
export const value1 = 1, value2 = 2, value3 = 3 

// インポート方法: import {value1, value2, value3} from './module'

別の変数名としてexportする:

module.ts
const foo = 1
export {foo as value}

// インポート方法: import {value} from './module'

変数の情報だけをexportする:

module.ts
const FOO = 'FOO'
export type {FOO}

// インポート方法: import {FOO} from './module'
// ただし、FOOの使用には、typeof演算子が必要です:
// const foo: typeof FOO = 'FOO'

変数の型だけをexportする:

module.ts
const FOO = 'FOO'
export type FOO = typeof FOO

// インポート方法: import {FOO} from './module'
// この場合、typeof演算子は不要です:
// const foo: FOO = 'FOO'

関数のexport

関数の定義とexportを同時にする:

module.ts
export function func() {
}

// インポート方法: import {func} from './module'

関数の定義とexportを別々にする:

module.ts
function func() {
}
export {func}

// インポート方法: import {func} from './module'

匿名関数のexport

匿名関数はnamed exportできません:

module.ts
export function () { // エラー
}

配列のexport

配列のexportは、変数のexportと同じ:

module.ts
export const array = [1, 2, 3]

// インポート方法: import {array} from './module'

as constをつけると、タプル型としてexportできます:

module.ts
export const array = ['foo', true] as const
//=> このarrayは ['foo', true] 型になります

// インポート方法: import {array} from './module'

配列を分割代入しながらexportする:

module.ts
const array = [1, 2, 3]
export const [val1, val2, val3] = array

// これは下記と同じ意味です
// export const val1 = array[0]
// export const val2 = array[1]
// export const val3 = array[2]

// インポート方法: import {val1, val2, val3} from './module'

配列を要素と余剰部分に分けながらexportする:

module.ts
const array = [1, 2, 3]
export const [val1, ...rest] = array

// これは下記と同じ意味です
// export const val1 = array[0]
// export const rest = [array[1], array[2]]

// インポート方法: import {val1, rest} from './module'

オブジェクトのexport

オブジェクトリテラルは、変数のexportと同様です:

module.ts
export const obj = {FOO: 1, bar: 2}

// インポート方法: import {obj} from './module'

この場合、objの型は、{FOO: number, bar: number}になります。型の制約を追加しつつexportすることもできます:

module.ts
export const obj = {FOO: 1 as const, bar: 2}
//=> {FOO: 1, bar: number} 型としてexportされる

export const obj = {FOO: 1, bar: 2} as const
//=> {FOO: readonly 1, bar: readonly 2} 型としてexportされる

// インポート方法: import {obj} from './module'

オブジェクトを分割代入しながらexportする:

module.ts
const obj = {foo: 1, bar: 2}
export const {foo, bar} = obj

// これは下記と同じ意味です
// export const foo = obj.foo
// export const bar = obj.bar

// インポート方法: import {foo, bar} from './module'

オブジェクトを分割代入+名前変更しながらexportする:

module.ts
const obj = {foo: 1, bar: 2}
export const {foo: hoge, bar: piyo} = obj

// これは下記と同じ意味です
// export const hoge = obj.foo
// export const piyo = obj.bar

// インポート方法: import {hoge, piyo} from './module'

オブジェクトを分割代入+余剰パターンに分けながらexportする:

module.ts
const obj = {foo: 1, bar: 2, buz: 3}
export const {foo, ...barAndBuz} = obj

// これは下記と同じ意味です
// export const foo = obj.foo
// export const barAndBuz = {bar: obj.bar, buz: obj.buz}

// インポート方法: import {foo, barAndBuz} from './module'

クラスのexport

クラスの定義とexportを同時する:

module.ts
export class Class {}

// インポート方法: import {Class} from './module'

クラス定義とexportを別々にする:

module.ts
class Class {}
export {Class}

// インポート方法: import {Class} from './module'

クラスの型だけをexportする:

module.ts
class Class {}
export type {Class}

// インポート方法: import {Class} from './module'
// インポート側は new Class はできませんが、型としてClassを参照することができます。

匿名クラスのexport

匿名クラスは変数としてexportします:

module.ts
export const AnonymousClass = class {}

// インポート方法: import {AnonymousClass} from './module'

インスタンスのexport

インスタンスは変数としてexportします:

module.ts
export const instance = new Class()

// インポート方法: import {instance} from './module'

匿名クラスのインスタンスのexport

匿名クラスのインスタンスのexportは変数と同じやりかたです:

module.ts
export const instance = new class {}

// インポート方法: import {instance} from './module'

インターフェイスのexport

インターフェイス定義とexportを同時にする:

module.ts
export interface Interface {
}

// インポート方法: import {Interface} from './module'

インターフェイス定義とexportを別々にする:

module.ts
interface Interface {
}
export {Interface}

// インポート方法: import {Interface} from './module'

インターフェイスを別名でexportする:

module.ts
interface Interface {
}
export {Interface as FooInterface}

// インポート方法: import {FooInterface} from './module'

型エイリアスのexport

型エイリアスの定義とexportを同時にする:

module.ts
export type Status = 'OK' | 'NG'

// インポート方法: import {Status} from './module'

型エイリアスの定義と、exportを別々にする:

module.ts
type Status = 'OK' | 'NG'
export {Status}

// インポート方法: import {Status} from './module'

型エイリアスを別名でexportする:

module.ts
type Status = 'OK' | 'NG'
export {Status as FooStatus}

// インポート方法: import {FooStatus} from './module'

名前空間のexport

名前空間を定義しつつ、exportする:

module.ts
export namespace FooSpace {
  export const value = 1
}

// インポート方法: import {FooSpace} from './module'

名前空間の定義とexportを別々に行う:

module.ts
namespace FooSpace {
  export const value = 1
}
export {FooSpace}
// インポート方法: import {FooSpace} from './module'

re-export (再エクスポート)

すべてをimportしつつ、すべてをexportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export * from './module'

// インポート方法
import {value, func, Class} from './re-rexport'

特定のものだけre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export {value, func} from './module' // Classを除外している
// これは下記と同じ意味
// import {value, func} from './module'
// export {value, func}

// インポート方法
import {value, func} from './re-rexport'

別名でre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export {value as foo, Class as FooClass} from './module'
// これは下記と同じ意味
// import {value, func} from './module'
// export {value as foo, Class as FooClass}

// インポート方法
import {foo, FooClass} from './re-rexport'

importしたすべてを、ひとつのオブジェクトに束ねてre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export * as module from './module'

// インポート方法
import {module} from './re-rexport'
console.log(module.value)
console.log(new module.Class())
console.log(module.func())

特定のものだけを、ひとつのオブジェクトに束ねてre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-export.ts
import {value, func} from './module'
export const module = {value, func} // Classを除外している

// インポート方法
import {module} from './re-rexport'
console.log(module.value)
console.log(module.func())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScript: 38の具体例から学ぶnamed export

本稿では、TypeScriptのexportの書き方のうち、named exportに限定して、多数の具体例を用意しました。具体例を見ながら、exportの使い方を学ぶという趣旨です。良く使う書き方、あまり使わない書き方、いろいろあります。

具体例を一般化しながら、抽象的な構文知識を得たい人向けです。抽象的な文法から入りたい人は、各公式ドキュメントをご参照ください:

変数のexport

変数の定義と同時に、変数をexportする:

module.ts
export const value1 = 1
export let value2 = 2
export var value3 = 3

// インポート方法: import {value1, value2, value3} from './module'

変数の定義と、そのexportを分ける:

module.ts
const value1 = 1
let value2 = 2
var value3 = 3
export {value1}
export {value2}
export {value3}

// インポート方法: import {value1, value2, value3} from './module'

1回のexportで3つの変数を一気にexportする:

module.ts
const value1 = 1
let value2 = 2
var value3 = 3
export {value1, value2, value3} // エクスポートリストと呼ばれます

// インポート方法: import {value1, value2, value3} from './module'

複数の変数定義と、exportを同時する:

module.ts
export const value1 = 1, value2 = 2, value3 = 3 

// インポート方法: import {value1, value2, value3} from './module'

別の変数名としてexportする:

module.ts
const foo = 1
export {foo as value}

// インポート方法: import {value} from './module'

変数の情報だけをexportする:

module.ts
const FOO = 'FOO'
export type {FOO}

// インポート方法: import {FOO} from './module'
// ただし、FOOの使用には、typeof演算子が必要です:
// const foo: typeof FOO = 'FOO'

変数の型だけをexportする:

module.ts
const FOO = 'FOO'
export type FOO = typeof FOO

// インポート方法: import {FOO} from './module'
// この場合、typeof演算子は不要です:
// const foo: FOO = 'FOO'

関数のexport

関数の定義とexportを同時にする:

module.ts
export function func() {
}

// インポート方法: import {func} from './module'

関数の定義とexportを別々にする:

module.ts
function func() {
}
export {func}

// インポート方法: import {func} from './module'

匿名関数のexport

匿名関数はnamed exportできません:

module.ts
export function () { // エラー
}

配列のexport

配列のexportは、変数のexportと同じ:

module.ts
export const array = [1, 2, 3]

// インポート方法: import {array} from './module'

as constをつけると、タプル型としてexportできます:

module.ts
export const array = ['foo', true] as const
//=> このarrayは ['foo', true] 型になります

// インポート方法: import {array} from './module'

配列を分割代入しながらexportする:

module.ts
const array = [1, 2, 3]
export const [val1, val2, val3] = array

// これは下記と同じ意味です
// export const val1 = array[0]
// export const val2 = array[1]
// export const val3 = array[2]

// インポート方法: import {val1, val2, val3} from './module'

配列を要素と余剰部分に分けながらexportする:

module.ts
const array = [1, 2, 3]
export const [val1, ...rest] = array

// これは下記と同じ意味です
// export const val1 = array[0]
// export const rest = [array[1], array[2]]

// インポート方法: import {val1, rest} from './module'

オブジェクトのexport

オブジェクトリテラルは、変数のexportと同様です:

module.ts
export const obj = {FOO: 1, bar: 2}

// インポート方法: import {obj} from './module'

この場合、objの型は、{FOO: number, bar: number}になります。型の制約を追加しつつexportすることもできます:

module.ts
export const obj = {FOO: 1 as const, bar: 2}
//=> {FOO: 1, bar: number} 型としてexportされる

export const obj = {FOO: 1, bar: 2} as const
//=> {FOO: readonly 1, bar: readonly 2} 型としてexportされる

// インポート方法: import {obj} from './module'

オブジェクトを分割代入しながらexportする:

module.ts
const obj = {foo: 1, bar: 2}
export const {foo, bar} = obj

// これは下記と同じ意味です
// export const foo = obj.foo
// export const bar = obj.bar

// インポート方法: import {foo, bar} from './module'

オブジェクトを分割代入+名前変更しながらexportする:

module.ts
const obj = {foo: 1, bar: 2}
export const {foo: hoge, bar: piyo} = obj

// これは下記と同じ意味です
// export const hoge = obj.foo
// export const piyo = obj.bar

// インポート方法: import {hoge, piyo} from './module'

オブジェクトを分割代入+余剰パターンに分けながらexportする:

module.ts
const obj = {foo: 1, bar: 2, buz: 3}
export const {foo, ...barAndBuz} = obj

// これは下記と同じ意味です
// export const foo = obj.foo
// export const barAndBuz = {bar: obj.bar, buz: obj.buz}

// インポート方法: import {foo, barAndBuz} from './module'

クラスのexport

クラスの定義とexportを同時する:

module.ts
export class Class {}

// インポート方法: import {Class} from './module'

クラス定義とexportを別々にする:

module.ts
class Class {}
export {Class}

// インポート方法: import {Class} from './module'

クラスの型だけをexportする:

module.ts
class Class {}
export type {Class}

// インポート方法: import {Class} from './module'
// インポート側は new Class はできませんが、型としてClassを参照することができます。

匿名クラスのexport

匿名クラスは変数としてexportします:

module.ts
export const AnonymousClass = class {}

// インポート方法: import {AnonymousClass} from './module'

インスタンスのexport

インスタンスは変数としてexportします:

module.ts
export const instance = new Class()

// インポート方法: import {instance} from './module'

匿名クラスのインスタンスのexport

匿名クラスのインスタンスのexportは変数と同じやりかたです:

module.ts
export const instance = new class {}

// インポート方法: import {instance} from './module'

インターフェイスのexport

インターフェイス定義とexportを同時にする:

module.ts
export interface Interface {
}

// インポート方法: import {Interface} from './module'

インターフェイス定義とexportを別々にする:

module.ts
interface Interface {
}
export {Interface}

// インポート方法: import {Interface} from './module'

インターフェイスを別名でexportする:

module.ts
interface Interface {
}
export {Interface as FooInterface}

// インポート方法: import {FooInterface} from './module'

型エイリアスのexport

型エイリアスの定義とexportを同時にする:

module.ts
export type Status = 'OK' | 'NG'

// インポート方法: import {Status} from './module'

型エイリアスの定義と、exportを別々にする:

module.ts
type Status = 'OK' | 'NG'
export {Status}

// インポート方法: import {Status} from './module'

型エイリアスを別名でexportする:

module.ts
type Status = 'OK' | 'NG'
export {Status as FooStatus}

// インポート方法: import {FooStatus} from './module'

名前空間のexport

名前空間を定義しつつ、exportする:

module.ts
export namespace FooSpace {
  export const value = 1
}

// インポート方法: import {FooSpace} from './module'

名前空間の定義とexportを別々に行う:

module.ts
namespace FooSpace {
  export const value = 1
}
export {FooSpace}
// インポート方法: import {FooSpace} from './module'

re-export (再エクスポート)

すべてをimportしつつ、すべてをexportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export * from './module'

// インポート方法
import {value, func, Class} from './re-rexport'

特定のものだけre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export {value, func} from './module' // Classを除外している
// これは下記と同じ意味
// import {value, func} from './module'
// export {value, func}

// インポート方法
import {value, func} from './re-rexport'

別名でre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export {value as foo, Class as FooClass} from './module'
// これは下記と同じ意味
// import {value, func} from './module'
// export {value as foo, Class as FooClass}

// インポート方法
import {foo, FooClass} from './re-rexport'

importしたすべてを、ひとつのオブジェクトに束ねてre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-rexport.ts
export * as module from './module'

// インポート方法
import {module} from './re-rexport'
console.log(module.value)
console.log(new module.Class())
console.log(module.func())

特定のものだけを、ひとつのオブジェクトに束ねてre-exportする:

// module.ts
export const value = 1
export function func() {}
export class Class {}

// re-export.ts
import {value, func} from './module'
export const module = {value, func} // Classを除外している

// インポート方法
import {module} from './re-rexport'
console.log(module.value)
console.log(module.func())

最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:Twitter@suin

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

AutoML Visionを使った鼓膜画像判別Botの作成

概要

普段は耳鼻科の開業医をしています。
今回AutoML Visionで作成した鼓膜画像分類モデルを使った鼓膜画像判別Botを作ってみました。Botが診断するのは法的にまずいので公開はしない予定ですが、名前は鼓膜占いボットということにしておきました(占いならよさそうなので)。

前回までの記事はこちら
GCP Cloud AutoML Vision を使った鼓膜画像分類
Node.jsからAutoML Vision の鼓膜画像分類モデルを使ってみる

完成動画

概念図

無題.png

作成

1.必要なパッケージのインストール

npm install --save fetch-base64

2.プログラムの作成

aombot.js
'use strict';
require('dotenv').config();
const fetch = require('fetch-base64');
const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
  channelSecret: "自分のチャンネルシークレット",
  channelAccessToken: "自分のアクセストークン"
};

const automl = require('@google-cloud/automl');

// Create client for prediction service.
const mlclient = new automl.PredictionServiceClient();

const projectId = "自分のプロジェクト名";
const computeRegion = "us-central1";
const modelId = "自分のモデルID";
const scoreThreshold = "0.5";

// Get the full path of the model.
const modelFullId = mlclient.modelPath(projectId, computeRegion, modelId);

const aomLabels = {
  "aom": "急性中耳炎",
  "ome": "滲出性中耳炎",
  "normal": "正常鼓膜",
};

const app = express();
app.use(express.static('public')); //追加

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)


app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);

  //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
  if (req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
    res.send('Hello LINE BOT!(POST)');
    console.log('疎通確認用');
    return;
  }

  Promise
    .all(req.body.events.map(event => handleEvent(event, req)))
    .then((result) => res.json(result));
});


const client = new line.Client(config);

async function handleEvent(event, req) {
  console.log(req);
  if (event.type !== 'message' || (event.message.type !== 'text' && event.message.type !== 'image')) {
    return Promise.resolve(null);
  }
  const params = {};
  params.score_threshold = scoreThreshold;

  // Read the file content for prediction.
  const data = await fetch.remote({
    url: `https://api-data.line.me/v2/bot/message/${event.message.id}/content`,
    headers: {
      'Authorization': `Bearer ${process.env.CHANNEL_ACCESS_TOKEN}`
    }
  });

  const payload = {};
  payload.image = { imageBytes: data[0] };

  // params is additional domain-specific parameters.
  // currently there is no additional parameters supported.
  const [response] = await mlclient.predict({
    name: modelFullId,
    payload: payload,
    params: params,
  });



  if (response.payload.length === 0) {
    return client.replyMessage(event.replyToken, {
      type: 'text',
      text: `ちょっとわかりませんね・・・`
    });
  }
  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: `出ました!占いではあなたの耳は${aomLabels[response.payload[0].displayName]}と出ています。`
  });
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);

.envはこちら

GOOGLE_APPLICATION_CREDENTIALS=./ダウンロードしたJsonファイル名 
CHANNEL_ACCESS_TOKEN=自分のチャンネルアクセストークン

考察

AutoML Visionで作成した鼓膜画像分類モデルを使った鼓膜画像認識Botを作成することが出来ました。実際テストすると急性中耳炎・滲出性中耳炎・正常鼓膜の判別は9割ほどの正解率でした。
最初は信頼度(0~1の間の数字で表される)も同時に表示しようとしたのですが下のねこの写真のように鼓膜以外の写真を送った場合でも高い信頼度(下の写真では1)が表示されてしまいます。
image.png

これはモデルは急性中耳炎と滲出性中耳炎と正常鼓膜の3つタグだけで鼓膜以外の画像を教師データとして登録していないためと思われます。
実用性を求めるなら1段回目として鼓膜画像検出を行い(ここに鼓膜以外の教師データを入れる)、2段階目として中耳炎の判定を行うような構成が必要と思われました。

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

Kinx 実現技術 - Yacc/Bison

コンパイラ・コンパイラの話

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。作ったものの紹介だけではなく実現のために使った技術を紹介していくのも誰の役に立つかもしれないしね。その道の人には当たり前でも、そうでない人にも興味をもって貰えるかもしれないので。ソースを具体的にさらすのは(アドホックにやってる部分もあるので)多少恥ずかしいが、どうせ OSS にしてるし、まぁいいか。

最初のテーマは構文解析。なお、以下のような感じで記事をプランしてみる。

  • コンパイル時編
    • 構文解析 ... Bison を使っている。kmyacc に変えるかもしれない。(本記事)
    • Switch-Case ... 色々条件を判断。結構複雑。
  • 実行時編
    • VM(Virtual Machine) ... gcc ではダイレクト・スレッディング。
    • Garbage Collection ... ザ・マーク・アンド・スイープ。静的スコープの扱い。
    • JIT ... ネイティブの機械語コードにコンパイルするために採用した方法。
    • Fiber ... 思い付きで実装したら割と動いたのでその方法。

Kinx での構文解析

Yacc/Bison とは

Kinx での構文解析は今現在 GNU Bison を使っている。いわゆる LALR(1) の Yacc 系統のパーサ・ジェネレータ。コンパイラを作る時のジェネレータであるため、俗に コンパイラ・コンパイラ と呼ばれている。GNU Bison 自体は GPLv2 だが、その出力(パーサ自体)は例外条項によって自分でライセンスを付けることができる。

元々コンパイラを書く場合、手書きの再帰下降構文解析で実装するのが好きだったのだが、あとから文法を理解しやすい、変えやすいという点で Yacc にしよう、と思ったので使った。最初は miniyacc という簡易 Yacc を使っていたのだが、エラーリカバリ処理がない という致命的な弱点のため現在は GNU Bison を使うように修正。ただ、Windows で Bison を使うのは面倒なのでここだけ Linux で修正する、という作業が非常に面倒くさいことになっている。それもあって、kmyacc に乗り換えようかなー、というのが現時点のステータス。

ちなみに、再帰下降構文解析(再帰下降パーサ)とは、BNF で書いた時の構文ルールを関数呼び出しの形で表したもの。結構書いてて面白い。例えば、expr → ... → factor という流れで、factor には '(' expr ')' が定義されているとき、factor 関数の中で '(' を認識したら再帰的に expr 関数を呼ぶことで構文解析していく。トップダウン的に下方向に向かって解析し、途中再帰的に上に戻ってくるので再帰下降と呼ばれている。詳しくは Wikipedia も参照してみるといいかも。

構文解析・字句解析

構文定義ファイルは src/kinx.y にあり、構文自体は BNF という記法で書く。尚、Yacc(Bison) の動きに関しては、Rubyソースコード完全解説 にある 第9章 速習yacc がわかりやすくて良いと思ったので紹介しておきます。

今回作ったパーサに関して、特徴的なところをピックアップしてみよう。

字句解析部分

コンパイラを作る場合、実は構文解析の前に 字句解析 というフェーズが入る。だいたい以下のような流れで進む。

字句解析
   ↓
構文解析
   ↓
意味解析

字句解析は yacc と対になって lex というツール(Bison の場合 flex)を使うのも標準的なのだが、私は lex は使わない。字句解析は手書きしている。src/lexer.c がソース。いろいろなステータスを細かく制御するのに都合がいいし、手書きでもそれほど面倒な処理ではないので、今回も手書きした。このほうが都合が良いのは、例えば、using のような構文は、字句解析レベルで実施しており、入力ソースを動的に入れ替えるようになっている。

一般的に構文解析では、主に 見た目 だけ扱う。見た目さえ正しければよい。内容が妥当かどうかは次の意味解析のフェーズで行う。

正規表現リテラル

あと、Factor のところに正規表現リテラルが来るのだが、字句解析レベルで /= を DIVEQ と認識してしまっているので、DIVEQ が来た時に /= で始まった正規表現リテラルと判断するようにしている。状態遷移的に DIVEQ として扱われる場所と異なるのでこれはコンフリクトしない。正規表現の最初の文字が = であることをきちんと認識すればよいという感じで扱った。

kmyacc について

kmyacc は森公一郎氏(故人)が作った素晴らしいパーサ・ジェネレータ。非常に昔からお世話になっているツールの一つ。Yacc を使おうというときはたいがいこれを使っていた。なぜ今回使っていなかったのか、それはビルド環境に yacc 含めたかったのでライセンスの緩い miniyacc にしたから(MIT License)。現時点では Bison に変えてしまったので kmyacc で全然かまわない。正直、森氏は後世に名を遺すべき人であって、kmyacc も遺しておくべき作品だと思う。彼が亡くなったとき(2015年)は、それはもう日本のプログラミング業界でひと騒動あったのも記憶に新しい。

森氏は LSI-C の作者でもあり、試食版というフリーの LSI-C にも(かなり)昔お世話になった。出力アセンブラが美しいことで有名だ。実際、当時は C では遅いのでサブルーチンはアセンブラで書く ということをやっており、これは今でいう Java は遅いのでライブラリは JNI で書く と同じことだと思えばよい。時代が変わってもやってることは変わんないな。扱うレイヤが変わっただけだ。

その時、当時使っていた Turbo C のアセンブラ出力と LSI-C のアセンブラ出力を見比べて 確かになんて美しいんだ、と感動した記憶もある。どう違うかというと、LSI-C は極限までレジスタを割り当てるので、ほとんどスタックを汚さない。Turbo C は関数呼び出しの引数は基本スタック渡しで汚いなー、と。アセンブラでサブルーチンを作る際に LSI-C の出力コードは大いに参考になった。

話は尽きないので、昔話はここでおしまいにしよう。

つまりは、森氏の名前と kmyacc という作品を後世に遺すべく、今後 Bison から kmyacc に鞍替えする計画を立てるかもしれない、ということだ。

おわりに

あまり構文解析処理自体には触れていないが、ソースコードを見てみると何となく理解できるかもしれない。もちろん、バグがあれば教えて下さい。お願いします。

最後はいつもの以下の定型フォーマットで締め括り。

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

TypeScriptでモジュールのimport/exportやrequire

TypeScriptを書いていて、たまにimport出来なかったりして、そしたらrequireしたりとか、モジュールのインポートはふんいきでやっていたんですが、TypeScriptで古いJSのライブラリの型定義を書いていて気持ち悪かったので、調べました。

2つのモジュールインポート方法

CommonJS (Node.js)

インポート方法

  • require

エクスポート方法

  • module.export
  • export

exportとmodule.exportの違い

例えば、require("some-module")とすると、id="some-module"で以下のようなコードが呼ばれる

const module = new Module(id)
(function(export, module) {
  /* モジュール本体のコードがここに差し込まれる */
  return module.export
}(module.export, module)

exportと、module.exportはモジュールが評価される関数への引数で、exportは、module.exportオブジェクトへのポインタ、関数が返しているmodule.exportは、引数で渡されているmoduleexportプロパティなので、モジュール内でexportを上書きすると、exportが元のオブジェクトを指さなくなるので、期待通りに動かない。同じ理由で、moduleを上書きしてしまってもダメ。

ES6

インポート方法

個別
  • import { x } from "module"
  • import * as x from "module"
export defaultされたもの
  • import x from "module"

エクスポート方法

個別
  • export x
なにか1つだけexportしたい場合
  • export default x

TypeScriptとJavaScriptでの違い

ない

どちらを使うか

インポートしたいモジュールが使っているやり方に合わせる。

まとめ

適当にimportやらrequireしていたのを、webpackがきれいにしてくれていたんですね。わかってよかった。

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

React Native × React Navigation v5 で Deep linking

はじめに

またしても React Navigation v5 の記事です。
今回は Deep linking を実装していきます。

下準備

iOS 用の設定をします。

URL Typesの設定をおこないます。

Screen Shot 2020-03-19 at 19.39.02.png

次に、 AppDelegate.mを修正します。

Screen Shot 2020-03-19 at 19.48.08.png

コードはこちらです。

#import "AppDelegate.h" // 元からある
#import <React/RCTLinkingManager.h>

...

- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
 return [RCTLinkingManager application:application
                  continueUserActivity:userActivity
                    restorationHandler:restorationHandler];
}

これだけで、 Safari などで<URL Schemas>://xxxxのように入力すれば、アプリを開くことができます。
Simulator Screen Shot - iPhone 11 Pro Max - 2020-03-19 at 22.44.05.png

これでアプリへのアクセスはできるようになりました。
各ページへの遷移は React Navigation と組み合わせて使うと便利です。
以下ではその方法を実装していきます。

React Navigation v5 で Deep linking

公式ドキュメントを参考に、useLinkingを使用します。

const App: React.FunctionComponent = () => {
  const ref = useRef();
  const { getInitialState } = useLinking(ref, {
    prefixes: ['srn5://'],
    config: {
      Details: 'details',
    },
  });
  const [isReady, setIsReady] = useState(false);
  const [initialState, setInitialState] = useState();

  useEffect(() => {
    Promise.race([
      getInitialState(),
      new Promise((resolve) => setTimeout(resolve, 150)),
    ])
      .catch((e) => {
        console.error(e);
      })
      .then((state) => {
        if (state !== undefined) {
          setInitialState(state);
        }

        setIsReady(true);
      });
  }, [getInitialState]);

  if (!isReady) {
    return null;
  }

  return (
    <AuthProvider>
      <NavigationContainer initialState={initialState} ref={ref}>
        <HomeNavigator />
      </NavigationContainer>
    </AuthProvider>
  );
}

refを定義して、NavigationContaineruseLinkingに渡しています。
useLinkingconfigでパスと画面を紐づけています。

HomeNavigatorは下記の形で定義しています。

import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';

const Stack = createStackNavigator();
const HomeNavigator = () => (
  <Stack.Navigator>
    <Stack.Screen name="Home" component={Home} />
    <Stack.Screen name="Details" component={Details} />
  </Stack.Navigator>
)

通常のアクセスだとHomeが表示されますが、srn5://detailsにアクセスするとDetailsが直接開きます。

おわりに

今回は、 Deep linking の実装について紹介しました。

実際に利用する場合、ユニバーサルリンク(https://~~~)という形で利用するのが現実的かと思いますが、まずはこの方法で簡易的なアプリへのリンクを設定することができます。

参考

https://reactnative.dev/docs/linking#handling-deep-links
https://reactnavigation.org/docs/deep-linking/

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

JavaScriptで超お手軽香川県民認証をつくる

前文

なんかヤバげな条例がうどんでおなじみのあの県で可決されてしまったのでノリと勢いと恨みにまかせてウェブサイトに県民認証を追加します。

明日のことを気にしてばっかじゃ毎日タイクツしちゃいますのでサクッと対応して気の滅入る条例のことは一旦忘れることにしましょう。

しれっと初投稿です。よろしくお願いします。割とひどい記事かもしれません。

前提

すでにあるサイトにちょっとだけ手を入れて認証ぶち込みてえなというのを想定します

image.png

https://github.com/project-alisa/Ouranos

このサイトのシステム(Ouranos)はLaravelで構築しており、viewはbladeで書いています。

求められそうな仕様

  • どのページにランディングされてもちゃんと確認する
  • 県民かどうかを聞く
  • 県民なら弾く
  • 県民じゃないならいらっしゃいませ

まぁやってることはえっちなサイトの年齢認証と同じですね。

localStorageが使えそう

localStorageとはWeb Storage APIという所謂キーバリューストアの一つで、オリジンごとにデータを保存できる仕組みです。

https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API

どう使うのか

コード例は localStorage ですが sessionStorage も同様に使えます。

処理 コード例
取得 localStorage.getItem('key')
変更 localStorage.setItem('key','value')
削除 localStorage.removeItem('key')
全削除 localStorage.clear()

Notice : 一応 localStorage['key']localStorage.key のような記法での参照や変更も可能ですがMDNでは getItem() 等のメソッドを使う方法が推奨されています

なるほどシンプルではありませんか。

ただし制限があり保存できる値は文字列に限られます
例えばオブジェクトをぶち込んだ場合、文字列に変換され [Object object] が返ってきます。悲しい。
JSONやオブジェクトを入れたいならば JSON.stringify() などで文字列化してからストアする必要があります。

作る

Laravelのbladeに組み込みますがやってることはHTMLとJSだけですので基本的にどんなサイトでも通用します。

見やすさ(シンタックスハイライト)重視で.htmlにしていますが実際には.blade.phpです。

wall-kagawa.html
<dialog id="wall-kagawa-dialog" style="height: 500px">
    <div class="msgbox" style="width: 750px;">
        <div class="msgboxtop">確認</div>
        <div class="msgboxbody" style="text-align: center">
            <span style="font-size: 60px">&#x26a0;</span>
            <p style="font-size: 23px">あなたは香川県民ですか?</p>
            <hr>
            <p>
                当サイトは、香川県ネット・ゲーム依存症対策条例第11条各項に基づき、<br>
                香川県内からのアクセスはすべてお断りしています。<br>
                なお、この確認は当サイトが同条例に対し賛同を示すものではありません。
            </p>
            <p style="font-size: 13px;">
                This dialog is displayed based on the Kagawa Prefectural Internet and Game Addiction Measures Ordinance.<br>
                If you are not a Kagawa citizen, press "いいえ" to continue.
            </p>
            <div id="localStorageUnavailable">
                <hr>
                <p style="font-size: 14px;">
                    あなたの使用しているブラウザは何らかの理由でlocalStorageを利用できません。<br>
                    ページ遷移ごとにこの確認ダイアログが表示されます。<br>

                </p>
            </div>
        </div>
        <div class="msgboxfoot">
            <a href="javascript:enableWallKagawa()" class="button jw">はい</a>
            <a href="javascript:disableWallKagawa()" class="button jw">いいえ</a>
        </div>
    </div>
</dialog>
<dialog id="wall-kagawa-blocker" style="height: 500px">
    <div class="msgbox" style="width: 750px;">
        <div class="msgboxtop">警告</div>
        <div class="msgboxbody" style="text-align: center">
            <span style="font-size: 60px">&#x26d4;</span>
            <p style="font-size: 23px">閲覧を制限しています</p>
            <hr>
            <p>
                当サイトは、香川県ネット・ゲーム依存症対策条例第11条各項に基づき、<br>
                香川県内からのアクセスはすべてお断りしています。<br>
                なお、この確認は当サイトが同条例に対し賛同を示すものではありません。
            </p>
            <p style="font-size: 13px;">
                This dialog is displayed based on the Kagawa Prefectural Internet and Game Addiction Measures Ordinance.
            </p>
        </div>
        <div class="msgboxfoot">
        </div>
    </div>
</dialog>
<style>
    dialog#wall-kagawa-dialog::backdrop, dialog#wall-kagawa-dialog + .backdrop{
        background: rgba(0,0,0,0.8);
    }
    dialog#wall-kagawa-blocker::backdrop, dialog#wall-kagawa-blocker + .backdrop{
        background: black;
    }
</style>

<script>
    /**
     * WebStorageAPI 使用可否検証関数
     * https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
     * */
    function storageAvailable(type) {
        var storage;
        try {
            storage = window[type];
            var x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        }
        catch(e) {
            return e instanceof DOMException && (
                // everything except Firefox
                e.code === 22 ||
                // Firefox
                e.code === 1014 ||
                // test name field too, because code might not be present
                // everything except Firefox
                e.name === 'QuotaExceededError' ||
                // Firefox
                e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
                // acknowledge QuotaExceededError only if there's something already stored
                (storage && storage.length !== 0);
        }
    }

    const wallKagawa = document.getElementById('wall-kagawa-dialog');
    dialogPolyfill.registerDialog(wallKagawa);
    const wallKagawaBlocker = document.getElementById('wall-kagawa-blocker');
    dialogPolyfill.registerDialog(wallKagawaBlocker);
    wallKagawa.addEventListener('cancel',function(event){
        event.preventDefault();
    });
    wallKagawaBlocker.addEventListener('cancel',function(event){
        event.preventDefault();
    });

    function disableWallKagawa(){
        localStorage.setItem('wallkagawa',false);
        wallKagawa.close();
        console.info('WallKagawa disabled!');
    }
    function enableWallKagawa(){
        localStorage.setItem('wallkagawa',true);
        wallKagawa.close();
        wallKagawaBlocker.showModal();
    }

    if(storageAvailable('localStorage')){
        console.info('localStorage available.');
        document.getElementById('localStorageUnavailable').setAttribute('style','display:none;');
    }

    if(localStorage.getItem('wallkagawa') === "true"){
        wallKagawaBlocker.showModal();
        console.error('WallKagawa enabled!');
        console.info('Are you not Kagawa citizen? run %clocalStorage.removeItem(\'wallkagawa\')','color:skyblue')
    }else if(localStorage.getItem('wallkagawa') !== "false"){
        wallKagawa.showModal();
    }else{
        console.info('WallKagawa is already disabled.');
    }


</script>

できました

あとは適宜viewにぶち込んでやるだけです。今回は大まかなレイアウトを定義したbladeがありましたのでそこで @include('wall-kagawa.blade') しました

やってること

1. ダイアログを用意する

HTMLでサクッと組みましょう

dialog要素は現時点ではChromium系など一部でしか動きませんので、Firefoxなどに対応するためにはdialog-polyfillを導入する必要があります。MDNも参照してください。

2. WebStorageAPIが使えるかどうかをチェック

MDNにコード例があるのでありがたくコピペします。

使えない場合はサーセン文言も入れておきます。Safariのプライベートモードは事実上localStorageが使えないようなのでコケるはずです。一応「あんたのブラウザだめやでー」というメッセージも置いておきます。後で試してみよ。

3. ESCキーキャンセルを無効化する

dialog要素はデフォルトではEscキーを押すなどするとキャンセルされる、つまりダイアログが閉じられるのですが、こいつが閉じられては意味がありません。
Escキーなどでダイアログを閉じるときcloseイベントが発火しますのでイベントリスナで preventDefault() します。

4. 状況に応じてWallKagawaを発動する

3つの状態を想定します

  1. localStorage.getItem('wallkagawa') === "true" : WallKagawa発動状態
    読み込み時点でWallKagawaを全面に表示します。
  2. localStorage.getItem('wallkagawa') !== "false" : WallKagawa待機状態
    ダイアログを提示し、県民かそうでないかを聞きます。ここでの選択に応じてlocalStorageに値をストアし以降の状態を変化させます。
  3. localStorage.getItem('wallkagawa') === "false" : WallKagawa無効状態
    県民でないと確認が取れたのでWallKagawaはおやすみです。

実際に動作している様子

確認ダイアログ

image.png

いいえを押した状態(WallKagawa無効化)

image.png
みて!環ちゃんがピースしているよ かわいいね

はいを押した状態(WallKagawa有効化)

image.png
香川県議会が条例を可決したせいで、香川県民は環ちゃんのピースを見れなくなってしまいました
香川県議会のせいです
あ~あ

さいごに

HTMLとJSだけでシンプルにやりました。

本来ならばMiddleware噛ませてHTTP451でも返したほうがいいんでしょうがまぁ香川県に対してだけなのでいいやってことにします。嘘です。あとでもうちょっと考えます。

ミリオンをメインに追っかけているPですが個人的にはデレマスの三好紗南ちゃんがどうなるのかが気がかりです。

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

GAS 特定の列の最終行番号を取得する

とにかくめんどくさいので自作。
普通にちゃんと全ての行を埋めてくれるようなスプレッドシートだったらもっと簡単に書けるが、
所々空の行が挿入されていたり、穴ぼこがたくさんあるときにうまく取得できない時がある。
そんなときにこれが使えそう。

var headers = speadersheet.getDataRange().getValues()[1]

// 列とか変更されると辛いので、動的に常に列番号を取得
function getColumnRow(columnName) {
  return header.map(function(h) {
    return h.replace(/\r?\n/g, '')
  }).indexOf(columnName)
}

function getAlphabets() {
  return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
}

// 特定のカラムの最終行を取得
function getLastRowByColumn(columnName) {
  var index = getColumnRow(columnName)
  var alph = getAlphabets()[index]
  var range = alph+':'+alph
  var rangeData = sheet.getRange(range).getValues()
  return rangeData.filter(function(data) {
    return data[0]
  }).length
}
  1. 指定の列名で列番号を取得する
  2. それがアルファベットの配列の中でどこに配置されているか
  3. A:Aみたいにrangeを作成する
  4. A:A列の値を前取得
  5. 空を排除
  6. 配列の大きさを返せばそれが最終行(などにヘッダーが複数行で構成されていたら、5で減るので帳尻合わせ)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gulp さんぷるこーど いろいろ

var gulp = require("gulp");
var del = require("del");
var rename = require("gulp-rename");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var sass = require("gulp-sass")

var copyTask = async function () {
    return gulp.src("./src/sample1.txt")
        .pipe(gulp.dest("./dist"));
};

var delTask = async function () {
    return del("./dist/*");
};

var renameTask = async function () {
    gulp.src("./src/sample1.txt")
        .pipe(rename({ suffix: ".min" }))
        .pipe(gulp.dest("./dist"));
};
var concatTask = async function () {
    gulp.src(["sample1.txt", "sample2.txt"], { cwd: "./src" })
        .pipe(concat("bundle.txt"))
        .pipe(gulp.dest("./dist"));
};

var minifyTask = async function () {
    gulp.src("./src/sample1.js")
        .pipe(uglify())
        .pipe(gulp.dest("./dest"));
};

var minifyTask2 = async function () {
    gulp.src(["sample1.js", "sample2.js"], { cwd: "./src" })
        .pipe(concat("bundle.js"))
        .pipe(uglify())
        .pipe(rename({ suffix: ".min" }))
        .pipe(gulp.dest("./dist"));
};

var sassTask = async function () {
    gulp.src(["sample1.scss", "sample2.scss"], { cwd: "./src" })
        .pipe(concat("bundle.scss"))
        .pipe(sass({ outputStyle: "compressed" }))
        .pipe(rename({suffix: ".min"}))
        .pipe(gulp.dest("./dist"));
};

module.exports.default = sassTask;//minifyTask2;//concatTask;//renameTask;//delTask;//copyTask;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

条件分岐

はじめに

Javascriptで条件分岐を行う際、
3つの方法が存在します。

if文、else文、else if文です。

この3つについて説明していきます。

if文

「もしも○○だったら〜」
という、処理を行いたい時に使用します。

else文

今までの処理で、(true)にならなかった時に
使用されます。

else if文

1の場合、2の場合、それ以外の場合、
というように条件を複数設定するときに使用されます。

記述方法

テストの点数で例えてみましょう。

var seiseki = 70;

if (seiseki > 60){
  alert("あなたの成績は60点以上");
}else if (seiseki > 50){
  alert("あなたの成績は50点以上です");
}else{
  alert("あなたの成績は50点未満です。");
}

このようになり、記述方法で、
alertで表記されるのは、「あなたの成績は60点以上」になります。

注意事項

else if文を使用する際は、
実行順序に気を付けましょう。

var seiseki = 80;

if (seiseki > 90){
  alert("あなたの成績は90点以上です。");
}else if (seiseki > 70){
  alert("あなたの成績は70点以上になります。");
}else if (seiseki > 80){
  alert("あなたの成績は80点以上になります。");
}else{
 alert("あなたの成績はそれ以下です。");
}

上記の記述方法では、80点となっています。
なので、本来は「あなたの成績は80点以上になります。」と、
表記されるのですが、
「あなたの成績は70点以上なります。」と表記されます。

理由として、
if文は、記述順で処理されてしまうからです。
条件式では、正しい(true)となった時点で、
処理されてしまい、それ以降は処理されません。

間違えた表記をしないように、注意しましょう。

本日はありがとうございました。

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