20200928のJavaScriptに関する記事は21件です。

new Proxy() の set() の返り値は true でいい

Uncaught TypeError

JavaScript らしいエラーに出会ったので残しておく。
エラーは以下。

VM213:16 Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'a' at <anonymous>:16:21

setter が false を返していることが上記のエラーの原因だが、そのときに書いていたコードは false なんて返していないと思っていた。

'use strict'
const target = {
    a: 0
};

const handler = {
    set: function (target, prop, value, receiver) {
        const res = target[prop] = value
        return res
    },
};

const proxy = new Proxy(target, handler);

console.log(proxy.a = 1);
console.log(proxy.a = 0);

false は返していないが 0 を返していたので、それは false を返していることになる

console.log(proxy.a = 0);の 0 の代入で、setter は代入された 0 をそのまま返す。
setter の返り値は boolean 型なので暗黙的に boolean 型に変換される。
Javascript は 0 は false として扱うため、setter の返り値は false となり、エラーとなる。

Boolean(1)
// true

Boolean(0)
// false

Boolean(2)
// true

そもそもの誤りとして、代入したものを return で返すコードが Proxy の仕様に沿っていない、ということ。なんとなくで resを返してたが、これがダメやな。

const handler = {
    set: function (target, prop, value, receiver) {
        const res = target[prop] = value
        return res
    },
};

どう書くべきか

MDN のサンプルでは、return の値に true を設定しているので、以下が正解の書き方になる。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy

const handler = {
    set: function (target, prop, value, receiver) {
        target[prop] = value
        return true
    },
};

エラーを発生させたいパターンは、 false を return に設定すればいい。

const handler = {
    set: function (target, prop, value, receiver) {
        if (value === 2) {
            return false
        } 
        target[prop] = value
        return true
    },
};

まとめ

JavaScript の仕様に沿った書き方をしよう、っていう記事。以上!

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

Qiitaでプログラミング言語トレンド

1. 概要

日本ではプログラミング言語のトレンドはどんな感じか知りたいので、Qiitaの過去データを取得して分析してみましょう。以下の写真は世界でのトレンドです(参考:PYPLサイト)。
世界のプログラミング言語トレンド.png

2. データ取得

2.1. APIの利用

APIの制限のため、問題がいくつか発生しました:

  1. 全部投稿を取得するAPIがありません(全部は580000投稿ぐらいがある)。
  2. APIによる一回の上限は1万投稿です(100ページ * 100投稿/1ページ)。
  3. 実際は5900投稿を取得すると、403エラーが発生してから、APIをアクセスできない(スパムから保護するため、1時間60リクエストだけ) → あるタグの投稿数は5900投稿以下のためフィルターを適用します(Stocksの数は30以上)。AWSのEC2インスタンスを適用して取得した時、403エラーになると、インスタンスを再起動しました(新しいIPになる)。
  4. あるタグの投稿数は5900以下なのに、全部投稿を取得できません。2017年09月の時点ぐらいには500エラーが発生しました。 → 最後取得できた投稿の作成日を保存して、新しいリクエストを作ります。

で、以上の問題を解決すると、以下のコードです。

let axios = require("axios")
let fs = require("fs")
let csvStr = require("csv-stringify/lib/sync")

let PAGE_MAX = 55

function parse_response(response, record_num, outcsv) {
    for (record_idx = 0; record_idx < record_num; record_idx++) {
        let record = []
        id = response["data"][record_idx]["id"]
        comments_count = response["data"][record_idx]["comments_count"]
        likes_count = response["data"][record_idx]["likes_count"]
        reactions_count = response["data"][record_idx]["reactions_count"]
        tags = response["data"][record_idx]["tags"]
        tags_str = ""
        for(tag_num = 0; tag_num < tags.length; tag_num++){
            tags_str = tags_str.concat(', ', tags[tag_num]["name"])
        }
        title = response["data"][record_idx]["title"]
        user_id = response["data"][record_idx]["user"]["id"]
        created_at = response["data"][record_idx]["created_at"]
        record.push(id, comments_count, likes_count, reactions_count, 
                    tags_str, title, user_id, created_at)
        outcsv.push(record)
    }
    return outcsv
}

async function getArticle(tag) {
    let outcsv = []
    let columns = ["id", "comments_count", "likes_count", "reactions_count",
                   "tags", "title", "user_id", "created_at"]
    outcsv.push(columns) 
    let last_date = "2020-09-28"
    let record_sum = 0
    for (page = 1; page <= PAGE_MAX; page++){
        let response =  await axios.get("https://qiita.com/api/v2/items?page=" + 
                        page + "&per_page=100&query=" + 
                        encodeURIComponent("stocks:>30") + "+" + 
                        encodeURIComponent("tag:" + tag))
        record_num = response["data"].length
        if(record_num != 0){
            outcsv = parse_response(response, record_num, outcsv)
            record_sum = record_sum + record_num
            last_date = response["data"][record_num-1]["created_at"].split('T')[0]
            console.log(record_sum + ":" + last_date)
        }
        if(record_num < 100){
            break
        }
    }

    for (page = 1; page <= PAGE_MAX; page++) {
        let response =  await axios.get("https://qiita.com/api/v2/items?page=" +
                        page + "&per_page=100&query=" +
                        encodeURIComponent("stocks:>30") + "+" + 
                        encodeURIComponent("tag:" + tag) + "+" + 
                        encodeURIComponent("created:<" + last_date))
        record_num = response["data"].length
        if(record_num != 0){
            outcsv = parse_response(response, record_num, outcsv)
            record_sum = record_sum + record_num
            console.log(record_sum)
        }
        if(record_num < 100){
            break
        }
    }
    fs.writeFileSync(tag + ".csv", csvStr(outcsv))
}

// それぞれのタグを変更する:C, C++, C#, Go, Java, Javascript, Kotlin, Objective-C, 
// PHP, R, Ruby, Swift, Typescript
getArticle("python") 

2.2. アウトプットデータ

結果のデータは各投稿のID,コメント数、LGTM数、絵文字リアクション数、タグ、タイトル、筆者のID、投稿の日時です。

データ量

言語 データ投稿数 全部投稿数
Python 5337 46000
Javascript 5305 37257
Ruby 2927 29307
PHP 2010 21088
Java 1568 15905
Swift 2577 15317
C# 815 10319
Go 1021 9084
C++ 630 8006
Typescript 701 5891
Kotlin 287 4100
Objective-C 935 4010
R 204 3526
C 261 3305

3. トレンド

以上の結果のデータをTableauにグラフを作成しましょう!

3.1. 全体グラフ

トレンド.png

横軸は投稿の作成日です(単位:年間)。
縦軸は各言語のタグの投稿数の割合です(単位:パーセント)。

3.2. 結果を分析しよう

3.2.1. Python

python.png
投稿数に基づと、2012年のPython言語ユーザーの割合は4%でした。その後、急速な成長により、この率は2014年にPHP、2015年にRubyとSwift、2016年にJavascriptを上回りました。現在、Pythonは一番人気がある言語(35%)になりました。

3.2.2. Javascript

js.png
2012年、JSの投稿数の割合は22%でしたが、2014年までに徐々に18%減少しました。現在、その割合は26%になり、人気がある言語トップで2位にランクされています。

3.2.3. Ruby

ruby.png
2012年、Rubyの投稿数の割合は35%に達し1位になりました。その後、何年にも渡って継続的に減少しています。現在、4%になりました。

3.2.4. Typescript

typescript.png
2013年から始まり、近頃の3年間の高い成長により、現在、3位(10%)になりました。

3.2.5. Swift/Objective-C

swift_objective-c.png
Swiftの発達とともに、Objective-Cは衰退していました(0.24%)。全体として、iOSの言語は2015年,2016年に人気があり、最近、少し減少しました。

3.2.6. 他の言語

PHP
php.png

Java
java.png

C/C++
c_c++.png

C#
c#.png

Go
go.png

R
r.png

Kotlin
kotlin.png

4. よく投稿した時間、曜日

作成日.png
よく投稿した曜日は月曜日です。

作成時間.png
よく投稿した時間は0時です。

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

JavaScriptでアラートメッセージを表示させる

プログラミング勉強日記

2020年9月28日
エラーメッセージやサクセスメッセージを表示させるときにJSのメッセージボックスを表示するためにアラートについて簡単にまとめる。

alertの使い方

 alert()メソッドとはアラートを出すメソッドで、以下のように記述する。
 文字列の場合は"'で値を囲む必要がある。

alert( 画面に表示させたい値 )

// 文字列・数値のメッセージを出力
alert('アラートのメッセージ')

変数を使ってメッセージを出力する

 以下のようにalertには変数を使用することができる。

var warn = "エラーメッセージ";
alert(warn);

※注意alertのダイアログはカスタマイズできない

 ポップアップメッセージに表示されるアイコンであったり、タイトルテキストやボタンのテキストは変更することができない。
 alert()メソッドは特定のメッセージをユーザに認識してもらうことを用途としているので、カスタマイズはできない。どうしてもカスタマイズしたい場合は、HTMLやCSS,JavaScriptを用いて独自にダイアログを作成しなくてはならない。

参考文献

JavaScriptでalertを使ってアラートを表示させる方法【初心者向け】
【JavaScript入門】alert(アラート)メッセージはこれで完璧。使い方を総まとめ!

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

JavaScriptを使ってTwitterのbotを作ってみた その2

JavaScriptを使ってTwitterのbotを作ってみた その2

 前回は、Twitterモジュールを使って、いろいろなコマンドを使って、基本的な機能を実装するやり方を紹介しました。今回は、その基本的な機能に応用を加えて、より高度な機能を実装していくためのやり方を紹介していきます。

自動的に機能を実装するようにする

 前回やってきた機能についてですが、nodeコマンドで実行した時は機能しますが、それ以降は全く機能しなくなるんですよね...
Dateを取得して、13時にツイートをするようにコードを書いたとしても機能してくれません。ではどうしたら自動的に機能してくれるようになるのか。ここでめちゃくちゃ重宝するのがsetIntervalです。

bot.js
const tweet = 'statuses/update';

bot.post(tweet, {status: "百合は尊い"}, function(err, tweet, res) {
  console.log("\n下記の内容をツイートしました!\n\n" + tweet.text);
});

 前回はこんな感じでツイートをする機能を書いていきましたね。ちなみに、コマンドは定数に代入することも出来るので、このようにして定数に代入しておくことで、「この機能をするためのコマンドなんだっけ...」といちいち考える必要がなくなるのですごくおすすめです!
この機能にsetIntervalを加えてみます。

bot.js
const tweet = 'statuses/update';

setInterval(function() {

  bot.post(tweet, {status: "百合は尊い"}, function(err, tweet, res) {
    console.log("\n下記の内容をツイートしました!\n\n" + tweet.text);
  });

}, 60_000);  // 1分ごと

こんな感じです。functionの時間指定の部分は、1,000で1秒なので、
1,000 * 60 = 60,000
60,000と指定することで、1分ごとに機能することが出来ます。こうすることで、1分ごとにツイートをする機能を取得することが出来るようになります。
 ただ、このままではツイート重複のエラーが出てしまうので、よくある方法としては、配列内に文字列をたくさん入れて、ランダム抽出する方法です。これはかなり使えるのですごくおすすめです!

bot.js
const tweet    = 'statuses/update';

let lily_texts = ["百合は尊い", "とりあえず百合は尊い", "やっぱり百合は尊い", "そして百合は尊い"];

let lily_text  = lily_texts[Math.floor(Math.random() * lily_texts.length)];  // ランダム抽出


setInterval(function() {

  bot.post(tweet, {status: lily_text}, function(err, tweet, res) {
    console.log("\n下記の内容をツイートしました!\n\n" + tweet.text);
  });

}, 60_000);  // 1分ごと

 まずはlily_textsにツイートしたい内容をどんどん書いていきます。そして、lily_textで、lily_textsの要素の中からランダムに抽出しています。このようにすることで、重複エラーはほぼ起こらなくなります。ただ、乱数なので、稀に被ってしまうことがあります。
 さて、これで自動的にツイートしたり、リツイートしたりする方法が分かりましたが、機能を実装する際に、決まった時間にだけ行いたいときもあると思います。そんなときは、setInterval内でDateを取得すれば大丈夫です!

bot.js
setInterval(function() {

  const now  = new Date();
  const hour = now.getHours();    // 時を取得
  const min  = now.getMinutes();  // 分を取得

  if(hour === 8 && min === 0) {   // 8時になったら
    bot.post(tweet, {status: lily_text}, function(err, tweet, res) {
      console.log("\n下記の内容をツイートしました!\n\n" + tweet.text);
    });
  };

}, 60_000);  // 1分ごと

 このように書くと、午前8時になったときだけ、ツイートをするようになります。

bot.js
setInterval(function() {

  const now  = new Date();
  const min  = now.getMinutes();  // 分を取得

  if(min === 0) {                 // 1時間ごとに
    bot.post(tweet, {status: lily_text}, function(err, tweet, res) {
      console.log("\n下記の内容をツイートしました!\n\n" + tweet.text);
    });
  };

}, 60_000);  // 1分ごと

 また、こういう風に書くと、setIntervalは1分ごとに機能していますが、ツイートをするのは1時間ごとになります。いろいろ試してみましょう!

まだフォローしていないフォロワーのみフォローする

 次に、botのことをフォローしてくれた方をフォローし返すための機能を書いてみましょう。

bot.js
const follower_list = 'followers/list';
const bot_name      = {screen_name: "maki_lily_bot"};

setInterval(function() {

  bot.get(follower_list, bot_name, function(err, follower_list, res) {
    console.log(follower_list);
  });

}, 60_000)  // 1分ごと

 まずはbotのフォロワーを確認する必要があるので、botのフォロワーを取得するコマンドから書いていきましょう。情報を取得するだけなので、getメソッドですね。そして、フォロワーを確認する機能で必須なパラメータはscreen_nameです。(@から始まるid名のことを指します。)
 さて、次は取得したフォロワーのことをフォローする機能を書いていきますが、フォローする機能は、通常は1回だけ行うことが出来るようになっています。では、どうすれば一度で一気にフォロワーのことをフォロー出来るようにするのか。ここで使えるのが繰り返し構文です。botのフォロワーの人数分繰り返したいので、配列を使った繰り返しを行うことが出来るfor...of文を使っていきましょう。

bot.js
setInterval(function() {

  bot.get(follower_list, bot_name, function(err, follower_list, res) {

    for(let followers of follower_list.users) {  // フォロワーの人数分繰り返す
      console.log(followers);
    };

  });

}, 60_000)  // 1分ごと

 ここで注意してほしいのが、配列を指定するときに、follower_listと書くのではなく、follower_list.usersと書くということです。理由は、フォロワー一覧を取得してみると、返ってくるのが配列ではなく、ハッシュになっているからです。その結果の中身をよく見てみると、usersというキーの中に、フォロワーさん達の情報が配列形式になって格納されています。つまり、follower_listのキーであるusersを指定してあげることで、配列を指定することが出来るということです。この状態で結果を見てみると、その配列の中身が表示されます。
 これでフォロワーの人数分機能を実装することが出来るようになりました。さて、次は、すでにbotがフォローしている人はフォローしないようにしなければいけません。そうしないと、「このフォロワーさんはすでにフォローしていますよ」とエラーが出てしまいます。とはいっても、やり方はそこまで難しくはありません。

bot.js
setInterval(function() {

  bot.get(follower_list, bot_name, function(err, follower_list, res) {

    for(let followers of follower_list.users) {  // フォロワーの人数分繰り返す

      if(followers.following === true) {         // すでにフォローしていたら飛ばす
        continue;
      }else {
        console.log(followers.following);
      };

    };

  });

}, 60_000)  // 1分ごと

 フォロワー一覧の結果をもう一度よく見てみると、【following】というキーがありますよね? これは、botがその人のことをフォローしているのかを真偽値の値で教えてくれるところなんです。trueと書かれていれば、その人のことはすでにフォローしているということです。ということは、trueの時は、その回は飛ばしてしまえばいいということですよね?繰り返し構文を飛ばすために使うコマンドはcontinueです。これですでにフォローしている人のことはフォローしないようにすることが出来ました。
 ただ、後1つだけエラーが起こる可能性が残されています... それが、鍵垢です。鍵垢は、普通の垢とは違い、フォローする前にリクエストを送る必要があります。またフォローする機能を実装する前に、そのユーザーが承認してくれれば全く問題ないんですが、してくれなかった場合は、またフォローしてしまうことになるんです。最後にそれを防ぐ必要があります。ただ、それも難しくはありません。フォロワーにフォローリクエストを送ったかどうか確認するには【follow_request_sent】という部分を見ます。trueなら送っているということになります。これを利用して、

bot.js
const follow        = 'friendships/create';

setInterval(function() {

  bot.get(follower_list, bot_name, function(err, follower_list, res) {

    for(let followers of follower_list.users) {       // フォロワーの人数分繰り返す

      if(followers.following === true) {              // すでにフォローしていたら飛ばす
        continue;
      }else {

        if(followers.follow_request_sent === true) {  // リクエストを送っていたら飛ばす
          continue;
        }else{
          bot.post(follow, {user_id: followers.id_str}, function(err, follow, res) {
            console.log("\n" + followers.name + "さんのことをフォローしました!");
          });
        };

      };

    };

  });

}, 60_000)  // 1分ごと

 これで遂に完成です! ちなみに、getメソッドの中で、postメソッドを使うときは、そのスコープ外のgetメソッドの値も利用することが出来るので、上記みたいに、user_idを書くときに、followers.id_strとすることで、指定することが出来ます。 本当にお疲れさまでした。

まとめ

 今回は基本的な機能に加えて、応用を使って、さらに高度な機能を実装するための方法を紹介しました。この方法を上手く使いこなせるようになると、フォロー機能だけでなく、いいね機能やリツイート機能などもすごく細かい設定をすることが可能になります。是非頑張ってみてください!なお、私が百合botを作成しているときに、一番苦闘した機能は返信機能です。マジでめちゃくちゃ苦労しました。多分30時間以上苦しんだかもしれません。もし要望があった場合は、返信機能のやり方も紹介してみようかなと思います。ありがとうございました。 ノシ

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

画面でエフェクトの実現の(三) --日付ダイアログ

一般的に、画面なかに日付の入力ボックスがあり、日付ダイアログを使って、いい効果が表現できます。
日付ダイアログの効果は以下のサイトを参照してください。
https://xdsoft.net/jqplugins/datetimepicker/

実装要点

・datetimeのプラグインを含める
・日付入力ボックスにスタイルを設定する
・datetimeのメソッドを付く

コードサンプル

// datetimeのプラグインを含める
<link rel="stylesheet" type="text/css" href="/jquery.datetimepicker.css"/ >
<script src="/jquery.js"></script>
<script src="/jquery.datetimepicker.js"></script>
// 日付入力ボックスにスタイルを設定する
<input id="date" class="icon-calendar" />
// datetimeのメソッドを付く
$("#date").datetimepicker({
timepicker:false,
format:'d.m.Y',
xxx:xxx,
xxx:xxx,
...
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サクッとi18n対応を行うためのReact hooksライブラリ、use-mini18nを作った。

現在、私はいくつかのアプリを個人で開発しています。
それらのほとんどはReactを用いたWebアプリです。アプリはどれも小さなものですが、世界中の人に使ってもらいたいと考えているため、i18n対応はなるべく行うようにしています。
(といっても、なかなか時間が確保できず、英語と日本語だけしか対応できていないケースばかりですが...)

一例を挙げると、ランダムな音楽を勧めてくれる、下記のようなシンプルなWebアプリを作っています。
Dig Music - Dig Musicはレコメンドエンジンを使用せず、完全にランダムな形でユーザに新しい音楽を紹介するWebアプリです。

Reactでi18n対応をする場合、react-i18nextreact-intl (FormatJSと呼ばれるモノレポに含まれる形に変わったようですね)を使うことが多いと思います。
私もこれらを使用したことがありますが、どちらも素晴らしいライブラリだと感じています。

ですが、私が作成するアプリは非常に小規模なものが多いため、これらのライブラリがオーバースペックに感じることもあります。
設定された言語設定に応じて、適切な言語ファイルを読み込み、表示できればそれでいいんだよな...と思いましたが、少し調べてみたところ、そのようなライブラリはなさそうに思えたので、自分で作ることにしました。
TypeScriptを用いてReact hooksライブラリを作成してみたかったという別の理由もあります。

シンプルでこじんまりとしたi18nライブラリ、use-mini18n

こちらが作ったものになります。

use-mini18n - A simple and minimalistic React hook library for i18n

ロゴも思い切って作りました。勢い大事。

use-mini18n-logo.png

こちらのライブラリでやれることはシンプルです。
以下のコードサンプルは、このライブラリがやれることをすべて表しています。
(実際に動作しているところをCodeSandbox上で確認できます)

import React from "react";
import ReactDOM from "react-dom";
import { TransProvider, useI18n } from "use-mini18n";
import "./styles.css";

const i18n = {
  en: {
    Hello: "Hello {name}",
    "Start editing to see some magic happen!":
      "Start editing to see some magic happen!"
  },
  ja: {
    Hello: "こんにちは {name}",
    "Start editing to see some magic happen!":
      "いくつかの魔法を目にするために編集を開始します!"
  }
};

const App = () => {
  const { t, lang, langs, changeLang, getText } = useI18n();

  return (
    <div className="App">
      <h1>{getText("Hello", { name: "CodeSandbox" })}</h1>
      <h2>{t["Start editing to see some magic happen!"]}</h2>
      <hr />
      <p>Selected lang: {lang}</p>
      <select
        name="lang"
        onChange={({ target }) => changeLang(target.value)}
        value={lang}
      >
        {langs.map((l, i) => (
          <option value={l} key={i}>
            {l}
          </option>
        ))}
      </select>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <TransProvider i18n={i18n}>
      <App />
    </TransProvider>
  </React.StrictMode>,
  rootElement
);

開発者が use-mini18n でやれることは、このサンプルコードに記載されていることだけです。
いくつかの細かなオプションも用意してあるので、そちらについてはリポジトリ内のREADME、もしくはexamplesを参照してください。

また、use-mini18nはデフォルトで、ユーザの希望した言語設定をブラウザの localStorage に保存します。
私がこのような設計にしたのは、開発者として言語設定の管理に一切時間を割きたくなかったからです。
もしそのような動作を望まない場合は、localStorageを利用しないオプションも存在しています。

<TransProvider i18n={i18n} enableLocalStorage={false}>
  <App />
</TransProvider>

これでuse-mini18nに関する話は終わりとなります。

use-mini18nのミニマムな世界観には満足しています。このライブラリはi18n関連のすべてをカバーしているわけではありませんが、それはそれで良いと考えています。
もしuse-mini18nが力不足だと感じるときがあれば、そのときは他の素晴らしいi18nライブラリに切り替えるときだと考えています。

もしバグや疑問点などありましたら、上に貼ったGitHubリポジトリか、こちらのポストに直接コメントを頂けたらと思います。

というわけで、サクッとi18n対応を行うためのReact hooksライブラリを作ってみた、という内容でした。
最後まで読んでいただき、ありがとうございます!

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

【vue-moment】vueでtimer設定する方法

目次

vueでtimer設定をしたい
timer設定するために必要なもの
vue-momentのインストール
vue-momentのtsファイル作成
moment.tsを読み込む
vueファイルでmomentを試してみる
momentでtimer設定を試してみる

vueでtimer設定をしたい

vueで一定期間表示する枠や時期が来たら表示または非表示にするためにタイマー設定をして実装したい場合ってありますよね?
実際、私はその事例に当たったので今回対応したことをお伝えしたいと思います。

timer設定するために必要なもの

vueでtimer設定するために

vue-moment

を使用します。

vue-momentは、日付の解析・検証等をするライブラリで、
日付のフォーマットを「YYYY-MM-DD」に変更した場合や、日付の期間を検証するのに便利なメソッドが揃っています。
Vue.jsプロジェクトに便利なmoment.jsフィルターを提供してくれます。

vue-momentのインストール

npm

$ npm install vue-moment

yarn

$ yarn add vue-moment

インストールが終わったら共通で使えるようにjsファイルを作成します。

vue-momentのtsファイル作成

src/plugins/moment.ts
import Vue from 'vue'
Vue.use(require('vue-moment'))

ファイルを作成したらnuxt.configで読み込んでおきましょう。

moment.tsを読み込む

nuxt.config.ts
plugins: [{ src: '~/plugins/moment.ts' }],

pluginsに先ほど作成したmoment.tsを読み込んでおきます。

ここまでできたらあとは、vue側での実装です。

vueファイルでmomentを試してみる

<template>
    <p>
      {{ now }}
    </p>
    <p>
      {{ end }}
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      now: this.$moment().format('YYYYMMDDHHmmss'),
      end: this.$moment().format('20200825150000'),
    }
  },
}
</script>

このようなファイルを作ると
nowは今の日時・時間・分・秒まで取得します。
今回はendとしてますが、表示を開始するなどの場合はstartなどにするといいと思います。
endには表示を終了する日時・時間・分・秒を設定しておきます。

この状態で上記を表示すると

See the Pen vue-moment by miyawash (@miyawash) on CodePen.

これをベースにタイマー設定をしてみます。

momentでtimer設定を試してみる

<template>
    <p>
      {{ now }}
    </p>
    <p>
      {{ end }}
    </p>
    <p v-if="now < end">まだ設定時間ではない</p>
    <p v-else>設定時間をすぎた</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      now: this.$moment().format('YYYYMMDDHHmmss'),
      end: this.$moment().format('20200825150000'),
    }
  },
}
</script>

このように実装すると設定時間までは
「まだ設定時間ではない」が表示され、設定時間後は「設定時間をすぎた」が表示される
これにより、設定時間までの表示や設定時間からの表示などができるようになりました!

See the Pen vue-moment timer by miyawash (@miyawash) on CodePen.

参考資料
https://classic.yarnpkg.com/ja/package/vue-moment
https://www.npmjs.com/package/vue-moment
https://momentjs.com/
https://qiita.com/taizo/items/3a5505308ca2e303c099
https://blog.capilano-fw.com/?p=908#24_8_20

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

QiitaAPIで20年投稿のストック数が多い記事のタグ情報を調べてみた

はじめに

ほとんどプログラミング経験のない初心者(VB.Netを少しかじった程度)ですが、最近「モノをつくる」学習の場を与えて貰えたので、自分なりに色々調べて、JavaScriptにチャレンジしてみました。記事の投稿も初で、初めて尽くしで戸惑ってます。。
ソースコードがキレイじゃないのもご愛敬。

目的

Node.jsとQiitaAPI(+axios)を使って今の自分に何ができるか考えてみた。
で、今回は、「今年(2020年)に投稿された記事で、ストック数の多い記事のタグってどんなの?」を調べてみました。
(ストック数が多い記事として、今回はストック数が300以上の記事を対象としてみた。)

コード

QiitaAPI.js
// axiosモジュールの読み込み
const axios = require('axios');
axios.defaults.baseURL = 'https://qiita.com/api/v2';

let array1 = [];  // タグ名用
let array2 = [];  // カウント用

// main関数
async function main(page) {

  // QiitaAPIからレスポンス受け取り(ストック>=300 + 作成日>=2020-01 の記事一覧)
  let response = await axios.get(
      '/items?page=' + page + '&per_page=100&query=stocks:' +
      encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01')
  );

  // 取得した記事から[tags]要素を取得
  for (let i=0; i<response.data.length; i++) {
    for (let j=0; j<response.data[i].tags.length; j++) {

        var tagname = response.data[i].tags[j].name;    // タグ名取得

        // 配列に登録があれば、カウントを+。登録が無いなら配列に追加。
        var result = array1.indexOf( tagname );         // 検索(既に配列にあるか)
        if (result < 0) {
            array1.push(tagname);
            array2.push(1);
        } else { array2[result] += 1; }
    }
  }

  // 結果出力
  if (page==5) {
      for (let i=0; i<array1.length; i++) {
        console.log(array1[i]+', '+array2[i]);
      }
  }
}

// main関数 実行
let promise = Promise.resolve();
for (let k=1; k<6; k++) {
    promise = promise.then(() => main(k));
}

色々ハマった点

1.QiitaAPIって何がとれるのさ?

QiitaAPIの仕様書を読み込んでみた。(色々取れそうだけど、技術力が追い付かないので断念。)
最初におや?と思ったのは、1回のレスポンスで最大100件、ページ指定も最大100ページ。つまり、1万件まで。
100件以上を取ろうと思うとページを何度か呼び出すしかないのかな。ってことで、main関数をループすることにした。

  let response = await axios.get(
      '/items?page=' + page + '&per_page=100&query=stocks:' +
      encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01')
  );

2.非同期処理ってなんぞや?

ループしようと思ったはいいけど、上手くいかない。
どうも非同期処理で走るからって理由らしい。正直良く分かってないが、以下サイトを参考に改善してみた。
PromiseによるJavaScript非同期処理レシピ集

let promise = Promise.resolve();
for (let k=1; k<6; k++) {
    promise = promise.then(() => main(k));
}

結果

結果は、EXCELで集計。
(あまり細かくなってもアレなので、表は上位10件、グラフは記事数7件以上を表示。)
※2020年に投稿された記事で、ストック数が300以上の記事についてタグを集計

タグ 記事数
JavaScript 67
Python 58
初心者 49
機械学習 24
新人プログラマ応援 24
HTML 16
DeepLearning 16
GitHub 16
Vue.js 16
React 16

image.png

考察

Qiitaトップのタグ・ランキングとはまた違った結果になった。
上位の「JavaScript」「Python」は、今年も熱い。ストック数も多いということは、興味関心が高く、役立つ情報がまだまだあるってことかなぁ。

興味深いのは、タグ・ランキングでは上位の「Ruby」がランク外。
最近の投稿が少ないか、ストック数が伸びないのか、、、??

最後に

JavaScriptの学習も兼ねて今回はチャレンジしてみましたが、もっとより良いアプローチがある気がしました。
そもそも、私の技術力の無さを痛感。(書いてませんが色々断念してます。。。)
まずは書く!調べる!やってみる!
トライアンドエラーで今後も素人ながら挑戦していきます。

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

【忘備録】すぐ忘れるからwebpackについてまとめておく

webpackとは?

  • バンドルツール
  • Node.jsのサーバーサイドで動く
  • プラグインなどで変換処理を加えることができる
  • JavaScriptなどのファイルをまとめて一つにしてくれる

npmとの違い。

  • npmはライブラリのバージョン管理をしてくれる。
  • webpackはJSやCSSのファイルをまとめてくれる。

webpackは何が嬉しい??

  • プラグインが使える。
  • 読み込み順を気にしなくてもよくなる。
  • 依存関係の解決。

すぐ、忘れてしまう。

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

【忘備録】npmがふわふわしてるから、軽くまとめておく。

 npmとは

  • Node Package Manager の略
  • Node.jsのパッケージを管理するもの

Node.jsとは?

サーバーサイドで動く、JavaScriptのこと。

 パッケージとは?

Node.jsに用意されたモジュール(ライブラリなど)をまとめたもの。

Rubyでいうところのgemと似ていますね。

以上。

メモがきでした。

すぐ忘れて、もやもやしてしまう。

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

GatsbyJS ローカルで開発中のページをスマホでも見る方法

ローカルで開発中のページをスマホで確認したい場合は、プライベートIPでアクセス可能にしてあげればスマホでも開発中のページをスマホでも見ることができます

実行コマンド

gatsby develop -H 0.0.0.0

実行後

参考画像1
スマホで赤枠で囲んでいるURLにアクセスすると開発中のページが、閲覧できます。
※ 接続できない場合は、同一のネットワーク環境出ない可能性があるので、ネットワークの接続先を確認しましょう。

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

FetchでExcelファイルがダウンロードできるページに条件をPOSTしてブラウザダウンロードする

やりたいこと

タイトル通りなんですが、いろいろ調べてみても直接ファイルが落ちてこなくて試行錯誤してたどり着いたやつです。

少しだけ解説

headersとしてheaderの内容を記載するのですが、AcceptにExcelであること、responseTypeにblob形式であることが重要なようです。
またfetchで返ってきた値をblob()で処理するとファイル形式(バイナリ形式)として扱われるので無理くりリンク作ってクリックさせています。
https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch
https://developer.mozilla.org/ja/docs/Web/API/Body/blob

example.js
            var filename = 'excelfile';
            var obj = {
                fname: filename,
                format: 'XLSX'
                };
            var method = "POST";
            var body = Object.keys(obj).map((key)=>key+"="+encodeURIComponent(obj[key])).join("&");
            var headers = {
              'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
              'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
              'responseType' : "blob",
            };
            fetch("アクセスするURL", {method, headers, body})
            .then((res)=> res.blob())
            .then(blob => {
                let anchor = document.createElement("a");
                anchor.href = window.URL.createObjectURL(blob);
                anchor.download = filename+".xlsx";
                anchor.click();})
            .then(console.log)
            .catch(console.error);

参考にさせていただきました

JavaScriptでファイルダウンロード
fetch API でも POST したい!

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

【改訂】Gatsbyでページごとに個別にCSSを読み込む方法

はじめに

HTML, JavaScript, CSSで構築された既存のサイトを、Gatsbyで再構築しています。

以前、Gatsbyでページごとに個別にCSSを読み込む方法という記事を書きました。
しかし、その後作業を進めていくうちに、こちらの方法では問題を解決できないことがわかりました。
今回は改めて対策方法を検討したので、共有します。

前回の内容

前回はこのような方法でページごとに個別にCSSを読み込むことができると言いました。

    import React from "react"

    export default () => (
           require("style.css")
    )

しかし、この内容でNetlifyにアップロードしたところ、うまくいきませんでした。
個別ページで読み込んだハズのCSSが、全てのページで読み込まれてしまい、上書きされてしまうという現象が再発してしまいました。

Gatsby Develop と Gatsby Serve の違い

原因を調べると、上記の方法は gatsby develop ではうまく動作するものの、
gatsby serve したものではうまく動作していませんでした。
Netlifyにアップロードされるのは gatsby serve の方です。

新・ページごとに個別にCSSを読み込む方法

とういうことで、gatsby serve でもうまくいく方法を紹介します。

  1. 既存のCSSの拡張子.css.scssに変更する
  2. ファイルを開き、先頭と末尾をhtml.hoge{}で囲む
hoge.css
        #header{
          background-color:red;
        }
hoge.scss
        html.hoge{
            #header{
               background-color:red;
            }        
        }

3.読み込みたいページ(例:hoge.js)でimportする

hoge.js
        import React from "react"
        import "../css/home.scss"

        export default () => {
             return(

               //ページの処理
               <header id="header">ヘッダー</header>

             )
        }

4.「Helmet」を使ってhtmlタグにhogeというクラス名を付ける

hoge.js
        import React from "react"
        import { Helmet } from "react-helmet"
        import "../css/home.scss"

        export default () => {
             return(
                <div>
                    <Helmet>
                       <html className="hoge"  lang="jp" />
                    </Helmet>

                    //ページの処理
                    <header id="header">ヘッダー</header>

               </div>
             )
        }

以上です。
Helmetの使い方は「Gatsby Helmet」などで調べてみてください。

おわりに

今回のやり方はかなり邪道な方法です。
お気づきかもしれませんが、このやり方はページ毎に個別に読み込んではおらず、
結局全てのCSSを読み込んでしまっています。
単純に、SCSSのネストでセレクタを指定することで、ページごとに適応するものを変えているだけです。
Gatsbyが提唱するサイトの高速化の手法をあえて無視するようなやり方です。

今回はとりあえず、一般的な HTML, CSS, JavaScript で組まれたWEBサイトを
あまり手を加えずにGatsbyにコンバートすることを試してみました。
ですが、その作業がこんなにも大変だとは思いませんでした。

WordPressみたいに、最初は静的ファイルでテストページを作成して、その後CMS化する
という手法はあまり馴染まないのかもしれないですね。

ある程度初期の段階から、Gatsbyで組み上げていったほうが早いかもしれません。
次回は、GatsbyでjQueryを使う方法(邪道)を書きたいと思います。

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

jQuery Validationで分岐処理

Webページの状態によって入力チェックの内容を変更する

validationってif分岐出来ないんですか?と聞かれて調べたのでメモ

ここ
に書かれていました。

$("#sample").validate({
        rules : {
            name :  {
                required : $("#flag").val() == "0"
            }
        },

の様に書けば良いらしいです。
  
例えば、
requiredであれば上記の様にbooleanで返るようにする。
maxlengthであれば以下の様にnumberになるようにすれば良いのかな?

maxlength: function () {
  if ($("#name").val == "jugemu") {
    return 200
  } else {
    return 100
  }
}

試していないです。。

if分岐ではないですが、処理を分けることはできそうです。

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

Javascriptでボタンのクリックを待ち受ける方法

ボタンが押されるまで待ち受けて、押されたボタンの種類を返す処理

waitForClick() {
  return new Promise((resolve, reject) => {
    document.getElementById('button1').onclick = () => resolve('button1');
    document.getElementById('button2').onclick = () => resolve('button2');
  });
}

const result = await waitForClick();

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

npm-scriptsをnpm-run-allで用途や環境毎に書く際の小技

  • Web開発の際のタスク処理として、package.json内に記述して利用できるnpm-scriptsを利用しています。
  • またその際に、複数処理の直列化や並列化に便利なnpm-run-allを利用しています。
  • 今回はそれを用いた用途や環境毎への記述を、明確かつ柔軟にする方法を記録します。

結果

  • 先に結果の記述を示します。以下の通りです。
{
  "scripts": {
    "build"      : "run-s build:{sass,ts}",
    "build:sass" : "sass input.scss output.css",
    "build:ts"   : "tsc main.ts",
    "watch"      : "run-s watch:{sass,ts}",
    "watch:sass" : "sass --watch input.scss output.css",
    "watch:ts"   : "tsc -w main.ts"
  }
}

内容

  • 上記の結果例は、主にbuildwatchを行うタスクです。
  • こちらで利用している方法は、処理内容を波括弧で指定するということです。

波括弧での指定

  • コロン(:)で用途毎に分けたタスクを一回で実行する際には、以下のようなやり方があります。
{
  "scripts": {
    "build-basic" : "run-s build:sass build:ts",
    "build-glob"  : "run-s build:*",
    "build-brace" : "run-s build:{sass,ts}",
    "build:sass"  : "sass input.scss output.css",
    "build:ts"    : "tsc main.ts",
  }
}
  • これらから私はbuild-braceのような波括弧で指定した方法を利用しています。
  • そのメリットは以下の通りです。
    • 非重複
      • run-s build:sass build:tsのように言葉の重複が無く、今後同一のタスクが増えても、用途の記述だけで済む。
    • 明示化
      • run-s build:*のようなアスタリスク指定と比べて、明示的に記述されているため、処理の内容が理解しやすい。
    • 順序化
      • 波括弧内の順に処理を行ってくれるため、仮にpackage.json内でbuild:sassbuild:tsを逆に記述しても問題無い。
        • run-s build:*での指定では、処理順番がpackage.json内の並び通りに行われるため、逆に記載してしまうとその順になってしまう。

まとめ

  • 上記のことから、タスクやscript記述においても可読性・明示化・非重複性を再認識しました。

参考

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

[Javascript] Const, Let, Varの違い(ES6関連)

Javascriptの変数

Javascriptには、変数を宣言するキーワードのconstletvarがあります。
これらのキーワードは以下について違いがあります。

・初期値
・重複した値割り当て
・呼び出し範囲

まずconst, let, var すべての変数は下記のように
、コンマで区切って複数の変数を定義することができます。

const category= "header",
      key= "content-Type",
      value= "application/json";

Constについて

constは値が変わらない変数を宣言するキーワードです。
constは必ず初期値を設定しなければならず、
宣言した変数に対して、あとで値を割り当てることはできません。

const で宣言した変数に値を再度割り当てる場合には、
次のようなエラー(TypeError)が発生します。

const key = "content-Type";
key = "appId"; 
// Result => TypeError: invalid assignment to const 'key'

変数に値を割り付ける必要がない場合には、
const キーワードで変数宣言することを推奨します。

ループなどの繰り返しの途中で特定変数が参照する値を変えなければならないときがあります。
その場合は変数に再割り当てできるletキーワードで宣言します。

Letについて

letは初期値を指定しなくても大丈夫です。
初期値を指定しない場合、undefined 値に初期化されます。

let key;
// `key` is initialized `undefined`

letが宣言されたkeyという変数に"content-Type"という文字列を代入しています。

let key;
key = "content-type";

Varについて

var キーワードは初期値を宣言しなくてもよく、
値を再割り当てしても良いという点でletに似ています。
しかし、varキーワードは同じ名前の変数を定義し直すことができます。

letconstは同じ名前の変数を定義することができず、
次のような構文エラー(Syntax Error)が発生します。
したがって、ミスとして変数を二重に定義するミスを防ぐことができます。

let key;
let key; // => SyntaxError: redeclaration of let key

varの場合には、同じ名前の変数を定義し直すことができます。
これは間違いで同じ変数の名前で定義しても間違いはなく、
以前に宣言した値を閉じてしまいます。

var x = 1;
var x = 2;
// x is 2

letは、変数を宣言する前にその変数を参照するとReferenceErrorが発生します。

console.log(x); 
//ReferenceError: can't access lexical declaration `x' 
before initialization
let x = 1;

varは変数を宣言する前に、その変数を参照してもundefined値が割り当てられます。

console.log(x); // => undefined
var x = 1;

まとめ

JavaScriptは変数を宣言するキーワードとして、constletvarについてまとめました。

constは値を再割り当てできない変数を宣言する時に使用されます。
letvarは再割り当てできる変数を宣言することができ、
初期値を宣言しなくてもかまいません。
var の場合のみ、同じ値を重複宣言することができます。

const は再割り当て不可能な変数を定義するキーワードです。
再割り当てを禁止することで、誤って他の値を割り当てることで発生するバグを減らすことができます。

したがって、変数を宣言する場合には、まずconstで定義できないかを検討の上、
不可能な場合はletの使用をお勧めします。

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

Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #5 ログイン・ログアウトの実装

Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #4 サインアップページの作成

ログアウト機能の実装

先にログアウトを作ります。

layouts/default.vue
...
       <v-btn
         v-if="logged_in"
         icon
+        @click="logOut"
       >
         <v-icon>mdi-logout</v-icon>
       </v-btn>
...
export default {
...
           this.$store.commit('setAuth', {})
         })
     }
+  },
+  methods: {
+    logOut () {
+      this.$store.dispatch('logOut')
+    }
   }
 }
 </script>
store/index.js
...
 export const actions = {
   async signUp (_c, user) {
     return await this.$axios.$post('/v1/auth', user)
+  },
+  async logOut ({ commit }) {
+    await this.$axios.$delete('/v1/auth/sign_out')
+    commit('setUser', null)
+    commit('setAuth', {})
+
+    this.$router.push('/')
   }
 }

v1/auth/sign_outすることでバックエンドでログアウトとします。
そしてフロントエンド側もstoreから削除し(同時にlocalStorageもクリアされます)、トップページに飛ばします。

ログイン機能の実装

$ touch pages/log_in.vue
pages/log_in.vue
<template>
  <v-app id="inspire">
    <v-main>
      <v-container
        class="fill-height"
        fluid
      >
        <v-row
          align="center"
          justify="center"
        >
          <v-col
            cols="12"
            sm="8"
            md="4"
          >
            <form @submit.prevent="logIn">
              <v-card class="elevation-12">
                <v-toolbar
                  color="primary"
                  dark
                  flat
                >
                  <v-toolbar-title>LogIn form</v-toolbar-title>
                </v-toolbar>
                <v-card-text>
                  <ul v-if="errors">
                    <li v-for="(message, i) in errors" :key="i">
                      {{ message }}
                    </li>
                  </ul>
                  <v-text-field
                    id="email"
                    v-model="email"
                    label="email"
                    name="email"
                    prepend-icon="mdi-email"
                    type="email"
                  />

                  <v-text-field
                    id="password"
                    v-model="password"
                    label="Password"
                    name="password"
                    prepend-icon="mdi-lock"
                    type="password"
                  />
                </v-card-text>
                <v-card-actions>
                  <v-spacer />
                  <v-btn color="primary" type="submit">
                    ログイン
                  </v-btn>
                </v-card-actions>
              </v-card>
            </form>
          </v-col>
        </v-row>
      </v-container>
    </v-main>
  </v-app>
</template>

<script>
export default {
  data () {
    return {
      email: '',
      password: '',
      errors: []
    }
  },
  methods: {
    async logIn () {
      try {
        const data = await this.$store.dispatch('logIn', {
          email: this.email,
          password: this.password
        })
        this.$store.commit('setUser', data.data)
        this.$router.push('/')
      } catch (e) {
        this.errors = e.data.errors
      }
    }
  }
}
</script>

store/index.js
...
export const actions = {
...
  async logOut ({ commit }) {
    await this.$axios.$delete('/v1/auth/sign_out')
    commit('setUser', null)
    commit('setAuth', {})

    this.$router.push('/')
+ },
+ async logIn (_c, user) {
+   return await this.$axios.$post('/v1/auth/sign_in', user)
  }
layouts/default.vue
...
         <v-icon>mdi-account-plus</v-icon>
       </v-btn>
+      <v-btn
+        v-if="!logged_in"
+        icon
+        to="/log_in"
+        nuxt
+      >
+        <v-icon>mdi-login</v-icon>
+      </v-btn>
     </v-app-bar>
...

ほとんどsignUpのコピペでいけます。

未ログインだと以下のようになります。
未ログイン中

そしてログイン中だと以下のようになります。
ログイン中.png

これでログイン周りの必要最低限の機能は揃いましたね。

次回は連載最終回。記事の投稿・削除を行います。

続き


連載目次へ

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

FlaskにAjaxリクエストのFetch APIを組み込んだシンプルなアプリを作成してさくっと説明する

はじめに

・Flask と JavaScript を勉強し始めた方向けとなります
TheCatAPI.com : Betaを使用して、猫の画像をAjaxリクエストで取得するアプリを作成します

Fetch API で猫の画像を取得するFlaskアプリを作成する

Flaskを用意します。
Flaskがまだちょっとよくわからないという方は以下の記事を確認してください。
【Pythonで多分人気2位のWebアプリケーションフレームワーク】Flaskの基本をわかりやすくまとめる

構成は以下です。

app.py
templates
└─hello.html
app.py
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def hello_world():
    return render_template('hello.html')

if __name__ == "__main__":
    app.run(debug=True)
hello.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
    <h1>hello</h1>
</head>
<body>

</body>
</html>

上記でFlaskを起動させるとブラウザ上で以下の画面が出ます。
image.png

JavaScript内でAPIをたたく版

それではFetch APIで猫の画像を表示させていきます。
ボタンをクリックして猫の画像を出すようにします。

HTMLの全体のコードが以下です

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>hello</title>
    <style>
        .w300 {
            width: 300px;
        }
    </style>
</head>
<body>
    <form id="form">
        <div id="image"></div>
        <button id="lookCatButton" type="button">猫見る</button>
    </form>
    <script>
        const url = 'https://api.thecatapi.com/v1/images/search?size=full';
        const options = {
            method: 'GET'
        }

        function getCatImage(url, options){
            return fetch(url, options)
            .then( response => response.json() );
        }
        async function getImage(url, options){
            const response = await getCatImage(url, options);
            const imageDiv = document.getElementById("image");
            const imageElement = document.createElement("img");
            imageElement.src = response[0].url;
            imageElement.className = "w300";
            imageDiv.appendChild(imageElement);
        }
        document.getElementById("lookCatButton")
        .addEventListener("click", () =>{
            getImage(url, options)
        })
    </script>
</body>
</html>

説明

Fetch API では1つ目の引数にリクエスト先のURL、2つ目にoptionsを設定できます。
今回は以下のようにURLとoptionsを定義します。

const url = 'https://api.thecatapi.com/v1/images/search?size=full';
const options = {method: 'GET'};

optionsではリクエストのメソッドとしてGETを指定します。
optionについて詳しく知りたい方は以下を参照すると良いです。
https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch
https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch

fetchの返り値はpromiseで返ってくるため、.then()メソッドが使えます。
かつ.then()ではコールバック関数が使えるので、中にアロー関数引数が1つなので () を省略し、かつ唯一のステートメント return なので {} を省略して書いています。

fetch(url, options)
.then( response => response.json() );

省略しないアロー関数で書くと以下のようになります。

fetch(url, options)
.then( (response) => {
    return response.json()
});

fetchを使用する際、リクエストを送ってレスポンスが返ってくる前に、その後ろに書いたコードが実行されることがあります。
そのためasyn/awaitを使用して、fetch を実行してレスポンスが返ってきてから後続のコードを実行させるようにしています。

asyncファンクションを定義して、その中でfetchを使用した関数にawaitを付けて変数に入れます。以下の箇所です

async function getImage(url, options){
    const response = await getCatImage(url, options);

その後に猫ちゃんの画像URLをappendChildして表示させるようにしています。

一連の処理はボタンをクリックしてから実行させるようにしているため、addEventListenerclickされたら実行するようにしています。

document.getElementById("lookCatButton")
.addEventListener("click", () =>{
    getImage(url, options)
})

これでボタンを押すたびに猫ちゃんの画像が表示されるようになりました。
image.png

あえてpython側でAPIたたく版

上述の通り、fetchでは1つ目の引数にリクエストを送るURLが必要であるため、Flaskのルーティングでリクエスト先のURLを作成します。
そこで猫ちゃんAPIのデータを取得するコードと、レスポンスとして返す際にjsonでダンプさせるためにjsonifyでreturnします。

以下のコードにしました。
/get_dataを追加しています。

from flask import Flask, render_template, jsonify
import requests
app = Flask(__name__)


@app.route('/get_data')
def get_data():
    url = 'https://api.thecatapi.com/v1/images/search?size=full' #猫ちゃんAPI
    response = requests.get(url)
    cat_img_url = eval(response.text)[0]['url'] #ここでイメージURLを取得
    response_data = {
        'cat_img_url' : cat_img_url
    }
    return jsonify(response_data) #jsonifyでjs側に返却する


@app.route('/')
def hello_world():
    return render_template('hello.html')

if __name__ == "__main__":
    app.run(debug=True)

JavaScript側では、リクエスト先のURLを、先ほど設定したルーティング先の ./get_data に変更します。

const url = './get_data';

pythonからはjsonにダンプされた値が入るため、python側で設定した「cat_img_url」を取得するようにします。

imageElement.src = response.cat_img_url;

それ以外はすべて同じにして、全体では以下のコードとなります。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .w300 {
            width: 300px;
        }
    </style>
</head>
<body>
    <form id="form">
        <div id="image"></div>
        <button id="lookCat" type="button">猫見る</button>
    </form>
    <script>
        const url = './get_data';
        const options = {
            method: 'GET'
        }

        function getCatImage(url, options){
            return fetch(url, options)
            .then( response => response.json() );
        }
        async function getImage(url, options){
            const response = await getCatImage(url, options);
            const imageDiv = document.getElementById("image");
            const imageElement = document.createElement("img");
            imageElement.src = response.cat_img_url;
            imageElement.className = "w300";
            imageDiv.appendChild(imageElement);
        }
        document.getElementById("lookCat")
        .addEventListener("click", () =>{
            getImage(url, options)
        })
    </script>
</body>
</html>

これでボタンををクリックすると、python側で画像イメージを取得して表示させることができます。

image.png

ちなみに今回追加したルーティング先を直接アクセスしてみると以下の内容が表示されます。
image.png

おわりに

今回python側でリクエストするのとJavaScript側でリクエストするパターンの2つでfetch リクエスト、レスポンスをしてみたのは頭の整理としてとても良かったです。

参考文献

最新Ajaxリクエスト通信、Fetch APIについてメモ
【Pythonで多分人気2位のWebアプリケーションフレームワーク】Flaskの基本をわかりやすくまとめる
MDN web docs - Fetch API
MDN web docs - Fetch の使用
MDN web docs - Callback function (コールバック関数)
MDN web docs - アロー関数
MDN web docs - async function

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

お手頃高機能スライダーはSwiper使っておけばいい

概要

実装をする中で実装可能だけど、そこに時間あんまり費やしたくないなぁって時結構ありますよね。
画像のスライダー機能の実装でそのような状況になりました。
自作実装もできるけど。。アニメーションを場面ごと切り替えたりするのが結構手間><
そこで、よく使われているライブラリを探していたところSwiperを見つけて実装がとても楽だったので共有したいと思います。

実装例

スライダーで使うcssをimportする

index.tsx
import "swiper/swiper.scss";
import 'swiper/components/pagination/pagination.scss';

スライダー用のコンポーネントを実装する

SwiperComponent.tsx
import * as React from 'react';
import SwiperCore, { Pagination, Autoplay } from "swiper";
import { Swiper, SwiperSlide } from "swiper/react";

SwiperCore.use([Pagination, Autoplay]);

interface Props {
  imageData: Array<string>;
  // trueで自動でスライダーアニメーションが動く
  isAuto: boolean;
  // trueでpaginationクリックでスライダーアニメーションが動く
  clickable: boolean
}

const SwiperComponent: React.FC<Props> = (props) => {
  return (
    <div>
      <Swiper pagination={{ clickable: props.clickable }} autoplay={props.isAuto}>
        {props.imageData.map((imageSrc, i) => {
          return (
            <SwiperSlide key={`${imageSrc}${i}`}>
              <img key={`${imageSrc}`} src={imageSrc} />
            </SwiperSlide>
          );
        })}
      </Swiper>
    </div>
  );
};
export default SwiperComponent;

これだけ!!

あとはスライダー用のコンポーネントをスライダー実装したい箇所に組み込んであげるだけ!

<SwiperComponent imageData={["image/image1.jpg", "image/image2.jpg"]} isAuto={true} clickable={false} />

これで画像スライダーの完成!
とても簡単。

他にも便利な機能が山ほどあります。
スライドアニメーションもたくさんありますので色々と試してみるといいかもです。

まとめ

今回は一番シンプルなスライダーをSwiperを使って簡単な実装例と共に紹介しました。
ちょっと実装に時間かかりそうなところがめちゃくちゃシンプルに実装できましたね。
Vueにも対応しているみたい

※注意点
ライブラリは便利な反面、何気なく使ってしまうと危険な時があります。
急に開発元が開発をやめたり、特定のバージョンや環境でしか動かなこともザラにあります。
ライブラリを使うときはドキュメントをしっかり読み、ライセンスの確認、使っているユーザーの数(githubスターの数)
さらには開発状況などを確認した上で慎重に使うことをお勧めします。
個人で試す、勉強で使う場合は問題ないと思いますが、
仕事として今後世に出ていくようなサービスで使う場合はその辺りは意識してライブラリは使っていくといいと思います。

参照

Swiper 公式
Swiper React
Swiper Vue
Swiper Demos

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

お手頃・高機能スライダーはSwiper使っておけばいい

概要

実装をする中で実装可能だけど、そこに時間あんまり費やしたくないなぁって時結構ありますよね。
画像のスライダー機能の実装でそのような状況になりました。
自作実装もできるけど。。アニメーションを場面ごと切り替えたりするのが結構手間><
そこで、よく使われているライブラリを探していたところSwiperを見つけて実装がとても楽だったので共有したいと思います。

実装例

スライダーで使うcssをimportする

index.tsx
import "swiper/swiper.scss";
import 'swiper/components/pagination/pagination.scss';

スライダー用のコンポーネントを実装する

SwiperComponent.tsx
import * as React from 'react';
import SwiperCore, { Pagination, Autoplay } from "swiper";
import { Swiper, SwiperSlide } from "swiper/react";

SwiperCore.use([Pagination, Autoplay]);

interface Props {
  imageData: Array<string>;
  // trueで自動でスライダーアニメーションが動く
  isAuto: boolean;
  // trueでpaginationクリックでスライダーアニメーションが動く
  clickable: boolean
}

const SwiperComponent: React.FC<Props> = (props) => {
  return (
    <div>
      <Swiper pagination={{ clickable: props.clickable }} autoplay={props.isAuto}>
        {props.imageData.map((imageSrc, i) => {
          return (
            <SwiperSlide key={`${imageSrc}${i}`}>
              <img key={`${imageSrc}`} src={imageSrc} />
            </SwiperSlide>
          );
        })}
      </Swiper>
    </div>
  );
};
export default SwiperComponent;

これだけ!!

あとはスライダー用のコンポーネントをスライダー実装したい箇所に組み込んであげるだけ!

<SwiperComponent imageData={["image/image1.jpg", "image/image2.jpg"]} isAuto={true} clickable={false} />

これで画像スライダーの完成!
とても簡単。

他にも便利な機能が山ほどあります。
スライドアニメーションもたくさんありますので色々と試してみるといいかもです。

まとめ

今回は一番シンプルなスライダーをSwiperを使って簡単な実装例と共に紹介しました。
ちょっと実装に時間かかりそうなところがめちゃくちゃシンプルに実装できましたね。
Vueにも対応しているみたい

※注意点
ライブラリは便利な反面、何気なく使ってしまうと危険な時があります。
急に開発元が開発をやめたり、特定のバージョンや環境でしか動かなこともザラにあります。
ライブラリを使うときはドキュメントをしっかり読み、ライセンスの確認、使っているユーザーの数(githubスターの数)
さらには開発状況などを確認した上で慎重に使うことをお勧めします。
個人で試す、勉強で使う場合は問題ないと思いますが、
仕事として今後世に出ていくようなサービスで使う場合はその辺りは意識してライブラリは使っていくといいと思います。

参照

Swiper 公式
Swiper React
Swiper Vue
Swiper Demos

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