htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

BookLiveで今まで使ってきた金額を出すスクリプト書いた

いやー久しぶりにJavaScript書いたわー。

(function(d){var s=d.createElement("script");s.src="//dl.dropboxusercontent.com/u/414379/www/BookLivePaid/script.js";d.body.appendChild(s)})(document)

一時期Amazon.co.jpで使った額を調べるスクリプトが流行ったことがありましたが、アレのBookLive版だと思えば大体あってます。

使い方

  1. 上のコードをコピーします。
  2. http://booklive.jp/my/top に移動してBookLiveにログインします。
    • すでにログイン済みならトップページで実行しても問題ないはず。
  3. 開発者コンソール出す。
    • IE/Firefox/Chromeなど主要ブラウザなら「F12」キーで出せます。
  4. 貼り付けてEnter。
  5. コンソールに結果が出力されます。


動作確認はIE11でしかしていません。
たぶん他でも動くと思いますが、Firefoxさん辺りは実行前に警告文出すかも。

IE/Firefox/Chromeのそれぞれ最新版で正常に動作することを確認しました。

ソース

実際に実行されるスクリプトの中身です。
呼び出すソースはDropboxにアップロードしたものですが、コードは同一です。

main();

function main() {
    var now = new Date();
    var startYear = now.getFullYear();
    var startMonth = now.getMonth() + 1;
    
    var paid = 0;
    var least = prompt("何年まで遡りますか?", startYear);
    if (!/^[0-9]{4}$/.test(least)) {
        console.warn("半角4文字の西暦で入力してください。");
        return;
    }
    
    for (var y = startYear + 1; --y >= least; ) {
        for (var m = (y === startYear ? startMonth : 12) + 1; --m > 0; ) {
            paid += monthSum(y, m);
            console.info("ここまでの累計: " + paid + "円");
        }
    }
    console.info("合計: " + paid + "円");
}

function monthSum(year, month) {
    var sum = 0;
    console.info(year + "年" + month + "月のリクエスト中...");
    var doc = request("/my/product?year=" + year + "&month=" + month);
    var nodes = doc.querySelectorAll('[id^="myproduct_display"]');
    
    if (nodes.length !== 0) {
        sum = [].slice.call(nodes)
            .map(function(e){ return e.textContent.trim(); })
            .filter(function(text){ return text.indexOf("円") === text.length - 1; })
            .map(function(price){ return parseInt(price.split(",").join("")); })
            .reduce(function(a, b){ return a + b; });
    }
    
    console.info(sum + "円");
    return sum;
}

function request(url) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, false);
    xhr.send(null);
    
    var range = document.createRange();
    return range.createContextualFragment(xhr.responseText);
}

ちなみに

私の消費金額は2013年1月から7月12日現在で654,126円らしいです。

今更Brainf*ckインタプリタ

何番煎じだよって感じのネタ。
まぁ、JavaScriptの練習ですね。

https://dl.dropboxusercontent.com/u/414379/www/brainf_ck/brainf_ckInterpreter.html

素地自体はあっさり作れたんですが、細かいところをアレも気になるコレも気になるってな感じでちょこちょこ弄っていたら、なんだかんだで3日くらい開発にかかってしまいました。

デザインには全く気を払っていないのでCSSは未使用です。


ところでイベントリスナの登録用メソッドについては、エラーハンドリングとか全然してませんしIE8以下全滅ですけど、それなりに流用できるのではないかな?と思います。
こんなの↓

HTMLElement.prototype.addEvent = function(){
    var args = Array.apply(null, arguments);
    
    switch (args.length) {
        case 1: addEventObj.call(this, args[0]); break;
        case 2:
            if (typeof args[0] === "string") {
                this.addEventListener(args[0], args[1].bind(this), false);
            }
            else {
                addEventArr.call(this, args[0], args[1]);
            }
            break;
    }
    
    function addEventObj(eventSet){
        var DELIMITER = ",";
        
        var name = "";
        var event = {};
        var namesArray = [];
        
        for (name in eventSet) {
            event = eventSet[name];
            
            if (name.indexOf(DELIMITER) !== -1) {
                namesArray = name.split(DELIMITER).map(function(e){ return e.trim() });
                addEventArr.call(this, namesArray, event);
            }
            else {
                this.addEventListener(name, event.bind(this), false);
            }
        }
    }
    function addEventArr(eventNameList, event) {
        eventNameList.forEach(function(e){
            this.addEventListener(e, event.bind(this), false);
        }, this);
    }
};

※ここではインデント浅くするためにprototypeに直接生やしてますが、元コードではObject.defineProperty使ってます。

これによって、例えば

var sample = document.getElementById("sample");

// 普通の単なるエイリアスっぽい書き方
sample.addEvent("click", function(){
    alert("hello!");
});

// 一気に複数リスナ指定
sample.addEvent(["click", "focus"], function(){
    alert("hello!");
});

// 一気に複数イベント指定
sample.addEvent({
    "click": function(){
        alert("hello!");
    },
    "focus": function(){
        alert("yeah!");
    }
});

// もちっと複雑に
sample.addEvent({
    "click": function(){
        alert("hello!");
    },
    "focus, blur": function(){
        alert("yeah!");
    }
});

みたいなコードでイベントリスナの追加ができるようになります。
あれ、それjQueryでできるんじゃね?と思ったアナタはスルドイ!!

でも最後の複雑な複数イベント登録はプラグイン無しの素のjQueryではできないと思っているんですが、どうでしょうか。実はできるのかな…。うーん。
まぁjQueryを使わないところに価値があるということで。


この記事書き始めてから思ったけど、callとかbindとか使わなくても、先頭の方でvar _this = this;とか書いておいてそれ参照すりゃ済む話ですね…。

追記 (2014/05/24 23:42)

今までIE11でしかテストしてなかったから全然気づかなかったけど、Firefox, Chromeで動かすとイベントハンドリングがうまく動いていない。
これは酷い。
でもFirefoxはともかくとしてChromeは普段使ってないからどうでもいいや。
気が向いたら修正しますが多分やりますん。

IEスキーとして一言物申したい

Web開発に必携の「Google Chrome デベロッパーツール」の便利ワザ10個まとめ*二十歳街道まっしぐら
を読んで、

うん!WebkitのDevTools便利だよね!でもそれほとんどIE11のF12 開発者ツール(以下、開発者ツール)でもできるよ!!

って言いたかった。
わざわざ対抗する意味あるのかとかそういうのは置いといて。


元記事に倣って、順に書いていきたいと思います。

1.開発者ツールのウィンドウ位置の変更

f:id:htsign:20140201220138p:plain
起動後、張り付いてる状態


f:id:htsign:20140201220539p:plain
切り離すにはここをクリック(右側配置はIEでは無理)

2.虫眼鏡で要素の選択

f:id:htsign:20140201221603p:plain
要素を選択中

要素サイズがリアルタイムで見られないのはちょい残念。
でも隣や上下の要素とのズレが見やすいのは評価してあげてもいいと思うんだ。

3.ファイルの横断検索

は無理です。


f:id:htsign:20140201222254p:plain
逐次検索はできるよ。

4.スタイルシートの有効/無効、追加/編集

f:id:htsign:20140201223405p:plain
カラーパレットは使えないので直感的ではない

使える値の候補は出てくるので、最低限の補完機能はある。

5.スマートフォンページの動作確認

これは簡単に使える。

f:id:htsign:20140201223952p:plain
ただし、モバイルのUAはデフォルトでWindows Phoneしかないので、その他を使いたい場合は適宜自分で追加する必要がある。
これが少し、いやかなり面倒。

f:id:htsign:20140201224319p:plain
カスタムUAは一度追加すれば記憶してくれるので、2度目以降は比較的楽に設定できる。
ちなみにiPadUAは初めからある。

タッチイベントのエミュレーションは試してないから知らないけど、たぶんできないんじゃないかな…。

6.読み込まれている外部ファイルを一覧表示

これはできない。
文句なしにWebkit系のDevToolsが圧倒的に便利です。

一応ネットワークタブで監視をスタートしてF5すれば、読み込まれるファイルとその中身は確認できるけど、それは他でもできるし。


ただし、JSファイルに限れば一覧できる。


f:id:htsign:20140201234312p:plain
なぜかChakraエンジン内でのみ実行されるコードも列挙される

7.圧縮されたソースコードを整形

f:id:htsign:20140201225701p:plain
before

f:id:htsign:20140201225709p:plain
after

右上の{三}みたいなところクリックすれば見やすくなる。

その右隣のabcって書かれたやつは、画面端でコードを折り返すかどうか。
見やすい方をお好みで。

8.JavaScriptデバッグする

これは余裕でできます。
というかこんな基本的な機能もないやつが開発者向けのツール名乗っちゃいかんでしょ。

f:id:htsign:20140201230711p:plain
ブレークポイント打ったり、一時停止・再開、ステップイン、ステップオーバー、ステップアウトなど、当たり前のことは当たり前にできる。


f:id:htsign:20140201231045p:plain
ウォッチ式一覧と、コールスタック、ブレークポイント

コールスタックとブレークポイントはタブ切り替え方式で、選択した方がその下に表示される。
もちろん自分でウォッチ式を追加することもできる。

9.コンソールでJavaScriptを実行

これは言わずもがな。
f:id:htsign:20140201231838p:plain
もちろんできる

ただ、IE10まではJavaScriptの補完が効かなかったので、ここに関してはやっと他ブラウザに追いついた感じ。

10.JavaScriptのパフォーマンスチェック

f:id:htsign:20140201232539p:plain
パフォーマンスの統計と


f:id:htsign:20140201232548p:plain
どの機能がどれだけ呼ばれてどれだけ時間を使ったかと


f:id:htsign:20140201232556p:plain
メモリの使用量

以上、まとめ

うーん、こうやって見ると開発者ツールって割と柔軟性がないな…。
まぁ俺はこっちのが慣れてるから好きだけどね!

あ、あと開発者ツールは完全日本語化されてるから機能の理解はしやすいと思う。
開発者ならある程度は英語読めるだろうから関係ないかもしんないけど。


エントリ執筆当初は文体の9割をパクって書いてたけど、流石にどうなの?と思ってかなり雑に書き直した。
小見出しが丸パクリなのはその名残。

DOM弄り

仕事中息抜きに書いてた。
文書の元々のソースコードと実際の表示が違うというのをやってみたかった。

ちなみに文書を読み込んだ時点で文書型は決まってしまうので、あとからDOCTYPE書き換えても意味ないです。

https://dl.dropboxusercontent.com/u/414379/www/DOMreplace/index.html

文字コードUTF-8(BOMなし)で作ったけど、英数だけだとバイナリレベルで見たときにASCIIと変わらない。
そうなると、DOM操作で日本語を挿入したときにWebブラウザが盛大に文字化け*1するので、それを防ぐために無理やり日本語をすべり込ませてみた。

DOCTYPEやDocumentそのものを作り出すメソッドがdocument.implementationにあるけど、まぁ普通にJavaScriptコードを書く分には一生お世話にならないメソッドたちでしょう。
JavaPerlなどの高位なプログラムからXMLを出力するときに使う為にあるものかなと。

CSSは特に何か意識したわけでもなく、なんとなく書いた。


JavaScript内で

var pi = document.createProcessingInstruction('xml', 'version="1.0" encoding="UTF-8"');
document.appendChild(pi); //==> <?xml version="1.0" encoding="UTF-8"?>

ってやってるけど、XML宣言はPIではないので、これは実は間違い。
比較的新しいバージョンのMozilla系エンジンでは動くけど、その他では例外を吐く。
その為try-catchで捕まえてエラーコードだけ出力するようにしておいてある。
XML宣言は、文字コードUTF-8の場合は省略してもいいことになっており、それ故にXHTMLでValidだと思います。

それから、createElementで要素生成してるけど、今回のケースではXHTMLで作っていることを明示的に示す必要があると思うので、本当ならcreateElementNSを使うべきだと思った。
めんどくさいのでしてない。

また、極力Web標準仕様に則って書いているため、古いIEIE7以前?)では

elem.setAttribute("class", "stretchable");

で処理がうまく通らないと思う。
理由は、古いIEではsetAttributeメソッドが、プロパティへの代入のシンタックスシュガーであるため。
つまりelem.setAttribute("class", "stretchable")IE内部でelem.class = "stretchable"として解釈されてしまっていた。というような記述をどこかで読んだ。

ちなみにsetAttributeについて、一部で第二引数にオブジェクトを入れてるコードを紹介しているサイトが散見されるけど、仕様上は第一引数も第二引数もプリミティブな文字列でないとダメだったはず。
どうしてもっていう場合は「古いIE向けのハックとして」程度に収めておくべきかと。

*1:試した限り、IE/Firefox/Chromeのそれぞれ現時点最新Verですべて発症

スーパーpre記法で書いたコード部分に行番号を出すようにした

こんなコードをフッタに入れた。

<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script>
jQuery(function($){
    $("pre.code").each(function(){
        var df = document.createDocumentFragment();
        var ol = $('<ol>').appendTo(df);
        $.each($(this).html().split("\n").filter(function(e, i, arr){
            return i !== arr.length - 1;
        }), function(i){
            $('<li>').html(this).appendTo(ol);
        });
        $(this).empty().append(df);
    });
});
</script>

jQueryで書いてるけど、ES5のメソッド使ってるので結局IE8以前では動きません。

今回はやっつけ実装なので、時間があったらまた見直してみる。

追記 (2014/01/18 13:55)

IE8以下でも動作するようにした。

<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script>
jQuery(function($){
    $("pre.code").each(function(){
        var df = document.createDocumentFragment();
        var ol = $('<ol>').appendTo(df);
        $.each($(this).html().split("\n").slice(0, -1), function(i){
            $('<li>').html(this).appendTo(ol);
        });
        $(this).empty().append(df);
    });
});
</script>

たぶん動作速度も上がったと思う。体感はできないけど。
どこが変わったかっつーとArray.prototype.filterをやめてArray.prototype.sliceにしただけ。
要は配列の最後の要素が邪魔だっただけなので、無理にfilter使う必要が全くなかった。

追記 2020/07/02

純粋な JavaScript のみで再実装しました。jQueryからの脱却。
処理内容自体は同じです。

<script>
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('main-inner').querySelectorAll('pre.code').forEach(el => {
    const ol = document.createElement('ol');
    ol.append(...el.innerHTML.split('\n').slice(0, -1)
      .map(s => Object.assign(document.createElement('li'), { innerHTML: s })));
    el.innerHTML = '';
    el.append(ol);
  });
});
</script>