2015年11月19日

<iframe> を動的生成する際の注意点

javascript で src 無しの <iframe> とその中身を構築した際、はまったことがあったのでメモ。

  • 動的に追加した <iframe> の中身をいじる際は、
    iframe.contentWindow.documentdocument.write() を使う
    または
    <iframe> のロード完了を待つ。(<iframe> 自身に onload イベントがある)
  • <iframe> に onload イベントを設置する際は、DOM への追加前に行う。
    DOM に <iframe> を追加した瞬間、onload イベントが処理されるブラウザがあるため (Chrome)

[サンプルコード]

// 単純化のため、jquery 使用

// 良い例
// IE:OK / Firefox:OK / Chrome:OK
$(function(){
    $('<iframe>').load(function(){
        // ロード完了後、DOM を変更できる。
        var ibody = this.contentWindow.document.body;
        $(ibody).append('<div>ブロック要素</div>');
    }).appendTo('body');
    // イベント設置後に DOM へ追加
});


// 悪い例(1)
// IE:OK / Firefox:NG / Chrome:OK
// Chrome は iframe の DOM 追加後、即時ロード完了する模様
// IE は iframe のロード完了を待たなくても大丈夫っぽい??
$(function(){
    var iframe = $('<iframe>').appendTo('body');
    var ibody = iframe[0].contentWindow.document.body;
    $(ibody).append('<div>ブロック要素</div>');
});


// 悪い例(2)
// IE:OK / Firefox:OK / Chrome:NG
// Chrome は iframe が即時ロード完了するので、DOM 追加後に load イベントを付けても無意味
$(function(){
    var iframe = $('<iframe>').appendTo('body');
    iframe.load(function(){
        var ibody = this.contentWindow.document.body;
        $(ibody).append('<div>ブロック要素</div>');
    });
});
[環境]
  • Windows 7 64bit
  • Internet Explorer 11
  • Firefox 42.0
  • Chrome 48.0.2564.8

2015年10月31日

highlight.js で行番号を表示

ブログ内の SyntaxHighlighterhighlight.js 移行作業がようやく終わったので投稿。

highlight.js には行番号を表示する機能が付いてない。
とりあえず検索してみたところ、ちょうどいいプラグインがあったのでそれの紹介。(当ブログでも使用中)
wcoder/highlightjs-line-numbers.js

使用方法は、highlight.js を適用した要素(普通は <code>)に対して hljs.lineNumbersBlock(要素) を実行。
もしくは、hljs.initLineNumbersOnLoad() を仕掛けておく。
[使用例]

[].forEach.call(document.querySelectorAll('pre > code'), function(elem){
    // highlight.js は <code> 内の先頭と末尾の改行を無視してくれないので、ここで削除
    // ※ HTML を書く際、先頭と末尾に改行を入れない方法もある
    elem.textContent = elem.textContent.replace(/^[\r\n]+|[\r\n]+$/g, '');

    // highlight.js の適用
    hljs.highlightBlock(elem);

    // highlightjs-line-numbers.js の適用
    hljs.lineNumbersBlock(elem);
});

また、行番号ブロックのスタイル(.hljs-line-numbers)を自分で用意する必要がある。
[使用例(当ブログのスタイル・2015/10/31)]

pre > code.hljs.hljs-line-numbers {
    background-color: #f5f5f5;
    border-right: 0 none;
    color: #707070;
    text-align: right;
    min-width: 14px;
    /* 以降は選択できないようにするためのスタイル */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

highlight.js に移行した理由

  • C# の新しいキーワードに対応 (素の SyntaxHighlighter は var 辺りから未対応)
  • 旧 Visual Studio 風ハイライトが選択可能
  • ハイライトする言語を選択してパッキングと Minify が公式サイトで可能
  • 外部ファイルの数が少ない (プラグインを除けば js と css の 2 ファイル)

2015年9月25日

python の加算代入演算子(+=)(プラスイコール)の注意点

python の一部のオブジェクト(list 等)では、「a = a + b」と「a += b」が同一視できない。
他言語経験者が思わぬバグを埋め込んでしまいそうな罠仕様・・・


>>> A = a = [1]
>>> a = a + [2]
>>> a, A, a is A
([1, 2], [1], False)
>>> # list の加算(+)は、元の a を変更しない
... # 加算の結果(新しい領域)を a に代入しているので、a と A は別オブジェクト
...

>>> B = b = [1]
>>> b += [2]
>>> b, B, b is B
([1, 2], [1, 2], True)
>>> # list の加算代入(+=)は、元の b を変更する
... # 加算代入しても新しい領域は割り当てられないので、b と B は同一オブジェクト
...

この理由は、list には 加算代入演算子(+=)用の __iadd__ メソッドが定義してあり、加算演算子(+)用の __add__ メソッド とは異なる動作になっているため。
※ list.__iadd__ の動作は list.extend と同じ。

__iadd__ メソッドが定義されてないオブジェクト(tuple 等)は、加算代入でも __add__ メソッドが使用される。
つまり、「a = a + b」と「a += b」 が同一視できる。

組み込みオブジェクトの大半には __iadd__ メソッドが定義されていないため、基本的に list とそのサブクラスを扱う際に注意すればいい。
加算代入演算子(+=)だけでなく、乗算代入演算子(*=)なども同様に注意。
ちなみに、__i???__ メソッドは、ミュータブルなオブジェクトに実装されるものらしい。

参考URL

2015年9月24日

python の代入は値を返さないけど多重代入は可能

python の代入は値を返さないので、下記のような書き方はエラーになる。

>>> # ファイルから少しずつ読み込んで処理
...
>>> f = open('hoge.txt', 'rb')
>>> while chunk = f.read(4096):
  File "", line 1
    while chunk = f.read(4096):
                ^
SyntaxError: invalid syntax

だけど、多重代入 (マルチターゲット代入 / chained assignment) はできる。

>>> a = b = 'test'
>>> a, b
('test', 'test')

何となく納得いかない感があるけど、多重代入だけは特別らしい。

下記のような書き方でも多重代入は可能。(自分がかつてやっていた方法)
左辺変更時に修正点多いし、あまりリーダブルじゃないと思うので使わない方がいい・・・

>>> a, b = ('test',) * 2
>>> a, b
('test', 'test')

おまけ

最初の例を綺麗に書きたい場合、iterfunctools.partial を組み合わせて使う。

>>> # ファイルから少しずつ読み込んで処理
...
>>> f = open('hoge.txt', 'rb')
>>> for chunk in iter(functools.partial(f.read, 4096), b''):
...    print(chunk)
...

>>> # partial の代わりにラムダ式でも可能
...
>>> for chunk in iter(lambda: f.read(4096), b''):
...    print(chunk)
...

2015年9月7日

python の等価演算子(==)と bool / 数値型

python の == 演算子は割と厳密で、公式ドキュメントにもあるとおり、数値型を除いて異なる型同士は等価にはならない。
javascript みたいに 1 == "1" が成立したりしない。
そのためか、python には === 演算子がない。

ただし、注意する点が 1 つあって、bool 型が int 型のサブクラスで、「True = 1」「False = 0」であること。
つまり、bool 型も数値型に含まれることになる。
そのため、下記のような結果になる。

>>> isinstance(False, int)
True
>>> isinstance(True, int)
True
>>> False == 0
True
>>> True == 1 == float(1) == complex(1)
True
>>> [True, False] == [1, 0]
True
>>> (True, False) == (1, 0)
True

上記を考慮して実装すれば基本的に問題ないのだが、どうしても厳密比較したい場合、対 bool 型であれば is 演算子を使う。

>>> value = True
>>> value is True
True
>>> value = 1
>>> value is True
False
対数値型であれば値の比較に加えて型の比較が必要になる。

>>> def isnum(value):
...   return type(value) in (int, float, complex)
...
>>> value = 1
>>> isnum(value) and value == 1
True
>>> value = True
>>> isnum(value) and value == 1
False

余談だが、python2 系では、True と False に値を代入して書き換えることができる。
割と危ない仕様・・・
python3 からはちゃんとエラーになってくれる。

2015年8月22日

document.querySelector で先頭が数字の ID を指定する方法

document.querySelectordocument.querySelectorAll で、数字から始まる ID をそのまま指定するとエラーになる。
document.getElementById はエラーにならず取得できる。
※ DOM じゃないけど、jquery セレクタもエラーにならず取得可能。

console.log(document.querySelector('#20150821abc'));
// Error: An invalid or illegal string was specified

console.log(document.getElementById('20150821abc'));
// <div id="20150821abc">

// 上記の div 要素は、このページ内に含まれているので、firebug・開発者ツールなどで確認可能

解決手段

querySelector は CSS セレクタを指定するものなので、CSS セレクタのエスケープ方法(コードポイント指定)で、先頭数字の ID を指定することができる。

// エスケープ方法は 2 通り
console.log(document.querySelector('#\\32 0150821abc'));    // '\3' + (digit) + ' '
console.log(document.querySelector('#\\0000320150821abc')); // '\00003' + (digit)

// <div id="20150821abc">

// もちろん、CSS の記述でも使用可能

自分用の GM スクリプトを作った際、先頭数字の ID を使っている web サイトがあったので・・・
普通は、先頭数字にはしないと思うのでレアケース?

参考URL