2016/06/20


SPA でアプリケーションを作る際、angular1 の時はどうも「書かされている」感がして好きになれませんでした。x-tag を使って自前でカスタムエレメントを作っても良いのですが、それでも書かされている感がしますし、若干大げになりがちです。そんな中、angular 寄りで、かつ x-tag よりも薄い JavaScript ライブラリを tokuhirom がガシガシと作ってくれました。

GitHub - tokuhirom/sj.js

Tiny javascript view for custom elements based on incremental-dom. This library supports angular1 like templating.

https://github.com/tokuhirom/sj.js

x-tag の様に js 一つで動きます。そしてだいたい angular っぽく動きます。動作はコチラで確認して下さい。

<div sj-app="">
 
<p>Input something in the input box:</p>
<p>Name : <input type="text" sj-model="this.name" placeholder="Enter name here"></p>
<h1>Hello <span sj-bind="this.name"></span></h1>

</div>

まずこのソースの短さを見て下さい。コードは何も書いてません。機能的には angular のそれと同じですがモデル(sj-model)やバインド(sj-bind)を指定する際に this が必要という事が異なります。angular や riot.js の場合は独自に式をパースし実行ていますが、そこをスコープに依存した JavaScript 式で書ける様にしてあります(元々は式パーサも実装していましたがコードベースが肥大してきたのでこの方式に転換)。またテンプレートは angular の様に {{ name }} という書き方にする事は出来ません。以下の様にノードを用意する必要があります。

<h1>Hello <span sj-bind="this.name"></span></h1>

当初はこの {{ name }} というテンプレートを実装していましたが、サーバサイドテンプレートとの相性が良くない(間違って使った場合に意図しない eval 発火が起きる)のを理由に、バインド方式に変更されました。

イベントハンドラも一通り揃っています。

<script>
function CounterApp() {
  this.counter = 0;
  this.count = function() {
    this.counter++;
    this.update();
  }
}
</script>

<div sj-app="CounterApp">
<button sj-click="this.count()">Click Me!</button>
<p sj-bind="this.counter"></p>
</div>

this は sj-app 属性を持ったのノードを指します。試しに動かしてみたい方はコチラで確認して下さい。

また sj-app にはコールバック関数名を指定できるので初期化を行う事もできます。自前でデータを更新したい場合は update() を呼び出します。もちろん repeat も動きます。その他のサンプルはココから参照して下さい。

また sj ではカスタムタグを生成する事も可能です。

customElements.define('sj-books'class extends sj.Element {
  template() {
    return `
      <h3>Books</h3>
      <input type="text" sj-model="this.filter" placeholder="検索するキーワードを入力して下さい" class="books-filter" />
      <input type="button" sj-disabled="!!!this.filter" sj-click="this.clear()" value="クリア" />
      <div class="books-container">
        <div sj-repeat="x in this.books">
          <div class="item" sj-if="this.matched(x,this.filter)" sj-click="this.clicked($index)">{{x.name}}</div>
        </div>
      </div>
    `;
  }

  initialize() {
    this.books = [];
    this.clear = () => {
      this.filter = '';
      this.update();
    };
    this.clicked = (index) => {
      const URI = 'http://www.amazon.co.jp/gp/search/';
      location.href = URI + `?field-keywords=${encodeURIComponent(this.books[index].name)}`;
    };
    this.matched = (x,filter) => !!!filter || x.name.toLowerCase().indexOf(filter.toLowerCase()) != -1;
  }

  get books() {
    return this.b;
  }

  set books(b) {
    this.b = b;
    this.update();
  }
});

この例では書庫検索画面を sj-books という Web Component に仕上げています。動作を確認したい人はコチラで確認できます。コードを見て貰えると sj の薄さ、そして「書かされている感」がそれほどしない事が分かって頂けると思います。

angular 程色んな事が出来る訳ではありませんが、アプリケーションを作るには十分機能がそろったライブラリになっています。ライセンスは MIT です。

Posted at by



2016/06/08


某 slack で x-tag を教えて貰ったのでちょっと遊んでみた。
X-Tag ★ Web Components

Docs Table of Contents Getting Started Registration - Where it All Begins The most important method ...

http://x-tag.github.io/

X-Tag は Web Component を簡単きれいに作れるライブラリで、Microsoft からのサポートを受けている事をウリにしているらしいです。動作に必要なのはカスタムタグがサポートされているブラウザというだけで、polyfills も使う事が出来ます。

簡単なチュートリアルが Docs に書かれているのでパッと見ただけでだいたい API が予想できます。自前のメソッドや属性、イベントを定義する事が出来るのでコンパクトながら強力な Web Component を作る事が出来ます。何が良いかって1ファイルで動くのが良いですね。

xtag.register('x-foo'{
  content'‹input/›',
  lifecycle:{
    createdfunction(){},
    insertedfunction(){},
    removedfunction(){},
    attributeChangedfunction(){}
  },
  methods{
    someMethodfunction(){}
  },
  accessors{
    someAccessor{
      // links to the 'some-accessor' attribute
      attribute{},
      setfunction(){},
      getfunction(){}
    }
  },
  events{
    tapfunction(){},
    focusfunction(){}
  }
});

簡単な例として、入力値のバリデーションを行うコンポーネントを作って見ました。HTML は以下の様に記述します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>俳句</title>
<script src="x-tag-core.min.js"></script>
</head>
<body>
<p>例: 古池や蛙飛び込む水の音</p>

<x-haiku value=""></x-haiku>
<input type="button" value="送信" />

<script src="xtag-haiku.js"></script>
<script>
</script>
</body>
</html>

そしてソース

xtag.register("x-haiku"{
  content'<input type="text" placeholder="俳句を入力して下さい"/>',
  lifecycle{
    insertedfunction() {
      xtag.fireEvent(this"blur");
    }
  },
  methods{
    validateHaikufunction() {
      if (this.value.length > 0{
        var s = document.createElement('script');
        var xhr = new XMLHttpRequest();
        xhr.open('GET''http://127.0.0.1:8888/check?text=' + encodeURIComponent(this.value), false);
        xhr.send();
        return JSON.parse(xhr.responseText).result;
      }
      return false;
    }
  },
  events{
    blurfunction(){
      if (this.validateHaiku())
        this.firstElementChild.style.borderColor = '';
      else
        this.firstElementChild.style.borderColor = 'red';
    }
  },
  accessors{
    value{
      attribute{},
      getfunction(){
        return this.firstElementChild.value;
      },
      setfunction(value){
        this.firstElementChild.value = value;
      }
    }
  }
});

プレースホルダの通り、俳句を入力して貰い俳句でない場合には入力ボックスを赤枠表示する Web Component です。さすがに js だけで俳句かどうかを検証するのは難しいので golang でサーバを書きました。

package main

import (
    "encoding/json"
    "net/http"
    "strings"

    "github.com/mattn/go-haiku"
)

func main() {
    http.HandleFunc("/check"func(w http.ResponseWriter, r *http.Request) {
        params := r.URL.Query()
        ts, ok := params["text"]
        if !ok || len(ts) != 1 {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }
        s := strings.TrimSpace(ts[0])
        if s == "" {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }
        w.Header().Set("Access-Control-Allow-Origin""*")
        json.NewEncoder(w).Encode(
            struct {
                Result bool `json:"result"`
            }{
                haiku.Match(s, []int{575}),
            },
        )
    })

    http.Handle("/", http.FileServer(http.Dir(".")))

    http.ListenAndServe(":8888"nil)
}

最初は Google Appengine で動かそうと思ったのですが、1ファイル32MB制限だったり、メモリ不足で動かなかったり色々起きたのでローカルで動かしています。

俳句

割と簡単に実装できるし他と干渉しないので仕事でも使って行けそうです。あと Microsoft がサポートしているので暫くは消えて無くならないってのも良いですね。

Posted at by



2016/06/03


僕はもっぱらコマンドラインで作業するので peco を使う事が多いです。

Big Sky :: Windows のコマンドプロンプトを10倍便利にするコマンド「peco」

Windows ユーザのごく一部には、コマンドプロンプトが無いと生きられない民族がいます。そしてその民族の一部には cygwin や msys bash 等といった、サードパーティなシェル(power...

http://mattn.kaoriya.net/software/peco.htm
GitHub - mattn/pcd: peco + cd = awesome!

README.md pcd peco + cd = awesome! Requirements peco Windows Installation Copy pcd.bat into your fav...

https://github.com/mattn/pcd

ただユースケースとして peco だと若干大げさになる事があって、絞り込んだりしなくて良くただ単純に候補を選ぶだけでいいって事がたまにあります。例えば画面に表示された内容を見ながら候補を選びたいであったり、「以下の中から選んで下さい」といった説明のあとユーザに数個の候補から選ばせるといったバッチコマンドを作る場合です。peco だと画面が隠れてしまうんです。さらに peco は termbox-go を使っていて termbox-go のバグっぽい物を踏んだりするし、Windows でワイド文字 API を使っている手前ラスタフォントを使わないと画面が崩れる事があります。正直エスケープシーケンスを使えば候補を選ぶくらいなら出来るし、Windows でも go-colorable があればある程度のエスケープシーケンスは扱えると分かっていたので peco とは違う路線で薄いコマンドラインセレクタを作ってみました。

GitHub - mattn/cho

README.md cho choice! Why cho? Why not choice ? Because Windows already have choice command. Why not...

https://github.com/mattn/cho

はじめは「choice」というコマンド名で開発していたのだけど、よく考えたら Windows には choice コマンドが既にあって、「じゃぁ choic?」「いやいや」「では choi は?」「ダメだろ」「それなら cho だ!」くらいの感覚でネーミングしました。ただ適当に名付けた割にキーボードのホームポジションからそれ程手を動かさなくてもいいので実はちょっと気に入っています。使い方は peco と変わりません。ただし絞り込み機能は提供していませんので、ながーいファイルから1つ選びたいなら頑張って j/k するか大人しく peco を使って下さい。

cho

Windows、Linux、BSD、Mac OSX で動作します。go-colorable にいくらか修正を入れているので最新版を使ったほうがいいです。

おまけ

例えば Windows でコマンドを実行し cho で選んだ結果を変数に入れる様にしたい場合(UNIX の FOO=`ls | cho`)は以下の様なバッチファイル「setvar.bat」を用意しておくと便利です。

@echo off
for /f "delims=;" %%i in ('%2 ^| nkf -Sw ^| cho -cl ^| nkf -Ws') do set %1=%%i

nkf 持ってない人はコチラ(結果がUTF-8になっちゃうけど

@echo off
for /f "delims=;" %%i in ('%2 ^| cho -cl') do set %1=%%i

すると以下のコマンドで dir の出力結果を cho で選んだ内容が変数 foo に入ります。

setvar foo "dir /b"
Posted at by