20190527のJavaScriptに関する記事は27件です。

スペースの連続入力を弾く入力フォームの作り方

作りたいもの

以下のようなエラーチェックを行う入力フォーム

OKな例
・山田太郎
・山田 太郎(間に半角スペース1個)
・山田 太郎(間に全角スペース1個)
・山田  太郎(間に全角スペース2個)
・山田 アンデルセン 太郎(間に全角スペース1個ずつ)

エラーを吐きたい例
・「 」←半角スペース1個のみ
・「 」←全角スペース1個のみ
・「 」←半角スペース2個連続
・「  」←全角スペース2個連続

名前の間に入るスペースは通すけど、スペースだけの入力は許容しない、といった感じ。

使ったもの

Javascript, 正規表現

実装

var str = '山田 太郎';
var pattern = /^\u3000+$/g;// 1文字以上の全角スペース

if(str.match(pattern) == null){
  // NG:スペースのみ
} else {
  // OK:スペース混じりの文字列
}
正規表現 意味
/^\u3000+$/g 1文字以上の全角スペース
+ 直前の文字の1文字以上の繰返し
^ 文字列の先頭
$ 文字列の末尾
/^正規表現$/ 任意の正規表現に完全一致する文字列
/正規表現/g 全ての文字についてマッチするかどうかを検査

半角スペースの場合は/^\x20+$/gで同じことができる。

\u3000ではなく「 」で全角スペースを直書きしても良いが、Lintエラーに引っかかることがあるのでこっちのほうがよい。

参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions

https://qiita.com/masayuki14/items/51884596fcb442006cfb

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

ブラウザの戻るボタンを依存ライブラリ無しで戻れなくする方法

社内向けアプリケーションなんかを作っている時に戻るボタンで画面遷移すると予期しない動きをするので、戻るボタンを押しても戻れないようにしたい場合があります。

そんな時はhtmlの先頭に以下のコードを追加することで戻るボタンを無効化することができます。
このコードはモダンブラウザであれば依存ライブラリ(JQueryなど)なしでも動きます。

このコードでは実際に戻るボタンを無効化するわけではなく、ページを表示するたびに遷移先がnullの履歴を追加する処理を入れることで、戻るボタンの無効化を実現しています。

onpopstateは戻るボタンで画面遷移した時に発火するイベントで、pushStateはブラウザに遷移先を追加するメソッドとなります。

イベントハンドラの外にあるpushStateは、最初に画面をロードした時に実行され、イベントハンドラの中にあるpushStateは戻るボタンを押した時に実行されるため、どちらのコードも必要です。

戻るボタンが押されたらアラートを出力する

//画面初期表示時に遷移先nullの履歴を追加する
history.pushState(null, null, null);

//ブラウザの戻る/すすむボタンで発火するイベント
window.onpopstate = function (event) {
        //戻るボタンを押して戻った時に再度nullの履歴を追加する。
        //※この処理はalertの前に書いておく必要あり。alertの後ろだと戻るボタンを連打したときに戻れてしまう。
    history.pushState(null, null, null);
    alert("ブラウザの戻るボタンは禁止されています。");
};

サンプルコードはこちらで実際に動かしてみることができます。
https://stackblitz.com/edit/js-n5zty9?embed=1&file=index.js

注意点

戻るボタンがクリックされた際にalertやconfirmで処理を止めたりする場合は、必ずalertの前にpushStateを記述する必要があります。
alertの次においてしまうと、戻るボタン連打した時に前の画面に戻れてしまう様になるので要注意です。

戻るボタンが押されたら確認ダイアログを表示する。

こんな風に確認画面を出して、OKが押されたらトップページに遷移することもできます。

history.pushState(null, null, null);

//ブラウザの戻る/すすむボタンで発火するイベント
window.onpopstate = function (event) {
         //連続で戻るボタンがクリックされたときにも戻れない様に、履歴にnullを追加する。 
         history.pushState(null, null, null)
     var r = confirm("このページでのブラウザの戻るボタンは禁止されています。トップページに移動しますか?");
     if (r == true) {
        //コンテキストルートに戻る
        var context = window.location.pathname.substring(0, window.location.pathname.indexOf("/", 2));
        window.location.replace(context);
     } else {

     }
};

このコードは、PCのfirefoxとchromeでテストしました。他のブラウザや環境での動作は不明です。

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

[Vue.js] 別ウィンドウで開くリンクの作成

やりたいこと

別ウィンドウで開くリンクをつくりたかった

方法

メインのコンポーネント

<script>
import { ROUTER } from '@/libs/constant-router'
export default {
  name: 'Register',
  methods: {
    toTerms() {
      let routeData = this.$router.resolve({name: ROUTER.TERMS_NAME, query: {data: "someData"}});
      window.open(routeData.href, '_blank'); //←★ここ
    },
}
</script>

 設定ファイル群

constant-router.js

プロジェクトルート直下に「libs」と名付けたディレクトリを切って、その中に格納

export const ROUTER = {
  TERMS_PATH: '/terms',
  TERMS_NAME: 'terms',
}

router.js

import Terms from '@/views/Terms.vue'

Vue.use(Router)

const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: ROUTER.TERMS_PATH,
      name: ROUTER.TERMS_NAME,
      component: Terms,
    },
  ],
})

router.toTerms = () => {
  router.push({ name: ROUTER.TERMS_NAME })
}


export default router

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

Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る

概要

MongoDBのCLIツールであるmongoシェルを使っていると、JavaScriptをコマンドシェル(REPL)をとして使うのも良い思います。それならNode.jsでも良いかもしれませんが、Go言語はシングルバイナリなところが気に入っています。今回使用するJavaScriptエンジンはECMAScript 5.1の仕様かつNode.jsとは違いI/O関係など実装されていないので、実用的にするためにはかなりGo言語で拡張しなければなりません。しかし、いろいろ拡張することでGo言語の勉強にもなり、実用的なツールも手にすることも可能です。

環境

  • Windows 10
  • Go 1.12
  • Go JavaScript パッケージ
    • github.com/dop251/goja (ECMAScript 5.1)
    • Javascriptエンジンはほかに otto, go-duptake go-duktapeなどがある

最低限の実装

  • コマンドの入力・実行。実行結果の表示
  • console.logもprintもないのでprintの実装
  • exitコマンドで終了
  • 継続行のサポート
    • 継続行のpromptは...>とします
    • 継続のキャンセルは...と入力します

実行例

go run main.go
> for(var i=0; i < 5; i++) {
...> print(i)
...> }
0
1
2
3
4
undefined
> doc={a:1, b:"xxxx"}
{"a":1,"b":"xxxx"}
> Object.keys(doc)
["a","b"]
> exit

実行例、特に実用的なものは実装していないので簡単なものしかできません。JavaScriptの仕様はECMAScript 5.1なので現在使わているのものより古いです。

ソースコード

main.go
package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"

    "github.com/dop251/goja"
)

type jsRuntime struct {
    runtime   *goja.Runtime
    stringify goja.Callable
    program   *goja.Program
}

func scanCommand(scanner *bufio.Scanner, prompt string) bool {
    fmt.Print(prompt)
    return scanner.Scan()
}

func (js *jsRuntime) jsprint(vals ...goja.Value) {
    rt := js.runtime
    format := "%v"
    for _, val := range vals {
        str, ok := val.Export().(string)
        if ok {
            fmt.Printf(format, str)
            format = " %v"
            continue
        }
        v, err := js.stringify(goja.Undefined(), val)
        if err != nil {
            rt.Interrupt(rt.NewGoError(err))
            return
        }
        fmt.Printf(format, v)
        format = " %v"
    }
    fmt.Println()
}

func (js *jsRuntime) setStringify() {
    rt := js.runtime
    json := rt.Get("JSON")
    jsonObj, ok := json.(*goja.Object)
    if !ok {
        panic("JSON not defined")
    }
    stringifyProp := jsonObj.Get("stringify")
    stringify, ok := goja.AssertFunction(stringifyProp)
    if !ok {
        panic("JSON.stringify not defined")
    }
    js.stringify = stringify
}

func initialSetting() *jsRuntime {
    rt := goja.New()
    js := &jsRuntime{runtime: rt}
    rt.Set("print", js.jsprint)
    js.setStringify()
    return js
}

func (js *jsRuntime) execute() (goja.Value, error) {
    return js.runtime.RunProgram(js.program)
}

const (
    execInput = iota
    contInput
    errorInput
)

func (js *jsRuntime) compile(cmd string) int {
    program, err := goja.Compile("console", cmd, false)
    if err != nil {
        idx := strings.Index(err.Error(), "Unexpected end of input")
        if idx == -1 {
            fmt.Println(err)
            return errorInput
        }
        return contInput
    }
    js.program = program
    return execInput
}

func main() {
    prompt := "> "
    js := initialSetting()
    scanner := bufio.NewScanner(os.Stdin)
    var cmds []string
    for scanCommand(scanner, prompt) {
        cmd := scanner.Text()
        if cmd == "exit" {
            break
        } else if cmd == "..." {
            cmds = nil
            prompt = "> "
            continue
        }
        cmds = append(cmds, cmd)
        switch js.compile(strings.Join(cmds, "\n")) {
        case execInput:
            ret, err := js.execute()
            if err == nil {
                js.jsprint(ret)
            } else {
                fmt.Println(err)
            }
            cmds = nil
            prompt = "> "
        case contInput:
            prompt = "...> "
        case errorInput:
            cmds = nil
            prompt = "> "
        }
    }
}

解説

  • コンソールから入力されたTEXTをscannerで取得してCompileしてエラーが無ければRunProgramで実行します。
    • exitが入力されたらプログラムを終了します
    • RunProgram()の戻り値あるいはエラーを表示します
  • 行を継続のためにはCompileしてみて"Unexpected end of input"というエラーなら入力を継続する
  • 各関数で今実行中のgoja.Runtimeが必要なのでjsRuntime構造体を定義します。
  • Goの関数をJavaScriptで使えるようするには*goja.RuntimeSetを使いGlobalな関数として定義します。オブジェクトの関数として定義するには*goja.ObjectSetを使います。
    • 今回定義したのはprint関数だけです
    • printで直接fmt.Printfなどを使うとJSONが正常に表示されないので、一度JavaScriptのJSON.stringify()を実行してからfmt.Printfで表示します
    • 文字列はJSON.stringifyを使うと"引用符が付くので文字列だけ別処理にしました。
    • setStringify()JSON.stringifyをGo言語から使えるようにします。
  • InterruptはJavaScriptでthrowのように働きます。

(拡張1) load関数の実装

外部ファイルを実行するためにload(ファイル名)を実装します。

func initialSetting() *jsRuntimeに次のコードを追加します

 rt.Set("load", js.jsload)

js.jsloadの実装

jsload
func (js *jsRuntime) jsload(file string) goja.Value {
    rt := js.runtime
    f, err := os.Open(file)
    if err != nil {
        rt.Interrupt(rt.NewGoError(err))
        return goja.Null()
    }
    defer f.Close()
    text, err := ioutil.ReadAll(f)
    if err != nil {
        rt.Interrupt(rt.NewGoError(err))
        return goja.Null()
    }
    val, err := js.runtime.RunScript(file, string(text))
    if err != nil {
        rt.Interrupt(err)
        return goja.Null()
    }
    return val
}

import "io/ioutil"パッケージを使ってファイルを入力します。
入力したファイルの実行はRunScriptを使います。これは入力の継続の判定する必要がないためです。

test.jsを作って実行します

test.js
for(var i=0; i < 5; i++) {
    print(i)
}
go run main.go
> load("test.js")
0
1
2
3
4
undefined
>

(拡張2) Go言語のオブジェクト(構造体とそのメソッド)をJavaScriptから使う

オブジェクトexampleObjとします。これを構造体として定義し、そのメソッドも定義します。以下の例はMethod1,Method2として両方とも円の面積を計算する同じ処理ですが戻り値が型が異なっています。メソッドはgojaパッケージから参照されるのでメソッド名を大文字で始めます。またexampleObjを生成する関数createExampleObjをJavaScriptに設定します。

initialSetting()に追加
    rt.Set("createExample", js.createExampleObj)
type exampleObj struct {
    js *jsRuntime
}

func (js *jsRuntime) createExampleObj() *exampleObj {
    return &exampleObj{js: js}
}

func (o *exampleObj) Method1(r float64) float64 {
    return r * r * 3.14
}

func (o *exampleObj) Method2(r float64) goja.Value {
    return o.js.runtime.ToValue(r * r * 3.14)
}

実行

go run main.go
> o=createExample()
{}
> o.Method1(1)
3.14
> o.Method2(1)
3.14
> o.Method2(2)
12.56
>

問題はないのですが、メソッド名が大文字で始まるのは入力コマンドとしては使いづらいです。そこでこれらのWrapperをJavaScriptで記述します。

initialSetting()に追加
    _, err := rt.RunString(builtin)
    if err != nil {
        panic(err)
    }
const builtin = `
example = function() {
    return new ExampleObj()
}
ExampleObj = function () {
    var _obj = createExample()
    this.getObj = function() {
        return _obj
    }
}
ExampleObj.prototype.method1 = function (r) {
    return this.getObj().Method1(r)
}
ExampleObj.prototype.method2 = function (r) {
    return this.getObj().Method2(r)
}
`

_objExampleObj外から直接アクセスできないようにthis._objにはしませんでした。
外部ファイルにしてloadで読み込むのも可能です。

実行

go run main.go
> o=example()
{}
> o.method1(1)
3.14
> o.method1(2)
12.56
> o.method2(2)
12.56
> 

参照

まとめ

いろいろ工夫すれば使いやすいツールが作れるのではないでしょうか。

問題点

  • 基本的なものもGoで拡張しないと使いえない
  • GCは実装されていないようです。
  • JavaScriptの仕様が古い
    • Proxyがない
    • 可変長引数を次の可変長引数に渡すには配列にしてapplyメソッドを使う

Appnedix

試作の紹介

今、自分用に試作中のものを少し紹介します。

database/sqlパッケージを使用してSQLをJavaScriptのコマンドとして実装して、データの受け取りはなるべくJSON化しています。現時点ではsqlite3PostgreSQLのドライバーを使って試作しています。これらはmongoシェルを参考にしています。
また、コマンドの入力用のパッケージとして https://github.com/chzyer/readline を使っています

さらに考えられるのは

  • MongoDBのようにテーブルが存在しないときに自動的に作成する 
    • 最初にinsertするJSONを用いて、create tableを発行する。
    • データの型をどうするか。データベースごとにdefaultの設定を考える
  • EXCELファイルやCSVファイルを読んでJavaScript上でJSONデータとして扱えるようにする
    • これらのファイルをDBにストアできる
  • MongoDB
    • すでにmongoシェルがあるが、ここに実装すれば他のRDBと連携できる
    • mongoシェルの実装(C++, SpiderMonkey)は複雑なので改造は難しい。
  • I/O関係の実装
  • RESTfulクライアント
  • https://github.com/gizak/termui/ のパッケージをJavaScriptから使えるようにして監視モニター

sqlite3のコマンド例

$go run main.go
script> db=sqlite3("sample.db")
{"driver":"sqlite3","conn":"sample.db"}
script> db.exec("create table tbl1(a integer, b varchar)")
{}
script> db.exec("insert into tbl1 values(?,?)", 1, "string 1")
{}
script> db.exec("insert into tbl1 values(?,?)", 2, "string 2")
{}
script> db.query("select * from tbl1")
{"a":1,"b":"string 1"}
{"a":2,"b":"string 2"}
script>

テーブルオブジェクトを実装して次のように使う

script> t = db.table("tbl1")
{"driver":"sqlite3","name":"tbl1"}
script> t.insert({a:3, b:"string 3"})
{}
script> t.find()
{"a":1,"b":"string 1"}
{"a":2,"b":"string 2"}
{"a":3,"b":"string 3"}
script> t.find("a>1")
{"a":2,"b":"string 2"}
{"a":3,"b":"string 3"}
script> t.find().forEach(function(doc) {
...   if (doc.a > 1) {
...     print(JSON.stringify(doc))
...   }})
{"a":2,"b":"string 2"}
{"a":3,"b":"string 3"}
script>

modesql()でSQLモードにします。SQLモードを終わらせるにはexit;と入力します。

script> modesql(db)
sql> select * from tbl1 where a > 1;
{"a":2,"b":"string 2"}
{"a":3,"b":"string 3"}
sql> create table tbl2(c real ,d varchar);
sql> insert into tbl2 values(1.2, "string 1.2");
sql> select * from tbl2;
{"c":1.2,"d":"string 1.2"}
sql> exit;
script> t2=db.table("tbl2")
{"driver":"sqlite3","name":"tbl2"}
script> t2.find()
{"c":1.2,"d":"string 1.2"}
script>

findにはlimit,skipオプションを実装しました。このほかにはsortselectする項目名を指定するコマンドも実装可能でしょう。

script> t.find().limit(2).skip(1)
{"a":2,"b":"string 2"}
{"a":3,"b":"string 3"}

sqlite3tbl1のデータをPostgreSQLpgtbl1にコピーする

script> pg = postgres()
{"driver":"postgres","conn":"dbname=test sslmode=disable"}
script> pg.exec("create table pgtbl1(a integer, b varchar)")
{}
script> pgt = pg.table("pgtbl1")
{"driver":"postgres","name":"pgtbl1"}
script> t.find().forEach(function(doc) {
...   pgt.insert(doc)
... })
script> pgt.find()
{"a":1,"b":"string 1"}
{"a":2,"b":"string 2"}
{"a":3,"b":"string 3"}
script>

PostgreSQLのpsqlコマンドで確認します

>psql test
psql (11.0)
"help" でヘルプを表示します。

test=# select * from pgtbl1;
 a  |     b
----+-----------
  1 | string 1
  2 | string 2
  3 | string 3

(3 行)


test=#

また、mongoシェル風なテーブルの記述にしたかったのでgojaパッケージを改造しました。

script> t = db.table("tbl1")
{"driver":"sqlite3","name":"tbl1"}
script> t.insert({a:3, b:"string 3"})
{}

としているものをmongoシェル風にdb.tbl1.insertとして使えるようになりました。通常はtbl1undefinedになりますので、undefinedのプロパティが参照されたときに特定のメソッドを呼び出すように改造しました。

script> db.tbl1.insert({a:4, b:"string 4"})
{}
script> db.tbl1.find()
{"a":1,"b":"string 1"}
{"a":2,"b":"string 2"}
{"a":3,"b":"string 3"}
{"a":4,"b":"string 4"}
script>

db.tbl1.fromExcel("Book1.xlsx", "Sheet1")db.tbl1.toExcel("Book1.xlsx", "Sheet1")なども実装出来たら有用かもしれない。

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

JavaScriptで特定の文字列が含まれているか調べるメソッドまとめ

JavaScriptでは特定の値が含まれているか調べる方法が複数あります。
用途によって使い分けられるのでまとめてみました。

String.prototype.indexOf()メソッド

呼び出すStringオブジェクトを引数の値で検索し、
指定された値が最初に現れた位置を返します。

使い方

test.js
let sample = 'abcdefg';
let result = sample.indexOf('c');
console.log(result);

実行結果

2

大文字と小文字は区別し、
値が見つからない場合は-1を返します。
なので…

test.js
let sample = 'ABCDEFG';
let result = sample.indexOf('c');
console.log(result);

実行結果

-1

検索し始める最初の位置を指定する事も出来ます。

test.js
let sample = 'abcdeabcde';
let result = sample.indexOf('cd', 5);
console.log(result);

実行結果

7

この場合、検索する位置を指定しなければ2が表示されますが、
検索する最初の位置を5番目に指定したので、
次のcdが当てはまる、7を取得することが出来ました。

String.prototype.includes()メソッド

選択した文字列を対象の文字列の中に見つけられるかどうかを判断し、
真偽値を返します。
見つかった場合はtrue、見つからなければfalseとなります。

使い方

test.js
let sample = 'abcdefg';
let result = sample.includes('c');
console.log(result);

実行結果

true

大文字と小文字も区別します。

test.js
let sample = 'ABCDEFG';
let result = sample.includes('c');
console.log(result);

実行結果

false

検索し始める最初の位置を指定する事も出来ます。

test.js
let sample = 'hello world';
let result = sample.includes('hello', 2);
console.log(result);

実行結果

false

検索する位置を指定しなければ文字列を見つけられるのでtrueが表示されます。
しかし今回、検索する最初の位置を2番目からに指定したので、
helloが見つからずにfalseを返しました。

String.prototype.search()メソッド

対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
成功した場合は、マッチした箇所の位置を、
失敗の場合は-1を返します。

使い方

test.js
let sample = 'HELLO world';
let regex = /[a-z]/g;
let result = sample.search(regex);
console.log(result);

実行結果

6

失敗パターン

test.js
let sample = 'HELLO world';
let regex = /[0-9]/g;
let result = sample.search(regex);
console.log(result);

実行結果

-1

対象のStringオブジェクトの中に数字は存在しなかったので、
-1を返しました。

RegExp.prototype.test()メソッド

対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
こちらは結果を真偽値で返します。

使い方

test.js
let sample = 'hello world';
let regex = /[a-z]/g;
let result = regex.test(sample);
console.log(result);

実行結果

true

String.prototype.match()メソッド

対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
こちらは結果を配列で返し、
マッチが見つからない場合はnullをかえします。

使い方

test.js
let sample = 'HELLO world';
let regex = /[a-z]/g;
let result = sample.match(regex);
console.log(result);

実行結果

["w", "o", "r", "l", "d"]

指定する正規表現のgフラグの有無によって取れる内容が変わります。
今度はgフラグの無い場合を試してみます。

test.js
let sample = 'HELLO world';
let regex = /[a-z]/;
let result = sample.match(regex);
console.log(result);

実行結果

["w", index: 6, input: "HELLO world"]

このgフラグが存在しない場合は、
RegExp.prototype.exec()メソッドと同じ結果を返します。
ただ、一番最初に見つけたマッチだけが欲しい場合、
代わりにRegExp.prototype.exec()メソッドを使ったほうが良さそうです。

RegExp.prototype.exec()

match()と同じく、対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
こちらは結果を配列で返し、
マッチが見つからない場合はnullをかえします。

使い方

test.js
let sample = 'HELLO world';
let regex = /[a-z]/;
let result = regex.exec(sample);
console.log(result);

実行結果

["w", index: 6, input: "HELLO world"]

まとめ

メソッドによって用途が異なる為、
使い分けが大切ですね。
何かあればご意見、ご指摘頂けますと嬉しいです。

参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/includes

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

JavaScriptで特定の文字列が含まれているかのメソッドまとめ

JavaScriptでは特定の値が含まれているか調べる方法が複数あります。
用途によって使い分けられるのでまとめてみました。

String.prototype.indexOf()メソッド

呼び出すStringオブジェクトを引数の値で検索を行い、
指定された値が最初に現れた位置を返します。

使い方

test.js
let sample = 'abcdefg';
let result = sample.indexOf('c');
console.log(result);
2

大文字と小文字は区別し、
値が見つからない場合は-1を返します。
なので…

test.js
let sample = 'ABCDEFG';
let result = sample.indexOf('c');
console.log(result);
-1

検索し始める最初の位置を指定する事も出来ます。

let sample = 'abcdeabcde';
let result = sample.indexOf('cd', 5);
console.log(result);
7

この場合、検索する位置を指定しなければ2が表示されますが、
検索する最初の位置を5番目に指定したので、
次のcdが当てはまる7を取得することが出来ました。

String.prototype.includes()メソッド

選択した文字列を対象の文字列の中に見つけられるかどうかを判断し、
真偽値を返します。
見つかった場合はtrue、見つからなければfalseとなります。

使い方

test.js
let sample = 'abcdefg';
let result = sample.includes('c');
console.log(result);
true

大文字と小文字も区別します。

test.js
let sample = 'ABCDEFG';
let result = sample.includes('c');
console.log(result);
false

検索し始める最初の位置を指定する事も出来ます。

test.js
let sample = 'hello world';
let result = sample.includes('hello', 2);
console.log(result);
false

この場合、検索する位置を指定しなければ文字列を見つけられるのでtrueが表示されます。
しかし検索する最初の位置を2番目からに指定したので、
helloが見つからずにfalseを返しました。

String.prototype.search()メソッド

対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
成功した場合はマッチした箇所の位置を、
失敗の場合は-1を返します。

使い方

test.js
let sample = 'HELLO world';
let regex = /[a-z]/g;
let result = sample.search(regex);
console.log(result);
6

失敗パターン

test.js
let sample = 'HELLO world';
let regex = /[0-9]/g;
let result = sample.search(regex);
console.log(result);
-1

対象のStringオブジェクトの中に数字は存在しなかったので、
-1を返しました。

RegExp.prototype.test()メソッド

対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
こちらは結果を真偽値で返します。

使い方

test.js
let sample = 'hello world';
let regex = /[a-z]/g;
let result = regex.test(sample);
console.log(result);
true

String.prototype.match()メソッド

対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
こちらは結果を配列で返し、
マッチが見つからない場合はnullをかえします。

使い方

test.js
let sample = 'HELLO world';
let regex = /[a-z]/g;
let result = sample.match(regex);
console.log(result);
["w", "o", "r", "l", "d"]

指定する正規表現のgフラグの有無によって取れる内容が変わります。
今度はgフラグを無い場合を試してみます。

test.js
let sample = 'HELLO world';
let regex = /[a-z]/;
let result = sample.match(regex);
console.log(result);
["w", index: 6, input: "HELLO world"]

このgフラグが存在しない場合は、
RegExp.prototype.exec()メソッドと同じ結果を返します。
ただ、一番最初に見つけたマッチだけが欲しい場合、
代わりにRegExp.prototype.exec()メソッドを使ったほうが良さそうです。

RegExp.prototype.exec()

match()と同じく、対象のStringオブジェクトが正規表現でマッチできるかどうかを判定します。
こちらは結果を配列で返し、
マッチが見つからない場合はnullをかえします。

使い方

test.js
let sample = 'HELLO world';
let regex = /[a-z]/;
let result = regex.exec(sample);
console.log(result);
["w", index: 6, input: "HELLO world"]

参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/includes

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

KEN_ALL.CSV加工マクロ(EmEditor)

機能

KEN_ALL.CSVから、郵便番号と住所(漢字)を抽出したファイルを作成する
出力ファイルはUTF–8、改行コードは未変換(元ファイルのまま)です。

使い方

  1. 日本郵便の郵便番号データダウンロードページで公開されている、"KEN_ALL.CSV"ファイルを開き、EmEditorで下記マクロを実行する。
  2. KEN_ALL.CSVと同じディレクトリにKEN_ALL_cnv.CSVファイルが作成される

マクロ

郵便番号データ変換.jsee
var startTime = new Date();
/*
□マクロ名:郵便番号データ変換.jsee
□バージョン:1.10
■開くファイル
 郵便番号データダウンロードから取得した住所の郵便番号(CSV形式)
 http://www.post.japanpost.jp/zipcode/download.html
■作成ファイル
 9.町域名の内容を簡略化したファイル(ファイル名=拡張子の前に"_cnv"を追加)
■手順(例)
 KEN_ALL.CSVを開く
 郵便番号データ変換.jseeを実行
 同一ディレクトリに変換後のファイル(UTF-8)が作成される
*/

Redraw = false;//ウィンドウの再描画:しない

document.CellMode = false;//CSV セル選択モード解除

columnN = document.GetColumns();//CSV モードで列の数を取得
if(columnN != 15){
    alert( "列数エラー。列数は、" + columnN + "です。" );
    Quit();
}

//■使用しない列を削除
document.selection.StartOfDocument()//カーソル位置を文頭へ移動
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(4490);//郵便番号(7桁)
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(4490);//都道府県名
editor.ExecuteCommandByID(4490);//市区町村名
editor.ExecuteCommandByID(4490);//町域名
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1
editor.ExecuteCommandByID(3962);//列の削除 1

//■保存するファイル名を設定
strPath = document.Path;//文書のパスを取得
aName = document.Name.split(".");//文書のファイル名を取得
sCnvPath_Name = strPath + "\\" + aName[0] + "_cnv." + aName[1];

//■UTF8で一旦保存
document.Encoding = eeEncodingUTF8;//保存時のエンコード:UTF-8
document.UnicodeSignature = false;//保存時に(BOM)を付けない
document.Save( sCnvPath_Name );//文書を保存
document.close();//文書を閉じる
editor.ExecuteCommandByID(4583);//最も最近閉じたファイルを開く
document.CellMode = false;//CSV セル選択モード解除

//■町域の複数行に渡るかっこ書きを削除
//郵便番号の列を取得
document.selection.SetActivePoint( eePosCellLogical, 1, 1 );
editor.ExecuteCommandByID(4461);
sTmp = "\r\n" + document.selection.Text;
aPostalCode = new Array();
aPostalCode = sTmp.split("\r\n");

//町域の列を取得
document.selection.SetActivePoint( eePosCellLogical, 4, 1 );
editor.ExecuteCommandByID(4461);
sTmp = "\r\n" + document.selection.Text;
aTyou = new Array();
aTyou = sTmp.split("\r\n");
sTmp = null;

//最終行から開始行へ向かって処理を行う
fOkikae = false;//フラグ置き換えモード:終わり
re1 = new RegExp("^\"[^(]*)\"$");//")"のみを検索
re2 = new RegExp("(^\".*)([^)]*(\")$");//"("のみを検索
for(i = aTyou.length - 2; i >= 1; i--){
    if( ( aPostalCode[i] == aPostalCode[i-1] ) || ( fOkikae == 1)){//郵便番号が同じor置き換えモードの場合
        document.selection.SetActivePoint( eePosCellLogical, 4, i );
        if( fOkikae ){
            if(aTyou[i].match(re2)){//"("のみを検索
                document.SetCell( i , 4, RegExp.$1 + RegExp.$2, eeDontQuote );
                fOkikae = false;//フラグ置き換えモード:終わり
            }else{
                document.selection.SelectLine();
                document.selection.Delete(1);//行削除
            }
        } else {
            if(aTyou[i].match(re1)){//")"のみを検索
                document.selection.SelectLine();
                document.selection.Delete(1);//行削除
                fOkikae = true;//フラグ置き換えモード:開始
            }
        }
    }
}

//■町域の文字列を置き換え
document.selection.SetActivePoint( eePosCellLogical, 4, 1 );//カーソル位置を設定
editor.ExecuteCommandByID(4461);//列を選択
document.selection.Replace("以下に掲載がない場合","",eeFindReplaceSelOnly | eeReplaceAll,0);
document.selection.Replace("(\".*)((その他|丁目|番地|次のビルを除く|地階・階層不明|.*[、~].*|.*以上|.*以下))(\")","\\1\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);
//★名駅ミッドランドスクエア(高層棟)(地階・階層不明)
document.selection.Replace("(\".*)(高層棟)(.*\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);
//★三田市の次に番地がくる場合
document.selection.Replace("\".*の次に番地がくる場合\"","\"\"",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);
//★土樋(1丁目「11を除く」) => 土樋(1丁目)
//★切畑(長尾山「その他」) => 切畑(長尾山)
document.selection.Replace("(\".*(.*)「(.*を除く|その他)」()\")","\\1\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);
//★音江町(国見その他) => 音江町(国見)
document.selection.Replace("(\".*(.*)その他()\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);
//★花田町官有地(無番地を除く)
//★芦田町福田(376-10を除く)
//★津島町下畑地(乙を除く)
document.selection.Replace("(\".*)(.*を除く)(\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//■町域のかっこ"("および")"を削除
document.selection.Replace("(\".*)((.*))(\")","\\1\\2\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

editor.ExecuteCommandByID(4099);//ファイルを上書き保存
document.close();//文書を閉じる
var endTime = new Date();

alert((endTime - startTime)/1000 + "sec");
Redraw = true;//ウィンドウの再描画:する
Quit();

補足

  • UTF-8への変換(内部処理は既にUnicodeですので、正確にはUTF-8での書き出し)は処理の最後にやっても良いです(上記サンプルでは、一旦UTF-8で保存していますが、サンプルと言うことで...)
  • 47行目は、元のソース上は"\\"です(¥マーク2つを二重引用符でくくっている)コードの色が変になっていますが...
  • EmEditorでのcsvの加工をしようと思い、その時の練習用に作成しました

参考

EmEditor マクロ ヘルプ チュートリアル

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

ゼロから始める生体認証webアプリケーション作成(1) node.jsチュートリアル

はじめに

初めてQiitaの記事を書くので、拙いところがあると思いますが見てくれると嬉しいです。
今回、webアプリケーションで生体認証を実装することを目標に、JavaScript等の必要な知識を勉強しながら実装します。

今回の目標

環境の構築、簡単なnode.jsの実装

1. node.jsのインストール

こちらの記事を参考にしました。
【Mac】node.jsインストール方法
Node.jsとは
Node.js を5分で大雑把に理解する

2. HTMLファイルの読み込み

実際にnode.jsをローカルホストで試してみます。
Node.jsサーバーの実装

server.js
//html,fsオブジェクトの生成
var http = require('http');
var fs = require('fs');

//リンクの作成
var html_paths = {index:'../html/index.html'};
var html_pages = {index:fs.readFileSync(html_paths.index,'utf-8')};

//サーバーリクエスト処理(何度も使うので関数化しました)
function res_write(res,target,text_type) {
  res.writeHead(200,{'Content-Type':text_type});
  res.write(target);
  res.end();
}
//サーバーの作成
var server = http.createServer(function(req,res){
  var target;
  switch (req.url) {
    case '/':
    case '/index':
      res_write(res,html_pages.index,'text/html');
      break;

    default:
      res.writeHead(404,{'Content-Type':'text/plain'});
      res.end('not fond');
      return;
  }

});

server.listen(1234);
console.log('Server has been started');
index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>sample</title>
</head>
<body>
  <h1>test.html</h1>
</body>
</html>

結果
スクリーンショット 2019-05-27 17.16.46.png

localhost:/1234に接続できました。

3. JavaScriptの読み込み

簡単なjavascriptファイルを作成

test.js
function test(){
  alert('test');
}
test();

javascriptのファイルを読み込めるようにserver.jsに追記します

server.js
var js_paths={test:'./test.js'};
var js_pages= {test:fs.readFileSync(js_paths.test,'utf-8')};
server.js
case '/test.js':
      res_write(res,js_pages.cam,'text/plain'); break;

index.htmlにjavascriptファイルを読み込む記述をします。

index.html
<script type="text/javascript" src="./test.js"></script>

結果 javascriptのファイルをHTML上に読み込むことができました。
スクリーンショット 2019-05-27 17.33.42.png

まとめ

  • node.jsの環境を構築することができた。
  • node.jsの簡単な実装。

次回は、JavaScriptで画像を加工する方法を実装します。
感想、アドバイス等あればお願いします。

おまけ

node.jsの参考書(amazonへのリンクです)
JavaScriptエンジニアのためのNode.js入門

kindle unlimitedなら無料、kindle版も350円と安くてオススメです。

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

ゼロから始める生体認証webアプリケーション作成(1)

はじめに

初めてQiitaの記事を書くので、拙いところがあると思いますが見てくれると嬉しいです。
今回、webアプリケーションで生体認証を実装することを目標に、JavaScript等の必要な知識を勉強しながら実装します。

今回の目標

環境の構築、簡単なnode.jsの実装

1. node.jsのインストール

こちらの記事を参考にしました。
【Mac】node.jsインストール方法
Node.jsとは
Node.js を5分で大雑把に理解する

2. HTMLファイルの読み込み

実際にnode.jsをローカルホストで試してみます。
Node.jsサーバーの実装

server.js
//html,fsオブジェクトの生成
var http = require('http');
var fs = require('fs');

//リンクの作成
var html_paths = {index:'../html/index.html'};
var html_pages = {index:fs.readFileSync(html_paths.index,'utf-8')};

//サーバーリクエスト処理(何度も使うので関数化しました)
function res_write(res,target,text_type) {
  res.writeHead(200,{'Content-Type':text_type});
  res.write(target);
  res.end();
}
//サーバーの作成
var server = http.createServer(function(req,res){
  var target;
  switch (req.url) {
    case '/':
    case '/index':
      res_write(res,html_pages.index,'text/html');
      break;

    default:
      res.writeHead(404,{'Content-Type':'text/plain'});
      res.end('not fond');
      return;
  }

});

server.listen(1234);
console.log('Server has been started');
index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>sample</title>
</head>
<body>
  <h1>test.html</h1>
</body>
</html>

結果
スクリーンショット 2019-05-27 17.16.46.png

localhost:/1234に接続できました。

3. JavaScriptの読み込み

簡単なjavascriptファイルを作成

test.js
function test(){
  alert('test');
}
test();

javascriptのファイルを読み込めるようにserver.jsに追記します

server.js
var js_paths={test:'./test.js'};
var js_pages= {test:fs.readFileSync(js_paths.test,'utf-8')};
server.js
case '/test.js':
      res_write(res,js_pages.cam,'text/plain'); break;

index.htmlにjavascriptファイルを読み込む記述をします。

index.html
<script type="text/javascript" src="./test.js"></script>

結果 javascriptのファイルをHTML上に読み込むことができました。
スクリーンショット 2019-05-27 17.33.42.png

まとめ

  • node.jsの環境を構築することができた。
  • node.jsの簡単な実装。

次回は、JavaScriptで画像を加工する方法を実装します。
感想、アドバイス等あればお願いします。

おまけ

node.jsの参考書(amazonへのリンクです)
JavaScriptエンジニアのためのNode.js入門

kindle unlimitedなら無料、kindle版も350円と安くてオススメです。

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

D3.jsのプラグイン「venn.js」を使ってベン図を描く

はじめに

ユーザーのサイト利用に関する併用状況をWebで可視化する必要があり、
(3つまでであれば)ベン図が直感的でわかりやすいと思ったのですが
意外と簡単にベン図を作成するJavaScriptライブラリが見つからなかった中
D3.jsのプラグイン「venn.js」が使いやすかったので備忘録的に残します。

venn.jsのGitHubこちら
venn.js

ベン図

examplesフォルダにサンプルが豊富にありますので、
そこを見ればだいたいやりたいことは載っています。
いくつか実際に使ってみたサンプルについて書いていきます。

基本

examples/simple.htmlを少し手直ししています。
・日本語でも動きます。
・D3.jsのバージョンをv4→v5に上げても動きます。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>venn.js sample</title>
<style>
body {
    font-family: "Hiragino Kaku Gothic Pro W3","Hiragino Kaku Gothic ProN",Meiryo,sans-serif;
    font-size: 14px;
}
</style>
</head>

<body>
    <div id="venn"></div>
</body>

<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="../venn.js"></script>
<script>
// データセットの用意
var sets = [ {sets: ['サイトA'], size: 12},
             {sets: ['サイトB'], size: 12},
             {sets: ['サイトA','サイトB'], size: 2}];

// #vennに描画
var chart = venn.VennDiagram();
d3.select("#venn").datum(sets).call(chart);
</script>
</html>

chart1.png

色の変更

examples/styled.htmlを参考にしています。
以降はscript内のみ書きます。
・色を指定することができます。
・丸の色及びテキストの色両方変更することもできます(もちろん片方だけも可能)

// データセットの用意
var sets = [ {sets: ['サイトA'], size: 12},
             {sets: ['サイトB'], size: 12},
             {sets: ['サイトA','サイトB'], size: 2}];

// #vennに描画
var chart = venn.VennDiagram();
d3.select("#venn").datum(sets).call(chart);

// 色の変更
var colors = ['red', 'blue']; // 1つ目を赤、2つ目を青に
d3.selectAll("#venn .venn-circle path")
    .style("fill", function(d,i) { return colors[i]})
d3.selectAll("#venn .venn-circle text")
    .style("fill", function(d,i) { return colors[i]})

chart2.png

順番の変更

examples/orientation_order.htmlを参考にしています。
・描画の順番を変えることができます。

// データセットの用意
var sets = [ {sets: ['サイトA'], size: 12},
             {sets: ['サイトB'], size: 12},
             {sets: ['サイトA','サイトB'], size: 2}];

// #vennに描画
var chart = venn.VennDiagram();

// 順番の変更
var customOrdering = {'サイトB': 1, 'サイトA': 2}; // 1つ目サイトBに、2つ目をサイトAに
chart.orientationOrder(function (a, b) { return customOrdering[a.setid] - customOrdering[b.setid]});

d3.select("#venn").datum(sets).call(chart);

chart3.png

ツールチップの表示

examples/intersection_tooltip.htmlを参考にしています。
ツールチップのstyleを定義する必要があるため、ここはHTMLすべて書きます。
・ツールチップで数値(ここではユーザー数)を表示することができます。
・マウス操作に合わせて色の透明度などを変化させることができます。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>venn.js sample</title>
<style>
body {
    font-family: "Hiragino Kaku Gothic Pro W3","Hiragino Kaku Gothic ProN",Meiryo,sans-serif;
    font-size: 14px;
}
</style>
</head>

<body>
    <div id="venn"></div>
</body>

<style>
.venntooltip {
  position: absolute;
  text-align: center;
  width: 128px;
  height: 22px;
  background: #333;
  color: #ddd;
  padding: 2px;
  border: 0px;
  border-radius: 8px;
  opacity: 0;
}
</style>

<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="../venn.js"></script>
<script>
// データセットの用意
var sets = [ {sets: ['サイトA'], size: 12},
             {sets: ['サイトB'], size: 12},
             {sets: ['サイトA','サイトB'], size: 2}];

// #vennに描画
var chart = venn.VennDiagram();
var div = d3.select("#venn")
div.datum(sets).call(chart);

var tooltip = d3.select("body").append("div")
    .attr("class", "venntooltip");

div.selectAll("path")
    .style("stroke-opacity", 0)
    .style("stroke", "#fff")
    .style("stroke-width", 3)

div.selectAll("g")
    .on("mouseover", function(d, i) {
        // sort all the areas relative to the current item
        venn.sortAreas(div, d);

        // Display a tooltip with the current size
        tooltip.transition().duration(400).style("opacity", .9);
        tooltip.text(d.size + " ユーザー");

        // highlight the current path
        var selection = d3.select(this).transition("tooltip").duration(400);
        selection.select("path")
            .style("fill-opacity", d.sets.length == 1 ? .4 : .1)
            .style("stroke-opacity", 1);
    })

    .on("mousemove", function() {
        tooltip.style("left", (d3.event.pageX) + "px")
               .style("top", (d3.event.pageY - 28) + "px");
    })

    .on("mouseout", function(d, i) {
        tooltip.transition().duration(400).style("opacity", 0);
        var selection = d3.select(this).transition("tooltip").duration(400);
        selection.select("path")
            .style("fill-opacity", d.sets.length == 1 ? .25 : .0)
            .style("stroke-opacity", 0);
    });
</script>
</html>

chart4.png

おわりに

venn.jsの説明というより、ほとんどD3.jsの説明になってしまいましたが。。。
見方を変えればvenn.jsでSVGに描画してしまえば、あとはD3.jsの世界なので
スタイルの変更などは比較的自由に行えます。
難易度は上がりますが、レイアウトを動的に変えることなどもできるので、
ビジュアライズの世界が広がりますね。

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

DOMだけでテーブルを作成する方法

こんにちは。
プログラミング初心者のオジサンです。

JavaScriptでミニアプリを作成しており、そのアプリでテーブルを動的に作成する必要がありまして、テストがてら、DOMだけでテーブルを作成するコードを作ってみました。

1.HTML(index.html)

テーブルはDOMで作るため、bodyタグの中はほぼ空です。
JavaScriptを読み込むためのscriptタグしかありません。
DOMを勉強するときは、だいたいこの内容でやってます。

<!DOCTYPE html> 
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>表をjsで作る</title>
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>
  <script src="js/main.js"></script>
</body>
</html>

2.CSS(css/styles.css)

書式は最低限、整えてます。
テーブルはdivタグでflexboxちっくに。
…まだ、flexbosをよく理解できていません(;^_^A

/* 表の書式設定 */
.table {
    text-align: center;
}

/* 行の書式設定 */
.row {
    display: flex;
}

/* 列の書式設定 */
.cell {
    margin: auto;
}

3.JavaScript(js/main.js)

今作っているミニアプリは、テーブルの列数は固定ですが、行数はどんどん追加される仕様となっています。
そのため、本来は行数や列数を選ばさせ、表を動的に作る内容としたかったのですが、ボリュームが増えそうなので今回は4×4の固定された表とさせていただきました。

コードの流れですが、createElement()でdivタグを生成し、その後にクラスやテキストを入れ、最後にappendChild()でブラウザに表示させるようにしています。
行はdlタグ、列はdtタグで作成しています。
こちらもcreateElement()でタグを生成し、その後クラスやテキストを入れ、最後にappendChild()でブラウザに表示させるようにしています。

'use strict';

{
    // divタグで表を作る
    const table = document.createElement('div');
    table.classList.add('table');
    table.textContent = '表';
    document.body.appendChild(table);

    // dlタグで行を作る(今回は4つ)
    for (let i = 0; i < 4; i++) {
        const dl = document.createElement('dl');
        dl.classList.add('row');
        table.appendChild(dl); 
        // dtタグで列を作る(今回は4つ)
        for (let j = 0; j < 4; j++) {
            const dt = document.createElement('dt');
            dt.classList.add('cell')
            dt.textContent = `${i}${j}列`;
            dl.appendChild(dt); 
        }
    }
}

最後に

Qiita初投稿になります!
今後もどんどんサンプルコードを書いていきたいと思いますので、よろしくお願いします。

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

vue.js 現在のパスに応じて表示したり隠したり

make ページでは メニュー要素を隠したい。
そんなときは以下のように書く。

vue.blade.php
    <el-footer id="footer" class="layout-footer" v-show="$route.path.indexOf('make') < 0">
こんな塩梅
    </el-footer>

これで現在のページが make にマッチしない時のみ メニューが表示されるようになる。

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

JSを勉強したい人向け!ゲーム制作ツール一覧

 まったくのプログラミング初心者(Progateを終えたぐらい)が、JSを勉強するため、ゲームをつくりたいが、ゲームのために一から全部プログラムをかくのはめんどくさい。
 そんな人(自分)のために、JSで書かれたツールを紹介する記事です!
 ここで紹介するふたつは、JSでいろいろいじれるものです。

RPGツクールMV(有料)

 みんな知ってる気がするRPGツクールシリーズの最新作。ただ、有志の人が公開しているJSで書かれたプラグイン(追加機能のこと)が多いため、自分で書かなくていいやとなりがち。
 個人的には、ツクールにデフォルトでついている、条件分岐などを使い慣れてからJSでいじることをおすすめします。最初はJSでなにかしようとしても何もできないかと思うので……。
 公式サイトはこちら:https://tkool.jp/mv/

 ティラノスクリプト(無料)

 吉里吉里というFateが作られたゲーム制作ツールに、仕様を似せたもの。ノベルゲームが作れる。もちろん、JSをつかえばノベルゲーム以外のものもつくれる。
 個人的には、こちらがおすすめです。
 公式サイトはこちら:https://tyrano.jp/

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

Javascriptで電卓を作ろう【レベル1】 第2回 四則演算をやってみよう。

スタイルが出来上がったので、次は計算をしていきたいと思います。
第一回のHTMLとCSSを作る記事は、こちら

目次

第ー回

  • HTMLのマークアップ
  • CSS

第二回(この記事はここ)

  • Javascriptで四則演算
  • 6)打った数字をコンソールに出力させよう。
  • 7)打った数字と記号を変数に格納しよう。
  • 8)計算結果を出力させよう。
  • 8-1)「=」を押した時の処理
  • 8-2) 計算結果と計算途中を、計算結果の画面に表示させよう。

第三回以降以下の不具合の改善

  • 最初に「+ ÷ - = .」が押せてしまう。
  • 最初に0を連続で押せてしまう。
  • 一度間違えると、全てやり直し(BSボタンを使えるようにしよう)
  • 一度計算をやると、次の計算をするときに、ブラウザを読み込みなおさないといけない(Cボタンを使えるようにしよう)
  • 12.12.や12..12のように小数点を正しくなく入力できてしまう。
  • 割り算等で小数点以下が異様に長い。
  • 記号ボタン(+×÷-)を連続で押せてしまい、計算ができない。(連続で押した場合は、最後に押した記号ボタンが採用されるようにしたい。
  • 電卓と同じように「.4」と入力したら0.4としたい。

前回のHTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>電卓</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class = "container">
<!--      計算結果を出力する場所-->
      <div id = "output_total" class="output ">0</div>
<!--      計算過程を出力する -->
      <div id = "output_sub" class="output active">0</div>
<!--      ボタンを配置する場所-->
      <div class = 'input'>
          <section class = 'row'>
            <div id = "clear">C</div>
            <div class = "num_bth"></div>
            <div class = "num_bth" id="bs">BS</div>       
            <div class = "num_bth " data-index-id= '/'>÷</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 9 >9</div>
            <div class = "num_bth " data-index-id = 8 >8</div>
            <div class = "num_bth " data-index-id = 7 >7</div>
            <div class = "num_bth " data-index-id = '*'>×</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 6 >6</div>
            <div class = "num_bth " data-index-id = 5 >5</div>
            <div class = "num_bth " data-index-id = 4 >4</div>
            <div class = "num_bth " data-index-id = '-'></div>     
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 3 >3</div>
            <div class = "num_bth " data-index-id = 2 >2</div>
            <div class = "num_bth " data-index-id = 1 >1</div>
            <div class = "num_bth " data-index-id = '+'>+</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth" data-index-id = 00 >00</div>
            <div class = "num_bth" data-index-id = 0 >0</div>
            <div class = "num_bth" data-index-id = . >.</div>
            <div id = 'equal_btn'>=</div>       
          </section>        
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

Javascript処理

6)打った数字をコンソールに出力させよう。
例1)【ボツ】 一応、打った番号は出力されるが、同じコードの繰り返しになる書き方は却下。

'use strict'
{
  const num_bth = document.getElementsByClassName('num_bth');
  num_bth[3].addEventListener('click', () => {
    console.log(num_bth[3].textContent);
  });//9をクリックすると9が出力

  num_bth[4].addEventListener('click', () => {
    console.log(num_bth[4].textContent);
  }) ///8をクリックすると9が出力

  //以下続く

}

これでもコンソールにクリックした数字は出力できますが、こんな書き方をしてたら、先生に怒られてしまいそうです。

例2)【ボツ】同じコードの繰り返しをやめるため、getElementsByClassNameとforを使ってみる

  const num_bth = document.getElementsByClassName('num_bth'); 
    for(let i = 0; i < num_bth.length ; i++) {
      num_bth[i].addEventListener('click', () => {
        console.log(num_bth[i].textContent);
      }) //click
    }//for

これで同じコードを繰り返して書かなくてもクリックしたキーの値をコンソールに出力できました。

例3)【ボツ】もう少し短く書くため、querySelectorAllとforEachを使う

  const num_bth = document.querySelectorAll('.num_bth');
  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.textContent);
    }) //click
  })//forEach
//123456890+-×÷が出力される

数字はうまく出力されますが、この方法だと「÷」を押すとが「÷」出力され、「×」を押すと「×」が出力される。。「/」や「*」が出力されないと計算できない。

ちなみにforEachは配列に対して行うので、複数あるものなら使えるわけではなかった。以下だとエラーになります。

  const num_bth = document.getElementsByClassName('num_bth');
  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.textContent);
    }) //click
  })//forEach
×以下のエラメッセージが表示される。
Uncaught TypeError: num_bth.forEach is not a function

例4)【採用】
カスタムdata属性を使う。

忘れてたら、
詳解JavaScript DOM編 #06 カスタムデータ属性を扱ってみよう 参照

  const num_bth = document.querySelectorAll('.num_bth');
  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.dataset.indexId)
    }) //click   
  })//forEach 
//123456890+-*/が出力される

7)打った数字と記号を変数に格納しよう。

  const num_bth = document.querySelectorAll('.num_bth');
  let total = 0;//計算式を表す変数 
  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.dataset.indexId)
      total += index.dataset.indexId;  
      console.log(total);  
    }) //click   
  })//forEach

12+3をクリックするとコンソールにこの以下のように出力される。
image.png

【失敗例】

  const num_bth = document.querySelectorAll('.num_bth');
  let total;//計算式を表す変数 

  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.dataset.indexId)
        total += index.dataset.indexId;     
      console.log(total);  
    }) //click   
  })//forEach

ボタンを押す前は、変数には数字がないため、undefinedとなってしまう。
image.png

  • 8)計算結果を出力させよう。 【失敗例】
'use strict'
{
  const num_bth = document.querySelectorAll('.num_bth');
  let total = 0;//計算式を表す変数 

//  //数字や記号のボタンを押した時
  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.dataset.indexId)
        total += index.dataset.indexId;     
      console.log(total);  
    }) //click   
  })//forEach

  //イコールを押した時
  const equal_btn = document.getElementById('equal_btn');
  equal_btn.addEventListener('click',() =>{
    console.log(eval(total));
  });

}

image.png

eval() は文字列として表された JavaScript コードを式として評価する関数。
0が付いていると計算ができない。
最初にクリックした時と、二番目の数字がクリックされた時で処理を変える。
【成功例】

'use strict'
{
  const num_bth = document.querySelectorAll('.num_bth');
  let total = 0;//計算式を表す変数 

//  //数字や記号のボタンを押した時
  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.dataset.indexId)
      if(total ===0) {
        total = index.dataset.indexId;  
      }else{
        total += index.dataset.indexId;
      }      
      console.log(total);  
    }) //click   
  })//forEach

  //イコールを押した時
  const equal_btn = document.getElementById('equal_btn');
  equal_btn.addEventListener('click',() =>{
    console.log(eval(total));
  });

}

12+3=15がコンソールに出力できた。
image.png

8-2) 計算結果と計算途中を、電卓の画面に表示させよう。

'use strict'
{
  const num_bth = document.querySelectorAll('.num_bth');
  let output_sub = document.getElementById('output_sub');//計算結果を表示する場所
  const output_total = document.getElementById('output_total');//計算過程を表示する場所
  let total = 0;//計算式を表す変数 

//  //数字や記号のボタンを押した時
  num_bth.forEach(index => {
    index.addEventListener('click', () => {
      console.log(index.dataset.indexId)
      if(total ===0) {
        total = index.dataset.indexId;  
      }else{
        total += index.dataset.indexId;
      }      
      output_sub.textContent = total;
    }) //click   
  })//forEach

  //イコールを押した時
  const equal_btn = document.getElementById('equal_btn');
  equal_btn.addEventListener('click',() =>{
    console.log(eval(total));
    output_total.textContent = eval(total);
  });

}

image.png
計算結果が出力できた。

現状の問題点

レベル1はとりあえず計算結果を出力できるまで、ということで、ここまで。

レベル2では、以下の現状の問題点を一つずつ直していきたいと思います。

  • 最初に「+ ÷ - = .」が押せてしまう。
  • 最初に0を連続で押せてしまう。
  • 一度間違えると、全てやり直し(BSボタンを使えるようにしよう)
  • 一度計算をやると、次の計算をするときに、ブラウザを読み込みなおさないといけない(Cボタンを使えるようにしよう)
  • 12.12.や12..12のように小数点を正しくなく入力できてしまう。
  • 割り算等で小数点以下が異様に長い。
  • 記号ボタン(+×÷-)を連続で押せてしまい、計算ができない。(連続で押した場合は、最後に押した記号ボタンが採用されるようにしたい。
  • 電卓と同じように「.4」と入力したら0.4としたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google reCAPTCHA v2 めも

ファ・・・!
chatworkなど、ログイン時などによく見るこの人↓
スクリーンショット 2019-05-28 0.30.21.png

気軽に使えるんだ!
今更知ったよ!

ちょうどお問い合わせページで使う機会があったのでメモメモ

今回はv2を使用

<form>

    <label>お名前</label><input type="text" name="onamae" value="" placeholder="お名前" >

    <!-- ▼reCAPTCHAのチェックボックスが表示される -->
    <div id="google_rechapcha"></div>

    <button id="send" type="submit" disabled="disabled">送信</button>
</form>

<script>
var onloadCallback = function() {
                     // IDがgoogle_rechapchaにレンダリングする
    grecaptcha.render('google_rechapcha', {
      'sitekey' : '*****************************', // 取得したkeyをいれる
      'callback' : verifyCallback, //コールバック関数を指定
    });
  };

  // コールバック関数を定義
  function verifyCallback(){
      // submitボタンのdisableをfalseにする。
      document.getElementById('send').disabled = false;
  }
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AJAXでGitHubのAPIからユーザー情報を取得

概要

ボタンを押すとAPIを通じて、GitHubのユーザーが一覧表示されます。

環境

MacOS Mojave
xamppをインストール&起動している前提で記述します。

ディレクトリ構成

htdocs
├── ajax.html

HTMLの雛形

下記ファイルをhtdocsへ配置します。

ajax.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Ajax - External API</title>
</head>
<body>
    <!-- buttonタグの設置 -->
    <button id="button">GitHubのユーザー読み込み</button>
    <br><br>
    <h1>GitHub Users</h1>
    <div id="users"></div>
</body>
</html>

APIの取得

認証等が特に必要ない下記URLから取得します。
https://api.github.com/users

Ajaxの記述

URLからAPIを取得する

ajax.html
<body>
    <button id="button">GitHubのユーザー読み込み</button>
    <br><br>
    <h1>GitHub Users</h1>
    <div id="users"></div>

    <script>
        // Ajax処理記述
        document.getElementById('button').addEventListener('click', loadUsers);

        // APIを読み込む
        function loadUsers(){
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'https://api.github.com/users', true);

            xhr.onload = function(){
                if(this.status == 200){
                    // JSONを変数に格納できる形に変更する
                    var users = JSON.parse(this.responseText);
                }
            }

            xhr.send();
        }
    </script>

取得した情報をHTMLに表示させる

ajax.html
    <script>
        // Ajax処理記述
        document.getElementById('button').addEventListener('click', loadUsers);

        // APIを読み込む
        function loadUsers(){
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'https://api.github.com/users', true);

            xhr.onload = function(){
                if(this.status == 200){
                    // JSONを変数に格納できる形に変更する
                    var users = JSON.parse(this.responseText);

                    // for文でuser情報を一つずつ抜き出す
                    var output = '';
                    for(var i in users){
                        output +=
                        '<div class="user">' +
                        '<img src="'+users[i].avatar_url+'" width="70" height="70">' +
                        '<ul>' +
                        '<li>ID: '+users[i].id+'</li>' +
                        '<li>ID: '+users[i].login+'</li>' +
                        '</ul>' +
                        '</div>';
                    }
                    // HTMLに抜き出した情報を表示させる
                    document.getElementById('users').innerHTML = output;
                }
            }

            xhr.send();
        }
    </script>

この状態で、localhost/ajax.htmlにアクセス→ボタンを押すと、ユーザ一覧が表示されます。

見た目を良くする

見た目が寂しいので、cssを記述します。

ajax.html
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Ajax - External API</title>
    <!-- cssの記述 -->
    <style>
        .user{
            display: flex;
            background: #f4f4f4;
            padding: 10px;
            margin-bottom: 10px;
        }

        .user ul{
            list-style: none;
        }
    </style>

完成

最終的なコードは下記のとおり

ajax.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Ajax - External API</title>
    <!-- cssの記述 -->
    <style>
        .user{
            display: flex;
            background: #f4f4f4;
            padding: 10px;
            margin-bottom: 10px;
        }

        .user ul{
            list-style: none;
        }
    </style>
</head>
<body>
    <button id="button">GitHubのユーザー読み込み</button>
    <br><br>
    <h1>GitHub Users</h1>
    <div id="users"></div>

    <script>
        // Ajax処理記述
        document.getElementById('button').addEventListener('click', loadUsers);

        // APIを読み込む
        function loadUsers(){
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'https://api.github.com/users', true);

            xhr.onload = function(){
                if(this.status == 200){
                    // JSONを変数に格納できる形に変更する
                    var users = JSON.parse(this.responseText);

                    // for文でuser情報を一つずつ抜き出す
                    var output = '';
                    for(var i in users){
                        output +=
                        '<div class="user">' +
                        '<img src="'+users[i].avatar_url+'" width="70" height="70">' +
                        '<ul>' +
                        '<li>ID: '+users[i].id+'</li>' +
                        '<li>ID: '+users[i].login+'</li>' +
                        '</ul>' +
                        '</div>';
                    }
                    // HTMLに抜き出した情報を表示させる
                    document.getElementById('users').innerHTML = output;
                }
            }

            xhr.send();
        }
    </script>
</body>
</html>

参照元

AJAX Crash Course (Vanilla JavaScript)

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

webpackを利用するkintoneカスタマイズ開発の流れ

webpackを利用するkintoneプラグイン開発の流れ」でも触れていますが、kintone界隈の開発手法・ツールの利用についても最近色々とツール拡充がなされ、いわゆるモダンな開発方法というのも取り込みやすくなってきました。

kintoneのカスタマイズ開発で既に webpack を利用されている方も多いと思いますが、今回はwebpack@kintone/customize-uploaderを用いたカスタマイズ開発の流れを見ていきたいと思います。

大まかな流れ

  1. npm init -yでプロジェクトを開始する
  2. webpack関連で必要なモジュールをインストールする
  3. @kintone/customize-uploaderのインストールと設定を行う
  4. webpackと@kintone/customize-uploaderのwatchオプションを共存させる

詳しく見ていきましょう。

プロジェクトを開始する

カスタマイズ対象アプリが1つの簡単なケースをnpmのプロジェクトとして新しく始める流れを考えたいと思います。

プロジェクトフォルダ(今回の例はnew-project)を作成・移動して、npmプロジェクトを開始します。

mkdir new-project; cd new-project
npm init -y

webpack関連のモジュールをインストールする

webpack

まず、webpackwebpack-cliをインストールします。

npm install --save-dev webpack webpack-cli

ES6

webpackを利用するカスタマイズではES6使いたい意図が含まれることが多いので、ES6関連のパッケージをインストールします(ES6で書いてもkintone対応ブラウザのIE11にも対応できるように)。

npm install --save-dev babel-loader @babel/core @babel/preset-env
npm install --save @babel/polyfill

webpack.config.jsを準備する

webpack.config.js
const path = require('path');

module.exports = {
  mode: "development",

  entry: {
    "customize": "src/js/customize.js"
  },

  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist', 'js'),
  },

  resolve: {
    extensions: ['.js', '.json']
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};

この設定によって、src/js/customize.jsのエントリーからdist/js/customize.jsのバンドルファイルが生成されるイメージで、次のようなフォルダ・ファイル構成になります。

new-project
├── dist/js/customize.js
├── package-lock.json
├── package.json
├── src/js/customize.js
└── webpack.config.js

また、この時点でのpackage.jsonはこのような感じになります。scriptsに webpack の操作を追加しています。npm run buildwebpackのショートカットになる、といった具合ですね。
また、webpackはwatchオプションでエントリの変更検知が走りますし、バンドルのリードタイムが都度ビルドより速いことが知られています。

package.json
{
  "name": "new-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "develop": "npm run build -- --watch",
    "build": "webpack",
    "build:prod": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "babel-loader": "^8.0.6",
    "webpack": "^4.32.2",
    "webpack-cli": "^3.3.2"
  },
  "dependencies": {
    "@babel/polyfill": "^7.4.4"
  }
}

@kintone/customize-uploaderのインストールと設定を行う

バンドルファイルをkintoneに適用する方法としては、ローカルWebサーバーを立ち上げたり色々な方法が取られていると思いますが、今回は@kintone/customize-uploaderを利用する方法を示していきます。

@kintone/customize-uploaderのインストール

npm install --save-dev @kintone/customize-uploader

@kintone/customize-uploaderの初期設定

$(npm install)/kintone-customize-uploader init
# dest/cutomize-manifest.json が生成される

生成されたdest/cutomize-manifest.jsondesktop.jsに、バンドルファイルであるdist/js/customize.jsを指定します。

dest/cutomize-manifest.json
{
    "app": "599",
    "scope": "ALL",
    "desktop": {
        "js": [
            "dist/js/customize.js"
        ],
        "css": []
    },
    "mobile": {
        "js": []
    }
}

@kintone/customize-uploaderwatchオプションを持っていますので、package.jsonscriptsuploadを足して使いやすくしておきます。これで、dest/customize-manifest.jsonで指定されたファイルの変更に合わせたアップロードがかかるようになります。

package.json
{
  "name": "kintone-customize-boilerplate",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "upload": "kintone-customize-uploader --watch dest/customize-manifest.json",
    "develop": "npm run build -- --watch",
    "build": "webpack",
    "build:prod": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "@kintone/customize-uploader": "^1.5.3",
    "babel-loader": "^8.0.6",
    "webpack": "^4.32.2",
    "webpack-cli": "^3.3.2"
  },
  "dependencies": {
    "@babel/polyfill": "^7.4.4"
  }
}

webpackと@kintone/customize-uploaderのwatchオプションを共存させる

ここまで来ると、watchによる処理がwebpackと@kintone/customize-uploaderによって2つ存在していることに気付きます。

1 webpackでエントリファイルの変更に応じてバンドル処理が走りバンドルファイルが生成される
2 バンドルファイルの変更に変更があった際にkintoneにアップロードする

更にこれらを共存できれば、エントリファイルを変更した際にバンドルが走ってkintoneにアップロードされるといった処理の一連化され、更に開発効率が上がってきます。

これは、@kintone/create-pluginで利用されているnpm-run-allというパッケージを利用すれば、同じように対応することが可能です。

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

npm install --save-dev npm-run-all

そして、@kintone/create-pluginと同じくscripts/npm-start.jsを準備します。

scripts/npm-start.js
"use strict";
const runAll = require("npm-run-all");

runAll(["develop", "upload"], {
  parallel: true,
  stdout: process.stdout,
  stdin: process.stdin
}).catch(({results}) => {
  results
    .filter(({code}) => code)
    .forEach(({name}) => {
      console.log(`"npm run ${name}" was failed`);
    })
  ;
});

合わせて、これを実行しやすくするためにpackage.jsonscriptsを追加します。@kintone/create-pluginと同じ体験にするために、startとして追加します。

package.json
{
  "name": "kintone-customize-boilerplate",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node scripts/npm-start.js",
    "upload": "kintone-customize-uploader --watch dest/customize-manifest.json",
    "develop": "npm run build -- --watch",
    "build": "webpack",
    "build:prod": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "@kintone/customize-uploader": "^1.5.3",
    "babel-loader": "^8.0.6",
    "npm-run-all": "^4.1.5",
    "webpack": "^4.32.2",
    "webpack-cli": "^3.3.2"
  },
  "dependencies": {
    "@babel/polyfill": "^7.4.4"
  }
}

これで、npm run startでエントリファイルの変更時にバンドルとkintoneへのファイルアップロードが都度行われるようになり、開発も捗る状態かと思います。

ちなみに、この時点でのフォルダ構造を確認しておくと、次の通りです。

new-project
├── dest
├── dist
├── node_modules
├── package-lock.json
├── package.json
├── scripts
├── src/js/customize.js
└── webpack.config.js

今回のエントリファイルであるsrc/js/customize.jsを例えばこのような状態から、色々書き換えてみてください。

src/js/customize.js
import '@babel/polyfill';


kintone.events.on('app.record.detail.show', event => {
    alert('Hello, webpack');
    return event;
});

変更すると、バンドルとアップロードが走るはずです。

まとめ

webpackを利用するkintoneカスタマイズ開発の流れについて見てきました。全体に言えることは、フォルダ・ファイル構成についてのベストプラクティスは対象が複数のアプリになったり状況によって変わってきますので、今回の記事はいち例として参考になればと思います(自分も実際にはESLintが入っていたり、複数アプリを対象にするため、少々複雑な構成を扱っています)。

webpackの設定については、CSSを同時にバンドルしたい、Reactを使いたいといった内容に合わせて昇華してもらえれば良いかと思います。これもケースバイケースだと思います。

kintoneへのカスタマイズ適用については、@kintone/customize-uploaderでファイルをアップロードするのか、Webサーバーを利用するのか等は自分は好みの領域かと思っています(ローカルサーバーを立てるのに慣れている人はそうすれば良いと思いますし、証明書発行等色々設定増やしたくない人は今回の方法でも良いでしょう)。

参考にしたサイト等

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

jQueryまとめ

とりあえず投稿しますが随時追加・修正します。
修正点あればご指摘ください。

jQueryの文法

基本構造

$(セレクタ).メソッド(引数としてイベント、関数、値)

example.js
$('#foo').on('click', function(){
  console.log("Hello, World")
});

上記の例では、
1. idfooである要素を#idセレクタとして指定し
2. セレクタで指定した要素にonメソッドを付与し
3. 第一引数にclickイベントを指定し
4. 第二引数にclickイベントが起きた時の処理を関数として定義している。

参考リンク

全体解説

  • jQuery DOM操作あれこれ
    1. DOMの取得(セレクタの指定)
    2. DOMの操作(メソッドの設定)
    3. イベント定義(on, offメソッドによるイベント処理の定義)
      の例。

メソッド

イベント

onメソッドについて

Ajax

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

Javascript計算機を作ろう【レベル1】 第一回 HTMLとCSSでボタンを横並びで配置しよう

初めに

動画で習った内容を理解できた。でも自分で作れるかはどうだろう?小さい作品でいいから自分で作り上げていこうと思い、電卓を作っていくことにしました。最も簡単な「とりあえず計算できる」状態から、エラーを減らし、様々な機能を追加していきたいと思います。

目次【レベル1】

  • HTMLのマークアップ
  • CSS(この記事はここまで)
  • とりあえず足し算をしよう

今後の改善が必要な点

  • 割り算、引き算、掛け算
  • 小数点の計算
  • いきなり+、=を押すと計算できない

レベル1のイメージ

image.png

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>電卓</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class = "container">
<!--      計算結果を出力する場所-->
      <div id = "output_total" class="output ">0</div>
<!--      計算過程を出力する -->
      <div id = "output_sub" class="output active">0</div>
<!--      ボタンを配置する場所-->
      <div class = 'input'>
          <section class = 'row'>
            <div id = "clear">C</div>
            <div class = "num_bth"></div>
            <div class = "num_bth" id="bs">BS</div>       
            <div class = "num_bth " data-index-id= '/'>÷</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 9 >9</div>
            <div class = "num_bth " data-index-id = 8 >8</div>
            <div class = "num_bth " data-index-id = 7 >7</div>
            <div class = "num_bth " data-index-id = '*'>×</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 6 >6</div>
            <div class = "num_bth " data-index-id = 5 >5</div>
            <div class = "num_bth " data-index-id = 4 >4</div>
            <div class = "num_bth " data-index-id = '-'></div>     
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 3 >3</div>
            <div class = "num_bth " data-index-id = 2 >2</div>
            <div class = "num_bth " data-index-id = 1 >1</div>
            <div class = "num_bth " data-index-id = '+'>+</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth" data-index-id = 00 >00</div>
            <div class = "num_bth" data-index-id = 0 >0</div>
            <div class = "num_bth" data-index-id = . >.</div>
            <div id = 'equal_btn'>=</div>       
          </section>        
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

HTMLの出力結果
image.png

「data-index-id = 9」 の記載がわからない場合は、「JavaScript」「data属性(カスタムデータ属性)」で検索して復習してみよう。

CSS

1)containerの幅は200px。上から50xp余白を作り、左右の余白は均等。全体を2pxの黒線の枠線を書こう。

.container {
  width: 200px;
  margin: 50px auto;
  border: 2px solid black;
}

image.png

2)計算結果の出力場所のスタイリング

  • 幅 200px
  • 高さ 50px
  • 文字は、高さは中央揃え、横は右寄せ
  • padding 5pxで文字と枠線にスペースを持たせる
  • 枠線の下に5pxのmarginをとる。
.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
}

image.png

3)widthがpaddingの分だけずれてしまった。widthにpadding含ませるにはどうしよう?

.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
  box-sizing: border-box;
}

box-sizing: border-box;とすると、widthにpadding含ませられるので、綺麗に揃う。

4)ボタンの部分のスタイリング

  • 幅 200px;
  • 2pxの黒の枠線
.input {
  width:200px;
  border: 2px solid black; 
}

image.png

5)ボタンのスタイリング

5-1)ボタンを横並びにしたい

.row {
  display: flex;
}

image.png

5-2)ボタンにスペースを作り、均等に配置したい

.row {
  display: flex;
  justify-content: space-between;
}

【均等配置 】
justify-content: space-between;
とすると、各アイテムを均等に配置し最初のアイテムは先頭に寄せ、最後のアイテムは末尾に寄せることができる。
MDNのjustify-contentの解説
image.png

5-3)ナンバーボタン、クリアボタン、イコールボタンをスタイリングしよう。

  • 幅 60px
  • 高さ 30px
  • 文字は高さ中央揃え、横は中央
  • 色はgray
  • 少し丸めよう(30%)
  • ボタンがカーソルが乗ったことがわかるようにしてみよう。
.row {
  display: flex;
  justify-content: space-between;
  border: 2px solid red;
  width: 200px;
}
/*rowの範囲がわかりやすいように赤の枠を作っています。*/

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer 
}

image.png

5-4)ボタンの間に余白を作ろう

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer;
  margin: 5px;
}

image.png

ボタンのスタイリングはほぼ完成。
今までのCSSのコードはこちら

.container {
  width: 200px;
  margin: 50px auto;
  border: 2px solid black;
}

.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
  box-sizing: border-box;
}

.input {
  width:200px;
  border: 2px solid black; 
}

.row {
  display: flex;
  justify-content: space-between;
  border: 2px solid red;
  width: 200px;
}
/*rowの範囲がわかりやすいように赤の枠を作っています。*/

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer;
  margin: 5px;
}

次回、javascriptで計算をしていきたいと思います。

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

Javascriptで電卓を作ろう【レベル1】  第1回 HTMLとCSSでボタンを横並びで配置しよう

初めに

動画で習った内容を理解できた。でも自分で作れるかはどうだろう?小さい作品でいいから自分で作り上げていこうと思い、電卓を作っていくことにしました。最も簡単な「とりあえず計算できる」状態から、エラーを減らし、様々な機能を追加していきたいと思います。

目次【レベル1】

  • HTMLのマークアップ
  • CSS(この記事はここまで)
  • とりあえず足し算をしよう

今後の改善が必要な点

  • 割り算、引き算、掛け算
  • 小数点の計算
  • いきなり+、=を押すと計算できない

レベル1のイメージ

image.png

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>電卓</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class = "container">
<!--      計算結果を出力する場所-->
      <div id = "output_total" class="output ">0</div>
<!--      計算過程を出力する -->
      <div id = "output_sub" class="output active">0</div>
<!--      ボタンを配置する場所-->
      <div class = 'input'>
          <section class = 'row'>
            <div id = "clear">C</div>
            <div class = "num_bth"></div>
            <div class = "num_bth" id="bs">BS</div>       
            <div class = "num_bth " data-index-id= '/'>÷</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 9 >9</div>
            <div class = "num_bth " data-index-id = 8 >8</div>
            <div class = "num_bth " data-index-id = 7 >7</div>
            <div class = "num_bth " data-index-id = '*'>×</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 6 >6</div>
            <div class = "num_bth " data-index-id = 5 >5</div>
            <div class = "num_bth " data-index-id = 4 >4</div>
            <div class = "num_bth " data-index-id = '-'></div>     
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 3 >3</div>
            <div class = "num_bth " data-index-id = 2 >2</div>
            <div class = "num_bth " data-index-id = 1 >1</div>
            <div class = "num_bth " data-index-id = '+'>+</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth" data-index-id = 00 >00</div>
            <div class = "num_bth" data-index-id = 0 >0</div>
            <div class = "num_bth" data-index-id = . >.</div>
            <div id = 'equal_btn'>=</div>       
          </section>        
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

HTMLの出力結果
image.png

「data-index-id = 9」 の記載がわからない場合は、「JavaScript」「data属性(カスタムデータ属性)」で検索して復習してみよう。

CSS

1)containerの幅は200px。上から50xp余白を作り、左右の余白は均等。全体を2pxの黒線の枠線を書こう。

.container {
  width: 200px;
  margin: 50px auto;
  border: 2px solid black;
}

image.png

2)計算結果の出力場所のスタイリング

  • 幅 200px
  • 高さ 50px
  • 文字は、高さは中央揃え、横は右寄せ
  • padding 5pxで文字と枠線にスペースを持たせる
  • 枠線の下に5pxのmarginをとる。
.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
}

image.png

3)widthがpaddingの分だけずれてしまった。widthにpadding含ませるにはどうしよう?

.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
  box-sizing: border-box;
}

box-sizing: border-box;とすると、widthにpadding含ませられるので、綺麗に揃う。

4)ボタンの部分のスタイリング

  • 幅 200px;
  • 2pxの黒の枠線
.input {
  width:200px;
  border: 2px solid black; 
}

image.png

5)ボタンのスタイリング
5-1)ボタンを横並びにしたい

.row {
  display: flex;
}

image.png

5-2)ボタンにスペースを作り、均等に配置したい

.row {
  display: flex;
  justify-content: space-between;
}

【均等配置 】
justify-content: space-between;
とすると、各アイテムを均等に配置し最初のアイテムは先頭に寄せ、最後のアイテムは末尾に寄せることができる。
MDNのjustify-contentの解説
image.png

5-3)ナンバーボタン、クリアボタン、イコールボタンをスタイリングしよう。

  • 幅 60px
  • 高さ 30px
  • 文字は高さ中央揃え、横は中央
  • 色はgray
  • 少し丸めよう(30%)
  • ボタンがカーソルが乗ったことがわかるようにしてみよう。
.row {
  display: flex;
  justify-content: space-between;
  border: 2px solid red;
  width: 200px;
}
/*rowの範囲がわかりやすいように赤の枠を作っています。*/

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer 
}

image.png

5-4)ボタンの間に余白を作ろう

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer;
  margin: 5px;
}

image.png

ボタンのスタイリングはほぼ完成。
今までのCSSのコードはこちら

.container {
  width: 200px;
  margin: 50px auto;
  border: 2px solid black;
}

.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
  box-sizing: border-box;
}

.input {
  width:200px;
  border: 2px solid black; 
}

.row {
  display: flex;
  justify-content: space-between;
  border: 2px solid red;
  width: 200px;
}
/*rowの範囲がわかりやすいように赤の枠を作っています。*/

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer;
  margin: 5px;
}

次回、javascriptで計算をしていきたいと思います。

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

Javascriptで電卓を作ろう【レベル1】  第1回  HTMLとCSSでボタンを横並びで配置しよう

初めに

動画で習った内容を理解できた。でも自分で作れるかはどうだろう?小さい作品でいいから自分で作り上げていこうと思い、電卓を作っていくことにしました。最も簡単な「とりあえず計算できる」状態から、エラーを減らし、様々な機能を追加していきたいと思います。
第ー回(この記事はここ)

  • HTMLのマークアップ
  • CSS

第二回
Javascriptで四則演算
- 6)打った数字をコンソールに出力させよう。
- 7)打った数字と記号を変数に格納しよう。
- 8)計算結果を出力させよう。
- 8-1)「=」を押した時の処理
- 8-2) 計算結果と計算途中を、計算結果の画面に表示させよう。

第三回以降以下の不具合の改善

  • 最初に「+ ÷ - = .」が押せてしまう。
  • 最初に0を連続で押せてしまう。
  • 一度間違えると、全てやり直し(BSボタンを使えるようにしよう)
  • 一度計算をやると、次の計算をするときに、ブラウザを読み込みなおさないといけない(Cボタンを使えるようにしよう)
  • 12.12.や12..12のように小数点を正しくなく入力できてしまう。
  • 割り算等で小数点以下が異様に長い。
  • 記号ボタン(+-×÷)を連続で押せてしまい、計算ができない。(連続で押した場合は、最後に押した記号ボタンが採用されるようにしたい。
  • 電卓と同じように「.4」と入力したら0.4としたい。

レベル1のイメージ

image.png

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>電卓</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class = "container">
<!--      計算結果を出力する場所-->
      <div id = "output_total" class="output ">0</div>
<!--      計算過程を出力する -->
      <div id = "output_sub" class="output active">0</div>
<!--      ボタンを配置する場所-->
      <div class = 'input'>
          <section class = 'row'>
            <div id = "clear">C</div>
            <div class = "num_bth"></div>
            <div class = "num_bth" id="bs">BS</div>       
            <div class = "num_bth " data-index-id= '/'>÷</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 9 >9</div>
            <div class = "num_bth " data-index-id = 8 >8</div>
            <div class = "num_bth " data-index-id = 7 >7</div>
            <div class = "num_bth " data-index-id = '*'>×</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 6 >6</div>
            <div class = "num_bth " data-index-id = 5 >5</div>
            <div class = "num_bth " data-index-id = 4 >4</div>
            <div class = "num_bth " data-index-id = '-'></div>     
          </section>
          <section class = 'row'>
            <div class = "num_bth " data-index-id = 3 >3</div>
            <div class = "num_bth " data-index-id = 2 >2</div>
            <div class = "num_bth " data-index-id = 1 >1</div>
            <div class = "num_bth " data-index-id = '+'>+</div>
          </section>
          <section class = 'row'>
            <div class = "num_bth" data-index-id = 00 >00</div>
            <div class = "num_bth" data-index-id = 0 >0</div>
            <div class = "num_bth" data-index-id = . >.</div>
            <div id = 'equal_btn'>=</div>       
          </section>        
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

HTMLの出力結果
image.png

「data-index-id = 9」 の記載がわからない場合は、「JavaScript」「data属性(カスタムデータ属性)」で検索して復習してみよう。

CSS

1)containerの幅は200px。上から50xp余白を作り、左右の余白は均等。全体を2pxの黒線の枠線を書こう。

.container {
  width: 200px;
  margin: 50px auto;
  border: 2px solid black;
}

image.png

2)計算結果の出力場所のスタイリング

  • 幅 200px
  • 高さ 50px
  • 文字は、高さは中央揃え、横は右寄せ
  • padding 5pxで文字と枠線にスペースを持たせる
  • 枠線の下に5pxのmarginをとる。
.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
}

image.png

3)widthがpaddingの分だけずれてしまった。widthにpadding含ませるにはどうしよう?

.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
  box-sizing: border-box;
}

box-sizing: border-box;とすると、widthにpadding含ませられるので、綺麗に揃う。

4)ボタンの部分のスタイリング

  • 幅 200px;
  • 2pxの黒の枠線
.input {
  width:200px;
  border: 2px solid black; 
}

image.png

5)ボタンのスタイリング
5-1)ボタンを横並びにしたい

.row {
  display: flex;
}

image.png

5-2)ボタンにスペースを作り、均等に配置したい

.row {
  display: flex;
  justify-content: space-between;
}

【均等配置 】
justify-content: space-between;
とすると、各アイテムを均等に配置し最初のアイテムは先頭に寄せ、最後のアイテムは末尾に寄せることができる。
MDNのjustify-contentの解説
image.png

5-3)ナンバーボタン、クリアボタン、イコールボタンをスタイリングしよう。

  • 幅 60px
  • 高さ 30px
  • 文字は高さ中央揃え、横は中央
  • 色はgray
  • 少し丸めよう(30%)
  • ボタンがカーソルが乗ったことがわかるようにしてみよう。
.row {
  display: flex;
  justify-content: space-between;
  border: 2px solid red;
  width: 200px;
}
/*rowの範囲がわかりやすいように赤の枠を作っています。*/

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer 
}

image.png

5-4)ボタンの間に余白を作ろう

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer;
  margin: 5px;
}

image.png

ボタンのスタイリングはほぼ完成。
今までのCSSのコードはこちら

.container {
  width: 200px;
  margin: 50px auto;
  border: 2px solid black;
}

.output {
  width: 200px;
  height: 50px;
  line-height: 50px;
  text-align: right;
  padding: 5px;
  margin-bottom: 5px;
  border:2px solid black;
  box-sizing: border-box;
}

.input {
  width:200px;
  border: 2px solid black; 
}

.row {
  display: flex;
  justify-content: space-between;
  border: 2px solid red;
  width: 200px;
}
/*rowの範囲がわかりやすいように赤の枠を作っています。*/

.num_bth, #clear, #equal_btn{
  width: 60px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border-radius: 30%;
  background: lightgray;
  cursor: pointer;
  margin: 5px;
}

次回、javascriptで計算をしていきたいと思います。

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

Class name mangled after minification

背景

minifyするとconstroctor.nameが置き換わってた

対策

ビルドする際に、--keep-fnamesをつける

au build --env prod --keep-fnames

参考

https://github.com/mishoo/UglifyJS2/issues/988

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

TypeScript地獄の地図と幻の型たち

地獄の管理人、その名はTypeScript

※前回の記事 地獄からの使者、その名はTypeScript ~ 固かった型の形 ~ 2

1. 気が付くとそこは迷宮

 地獄の地図

image.png

 初学者がTypeScriptを始めようとするのなら、おそらくはどこかしらの解説記事をみることになるだろう。ところがTypeScriptは迷宮の入り口なのである。知らずに進めば、GPSもコンパスも役に立たない永劫の樹海を彷徨うことになるだろう。

 まずは入り口でこう問われることになる

『おぬしの進むべき道は、前か後ろか?』

 大抵の人間はこう答えるだろう。

「もちろん前に進む、そのために来たのだ!」

 その答えに対して、再び問われる。

『ふむ、まあ良いだろう。では次の問いだ。おぬしは環境を重んじるか?』

 大半の人間にはその意味が分からない。しかし分からないままこう答える。

「環境は良い方がいいなぁ」

 気がついたらフロントエンドでエコシステムをぶん回して、わけも分からずWebPackを設定する結果となる。この時点で、TypeScriptをまともに動かすための設定地獄が始まるのだ。

 初学者がTypeScriptの真の地獄を回避するために必要なこと

 まずは用途を明確にする。ターゲットはフロントエンドなのかバックエンドなのか?
 大抵の場合は、フロントエンドにTypeScriptを使おうとする人が多いと思われる。逆にバックエンドでTypeScriptを使うのはそれほど苦労しない。フロントエンドでTypeScriptを使おうとする者が地獄に填まりやすいのである。

 そもそものTypeScriptを地獄とせしめているのは何か?それはnode_modulesに潜んでいる魑魅魍魎である。こいつらとの接触しなければ、新月の闇夜も安心して越せる。地獄からの誘いを回避できるのだ。

 しかし基本文法を脱した先にあるTypeScriptの解説記事は、気軽にimportというキーワードが書いてある。これがある時点で.tsから.jsに変換される際に、面倒なお約束ごとが生成されるのだ。

 フロントエンドでTypeScriptを扱おうと思ったら、まずはimportとは一切関わらずにプログラムを組んだ方が良い。地獄へ踏み込むのは、ある程度慣れた後だ。ノーマルモードを選択せず、いきなりヘルモードで始めて叩きのめされるのはCXな課長のお約束とするだけで十分だろう。まあ、ノーマルモードに戻っても、結局叩きのめされるのだが。

2. TypeScriptの真の力、それは幻を生み出す力

 幻の混入した図

 
image.png

 TypeScriptには二種類の挙動がある。トランスコンパイル時に、古いターゲット環境に合わせて互換コードが出力する挙動と、書いたものが蜃気楼のように消える挙動である。

 互換コード出力機能は大変便利な機能である反面、TypeScriptの本懐の機能では無い。では、TypeScriptの真の機能は何か、それは幻を生み出す力なのだ。

 幻の力とは何なのか。その片鱗を見いだすためのコードはこれだ。

const value = "今日は世界"
const typeName = typeof value
type typeName2 = typeof value
console.log(typeName) //'string'
//console.log(typeName2) //error

 TypeScriptではtypeofの扱いがJavaScriptと違う。typeNameはJavaScriptの挙動として文字列の"string"という実態を持つが、typeName2はTypeScriptの挙動で実態を持たない。ただの幻なのだ。何をどうやってもtypeName2を出力する方法は存在しない。この実体を持つtypeofと、持たないtypeofの違いをきちんと理解しておかないと、TypeScriptでの転生無双どころか六道輪廻の宿業が終わらない。

 出力できないものをわざわざ作るのは何故か?それがTypeScriptの型定義なのである。ただし、ここで勘違いしてはならないのは、JavaScriptの型とTypeScriptの型は根本的に別物だと言うことだ。JavaScriptの型は実行時に実体を持ち、TypeScriptの型定義はただの蜃気楼である。たどり着いてみると、何も存在しないのだ。

const value2:number = "あいうえお" as any as number
console.log(typeof value2) //string

型に何を指定しようと、TypeScriptの型はコンパイル時のエラーチェックまででその寿命を終える。そして型に何が指定されていようと、出力コードには一切の影響を与えない。実行時にエラーを発生させることも無い。コンパイルが終わった時点で、幻を作り出していた霧は、綺麗さっぱり消え去るのだ。

 その他に幻の代表格、interfaceがある。このinterfaceは実行時に、その型の内容を取り出すことは出来ない。変数の中に、たまたま定義したとおりの実態が入っていれば話は別だが、結局のところ自分で実態を用意しない限り、触れることはかなわないのである。

4. 幻、それはヘブンへの誘いの光

 地獄、そう、そこはヘルなのだ。ヘルであっても腹は減る。しかし幻の力の使い道を知っていれば話は別だ。幻の導く先にこそ、ユートピアでありヘブンであり、真の極楽が広がっているのだ。必要なのは、幻と侮ること無く信じて付いていくことなのだ。

 終着点、そこにあるものはきっと・・・

 成仏

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

画像処理100本ノックにJavaScriptで挑戦してみた 【画像処理100本ノックJS】

test.gif

概要

画像処理100本ノックをJavaScriptで挑戦してみました。
「ブラウザ上で完結させたい」 & 「デモを共有できたら面白い」という動機ではじめました。
まだ100問完了していませんが、ここまで解いてみた所感を書きます。

とりあえず、、、「100問は辛いです。」

画像処理100本ノックについて

画像処理が初めての人のための問題集をつくったりました。(完成!!) 研究室の後輩用に作ったものです。 自然言語処理100本ノックがあるのに、画像処理のがなかったので作ってみました。 画像処理の基本のアルゴリズム理解につながると思います。 https://qiita.com/yoyoyo_/items/2ef53f47f87dcf5d1e14

GitHub Qiita

この「画像処理100本ノック」にはPythonとC++のコードが解答例として用意されています。

デモの例 ※ → デモ はこちらから(Gasyori100KnockJS)

いくつかのデモの例を紹介します。


201904071958_njvf00.png
大津の2値化


20190518004109.png
プーリング (MAX値)


 2019-05-26 19.56.11.png
メディアンフィルタ



アフィン変換 (スキュー)


 2019-05-26 19.51.21.png
テンプレートマッチング


 2019-05-26 19.57.46.png
バイリニア補間


 2019-05-26 20.03.21.png
JPEG圧縮 (Step.2)DCT+量子化


 2019-05-26 19.53.02.png
Harrisのコーナー検出

実装の紹介

GitHub にソースを置いてます。

canvasを使った画像の表示と操作

画像の読み込み

画像の表示ピクセル値の操作にはcanvasのAPIを利用しています。

任意の画像を読み込む際には次のように実装しています。

<canvas id="canvas"></canvas>
// canvas関連のオブジェクト
const canvas = document.getElementById("canvas")
const ctx = canvas.getContext("2d")

// 任意の画像読み込み
let image = new Image()
image.src = "path/to/image.png"

// 読み込み完了時のイベント
image.onload = () => {
    canvas.width = image.width
    canvas.height = image.height
    ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
    // canvas描画後、画像の処理を実行
}

ピクセル操作

html5-canvas-imageData-example.png

CREATE A PAINT BUCKET TOOL IN HTML5 AND JAVASCRIPT
http://www.williammalone.com/articles/html5-canvas-javascript-paint-bucket-tool/

より

getImageDataメソッドでImageDataオブジェクトを取得しています。
このオブジェクトにはr, g, b, a の順に画像情報が格納されています。
putImageDataを使って編集したImageDataオブジェクトをcanvasに描画しています。

これを用いることにより大概の画像の処理を行うことができます。

(canvasを用いず、Imageオブジェクトの画像情報に対して直接参照する方法があればいいんですけど... )

let src = ctx.getImageData(0, 0, canvas.width, canvas.height)
let dst = ctx.createImageData(canvas.width, canvas.height)
for (let i = 0; i < src.data.length; i += 4) {
    dst.data[i] = src.data[i]          // r
    dst.data[i + 1] = src.data[i + 1]  // g
    dst.data[i + 2] = src.data[i + 2]  // b
    dst.data[i + 3] = src.data[i + 3]  // a (透過度)
}

例えば、グレースケール画像であれば次のような処理になります。

const grayscale = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b
// 略
for (let i = 0; i < src.data.length; i += 4) {
    let gray = grayscale(src.data[i], src.data[i + 1], src.data[i + 2])
    dst.data[i] = gray[0]
    dst.data[i + 1] = gray[1]
    dst.data[i + 2] = gray[2]
    dst.data[i + 3] = src.data[i + 3]
}
ctx.putImageData(dst, 0, 0)

こんな風に表示されます。

201904071946_398cds.png

参考 : 画像をグレースケールに変換する JavaScript + canvas 【画像処理】

ヒストグラムの表示

 2019-05-26 1.46.55.png

このデモでは、ヒストグラムの表示にChart.jsを使っています。

実装については次のように行なっています。

ヒストグラム描画

import Chart from "chart.js"

export default class Histogram {
  /**
   * ヒストグラムを描画する
   * @param {Object} canvas 
   * @param {Object} data 
   */
  static renderHistogram(canvas, data) {
    let labels = new Array(data.length).fill('')
    new Chart(canvas, {
      type: 'bar',
      data:{
        labels,
        datasets: [
          {
            label: '画素値',
            data,
            backgroundColor: "rgba(80,80,80,0.5)"
          }
        ],
      },
      options: {
        title: {
          display: true,
          text: 'Histogram'
        },
        scales: {
          yAxes: [{
            ticks: {
              suggestedMin: 0,
            }
          }]
        },
        animation: {
          duration: 0
        }
      }
    })
  }
}
import Histogram from 'path/to/Histogram'

const grayscale = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b
// 略
let pixelValues = new Array(255).fill(0)
for (let i = 0; i < src.data.length; i += 4) {
    let gray = grayscale(src.data[i], src.data[i + 1], src.data[i + 2])
    gary = Math.floor(gray)
    pixelValues[gray]++
}
Histogram.renderHistogram(canvas, pixelValues)

参考 : 画像のヒストグラムを表示する Char.js JavaScript canvas

フレームワークにVueを使っています。
またSPAにも挑戦しました。

コンポーネントの制御が難しく、処理がバグっている箇所があると思います()

まとめ : JSで挑戦するメリット・デメリット

ブラウザ上で動かせるのがJSを使う最大のメリットだと思います。
加えて、チャート系のライブラリが豊富なので、matplotlibに比べ、グラフィカルな表現がしやすいのも良い点だと感じました。

一方で、行列演算に関してはJSではnumjsやmath.jsといったものはありますが、
Numpyほど簡潔に行列の処理を書くことはできません。

(※今回のデモではアフィン変換などの行列演算を多用する箇所で math.js を使いました。)

また、フーリエ変換のデモでは、実装に複素数を利用しますが、
Pythonは「j」が利用できるのに対し、JSの場合は実部と虚部に分けるような処理に実装する必要がありました。

改めてPython、Numpyの偉大さには感謝したいと思います。


画像処理100本ノックJS
https://s-yoshiki.github.io/Gasyori100knockJS/#/


画像処理100本ノックJS - GitHub
https://github.com/s-yoshiki/Gasyori100knockJS


JavaScriptで画像処理100本ノックに挑戦してみた
https://tech-blog.s-yoshiki.com/2019/03/1094/

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

ContentEditable な caret の位置(座標)を取得する

ブラウザで動くエディタを開発していると避けては通れない contenteditable な caret の位置(座標)。いろいろなアプローチがありますが、一部のケース(縦書き)でうまく動かないことがあったので回避策をメモしておきます。

ダミーの DOM を挿入する

わりと古典的でよく使われている方法です。caret のある位置にダミーの DOM を挿入して getBoundingClientRect で位置を取得するというもの。どんな場面でもほぼ確実に caret の位置が取得できます。ただ、大きな弱点があり DOM を挿入するので対象 DOM の TextNode が分割されてしまいます。contenteditable を使ってゴリゴリ開発をする場合、むやみに Node が変わるとつらすぎるので、あまり使うメリットはありません。

<p contenteditable="true">click me</p>
<script>
  document.onselectionchange = () => {
    const range = window.getSelection().getRangeAt(0)
    const anchor = document.createElement('span')
    anchor.innerHTML = '&#8203;'
    range.insertNode(anchor)
    const pos = anchor.getBoundingClientRect()
    anchor.parentElement.removeChild(anchor)
    console.log(pos)
  }
</script>

Range オブジェクトの座標を取得する

caret 位置は Range オブジェクトで知ることができます。つまり単純に Range にたいして getBoundingClientRect を取得すれば caret の位置は取得できちゃいます。非常に簡単。通常はこちらを利用するのが良いと思います。

<p contenteditable="true">click me</p>
<script>
  document.onselectionchange = () => {
    const pos = window.getSelection().getRangeAt(0).getBoundingClientRect()
    console.log(pos)
  }
</script>

writing-mode: vertical-lr では機能しない

しかし、冒頭に書いた一部のケースでうまく動かないというのが縦書きです。CSS で writing-mode: vertical-lr にしている要素に対しては Chrome / Safari では上記の Range オブジェクトで getBoundingClientRect が正常に取得できません。すべての値が 0 で返ってきます。Firefox は正常に返ってくるので…バグのような気がしなくもないです。

<style>
  p{ writing-mode: vertical-lr; }
</style>
<p contenteditable="true">click me</p>
<script>
  document.onselectionchange = () => {
    const pos = window.getSelection().getRangeAt(0).getBoundingClientRect()
    console.log(pos) // すべて 0 になる…
  }
</script>

そこで回避策として以下のように Range オブジェクトを clone して選択範囲を指定し直すと、なぜか取得できるようになります。こちらでは正常に取得できる理由は分かりませんが…とりあえずこれで縦書きでも caret 位置を取得することができるようになりました。

<style>
  p{ writing-mode: vertical-lr; }
</style>
<p contenteditable="true">click me</p>
<script>
  document.onselectionchange = () => {
    const range = window.getSelection().getRangeAt(0)
    const clone = range.cloneRange()
    const fixedPosition = range.endOffset
    // 末尾の文字列を選択した時はダミーテキストを追加して選択範囲を拡大する
    if (fixedPosition + 1 > range.endContainer.length) {
      const dummy = document.createTextNode('&#8203;')
      clone.insertNode(dummy)
      clone.selectNode(dummy)
      const rect = clone.getBoundingClientRect();
      console.log(rect);
      dummy.parentNode.removeChild(dummy)
    } else {
      clone.setStart(range.endContainer, fixedPosition);
      clone.setEnd(range.endContainer, fixedPosition + 1);
      const rect = clone.getBoundingClientRect();
      console.log(rect);
    }
    clone.detach()
  }
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiitaのタグを2018年度の第4四半期の記事数、フォロワー数、いいね数で比較してみた

先日、「Qiitaタグの記事数とフォロワー数を比較してみた」を初投稿した。
投稿後に自身の関連記事を見てみると、、
最上位の記事「Qiitaの投稿をNVD3で可視化してみるとRuby人気なんだなと改めて分かった。」と、グラフの形式が完全に被っていることと、グラフの見やすさも私の方が劣っていた。
正規化や、記事投稿の動機の1つにもなった「#migrated」などオリジナルな観点もあったけど。

そのため、今回は下記、投稿後に思った下記改善を考慮し、完全オリジナルな内容にアップデートする。

  • 全数では5年以上のデータとなり、トレンドがわからないため、直近4ヶ月(昨年度の第4四半期)のデータでグラフを作成する。フォロワーは、集計期間に記事を投稿したユーザのみを対象とする。
  • バブルチャートでは3軸の可視化ができるため、いいね数も評価する。
  • 傾向からグループを決定してチャートの色分けを行う。
  • chart.jsがスマホ環境では崩れていたため、不要なラベルを消してスマホでも結果を見やすくする。

やったこと

  • Qiita apiの記事一覧を日付毎に第4四半期分取得し、タグ毎の記事数と、タグ毎のいいね数、投稿ユーザ一覧を集計する。
  • 上記で取得した全てのユーザについて、フォローしているタグを集計し、タグ毎のフォロワー数を集計する。(今回投稿ユーザが15,178ユーザいたので、1,000回/1hのAPI制限があるので、15時間以上APIを動かし続けてデータを取得した)
  • 記事数、フォロワー数、いいね数を、正規化し、同じ重み付けで上位10を決定し、前回の全数のランキングと比較する
  • 上位100について、記事数、フォロワー数、いいね数の傾向でタグを19個のグループに分ける
  • 上位100について、バブルチャートで、いいね数を大きさで表して、グループ毎に色分けしてグラフ化する。

結果

総タグ数: 15,649,
総記事数: 43,684,
総投稿者数: 15,178,
総タグ記事数: 12,1912,
総タグフォロワー数: 91,046,
総タグいいね数: 1,307,100

集計方法の変更による違い(全数 vs 第4四半期)

前回記事と順位の差異を含めて下記の通り。

前回記事と比較して、フォロワー数側に大きく変化が生じたのは、フォロワー数をその期間に記事を投稿している人に限っているのが大きいかもしれないが、前回の結果はフォロワー数側に引っ張られて評価値順位が高いものもあったため、今回の修正は妥当(期間を区切った集計の方が有意な結果となる)だと考える。

なお、Railsが評価+8は少し意外だが、それだけRailsのフォロワーに投稿者が多いのではないかと推測する。

タグ名 評価値順位 フォロワー数順位 記事数順位 評価値(%) フォロワー数 記事数 評価値差分(%)
Python 1(+1) 1(+1) 1(-) 3.35 3175 3928 0.27
JavaScript 2(-1) 2(-1) 2(-) 2.76 3106 2583 1.29
Ruby 3(-) 4(+10) 3(-) 1.72 1829 1747 0.58
PHP 4(-) 9(-) 6(-2) 1.48 1642 1405 0.65
Rails 5(+8) 15(+14) 4(+1) 1.40 1355 1603 0.17
Java 6(-1) 6(+1) 12(-3) 1.34 1715 972 1.09
GitHub 7(+3) 3(+2) 30(-) 1.33 2032 522 1.80
Git 8(+3) 5(+8) 21(-7) 1.28 1812 689 1.43
Linux 9(-1) 8(+4) 18(-6) 1.28 1709 826 1.20
Android 10(-4) 13(-2) 14(-6) 1.19 1471 927 0.86

いいね数も含めたTOP100

タグ名 評価値順位 フォロワー数順位 記事数順位 いいね数順位 評価値 フォロワー数 記事数 いいね数
Python 1 1 1 2 3.54 3175 3928 51004
JavaScript 2 2 2 1 3.25 3106 2583 55042
Ruby 3 4 3 14 1.43 1829 1747 10901
PHP 4 9 6 18 1.22 1642 1405 9305
Rails 5 15 4 24 1.16 1355 1603 8946
HTML 6 7 28 6 1.14 1710 544 14513
Git 7 5 21 22 1.08 1812 689 9142
Vue.js 8 41 13 3 1.07 587 966 23240
Linux 9 8 18 27 1.06 1709 826 8186
Java 10 6 12 35 1.04 1715 972 5684
GitHub 11 3 30 36 1.03 2032 522 5666
CSS 12 11 24 12 1.02 1518 633 11473
AWS 13 29 5 16 1.01 869 1597 10088
Docker 14 26 8 8 1.01 931 1187 13399
Node.js 15 14 17 20 0.99 1412 851 9250
Android 16 13 14 30 0.98 1471 927 7521
iOS 17 18 11 9 0.98 1122 975 11733
機械学習 18 40 20 4 0.91 597 765 18793
C++ 19 24 23 17 0.77 978 636 9339
初心者 20 101 9 5 0.77 125 1012 17543
C# 21 23 16 32 0.76 994 857 6222
Swift 22 37 10 29 0.72 688 980 7880
Go 23 36 19 26 0.69 697 786 8723
HTML5 24 10 68 82 0.69 1525 225 2700
Mac 25 21 22 52 0.67 1083 640 3708
MySQL 26 16 27 161 0.65 1260 556 1546
Python3 27 62 7 28 0.65 314 1210 7945
Qiita 28 12 88 142 0.65 1508 182 1731
Unity 29 48 15 25 0.62 422 864 8859
DeepLearning 30 52 32 7 0.61 372 491 13471
TypeScript 31 49 29 11 0.60 417 540 11608
Vim 32 17 59 53 0.58 1145 260 3696
React 33 45 26 19 0.58 483 621 9264
jQuery 34 19 54 56 0.57 1107 265 3667
Firebase 35 65 37 10 0.53 303 428 11687
Chrome 36 25 86 51 0.50 962 183 3735
iPhone 37 20 193 123 0.48 1106 84 1930
新人プログラマ応援 38 67 55 13 0.46 258 263 11383
Xcode 39 32 41 92 0.45 779 350 2536
Twitter 40 30 92 75 0.44 863 170 2939
CSS3 41 22 174 167 0.43 1002 94 1429
Ubuntu 42 34 38 285 0.42 767 427 816
Laravel 43 63 25 41 0.41 309 632 4765
C 44 33 52 118 0.41 768 266 2049
kubernetes 45 58 31 37 0.40 335 494 5651
ポエム 46 123 43 15 0.39 85 338 10632
MacOSX 47 27 96 567 0.39 909 165 332
RaspberryPi 48 59 34 42 0.37 332 463 4734
CentOS 49 35 52 215 0.37 727 266 1109
ShellScript 50 31 114 575 0.36 846 146 329
正規表現 51 28 188 473 0.36 877 87 418
Kotlin 52 53 35 74 0.33 366 446 3023
自然言語処理 53 91 106 23 0.32 138 155 9032
TensorFlow 54 57 55 43 0.31 338 263 4628
Heroku 55 46 44 119 0.31 465 309 2040
nuxt.js 56 91 44 31 0.31 138 309 6682
VSCode 57 68 42 40 0.30 231 344 4798
golang 58 51 47 73 0.30 382 300 3032
MachineLearning 59 75 58 33 0.30 190 262 6096
SSH 60 38 115 420 0.29 656 145 498
Apache 61 39 118 599 0.29 654 138 314
個人開発 62 214 167 21 0.28 44 99 9247
nginx 63 42 85 398 0.28 578 185 543
Slack 64 69 49 55 0.26 228 288 3688
Scala 65 44 90 214 0.26 487 178 1113
Django 66 71 36 111 0.25 198 433 2175
Rust 67 65 79 98 0.23 303 198 2448
Windows 68 124 33 101 0.22 84 475 2403
Haskell 69 50 156 139 0.22 390 105 1741
gcp 70 83 47 71 0.22 149 300 3095
Keras 71 80 65 54 0.22 162 226 3689
Emacs 72 47 187 257 0.21 457 88 924
docker-compose 73 181 64 39 0.21 53 242 5072
数学 74 89 82 45 0.21 139 189 4394
Objective-C 75 43 360 421 0.21 504 45 492
SQL 76 98 39 134 0.19 129 371 1797
lambda 77 107 40 126 0.19 109 359 1914
OpenCV 78 84 65 77 0.19 147 226 2778
GoogleAppsScript 79 78 63 106 0.19 167 243 2281
Bash 80 98 55 87 0.18 129 263 2581
Elixir 81 72 74 110 0.18 197 205 2177
Flutter 82 85 70 93 0.18 143 217 2534
ネタ 83 500 379 34 0.17 14 43 6037
Elm 84 107 128 60 0.17 109 131 3631
Zsh 85 55 273 262 0.17 348 61 898
競技プログラミング 86 121 80 68 0.17 89 193 3193
IoT 87 96 59 131 0.17 132 260 1843
MongoDB 88 56 202 329 0.17 341 82 705
Azure 89 86 46 205 0.16 142 301 1163
データ分析 90 130 109 62 0.16 79 154 3571
設計 91 262 308 38 0.16 33 53 5262
アルゴリズム 92 130 124 64 0.16 79 134 3517
UX 93 248 188 44 0.15 36 87 4563
初心者向け 94 901 106 46 0.15 6 155 4235
GoogleCloudPlatform 95 93 76 130 0.15 135 202 1848
pandas 96 146 74 79 0.15 66 205 2754
R 97 77 78 192 0.15 170 200 1268
PostgreSQL 98 94 51 287 0.15 134 280 801
api 99 199 68 89 0.14 47 225 2572
AI 100 194 119 65 0.14 48 136 3501

グループの決定

多次元データ分析に全く詳しくないため、直感的にフォロワー数/記事数/いいね値数の一部が多いもの一部が少ないもので下記の(6 * 3) + 1 = 19グループに分ける
※判定は+や-数が多い順に行う

グループ名 判定内容 タグ(TOP100内) 補足
いいね+++ rgba(255, 0, 0, 0.5) いいねの割合が一番高く、
他のポイントの2.5倍以上
DeepLearning
Firebase
新人プログラマ応援
ポエム
自然言語処理
個人開発
ネタ
設計
UX
初心者向け
記事数/フォロワー数の割にいいねが多い?
いいね++ rgba(191, 0, 0, 0.4) いいねの割合が一番高く、
他のポイントの1.6倍以上
Vue.js
機械学習
TypeScript
nuxt.js
MachineLearning
数学
Elm
データ分析
アルゴリズム
AI
記事数/フォロワー数の割にいいねが多い?
いいね+ rgba(127, 0, 0, 0.3) いいねの割合が一番高く、
他のポイントの1.1倍以上
Python
React
VSCode
Slack
Keras
OpenCV
記事数/フォロワー数の割にいいねが多い?
フォロワー+++ rgba(0, 0, 255, 0.5) フォロワーの割合が一番高く、
他のポイントの2.5倍以上
Git
Linux
GitHub
HTML5
MySQL
Qiita
Vim
jQuery
Chrome
iPhone
Xcode
Twitter
CSS3
C
MacOSX
CentOS
ShellScript
正規表現
SSH
Apache
nginx
Scala
Haskell
Emacs
Objective-C
Zsh
MongoDB
記事数/記事内容共に不足している?
フォロワー++ rgba(0, 0, 191, 0.4) フォロワーの割合が一番高く、
他のポイントの1.6倍以上
HTML
Java
CSS
Node.js
Android
Mac
Heroku
golang
Rust
記事数/記事内容共に不足している?
フォロワー+ rgba(0, 0, 127, 0.3) フォロワーのポイントが一番高く、
他のポイントの1.1倍以上
iOS
C++
C#
Go
Elixir
記事数/記事内容共に不足している?
記事数+++ rgba(0, 255, 0, 0.5) 記事数の割合が一番高く、
他のポイントの2.5倍以上
- フォロワーが少なく、いいねがもらいにくい?
記事数++ rgba(0, 191, 0, 0.4) 記事数の割合が一番高く、
他のポイントの1.6倍以上
Python3
Django
Windows
SQL
lambda
フォロワーが少なく、いいねがもらいにくい?
記事数+ rgba(0, 127, 0, 0.3) 記事数の割合が一番高く、
他のポイントの1.1倍以上
AWS
Laravel
IoT
GoogleCloudPlatform
フォロワーが少なく、いいねがもらいにくい?
いいね--- rgba(0, 255, 255, 0.4) いいねの割合が一番低く、
他のポイントの0.4倍以下
Ubuntu 記事数、フォロワー数は比較的多いが、いいねがつきにくい?
いいね-- rgba(0, 191, 191, 0.3) いいねの割合が一番低く、
他のポイントの0.625倍以下
Ruby
PHP
Rails
Azure
R
PostgreSQL
記事数、フォロワー数は比較的多いが、いいねがつきにくい?
いいね- rgba(0, 127, 127, 0.2) いいねの割合が一番低く、
他のポイントの0.9倍以下
Swift
Kotlin
記事数、フォロワー数は比較的多いが、いいねがつきにくい?
フォロワー--- rgba(255, 255, 0, 0.4) フォロワーの割合が一番低く、
他のポイントの0.4倍以下
初心者
docker-compose
api
フォロワー数は比較的少ないが、記事は多く、いいねもついている?
フォロワー-- rgba(191, 191, 0, 0.3) フォロワーの割合が一番低く、
他のポイントの0.625倍以下
競技プログラミング
pandas
フォロワー数は比較的少ないが、記事は多く、いいねもついている?
フォロワー- rgba(127, 127, 0, 0.2) フォロワーの割合が一番低く、
他のポイントの0.9倍以下
Unity
kubernetes
gcp
Bash
Flutter
フォロワー数は比較的少ないが、記事は多く、いいねもついている?
記事数--- rgba(255, 0, 255, 0.4) 記事数の割合が一番低く、
他のポイントの0.4倍以下
- 記事数は比較的少ないが、フォロワーは多く、いいねもついている?
記事数-- rgba(191, 0, 191, 0.3) 記事数の割合が一番低く、
他のポイントの0.625倍以下
JavaScript
TensorFlow
記事数は比較的少ないが、フォロワーは多く、いいねもついている?
記事数- rgba(127, 0, 127, 0.2) 記事数の割合が一番低く、
他のポイントの0.9倍以下
- 記事数は比較的少ないが、フォロワーは多く、いいねもついている?
バランス rgba(0, 0, 0, 0.4) 上記以外
分散が近い
Docker
RaspberryPi
GoogleAppsScript
-

グラフ化

縦軸が「記事数」、横軸が「フォロワー数」、円の大きさを「いいね数」で表して、上記のグループ色でグラフ化しました。

See the Pen qiita_tag_score_v2 by j5c8k6m8 (@j5c8k6m8) on CodePen.

tooltipとかスケールとか頑張ればもう少し何とかなりますが、疲れたので。

考察等

  • グラフで見ると、「Python」と「JavaScript」が圧倒的。JavaScriptはフォロワー数やいいね数に比べると記事が不足気味
  • 記事数よりもいいね数のほうが顕著にトレンドを表しているように見える。2018年度の第4四半期でいうとDeepLearning,Firebase,自然言語処理,Vue.js,機械学習,TypeScript,nuxt.js,Elmあたり
  • RUby,Rails,PHPは安定し記事数やフォロワー数に対して「いいね」はつきにくい。
  • AWSは記事数は多いが、いいね数は少ない
  • gitを含めフォロワー数が多いが、記事数といいね数が少ない安定した領域が多い←これはPythonやJavaScriptにデータが引っ張られている要因もあるかも

コード

使用した全コードはこちら

汚いので参考まで

getItems.js
const moment = require('moment')
const DATE_PIRIOD = [ '2018-12-01', '2019-03-31' ];

let request = require('request');
let fs = require("fs");

let headers = {
    'Authorization': 'Bearer 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd' // token
};

let itesm = [];
let users = [];

function getDayItems(argMoment, cnt, callback) {
  let options = {
    url: `https://qiita.com/api/v2/items?page=${cnt}&per_page=100&query=created:<${argMoment.format('YYYY-MM-DD')}`,
    headers: headers
  }
  request(options, function(error, response, body) {
    if (error) {
        return console.error('ERROR!', error);
    } else {
        let subtractDate = moment(argMoment.format('YYYY-MM-DD') + 'T09:00:00+09:00').subtract(1, 'days');
        console.log(subtractDate.format('YYYY-MM-DD'));
        let tmp = JSON.parse(body).filter( v => subtractDate.isSameOrBefore(v.created_at));
        users = users.concat(tmp.map( v => v.user.id));
        itesm = itesm.concat(tmp.map( v => { return { id: v.id, likes_count: v.likes_count, tags: v.tags, created_at: v.created_at}; }));
        if (tmp.length === 100) {
          setTimeout( () => getDayItems(argMoment, cnt + 1, callback), 3600); // 3,600秒 / 1,000回
        } else {
          callback();
        }
    }
  });
}

function getPeriod(target_moment, end_moment) {
  if (end_moment.isSameOrBefore(target_moment)) {
    getDayItems(target_moment, 1, () => getPeriod(target_moment.subtract(1, 'days'), end_moment));
  } else {
    fs.writeFile('itesm.json', JSON.stringify(itesm) , (err, data) => {});
    fs.writeFile('users.json', JSON.stringify(users.filter((x, i, self) => self.indexOf(x) === i)), (err, data) => {});
    fs.writeFile('created_at.list', itesm.map( v => v.created_at ).join('\n') , (err, data) => {});

  }
}
getPeriod(moment(DATE_PIRIOD[1]).add(1, 'days'), moment(DATE_PIRIOD[0]).add(1, 'days'));
getFollowers.js
let request = require('request');
let fs = require("fs");

let headers = {
    'Authorization': 'Bearer 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd' // token
};

let tags = [];
let followers = [];

function getFollowers(user_id) {
  return new Promise( (resolve) => {
    let options = {
      url: `https://qiita.com/api/v2/users/${user_id}/following_tags`,
      headers: headers
    }
    request(options, function(error, response, body) {
      if (error) {
          return console.error('ERROR!', error);
      } else {
          let followers = JSON.parse(body);
          fs.writeFile(`./followers/${user_id}.json`, JSON.stringify(followers) , (err, data) => {});
          setTimeout( () => resolve(followers), 3600); // 3,600 * 1000秒 / 1,000回
      }
    });
  });
}

function getFollowersByFile(path) {
  return new Promise( (resolve) => {
    fs.readFile(path, 'utf8', function (err, text) {
      let followers = JSON.parse(text);
      resolve(followers);
    });
  });
}

(async () => {
  fs.readFile('./users.json', 'utf8', async (err, text) => {
    let user = JSON.parse(text);
    for (let i = 0; i < user.length; i++) {
      let follower_tags;
      try {
        fs.statSync(`./followers/${user[i]}.json`);
        follower_tags = await getFollowersByFile(`./followers/${user[i]}.json`);
      } catch(err) {
        follower_tags = await getFollowers(user[i]);
      }
      tags.push(follower_tags);
      console.log(tags.length);
    }
    fs.writeFile('followers.json', JSON.stringify(tags) , (err, data) => {});
  });
})()

createTags.js
let fs = require("fs");
let tag_Hash = {};
let tags = [];

fs.readFile('./items.json', 'utf8', function (err, text) {
  let items = JSON.parse(text);
  items.forEach(item => {
    item.tags.forEach( tag => {
      if (tag.name) {
        if (!tag_Hash[tag.name]) {
          tag_Hash[tag.name] = { id: tag.name, items_count: 0, followers_count: 0, likes_count: 0 };
          tags.push(tag_Hash[tag.name]);
        }
        tag_Hash[tag.name].items_count += 1;
        tag_Hash[tag.name].likes_count += item.likes_count;
      }
    });
  });

  fs.readFile('./followers.json', 'utf8', function (err, text) {
    let followers = JSON.parse(text);
    followers.forEach(follower_tags => {
      if (follower_tags.type !== 'not_found') {
        follower_tags.forEach( tag => {
            if (!tag_Hash[tag.id]) {
              tag_Hash[tag.id] = { id:tag.id, items_count: 0, followers_count: 0, likes_count: 0 };
              tags.push(tag_Hash[tag.id]);
            }
            tag_Hash[tag.id].followers_count += 1;
        });
      }
    });
    fs.writeFile('tags.json', JSON.stringify(tags) , (err, data) => {});
  });
});

editJson.js
// tags.jsonにデータが配置している前提
const OUTPUT_RANKING_NUM = 100;
const WEIGHT = { item_count: 1, followers_count: 1, likes_count: 1,};

let fs = require("fs");

const set_rank = (arr, attr, sort_flg) => {
  let target = sort_flg ? arr : arr.slice();
  target.sort( (a, b) => b[attr] - a[attr] );
  let tmp_ranking = 0;
  let tmp_score = 9999999999;
  target.forEach( (v, i) => {
    if (v[attr] < tmp_score) {
      tmp_score = v[attr];
      tmp_ranking = i + 1;
    }
    v[`${attr}_rank`] = tmp_ranking;
  })
}

const get_ranked_json = (tags, num, weight) =>{
  let total_items_count = tags.reduce( (s, v) => v.items_count + s, 0)
  let total_followers_count = tags.reduce( (s, v) => v.followers_count + s, 0)
  let total_likes_count = tags.reduce( (s, v) => v.likes_count + s, 0)
  console.log(`総タグ数: ${tags.length}, 総記事数: ${total_items_count}, 総フォロワー数: ${total_followers_count}, 総いいね数: ${total_likes_count}`);
  tags = JSON.parse(JSON.stringify(tags)); // 属性を書き換えるのでオブジェクトをコピーする

  set_rank(tags, 'items_count');
  set_rank(tags, 'followers_count');
  set_rank(tags, 'likes_count');
  tags.forEach( t => {
    t.items_point = t.items_count * 100 / total_items_count;
    t.followers_point = t.followers_count * 100 / total_followers_count;
    t.likes_point = t.likes_count * 100 / total_likes_count;
    t.score = (t.items_point * weight.item_count + t.followers_point * weight.followers_count + t.likes_point * weight.likes_count) / (weight.item_count + weight.followers_count + weight.likes_count);
    t.score_old = (t.items_point * weight.item_count + t.followers_point * weight.followers_count) / (weight.item_count + weight.followers_count);
    if (t.items_point > t.followers_point * 2.5 && t.items_point > t.likes_point * 2.5) {
      t.type = 'ITEM+++';
      t.color = 'rgba(0, 255, 0, 0.5)';
    } else if (t.followers_point > t.items_point * 2.5 && t.followers_point > t.likes_point * 2.5) {
      t.type = 'FOLLOWERS+++';
      t.color = 'rgba(0, 0, 255, 0.5)';
    } else if (t.likes_point > t.items_point * 2.5 && t.likes_point > t.followers_point * 2.5) {
      t.type = 'LIKES+++';
      t.color = 'rgba(255, 0, 0, 0.5)';
    } else if (t.items_point * 2.5 < t.followers_point && t.items_point * 2.5 < t.likes_point) {
      t.type = 'ITEM---';
      t.color = 'rgba(255, 0, 255, 0.4)';
    } else if (t.followers_point * 2.5 < t.items_point && t.followers_point * 2.5 < t.likes_point) {
      t.type = 'FOLLOWERS---';
      t.color = 'rgba(255, 255, 0, 0.4)';
    } else if (t.likes_point * 2.5 < t.items_point && t.likes_point * 2.5 < t.followers_point) {
      t.type = 'LIKES---';
      t.color = 'rgba(0, 255, 255, 0.4)';
    } else if (t.items_point > t.followers_point * 1.6 && t.items_point > t.likes_point * 1.6) {
      t.type = 'ITEM++';
      t.color = 'rgba(0, 191, 0, 0.4)';
    } else if (t.followers_point > t.items_point * 1.6 && t.followers_point > t.likes_point * 1.6) {
      t.type = 'FOLLOWERS++';
      t.color = 'rgba(0, 0, 191, 0.4)';
    } else if (t.likes_point > t.items_point * 1.6 && t.likes_point > t.followers_point * 1.6) {
      t.type = 'LIKES++';
      t.color = 'rgba(191, 0, 0, 0.4)';
    } else if (t.items_point * 1.6 < t.followers_point && t.items_point * 1.6 < t.likes_point) {
      t.type = 'ITEM--';
      t.color = 'rgba(191, 0, 191, 0.3)';
    } else if (t.followers_point * 1.6 < t.items_point && t.followers_point * 1.6 < t.likes_point) {
      t.type = 'FOLLOWERS--';
      t.color = 'rgba(191, 191, 0, 0.3)';
    } else if (t.likes_point * 1.6 < t.items_point && t.likes_point * 1.6 < t.followers_point) {
      t.type = 'LIKES--';
      t.color = 'rgba(0, 191, 191, 0.3)';
    } else if (t.items_point > t.followers_point * 1.1 && t.items_point > t.likes_point * 1.1) {
      t.type = 'ITEM+';
      t.color = 'rgba(0, 127, 0, 0.3)';
    } else if (t.followers_point > t.items_point * 1.1 && t.followers_point > t.likes_point * 1.1) {
      t.type = 'FOLLOWERS+';
      t.color = 'rgba(0, 0, 127, 0.3)';
    } else if (t.likes_point > t.items_point * 1.1 && t.likes_point > t.followers_point * 1.1) {
      t.type = 'LIKES+';
      t.color = 'rgba(127, 0, 0, 0.3)';
    } else if (t.items_point * 1.1 < t.followers_point && t.items_point * 1.1 < t.likes_point) {
      t.type = 'ITEM-';
      t.color = 'rgba(127, 0, 127, 0.2)';
    } else if (t.followers_point * 1.1 < t.items_point && t.followers_point * 1.1 < t.likes_point) {
      t.type = 'FOLLOWERS-';
      t.color = 'rgba(127, 127, 0, 0.2)';
    } else if (t.likes_point * 1.1 < t.items_point && t.likes_point * 1.1 < t.followers_point) {
      t.type = 'LIKES-';
      t.color = 'rgba(0, 127, 127, 0.2)';
    } else {
      t.type = 'NORMAL';
      t.color = 'rgba(0, 0, 0, 0.3)';
    }
    t.diff = t.followers_point - t.items_point;
  })
  set_rank(tags, 'score_old');
  set_rank(tags, 'score', true);
  return tags.slice(0, num);
}

const to_chart_data = v => {
  return { label: v.id, data: [ { x: v.followers_count, y: v.items_count, r: 6 + (8 * v.likes_point) } ], backgroundColor: v.color }
};

fs.readFile('./tags.json', 'utf8', function (err, text) {
  let tags = JSON.parse(text);
  tags = get_ranked_json(tags, OUTPUT_RANKING_NUM, WEIGHT);

  let str = '"タグ名",評価値順位,フォロワー数順位,記事数順位,いいね数順位,評価値,フォロワー数,記事数,いいね数,タイプ\n';
  str += tags.map( v => `"${v.id}",${v.score_rank},${v.followers_count_rank},${v.items_count_rank},${v.likes_count_rank},${v.score},${v.followers_count},${v.items_count},${v.likes_count},${v.type}`).join('\n')
  fs.writeFile(`list.csv`, str , (err, data) => {});
  fs.writeFile(`chart.json`, JSON.stringify(tags.filter( v => v.score_rank <= OUTPUT_RANKING_NUM ).map(to_chart_data)) , (err, data) => {});

  // 最後ソートしてから
  tags.sort( (a, b) => b.score_old - a.score_old );
  str = '"タグ名",評価値順位,フォロワー数順位,記事数順位,評価値,フォロワー数,記事数,評価値差分,\n';
  str += tags.map( v => `"${v.id}",${v.score_old_rank},${v.followers_count_rank},${v.items_count_rank},${v.score_old},${v.followers_count},${v.items_count},${v.diff}`).join('\n')
  fs.writeFile(`list_old.csv`, str , (err, data) => {});

});

さいごに

意外と感覚的に正しそうな値が出たので満足したが、2018年度の第4四半期以外に、時系列で同様の分析をしても新たな発見があるかも。(リクエストがあれば。)

これで、関連記事に同じ観点の記事が出たら負け。

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

WordPressプラグイン作成の勉強をしてみた

wordpress初心者です。サンプルは調べながら作成しました、間違いなどありましたら指摘お願いします:bow:

※この記事は @technote-space さんからの指摘により修正中となっております:construction::pick:

参考

サンプル

Server-Sent Events(SSE)を使用した簡易チャットプラグインを作成してみました

確認バージョン

  • PHP 7.2
  • wordpress 5.2.1
  • Google Chrome 74.0.3729.169

ソースはこちらです

EIrvCHs1Ic.gif

この記事の概要

wordpressの基本的な機能を勉強しながら、簡易チャットプラグインを作成していきます


最終的なコードを見たい方はこちらを見て、本記事は読まなくて良いです。

私が調べながら作っていった手順を一歩ずつ書いていく内容となっています。
間違いやより良いやり方がある場合は教えてください。

対象読者

  • wordpressのplugin作成に壁を感じてる方

    • 初心者の私でもやればできたというのが伝わればと、
  • wordpress初心者 (wordpressを画面から操作したことがあり、PHPの基礎的な知識がある)

    • plugin作成はそこまで難しくなかったので、初心者がいきなりplugin作成してみる、もありだと思いました

1 プラグインをwordpressに認識させる

1-1 phpファイル作成

{root}/wp-content/plugins/simple-sse-chat/simple-sse-chat.php

pluginsディレクトリに作成したいプラグイン用のフォルダと、それと同じ名前のphpファイルを作成します

image.png

1-2 プラグイン名と説明を設定

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php
/*
    Plugin Name: Simple SSE Chat
    Description: Server Snet Eventsを使用した簡易チャット
*/

この状態で管理画面のプラグインのページをみると、プラグインを確認することができます

Screen Shot 2019-05-26 at 15.16.12.png

1-3 有効化する

有効化を押すと/wp-content/plugins/simple-sse-chat/simple-sse-chat.phpで書いた内容が実行されることになります。
現時点ではまだ何もおきません。

ZeOrdi3X3l.gif

2 管理画面のメニューに追加

2-1 add_action('admin_menu', $callback)

add_actionまたは、add_filterを使って、フックを設定します。
ここでは、管理メニューが読み込まれる前に実行される admin_menuを指定します。

フックについて参考:
【WordPress入門】アクションフックとフィルターフックを使いこなそう | 滋賀/京都/大阪でホームページ制作ならYUKiYURi WEB

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

add_action('admin_menu', function () {
    add_menu_page(
        'Simple Chat Settings', // <title>タグの内容を設定
        'Simple Chat', // 左メニューに表示される名前を設定
        'manage_options', // 権限
        'simple-sse-chat', // スラッグ
        'admin_menu_simple_sse_chat', // メニューを開いたときに実行される関数名
        'dashicons-admin-comments', // アイコン
        200 // メニューの表示順、200と大きい数字にしたので、メニューの一番下に表示される
    );
});
function admin_menu_simple_sse_chat() {
    ?>
        <div class="wrap">
            <h2>Simple Chat Settings</h2>
        </div>
    <?php
}

Screen Shot 2019-05-26 at 15.32.02.png

メニューに追加され、ページを表示することができました。

2-2 get_optionupdate_optionで入力値を保存する

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

add_action('admin_menu', function () {
    add_menu_page(
        'Simple Chat Settings', // <title>タグの内容を設定
        'Simple Chat', // 左メニューに表示される名前を設定
        'manage_options', // 権限
        'simple-sse-chat', // スラッグ
        'admin_menu_simple_sse_chat', // メニューを開いたときに実行される関数名
        'dashicons-admin-comments', // アイコン
        200 // メニューの表示順、200と大きい数字にしたので、メニューの一番下に表示
    );
});
function admin_menu_simple_sse_chat() {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        update_option('simple_sse_chat_header', $_POST['chat_heder']);
        ?>
        <div id="setting-error-settings-update" class="update-settings-error notice is-dismissible"><strong>Settings have been saved.</strong></div>
        <?php
    }
    $chat_heder = get_option('simple_sse_chat_header', 'Simple Chat'); // 第2引数は指定のoption_nameがない場合の初期値
    ?>
        <div class="wrap">
            <h2>Simple Chat Settings</h2>
            <form method="POST" action="">
                <label for="chat_heder">チャットタイトル</label>
                <textarea name="chat_heder" class="large-text"><?= $chat_heder ?></textarea>
                <input type="submit" name="submit_scripts_update" class="button button-primary" value="UPDATE">
            </form>
        </div>
    <?php
}

sGFRYQa8ze.gif

wp_optionsテーブルに入力値が保存されていることが確認できました。

Screen Shot 2019-05-26 at 16 (2).png

3 ショートコードでチャットのHTMLを表示する

3-1 まず、簡単なHTMLだけを表示

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

add_shortcode('simple_sse_chat', function () {
    return '<span style="color:blue;">ショートコードのテスト</span>';
});

※wordpress 5からの投稿エディタのGutenberg(グーテンベルグ)でのショートコード設定方法↓

lGhEyE8N28.gif

ショートコードがページに表示できたのを確認できました。

3-2 チャット用のHTMLを表示

HTMLを見やすくするためにバッファリングしてHTMLを表示するようにしてみました。
get_option('simple_sse_chat_header', 'Simple Chat');として管理画面から登録した値を出力します。

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

add_shortcode('simple_sse_chat', function () {
    $header = get_option('simple_sse_chat_header', 'Simple Chat');
    ob_start(); // バッファリング開始
    ?>
    <form id="js-simple-sse-chat-form">
        <h2><?= $header ?></h2>
        <div class="simple-sse-chat-container">
            <table id="js-simple-sse-chat-body">
                <tbody></tbody>
            </table>
        </div>
        <input type="text" name="chat-content" id="js-simple-sse-chat-input">
        <input type="submit" value="送信">
    </form>
    <style>
        .simple-sse-chat-container {
            height: 200px;
            overflow: scroll;
        }
    </style>
    <?php
    return ob_get_clean(); // バッファからデータを取得してクリア
});

Screen Shot 2019-05-26 at 16.32.53.png

4 チャットデータ登録用のテーブルをDBに作成する

4-1 register_activation_hook()でプラグインがアクティブ化されたときに実行される関数を定義

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

register_activation_hook(__FILE__, function () {
    // プラグインがアクティブ化されたときに実行される
});

4-2 $wpdbを使用してDBを操作する

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

register_activation_hook(__FILE__, function () {
    global $wpdb;
    $sql = sprintf('CREATE TABLE %ssimple_sse_chat (
        `id` INT NOT NULL AUTO_INCREMENT,
        `user_id` INT NOT NULL,
        `content` TEXT NOT NULL,
        PRIMARY KEY (`id`)
    ) %s;', $wpdb->prefix, $wpdb->get_charset_collate());
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
});

一度停止して、最有効化しても実行してくれます

RPH5RSjPOU.gif

プラグイン有効化時にテーブルを作成してくれました。

Screen Shot 2019-05-26 at 16.44.50.png

5 チャットの入力値を登録する

5-1 javascriptを読み込む

jsファイルを作成します。

/wp-content/plugins/simple-sse-chat/script.js
console.log(simple_sse_chat_data) // <- PHPから渡される変数

image.png

今回はショートコードのHTMLをjavascriptで操作したいので、

  1. add_action('the_content', $callback)で、投稿や固定ページの内容が表示されたときに、
  2. has_shortcode($content, 'simple_sse_chat') で指定のショートコードが使われていた場合のみ、jsファイルを読み込みます。
/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

add_action('the_content', function ($content) {
    // ショートコードが使われているページのみjsを読み込む
    if (has_shortcode($content, 'simple_sse_chat')) {
        // script.jsにsimple_sse_chat_dataという名前のオブジュエクトを定義し、home_urlというプロパティを定義
        wp_enqueue_script('simple_sse_chat', plugin_dir_url(__FILE__) . 'script.js');
        wp_localize_script('simple_sse_chat', 'simple_sse_chat_data', [
            'home_url' => home_url(),
        ]);
    }
    return $content;
});

指定のjavascriptが読み込まれ、PHPからjavascriptにsimple_sse_chat_dataという変数名で値を渡せていることが確認できました。

Screen Shot 2019-05-26 at 17.04.34.png

5-2 wp_ajax_{action_name}でチャットの内容を登録する用のurlを設定

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

add_action('wp_ajax_chat_post', function () {
    global $wpdb;
    $wpdb->insert($wpdb->prefix.'simple_sse_chat', [
        'user_id' => get_current_user_id(),
        'content' => $_POST['chat-content'],
    ]);
});

↑のような書き方ですと/wp-admin/admin-ajax.php?action=chat_postでリクエストすると、コールバック関数を実行してくれます。

5-3 ajaxで登録処理をする

/wp-content/plugins/simple-sse-chat/script.js
const { home_url } = simple_sse_chat_data;

// 登録
document.getElementById("js-simple-sse-chat-form").addEventListener("submit", async e => {
    e.preventDefault();
    formData = new FormData(e.target)

    // 入力値が空の場合は何もしない
    if (formData.get("chat-content") === "") return;

    // 登録のリクエスト
    const res = await fetch(`${home_url}/wp-admin/admin-ajax.php?action=chat_post`, {
        method: "POST",
        body: formData,
    })

    // 入力欄を空にしからにしておく
    document.getElementById("js-simple-sse-chat-input").value = ""
});

6i9KVEcoqd.gif

チャットからの入力値をDBに保存することができました。

Screen Shot 2019-05-26 at 17.57.49.png

6 Server-Sent Eventsでチャットの内容を表示する

6-1 Server-Sent Eventsを実行するurlを定義する

/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
<?php

// ...

// ajaxのhookだが、SSEも問題なかったのでwp_ajax_{action_name}を使用しました
add_action('wp_ajax_event_streame', function () {
    global $wpdb;
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-store');
    while(true) {
        printf("data: %s\n\n", json_encode([
            'chat_data' => $wpdb->get_results(
                "SELECT s.id, s.content, u.user_login
                 FROM {$wpdb->prefix}simple_sse_chat s
                 LEFT JOIN {$wpdb->prefix}users u ON s.user_id = u.id
                 ORDER BY id DESC
                 LIMIT 10"
            ),
        ]));
        ob_end_flush();
        flush();
        sleep(1);
    }
});

6-2 javascriptで表示処理をする

/wp-content/plugins/simple-sse-chat/script.js
const { home_url } = simple_sse_chat_data;

// ...

const es = new EventSource(`${home_url}/wp-admin/admin-ajax.php?action=event_streame`);

// 表示
let lastId = 0;
es.addEventListener("message", e => {
    const { chat_data } = JSON.parse(e.data);

    // 更新がなければ何もしない
    if (lastId === (chat_data[0] ? chat_data[0].id : 0)) return;

    // jsonを受け取ってHTMLにして表示
    targetElement = document.getElementById("js-simple-sse-chat-body").querySelector("tbody");
    targetElement.innerHTML = ""
    chat_data.forEach(data => {
        const { user_login, content } = data
        targetElement.insertAdjacentHTML("afterbegin", `
            <tr>
                <td>
                    <small>${user_login}</small>
                    <br>
                    <strong>${content}</strong>
                </td>
            </tr>
        `)
    })

    // 新着があったら下にスクロール
    const scrollElement = document.querySelector(".simple-sse-chat-container")
    scrollElement.scrollTop = scrollElement.scrollHeight

    lastId = (chat_data[0] ? chat_data[0].id : 0);
});

Server-Sent Eventsでチャットの内容を表示することができました。

xMzAQP4EHQ.gif


最後まで読んでいただいてありがとうございました。m(_ _)m

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