IEではImageオブジェクトに対してのinstanceofがうまくいかない?
yukiinu2ndです。
たまたま自分で作成したJavascriptをIEで動作確認していたのですが、Firefoxで動くのにIEで動かないという謎の現象が起きてしまいました。
該当部分は以下のようになっています。
if(target instanceof Image){//←問題の箇所はここ! if(target.complete) return true; else return false; }
実際の物とは違いますが、やりたかったのは投げ込まれたのがImageオブジェクトかどうか確認することです。
どういう訳か、IEではinstanceof Imageのところでエラーが出てしまいます。
結局、いい方法が思い浮かばず、色々検索した物の見つからなかったので以下のような方法で逃げることにしました……。
//if(target instanceof Image){ if(typeof target == "object" && typeof target.hspace != "undefined" && typeof target.vspace != "undefined"){ if(target.complete) return true; else return false; }
ただ、上記のコードでは他にhspaceやvspaceを持っているオブジェクトの場合も条件を満たしてしまいます。
とりあえずはこれでよいとしても、他に何か良い方法はないでしょうか……。
今日のVim
何気に前回書き忘れていました……。
今回は選択系を取り上げてみます。
選択
- 通常の範囲選択
- v
- 行単位での範囲選択
- V
- 矩形範囲選択
- <Ctrl>+v
上記の操作を行うと始点が決まるので、あとは終点までカーソルを持っていってdで切り取り、yでコピーといった編集ができます。
これ以外にも、矩形選択をした後に「I」を入力して挿入モードになり、文字を入力してESCで標準モードに戻ると矩形選択した行の左端に先程挿入した文字が入ります。
数行まとめてコメントアウトしたい時などに便利です。
また、範囲選択した状態で「:」を入力すると範囲対象へのみ置換処理をかけることもできます。
範囲選択→「:」→「s/Hoge/Moge/g」とすれば範囲中の「Hoge」を「Moge」に変えることができます。
今更ながらGreaseMonkey
yukiinu2ndです。今更ながらGreaseMonkeyを触ってみました。
現在社内で動いているシステムの一つがUI的にどうにも使いにくく、ふとGreaseMonkeyの存在を思い出し、「これで何とかできないかな」と思ったのがきっかけです。
空いた時間に少しずつ触っていますが、結構面白いですね。(自分でシステムをカスタマイズしている、という感覚のせいかもしれません)
ただ、functionを定義し、そのfunctionをOnclickで呼び出すボタンを差し込む、としたのですがfunctionがないと言われて困りました。
GreaseMonkeyのスクリプトと対象ページとの間でスコープが違うからだとは思うのですが、少しばかり強引に解決しました。
var scrtag = document.createElement("script"); scrtag.type = "text/javascript"; scrtag.text = "window.HogeHoge = function(){\ alert("hogehoge!");\ }"; document.body.appendChild(scrtag);
見ての通り、Script要素を作成し、textへJavaScriptをセットしてbodyの最後に差し込む、これだけです。
あとはこのfunctionを呼び出すボタンをページ上に追加すればOKです。
text内でJavaScriptを書く際には改行をエスケープするために全行末に「\」を付けています。
もっとよさそうな方法はありそうですが、とりあえずは上記の方法で逃げておきます……。
Perl+MySQLで動くジョブキューサーバ『TheSchwartz』を使ってみた
yukiinu2ndです。クリスマスイブですが気にせずPerlしています。
さて、今回は仕事上でどうしてもキューのような仕組みで少しずつ順番に処理をする必要が出てきてしまいました。
一から作るのは面倒&時間がなかったため、探してみたところ『TheSchwartz』が見つかったので使ってみました。
この『TheSchwartz』ですが、PerlだけでなくMySQLを合わせて使う*1ことでジョブキューの仕組みを実装しています。
クライアント(仕事をお願いする側のアプリケーション)はMySQL上のジョブキューにenqueue(RDBMSでいうINSERT)します。
ワーカー(仕事をこなすプロセス)は定期的(デフォルトは5秒間隔)でジョブキューを監視し、ジョブがあれば消化していきます。
一定時間毎に監視するという仕組みのため、リアルタイムで来たものを処理したり、処理性能が求められる用途には向きません。
逆に、メモリ上ではなくMySQLに一旦記録するためワーカーがフリーズしても消えない、信頼性があるという利点もあります。
今回の場合はリアルタイム性は必要なく、ゆっくり仕事してくれればそれでよい、MySQLも動いている、という点でぴったりでした。
インストール
それでは、早速CPANでインストールしてみます。
CPANシェルを起動して
cpan> install TheSchwartz
実行したものの、その前に必要なData::ObjectDriverのインストールでテストに失敗しているようです。
Perl::Criticという文法チェックのところで引っかかっているようなのでforce installしてしまいます。
cpan> force install Data::ObjectDriver
今度こそ、TheSchwartzをインストールします。もう一度……
cpan> install TheSchwartz
としますが、またテストに失敗してしまいました。
これもforce installしてもよかったのですが、http://rt.cpan.org/Public/Bug/Display.html?id=38570を見てテストを直してすっきりインストールします。
CPANがパッケージのダウンロードや展開まで済ませてくれてあるので便乗します。
# cd ~/.cpan/build/TheSchwartz-1.07-ZYS6PA/t/ <修正作業> # cd .. # perl Makefile.PL # make && make test # make install
これで無事にインストールできたはずです。
試してみる
インストールも終わったところで実際に動かしてみます。
試すだけであれば、TheSchwartzインストール時のテストが使った残り物のスキーマを使ってしまうのが楽でよいです。
恐らく、『t_sch_ts1』、『t_sch_ts2』ができているはずです。
中には
- error
- exitstatus
- funcmap
- job
- note
の5つのテーブルがありました。
それにしても、どういう訳か『TheSchwartz』で使うこういったテーブル群を作る仕組みやスクリプト等がありません。
新しく導入する際にはこのテスト時のテーブル群を『show create table 〜』で調べて同じものを作ればよいと思います。
今回は面倒なのでインストール時のテストが使った物を流用します。
以下にサンプルコードを示します。
まず、ワーカー側。
#!/usr/bin/perl # #TheSchwartz Test Script -worker package MyWorker; use strict; use base qw(TheSchwartz::Worker);#Workerを継承 use Data::Dumper; #workサブルーチン #ここに行わせたい処理を書いておく。 sub work { #おまじないみたいなもの? my $class = shift; my $job = shift; #処理 print "Workin' hard or hardly workin'? Hyuk!!\n"; print Dumper $job->arg; $job->completed();#処理正常完了! } package main; use TheSchwartz; my $client = TheSchwartz->new( #データベース接続情報を配列リファレンスで渡す。 #接続先は複数記述でき、1つめがダメでも次をトライする、ということをしてくれるらしい。 #その中はハッシュリファレンスでdsn,user,rootを持つ。 databases => [{dsn => "dbi:mysql:t_sch_ts1",user => "root",pass => ""}], #ログメッセージを出すかどうか。 #Coderef(サブルーチンへのリファレンス)を渡すとデバッグログ出力時に指定したサブルーチンが呼ばれる #それ以外のTrueになるような値であればSTDERRに出力する。 verbose => 1, ); #Workerの登録をする。workメソッドを持つパッケージを指定する。(……で間違いなさそう?) $client->can_do('MyWorker'); #処理開始。定期的にQueueをチェックする。 $client->work();
クライアント側はこちら。
#!/usr/bin/perl # #TheSchwartz Test Script -Client- use strict; use TheSchwartz; my $client = TheSchwartz->new( #データベース接続情報を配列リファレンスで渡す。 #接続先は複数記述でき、1つめがダメでも次をトライする、ということをしてくれるらしい。 #その中はハッシュリファレンスでdsn,user,rootを持つ。 databases => [{dsn => "dbi:mysql:t_sch_ts1",user => "root",pass => ""}], #ログメッセージを出すかどうか。 #Coderef(サブルーチンへのリファレンス)を渡すとデバッグログ出力時に指定したサブルーチンが呼ばれる #それ以外のTrueになるような値であればSTDERRに出力する。 verbose => 1, ); #仕事用のデータ #なんとなく、犬を渡してみる。 my $dat = {dog => "shiba",age => 5}; #WorkerにJobを渡す。 #Worker名をキー、引数を値としたものを渡せばよい、らしい。 $client->insert('MyWorker' => $dat);
まず、ワーカーを実行します。
これでキューを定期的に監視する状態になります。
そのままの状態で放置すると、一定時間(デフォルト5秒)毎に「TheSchwartz::work_once found no jobs」とでます。
表示のままの意味ですが、処理するjobがなかったことを示すメッセージです。
ワーカーが居る状態でクライアントを動かしてみると、Dumperの出力が出て、その前後にはワーカーが働いた旨のメッセージが表示されます。
キューに貯まった仕事をちゃんと消化した、というわけですね。
実際に何度もクライアントを実行すると、その分だけワーカーはメッセージを表示します。
ゆっくり動作させたければ、ワーカー内でsleepしたりすればよいでしょう。
ここで示したのはサンプルですが、色々な使い方ができそうです。
他の候補
他にもジョブキューのようなものを実現する方法はありました。
例えば、Q4MやGearmanです。
しかし、前者はMySQL5.1系が必要だったこと(使いたかったサーバ上で動いていたのは5.0系)、後者は来た瞬間に処理してしまったり、データロストの可能性がある、という点で見送りました。
参考URL
TheSchwartzを使う際に、下記のURLを参考にしましたので、紹介させて頂きます。ありがとうございました。
http://d.hatena.ne.jp/holidayworking/20081123/1227414520
インストール時のテスト失敗を解決する手段が書かれています。
http://d.hatena.ne.jp/tokuhirom/20070501/1177997739
サンプルコードが載っています。
http://labs.gmo.jp/blog/ku/2008/06/theschwartz.html
サンプルコードとワーカーで使用する完了時のメソッドについて日本語で書かれています。
TheSchwartz の worker を安全に停止する - 酒日記 はてな支店
今回触れませんでしたが、ワーカーを安全に終了させる方法について書かれています。
今日のVim
今回は検索系についてまとめてみました。
検索系
全て標準モードから操作します。
- 下方向へ検索
- /<文字列>
- 上方向へ検索
- ?<文字列>
- 次の候補
- n
- 前の候補
- N
- カーソル位置の単語を下方向に検索
- *
- カーソル位置の単語を上方向に検索
- #
- 検索した際のハイライトを解除する
- :noh もしくは :nohighlight
置換
置換は標準モードから下記のようなコマンド入力で行います。
:<範囲指定>s/<置換対象>/<置換後>/<オプション>
- 範囲指定
- %を設定するとページ全体、A,BでA行目からB行目までを対象に取る
- オプション
- gを指定すると見つかった範囲で繰り返し行う。cを指定すると1回毎に置換するか確認する
少なめですが、今回はここまでです。
childNodesの罠
yukiinu2ndです。また間が空いてしまいました……。
#文章にまとめるのは難しいです。
今回は、JavaScriptについて勉強をしている方から相談を受けた(というより自ら首を突っ込んだ)際にはまってしまったことについて紹介します。
その方、何でもJavaScriptからchildNodesなどを使ってDOM操作をすることで動的に要素を操作する、というサンプルを動かしていたのですがうまく動かないと困っていました。
childNodesはノードオブジェクトのプロパティであり、
var kodomotachi = document.getElementById("oya").childNodes; alert(kodomotachi[0].innerHTML);
とするとkodomotachiにid属性が「oya」である要素の子ノードの配列が返ります。
上記のようにその要素の持つ子ノードのHTML等を取得することもできます。
しかし、このchildNodesですが、IEとFirefoxで挙動が違う*1ようなのです。
困っていた方のサンプルが動かなかった原因も、この挙動の違いによるものでした。
例えば、以下のようなサンプルを動かしてみます。
<html> <head> <title>JS Test</title> </head> <body> <div id="textblock"> <p>段落1</p> <p>段落2</p> <div id="textblock2"> <p>段落3</p> </div> </div> <script language="JavaScript"><!-- function changeNodes(){ var target = document.getElementById("textblock"); target.childNodes[0].innerHTML = "change!\n"; } //--></script> <form action="#" method="GET"> <input type="button" value="Check!" onclick="changeNodes()" /> </form> </body> </html>
このサンプルは「Check!」ボタンを押すと、1つめのp要素の中身を「change!」に置き換えます。
IEでは意図通りの動作をしますが、Firefoxではぱっと見何も起きません……。
困っていた方のサンプルも上記サンプルとほぼ似たようなことをしていました。
もう一つサンプルを見てみましょう。
<html> <head> <title>JS Test</title> </head> <body> <div id="textblock"> <p>段落1</p> <p>段落2</p> <div id="textblock2"> <p>段落3</p> </div> </div> <script language="JavaScript"><!-- function getNodes(){ var target = document.getElementById("textblock"); alert("childNodes.length="+target.childNodes.length); } //--></script> <form action="#" method="GET"> <input type="button" value="Check!" onclick="getNodes()" /> </form> </body> </html>
このサンプルを実行し、「Check!」ボタンを押すとtextblockのid属性を持つdiv要素の子ノード数をAlertで表示します。
「p要素が2つ、div要素が1つ子としてあるから3が返るだろう!」と考える人も多いと思います。自分も調べるまではそうでした。
実際に、IEで実行すると答えは3で返ります。
しかし、FirefoxやSafariで実行するとなぜか7が返ります。7とは何とも中途半端な数字です……。
では、この子ノード数の3個と7個の違いは何かを見てみます。
<html> <head> <title>JS Test</title> </head> <body> <div id="textblock"> <p>段落1</p> <p>段落2</p> <div id="textblock2"> <p>段落3</p> </div> </div> <script language="JavaScript"><!-- function getNodes(){ var target = document.getElementById("textblock"); var tmplog = ""; for(var i = 0;i < target.childNodes.length;i++){ tmplog += target.childNodes[i].nodeName+","+target.childNodes[i].nodeValue+","+target.childNodes[i].nodeType+"\n"; } document.getElementsByTagName("body")[0].innerHTML += tmplog; } //--></script> <form action="#" method="GET"> <input type="button" value="Check!" onclick="getNodes()" /> </form> </body> </html>
このサンプルは「Check!」ボタンを押すと、HTMLの末尾に子ノードの簡単な情報を表示します。
一行一ノードとなっていて、左からカンマ区切りで
- ノード名(nodeName)
- ノート値(nodeValue)
- ノード種別(nodeType)
を表示します。
実行すると、次のような結果になります。
まず、IEで動かした結果です。
P,null,1 P,null,1 DIV,null,1
これは予想通りの内容です。
次に、Firefox2で実行してみます。
#text, ,3 P,null,1 #text, ,3 P,null,1 #text, ,3 DIV,null,1 #text, ,3
何かIEとは違うノードが存在するようです。
ノード名が「#text」となっていて、ノード種別も3になっています。
ノード種別について調べてみると、1は要素、3はテキストとなっています。
テキストノードはその名の通り文字列情報を持つノードです。要素に囲まれていない文字列がこのノードになるようです。(この辺りは正確かどうか自信がないですが……)
これは、勝手にFirefoxが謎の空白文字を含んだテキストノードを追加しているのか……。
それを確かめるために以下のようにサンプルを変えてみます。
<html> <head> <title>JS Test</title> </head> <body> <div id="textblock"> 1つめ <p>段落1</p> 2つめ <p>段落2</p> 3つめ <div id="textblock2"> <p>段落3</p> </div> 4つめ </div> <script language="JavaScript"><!-- function getNodes(){ var target = document.getElementById("textblock"); var tmplog = ""; for(var i = 0;i < target.childNodes.length;i++){ tmplog += target.childNodes[i].nodeName+","+target.childNodes[i].nodeValue+","+target.childNodes[i].nodeType+"\n"; } document.getElementsByTagName("body")[0].innerHTML += tmplog; } //--></script> <form action="#" method="GET"> <input type="button" value="Check!" onclick="getNodes()" /> </form> </body> </html>
まず、Firefox2の結果。
#text, 1つめ ,3 P,null,1 #text, 2つめ ,3 P,null,1 #text, 3つめ ,3 DIV,null,1 #text, 4つめ ,3
予想通り入力した文字が入っています。
では、IEの結果はどうでしょうか。
#text,1つめ ,3 P,null,1 #text,2つめ ,3 P,null,1 #text,3つめ ,3 DIV,null,1 #text,4つめ ,3
……きちんと文字列が格納されています。
どうも、IEの場合、空白文字のみで構成されている文字列に関しては切り捨てる(もしくはどこか別の要素に入っている?)処理になるようです。
これは、childNodesを基準にDOM操作をする時はブラウザによる挙動の違いに気をつけないといけないようです。
まさか、こんな罠があるとは思いませんでした……。