20200122のJavaScriptに関する記事は23件です。

Slackに匿名で画像を投稿できるようにした

はじめに

Slackで匿名チャンネルを作りました。
直接聞きにくい相談やちょっとした雑談で使えるので便利です。

「画像も投稿できたらコミュニケーションの幅が広がるのでは?」と思ったのがきっかけで、
画像も匿名で投稿できるようにしました。

処理の流れとしては、
1. BotにDMで画像を送信する
2. Botが画像を受け取ったら、ローカルに保存する
3. 指定したチャンネルに保存した画像をBotが代わりに投稿する
4. ローカルに保存した画像を削除する
5. 終わり
となってます。

JavaScript初心者なので至らない点もあると思いますが、ご了承ください。

準備

この記事を参考に、

  • ワークスペース内で使うSlack Botの作成
  • API Tokenの取得
  • Botkitのインストール

を行ってください。

説明のために、今回作成したBotの名前は"anonymous_bot"とします。

BotとChannelのIDを取得

先ほど取得したAPI Tokenを使って、Botと匿名で投稿したいチャンネルのIDを取得してください。

  • BotのIDを取得
    https://slack.com/api/users.list?token=さっき取得したAPI_Token
  • ChannelのIDを取得
    https://slack.com/api/channels.list?token=さっき取得したAPI_Token

取得できるIDは、大文字のアルファベットと数字の組み合わせになっているはずです。
例:ABC0ED123

プログラムの作成

以下のプログラムをコピペしてください。

slack_bot.js
const Botkit = require('/path/to/Botkit.js');
const os = require('os');
const fs = require('fs');
const download = require('download');
const https = require('https');
const del = require('delete');

const slackBot_id = 'BotのID';
const channel_id = '投稿したいChannelのID';
const token = '取得したAPI Token';

var controller = Botkit.slackbot({
    debug: false,
});

var bot = controller.spawn({
    token: token
}).startRTM();

controller.on('file_shared', function(bot, message){

    if(message.user_id != slackBot_id){ //Bot自身の投稿には反応しない
        const messageObj = {
            token: token,
            file: message.file_id
        };

        bot.api.files.info(messageObj, function(err, res){
            if(err){
                console.log(err)
            }

            else{
                console.log('[file_shared] on');
                var now = new Date();
                var file_name = now.getFullYear()+':'+(now.getMonth()+1)+':'+now.getDate()+':'+now.getHours()+':'+now.getMinutes()+':'+now.getSeconds()+'.jpg';
                var file_dir = '/path/to/image_dir/';
                var file_path = file_dir + file_name; //ローカルに保存する際のディレクトリとファイル名
                var file_url = res.file.url_private_download;//送信された画像のURL

                var options = {
                    'method': 'GET',
                    'hostname': 'files.slack.com',
                    'path': file_url,
                    'rejectUnauthorized': 'false',
                    'headers': {
                    'Authorization': 'Bearer ' + token
                    }
                };

                var file = fs.createWriteStream(file_path);
                var responseSent = false;

                //URL先の画像をローカルに保存
                https.get(options, response => {
                    response.pipe(file);
                    file.on('finish', () => {
                        file.close(() => {
                            if(responseSent) return;
                            responseSent = false;
                        });//file.close
                    });//file.on
                });//https.get
                console.log('file download');

                //時間差で画像の送信→画像の削除を行う
                setTimeout(() => {
                    const messageObj = {
                            file: fs.createReadStream(file_path),
                            filename: file_name,
                            title: file_name,
                            channels: channel_id
                    };
                    bot.api.files.upload(messageObj, function(err, res){
                        if(err){
                            console.log(err);
                        }

                        else{
                            console.log('file upload');
                        }
                    });//bot.api.files

                    setTimeout(() => {
                        del([file_path], function(err, res){
                            if(err){
                                console.log(err);
                            }

                            else{
                                console.log('file delete');
                            }
                        });//del
                    }, 1000);//setTimeout
                },1000);//setTimeout

            }//else
        });//bot.api.files
    }//if

    console.log("finish");
});//controller.on

実行方法

# Botの起動
$ forever start slack_bot.js 

# Botの停止
$ forever stop slack_bot.js

BotにDMで画像を送ると、
Screenshot from 2020-01-22 23-18-54.png

Botが指定したチャンネルに代わりに投稿してくれます。
Screenshot from 2020-01-22 23-17-00.png


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

もしものために,香川県からのアクセスを避ける

茶番

「どうも〜,セミコロンつけない派です.お願いします」
「お願いします」
「あ〜,ありがとうございます.今,いいねを頂きました.こんなんなんぼあってもいいですからね」

「あのな,おかんがな,思い出せない都道府県があるらしいねん」
「ほな,その特徴教えてくれる?」
「おかんが言うにはな,最近ゲームが

「香川やないか!」

(ミルクボーイやりたかっただけ.)

ちまたで噂のネット・ゲーム依存症対策条例

ネットやゲーム依存症を防ぐため,18歳未満の使用時間を制限するというやつです.
今はちょっと変わってコンピュータゲームだけになったみたいです.
これに関する議論は尽きないですが,ここでは置いておきます.

仮にサービス側が何とかしろと言われたら...

ネット界隈では,「香川県民ですか? はい/いいえ」のような香川認証がネタになってます.

しかし,本当にサービス側が対策しないと行けなくなったらやばい.(絶対ないと思うけど)

そこで,JavaScriptのGeolocation APIを使って香川からのアクセスを避けてみます.

ソースコードはこちらにあります.

香川ブロックプログラム

概略

回避法としては下の図のようになります.

Geolocation APIで緯度経度を取得し,逆ジオコーティングAPIで都道府県を割り出し,香川県だった場合はブロックします.
今回は簡易的に香川県だった場合は,香川県のホームページに飛ばします.
メインの部分は以下の感じです.

index.js
document.addEventListener('DOMContentLoaded', () => {
    const message_id = document.getElementById('message')

    // 位置情報取得対応かチェック
    if(navigator.geolocation){
        // 現在位置をPromiseとして取得  - (1)
        getCurrentPositionAsPromise().then(postion => {
            // 逆ジオコーディングにかける - (2)
            getPrefecture(postion.coords.latitude, postion.coords.longitude).then(json => {
                const prefecuture = json.result.prefecture.pname
                // 香川県なら県のホームページへ
                if(prefecuture == '香川県'){
                    location.href = 'https://www.pref.kagawa.lg.jp/'
                }else {
                    message_id.innerHTML = 'ようこそ'
                }
            }, reject => {
                console.log(reject)
                message_id.innerHTML = '都道府県を特定できませんでした'
            })
        }, reject => {
            console.log(reject)
            message_id.innerHTML = '現在位置を取得できませんでした'
        })
    }else {
        message_id.innerHTML = '位置情報取得を許可してください'
    }
})

(1) 位置情報取得

Geolocation APIは,getCurrenctPosition(successFunc, errorFunc, options)で現在位置を取得することができます.
ただし,位置情報取得は許可されてる時のみです.
また,この許可をポップアップによって求めることがあるのですが,この応用待ちが必要なので,Promiseで包みます.

index.js
function getCurrentPositionAsPromise(){
    return new Promise((resolve, reject) =>{
        navigator.geolocation.getCurrentPosition(resolve, reject)
    })
}

(2) 都道府県情報照合

Geolocation APIで取得した位置情報を逆ジオコーディングAPIにかけます.
位置情報はgetCurrentPosition()の返り値をpositionとすると,
緯度: position.coords.latitude
経度: position.coords.longitude
で受け取れます.

逆ジオコーディングにはfinds.jsの簡易逆ジオコーディングサービスを使いました.
APIキー取得が不要で使いやすいです.

APIにはfetchを使って投げます.
ここも取得待ちが必要なので,fetchをそのままPromiseとして返します.

index.js
const API_URL = 'http://www.finds.jp/ws/rgeocode.php'

function getPrefecture(latitude, longitude){
    return fetch(`${API}?json&lat=${latitude}&lon=${longitude}`,{
        mode: 'cors'
    }).then(response => {
        if(response.ok){
            return response.json()
        }else {
            return Promise.reject(new Error(`${response.status}: ${response.statusText}`))
        }
    })
}

注意

このプログラムを動かす時,Web APIを使ってるためCORS要求がうまくいかず,失敗する場合があります.
Chromeだと,拡張機能としてMoesif Origin&CORS Changerを入れるとなんとかできる場合があります.

あと位置情報取得にもAPIの応答待ちも少し時間がかかるため,待つ必要があります.

位置偽造実験

香川県にいないので,実際に香川からアクセスした場合の挙動がわかりません.
なので,Chromeの機能を使って位置情報を香川県に変更します.
香川県の高松市の緯度経度を使用します.

香川デモ

結果,無事香川県のホームページに飛ばされました.
位置情報変えられるのであれば,香川県からのアクセスを別のところからにもできますが(笑).

まとめ

今回はGeolocation APIを使って香川県からアクセスされた場合,ブロックしてみました.
というか,香川県のゲーム規制をネタにGeolocation APIで遊んでみました.

実際,ゲーム規制どうなるんですかね.
記事執筆段階では,親に努力義務が課せられるという状態ですが,果たして.

参考文献

Geolocation APIをPromiseでラップして扱う(TypeScript)
簡易逆ジオコーディングサービス
Moesif Origin&CORS Changer
ローカルのHTMLファイルでfetchを試す & CORSとその回避策
【Google Chrome】位置情報を変更してPCの現在地を自由自在に設定
香川県の市町村ごとの緯度経度一覧

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

URLSearchParamsで配列の検索パラメータを取り扱う

URLSearchParamsとは?

URLSearchParamsは、検索条件を指定する際に使用されるURL?以降の文字列を取り扱う際に、様々な便利機能を提供してくれるAPIです。

const params = new URLSearchParams('?type=test&status=done');
console.log(Object.fromEntries(params));
// => {type: "test", status: "done"}

検索パラメータとして配列を指定したい場合はどうするのか

?type=test&statuses=process&statuses=donestatusesのように同じkeyに複数の値を含めて、OR検索を行いたい場合があると思います。
この場合は以下のようにパラメータを取得すると良いでしょう。

// ステータスは配列で取得したい
const isArrayField = (key: string) => key === 'statuses';
const params = new URLSearchParams('?type=test&statuses=process&statuses=done');
const keys = Array.from(new Set(searchParams.keys()));

let q: { type?: string, statuses?: string[] } = {}
for (const key of keys) {
  const values = params.getAll(key);
  if (values.length <= 0) {
    continue;
  }

  q = {
    ...q,
    [key]: isArrayKey(key) ? values : values[0]
  };
}

console.log(q);
// => {type: "test", statuses: ["process", "done"]}

searchParams.keys()は上記の例の場合["type", "statuses", "statuses"]となっているので、Setを使って重複を弾いたあと、Array.fromで再び配列に戻しています。
(コンパイルオプションの--downlevelIterationフラグをtrueに設定すれば、配列に戻す必要はありません)

getAll関数はstatusesに設定されている値の全てが配列で返してくれるので、配列で取得したいフィールドの場合は配列をそのまま格納すればOKです。

ちなみに、get関数もありますが、この関数は最初の1個目の値をstring型で返します。

console.log(params.get('statuses'));
// => "process"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryのメソッド

jQueryとこの記事について

  • 『jQuery』とは、効果やアニメーションを簡単に実装できるJavaScriptライブラリ。

  •  jQueryを使うとWEBページにアニメーションや効果を簡単につけることができる。

  •  jQueryを勉強中(ど素人)なのですが、復習も兼ねて学んだメソッドを
     下記にメモとして記述していこうと思います。随時更新していきます。

hide

  • セレクタ(HTMLのタグ名やclass名,idなどを指定する)で指定した要素を隠すことができる。

 例: $('h1').hide();

show

  • セレクタで指定した隠れている要素を表示することができる。

 例: $('h1').show(slow);

fadeOut

  • セレクタで指定した要素を徐々に隠すことができる。
  • アニメーションの速度は()内に引数として指定することができる。(1.0秒 → 1000、slowなど)

 例: $('.class').fadeOut(1000);

fadeIn

  • セレクタで指定した要素を徐々に表示ことができる。

 例: $('.class').fadeIn(slow);

slideUp

  • セレクタで指定した要素を下から上にスライドしながら隠すことができる。

 例: $('#id').slideUp(1000);

slideDown

  • セレクタで指定した要素を上から下にスライドしながら表示することができる。

 例: $('#id').slideUp(slow);

css

  • セレクタで指定したCSSを変更することができる。引数①にプロパティ、引数②にプロパティの値を設定する。

 例: $('p').css('color','blue');

 ※ちなみに、
    $('img').css('display','none'); ⇦ hideメソッドと同じ
    $('img').css('display','block'); ⇦ showメソッドと同じ

text

  • セレクタで指定した要素のHTMLの中身を引数で指定した内容に書き換えることができる。

 例: $('p').text('アイウエオ');

html

  • セレクタで指定した要素の中身のHTMLを引数で指定したものに書き換えることができる。

 例: $('p').html('アイウエオ');

find

  • セレクタで指定した要素の子孫要素から、引数で指定した要素を全て取得することができる。

 例: $('.class').find('p');

  この場合、'.class'というクラス内の'p'要素を取得することができる。

children

  • セレクタで指定した要素の子要素(1階層分)から、引数で指定した要素を取得することができる。

 例: $('.class').children('p');

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

Express 4におけるmiddlewareの扱い

きっかけ

Express.jsをドットインストールで勉強してたが、動画のExpressは3系だった。手元のExpressのバージョンは4なのでいくつかの相違点があり、特にmiddlewareの使い方が大きく変わっている事がわかった。

結論

ほとんどのmiddleware(loggerなど)は個別にインストールする必要がある。

//ロガーの例
// Express 3
app.use(express.logger('dev'))

// Express 4
// まず`npm install morgan`が必要
logger = require('morgan')
app.use(logger('dev'))

参考

Express入門
https://dotinstall.com/lessons/basic_expressjs

Connect
https://github.com/senchalabs/connect#middleware

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

HTML + JavaScript 非同期でIPアドレスを取得して表示する

ちょっとクライアントでIPアドレスを取得する必要があったので、
やり方を残しておきます。

今回はipinfo.ioのJSONP API使用してIPアドレスを取得しました。
こちらはchromeのデベロッパーツールでも簡単に試せます。

ちなみにipinfo.ioは自分のIPアドレスを教えてくれるWebサービスです。

JavaScript

$.ajax({
    url: "https://ipinfo.io",
    dataType: "jsonp",
    success: function(res){
         $(".ip-address").text(res.ip);
    }
});

chromeのデベロッパーツールのConsoleに、↑をそのまま貼り付けて実行してもらうと
どういった値が取得できるのか確認できます。

HTML

<p>
    あなたのIPは<a class="ip-address"></a>です。
</p>

画面に表示させるためにデベロッパーツールのElementsにこちらを追加し、
もう一度Consoleでさっきのスクリプトを実行するとIPアドレスが表示できると思います。

まとめ

通常、IPアドレスはサーバで取得することが多いかと思いますが、
今回のようにクライアントでもIPアドレスを取得したい時に役立ちます。

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

React.Componentのメンバ変数は、再レンダー時に更新されるとは限らない!

ReactでsetState()しても値が更新されなくてハマったので、記事化します。

更新すべき値をメンバ変数に格納した場合

ボタンを押すと値が+1されるアプリケーションを作ります。

以下は間違ったコードです。
Appでカウンターの値を管理し、Counterはその値とカウントボタンを表示します。

app.js
import React from 'react'
import ReactDOM from 'react-dom'
import Counter from './counter'

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {count: 0}
    this.increment = this.increment.bind(this)
  }

  increment() {
    this.setState(
      (state) => {return {count: state.count + 1}}
    )
  }

  render() {
    return (
      <Counter count={this.state.count} onClick={this.increment} />
    )
  }
}

let app = document.getElementById('app')
ReactDOM.render(<App />, app)

counter.js
import React from 'react'

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.count = this.props.count
  }

  render() {
    // 以下の`this.count`は、新しいpropsを反映しない!
    return (
      <div>
        <p>{this.count}</p>
        <button onClick={this.props.onClick}>カウント</button>
      </div>
    )
  }
}

export default Counter

このコードを実行し、ボタンをクリックしても、カウンターの値は増加しないと思います。

レンダーごとに更新する値は、メンバ変数に保存しない

値が更新されない原因は、表示する値がthis.countになっていることです。
counter.jsを以下のように修正すれば、ちゃんとカウント値が増加します。

counter.js
import React from 'react'

class Counter extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <div>
        <p>{this.props.count}</p>
        <button onClick={this.props.onClick}>カウント</button>
      </div>
    )
  }
}

export default Counter

今回は、this.props.countを表示するだけなので、わざわざメンバ変数に格納することはありませんが、表示させるべき値が複雑になる場合、renderメソッドの戻り値に直接式を書かずに、結果を変数に格納したい場合があります。そういう場合は、たとえば関数にすると良いと思います。

counter.js
class Counter extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    let count = this._getCount()
    return (
      <div>
        <p>{count}</p>
        <button onClick={this.props.onClick}>カウント</button>
      </div>
    )
  }

  _getCount() {
    let count;
    // すごく複雑な処理
    return count
  }
}

以上です。

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

[JavaScript][Node.js]メモ:アロー関数の即時関数(関数を定義してその場で実行する)

アロー関数と即時関数を組み合わせる書き方。

const a = (() => {
    // 何らかの処理
    return 'Hello!'
})()

余談だが、即時関数の正式名は、「IIFE (即時実行関数式)」っぽい。
「即時関数」でググっても公式のドキュメントにはなかなかたどり着けない。

参考リンク

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

MDN ja の間違い探し

概要

メモです。
適宜追記する予定。

コメント欄に記載頂くと記事に反映します。

内容

String.prototype.includes

2020/01/22(Wed)

日本語版
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/includes

  if (!String.prototype.includes) {
    String.prototype.includes = function(search, start) {
      'use strict';
      if (typeof start !== 'number') {
        start = 0;
      }

      if (start + search.length > this.length) {
        return false;
      } else {
        return this.indexOf(search, start) !== -1;
      }
    };
  }

英語版
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes

  if (!String.prototype.includes) {
    String.prototype.includes = function(search, start) {
      'use strict';

      if (search instanceof RegExp) {
        throw TypeError('first argument must not be a RegExp');
      }
      if (start === undefined) { start = 0; }
      return this.indexOf(search, start) !== -1;
    };
  }

Polyfill のコードが違っていて、WSH環境で実行すると、日本語版だと文字列.includes(null)文字列.includes(undefined)で、エラーになる。英語版のPolyfillだと、戻り値falseになって正常に動作する。

追記:2020/01/23(Thu) 00:33:すでに修正されていました。

Object.defineProperty()

2020/01/23(Thu) 00:43

日本語版
Object.defineProperty() - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
image.png

英語版
Object.defineProperty() - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

image.png

英語版には「Compatibility notes」に「Chrome 37」の項目があるが
日本語版の「互換性のメモ」欄にはない。

Array.prototype.indexOf()

2020/01/23(Thu) 00:53

日本語版
Array.prototype.indexOf() - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf

英語版
Array.prototype.indexOf() - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf

image.png

英語版には「Specifications」が1つしかない(2つ削除されている)、日本語版には3つある。

Object.defineProperty()でも、「Specifications」欄が同じようになっている。

全体の方針なのだろうか。

終わりに

探せばいろいろみつかる気が。
ログインしたら自分で更新もできるみたいなので、ご自身で更新して内容提案してみてはいかがでしょうか。

スクリーンショット撮るのはめんどいね。

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

我々はITPをどのように回避すべきか

【書類名】  明細書
【発明の名称】判定装置及びコンピュータプログラム
【技術分野】
 【0001】
 本発明は、端末の識別情報としてのCookieに代わる判定システム、判定方法、判定装置及びコンピュータプログラムに関する。
【背景技術】
 【0002】
 端末は、ブラウザの動作によってサーバに接続する。ブラウザの動作によってサーバに接続する場合に、端末の識別情報としてCookieが使用されることが一般的である。サーバは、端末が送信するCookieを取得することで、ブラウザの動作に応じて、ブラウザに情報を提供したり、そのブラウザの動作の履歴を追跡することができる。
 【0003】
 端末は、ブラウザの動作によってJavaScriptを使用することにより、端末の識別情報としてCookieを使用することができる。JavaScriptは、端末に記録されるCookieを取得することで、ブラウザの動作に応じて、ブラウザに情報を提供したり、そのブラウザの動作の履歴を追跡することができる。

(省略)

 【0007】
 上記事情に鑑み、本発明は、端末の識別情報としてのCookieに代わる判定システム、判定方法、判定装置及びコンピュータプログラムに関する技術の提供を目的としている。
【課題を解決するための手段】
 【0008】
 本発明の一態様は、端末から通信を介してサーバから取得される第1の画像と、端末に保存された第1の画像のキャッシュとして取得される第2の画像とが、同一の画像情報であることに基づいて、第1及び第2の画像に含まれる画像情報から、端末の識別情報を判定する判定部を備える判定装置である。
 【0009】
 本発明の一態様は、上記判定装置であって、前記画像情報は、画像に含まれる色情報である。
 【0010】
 本発明の一態様は、上記の判定装置であって、前記画像情報は、画像の縦又は横の少なくとも一方のサイズ情報である。
 【0011】
 本発明の一態様は、上記の判定装置としてコンピュータを機能させるためのコンピュータプログラムである。
【発明の効果】
 【0012】
 本発明により、Cookieを使用することなく、端末を識別するための情報をブラウザに記憶させ、ブラウザの動作によってJavaScriptを使用することにより、端末の識別情報を取得することが可能となる。
【図面の簡単な説明】
 【0013】
  【図1】判定システムのシステム構成を表す構成図である。
  【図2】画像情報として、画像に含まれる色情報を使用した場合の図である。
  【図3】画像情報として、画像の縦と横のサイズ情報を使用した場合の図である。
【発明を実施するための形態】
 【0014】
 図1は、判定システムのシステム構成を表すシステム構成図である。判定システムは、サーバ100及び複数の端末200を備える。サーバ100及び各端末200は、ネットワーク300を介して互いに通信可能に接続される。ネットワーク300は、例えば、インターネットで構成されていてもよい。

(省略)

 【0023】
 制御部104は、端末200で動作するブラウザが、通信部201とネットワーク300を介して、通信部101が受信した通信に応じて、画像生成部105に対し、画像の生成の処理に関する制御を実行する。画像は、インターネットで一般に利用されるPNG(Portable Network Graphics)フォーマットや、JPEG(Joint Photographic Experts Group)フォーマットの画像であってもよい。
 【0024】
 制御部204は、サーバ100で動作するサーバが、通信部101とネットワーク300を介して、通信部201が受信した通信に応じて、画像読取部205に対し、画像の読み取りに処理に関する制御を実行する。また、制御部204は、画像をキャッシュ記憶部206に格納する処理に関する制御を実行する。
 【0025】
 キャッシュ記憶部206は、端末200で動作するブラウザが、通信部201とネットワーク300を介して、サーバ100で動作するサーバから取得したコンテンツを一時的に格納する装置である。キャッシュ記憶部206に格納されたキャッシュは、サーバ100で動作するサーバが、HTTP(Hypertext Transfer Protocol)のLast-Modified、Cache-Control又はPragmaヘッダで指定した時刻を経過した後にパージ(削除)される。
 【0026】
 画像生成部105は、制御部104からの要求に応じ、端末の識別情報を埋め込んだ画像を生成する。端末の識別情報は、ランダムな文字列であったり、UUID(Universally Unique Identifier)フォーマットであってもよい。複数の端末200を識別することのできる十分な組み合わせ数があればよい。
 【0027】
 画像読取部205は、制御部204からの要求に応じ、端末の識別情報が埋め込まれた画像を読解する。
【0028】
 図2に示される例では、サーバ100は、画像生成部105において、画像に含まれる色情報として、白黒の8ピクセルの正方の画像500を生成したものであり、この画像は白又は黒のドットが描かれている。端末200は、通信部201とネットワーク300を介して、サーバ100から前記画像を取得し、これをキャッシュ記憶部206に格納する。端末200は、前記画像をキャッシュ記憶部206から読み込み、画像読取部205によって、画像に含まれる色情報として、白黒の8ピクセルの正方の画像を、上から下に各行を8ビットの2進数のデータとして、画像の左を上位ビット、画像の右を下位ビット、白のドットを0として、黒のドットを1として読み取る。この画像は、1行目は0を、2行目は1を、3行目は2を、4行目は3を、5行目は4を、6行目は5を、7行目は6を、8行目は7を表す。この画像は01234567という端末の識別情報を表す。
【0029】
 図2の例として、白黒の8ピクセルの正方の画像の例を示したが、サイズは任意である。カラー画像を使用すると、より多くの情報を格納することができる。また、画像に含まれる色情報として、8ビットの2進数のデータの例を示したが、バーコードを利用したり、QRコードを埋め込んでもよい。
【0030】
 図3に示される例では、サーバ100は、画像生成部105において、画像の縦と横のサイズ情報として、横8縦7ピクセルの画像、横6縦5ピクセルの画像、横4縦3ピクセルの画像、横2縦1ピクセルの複数の画像を生成したものである。端末200は、通信部201とネットワーク300を介して、サーバ100から前記複数の画像を取得し、これをキャッシュ記憶部206に格納する。端末200は、前記複数の画像をキャッシュ記憶部206から読み込み、画像読取部205によって、画像の縦と横のサイズ情報として、各画像の縦と横のサイズから1を引いて計算し、各画像の横のサイズ、縦のサイズを順に読み取る。画像600は7と6を、画像601は5と4を、画像602は3と2を、画像603は1と0を表す。前記複数の画像は76543210という端末の識別情報を表す。
【0031】
 図3の例として、一片の長さが8ピクセル以下の画像を用いたが、画像のサイズは任意である。一片の長さが大きな画像を使用すると、端末の識別情報の桁数を大きくすることができる。画像の枚数を4枚としたが、画像の枚数は任意である。枚数を多くすると、端末の識別情報の桁数を大きくすることができる。
【0032】
 図2及び図3では、画像を用いて詳述してきたが、動画であってもよい。動画情報がキャッシュされ、動画情報として、ブラウザに表示される縦又は横サイズ、再生時間が取得できればよい。
【0033】
 図2及び図3では、画像を用いて詳述してきたが、JavaScriptファイルであってもよい。JavaScriptファイルがキャッシュされ、JavaScriptファイルに、端末の識別情報として固定の文字列が埋め込まれていればよい。
【0034】
 図2及び図3では、画像を用いて詳述してきたが、JavaScriptファイルであってもよい。JavaScriptファイルがキャッシュされ、JavaScriptによって、HTMLの特定のタグに端末の識別情報として固定の文字列を埋め込めむことができればよい。JavaScriptによって、特定のタグの幅又は高さを設定するものであってもよい。
【0035】
 以上、この発明の実施形態について図面を参照して詳述してきたが、具体的な構成はこの実施形態に限られるものではなく、この発明の要旨を逸脱しない範囲の設計等も含まれる。
【産業上の利用可能性】
【0036】
 プライバシーの保護やセキュリティの強化を目的として、ITP(Intelligent Tracking Prevention)と呼ばれるCookieを制御する機能がブラウザに導入されている。この機能により、ウェブサイトを横断して情報を取得していると判断されたドメインのCookieは付与から即時無効となり、サードパーティーCookie(3rd Party Cookie)は保存できなくなったため、ブラウザの動作の追跡が困難になった。
【0037】
 本発明により、Cookieを使用することなく、端末を識別するための情報をブラウザに記憶させ、ブラウザの動作によってJavaScriptを使用することにより、端末の識別情報を取得することが可能となるため、インターネット広告の費用対効果を明らかにするためのコンバージョン(Conversion)の計測が容易に行えるようになる。
【符号の説明】
【0038】
1…判定システム、 100…サーバ、 101…通信部、 102…入力部、 103…出力部、 104…制御部、 105…画像生成部、 200…端末、 201…通信部、 202…入力部、 203…出力部、 204…制御部、 205…画像読取部、 206…キャッシュ記憶部、 300…ネットワーク

スクリーンショット 2020-01-22 16.46.46.png

スクリーンショット 2020-01-22 16.46.55.png

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

Node.js(axios)からDiscordに通知を送るメモ

忘れがちなのでコピペできる簡単なサンプルをメモしておきます。

スクリーンショット 2020-01-22 13.25.38.png

準備

$ mkdir myapp
$ cd myapp
$ npm init -y

インストール

$ npm i axios

コード

app.js
'use strict'

const axios = require('axios');
const URL = `DiscordのWebhook URL`;

//ヘッダーなどの設定
const config = {
    headers: {
        'Accept': 'application/json',
        'Content-type': 'application/json',
    }
}

//送信するデータ
const postData = {
    username: 'n0bisuke BOT',
    content: 'Node.jsからポストしてるよ :)'
}

const main = async () => {
    const res = await axios.post(URL, postData, config);
    console.log(res);    
}

main();

実行するとPOSTされます。

$ node app.js

ちなみにcurl版

curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"n0bisuke BOT","content":"Node.jsからポストしてるよ :)"}' 'DiscordのWebhook URL'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda + API GatewayでCORSを有効にしているのにCORSでエラーになる

Amazon Pinpoint を使用するために、チュートリアル: E メール設定管理システムの設定をやってみたら、ステップ 6: ウェブフォームを作成してデプロイするで引っかかったので。

API GatewayでCORSは有効になっている

API Gatewayのメソッドレスポンスではこんな感じで設定済みなのに、いざ違うドメインから実行しようとするとブロックされてしまう。
1.PNG

対策:Lambda 関数側でもレスポンスヘッダーを設定する必要がある

チュートリアルの前ステップで作成したLambda関数のコードでは、レスポンスが特に設定されていない。

終了時にレスポンスを設定・返却する必要がある。

  const response = {
    "statusCode": 200,
    "headers": {
        "Content-Type": 'application/json',
        "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
        "Access-Control-Allow-Methods": "POST",
        "Access-Control-Allow-Origin": "*"
    },
    "body": JSON.stringify(event)
  };

  callback(null, response);

"body" の中身はJSON形式であれば空でもいい。設定しなかったりただの文字列だとAPI Gateway側のエラーで返される。

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

JavaScript: 変数の初期化では`undefined` と `null` のどちらを使うのがよい?

概要

JavaScriptには undefinednull があります。

先ほど以下のようなコードを見かけました。

let callback = undefined;

私個人としては初期化の時は null を入れておくことが多いので、変数の初期化ではundefinednull のどちらを使うのがよいのか考えました。

個人的な結論

undefined はその名の通りundefinedを示すものです。
変数を初期化している以上、その変数はundefinedとは呼べないのではないかと。

そんなこんなで 「初期化の時は null を入れておく」 に落ち着きました。

補足

ググり力が足りないためか良い感じの初期化に関するドキュメントが見つけられませんでした。
何かあれば教えていただけると嬉しいです。

補足2

いろんなところに書かれている内容ではありますが、udefinednull の比較について。

% node -v
v12.14.1
> undefined == undefined
true
> undefined == null
true
> null == null
true

> undefined === undefined
true
> undefined === null
false
> null === null
true

ポイント: undefined == nulltrue

参考

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

HTMLに​などの特殊空白文字が含まれていないか検品する

Gitのpre-commitでしっかり検査したい方はこちら

ブックマークレット

javascript:!function(){const o=(o=>{const e=[];for(const o of[11,847,8203,8204,8205,8206,8207,8232,8233,8234,8235,8236,8237,8238,8289,8290,8291,65279])e.push(String.fromCharCode(o));return e})(),e=[],t=[],n=o=>{let e=o.parentNode,t=0;for(;e;)e=e.parentNode,t++;return t};for(const o of[...document.all]){const t=n(o);e[t]||(e[t]=[]),e[t].push(o)}e.reverse();for(const n of e)if(n&&n[Symbol.iterator])for(const e of n)for(const n of o)if(-1!==e.outerHTML.indexOf(n)){const o=document.createElement("div");let r=null;e.style.backgroundColor="tomato",e.style.outline="2px solid tomato",o.innerHTML=e.outerHTML.replace(new RegExp(n,"g"),""),r=o.firstElementChild,e.replaceWith(r),t.push([r,e])}if(t.length){console.group("★の位置に特殊文字を検出しました");for(const{0:e,1:n}of t){let{outerHTML:t}=n;e.replaceWith(n);for(const e of o)t=t.replace(new RegExp(e,"g"),"");console.log(n,t)}return console.groupEnd(),void window.alert(`${t.length}個の要素に特殊空白文字が含まれているようです`)}window.alert("OK")}();

概要

Microsoft製品(だけではないけれども)を利用していると、原稿に特殊な目に見えない空白文字が紛れ込むことがあります。

代表格が文字コード8203のゼロ幅スペースです。一見すると何も入力されていないように見えるますが、実際には目に見えない空白文字が入力されています。

これがURLの中に紛れていたりすると非常に厄介です。URLエンコードされてしっかり文字列として解釈されるので、正しくリンクが繋がりません。目視ではこの特殊文字がいないように見えるので、原因に気づくまでに時間がかかってしまうかもしれないですね。

console.log('abc'.length); // > 3
console.log('a'.charCodeAt(0)); // > 97
console.log(String.fromCharCode(97)); // > "a"
console.log(String.fromCharCode(97).length); // > 1

console.log(String.fromCharCode(8203)); // > "​"
console.log(String.fromCharCode(8203).length); // > 1

&#8203;はエディタ上で視認できませんでした。ただ、「?」と表記されることもあるようです。
※ VS Code、Sublime Text3では確認できませんでした。

空白文字は意外と種類がある

NeqZ8y5◆mmft4k9vgtL6さんの公開している5chってどうよ?内、「2. スペースは" "だけじゃない的な話」によると、50種類以上もスペースに似た何かがあるようです。

このブックマークレットは、当該記事で紹介されている空白文字をページ内から検出するものです。

アラートダイアログで結果を表示し、コンソールに詳細を出力します。

おまけで元コード

void function () {
    // 検査する文字列
    const strSet = (charCodeSet => {
        const set = [];

        for (const charCode of charCodeSet) {
            set.push(String.fromCharCode(charCode));
        }

        return set;
    })([
        // 引用:http://anti.rosx.net/etc/memo/002_space.html
        11, // 垂直タブ
        847, // 結合書記素接合子
        8203, // ゼロ幅スペース
        8204, // ゼロ幅非接合子
        8205, // ゼロ幅接合子
        8206, // 記述方向制御(左から右へ)
        8207, // 記述方向制御(右から左へ)
        8232, // 行区切り文字
        8233, // 段落区切り文字
        8234, // LRE
        8235, // RLE
        8236, // PDF
        8237, // LRO
        8238, // RLO
        8289, // 関数適用
        8290, // 不可視の乗算記号
        8291, // 不可視の区切り文字
        65279 // ゼロ幅のノーブレークスペース
    ]);
    const targets = []; // すべての要素が親要素の多い順(ネストの深い順)に格納される
    const cache = []; // もともとのマークアップをキャッシュする
    const getParentLength = node => {
        let parent = node.parentNode;
        let count = 0;

        while (parent) {
            parent = parent.parentNode;
            count++;
        }

        return count;
    };

    for (const node of [...document.all]) {
        const length = getParentLength(node);

        if (!targets[length]) {
            targets[length] = [];
        }

        targets[length].push(node);
    }

    // 親の数が多い順
    targets.reverse();

    for (const layer of targets) {
        if (!layer || !layer[Symbol.iterator]) continue;

        for (const node of layer) {
            for (const str of strSet) {
                if (node.outerHTML.indexOf(str) === -1) continue;

                {
                    const dummyContainer = document.createElement('div');
                    let firstElementChild = null;

                    node.style.backgroundColor = 'tomato';
                    node.style.outline = '2px solid tomato';
                    dummyContainer.innerHTML = node.outerHTML.replace(
                        new RegExp(str, 'g'),
                        ''
                    );
                    // 一次置き換え後の要素のアドレスを確保しておく
                    firstElementChild = dummyContainer.firstElementChild;

                    // 上位階層が多重検出されないように
                    // いったん特殊文字を削除したマークアップに置き換える
                    node.replaceWith(firstElementChild);
                    cache.push([
                        firstElementChild,
                        node
                    ]);
                }
            }
        }
    }

    if (cache.length) {
        console.group('★の位置に特殊文字を検出しました');

        for (const {0: dummy, 1: origin} of cache) {
            let {outerHTML} = origin;

            dummy.replaceWith(origin);

            for (const str of strSet) {
                outerHTML = outerHTML.replace(new RegExp(str, 'g'), '');
            }

            console.log(origin, outerHTML);
        }

        console.groupEnd();
        window.alert(`${cache.length}個の要素に特殊空白文字が含まれているようです`);

        return;
    }

    window.alert('OK');
}();

出典

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

JapaScript の dateObj.toISOString() が便利だった。

日付をYYYY/MM/DD形式などにフォーマットして出力したい場合に、年月日をそれぞれ求めてから文字列に加工していました。

インターネットを検索しても同様の記述が多く、もっと簡単な方法がないかなとMDNをみていたら常にYYYY-MM-DDTHH:mm:ss.sssZの文字列を返すtoISOString()があり、使ってみたら便利でした。

Qiitaでもすでに投稿されている方がいますので、その一部のリンクを記述します。

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

JavaScript の dateObj.toISOString() が便利だった。

日付をYYYY/MM/DD形式などにフォーマットして出力したい場合に、年月日をそれぞれ求めてから文字列に加工していました。

インターネットを検索しても同様の記述が多く、もっと簡単な方法がないかなとMDNをみていたら常にYYYY-MM-DDTHH:mm:ss.sssZの文字列を返すtoISOString()があり、使ってみたら便利でした。

Qiitaでもすでに投稿されている方がいますので、その一部のリンクを記述します。

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

JavaScript map()で上手く新しい配列が作れなかった話

JSに慣れていなくて、配列に対して処理を行うmap()で上手く配列が作成できなかった話。

mapでの配列新規作成

numを要素に持つobjArray1配列を定義し、mapを使ってnumを2倍にしたobjArray2を新しく作成しようとした。

const objArray1 = [{num:1}, {num:3}, {num:5}];

const objArray2 = objArray1.map(obj => {
    obj.num = obj.num *2;
    return obj;
});

console.log(objArray1); // {num:2}, {num:6}, {num:10}
console.log(objArray2); // {num:2}, {num:6}, {num:10}

するとobjArray1objArray2が同じになってしまった。

map()は新しく配列を生成するメソッドと聞いていたが、各要素に対しては参照渡しするだけなのねーと思い、以下の通りにソースコードを修正した。

const objArray1 = [{num:1}, {num:3}, {num:5}];
const objArray2 = objArray1.map(obj => {
        const newObj = {num: obj.num * 2};
        return newObj;
    });
console.log(objArray1); // {num:1}, {num:3}, {num:5}
console.log(objArray2); // {num:2}, {num:6}, {num:10}

これで思う通りに配列が作成できた。

数値型の配列の場合

多分数値はプリミティブ型(?!)なのかと思い、数値の配列で同様のことを試してみた。

const numArray1 = [1, 3, 5];
const numArray2 = numArray1.map(num => {
    return num * 2;
});
console.log(numArray1); // [1, 3, 5]
console.log(numArray2); // [2, 6, 10]

これは上手くいった。

Javascriptにおけるプリミティブ型とオブジェクト

数値型以外に何がプリミティブ型なのかなー、と思い調べたところ。
数値, 文字列, Bool, undefined, nullがプリミティブ型。
それ以外はすべてオブジェクト型、だそうである。

Javaとは違って文字列もプリミティブ型なのかー、と思った。

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

【Rails】 DataTables 検索結果の保持方法

はじめに

これまでに Rails + DataTable 関連の記事を書いてきましたが、別のページから戻ってきた時に検索結果を保持していて欲しいなどのユーザーの要望があるかと思いますので、そのような細かい設定の変更方法について今回はまとめさせていただきます。

関連リンク

関連リンクを下記に載せておくので、必要であれば参考にしてください。。

検索結果の保持方法

次の2つを設定するだけで、検索結果を保持してくれるようになります。

  • stateSave
  • stateDuration
app/assets/javascripts/concern/datatables.coffee
class @DataTables
  # *** 省略 ***
  drawTable: ->
    # *** 省略 ***

    # stateSave : 別のページから戻ってきた時に検索結果を保持するかどうかを設定可能(trueが保持)
    stateSave: true,

    # stateDuration : stateSave の検索結果の保持する時間を設定可能(1秒単位で設定可能)
    stateDuration: 60 * 15,   # sec * min

  # *** 省略 ***

まとめ

英語のドキュメントさえ読んで使いこなすことができれば、DataTablesはユーザーのニーズを柔軟に反映した高機能なテーブルを短時間で実装することができます。
Rails と組み合わせると爆速で開発が捗ると思っているので、ぜひ活用してみてください。

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

Chart.js【javascriptで簡単にグラフが描ける!】

1.Chart.jsを読み込む

CDNサーバからスクリプト「Chart.js」を読み込みます。
具体的には、グラフを描きたいWebページのHTMLソース中に以下の1行を加えるだけです。

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>

2.canvas要素を記述

グラフを掲載したい場所に、canvas要素を使って下記のように1行を記述するだけです。
idは他と重複しない名前をつけます。

<canvas id="ChartDemo" height="450" width="600"></canvas>

3.グラフの描画内容をscript要素内に記述

<script>
   描画内容の指示(下記に記述例があります)
</script>

描画内容の指示(以下のようなグラフの例)
・折れ線グラフです。
・X軸には、Item1~Item7の項目があります。
・値は、Item1から順に10, 11, 12, 13, 14, 15, 16です。
・オプションで、Y軸を0~100に固定しています。

var ctx = document.getElementById("ChartDemo").getContext('2d');
var ChartDemo = new Chart(ctx, {
  // 作成するグラフの種類
  type: 'line',
  data: {
  // ラベルとデータセットを設定
    labels: ["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7"],
    datasets: [
      {
        label: "Chart-1",
        borderColor: 'rgb(255, 0, 0)',
        data: [10, 11, 12, 13, 14, 15, 16],
      },
    ]
  },
  // オプション設定
  options: {
    scales: {
      yAxes: [
        {
          ticks: {
            beginAtZero: true,
            min: 0,
            max: 100
          }
        }
      ]
    }
  }
});

感想

簡単ですね、すごい!
オプション指定で色々できそうです。
今は便利なライブラリが色々あるんですねー。

自己紹介

プログラマー歴7年、ブランク10年のアラフォー主婦です。
記事中に間違っていることがあったら、どんどんつっこんで頂けると嬉しいです。

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

[JavaScript] if文の条件で代入をしてはいけない

ifの条件には truthy または falsy と認識される式が書けます。1
そして条件の中で代入も行うことができます。

代入の例
if (a = 'a') { // 結果、'a'(truthy)が評価される
  console.log(a); // a
}

if (a = '') { // 結果、空文字(falsy)が評価される
} else {
  console.log(a); // ''
}

さらに、こんなこともできます。

複数代入の例
if (a = 'a', b = 'b', a === 'b') {
  console.log('true');
} else {
  console.log('false'); // こっちに入る 
}

// 複数の代入もできている
console.log(a); // a
console.log(b); // b

abに代入を行なった上で、a === 'b'の評価をしています。

これはいつ使うの?

基本的に使いません。
この書き方は悪い習慣とされています。

理由

その1

比較演算子のタイプミスかどうかがわかりにくい。
もし、代入を行う際は以下のように括弧を記述することを推奨されています。

if ((a = 'a')) {}

その2

ほとんどの場合においてわかりにくい。

if ((res = myFunc())) {
  console.log(res);
}

// あえてif文で代入する必要がない
res = myFunc();
if (res) {
  console.log(res);
}

その3

スコープがわかりにくい。

if ((a = 'a')) {
  console.log(a); // a
}
console.log(a); // a

便利な場面を 無理やり 見出してみた

特定の処理だけをスキップする時に使う。

let res = null;
let err = null;
if (({res, err} = myService()), err == null) {
  // resを使った処理とか
}

function myService() {
  // 何かの処理の結果
  return { res: {}, err: {name: 'hogeErr'}}
}

うーん、、Goっぽくもない気がしますが、、
やっぱり嬉しい場面がないですね、、

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

Reactで行数可変なtextareaを作った話

TL;DR

タイトルなし.gif

中身に応じて高さが変わるtextareaが出来た
コピペ、複数行まとめて削除しても大丈夫

軽く検索しても出てこなかったので
似たようなことをしたくなった人や未来の自分のために書いてみました

環境

react:15.6.2
redux-form:6.8.0
lodash.isempty:4.4.0

正直reactだけでもOKなはず
廃止されるライフサイクルメソッドなども使っていないので最新版のreactでも動くと思います

他2つは細かいところ書くのが面倒なので使ってます
無くても本質には関係ないので大丈夫です

chromeとIEで動作確認済み(バージョンはわからないですが2020/01付近です)

参考

textareaの大きさを自動調整

実装

細かいところはフィーリングで読んでください

index.js
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import TextArea from './textArea';

function index() {
  return (
    <div>
      <Field name="TextArea" component={TextArea} rows={2} />
    </div>
  )
}

export default reduxForm({ form: 'form' })(index);
textArea.js
import React, { Component } from 'react';
import isEmpty from 'lodash.isempty';

class TextArea extends Component {
  constructor(props) {
    super(props);
    this.textArea = null;
  }

  shouldComponentUpdate(nextProps) {
    if (this.props.input.value !== nextProps.input.value) {
      if (isEmpty(nextProps.input.value)) {
        this.textArea.setAttribute('rows', this.props.rows);
      } else {
        this.textArea.setAttribute('rows', this.props.rows);
        while (this.textArea.scrollHeight > this.textArea.offsetHeight) {
          const tempRows = Number(this.textArea.getAttribute('rows'));
          this.textArea.setAttribute('rows', tempRows + 1);
        }
      }
    }
    return true;
  }

  render() {
    return (
      <textarea
        ref={c => {
          this.textArea = c;
        }}
        style={{ resize: 'none' }}
        name={this.props.input.name}
        onChange={e => this.props.input.onChange(e.target.value, this.props.input.name)}
        rows={this.props.rows}
      />
    );
  }
}

解説

参考にしたコードと似たようなことをしています
以下の部分が全てです
もうちょっとifまとめられそう...

  shouldComponentUpdate(nextProps) {
    // textareaの内容が変更された時のみ操作
    if (this.props.input.value !== nextProps.input.value) {
      if (isEmpty(nextProps.input.value)) {
        // 内容が空なら初期値の行数に戻すだけ
        this.textArea.setAttribute('rows', this.props.rows);
      } else {
        this.textArea.setAttribute('rows', this.props.rows);
        // 内容が有るならスクロールせずに表示できる高さになるまで初期値から1行ずつ増やす
        while (this.textArea.scrollHeight > this.textArea.offsetHeight) {
          const tempRows = Number(this.textArea.getAttribute('rows'));
          this.textArea.setAttribute('rows', tempRows + 1);
        }
      }
    }
    return true;
  }

注意点

高さにmax-heightなどで上限を定めたい時は
直接textareaにmax-heightを書かないでください

高さの計算とぶつかるのか無限ループになってブラウザが固まります
高さを決めたい場合は適当にラップしてそちらに色々設定してください

※今回で言うと、index.jsのdivに{max-height: 100px, overflow-y: scroll}みたいにすれば
100pxより入力欄が高くなったらスクロールになるよう切り替えられます

まとめ

shouldComponentUpdateでこういうことして良いのかはわかりませんがとりあえず動いてるのでヨシとします
横方向(cols)にも同じ方法で拡縮できるのではないかと思いますがそこまでは試してないです

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

HTML+Javascriptでデスクトップ版Cytoscape用の拡張GUIを手軽に作る方法

日頃からQiitaにはお世話になっているので、ブログに書いた内容のうち、過去の自分が欲しかった情報(動かすのに必要な部分)を要約してご紹介します。

元のブログ記事はこちらです。
ネットワーク可視化ツール「Cytoscape」の拡張GUIを、htmlファイル1つでお手軽に作る話

Cytoscape自体の使い方についても書いているので、よかったら参考にしてみてください。
ネットワーク可視化ツール「Cytoscape」に雑に入門して手っ取り早く使う話

概要

HTML+Javascriptから、XMLHttpRequest(xhr)を使ってCyREST APIを叩く方法です。
デスクトップ版のCytoscapeに、お手軽に外付けのGUIを生やすことができます。

例として、サンプルセッションの「Yeast Perturbation.cys」を使って、「選択中のエッジのEdgeBetweennessの合計値をリアルタイムで表示する」GUIをつくってみました。

右のウィンドウがそれです。

Cytoscapeで上記サンプルセッションを開いたうえで、下のソースコードをUTF-8のHTMLとして保存して、IE以外のブラウザで開いてください。

Shift+ドラッグでエッジを複数選択すると、ほぼリアルタイムで集計値が表示されます。

ソースコード

ソースコードの説明は、コード内のコメントを参照ください。

HTMLもJavascriptもCytoscapeも専門外なので、品質は保証できませんが、狙い通り動くことは確認済みです。

cy.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <style>
    #result {
      font-size: 25pt;
    }
  </style>
</head>

<body>
  <div id="result">ここに結果が入ります。</div>
  <script>
    // 計算結果を表示する用のdivを取得
    const result = document.getElementById("result");

    // たまにメモリー解放してあげないと暴走するので、メモリー解放コマンドを用意
    const free_unused_memory = () => {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', 'http://localhost:1234/v1/gc');
      xhr.send(null);
    }

    // とりあえずHTTPリクエストのとこ全部囲む
    const update = () => {

      // APIのリクエストボディを、Json文字列として用意する(書き方はCytoscapeのヘルプを参照)
      const request_body = JSON.stringify({
        "columnList": "EdgeBetweenness",
        "edgeList": "selected",
        "network": "current"
      });

      // xhrを使う準備
      const xhr = new XMLHttpRequest();

      // Edgeのアトリビュートを取得するAPI
      xhr.open('POST', 'http://localhost:1234/v1/commands/edge/get attribute');

      // リクエストはJson形式で送る
      xhr.setRequestHeader('Content-Type', 'application/json');

      // 返事はJson形式で解釈する
      xhr.responseType = 'json';

      // HTTPリクエストに返事が来たときの処理
      xhr.onload = () => {

        // 返事の中身を取り出して
        const response_body = xhr.response.data;

        // reduce関数でEdgeBetweennessを積算してtoFixedで小数点1桁表示
        // 0を足しているのはnull値対策
        const total_Betweenness = (response_body.reduce((acc, x) => acc + x.EdgeBetweenness, 0) + 0).toFixed(1);

        // テンプレートリテラルで結果のhtml文字列を作る
        const html = `選択中のEdgeは${response_body.length}本です。<br>
                               EdgeBetweennessの合計は${total_Betweenness}です。`;

        // 結果をhtmlに書き込む
        result.innerHTML = html;
      }

      // HTTPリクエストを送信
      xhr.send(request_body);

      // 10回毎にメモリ開放
      if (update_count > 10) {
        free_unused_memory();
        update_count = 0;
      } else { update_count++ }
    }

    // メモリ解放するタイミング用のカウンタ
    let update_count = 0;

    // 最初の1回はすぐに実行
    update();

    // あとは1秒間隔でアップデート
    setInterval(update, 1000);
  </script>
</body>

</html>

おまけ

  • リクエストJsonの"columnList"の値は、カンマ区切りで複数アトリビュートを指定することもできます。
  • この例では結果を文字列として表示しただけですが、集計データをChart.jsなどでリアルタイムにグラフ化するような使い方もできると思います。
  • ブログには、このHTMLをCytoscapeのウィンドウ内で開く方法も書いてあるので、よかったら覗いてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

デザイナがReactを始める前に押さえておきたいJavascriptの基本③(クラス編)

はじめに

テーブルレイアウトからcssスタイリングへの過渡期だった頃:sweat_smile:、私はWebデザイナーを目指し始めました。
それから◯年あまりの間、ビジュアルを作ったり、HTML・CSS・jQuery(コピペ多め)でのマークアップをやってまいりました。

そんな中、ここ最近急速にフロントエンド界隈の難易度が上がり、今までデザイナが担当していた分野のフロントエンドコーディングにおいても、より専門性の高いJSコーディングスキル、Reactなどの知識が求められるようになりました。

自身がReactを学ぶ上で色々とつまずき、心折れそうになった理由は、Javascriptの配列・オブジェクト・クラスをきちんと理解しないまま始めてしまったから。
この経験に基づき、デザイナがReactを始める前に最低限押さえておくべきJavascriptの基本(個人的見解)について、3回(予定)に分けて書きます。

クラス構文

オブジェクトをテンプレート化できるのがクラス構文です。
色々な関数をひとまとめにして使い回せたり、他のクラスに内容を引き継ぐことができるので、よりメンテナンス性の高いコードを書くことができます。

(前提知識としてオブジェクトの基本への理解が必要です。)

クラス構文に書き換えてテンプレートを作る

まずはJavascriptの基本②(オブジェクト編)の復習も兼ねて、複数オブジェクトの配列とメソッドを使って多角形の内角の和を求めるコードを作成し、そこからクラス構文を使ったテンプレートを作っていきます。

図形 含まれる三角形の数 内角の和
3角形 1 180°
4角形 2 360°
5角形 3 540°
6角形 4 720°
const insideAngleSums = [
  {
    n: 3, //3角形
    getAngleSum(){console.log(`${this.n}角形の内角の和は${180 * (this.n - 2)}°`)}
  },
  {
    n: 4, //4角形
    getAngleSum(){console.log(`${this.n}角形の内角の和は${180 * (this.n - 2)}°`)}
  },
  {
    n: 5, //5角形
    getAngleSum(){console.log(`${this.n}角形の内角の和は${180 * (this.n - 2)}°`)}
  },
  {
    n: 6, //6角形
    getAngleSum(){console.log(`${this.n}角形の内角の和は${180 * (this.n - 2)}°`)}
  }
]
insideAngleSums[0].getAngleSum() //3角形の内角の和は180°
insideAngleSums[1].getAngleSum() //4角形の内角の和は360°
insideAngleSums[2].getAngleSum() //5角形の内角の和は540°
insideAngleSums[3].getAngleSum() //6角形の内角の和は720°
図形 含まれる三角形の数 内角の和
n角形 n-2 180°* (n-2)

n角形の内角の和の公式は
180 * (n - 2)

クラス構文を使って、この公式をテンプレート化してみます。
コンストラクタはインスタンス生成時に呼び出される関数です。

class InsideAngleSum { //classの最初の I は大文字
  constructor(n){ //constructor()で初期化し、nの値には引数を渡してセット
    this.n = n //このクラスから作られるインスタンスはthisで表現できる
  }
  getAngleSum(){console.log(`${this.n}角形の内角の和は${180 * (this.n - 2)}°`)}
}
const insideAngleSums = [
  //newを使ってインスタンスを作る
  new InsideAngleSum(3),
  new InsideAngleSum(4),
  new InsideAngleSum(5),
  new InsideAngleSum(6)
]

insideAngleSums.forEach(insideAngleSum => insideAngleSum.getAngleSum())
//3角形の内角の和は180°
//4角形の内角の和は360°
//5角形の内角の和は540°
//6角形の内角の和は720°

テンプレートする前と同じ結果になりました。
ちなみに100角形の内積の和を求めてみると。。

new InsideAngleSum(100).getAngleSum() // 100角形の内角の和は17640°

クラスの継承

さらにclass構文は、内容を他のclass構文に引き継げるので、もともと書かれている構文を変更せずに内容の追加などを行なうことができます。

まず三角形の面積を求める公式をクラスにしてみます。
底辺 * 高さ / 2 から、

class Triangle {
  constructor(width, height){
    this.width = width
    this.height = height
  }
  area(){console.log(`底辺${this.width}cm 高さ${this.height}cm の三角形の面積は、${this.width * this.height / 2}㎠ です。`)}
}

n角形の内角の和の公式を親クラス、三角形の面積の公式を子クラスとして継承させてみます。
(クラスの仕組みを説明するという目的達成のために、無理やりなコードになってしまってるかも:innocent:

class InsideAngleSum { //親クラス
  constructor(n){
    this.n = n
  }
  getAngleSum(){console.log(`${this.n}角形の内角の和は${180 * (this.n - 2)}°`)}
  //staticで始まる静的メソッドはインスタンスを介さずに、直接クラスから呼び出せる。(インスタンスを介さないので、thisは使えない)
  static outsideAngleSum(){console.log('多角形は何角形でも外角の和は360°')} 
}

class Triangle extends InsideAngleSum { 
//extends InsideAngleSumでInsideAngleSumクラスに書かれたコードが、そのまま Triangle クラスに引き継がれる
  constructor(n, width, height){
  //子クラスの constructor()で this を使うには constructor()の最初に super()と記述。親クラスの constructor()が呼ばれ、親クラスで引数も渡しているのでここでも渡す。
    super(n)
    this.width = width
    this.height = height
  }
  polygon(){console.log(`${this.n}角形!!`)}
  area(){
    console.log(`底辺${this.width}cm 高さ${this.height}cm の三角形の面積は、${this.width * this.height / 2}㎠ です。`)
    super.getAngleSum() //親クラスのメソッドを呼ぶには、super をつける。
    this.polygon() // 子クラスのthisは定義されたクラス自身のメソッドを指す
    InsideAngleSum.outsideAngleSum() //静的メソッドはクラスから直接呼び出せる。
  }
}

const info = new Triangle(12, 5, 8)
console.log(info.area())
//底辺5cm 高さ8cm の三角形の面積は、20㎠ です。
//12角形の内角の和は1800°
//12角形!!
//多角形は何角形でも外角の和は360°

シンプルにまとめられました :thumbsup_tone1::thumbsup_tone1:

最後に

道半ばですが、分からないことがあっても諦めずに少しずつすすめると、小さな成長を感じる瞬間があります。
そんな素敵な瞬間を楽しみながら頑張ろうと思っております:innocent:
フロントエンドスキルを武器にしたいWebデザイナーのみなさん、一緒にがんばりましょう!

次回からはReact入門編の記事をシリーズで書いてみたいと思います。

参考にさせていただいたサイト

https://developer.mozilla.org/

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