20191001のJavaScriptに関する記事は7件です。

javascriptのMapは中身が入っていてもJSON.stringifyすると空に見える

掲題の通り

const hoge = new Map();
hoge.set(1,1);
console.log(JSON.stringify(hoge));// {} が出力される

正しい動作なので慌てずに中身をentriesなどで取り出して検証しましょう。

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

Amazon Photosで一括削除できなかったのでスクリプト書いた

きっかけ

アマゾンのプライム会員になると、Amazon Photosというサービスを利用できます。

外部リンク : Amazonプライムフォトが超最強!RAWも無制限にバックアップ可能

画像のみなら容量制限がなく、枚数制限もなく、劣化もしないという神サービスなのですが
画像枚数が一定枚数を超えると一括削除できません

最初の方は1枚選ぶと「すべて選択」というボタンが出現していたのですが、3000枚を超えたあたり?で出なくなったようです。
1枚ずつ選んで消すなんて修行はしたくなかったので、スクリプト書きました。

画像を選択してスクロールしていくだけのスクリプトです。
ChromeのデベロッパーツールからConsoleを開いて(Ctrl + Shift + J)貼り付けて実行すると
3000枚まで自動選択されます。※一度に選択できる上限

削除できたらリロードして貼り付けて~を繰り返してください。

うまくいかない場合はtimeとかいじってみてください。

const time = 0;
let selectCount = 0;
let ngCount = 0;
const id = setInterval( async () => {
    try {
        let tmp = 0;
        document.querySelectorAll('button[class*=selector]').forEach(element => {
            if(!element.className.includes("selected")) {
                element.click();
                tmp++;
                ngCount = 0;
            }
        });
        selectCount += tmp;

        if(tmp === 0) {
            if(ngCount > 10) {
                end(id);
            } else {
                ngCount++;
            }
        } else if(selectCount >= 3000) {
            end(id);
        }

        await new Promise(resolve => setTimeout(() => resolve(), time));

        scrollBy(0, 400);
    } catch (error) {
        end(id, false);
    }

})

const end = (id, click = true) => {
    if(click) {
        document.querySelector("button[class=trash]").click();
    }
    clearInterval(id);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

localstorageに直接true/falseを格納しようとした時にハマったこと

ハマったこと

localstorage上にtrue/falseを格納して、取り出してifしようとしたところ、falseを取り出してるのにifをtrueで抜けてきた

localstorage.setItem('setVariable',true)

参考記事

JavaScriptで"false"が文字列扱いされる場合の覚書

解決方法

原因としては、true/falseを格納してもStringで格納されてしまうので、1/0で格納する。

localstorage.setItem('setVariable',1)

取り出して来た時にparseIntで数値化して、Booleanで真偽値型に変更する

Boolean(parseInt(localstorage.getItem('setVariable'),10))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】雨が降りそうな日に通知してくれる LINE BOT を作る

:star: 作るもの

雨が降りそうな日の朝にLINEでメッセージを届けるようにします。

unnamed.jpg

(1個だけごみ捨てのメッセージが混ざってますが、これは別です。)

:scissors: 使うもの

・LINE Developersアカウント
・Google App Script
・Liverdoor Weather API

:exclamation: おおまかな流れ

・LINE Developersに登録
・プロパイダを作成
・チャネル(BOTのアカウント)を作成
・BOTと友達になる
・GASでAPIを叩いて雨が降りそうか判定するスクリプトを作る
・GASからLINE BOTを介して自分にメッセージを送らせる
・毎朝このスクリプトを実行するように設定する

:boom: LINEのDevelopers設定編

LINE Developersに登録

https://developers.line.biz/ja/
LINE Developers Top.png

LINEアカウントを持っていれば簡単に登録出来ると思います。

※UIなどは記事執筆時点のものなので多少変わるかもしれません

プロパイダを作成

LINE Developers.png
ログインが済むと上のような画面になると思うので、新規プロバイダを作成します。
普通に開発者自身の情報を入れて行けば良いです。

チャネルの作成

LINE Developers2.png

次にチャネルの作成です。「新規チャネル作成」を押します。
チャネルと言うと何かややこしいですが、BOTの登録だと思ったら良いと思います。

LINE Developers3.png

今回の用途では「Messaging API」を選択します。
次の画面で、アプリ名やアイコンなど好きに設定したらええんちゃうかな。

作ったBOTと友達になる

作成したチャネルの設定画面の下の方に行くと、QRコードがあります。
ここから友達登録をすると良いと思います。
LINE Developers 7.png

メモっておくもの

  • アクセストークン
    チャネルの設定画面の中盤くらいにあります
    「-」になっていたら「再発行」を押す
    LINE Developers4.png

  • 自分のID
    チャネルの設定画面の一番下にあります
    ※この自分のIDとは友達検索とかで使うIDとは違います
    LINE Developers6.png

:boom: Google App Script編

Apps Script – Google Apps Script.png

Googleアカウントは持っている前提で行きます。

https://www.google.com/script/start/

ここの「Start Scripting」から App Script を始めることが出来ます。
ダッシュボードに入ったら左上くらいにある「新規スクリプト」を押してスクリプト編集画面を開きます。

具体的なコードの例

とりあえず先にコードを貼ります。

function main() {

  // お天気APIを読んで結果をもらう
  const result = callWeatherAPI();

  // 正常にAPIから応答があるか
  if (result.getResponseCode() !== 200) {

    // 異常があればメッセージを失敗を知らせるものにする
    var message  = "何か失敗しました。レスポンスコード:" + result.getResponseCode();

  } else {

    // 正常ならデータをJSONにパース
    var data =  JSON.parse(result.getContentText()).forecasts[0].telop;

    // 結果に雨という文字が含まれないなら何もせず中断する
    if(data.split("").indexOf("") == -1) {
      return;
    }

    // メッセージを設定
    var message = "今日は雨が降るかもしれません。\n\n予報:" + data;

  }

  // LINEへ送信
  var line_access_token = "YOUR_ACCESS_TOKEN";
  var line_to = "YOUR_LINE_TO";
  sendToLine(line_access_token, line_to, message);

}

// *******************************************
// Livedoorのお天気APIを叩いてレスポンスをもらう関数
// *******************************************
function callWeatherAPI() {
  const response = UrlFetchApp.fetch(
    "http://weather.livedoor.com/forecast/webservice/json/v1?city=400010", // TODO: set your city number
    {
      "method" : "get",
      "headers" : {
        "Content-Type": "application/json"
      },
      "muteHttpExceptions": true
    }
  );
  return response;
}

// *******************************************
// LINEボットへ送信する関数
// *******************************************
function sendToLine(access_token, to, message) {
  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    'Authorization': 'Bearer ' + access_token,
  };

  var postData = {
    "to" : to,
    "messages" : [
      {
        'type':'text',
        'text': message,
      }
    ]
  };

  var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };

  return UrlFetchApp.fetch(url, options);
}

このままでは動かないので、編集しないといけない部分があります。

LINEの設定部分

中盤のこの部分

  var line_access_token = "YOUR_ACCESS_TOKEN";
  var line_to = "YOUR_LINE_TO";

ここをそれぞれLINE Developers編でメモったアクセストークンと自分のIDに差し替える必要あり。

どの都市の天気を取得するかの設定

const response = UrlFetchApp.fetch(
    "http://weather.livedoor.com/forecast/webservice/json/v1?city=400010", 

デフォルトだと九州の久留米になっています。

都市のIDやパラメータの設定などはこちら参照。
今のところAPI KEYなどは不要なようです。

http://weather.livedoor.com/weather_hacks/webservice

関数を実行する

編集画面上部の再生ボタンみたいなマークを押すと関数を実行出来ます。

この時、実行する関数は「main()」関数にします。
「関数を選択」からmain関数が指定されているのを確認してください。

wheather_forecast.png

各項目が正常に指定できていれば、さっき作ったボットからメッセージが送られてくる...と言いたいところですが、雨の予報でなければ送られてきません。

    // 結果に雨という文字が含まれないなら何もせず中断する
    /*
  if(data.split("").indexOf("雨") == -1) {
      return;
    }
    */

この部分をこのようにコメントアウトすれば、とりあえずどんな予報でも「今日は雨が降るかもしれません: 予報内容」というメッセージが届くと思います。

挙動を確認したらコメントアウトを元に戻しておきましょう。

定期実行を行う

今度はこれを自動的に毎朝実行出来るようにしたいです。
GASはかなり簡単に定期実行が出来るようになってます。

まずスクリプト編集画面上部にある時計みたいなアイコンをクリックします。
wheather_forecast2.png

するとトリガー一覧画面に移るので「トリガーを追加」をクリック。
wheather_forecast - プロジェクトのトリガー - Apps Script.png

実行する関数を「main」にして、「日付ベースのタイマー」を選択。
時間は好みで良いです、朝の6時~7時とかが良いんじゃないでしょうか。

weather_forecast - プロジェクトのトリガー - Apps Script2.png

:umbrella: 結果

毎朝、天気予報が雨の場合はメッセージが届くようになります。

冒頭と同じ画像ですが...以下のように。


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

React-ReduxだけどReducerもActionも書かず、Dispatchすら使わず、データも何となく受け取れるようにする方法

React-ReduxだけどReducerもActionも書かず、Dispatchすら使わず、データも何となく受け取れるようにする方法

前回
Reduxがあまりにも面倒なので、何もかも隠蔽しつつ、その力を引き出すことにした

とにかく面倒くさいFlux

 React上での状態管理は、ことごとくがFluxの考え方を用いて作られています。

 Actionのコマンドを定義してDispatchで送信し、Reducerで処理してStoreに書き込み、ViewがStoreの更新を察知して処理を行うという流れです。これらの処理の厄介なところは、全部が全部、記述箇所がバラバラになってしまうということです。

 とにかく何もかも面倒くさいので、この地獄から抜け出す方法を探しました。結果として、上記に書いたようなことを一切書かず、簡単にStoreデータを操作する方法にたどり着きました。

 環境設定と必要なパッケージ

今回のサンプルプログラムを動かすにはReact+TypeScript環境を用意してください。
また、以下のパッケージをインストールする必要があります。

npm -D i @jswf/redux-module

サンプルプログラム

 HooksのFunctionコンポーネントと、Classコンポーネントでの利用法を同時に載せているので、プログラムが少々長くなっています。
 中でやっているのはinputの内容をコンポーネント間で共有するというものです。
 もしこれをやるためだけにReduxの書式を真面目に書いたら、確実に無駄地獄へ落ちることが出来るでしょう。

 コンポーネント間のデータ共有にはReduxModuleというクラスを継承して利用します。
 クラスを作ると、クラスごとに一つStore領域が割り当てられます。
 そのため同じクラスを使用する限り、同じデータを参照することが出来ます。
 また、読み書きの受付も全てこのReduxModule継承クラスが担当するので、処理をあちこちに書く必要はありません

Microsoft Edge 2019-09-06 23-01-19.gif

https://github.com/JavaScript-WindowFramework/redux-module-sample

index.tsx
import React, { Component } from "react";
import * as ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import {
  ModuleReducer,
  useModule,
  ReduxModule,
  mapModule,
  mapConnect
} from "@jswf/redux-module";

/**
 *データ構造の定義(TypeScript使用時)
 *
 * @export
 * @interface TestState
 */
export interface TestState {
  msg: string;
}
/**
 *Storeアクセス用クラス
 *(クラスごとに自動的にStoreに領域を確保する)
 * @export
 * @class TestModule
 * @extends {ReduxModule<TestState>}
 */
export class TestModule extends ReduxModule<TestState> {
  //ここに初期値を設定可能
  protected static defaultState: TestState = {
    msg: "初期値"
  };
  //以下のようなアクセス用のメソッドは、必ずしも作る必要は無い
  //getStateとsetStateはpublicなので、外から直接書き換えてしまってもOK
  public getMessage() {
    return this.getState("msg")!;
  }
  public setMessage(msg: string) {
    this.setState({ msg });
  }
}

/**
 *Hooks用サンプル
 *
 * @returns
 */
function HooksApp() {
  //モジュールのインスタンスを受け取る
  //useModuleの使用可能場所の制限は他のhookと同じ
  const testModule = useModule(TestModule);
  //以下のようにPrefixを付けると、同じクラスが違う領域を持つことも出来る
  //const testModule = useModule(TestModule,"Prefix");
  return (
    <>
      <div>FunctionComponent</div>
      <input
        value={testModule.getMessage()}
        onChange={e => testModule.setMessage(e.target.value)}
      />
      <hr />
    </>
  );
}

/**
 *Class用サンプル
 *
 * @class _ClassApp
 * @extends {Component}
 */
class _ClassApp extends Component {
  render() {
    //モジュールのインスタンスを受け取る
    //Hooksと名前と引数が微妙に違うので注意
    const testModule = mapModule(this.props, TestModule);
    return (
      <>
        <div>ClassComponent</div>
        <input
          value={testModule.getMessage()}
          onChange={e => testModule.setMessage(e.target.value)}
        />
        <hr />
      </>
    );
  }
}
//クラスコンポーネントを利用する場合は以下の方法でマッピングする
//ここで宣言したモジュール以外はクラスで使用できない
//モジュールは配列で複数指定も可能
const ClassApp = mapConnect(_ClassApp, TestModule);

//Reduxに専用のReducerを関連付ける
//他のReducerと併用することも可能
const store = createStore(ModuleReducer);
ReactDOM.render(
  <Provider store={store}>
    <HooksApp />
    <ClassApp />
  </Provider>,
  document.getElementById("root") as HTMLElement
);

 必要なことのまとめ

1 ModuleReducerをReduxのStoreに関連付ける
2 ReduxModuleを継承したデータクラスを作成する
3 Classコンポーネントを使う場合はmapConnectで使用するデータクラスを関連付ける
4 useModule/mapModuleでデータクラスを呼び出す
5 setState/getStateでデータの読み書きを行う

一応解説しておく付加機能

 蛇足になるので最小限にとどめますが、データクラスは外部参照機能も付いています。
 別のデータクラスの機能が必要な場合はincludesに利用するモジュールを指定しておけば、getModuleで対象のクラスを呼び出すことが出来ます。

export class OtherModule extends ReduxModule {
  static includes = [TestModule]
  public getMessage() {
    return this.getModule(TestModule).getState("msg")!;
  }
  public setMessage(msg: string) {
    this.getModule(TestModule).setState({ msg });
  }
}

まとめ

 とにかく状態管理が楽になりました。これを使うことによって、コンポーネント間の手続きの大部分が省略可能となります。コンポーネント間のデータ共有にReduxを使いたいけれど、Flux的な書き方が嫌だと考えているのならぜひ使ってみてください。

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

jsPDFでiOSに対応するよ

jsPDFはフロントエンドでPDFを生成できるとっても便利なライブラリー。早速試してみましょう。

const doc = new jsPDF({
  orientation: 'p',
  unit: 'px',
  format: 'a4',
  putOnlyUsedFonts: true
})
const fileName = 'hoge.pdf'
doc.setFontSize(10).text('Hoge', 10, 10)
doc.save(fileName)

とっても簡単ですね。でも、あれ? iOSだとPDF出力できませんね。どうやらiOSはFileSystemがないので、直接PDFダウンロードができないようです。
というわけで、iOSの時はダウンロードしないで、新しいタブにPDFを直接表示してみましょう。

const ua = window.navigator.userAgent.toLowerCase()
const isIos = ua.indexOf('iphone') !== -1 || ua.indexOf('ipod') !== -1 || ua.indexOf('ipad') !== -1

if (isIos) {
  window.open(doc.output('bloburl', { filename: fileName }))
} else {
  doc.save(fileName)
}

これで新しいタブにPDFが表示されました。
ユーザーはそこからPDFを保存したりメールで送ったりできるようになりましたとさ。

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

jsPDFでモバイルに対応するよ

jsPDFはフロントエンドでPDFを生成できるとっても便利なライブラリー。早速試してみましょう。

const doc = new jsPDF({
  orientation: 'p',
  unit: 'px',
  format: 'a4',
  putOnlyUsedFonts: true
})
const fileName = 'hoge.pdf'
doc.setFontSize(10).text('Hoge', 10, 10)
doc.save(fileName)

とっても簡単ですね。でも、あれ? iOS SafariだとPDF出力できませんね。どうやらiOSはFileSystemがないので、直接PDFダウンロードができないようです。
というわけで、モバイルの時はダウンロードしないで、新しいタブにPDFを直接表示してみましょう。

if (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase())) {
  window.open(doc.output('bloburl', { filename: fileName }))
} else {
  doc.save(fileName)
}

これでモバイルでは新しいタブにPDFが表示されました。
ユーザーはブラウザのシェア機能を使って、PDFを保存したりメールで送ったりできるようになりましたとさ。

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