20200706のJavaScriptに関する記事は26件です。

【JavaScript】IF文で「指定した変数がいずれかの値と一致したらTRUE」条件を記述

ちょっと面白かったので、メモを兼ねて投稿します。

コードの実例

以下のような、一つの変数しか使っていないOR条件のIF文をもっと短く書けないかなぁと思いました。

if (value === 1 || value === 2 || value === 3
  || value == 4 || value == 5 || value == 6
  || value == 7 || value == 8 || value == 9) {
  // 処理
}

以下のように、配列にすると短く書けますね。

if ([1, 2, 3, 4, 5, 6, 7, 8, 9].includes(value)) {
  // 処理
}

ただしIEではincludes()メソッドはサポートしていないようです。
そこで、代わりにindexOf()メソッドを使う手もあります。

if ([1, 2, 3, 4, 5, 6, 7, 8, 9].indexOf(value) !== -1) {
  // 処理
}

これならIE11では動くはずです。
どのような書き方にせよ、他の人がぱっと見で理解できるのが一番ですね。

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

Intl.DateTimeFormat による明治以前の和暦の扱い

TL;DR

少なくとも Chrome, Firefox, Edge の実装において、明治改暦以前の日付について、 Intl.DateTimeFormat の返す和暦は多分に怪しいです。

  • 元号の始まりは、実際の改元日ではなく、和暦1における改元日の月日を西暦2(グレゴリオ暦3/ユリウス暦4)における月日と解釈した日となっている。
  • 南北朝時代の元号は、南朝の元号と北朝の元号とが混在しており、年の数値も正しくない。
  • 初期の元号については、通説と異なる扱いとなっている。

動作確認環境

本記事では以下のブラウザでの挙動について記載します。

  • Google Chrome 83.0.4103.116
  • Mozilla Firefox 78.0.1
  • Microsoft Edge 83.0.478.58

※ 因みに Internet Explorer 11 では挙動が異なりました(1867年以前の日付は常にエラー、1868年1月1日より明治元年)。本記事では Internet Explorer は無視します。

はじめに

現行の ECMAScript の標準では、ロケールを指定して日時をフォーマット化するには Intl.DateTimeFormat を使用します。
例えば以下のようにすることで、日本の和暦で日付を出力することができます。

var option = { era: "long", year: "numeric", month: "long", day: "numeric" };
var formatter = new Intl.DateTimeFormat("ja-JP-u-ca-japanese", option);
console.log(formatter.format(new Date("2020-07-06")));
// "令和2年7月6日" と出力される

Intl.DateTimeFormat の仕様の詳細については以下を参照してください。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat

改元日の扱い

それでは、令和, 平成, 昭和, … の改元前後の日付を和暦で出力してみましょう。
ちゃんと改元の日から元号が変わっているようですね――

console.log(formatter.format(new Date("2019-05-01")));    // 令和1年5月1日
console.log(formatter.format(new Date("2019-04-30")));    // 平成31年4月30日
console.log(formatter.format(new Date("1989-01-08")));    // 平成1年1月8日
console.log(formatter.format(new Date("1989-01-07")));    // 昭和64年1月7日
console.log(formatter.format(new Date("1926-12-25")));    // 昭和1年12月25日
console.log(formatter.format(new Date("1926-12-24")));    // 大正15年12月24日
console.log(formatter.format(new Date("1912-07-30")));    // 大正1年7月30日
console.log(formatter.format(new Date("1912-07-29")));    // 明治45年7月29日
console.log(formatter.format(new Date("1868-10-23")));    // 明治1年10月23日
console.log(formatter.format(new Date("1868-10-22")));    // 明治1年10月22日
// 以上において "1年" とあるのは Firefox では "元年" と出力される

……いや、おかしいですね。
慶応から明治への改元は西暦1868年10月23日の筈なのに、その前日の1868年10月22日も "明治1年" になっていますね。

これはもしや、明治改元が立年改元(改元年の元日に遡って新元号を適用する)であったことを反映しているのか?
と思って明治元年1月1日(西暦1868年1月25日5)を表示してみると……

console.log(formatter.format(new Date("1868-01-25")));    // 慶応4年1月25日

そんなことは無かった。
はて、では慶応と明治との境目はどこにあるのだろう?
と思って探ってみると、結果は以下の通り。

console.log(formatter.format(new Date("1868-09-08")));    // 明治1年9月8日
console.log(formatter.format(new Date("1868-09-07")));    // 慶応4年9月7日

1868年9月8日だそうで。
確かに明治改元の日(西暦1868年10月23日)は和暦で言うと9月8日ですが、これはおかしな話です。
new Date("1868-09-08") というのは西暦の1868年9月8日のことであって、明治改元の日である和暦の9月8日とは全く異なる日です。

「もしや」と思って慶応以前の改元日についても調べてみると、案の定。

// 慶応改元は西暦1865年5月1日 和暦4月7日
console.log(formatter.format(new Date("1865-04-07")));    // 慶応1年4月7日
console.log(formatter.format(new Date("1865-04-06")));    // 元治2年4月6日

// 元治改元は西暦1864年3月27日 和暦2月20日
console.log(formatter.format(new Date("1864-02-20")));    // 元治1年2月20日
console.log(formatter.format(new Date("1864-02-19")));    // 文久4年2月19日

// 文久改元は西暦1861年3月29日 和暦2月19日
console.log(formatter.format(new Date("1861-02-19")));    // 文久1年2月19日
console.log(formatter.format(new Date("1861-02-18")));    // 万延2年2月18日

いずれも実際の改元日ではなく、和暦における改元日の月日を西暦における月日と解釈したものを改元日として扱うという謎の挙動になっています。

では、「和暦における改元日の月日を西暦における月日と解釈したもの」が存在しない場合はどうなるのかと言うと……

// 寛永改元は西暦1624年4月17日 和暦2月30日
console.log(formatter.format(new Date("1624-03-01")));    // 寛永1年3月1日
console.log(formatter.format(new Date("1624-02-29")));    // 元和10年2月29日

「1624年2月30日」なんてのは西暦には存在しない日付ですが、「2月29日までは元和、3月1日からは寛永」として扱われています。

ユリウス暦時代の日付

前節で明治以前の改元日の扱いが怪しいということが確認できた訳ですが、日付をもっと遡っていくと、1582年10月にまた違った挙動が出てきます。
ユリウス暦からグレゴリオ暦への改暦です。

console.log(formatter.format(new Date("1582-10-17")));    // 天正10年10月17日
console.log(formatter.format(new Date("1582-10-16")));    // 天正10年10月16日
console.log(formatter.format(new Date("1582-10-15")));    // 天正10年10月15日
console.log(formatter.format(new Date("1582-10-14")));    // 天正10年10月4日
console.log(formatter.format(new Date("1582-10-13")));    // 天正10年10月3日
console.log(formatter.format(new Date("1582-10-12")));    // 天正10年10月2日
console.log(formatter.format(new Date("1582-10-11")));    // 天正10年10月1日
console.log(formatter.format(new Date("1582-10-10")));    // 天正10年9月30日

Date オブジェクトのコンストラクタは先発グレゴリオ暦で表現することになっているので、 new Date("1582-10-15") の前日を表す Date オブジェクトは new Date("1582-10-14") です。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/Date

しかし乍ら、文字列表現の際には慣例に倣って1582年10月4日以前の日付はユリウス暦で表すことになります。
これはロケールが "ja-JP-u-ca-japanese" の場合でも同じのようですね。

そうなると気になるのが、ユリウス暦時代の改元日の扱い。結果は以下の通り。

// 天正改元は西暦1573年8月25日 和暦7月28日
console.log(formatter.format(new Date("1573-08-07")));    // 天正1年7月28日
console.log(formatter.format(new Date("1573-08-06")));    // 元亀4年7月27日

// 元亀改元は西暦1570年5月27日 和暦4月23日
console.log(formatter.format(new Date("1570-05-03")));    // 元亀1年4月23日
console.log(formatter.format(new Date("1570-05-02")));    // 永禄13年4月22日

// 永禄改元は西暦1558年3月18日 和暦2月28日
console.log(formatter.format(new Date("1558-03-10")));    // 永禄1年2月28日
console.log(formatter.format(new Date("1558-03-09")));    // 弘治4年2月27日

見ての通り、ユリウス暦時代においても和暦における改元日の月日を西暦(ユリウス暦)における月日と解釈したものを改元日として扱うという挙動となっています。

南北朝時代の元号

ここまでの時点で既に謎に満ちた挙動なのですが、南北朝時代まで遡ると更にカオスになってきます。

console.log(formatter.format(new Date("1329-09-05")));    // 嘉暦4年8月28日
console.log(formatter.format(new Date("1329-09-06")));    // 元徳1年8月29日
console.log(formatter.format(new Date("1331-08-16")));    // 元徳3年8月8日
console.log(formatter.format(new Date("1331-08-17")));    // 元弘1年8月9日
console.log(formatter.format(new Date("1334-02-05")));    // 元弘4年1月28日
console.log(formatter.format(new Date("1334-02-06")));    // 建武1年1月29日
console.log(formatter.format(new Date("1336-03-07")));    // 建武3年2月28日
console.log(formatter.format(new Date("1336-03-08")));    // 延元1年2月29日
console.log(formatter.format(new Date("1340-05-05")));    // 延元5年4月27日
console.log(formatter.format(new Date("1340-05-06")));    // 興国1年4月28日
console.log(formatter.format(new Date("1346-12-15")));    // 興国7年12月7日
console.log(formatter.format(new Date("1346-12-16")));    // 正平1年12月8日
console.log(formatter.format(new Date("1370-07-31")));    // 正平25年7月23日
console.log(formatter.format(new Date("1370-08-01")));    // 建徳1年7月24日
console.log(formatter.format(new Date("1372-04-08")));    // 建徳3年3月31日
console.log(formatter.format(new Date("1372-04-09")));    // 文中1年4月1日
console.log(formatter.format(new Date("1375-06-03")));    // 文中4年5月26日
console.log(formatter.format(new Date("1375-06-04")));    // 天授1年5月27日
console.log(formatter.format(new Date("1379-03-29")));    // 天授5年3月21日
console.log(formatter.format(new Date("1379-03-30")));    // 康暦1年3月22日
console.log(formatter.format(new Date("1381-02-17")));    // 康暦3年2月9日
console.log(formatter.format(new Date("1381-02-18")));    // 弘和1年2月10日
console.log(formatter.format(new Date("1384-05-05")));    // 弘和4年4月27日
console.log(formatter.format(new Date("1384-05-06")));    // 元中1年4月28日
console.log(formatter.format(new Date("1387-08-29")));    // 元中4年8月21日
console.log(formatter.format(new Date("1387-08-30")));    // 至徳1年8月22日
console.log(formatter.format(new Date("1387-08-31")));    // 嘉慶1年8月23日
console.log(formatter.format(new Date("1389-02-16")));    // 嘉慶3年2月8日
console.log(formatter.format(new Date("1389-02-17")));    // 康応1年2月9日
console.log(formatter.format(new Date("1390-04-02")));    // 康応2年3月25日
console.log(formatter.format(new Date("1390-04-03")));    // 明徳1年3月26日
console.log(formatter.format(new Date("1394-07-12")));    // 明徳5年7月4日
console.log(formatter.format(new Date("1394-07-13")));    // 応永1年7月5日

これだけ見ても何がどうカオスなのかわかりづらいのですが、よく見ると南朝の元号北朝の元号とが混在しています。
南北両朝の元号と Intl.DateTimeFormat による扱いを表に纏めると、次のようになります。6

日付 南朝(大覚寺統) 北朝(持明院統) Intl.DateTimeFormat
1326年4月26日~ 嘉暦 嘉暦
1329年8月29日~ 元徳 元徳 元徳
1331年8月9日~ 元弘 元弘
1332年4月28日~ 正慶
1333年5月25日~ (元弘)
1334年1月29日~ 建武 建武 建武
1336年2月29日~ 延元 延元
1338年8月28日~ 暦応
1340年4月28日~ 興国 興国
1342年4月27日~ 康永
1345年10月21日~ 貞和
1346年12月8日~ 正平 正平
1350年2月27日~ 観応
1352年9月27日~ 文和
1356年3月28日~ 延文
1361年3月29日~ 康安
1362年9月23日~ 貞治
1368年2月18日~ 応安
1370年7月24日~ 建徳 建徳
1372年4月1日~ 文中 文中
1375年2月27日~ 永和
1375年5月27日~ 天授 天授
1379年3月22日~ 康暦 康暦
1381年2月10日~ 弘和 弘和
1381年2月24日~ 永徳
1384年2月27日~ 至徳
1384年4月28日~ 元中 元中
1387年8月22日 至徳1年8月22日
1387年8月23日~ 嘉慶 嘉慶
1389年2月9日~ 康応 康応
1390年3月26日~ 明徳 明徳
1392年閏10月5日~ 明徳
1394年7月5日~ 応永 応永

南朝・北朝いずれの元号を採用しているのか、基準が全くの謎です。

特に怪しいのがこれ。

console.log(formatter.format(new Date("1387-08-30")));    // 至徳1年8月22日

どういう訳か、この1日だけ「至徳」という元号になっています。
しかも、1387年は正しくは "至徳4年" であるべきなのに、何故か "至徳1年" と出力されます

初期の元号

更に更に遡って飛鳥時代。

console.log(formatter.format(new Date("0701-03-25")));    // 大宝1年3月21日
console.log(formatter.format(new Date("0701-03-24")));    // 朱鳥16年3月20日
console.log(formatter.format(new Date("0686-07-23")));    // 朱鳥1年7月20日
console.log(formatter.format(new Date("0686-07-22")));    // 白鳳15年7月19日
console.log(formatter.format(new Date("0672-01-04")));    // 白鳳1年1月1日
console.log(formatter.format(new Date("0672-01-03")));    // 白雉22年12月31日
console.log(formatter.format(new Date("0650-02-18")));    // 白雉1年2月15日
console.log(formatter.format(new Date("0650-02-17")));    // 大化6年2月14日
console.log(formatter.format(new Date("0645-01-04")));    // 大化1年1月1日
console.log(formatter.format(new Date("0645-01-03")));    // 大化0年12月31日
console.log(formatter.format(new Date("0644-01-03")));    // 大化-1年12月31日
console.log(formatter.format(new Date("0643-01-03")));    // 大化-2年12月31日

これもだいぶ通説と異なります。

  • 「朱鳥」は朱鳥元年9月9日(天武天皇崩御)を以て廃止されたとされているが、次の「大宝」制定までの間が全て「朱鳥」として扱われている。
  • 「白鳳」は「白雉」の別称とされる私年号(公文書に現れない元号)だが、672年~686年の間が「白鳳」として扱われている。
  • 「白雉」は白雉5年10月10日(孝徳天皇崩御)を以て廃止されたとされているが、671年までの間が全て「白雉」として扱われている。
  • 「大化」は大化元年6月19日(孝徳天皇即位)を以て制定されたとされているが、それ以前の期間が全て「大化」として扱われている。

大化より前の期間や元号の空白期間については仕方無いとしても、「白鳳」の扱いはかなり謎です。

まとめ

そんな訳で、少なくとも Chrome, Firefox, Edge の実装において、明治改暦以前の日付について、 Intl.DateTimeFormat の返す和暦は多分に怪しいということがわかりました。
明治以前の和暦を正しく扱いたい場合は、自分で実装し直した方が良いでしょう。


  1. 本稿において和暦とは、日本の元号紀年法に、明治5年以前は天保暦等の太陰太陽暦(所謂旧暦)を、明治6年以降はグレゴリオ暦を組み合わせたものを指す。 

  2. 本稿において西暦とは、キリスト紀年法に、1582年10月4日以前はユリウス暦を、1582年10月15日以降はグレゴリオ暦を組み合わせたものを指す。 

  3. グレゴリオ暦: 我々が普段使っている暦。平年の日数は365日で、400年に97回の閏日が挿入される。 

  4. ユリウス暦: グレゴリオ暦採用以前のヨーロッパで採用されていた暦。1582年10月15日(ユリウス暦10月5日)にローマ・カトリック教会で採用された後、順次広まっていった。 

  5. 日本におけるグレゴリオ暦の採用は1873年(明治6年)なので、明治元年時点では和暦と西暦の月日は異なる。 

  6. 表中の日付は日本の旧暦による年にキリスト紀年法を当て嵌めたもの。ユリウス暦の年とは必ずしも一致しない。 

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

オブジェクト・クラス・インスタンスの勉強(100 days of code)

はじめに

昨日から始めた100days of codeの2日目になります。
今日はクラス・インスタンス・オブジェクトの概念について復習しました。

オブジェクトとは?

オブジェクトとは、関連する変数(値)とメソッド(動作)をまとめたものに名前を付けたものになります。
JavaScriptであれば、

const person = {
 name: 'Yuki',
 gender: '男性'
};

という形でオブジェクトの中にkeyとvalueをセットします。
ここで述べているkeyはnameとgenderを指し、valueはYukiと男性を指します。

クラスとは?

クラスとは、オブジェクトの設計図になります。
クラスの中で変数やメソッドを定義し、関連する変数やメソッドはこの時点で決まります。

日本語を例にクラスを作ると、

スライムクラス

*スライムの状態
・color(スライムの色)
・level(スライムのレベル)

*スライムの機能
・attack(攻撃)
・defend(防御)

という感じで、スライムのクラス(設計図)の中に変数とメソッドを定義しています。
これがクラスと呼ばれるものです。

インスタンスとは?

インスタンスとは、オブジェクトの実物であり、クラス(設計図)から生成されるものになります。

先ほど作ったスライムクラスからインスタンスを生成すると、

*スライムクラスのインスタンス(その1)
色:青、レベル:1、攻撃:1、防御:1

*スライムクラスのインスタンス(その2)
色:黄色、レベル:2、攻撃:2、防御:2

という感じです。
これが、1つ1つのスライムがクラスから作られたインスタンスに当たります。

最後に

クラスやインスタンスのような概念はどこで役に立つかが分からない知識を他人に分かりやすく説明することが難しいと気付かされました。
明日も頑張ります。

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

クラウド AI 開発講座 by 高専キャリア × マイクロソフト #1

※ 本記事は「高専キャリアとマクロソフトによるクラウド AI 開発講座」の進捗報告です.

最近観ているアニメは文豪ストレイドッグスです.わたぽん(@what_a_pon)です.
高専キャリアとマイクロソフトによるクラウド AI 開発講座が始まりました.最終的なミッションは「Azure を活用した Teams 連携 Bot を開発せよ」です.Teams 使ったことない.

要約

  • Azure はマイクロソフト製のクラウドサービス
  • 超簡単に物体検出や表情分析ができる
  • 表情分析の結果を JSON 形式でゲットした

Azure ってなんぞや

Microsoft Azure

Azure は,マイクロソフトが提供しているクラウドサービスで,AI や分析に関するサービスを手軽に利用することができます.テキストのネガポジ分析や画像認識,表情認識など分野はさまざまなので「こんな API あるかな?」と気になった方は探してみましょう!

料金に関しては従量課金制になっているため,使った分だけお金が飛んでいきます.学生は年間 $100 分のクレジットが無料でゲットできるみたいなので是非触ってみてください.

やったこと

Face API を使った表情分析

Face API を使うと,入力画像から「顔」の位置を特定したり,表情を分析したりすることができます.今回は de:code 2020 の講義を参考にして動かしました.

公式ドキュメントに書かれているサンプルコードを使うとすぐに動かすことができます.今回は JavaScript で書きましたが,Python や Go でも試すことができます.

↓サンプルコードでできたもの
Screen Shot 2020-07-06 at 21.43.00.png

image to analyzeの部分に任意の画像へのリンクを入力し,Analyze Faceボタンを押すと,結果が JSON として返ってきます.

Screen Shot 2020-07-06 at 22.22.00.png

抜粋.json
{
    "faceRectangle": {
        "top": 179,
        "left": 30,
        "width": 381,
        "height": 381
    },
    "faceAttributes": {
        "smile": 1,
        "gender": "male",
        "age": 29
    },
    "emotion": {
        "anger": 0,
        "happiness": 1,
        "neutral": 0,
        "surprise": 0
      },
}

実際に分析した結果です.顔の位置や表情,性別,感情などが出力されていることがわかります.とっても簡単ですね!いくつかの画像で試しましたが,かなりの精度で分析されています.

次やること

  • 返ってきた JSON を使って簡易的なアプリを作る

参考文献

de:code 2020
Azure Face API クイックスタート

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

Array.slice()で新たな配列として返す。

(対象)非エンジニア一般人 想定

JavaScriptの配列の数え方みたいなん、あるじゃないですか、、インデックスってやつですかね。

Array.slice() 使ったときの負のインデックスの数え方がうろ覚えだったのでメモっときます~~

const beautifull = ['長澤まさみ','剛力彩芽','橋本環奈','','新垣結衣','広瀬アリス','中条あやみ'];

これら人気女優を、新たな配列

'橋本環奈','','新垣結衣','広瀬アリス'

として返すときにどうするのか見ていきたいと思います。

結論から

const beauty = ['長澤まさみ','剛力彩芽','橋本環奈','新垣結衣','広瀬アリス','中条あやみ'];
const kawaii = beauty.slice(2,-1);
console.log(kawaii);

又は

const beauty = ['長澤まさみ','剛力彩芽','橋本環奈','新垣結衣','広瀬アリス','中条あやみ'];
const kawaii = beauty.slice(-4,-1);
console.log(kawaii);

です!!

構文 arr.slice([begin[, end]])

const kawaii = beauty.slice(-4,-1); の場合、、

数え方が、
-4→負のbeginは {0 ....,-6,-5,-4,-3,-2,-1}となり
-1→負のendは {....,-6,-5,-4,-3,-2,-1,0}となるようですね。

もし間違ってたら教えてください。

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

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

React hooks で複数のcheckboxを実装してみる

useStateとuseEffectを使って複数のcheckboxを実装してみる

いろいろ手探りで書いてみたのでメモに残しておく。

仕様について

  • チェックボックスが一つ以上checkedになった場合のみ送信ボタンを表示させたい。
  • checkedアイテムのidを、送信ボタンが押されたタイミングで配列で送信したい。
checkBox.jsx
import React, { useState, useEffect } from "react"

//checkboxのvalueリスト
const checkLists = [
  "パン",
  "おにぎり",
  "焼き肉",
  "ラーメン",
  "たこ焼き",
  "アイスクリーム",
]

//checkboxコンポーネント
const CheckBox = ({id, value, checked, onChange}) => {
  return (
    <input
      id={id}
      type="checkbox"
      name="inputNames"
      checked={checked}
      onChange={onChange}
      value={value}
    />
  )
}

const CheckBoxList = () => {

//checkedItemsは初期値を空のオブジェクトにする
  const [checkedItems, setCheckedItems] = useState({})
//ひとつでもcheckedになっている場合にのみ送信ボタンを表示させたいので、全体のStateを監視する
  const [isBtnHide, setIsBtnHide] = useState(true)

  useEffect(() => {
//checkedItemsが空では無い場合、送信ボタンを表示させる
    Object.keys(checkedItems).length && setIsBtnHide(false)
//すべてのcheckedItemの値がfalseの場合に送信ボタンを表示させる
    setTimeout(() => {
      if (
        Object.values(checkedItems).every(checkedItem => {
          return checkedItem === false
        })
      ) {
        setIsBtnHide(true)
      }
    },100);
  }, [checkedItems])

  const handleChange = e => {
//checkedItemsのstateをセット
    setCheckedItems({
      ...checkedItems,
      [e.target.id]: e.target.checked
    })
    console.log('checkedItems:', checkedItems)
  }

  const dataSendBtn = e => {
//既定のイベントをキャンセルさせる
    e.preventDefault()
//送信ボタンを押したタイミングで、checkedItemsオブジェクトのvalueがtrueのkeyのみを配列にしてconsoleに表示させる
    const dataPushArray = []
    for (let [key, value] of Object.entries(checkedItems)) {
      if (value) {
        dataPushArray.push(key)
      }
    }
    console.log("dataPushArray:", dataPushArray)
  }

  return (
    <>
      <h2>好きな食べ物</h2>
      <form>
        {checkLists.map((item, index) => {
          index = index + 1
          return (
            <label htmlFor={`id_${index}`} key={`key_${index}`}>
              <CheckBox
                id={`id_${index}`}
                value={item}
                onChange={handleChange}
                checked={checkedItems[item.id]}
              />
              {item}
            </label>
          )
        })}
{/* checkedがない場合には送信ボタンを表示させない */}
        {!isBtnHide && <button onClick={dataSendBtn}>アンケート送信ボタン</button>}
      </form>
    </>
  )
}

export default CheckBoxList


とりあえず意図した通りの動きになった。
でもなんか遠回りしている気がする。。。。

new Map()での実装

 const [ischecked, toggleChecked] = useState(new Map())

Mapはstateを更新するため(再レンダーをトリガーする)に、Mapをnew Mapに置き換える必要がある。

const handleChange = e => {
  checkedItems.set(e.target.id, e.target.checked)
  setCheckedItems(new Map(checkedItems) );
  console.log("checkedItems: ", checkedItems);
}

もっと良い方法はないか、いろいろ考えてみる、追記していく :relieved:

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

配列から任意の条件のものだけ取得する

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

【javascript】クラス

書籍のアウトプットとして

クラス定義でインスタンスプロパティを設定する

各メソッドはプロトタイプに追加されるが
プロパティは各インスタンスに割り当てられる。

class DateStore {
  url = '/data/resources';
  records = [];
  fetch() {
    ajax(this.url, records => this.records = records);
  }
}
console.log(DateStore.url)//undefined
const store = new DateStore()
console.log(store.url)///data/resources
console.log(store.records.length)//0

このように、クラスプロパティはインスタンスで使用可能になる。
これはコンストラクタメソッドの中で設定されたように処理される。

class DateStore {
  constructor() {
    url = '/data/resources';
    records = [];
  }
}

プロパティをプロタイプに直接設定するのはバグのもと

クラスメソッドはクラスのプロトタイプに追加される。
メソッドはプロトタイプ継承され、インスタンスに継承される。

対して、クラスプロパティはプロタイプに追加されず、インスタンスで直接割り当てられる。

class DataStore {}

DataStore.prototype.records = []

const storeA = new DataStore()
console.log(storeA.records.length)//0

const storeB = new DataStore()
console.log(storeB.records.length)//0

storeB.records.push('Example Record')

console.log(storeA.records.length)//1
console.log(storeA.records[0])//Example Record

recordsプロパティなhDataStoreのインスタンスではなくDataStoreのプロトタイプのプロパティに直接設定されている。
つまり、すべてのインスタンスが同じrecords配列を使用するということ。
すべてのオブジェクトが同じrecords配列を共有していることが問題。

静的プロパティ

クラスのオブジェクト(コンストラクタ)で設置される特殊なプロパティ。
インスタンスでもプロトタイプでも設定されない。
静的プロパティはインスタンス間で変化しない。

class DataStore {
  static domain = 'htt://example.com';//静的プロパティの割当
  static url(path) {                  //静的プロパティの設定
    return `${this.domain}${path}`
  }

  constructor(resource) {
    this.url = DataStore.url(resource) //静的メソッドの呼び出し
  }
}

const userStore = new DataStore('/users')
console.log(userStore.url)//htt://example.com/users
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Object.entries() を使ってObject → 配列変換

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

Quillでペースト時にスクロール位置がジャンプしてしまう問題

エディタライブラリのQuillで、テキストのペースト時などにスクロール位置がジャンプしてしまうことがある。
https://github.com/quilljs/quill

原因

どうやら、ペースト操作時にエディタがフォーカスを一瞬失うのが原因らしい。
テキストカラーなどの、ピッカー系の操作でも同じ現象が起こることがある。

解決策

この問題は、github上でも活発に議論されているようだが、私としてはこの方法がベストな解決策だと思う。

Quillの初期化時のオプションに scrollingContainer: document.documentElement を指定する。

const editor = new Quill('#editor', {
  modules: {
    toolbar: ['bold', 'italic', 'underline', 'strike'],
  },
  scrollingContainer: document.documentElement,
  theme: 'bubble'
});

参考

いくつか他の対応策も提示されています。
https://github.com/quilljs/quill/issues/1374

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

RailsでSwiperを導入する方法(Swiperは2020年7月にバージョンアップし、従来と設定方法が変わりました!)

はじめに

この記事は、以下のような方を対象としています。

  • これからRailsでSwiperを導入しようとされている方
  • Swiperを導入せよという課題が与えられたけど、そもそもSwiperが何かよく分からない初学者
  • 他のQiitaやブログ等の記事を見たけれどなかなか上手く設定できず、Swiperを動かすことができない方

Swiperではメジャーバージョンアップが行われ、いくつか大きな変更がありました。
その点を踏まえて、本記事で解説していこうと思います。

また、導入環境については末尾に記すので、そちらを参照してください。

Swiperのバージョンアップについて

Swiperのリリース情報

  • 2020年7月3日にv6.0.0をリリース
  • 以前のバージョンである v5.4.5 から大きく仕様が変更
  • 詳細については、公式で確認できる(変更点も記載されている)

変更点

ディレクトリ構造の変更やファイル名の変更などがあり、従来どおりの設定のままだと動かなくなりました!
(煽り気味に書きましたが、NPMやYarnなどのパッケージマネージャを使って設定している場合の話です)

詳細については、設定方法を紹介する中で併せて紹介していきます。

Swiperとは

84c66e482b652c5be609ffb0c3fe909d.gif

CSSとJSを適用することで、画像などをスライドできる機能を実装するものです。

公式サイトにてどのような機能を実装できるか詳細に紹介されています。

また、ガリガリコードさんというサイトでサンプル付きで詳細に紹介されているので、
そちらをまず見てみるとイメージがつくかと思います。

Swiper公式サイトのデモ
ガリガリコード

Swiperの実装方法

投げやりなことを言うと、公式サイトにまとめられています。
イメージさえつかめれば、英語がさほど分からなくても、実装できるかと思います。

Swiper公式サイトで紹介されている実装方法(Getting Started With Swiper)

とはいえ、いきなり見ても分かりづらい部分があるかもしれないので、
概要について簡単にまとめてみました。

1. HTMLの実装

イメージだけお伝えしますが、HTMLの構造は以下のようにする必要があります。
あまりよくないような気もしますが、公式サイトからコピペしています。

<!-- Slider main container -->
<div class="swiper-container">
    <!-- Additional required wrapper -->
    <div class="swiper-wrapper">
        <!-- Slides -->
        <div class="swiper-slide">Slide 1</div>
        <div class="swiper-slide">Slide 2</div>
        <div class="swiper-slide">Slide 3</div>
        ...
    </div>
    <!-- If we need pagination -->
    <div class="swiper-pagination"></div>

    <!-- If we need navigation buttons -->
    <div class="swiper-button-prev"></div>
    <div class="swiper-button-next"></div>

    <!-- If we need scrollbar -->
    <div class="swiper-scrollbar"></div>
</div>

なぜこのような構造にするかというと、Swiperの方でswiper-wrapperクラスや
swiper-slideクラスなどに適用するためのコードが用意されているためです。

Swiperの導入が無事終われば、該当のクラスにSwiperのCSSとJSが適用されるので、
「あら不思議!」という感じで、いい感じにスライド機能を実装してくれるという訳です。

念のため補足すると、<!-- If we need...と書かれているところは、
必要なければHTML上に記載する必要はありません。

2. CSS、JSの適用方法

公式サイトで紹介されているとおりですが、
CSS及びJSの適用方法には3種類あります。

  • CDN(クラウド上に公開されているCSSとJSを適用させる)
  • ファイルをダウンロードして、愚直にCSSとJSを適用させる
  • NPMというJSのパッケージマネージャ(RubyでいうところのGemを管理するBundlerのようなもの)
    • Yarnという類似のパッケージマネージャーを使うことも可能です

2.1 CDNでの設定方法

CDNとは

詳細については全く詳しくないため、気になる方はググっていただきたいのですが、
Content Delivery Networkの略です。

以前は、クラウド上に公開したファイルにアクセスが集中してしまうと困るので、
「各自ダウンロードして、自分が公開しているサーバーにCSSとJSをアップロードして!」
という感じでおそらくやっていました。

ただ、大量配信できる仕組みが整い、アクセスが集中しても耐えうる体制を構築できたので、
「もうクラウド上のファイルを使ってしまってかまへんで〜」となりました。
この大量配信できる仕組みが、CDNです。

誤解が多分に含まれていそうなのですが、 大枠の理解は間違ってないかと思います。。。

設定方法

HTMLのheadタグ内に以下のコードを貼り付ければCSSとJSが適用できるかと思います。
公式サイトにバッチリ書いてあるので、そちらを参照してください。

Swiper公式サイトで紹介されている実装方法(Getting Started With Swiper)

application.htmlなどに適用
<!-- Swiperの全ての機能を使いたい方はこちらを使用 -->
<link rel="stylesheet" href="https://unpkg.com/swiper/swiper-bundle.css">
<script src="https://unpkg.com/swiper/swiper-bundle.js"></script>

<!-- 最小限の機能で構わない! CSSやJSのファイルが小さい方がいい! という方はこちらを使用 -->
<!-- 最小限の機能が何かは調べられていないので、公式を参照するなり、通常版と比較考慮するなどしてみてください -->
<link rel="stylesheet" href="https://unpkg.com/swiper/swiper-bundle.min.css">
<script src="https://unpkg.com/swiper/swiper-bundle.min.js"></script>
application.html.slimなどに適用
/Swiperの全ての機能を使いたい方はこちらを使用
= stylesheet_link_tag "https://unpkg.com/swiper/swiper-bundle.css"
= javascript_include_tag "https://unpkg.com/swiper/swiper-bundle.min.css"

/最小限の機能で構わない! CSSやJSのファイルが小さい方がいい! という方はこちらを使用
/最小限の機能が何かまでは調べられていないので、公式を参照するなり、通常版と比較考慮するなどしてみてください
= stylesheet_link_tag "https://unpkg.com/swiper/swiper-bundle.min.css"
= javascript_include_tag "https://unpkg.com/swiper/swiper-bundle.min.js"

Swiperだから特別なことをしているという訳ではないので、
CSSやJSの適用方法の基本について調べれば、すぐ理解できるかと思います。

なお、その他のQiita記事等を参考にする場合、注意が必要です。

参照しているSwiperのバージョンが古いものである可能性が高いので、
その旨を踏まえて作業を進めてください。

2.2 ファイルをダウンロードして、愚直にCSSとJSを適用させる方法

ここでは触れません!
おそらくあえて選択する方はいないかと思いますし。

「CSSとJSをダウンロードして、HTMLに適用してあげればスライダー実装できるよ!」
という当たり前の話です。

それをローカルのファイルでやるか、クラウドのファイルでやるかだけの違いです。

2.3 NPMもしくはYarnで設定する方法

ここではYarnを使った設定方法について取り上げます。

NPMについても基本的には同じような考え方になるかと思いますし、
公式サイトで設定方法について紹介されているので、そちらを参照してください。

Yarnの導入からCSS・JSの適用まで

既によくまとめられたQiita記事があるので、まずはそちらを参照ください。
Yarnとは何かということについても触れています。

swiperをyarnで導入して、画像をスライダー形式にする! - Qiita

注意点1(ディレクトリ構造やファイル名が変更しました)

先ほどの記事について、一点だけ注意していただきたいことがあります。

それは、node_modulesディレクトリ配下に作成されるswiperというフォルダ内に
インストールされるファイルやディレクトリ構成が変更されたため、
このQiita記事のとおり設定をしても上手くいかないということです。

なので、導入したファイルの読み込み設定は以下のとおり行いましょう。

【Ver6】assets/javascript/application.js
//= require swiper/swiper-bundle.js
//= require swiper.js

# ちなみに、この順番を間違えると上手く動かないらしいです
【Ver6】assets/stylesheets/application.scss
@import 'swiper/swiper-bundle';

参考までに、Swiper(Ver5)の場合の設定について以下に掲載します。
参照ファイルが変更されていことが分かるかと思います。

【Ver5】assets/javascript/application.js
//= require swiper/js/swiper.js
//= require swiper.js
【Ver5】assets/stylesheets/application.scss
@import 'swiper/css/swiper';

参考までに、nodes_modules配下のディレクトリ構造のスクリーンショットを貼っておきます。
「あー、だいぶ変わったな」ということが分かるかと思います。

Ver6 Ver5
スクリーンショット 2020-07-06 16.15.56.png スクリーンショット 2020-07-06 16.16.11.png

注意点2(config/initializers/assets.rbの設定)

なお、以上については、あくまでconfig/initializers/assets.rb
以下のとおり設定している前提での話となります。ご注意ください。

こちらについては、先ほど紹介したQiita記事にも書かれている内容になります。

config/initializers/assets.rb
Rails.application.config.assets.paths << Rails.root.join('node_modules')

3. JSファイルの作成(先ほどのJSとは別!)

個人的にはここが分かりづらかったのですが、CDNから持ってくるJSファイル、
もしくはYarnでインストールしてnode_modules配下に置かれるJSファイルとは別に、
新しくJSファイルを自分で作成する必要があります。

ここで作成するJSファイルが、先ほど設定したswiper/js/swiper.jsを参照し、
スライダー機能を各クラスに適用させるような仕組みとなっています。

作成するJSファイルですが、以下を参考にしてください。
公式に書かれているとおりです。

Swiper公式サイトで紹介されている実装方法(Getting Started With Swiper)

app/assets/javascripts/任意の名前.js
var mySwiper = new Swiper('.swiper-container', {
  // Optional parameters
  direction: 'vertical',
  loop: true,

  // If we need pagination
  pagination: {
    el: '.swiper-pagination',
  },

  // Navigation arrows
  navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev',
  },

  // And if we need scrollbar
  scrollbar: {
    el: '.swiper-scrollbar',
  },
})

多くの場合、全てのコードは必要になりません。
また、逆に以上には書かれていないコードが必要となる場合もあります。

公式のデモサイトを参照し、該当のスライダー機能においてどのようなソースコードが
使用されているか確認してみるとよいでしょう。

例えば、Fraction Paginationという機能においては、以下のようなJSが使われています。

FractionPaginationで使用するJS
var swiper = new Swiper('.swiper-container', {
  pagination: {
    el: '.swiper-pagination',
    type: 'fraction',
  },
  navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev',
  },
});

このことは、公式サイトで紹介されているデモの一覧ページから確認することができます。

Swiper公式サイトのデモ(Fraction Pagination)
ソースコード(先ほどのデモサイトにリンクあり)

$(function() {...})について

私の場合は以上のコードで動かなかったので、以下のとおりとしました。
(JSむずかしい。。。)

app/assets/javascripts/任意の名前.js
// var swiper = と始めるのではなく、$(function(){ で始める

$(function() {
    new Swiper('.swiper-container', {

        // Optional parameters
        direction: 'vertical',
        loop: true,

        pagination: {
            el: '.swiper-pagination',
        },

 以下省略 

よくよく調べていくと、どうやらJSのスクリプトを置く場所が悪かったようです。

私の場合、HEADタグ内にJSのスクリプトを置くように設定していたので、
BODYタグ内のDOM(HTMLの構造、どこに何クラスがあるかなど)を読み込む前に
Swiperが発動してしまい、上手くJSを適用することができなかったようです。

BODYタグの前にJSのスクリプトを置く場合、$(function() {...})
囲ってあげて、DOMを読み込んでからSwiperが発動するよう設定する必要があります。

このあたりの話は以下のQiita記事に書かれています。

jQueryの基本 - $(document).ready - Qiita
【jQuery】$(function() {...}) について 「意味や実行されるタイミング」 - Qiita

また、改めてSwiperの公式デモサイトをみると、たしかにBODYタグ内の末尾に
JSのスクリプトが置かれています。

Swiper公式サイトのデモ(Fraction Pagination)
ソースコード(先ほどのデモサイトにリンクあり)

4. CSSの追加設定

こちらも公式に書いてある内容をそのまま書いているだけなのですが、
適宜Swiperのサイズなどを設定してください。

Swiper公式サイトで紹介されている実装方法(Getting Started With Swiper)

公式で紹介されている事例を以下に貼っておきます。

application.scssなど任意のcssファイル
.swiper-container {
    width: 600px;
    height: 300px;
}

参考

本記事内で紹介した公式サイト・ブログ・Qiita記事ですが、以下のとおりです。
公式サイトをぜひ一度は見てみてください。

なお、本記事では紹介していませんが、細かいカスタマイズをする場合、
以下の記事などを参照するとよいかと思います!

導入環境

バージョンとうるさいくせに、Railsのバージョンが5系なのは見逃してください。

また、初学者であるため、不足・過剰に書きすぎている部分があるかもしれません。
とりあえず、CSS・JS周りのものをざっと書き連ねてみました。

全般

  • MacOS X 10.15.5 (Catalina)
  • ruby '2.6.4'

Yarn関係

  • swiper (6.0.0)
  • bootstrap-material-design(4.1.3)
  • bootstrap(4.5.0)
  • dom7(3.0.0-alpha.5)
  • jquery(3.5.1)
  • popper.js(1.16.1)
  • ssr-window(3.0.0-alpha.4)

Gem関係

  • gem 'rails' (5.2.3)
  • gem 'sass-rails' (5.0)
  • gem 'uglifier' (4.2.0)
  • gem 'jquery-rails'(4.4.0)
  • gem 'popper_js'(1.16.0)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windowsで iOSのSafariのWebアプリをリモートデバッグする方法

概要

Macはもっていないけど、Windows10 PCをつかって、iOS11のSafari上で操作するWebアプリをデバッグする方法です

使うもの

  • Windows10 PC
  • iOSデバイス(iPhone,iPad,iPod touchなど)
  • Chromeブラウザ(Windows版)

準備

作業には Windows Power Shell(コマンドプロンプトの強化版、ふつうにインストールされてる) を使う。

Step0. iTunesをインストールしておく

https://www.apple.com/lae/itunes/

iTunesをインストールして、手持ちのiOSデバイスをUSBでPCに接続しておく

Step1. Scoopを導入する

Scoopはコマンドラインからソフトウェアをインストールするツール。
(macでいうhomebrewのwindows版的なものと理解)

Power Shellを起動し、以下コマンドを実行。これでscoopがインストールされる。

Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')

image.png

Step2. ios-webkit-debug-proxy をインストールする

Power Shellで以下コマンドを実行。これでios-webkit-debug-proxyがインストールされる。

scoop bucket add extras
scoop install ios-webkit-debug-proxy

image.png

Step3. ios-webkit-debug-proxyを起動する

Power Shellで以下コマンドを実行。これで、ios-webkit-debug-proxyが起動する。

remotedebug_ios_webkit_adapter -— port=9000

Step4. iOSデバイスでSafariを起動する

iOSデバイス側で、Safariを起動してリモートデバッグしたいWebアプリ、Webサイトを開いておく

(もちろんiOSデバイスとPCはUSBでつないだまま)

Step5. Chromeからデバイスに接続

Chromeブラウザを開いて、以下のURLを開く

chrome://inspect/#devices

すると以下のような画面が出るので Configure をクリック

image.png

すると、ダイアログが表示されるので、そこにlocalhost:9000を追加してDoneをクリックする。
image.png

これで、しばらくまつと、以下のようにSafariで動作しているWebアプリが表示される

image.png

Step6. ChromeのDevToolでデバッグする

inspectをクリックすると、おなじみのDevToolが起動し、Windows上からiOS Safari上のアプリの各種デバッグが行えるようになる。

image.png

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

【JavaScript】外部ライブラリに頼らないDateフォーマット (日付編)【Intl.DateTimeFormat】

この記事について

  • JavaScriptで日付をフォーマットする方法を3種類紹介しています。

    • "/"区切り (例: 2020/7/3)
    • "西暦+年月日" (例: 2020年7月3日)
    • "和暦+年月日" (例: 令和2年7月3日)
  • いろいろ検証した結果や調べたことも書いています。

  • コピペしたいだけ!結論だけほしい!」という方は各章の「コードと解説」だけ読めばOKです。

  • この記事は前回の記事(時間編)の知識を前提にしています。解説で不明点があれば参考にしてみてください。

? "/"区切り (例: 2020/7/3)

日付を年/月/日というフォーマットで出力する方法を紹介します。

コードと解説

Intl.DateTimeFormatの引数にlocaleを設定することで、その国の標準的な日付フォーマットを取得できます。
localeが日本('ja-JP')だと「yyyy/MM/dd」の形式で表示されます。

const dateTimeFormat = new Intl.DateTimeFormat('ja-JP');
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// 2020/7/3

localeをen-USにしてみる

localeをアメリカ(en-US)にすると、日付は「MM/dd/yyyy」の形式で表示されます。

const dateTimeFormat = new Intl.DateTimeFormat('en-US');
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// 7/3/2020

localeを省略してみる

locale引数を省略すると、ブラウザ既定のロケール(タイムゾーン)を使用することになります。
よって、日本にいるほとんどの方は、引数なしでも「yyyy/mm/dd」が得られると思います。

const dateTimeFormat = new Intl.DateTimeFormat();
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// 2020/7/3

ブラウザ既定のロケールについて

ブラウザ既定のロケール(タイムゾーン)は以下で確認できます。

console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);
// Asia/Tokyo

? "西暦+年月日" (例: 2020年7月3日)

日付の区切り文字を/ではなく「年月日」にしてみます。

コードと解説

Intl.DateTimeFormatの第2引数にオプションを渡すことで日付のフォーマットを定義できます。

const dateTimeFormat = new Intl.DateTimeFormat('ja-JP', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// 2020年7月3日

ポイントはmonthのフォーマットです。
longshortnarrowのいずれかを設定すると年月日表記になります(後述あり)。
yearやdayのオプションはなんでもOKですが、省略はできません(後述あり)。

en-USにしてみる

第2引数でオプションを渡しているこの状態で、第1引数のlocaleをen-USにすると、月の表記が英文字に変わります。

const dateTimeFormat = new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// July 3, 2020

monthのフォーマットについて

monthのフォーマットはlong以外にも用意されています。
localeja-JPでmonthにnumeric2-digitを使うと、年月日表記にならないので注意してください。

   en-US ja-JP
numeric 7/3/2020 2020/7/3
2-digit 07/3/2020 2020/07/3
long July 3, 2020 2020年7月3日
short Jul 3, 2020 2020年7月3日
narrow J 3, 2020 2020年7月3日

yearとdayを省略してみる

yearとdayを省略すると、その2つが出力されなくなります。
yearだけ省略すると、yearだけ出力されなくなります。dayも同様です。

const dateTimeFormat = new Intl.DateTimeFormat('ja-JP', {
  // year: 'numeric',
  month: 'long',
  // day: 'numeric',
});
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// 7月

オプションで年、月、日いずれかのフォーマットを1つでも定義した場合は、定義した項目しか戻しません。
なので、この方法で年月日を表示したい場合はyearとdayは省略できません。
逆に、オプションに何も指定しなければ前章の通り「yyyy/MM/dd」を返してくれます。

? "和暦+年月日" (例: 令和2年7月3日)

西暦(2020年)ではなく和暦(令和2年)にしてみます。

コードと解説

前章のIntl.DateTimeFormatの引数にera: 'long'を追加するだけです。

const dateTimeFormat = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
  era: 'long',
  year: 'numeric',
  month: 'long',
  day: '2-digit',
});
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// 令和2年7月03日

year,month,dayを省略してみる

「令和」だけ表示されるのかな・・・と思ったら年月日も付いてきました。

const dateTimeFormat = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
  era: 'long',
});
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// 令和2/7/3

long以外のフォーマットにしてみる

eraも複数のフォーマットを持っています。
longshortは「令和」になりますが、narrowだと頭文字(R)になるので注意してください。

const dateTimeFormat = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
  era: 'narrow',
});
console.log(dateTimeFormat.format(new Date(2020, 6, 3)));
// R2/7/3
   ja-JP
long 令和2年7月3日
short 令和2年7月3日
narrow R2年7月3日

参考

まとめ

いろいろ検証しているうちに、JSの日付操作方法はIntl.DateTimeFormatのオプションを使えば大体どんな形式にもできることがわかってきました。
同時に、噂通りJSのDateTimeには落とし穴が多いこともよくわかりました。
タイムゾーンについてまだよくわかってないので、いつかまた勉強して記事書いてみようと思います。

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

俺のjs

連想配列を配列にする

array = Object.values(data);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Javascript] 多次元ポケモンデータから取得するメソッドを作って”this”を理解した話[this]

目的

  • クラスのメソッド内で、メソッドを呼び出したい時にうまく呼び出せない事象を解決する

結論

  • 今後、"this"を使いたい時は、具体的に何を指しているか確認すること。

今回やること

多次元データに対して、クラス内でメソッドを使います。

ポケモンが格納されているデータは、こんな感じ。(1匹分)
これが245匹分ある。

(() => {
  window.allPokemon = [{
    "Number": "001",
    "Name": "Bulbasaur",
    "Generation": "Generation I",
    "About": "Bulbasaur can be seen napping in bright sunlight. There is a seed on its back. By soaking up the sun's rays, the seed grows progressively larger.",
    "Types": [
      "Grass",
      "Poison"
    ],
    "Resistant": [
      "Water",
      "Electric",
      "Grass",
      "Fighting",
      "Fairy"
    ],
    "Weaknesses": [
      "Fire",
      "Ice",
      "Flying",
      "Psychic"
    ],
    "Fast Attack(s)": [{
      "Name": "Tackle",
      "Type": "Normal",
      "Damage": 12
    },
    {
      "Name": "Vine Whip",
      "Type": "Grass",
      "Damage": 7
    }
    ],
    "Special Attack(s)": [{
      "Name": "Power Whip",
      "Type": "Grass",
      "Damage": 70
    },
    {
      "Name": "Seed Bomb",
      "Type": "Grass",
      "Damage": 40
    },
    {
      "Name": "Sludge Bomb",
      "Type": "Poison",
      "Damage": 55
    }
    ],
    "Weight": {
      "Minimum": "6.04kg",
      "Maximum": "7.76kg"
    },
    "Height": {
      "Minimum": "0.61m",
      "Maximum": "0.79m"
    },
    "Buddy Distance": "3km (Medium)",
    "Base Stamina": "90 stamina points.",
    "Base Attack": "118 attack points.",
    "Base Defense": "118 defense points.",
    "Base Flee Rate": "10% chance to flee.",
    "Next Evolution Requirements": {
      "Amount": 25,
      "Name": "Bulbasaur candies"
    },
    "Next evolution(s)": [{
      "Number": 2,
      "Name": "Ivysaur"
    },
    {
      "Number": 3,
      "Name": "Venusaur"
    }
    ],
    "MaxCP": 951,
    "MaxHP": 1071
  },

実装(うまくいっていない)

(() => {
  window.pokemonager = {

    findNames: (array = allPokemon) => {
      return array.map(pokemon => pokemon.Name)
    },

    findByResistance: (attack) => {
      return allPokemon.filter((pokemon) => {
        return pokemon.Resistant.includes(attack)
      })
    },

    findNamesByResistance: (attack) => {
      const a = this.findByResistance(attack)
      const b = this.findNames(a)
      return b;
    },

だいたいこうなる

試行錯誤の結果だいたいこうなる。

is not defined

もしくは

is not function 

学び

thisの親は? 
->アロー関数である

つまり、thisは findNamesByResistance自体を呼んでいる!!
完 全 理 解 !
(オブジェクトを呼んでいるつもりだった)

実装

(() => {
  window.pokemonager = {

    findNames: (array = allPokemon) => {
      return array.map(pokemon => pokemon.Name)
    },

    findByResistance: (attack) => {
      return allPokemon.filter((pokemon) => {
        return pokemon.Resistant.includes(attack)
      })
    },

    findNamesByResistance: function (attack) {
      const a = this.findByResistance(attack)
      const b = this.findNames(a)
      return b;
    },

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

ニッチかもしれないフロントエンド向けGitHub Template

GitHub Templateというのを見つけてしまったので

元々個人用のボイラープレート的なものとして、ちょいちょい使う構成を保存しておこうと思ったのが始まりでした。
ところがこんなのを見つけてしまったので。

「GitHubでは既存のRepositoryをTemplateとして、新しいRepositoryを作成することができる」
https://dev.classmethod.jp/articles/github-template-repository/

ということで個人で使う構成をテンプレートリポジトリとしてpublicにしましたので、こんな構成を使うという奇特な方はどうぞご利用ください。
同じような構成で作る場合の参考にしていただいてもいいと思います。
(実際動くまでにはそれなりに詰まったので)

共通条件

  • IE11対応(一応サンプルソースでの動作確認済)
  • dev/prodを同時出力(最近の仕事上その方が都合いいので)
  • Polyfill.ioさん本当にありがとうございます
  • babel様様
  • scssを変換してPostCssでAutoprefixer

基本的なWeb構成

template_webpack_web
template_rollup_web

一般的かは知らない。
現時点でのディレクトリ構成は以下のような感じ(今後変更する可能性はある)

template/
├ assets/
│   ├ js/
│   └ css/
├ src/
│   ├ js_src/
│   │    ├ js_util/
│   │    │      └ util.js
│   │    └ main.js
│   └ css_src/
│        ├ css_util/
│        │      └ util.css
│        └ main.css
├ index.html
└ (config類)

基本的なWeb構成(だけどビルド分割)

template_webpack_web_separate
template_rollup_web_separate

JSとCSSを別のconfigで切ってある。
JSだけとかCSSだけ使う場合のピックアップ用。
構成は前項と同様。

svelte構成

template_webpack_svelte
template_rollup_svelte

すごく楽なのでもっと広まるといいと思うsvelte。
現時点でのディレクトリ構成は以下のような感じ(今後変更する可能性はある)

template/
├ assets/
│   ├ js/
│   └ css/
├ src/
│   ├ svelte.js
│   └ App.svelte
├ index.html
└ (config類)

今後

必要に応じて増えたり変わったりするかもしれません。
合理的なツッコミは是非お待ちしております。
あと、タグに何を入れるべきか悩ましい。

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

Objectの子要素までimmutable/readonlyにする

tl;dr

const variable = <element> as const の形で定義する

// object
const immutableObj = {
    a: 1,
    b: "test message",
    inner: { key: "foo" },
  } as const;
// =>型
// const immutableObj: {
//    readonly a: 1;
//    readonly b: "test message";
//    readonly inner: {
//        readonly key: "foo";
//    };
// }

const immutableList = [1, "test message", ['inner']] as const;
// => 型
// const immutableList: readonly [1, "test message",
//            readonly ["inner"]
//       ]

as const

  • ネストされた子要素までreadonlyになる
    • 上記だとinnerの箇所
  • リテラル型が使われる
    • 上記だとb: stringでなくて、b: "test message"型になる
  • TypeScriptレイヤーの話なのでトランスパイルされたJSには何も影響がない
    • (左TS、右トランスパイルされたJS)memo.png
  • Classはas constできない。なので、Object.freezeを使うか、クラスプロパティにreadonlyを指定する(後述)

Object.freeze

Object.freeze() - JavaScript | MDN

      const mutableObj = { a: 1, b: "test message", inner: { key: "foo" } };
      const freezed = Object.freeze(mutableObj);
      freezed.b = 'dar'          // ERROR
      freezed.inner.key = "bar"; // SUCCESS
  const freezed = Object.freeze(
     { a: 1, b: "test message", inner: { key: "foo" } }
  );
  // => 型
  // const freezed: Readonly<{
  //  a: number;
  //  b: string;     << stringでなくて'test message'型であってほしい
  //  inner: {
  //      key: string;
  //  };
  //}>
  • Classでは as const でなくて Object.freeze が使える。
    • ただ、nestしたオブジェクトに対するfreezeがガバガバなので注意
  class Person {
    public obj: { a: number; b: string, inner: {key: string} };
    constructor() {
      this.obj = { a: 1, b: "hello world", inner: {key: 'foo'} };
    }
  }
  const p = new Person();

  const freezed = Object.freeze(p);
  freezed.obj = {...freezed.obj, b: 'bar1'}; // ERROR TS-2540
  freezed.obj.b = 'new bar';                 // SUCCESS
  freezed.obj.inner = {key: 'bar' };         // SUCCESS

まとめ

  • 極力 as const を使う
  • Class のときは仕方ないので Object.freeze を使う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Googleデータポータルでのデザインの自由度が高いツールチップの表示と設定方法

はじめに

Googleデータポータル(旧Googleデータスタジオ)はその名の通りGoogleが提供しているBIツールです。
他のBIツールと比較すると無料で使えるという点が利点として挙げられますが、データ系のミートアップイベントでは、かゆいところに手が届かないとよく言われてるところを耳にします。
しかし、Googleデータポータルにはコミュニティ・ビジュアライゼーションという、ユーザがグラフ等のコンポーネントを作り公開することができる機能があります。

その機能を用いて、以下のDEMOのように、マウスオーバーでツールチップを表示させるコミュニティビジュアライゼーション(自作コンポーネント)「MouseOver Tooltip」を作成しました。

レポートについてちょっとした説明を入れたいけど、常時表示させるレベルのものじゃない...
かと言ってレポートの見方みたいな別なドキュメントを作る程のものでもない...

そんなときにこのコンポーネントは役に立つと思います。(実際自分が欲しいから作ったというのが開発の一番の動機です。)

今回はこのコンポーネントの使用方法について解説します。
画面収録-2020-06-08-13.11.49.gif

コミュティビジュアライゼーションの使用方法

今回紹介する MouseOver Tooltipに限らず、他のコミュニティビジュアライゼーションにも共通するコミュニティビジュアライゼーションの使い方を紹介します。

Step1 コミュニティビジュアライゼーションへデータソースの使用を許可する

MouseOver Tooltipでは特にデータソースのデータを利用していませんが、Googleデータポータルの仕様上、何かしらのデータソースへ紐付ける必要があります。
そのアクセスを許可します。

【設定方法】
ツールバー → リソース → 追加済みデータソースの管理 → 使用するデータソースの「編集」 → 右上の「コミュニティにおける可視化へのアクセス」を「オン」にして保存
step1.gif

Step2 コミュニティビジュアライゼーションをレポートに追加する

コミュニティビジュアライゼーションをレポートに追加するには、コミュニティ ギャラリーから選択して追加するか、ビジュアライゼーションIDを入力して追加します。

コミュニティビジュアライゼーション及びコンポーネントをクリックしてコミュニティビジュアライゼーションメニューを表示
comviz.png
「+もっと見る」をクリック

コミュニティ ギャラリーに追加されているものがここに一覧表示されています。

MouseOver Tooltipをレポートに追加するためには、一覧表示されているコミュニティ ギャラリーをスクロールして「MouseOver Tooltip」を選択するか、直接ビジュアライゼーションIDを入力します。
スクリーンショット 2020-07-03 20.21.35.png
※現在コミュニティギャラリーに追加できるのは英語のコンポーネントのため、英語版のみこの操作で追加できます。

ちなみに本記事執筆時点で、コミュニティギャラリーに追加されているコンポーネントのうち、日本語にも対応している(日本人と思われる人が作った)コンポーネントはMouseOver Tooltipのみと見受けられます。
恐らく日本からは初となるGoogleのコミュニティビジュアライゼーションでギャラリーに公開されたコンポーネントかと思われます。
(筆者調べ、もし誤っていましたらご一報ください)

なお、日本語のコンポーネントを追加したい場合は、ビジュアライゼーションIDを直接入力してコンポーネントを追加する必要があります。
add_dokuji.png
独自のビジュアライゼーションの作成をクリックし、コミュニティビジュアライゼーションのテストと追加の欄に、マニフェストパスを入力します。
MouseOver Tooltipのマニフェストパスは以下です。
[日本語] gs://community-viz-shotaokb/mouseovertooltip/ja
[英語] gs://community-viz-shotaokb/mouseovertooltip/en
input_gs.png
送信をクリックして表示されたコンポーネントをクリックして、レポートに追加します。

MouseOver Tooltipの設定

コンポーネントをレポートに追加したら、プロパティの設定を行います。

「データ」プロパティの設定

MouseOver Tooltipはユーザデータを利用しないため、基本的にはデータプロパティの設定は不要です。ただしGoogleデータポータルの仕様上、「データソース」を各コンポーネントに設定する必要があります。
Step1でコミュニティビジュアライゼーションへデータソースの仕様を許可したデータソースを設定してください。
※この設定がうまく出来ていないと、画像のようにビジュアライゼーションが表示されないエラーとなります。
disable.png

「スタイル」プロパティの設定

MouseOver Tooltipコンポーネントは「スタイル」プロパティの設定が主となります。設定は大きく「ツールチップの設定」と「アイコンの設定」の2つに分かれています。
スクリーンショット 2020-06-16 14.31.40.png

尚、各種設定項目の詳細については、MouseOver Tooltipの公式ドキュメントでも詳しく紹介しています。

ツールチップの設定一覧表

設定項目 設定内容
テキスト ツールチップに表示する文字列
フォント テキストのフォント
テキストサイズ テキストの大きさ
文字色 テキストの文字色
ツールチップの枠の色 ツールチップの枠線の色
ツールチップの枠の太さ 枠線の太さ
ツールチップの枠の種類 枠線の種類
{実線,破線,点線,二重線}
角を丸くする 枠の角の丸み
ツールチップの背景色 ツールチップの背景の塗りつぶし

アイコンの設定一覧表

設定項目 設定内容
アイコン アイコンの種類
{インフォメーション(i),クエスチョン(?),ひらめき電球}
サイズ アイコンの表示サイズ
アイコンの色 アイコンの表示色

MouseOver TooltipのTIPS

コンポーネントの大きさと表示順序

tips.png
MouseOver Tooltipコンポーネントのそれ自体の大きさは、他のコンポーネントと被らない限りでできるだけ大きいサイズにしておくことをオススメします。
また、MouseOver Tooltipは最前面に配置しておくことをオススメします。

【理由】
①他のコンポーネントと被ると・・・被っている部分はクリックできなくなります。※
②サイズが小さいと・・・ツールチップのはみ出た部分にスクロールバーが表示され、見た目が損なわれます。
③最前面に配置しないと・・・ツールチップが他のコンポーネントに被ると見えなくなります。

※①の補足画像:ツールチップが表示されていない状態でも表示領域が被っている場合、クリックできません。
kaburi.gif

その他リンク集

MouseOver Tooltip公式ドキュメント(英語)
https://datastudio.google.com/reporting/b9596a2a-d10a-4916-a79b-c8ccb03c9138/
MouseOver Tooltip日本語公式ドキュメント
https://datastudio.google.com/u/0/reporting/0aa1fb77-5e31-49fd-81fb-89f77560864c/
Github
https://github.com/shotaokb/MouseOverTooltip
関連note「必要な情報を必要な人に届けるためのものを開発したらGoogleのプラットフォームに載った話」
https://note.com/notes/n011a2d66addd/
開発者へのご質問、ご意見、ご要望等
https://datastudio.google.com/u/0/reporting/0aa1fb77-5e31-49fd-81fb-89f77560864c/page/5ThTB

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

古いReactベースのWebアプリも完全にリファクタリング

この記事では、数ヶ月前のReactベースのWebアプリをリファクタリングして完全に書き換える方法と理由を共有します。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

image.png

私たちはミスをしがちです。そのため、作業の見直し、再編成、見直しが必要になることがよくあります。ソフトウェアエンジニアリングでは、このフェーズをリファクタリングと呼びます。

リファクタリングには価値があります。ソフトウェアを健全な状態に保つことができるからです。これはAoneに現れるような特別なタスクではありません。上手くやれば、プログラミング活動の定期的な一部になります。

リファクタが完全なリライトになる場合

新しいプロジェクトや新しい開発スプリントを開始するときはいつも、最新かつ最高の技術やソフトウェアのデザインスキームを使用します。しかし、しばらくすると、新しい技術や新しい設計手法が出てきます。コアとなるコードベースの設計は、変更のしやすさに大きな違いをもたらします。拡張機能は多くの場合、作成時には意味のある方法で重ねて適用されますが、後になって、さらなる変更や機能、改善を開発するのがますます難しくなります。

私たちのチームの最初のReactプロジェクトは、2018年後半にアルファ版として開発されましたが、現在(2019年初頭)は、高い要求に応えるために、新しい機能、より多くのメンテナンス、より良いパフォーマンスを必要としています。

まず私たちのコアコードベースを改善する必要がありました。

"Embrace change" === "Re-write a five months old app"
  • 依存関係の自由 - 依存関係をよりコントロールできるようにするために、Next.js から独自の configure 環境に移行しました。
  • 懸念事項の分離 - 単一の責任という観点から、重要な機能(ルーティングなど)をReactから分離しました。
  • 誇大広告の背後を見る - みんなが使っているものを自動的に使うのをやめて、自分たちに合ったものを検討し始めました。
  • npmインストール時間 - いくつかの簡単な質問に基づいて、プロジェクトに注入する依存関係を選択します。最初に自問したのは、「自分たちで実装するにはどれくらいの時間がかかるのか」ということでした。
  • 変化を受け入れる - コードと依存関係を定期的に更新し、リファクタリングします。
  • パフォーマンスのコツ - このリファクタリングの段階では、アプリのパフォーマンスとユーザーエクスペリエンスも大幅に改善されました。

プロローグ

今回関係するReactプロジェクトはHyperMLと呼ばれるものです。

HyperMLは、研究者や開発者を念頭に置いて設計された継続的な研究プラットフォームです。これは、研究者や開発者が(ディープラーニングのトレーニングのような)計算量の多いタスクをクラウドGPU上で最小限のオーバーヘッドで合理化された方法でWebインターフェースを介して実行できるようにするためのものです。

依存性の自由

image.png

HyperMLは、チームの最初のReactアプリとして2018年に構築された、それを持っています。最良の方法は、最も早く、最適な設定のジャンプスタートを見つけること、そして、展開のためのマインドセットを持つことが重要であることに変わりはありませんでした。

"開発者の経験とエンドユーザーのパフォーマンスの両方に恍惚としているので、コミュニティで共有することにしました。" - Next.js

それは完璧に聞こえましたが、その後、私たちはそのSSRを使用していませんでした。私たちはより多くのコントロール、より多くの柔軟性を必要としていました、そして正直に言うと、私たちはこの "ブラックボックス "に快適さを感じませんでした。Creat-React-App は良い代替手段ですが、それでも私たちの問題は解決されませんでした。

偶然にも、JetpackはGithubのトレンドにちょうどヒットしました(JetpackはWebpackの周りの薄いラッパーであり、Webpackの設定のいずれかで拡張することができます) - それは私たちに完全にフィットします。基本的な最良の構成と、それを好きなように拡張する方法を提供してくれました。

懸念事項の分離

image.png

Next.js関連のコードとその依存関係をすべて削除したので、ルーティングを処理する別の方法を見つけなければなりませんでした - 今回はRouter5 JSを選択しました。react-routerはReactアプリのためのコミュニティで好まれているルーティングパッケージですが、Router5はより成熟しており、安定しており、私たちの「懸念の分離」という設計原則に適合しているように思えました。

Router5 (React プラグイン付き) は、React からルーティングを分離し、遅延ロードのようなものを処理するためにコアの React 関数を使用することを可能にしてくれました。その方法は以下の通りです。

[Root page]
import React, { Suspense } from 'react';

import Loading from 'components/loading';
import TopBar from 'components/top_bar';

// Common
const NotFound = React.lazy(() => import('pages/404'));
//...

// HyperML
const HomePage = React.lazy(() => import('pages/hyperml_pages/home_page'));
const JobView = React.lazy(() => import('pages/hyperml_pages/job_view'));
//...

// AutoML
//...

class Root extends React.Component {

    //...

  getContent = () => {
    switch (route) {
      case 'home_page':
        return <HomePage />;
      case 'job':
        return <JobView />;
      //...
      default:
        return <NotFound />;

    }
  };

  render() {
    return (
      <Layout className="layout">
        <Layout.Header>
          <TopBar />
        </Layout.Header>
        <Layout.Content>
          <Suspense fallback={<Loading />}>
            <div>{this.getContent()}</div>
          </Suspense>
        </Layout.Content>
        <Layout.Footer>
          Alibaba ©2019 Created by IMVL
        </Layout.Footer>
      </Layout>
    );
  }

}

//...

誇大広告の先を見る

image.png

自分に合ったものを選ぶ

HyperMLはチーム初のReactアプリでしたが、それだけではありませんでした。

アプリの状態を管理するために他の可能性のある方法を探っていくうちに、正しい方法は一つではないことがわかりました。

しかし、1つだけはっきりしていたことは、サードパーティのパッケージを使用する必要がない限り、サードパーティのパッケージを使用しないことです。Reactのデフォルトの状態管理は、正しく使えば素晴らしいものですが、私たちはReduxを状態管理に使っている "古い "プロジェクトを書き換えているので、できるだけデフォルトに近い状態を維持しつつ、"Reduxのやり方 "を維持する方法を探し始めました。

このMediumの記事(新しいコンテキストAPIの上に構築された状態管理を実装する新しいライブラリ)を見つけ、そのスマートでわかりやすいソースコードを調べた後、"npm install react-waterfall -save "を選択しました。

最終的には、より管理しやすく、より明確なコードになり、おまけとして、Redux で使用しなければならなかったすべてのパッケージを手放すことができました。

[Jobs Store]
import { setData, mergeData } from './utils';

const initialState = {
  jobs: {},
  lastActivity: {},
};

function updateJob(state, callback, newJob) {
  const { lastActivity } = state;
  if (lastActivity[newJob.ID]) lastActivity[newJob.ID] = newJob;
  return {
    jobs: {
      ...state.jobs,
      [newJob.ID]: newJob,
    },
    lastActivity,
  };
}

function deleteJob(state, callback, jobID) {
  const jobs = Object.assign({}, state.jobs);
  const lastActivity = Object.assign({}, state.lastActivity);
  delete jobs[jobID];
  delete lastActivity[jobID];
  return { jobs, lastActivity };
}

const actionsCreators = {
  setLastActivity: setData('lastActivity'),
  setJobs: setData('jobs'),
  mergeJobs: mergeData('jobs'),
  updateJob,
  deleteJob,
};

export default { initialState, actionsCreators };
[Projects Store]
import { setData, mergeData, addData } from './utils';

const initialState = {
  projects: {},
};

const actionsCreators = {
  setProjects: setData('projects'),
  mergeProjects: mergeData('projects'),
  updateProject: addData('projects'),
};

export default { initialState, actionsCreators };
[Index Store]
import createStore from 'react-waterfall';

import mergeStores from './utils';
import jobs from './jobs';
import projects from './projects';

export const { Provider, connect, actions } = createStore(mergeStores([
  jobs,
  projects,
]));
[Connect to react]
//...

import { Provider as StoreProvider } from 'store';
import Root from 'pages';


const App = () => (
  <StoreProvider>
    <Root />
  </StoreProvider>
);

[Use "action" anywhere]
//...

import { actions } from 'store';
actions.deleteJob(jobID);

[Connect to component]
//...
import { connect } from 'store';

const Activity = ({ lastActivity }) => (
  <Card>
      <JobsList jobs={lastActivity} />
  </Card>
);

Activity.propTypes = {
  lastActivity: PropTypes.arrayOf(PropTypes.shape()),
};

export default connect(({ lastActivity }) => ({ lastActivity }))(Activity);

npm インストール時間

プロジェクトの依存関係をコントロールできるようになってからは、何を追加するかをより賢く選択できるようになりました。私たちは特定の目標を達成するためにこのリファクタリングを計画しました。

プロジェクトの依存関係を大幅に減らしたことで、開発の進捗の中で最も時間のかかる部分であるデザインに役立つ重要なライブラリを追加することができました。

CSSコードの記述とメンテナンスには驚くほどの時間がかかっていましたが、Ant-Designを使用することでHyperMLのUIが改善され、ユーザーの体験が向上しただけでなく、新機能の開発にかかる時間が大幅に短縮され、より良いコードを書くための時間を確保することができました。

変化を受け入れる

React v16.8:Hooksを導入したもの

2019年2月にReact v16.8が "Hooks "を導入して登場しました。

"一晩でHooksを使うために既存のアプリケーションを書き換えることはお勧めしません。"と彼らはその日のうちに書いていました。

しかし、変化を受け入れることは私たちの文化の中にあります。現在のコードを改善していく中で、クラスのコンポーネントを関数に置き換えたり、新しいコンポーネントをHooksで書いたり、独自のHooksを構築したりするようになりました。

振り返ってみると、それは私たちがHooksをよく理解するのに役立ち、それをよく理解することで、私たちはコーディング時間を大幅に改善することができました。50個以上のコンポーネントを書き換えなければ、それは達成できませんでした。

フロントエンドパフォーマンスソリューション

アプリのパフォーマンスを向上させるには、サーバー側のコードを修正する必要があるかもしれませんが、サーバーのバックログにタスクが多すぎる場合は、フロントエンドで解決策を考える必要があります。ここでは、サーバー側で変更を加えることなくアプリのパフォーマンスを向上させた2つの方法を紹介します。

image.png

フロントエンドページング

アプリのページの一部にはジョブのリストが含まれており、これらのリストは大きくなることがあります。これらのリストは大きくなる可能性があります。広範なリストをレンダリングすることは、ほとんどのブラウザにとって複雑な作業であり、アプリのパフォーマンスだけでなく、ユーザーエクスペリエンスにも影響を与えます(ブラウザがリストのレンダリングを終えるまで真っ黒な白いページに直面する)。

この問題に対処するために、ページング機構を開発する必要があります。通常、これはサーバー側で行うものですが、主な問題はDOMにデータをレンダリングすることであり、サーバーからデータを取得することではないので、フロントエンドで開発することができます。

そのために、このパッケージでは react-infinite-scroller を使うことにしました。

[Jobs list]
//...
import InfiniteScroll from 'react-infinite-scroller';

import { connect } from 'store';
import JobItem from './item';


const JobsList = ({ jobs }) => {
  const [numOfJobsToShow, setNumOfJobsToShow] = useState(10);

  const jobsInView = useMemo(() => jobs.slice(0, numOfJobsToShow), [jobs, numOfJobsToShow]);

  return (
    <InfiniteScroll
      pageStart={0}
      loadMore={() => setNumOfJobsToShow(showingJobs => showingJobs + 10)}
      hasMore={numOfJobsToShow < jobs.length}
      loader={<Spin />}
    >
      {jobsInView.map(job => <JobItem job={job} />)}
    </InfiniteScroll>
  );
};

JobsList.propTypes = {
  jobs: PropTypes.arrayOf(PropTypes.shape()),
};

ローカルストレージに状態を保存

パフォーマンスとユーザーエクスペリエンスを向上させるもう一つの方法は、特に遅いインターネット接続がある場合に、ブラウザがデータをフェッチするのを「助ける」ことです。

私たちは、アプリのデータストアをブラウザのローカルストレージに保存することでこれを実現しました。この方法では、ユーザーは次回アプリにアクセスしたときに(サーバーからの)データを待つ必要がありません。

この方法には1つの欠点があります。それは、変更があるたびにローカルストレージにデータを書き込むことによるパフォーマンスへの影響です。これを克服するために、このタスクにはWeb Workerを使用して、バックグラウンドスレッドで実行するようにしています。

[Index Store]
import createStore from 'react-waterfall';

import mergeStores from './utils';
import jobs from './jobs';
import projects from './projects';
import { importLocalStore } from './localStore';
const localStorage = require('workerize-loader!./localStore')(); // ** Web Worker **

export const { Provider, connect, actions, subscribe } = createStore(mergeStores([
  jobs,
  projects,
]));

importLocalStore(actions);

subscribe((action, state) => localStorage.update(state));
[localStore.js]
// Runs on Web Worker
export function update(state) {
  Object.keys(state).forEach(key => localStorage.setItem(key, state[key]));
}

// Runs on main thread
export function importLocalStore(actions) {
  localStorage.getItem('jobs').then(jobs => actions.mergeJobs(jobs || {}));
  localStorage.getItem('projects').then(projects => actions.mergeProjects(projects || {}));
}

ボーナスのヒント

HyperMLに画像を追加することで、アプリがより使いやすくなり、作業ツールを楽しくするちょっとしたおまけを与えてくれることがわかりました。Undrawは定期的に更新される美しいSVG画像のオープンソースのコレクションで、完全に無料で帰属表示なしで使用することができます。

ボーナスヒントII

ESlintは、より良いコードを書くことを強制することで、チームのdevスキルを向上させます。プロジェクトでの使用を検討してみてください。

概要

数ヶ月前のアプリを書き換えるのは無駄に思えるかもしれませんが、コードのメンテナンス、新機能の追加、新しいプロジェクトの開発にかかる時間を考えれば、このリアクターが時間の適切な使い方であるだけでなく、必要な作業であることは明らかです。

私たちは、最初にいくつかの部分を書き換え、他の部分はマイナーチェンジを加えたままにして、少しずつ始めました。約2週間後には、完全に刷新された作業版のウェブアプリが完成しました。今では、このリファクタリングのおかげで、それ以降に開発した新機能ごとに何時間もの作業時間を節約できたと推定しています。

私たちの経験を共有し、時々リファクタリングをしてコードを更新することを奨励することは、私たちにとって不可欠でした。

例えば、コードベースが膨大で、長い間改訂が行われていない場合など、私たちの推奨を行うことが難しい場合があることを想定しています。しかし、その場合はもっと重要なことがあるかもしれません。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

JIRAチケットの説明文にデフォルト値を設定したいのでブックマークレットを書いてみた

はじめに

JIRAチケットを作成したとき、説明文にデフォルト値を設定したいときありませんか。
テンプレートを作ってそれを説明文に差し込みたい…

それっぽいのはあるんですが。

https://community.atlassian.com/t5/Jira-Core-articles/Jiraのチケット起票時にデフォルト値を設定したい/ba-p/766066

ということで、ブックマークレットでめちゃくちゃ簡単なコードを書きました!

やること

コードを書く

descriptionText の部分は便宜変更してあげてください。

javascript:(
  function(){
    descriptionText = "■デザイン\n\n\n■概要\n\n\n■背景\n\n\n■受入条件\n\n\n■リリース調整の有無\n\n";
    document.getElementById("description").value = descriptionText;
  }
)();

ブックマークする

ブックマークをしてあげてください。名前は自分がわかるように適当に入力。
URL欄にコードをコピペしましょう!

スクリーンショット 2020-07-06 10.51.58.png

あとは使うだけ

チケットを作成して、あとはブクマしたやつをクリックするだけ!

説明に descriptionText で設定した値が入ってると思います!

スクリーンショット 2020-07-06 10.47.27.png

終わりに

簡単に設定することができました!
今回は説明の箇所に値を設定しましたが、他の箇所でもいい感じに処理を書けそうですね(о´∀`о)

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

JIRAチケットの説明文にデフォルト値を設定したいのでブックマークレットを作ってみた

はじめに

JIRAチケットを作成したとき、説明文にデフォルト値を設定したいときありませんか。
テンプレートを作ってそれを説明文に差し込みたい…

それっぽいのはあるんですが。

https://community.atlassian.com/t5/Jira-Core-articles/Jiraのチケット起票時にデフォルト値を設定したい/ba-p/766066

ということで、ブックマークレットでめちゃくちゃ簡単なコードを書きました!

やること

コードを書く

descriptionText の部分は便宜変更してあげてください。

javascript:(
  function(){
    descriptionText = "■デザイン\n\n\n■概要\n\n\n■背景\n\n\n■実装方針\n\n\n■受入条件\n\n\n■リリース調整の有無\n\n";
<img width="788" alt="スクリーンショット 2020-07-06 11.24.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50208/319f76ff-a035-fdd7-8e14-2fc56f86af00.png">
    document.getElementById("description").value = descriptionText;
  }
)();

ブックマークする

ブックマークをしてあげてください。名前は自分がわかるように適当に入力。
URL欄にコードをコピペしましょう!

image.png

あとは使うだけ

チケットを作成して、あとはブクマしたやつをクリックするだけ!

説明に descriptionText で設定した値が入ってると思います!

image.png

終わりに

簡単に設定することができました!
今回は説明の箇所に値を設定しましたが、他の箇所でもいい感じに処理を書けそうですね(о´∀`о)

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

Node.jsを使ったECS上でのTCPアプリケーションの構築

このチュートリアルでは、Alibaba Cloud ECSを使ってTCPクライアント/サーバのペアを作成し、Node.jsを使ってTCPアプリケーションを構築していきます。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

Alibaba Cloud Tech Share執筆者のKunal Relanによるものです。Tech Shareは、クラウドコミュニティ内で技術的な知識やベストプラクティスを共有することを奨励するAlibaba Cloudのインセンティブプログラムです。

まず基本的なことから説明すると、オープンソースのJavaScriptランタイム環境として人気のあるものに、ChromeのV8 JavaScriptエンジン上に構築されたNode.jsがあります。Node.jsは主にサーバーサイドやネットワークアプリケーションの構築に使われています。TCP (Transmission Control Protocol) は、アプリケーション間でデータのストリームを信頼性の高い、順序立てられた、エラーチェックされた配信を可能にするネットワーキングプロトコルです。双方がデータストリームを交換するためには、TCPサーバがTCP接続要求を受け入れ、接続が確立されなければなりません。

TCP ソケットプログラムには、サーバとクライアントの 2 種類のプログラムを書くことができます。サーバの機能は、クライアントからの接続をリッスンし、処理されたデータを送り返すことです。この通信はソケットを介して行われます。

node.jsのTCPプログラミングにはnetという内部モジュールが必要で、ネットワークプログラミングの非同期ラッパーとして機能します。サーバとクライアントの構築にはAlibaba Cloud Elastic Compute Service (ECS)を使用します。

はじめに

このチュートリアルに従うためには、ubuntuやmacOSなどのlinux/unixディストリビューションが動作するマシン、コードエディタ/IDEのnode.jsがインストールされていること、node.jsの基本的な知識が必要です。このチュートリアルでは、アプリケーションを本番環境にデプロイするのではなく、開発用のマシンで実行し、そこからテストを行います。

TCPサーバの作成

まず、アプリケーションを保存するディレクトリを作成します。このチュートリアルでは、~/nodejs-tcp-appにアプリケーションを作成します。

このチュートリアルでは、ほとんどの作業をターミナルで行います。また、ターミナルでnanoエディタを使用して、すべてのプラットフォームで同じプロセスを保証します。

開始するには、ターミナルを開きます。

mkdir ~/nodejs-tcp-app

さて、新しく作成したディレクトリに切り替えて、npm initを実行してpackage.jsonファイルを作成します。

cd ~/nodejs-tcp-app && npm init

これでターミナルからプロジェクトの基本情報を聞かれるので、server.jsとして名前、作者、メインファイルを追加してファイルを作成します。これでディレクトリ内にpackage.jsonファイルが表示されるはずです。

次に、TCPサーバのコードを含むserver.jsファイルを作成します。

同じディレクトリに以下のコマンドを入力して、server.jsファイルを作成し、テキストエディタを開いてコードを書きます。

nano server.js

まず、node.jsに同梱されているnetモジュールをインポートし、サーバーを実行するポートとホストを定義して、サーバーのインスタンスを作成します。

const net = require('net');
//define host and port to run the server
const port = 8080;
const host = '127.0.0.1';

//Create an instance of the server
const server = net.createServer();
//Start listening with the server on given port and host.
server.listen(port,host,function(){
   console.log(`Server started on ${host}:${port}`); 
});

これはアプリケーションの基本的な構成要素であり、TCP サーバを起動するのに十分なものです。

次に、クライアントが接続する接続にリスナーを追加する必要があります。

サーバの宣言を編集して、onClientConnectionという接続リスナー関数を追加し、下部に関数を宣言します。

const net = require('net');
//define host and port to run the server
const port = 8080;
const host = '127.0.0.1';

//Create an instance of the server
const server = net.createServer(onClientConnection);
//Start listening with the server on given port and host.
server.listen(port,host,function(){
   console.log(`Server started on port ${port} at ${host}`); 
});

//Declare connection listener function
function onClientConnection(sock){
    //Log when a client connnects.
    console.log(`${sock.remoteAddress}:${sock.remotePort} Connected`);
     //Listen for data from the connected client.
    sock.on('data',function(data){
        //Log data from the client
        console.log(`${sock.remoteAddress}:${sock.remotePort} Says : ${data} `);
        //Send back the data to the client.
        sock.write(`You Said ${data}`);
    });
    //Handle client connection termination.
    sock.on('close',function(){
        console.log(`${sock.remoteAddress}:${sock.remotePort} Terminated the connection`);
    });
    //Handle Client connection error.
    sock.on('error',function(error){
        console.error(`${sock.remoteAddress}:${sock.remotePort} Connection Error ${error}`);
    });
};

そこで、onClientConnection関数では、接続オブジェクトのsockを期待して、datacloseerrorの3つのイベントリスナーを作成します。

データイベントリスナーでは、クライアントから受信したdataをコンソールログに記録してクライアントに送信し、closeイベントリスナーでは、接続の終了を処理してコンソールログに記録します。errorイベントリスナーはクライアントからの接続エラーを処理します。

これで server.js のコードが完成し、これで TCP クライアントの接続を受け入れ、それらのデータをリッスンしてクライアントにエコーバックする TCP アプリケーションが動作するようになりました。ファイルを保存してnanoエディタを終了します。

同じモジュールを使って TCP クライアントを作成してみましょう。

TCP クライアントの作成

nanoコマンドを使ってclient.jsファイルを作成し、作業を開始してみましょう。

nano client.js

server.jsでやったように、netモジュールをインポートして引数を定義してみましょう。

const net = require('net');
//define the server port and host
const port = 8080;
const host = '127.0.0.1';
//Create an instance of the socket client.
const client = new net.Socket();
//Connect to the server using the above defined config.
client.connect(port,host,function(){
   console.log(`Connected to server on ${host}:${port}`);
   //Connection was established, now send a message to the server.
   client.write('Hello from TCP client');
});
//Add a data event listener to handle data coming from the server
client.on('data',function(data){
   console.log(`Server Says : ${data}`); 
});
//Add Client Close function
client.on('close',function(){
   console.log('Connection Closed');
});
//Add Error Event Listener
client.on('error',function(error){
   console.error(`Connection Error ${error}`); 
});

これでclient.jsはサーバーに接続し、接続時にメッセージを送信し、サーバーからのレスポンスをログに記録します。

ここでもサーバと同様に、データ、接続終了、接続エラーを処理するための3つのイベントリスナーを追加しています。

ファイルを保存してnanoエディタを終了します。

接続のテスト

TCPベースのサーバとクライアントアプリの作成が完了したので、サーバを起動してTCPクライアントをサーバに接続する必要があります。

このためには、2つのターミナルセッションが必要です-1つはサーバ用、もう1つはクライアント用です。

最初のセッションでは、次のコマンドを使用してサーバを起動します。

node server.js

このコマンドはTCPサーバを起動し、ターミナルにログが表示されるはずです。

Server started on 127.0.0.1:8080

次に、サーバーに接続してメッセージを送信するためにTCPクライアントファイルを起動する必要があります。別のターミナルで次のコマンドを入力します。

node client.js

これでTCPクライアントが起動し、サーバに接続してメッセージを送信しようとします。さて、あなたのターミナルには次のようなテキストが表示されているはずです。

Connected to server on 127.0.0.1:8080
Server Says : You Said Hello from TCP client

これで、クライアントがサーバに接続できて、ログを記録できるエコーを受信したことがわかりました。

次に、サーバを実行しているターミナルセッションに戻ると、このように表示されるはずです。ただし、クライアントのポートは異なる場合があります。

127.0.0.1:56330 Connected
127.0.0.1:56330 Says : Hello from TCP client 

プログラムでクライアント接続を閉じるわけではないので、close イベントリスナーはトリガーされません。しかし、クライアント端末に戻って ^c を押すと、クライアントは終了し、サーバ端末では接続終了のログを取得することができます。

127.0.0.1:56330 Terminated the connection

TCPサーバとクライアントのテストに成功しました。

結論

このチュートリアルでは、Alibaba Cloud Elastic Compute Service (ECS)インスタンス上でNode.jsを使ってTCPアプリケーションを作成しました。TCPソケットを使ってもっと多くのことができますが、これはその入門的なプログラムでした。これの高度なバージョンでは、TCPサーバを介してメッセージの送受信を可能にするチャットルームにすることができます。また、リアルタイムのデータ通信のための大きなデータストリームの塊を処理するために使用することもできます。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

【アロー関数】 javascript () => {}  意味 一言で、 入門 

【ゴール】

アロー関数理解

【コード】

■JSを学んでいくと出てきた「 () => {} 」

hoge.js
const name = () => {
  console.log('名前は太郎です');
}

■関数の省略
■下記と同じ意味(fucntionが有る、無い)
■function無しが推奨みたいです。

hoge.js
const name = () => {
  console.log('名前は太郎です');
}

const hoge = function(){
  console.log('名前は太郎です');
}

【まとめ】

■「() => {}」はアロー関数という

■推奨されている記述

【合わせて読みたい】

■ 【javascript】 テンプレートリテラル とは 一言で。
https://qiita.com/tanaka-yu3/items/9b07bd9fc4126291be28

■ 【Javascript】JS 変数 定数 違い  一言でまとめました
https://qiita.com/tanaka-yu3/items/51b8b0630a1e4e2d52c8

■ 【Javascript】 メソッド まとめ 基礎基本コード メモ
https://qiita.com/tanaka-yu3/items/2438798d159fa402b1d5

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

【Rails】アクティブハッシュによる選択肢の表示まで

「送料込み」や「着払い」を選択する度に配送方法の選択肢を変えたい時の
手順についてこちらでご紹介。

1.アクティブハッシュの準備

今回は商品の配送方法についてだったので、以下の選択肢を準備。

class ShippingMethod < ActiveHash::Base
  self.data = [
    {id: 1, name: '未定'}, {id: 2, name: 'ラクダ便'},
    {id: 3, name: 'こうもり便'}, {id: 4, name: 'ゆうメール'},
    {id: 5, name: 'レターパック'}, {id: 6, name: '普通郵便(定形、定形外)'},
    {id: 7, name: 'クロネコヤマト'}, {id: 8, name: 'ゆうパック'},
    {id: 9, name: 'クリックポスト'}, {id: 10, name: 'ゆうパケット'}
  ]
end

2. HTMl側に表示させる

htmlでとりあえず全ての選択肢を表示させます。

.box_form  
  .form-title
    %span.box-form-explanaion
      配送の方法
    %span.indispensable
      必須
  = f.collection_select :shipping_method_id, ShippingMethod.all, :id, :name, {prompt:"選択してください"}, {class: 'exhibit-form-field'}

以上で終了です。
ご覧いただきありがとうございました。

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

GASで簡易的な自動送信メルマガを作る

目的

指定した時間に自動的に送信される簡易的なメールマガジンをGASで実装します。
エンジニア以外のメンバーに運用してもらうため、スプレッドシート上に内容を記入してもらう形にしています。

概要

スプレッドシート上に以下の項目を記入しておくと、指定した内容でメールが送信されます。

  • 送信日/時間
  • 宛先(to,cc,bcc)
  • タイトル
  • 本文(定型文)
  • 本文(都度変更文)

このような形です。

今回の例では宛先に英語圏と中国語圏を含み、本文(都度変更文)としてそれぞれの言語のメッセージボックスを用意しておき、必要に応じて挨拶を記入してもらいます。

事前準備

スプレッドシートに必要な入力項目を用意する

概要で記した通りです。日付部分や宛先部分は条件付き書式や入力制限を使ってバリデーションします。

moment.js

日付を扱うためのライブラリです。文字列からパースしたり、月末月初を求める演算ができたり、任意のフォーマットで出力できたり、と様々な機能があります。指定した時間にメールを送る処理を調べていて知り、使ってみました。

ライブラリ追加

デフォルトだと利用できないので、ライブラリの追加を行います。リソースタブからライブラリを選択します。

「Add a library」部分にMHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48を指定して「追加」を選択します。
するとMomentが表示されますので、最新バージョンを指定して追加します。

これでmoment.jsを使えるようになりました。

実装

事前準備で作成したスプレッドシートのツールタブからスクリプトエディタを選び、以下の通り貼り付けます。
sh.getRangeで取得している値はスプレッドシート上の項目の場所を参照しているので、ご利用の際は用途に応じて適当に変更してご利用ください。

sendMail.gs
function sendMail() {
  var SS_ID = "スプレッドシートID";
  var TARGET_SHEET_MAIL = "シート名";

  var sh = SpreadsheetApp.openById(SS_ID).getSheetByName(TARGET_SHEET_MAIL);
  var range = sh.getRange('B:B').getValues(); 
  var lastRow = range.filter(String).length;

  var toAdr = [];
  var ccAdr = [];
  var bccAdr = [];
  var title = sh.getRange("L11").getValue();
  var body = sh.getRange("L12").getValue();
  var EnglishGreeting = sh.getRange("L3").getValue();
  var ChineseGreeting = sh.getRange("L4").getValue();
  var deliveryDateTime = "";
  var moment = Moment.moment();

  //メール本文の置き換え
  body = body.replace("ChineseGreeting",ChineseGreeting); 
  body = body.replace("EnglishGreeting",EnglishGreeting); 

  //当月確認
  for(i=1; i<=lastRow; i++){
    var deliveryValue = sh.getRange(i + 3,2).getValue(); 
    var Month = new Date().getMonth() + 1 + "";
    if (moment.isSame(deliveryValue[i],'hour')) {
      body = body.replace("Month",Month); //本文中に記載した月の書き換え
   }
  }

  //メール宛先振り分け
  for(i=1; i<=lastRow; i++){
    var sendToValue = sh.getRange(i + 3,7).getValue()
    if (sendToValue == "to") {
       toAdr.push(sh.getRange(i + 3,9).getValue()); 
       body = body.replace("sendTo",(sh.getRange(i + 3,8).getValue())); 
    }
    if (sendToValue == "cc") ccAdr.push(sh.getRange(i + 3,9).getValue()); 
    if (sendToValue == "bcc") bccAdr.push(sh.getRange(i + 3,9).getValue()); 
  }

  //メール送付時間になったら送付する
  if (moment.isSame(deliveryDateTime,'hour')) {
    MailApp.sendEmail({to:toAdr[0], cc:ccAdr[0], bcc:bccAdr[0], subject:title, body:body});
    console.log("\nmailto: " + toAdr + "\n" + ccAdr + "\n" + bccAdr + "\n" +
             "title: " + title + "\n" +
             "body: " + body + "\n"
               );
   }
}

自動送信のための設定

Apps Script ダッシュボードからマイトリガーで以下の通り設定します。
これにより、1時間に一度スクリプトが起動し、スプレッドシートで指定した時間と重なった時に指定した宛先にメールを送信します。
重ならない時は空振りします。

結果

1時間ごとにスクリプトが実行されます。
なお、GASでのメールは1日の送信制限件数がありますので,
特に宛先が多い場合は気をつけてください。

image.png

指定した時間になると自動的にメール送信されます。

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

お絵かきできるSNSを作りたい!11

1つ前に戻す(日本語あってる?)を実装したのでついついやりたくなるCtrl+ZをWEBページに実装ます。
方法は簡単でページ全体に対してonkyedownイベントを付与するだけです。

document.onkeydown = function(e) { 
    var keycode,shift,ctrl,alt; 
    keycode = event.keyCode; 
    shift = event.shiftKey; 
    ctrl = event.ctrlKey; 
    alt = event.altKey; 
    event.cancelBubble = true; 
    keychar = String.fromCharCode(keycode).toUpperCase(); 
}

このサンプルでは、documentに対して付与しました。

あとで扱いやすいように以下の変数を宣言しています。
keycode→押された文字列を大文字にして格納
shift→Shiftを押されているかの判定用
ctrl→Ctrlを押されているかの判定用
alt→Altを押されているかの判定用

つまり、Ctrl+Zを押された場合は1つ前に戻す用のdoPrevCanvas関数を呼びたいのでこんな感じです。

document.onkeydown = function(e) { 
    var keycode,shift,ctrl,alt; 
    keycode = event.keyCode; 
    shift = event.shiftKey; 
    ctrl = event.ctrlKey; 
    alt = event.altKey; 
    event.cancelBubble = true; 
    keychar = String.fromCharCode(keycode).toUpperCase(); 
    if (ctrl) { 
        if (keychar == "Z") {
            doPrevCanvas();
        }
    }
}

これでCtrl+Zが出来ました。
どうせなのでもういくつかショートカットを作ろうと思います。

作ったショートカットは以下の通り。
Ctrl+Z→1つ前に戻す
Ctrl+上矢印キー→上レイヤー
Ctrl+下矢印キー→下レイヤー
Ctrl+左右矢印キー→線の太さ変更
Ctrl+スペースキー→線の色の変更
Shift+左右矢印キー→画像の左右反転
Shift+スペースキー→フルスクリーン(全体画面)
Shift+Sキー→画像保存

[github]修正内容はこちら
線の太さ変更用に一部HTMLを変更しています。

この後は画像をサーバーにアップロード出来るようにするか、WebSocketを使ってお絵かきチャットできるようにするか、画像をドラッグアンドドロップだけで下絵にできるトレース機能を作るか機能改善するかは未定です。
ブランチ切ってそれぞれのバージョン作っても良いかもですね。

最初:お絵かきできるSNSを作りたい!

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