ラベル javascript の投稿を表示しています。 すべての投稿を表示
ラベル javascript の投稿を表示しています。 すべての投稿を表示

2012年8月1日水曜日

iMacros と JavaScript を組み合わせて使う

よく使う Firefox add-on のひとつが、 iMacros なるもの(厳密には名前の右肩にTMが付く)。browser-based macro recorder であり、Webブラウザを使った操作を記録&再生することができる。


で、今日メモっておきたいのは、iMacros と JavaScript を組み合わせる方法について。iMacros のマクロはいくつかのコマンドからなっているが(Command Reference - iMacros に掲載されている)、一般的なプログラミング言語が提供するような関数や制御構造、データ構造をもたない。そこを JavaScrpt で補完しよう、という話。


環境は Firefox 14.0.1, iMacros for Firefox 7.5.0.4.

1. マクロを記録
まずは簡単なマクロをひとつ作るため、google.co.jp を開いて検索する操作を記録してみた。そうすると、次の内容のマクロ(#Current.lim)ができた。
VERSION BUILD=7500718 RECORDER=FX
TAB T=1
URL GOTO=https://www.google.co.jp/
TAG POS=1 TYPE=INPUT:TEXT FORM=NAME:f ATTR=ID:lst-ib CONTENT=foo
ここで、fooの部分が検索した文字列に相当する。
2. マクロを編集(変数の導入など)
記録したマクロをもとにして、繰り返し検索を行う処理をJavaScriptで作ることにする。そのための準備としてまずはマクロ "#Current.lim" を "my.lim" にリネーム。それから内容を編集。
第一に、新しいタブを作ってフォーカスを移すための iMacrosコマンド TAB OPENTAB T={{!VAR1}} を追加する。{{!VAR1}} はタブの番号を指定するために導入した変数。
第二に、JavaScriptから指定した文字列で検索を行うことにしたいので、foo{{!VAR2}} と置換。
結果、マクロは次の内容になった。
VERSION BUILD=7500718 RECORDER=FX
TAB OPEN
TAB T={{!VAR1}}
URL GOTO=https://www.google.co.jp/
TAG POS=1 TYPE=INPUT:TEXT FORM=NAME:f ATTR=ID:lst-ib CONTENT={{!VAR2}}
TAG POS=1 TYPE=INPUT:BUTTON ATTR=VALUE:Google<sp>検索
3. JavaScriptを作成
とりあえずテキトーにマクロを作って、my.js とリネームしてから編集。 iimSet, iimPlay という2つのインターフェースを利用してJavaScriptを書く。具体的には、マクロに導入した変数 {{!VAR1}} に、タブ番号を iimSet() という関数でセット。それから配列にはいっている文字列 foo, bar, baz を {{!VAR2}} にセット。最後に、iimPlay() で my.lim を実行する。
var a = ['foo', 'bar', 'baz'];
for(var i=0, n=a.length; i<n; i++){
  iimSet("VAR1", i + 1);
  iimSet("VAR2", a[i]);
  iimPlay("my");
}
4. JavaScriptを実行
作成した JavaScript(my.js)を保存して、実行する(Playボタンとか)。うまくいくとタブを新規作成して検索する操作が3回繰り返される。
※空白のタブがひとつできてしまうから、いまひとつなところがあるのかもしれないな。。。

2011年8月19日金曜日

Node.js をビルドするときの openssl-libpath

うちのMacで久しぶりにビルドしようとしたら、makeの途中でエラーが発生した(Mac OS 10.6.8 での話)。むかしは問題なかった気もするが…

解決策

makeのメッセージを確認したところ、SSL関係の共有ライブラリ(/opt/local/lib/libssl.dylib)がおかしい。そこで configure のオプションで別のパスを指定してみたら解決した。

$ git clone --depth 1 git://github.com/joyent/node.git
$ cd node
$ git checkout v0.4.11
$ ./configure --prefix=$HOME/local/node --openssl-libpath=/usr/lib/
$ make

ちなみに、オプションを表示するには configure --help と打てばOK.

$ ./configure --help
waf [command] [options]

Main commands (example: ./waf build -j4)
  abspath  : Return an absolute path.
  build    : builds the project
  clean    : removes the build files
  configure: configures the project
  dirname  : Returns the directory component of a pathname
  dist     : makes a tarball for redistributing the sources
  distcheck: checks if the sources compile (tarball from 'dist')
(略)

それと、ここで表示される "waf" って何のことだろと思ったら、Pythonでできたビルドツールらしい。Waf - Wikipedia, the free encyclopedia … 初耳でした。

2011年8月18日木曜日

JavaScriptコードの性能比較 - jsPerf

概要はこちら「JavaScriptコードのパフォーマンス比較ができるWebサービス「jsPerf」の使い方 | Web scratch」に。

とりあえず、キャピタライズ処理のためのコードを比較してみた > Upcase first character · jsPerf

※コードは「デザインとプログラムの狭間で: javascriptでキャピタライズ(一文字目を大文字にする)」のものを利用させていただいた。

使うのはとても簡単。コードだけでなく、いろいろな環境での結果を共有できるのも面白い。

あとはどういった場面で活用するか。たとえばFirebugのプロファイラでボトルネックを特定し終わって、具体的な改善方法を模索するときとか。

2011年8月7日日曜日

JavaでJSONのパース/シリアライズ - Gson

久しぶりにJavaの仕事。

JSONを処理するにあたってGsonというライブラリを使ったのでメモ。

Gson

そもそもGoogle社内で開発・利用していたものを、一般に公開したとのこと。

google-gson - A Java library to convert JSON to Java objects and vice-versa - Google Project Hosting から現在の最新版である google-gson-1.7.1-release.zip をダウンロードし、コンパイル&実行に必要な gson-1.7.1.jar を抽出しておく。

JSONのパース/シリアライズ

Gsonクラスをインスタンス化して、インスタンスメソッドである fromJson(), toJson() でそれぞれパース、シリアライズを行う。

JSONオブジェクトのキーがJavaの予約語と一致してしまう場合、つまり {"try":1, "class":2} のような場合、そのままではパースができないためアノテーション @SerializedName を使って対応する必要がある。

サンプルコード

Gsonを使ってパースとシリアライズを行う。パースするJSONは {"class":999} でキーにJavaの予約語"class"を含むため、@SerializedName を使ってJavaオブジェクトのフィールド_class と対応させる。

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;

public class Test{

    // JSONに対応するクラス。フィールド1個だけ。
    private class Myobj {
        // _classフィールド
        // 対応するJSONのキーがJava予約語(class)なのでアノテーションを付ける
        @SerializedName ("class") private int _class;

        // toString()
        public String toString() {
            return "_class: " + this._class;
        }
    }

    // main
    public static void main (String[] args) {
        // JSON
        String json = "{\"class\":999}";

        // JSONをパース
        Gson gson = new Gson();
        Myobj myo = gson.fromJson(json, Myobj.class);
        System.out.println("fromJson: " + myo);

        // Javaオブジェクトをシリアライズ
        String s = gson.toJson(myo);
        System.out.println("toJson: " + s);
    }
}

コンパイル&実行結果。ちなみに環境は Mac OS X 10.6.8 (Darwin 10.8.0 x86_64)。

$ javac -classpath gson-1.7.1.jar Test.java
$ java -classpath ./:gson-1.7.1.jar Test
fromJson: _class: 999
toJson: {"class":999}

2011年8月3日水曜日

GoogleのBloggerに関連記事を表示する(brps - gas.js)

クリボウの Blogger Tips: Blogger で「関連記事」リストを表示する 2 つの方法 を参照して「3行コピペでできる方法」を今日試したのだが、期待どおりに動作しなかった。具体的には、関連記事のリストが表示されるべき位置に「キーを取得してください」という旨のエラーメッセージが表示されてしまった(英語だった)。

問題のJavaScriptを提供しているbrpsプロジェクトページ brps - Blogger Related Posts Service - Google Project Hosting を調べたら、「ClientGasJs」のページに違う方法が書いてあった。BRPSデータベースを使う方式から、Google AJAX Search APIを使う方式に変えた、ということらしい。

こちらの方法はうまく動作したので、メモしておく。

1. Blogger 管理画面「レイアウト > HTML の編集」ページを開く
「ウィジェットのテンプレートを展開」にチェックを入れる
2. 関連記事を表示したい場所に <div id='gas-results'/> 等を追加
  <b:if cond='data:blog.pageType == &quot;item&quot;'>
    <div>
      <h3>Related Posts</h3>
      <div id='gas-results'/>
    </div>
  </b:if>

見出しに相当するh3の部分は、利用しているテンプレートに依存する(h2h4を使うほうが適切な場合もあるだろう)。<b:if>...</b:if>で囲んでいるのは、一つの記事だけが表示される場合に限り関連記事を表示するため。トップページやアーカイブページのように複数記事が表示される場合、今回の方法はうまく動作しない(1番上にある記事しか関連記事が表示されない)。

3. </body>の前あたりにJavaScriptを追加
<b:if cond='data:blog.pageType == &quot;item&quot;'>
  <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'/>
  <script>
window.brps_gas = {
//  remove_tags: ['unwanted_tag1', 'unwanted_tag2'],
//  tag_selector: 'a[rel=tag]',
  limit: 5,
//  add_sites: ['secondblog.blogspot.com', 'thirdblog.blogspot.com'],
  remove_string_regexp: /^.*?: /,
  exclude_url_regexp: /(\/search\/label\/|(archive\.html|blog\.example\.com\/|\.blogspot\.com\/)$)/,
  html_loading: '<span>Loading...</span>',
  html_no_results: '<span>Found no results.</span>'
  };
  </script>
  <script src='http://brps.appspot.com/gas.js'/>
</b:if>

jQueryをすでに利用している場合、<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'/>は必要ない。動作を調整するためにremove_tags, tag_selector, limit等のオプションを指定できるので、状況に合わせて調整する。自分の場合は不要だったので//でコメントアウトしたが、

  • 関連記事を洗い出すときに無視して欲しいラベル(tag)があれば、remove_tagsの所に列挙する
  • ラベル(tag)のhtmlコードをカスタマイズしているのであれば、tag_selectorの所で変更する
  • 複数のドメインでブログを書いているのであれば、add_sitesの所に列挙する。

以上の操作を行ってレイアウトを保存すればhtmlに反映される。あとは必要に応じてCSSを調整すればOK。

2011年4月27日水曜日

RangeオブジェクトとSelectionオブジェクト

JavaScriptにおけるRangeオブジェクトとSelectionオブジェクトの使いかたとして、次の2つのケースを書いておく。

  1. 選択範囲のテキストを取得する
  2. 指定したノードを選択状態にする

1. 選択範囲のテキストを取得する

マウスで選択したテキストがアラートで表示される、という簡単な例題。

IE以外の場合

まずSelectionオブジェクトを取得して、そこからさらにRangeオブジェクトを取得する(それぞれ、getSelectionメソッドとgetRangeAtメソッドを使う)。RangeオブジェクトのtoStringメソッドで求めるテキストが得られる。

    <p id="p">foo bar baz</p>

    <script type="text/javascript">
      document.getElementById('p').onmouseup = function(){
        var range = window.getSelection().getRangeAt(0);
        alert(range.toString());
      }
    </script>
    

Windows7のFirefox, Chrome, Safariで期待通りに動作した。

IEの場合

考え方は同じだが、使用するメソッドが異なる。

  • Selectionオブジェクト … documentオブジェクトのselectionプロパティ
  • Rangeオブジェクト(IEの場合はTextRangeオブジェクトと言う) … SelectionオブジェクトのcreateRangeメソッド
  • テキスト … TextRangeオブジェクトのtextプロパティ
    <p id="p">foo bar baz</p>

    <script type="text/javascript">
      document.getElementById('p').onmouseup = function(){
        var range = document.selection.createRange();
        alert(range.text);
      };
    </script>
    

2. 指定したノードを選択状態にする

HTMLの中の任意の要素を選択状態(明暗が反転した状態)にする、という簡単な例題。

IE以外の場合

SelectionオブジェクトのselectAllChildrenメソッドを使う。引数として渡した要素が、子要素を含めて選択状態になる。

    <p id="p">foo bar baz</p>

    <script type="text/javascript">
      window.getSelection().selectAllChildren(document.getElementById('p'));
    </script>
    

これもWindows7のFirefox, Chrome, Safariで期待通りに動作した。

IEの場合

IEの場合は、Selectionを使わない。

まずはTextRangeオブジェクトを生成し、それが指定の要素を含むようにmoveToElementTextメソッドを実行する。さらにselectメソッドを実行して、要素を選択状態にする。

    <p id="p">foo bar baz</p>

    <script type="text/javascript">
      var range = document.body.createTextRange();
      range.moveToElementText(document.getElementById('p'));
      range.select();
    </script>
    

とりあえず以上。テキストの追加、削除、比較などについてはリファレンスを参考に。

2011年2月6日日曜日

CGIの環境変数 SERVER_NAME と HTTP_HOST

ほかの誰かが作ったCGIを移行する仕事で、バグの原因になったので記録しておく。

症状

CGIで作成された、お問い合わせフォーム。このCGIが出力するHTMLは、head要素内のscript要素でJavaScriptファイルを参照し、ユーザーインターフェースを制御している。

www有りと無しの二通りのURLでアクセスが可能(http://www.example.com/form/ と http://example.com/form/)。しかし、www有りでアクセスした場合は、JavaScriptが正しく動作しない。

原因と対応

JavaScriptファイルのうち、動的に生成されるファイルにバグがあった。動的に生成される部分というのは XMLHTTPオブジェクトがアクセスするURLであり、環境変数SERVER_NAMEを使って生成されていた。次のように。

$url = "\/\/" . $ENV{'SERVER_NAME'} . $service_path . '?';
 $script =~ s/<url>/$url/g;  # $scriptの中身はJSのテンプレ。それを置換している。
 print "Pragma: no-cache\n";
 print "Cache-Control: no-cache\n";
 print "Content-type: text/plain; charset=UTF-8\n\n";
 print $script;

SERVER_NAMEの値はWebサーバーの設定に依存してしまうため、JavaScriptファイルを取得するときのリクエストが www.example.com なのに対して、XMLHTTPオブジェクトは example.com を参照してしまうという矛盾が生じた。これはクロスドメイン制限(cross-domain restrictions)に抵触、あるいは同一生成元ポリシー(same-origin policy)に反してしまう。よって、次のように修正すればOK。

$url = "\/\/" . $ENV{'HTTP_HOST'} . $service_path . '?';  # HTTP_HOST に変更
 $script =~ s/<url>/$url/g;
  (略)

SERVER_NAME と HTTP_HOST

CGIの環境変数のうち、SERVER_NAME と HTTP_HOST はよく似ていているのだが、意味が異なる。

これらの意味は、CGIに環境変数を渡しているApacheのドキュメントに書いてあるはずなのだが、いまいちみつからない。とりあえず、mod_rewriteの中に説明があったのでそこを引用しておく。

mod_rewrite - Apache HTTP Server
  • HTTP headers:
    • (略)
    • HTTP_HOST
    • (略)
  • server internals:
    • (略)
    • SERVER_NAME
    • (略)

These variables all correspond to the similarly named HTTP MIME-headers, C variables of the Apache server or struct tm fields of the Unix system. Most are documented elsewhere in the Manual or in the CGI specification.

SERVER_NAME and SERVER_PORT depend on the values of UseCanonicalName and UseCanonicalPhysicalPort respectively.

要するに、HTTP_HOSTのほうは"HTTP headers"、つまりブラウザからのリクエストヘッダに含まれるHOSTを表し、SERVER_NAMEのほうは"server interanals"、つまりApacheサーバー内の設定ファイル等に記述されているSERVER_NAMEを表すということ。したがって、HTTP_HOSTを扱うときはセキュリティ的に注意を要する、なんて話題も避けられない(変な文字列が送られてくる可能性があるから、内容をチェックしたりエスケープしたりする)。

このあたりの話は、Apacheから環境を引き継ぐプロセス全般に通用する話だから、PerlだけでなくPHPやRuby, Javaサーブレットなどを作るときにも頭に入っているとよいかもしれない。あと、mod_rewrite の条件を書く時にも。

Custom javascript dialog boxes

Custom JavaScript Dialog Boxes - Web Development Blog は、他のライブラリに依存しておらず、jsとcssを組み込むだけで手軽に使えるカスタムダイアログボックス。IE5.5 や IE6 でも動作する。

デザイン面ではパステルカラーと「×」や「?」などの背景画像を必要十分な程度に使ってあり、シンプル。そのせいか現在でも陳腐な感じがしない。

2008年4月以降に少しずつ更新が行われたようで、27 November 2009の日付でバージョン1.7が公開されている(Upgrade to Dialog Box Available)。

このバージョンのサンプルを見る限り、以下のオプションが指定できる。

  • ダイアログのスタイル6種類:Error, Warning, Success, Prompt, Message, Query
  • モーダル / ノンモーダル
  • auto hide(時間を指定して自動的に消去)
  • ボタンあり / なし
  • ダイアログボックスのサイズ指定 / 自動調整

ダイアログの例

モーダル(Error)

ノンモーダル(Warning)

モーダル+ボタンあり+サイズ指定(Success)

モーダル+ボタンあり+サイズ自動(Prompt)

2011年1月27日木曜日

DOMのhashプロパティ×2

URLを構成する文字列のうち、"#…"の部分はフォーマルには "fragment identifier" と呼ばれるが、「フラグメント・アイデンティファイア」と発音しても他人に通じることは稀。"#"は一般的に「ハッシュマーク」や「シャープ」と呼ばれるから、日常会話では「ハッシュ以下」とか「シャープなんとか」などと言えば通じる。

JavaScriptでこの fragment identifier を参照する方法を整理しておく。

window.location.hash

ブラウザで現在表示しているURLの fragment identifier を知りたい場合、JavaScriptでは window.location.hash と書けばよいことが知られている(ちなみに、Mozillaのドキュメント window.location - MDC Doc Center には "DOM Level 0. Not part of any standard." と書いてあるので、W3Cで標準化されたものではない)。

HTMLAnchorElement.hash

location以外に fragment identifier が関係してくる場面としては、a要素のhref属性値を処理する場面がある。実はつい最近までhref属性値を文字列処理して"#…"の部分を取り出す、なんてことをしていたのだがその必要はない。アンカーオブジェクトにもhashプロパティが存在しているので、getElementしてhashプロパティを参照するだけで済む。次の例はアンカーのhref属性値"http://dminor11th.blogspot.com/index.html#foo"のうちの"#foo"をアラートで表示する。

<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p><a id="a1" href="http://dminor11th.blogspot.com/index.html#foo">anchor1</a></p>
    <script type="text/javascript">
      alert(document.getElementById('a1').hash);
    </script>
  </body>
</html>

ちなみに、このhashプロパティ、つまりHTMLAnchorElementのhashプロパティも、W3Cで標準化されたものではないようだ(DOM Level 2 HTML Spec.等に記載されていない)。Mozillaの HTMLAnchorElement - MDC Doc Center によるとHTML5には記載されるらしい。

2011年1月15日土曜日

document.anchors と document.links

今まで気にかけていなかった、DOMの"document.anchors"と"document.links"の違いを整理。

  • name属性を持つa要素はアンカーとみなされるので、document.anchors に属する。
  • href属性を持つa要素はリンクとみなされるので、document.links に属する。
  • name属性とhref属性を両方とも持っているa要素は、document.links, document.anchors の両方に属する。
  • id属性の有無はアンカー/リンクの判別には無関係(IEでは仕様が異なり、id属性を持つ場合はアンカーとみなされる)

確認のための例

アラートでa要素のテキストノードを表示。a1とa3が document.anchors、a2とa3が document.links として表示される。

<html>
<head>
  <title>test</title>
</head>
<body>

<ul>
  <li><a name="a1">a1</a></li> <!-- anchor -->
  <li><a href="foo">a2</a></li> <!-- link -->
  <li><a name="a3" href="bar">a3</a></li> <!-- anchor, link -->
</ul>

<script type="text/javascript">
  function collectText(a){
    var stack = new Array();
    for(var i=0, n=a.length; i<n; i++){
      stack.push(a[i].childNodes[0].nodeValue);
    }
    return stack.join(', ');
  }
  alert('document.anchors[' + collectText(document.anchors) + ']');
  alert('document.links[' + collectText(document.links) + ']');
</script>

</body>
</html>

※IE-6, IE-7, IE-8, Firefox-3.6.13, Chrome-8.0.552.224, Safari-5.0.2で期待通りに動いた(Windows7)。

2010年12月16日木曜日

IE5.5 で MochiKit のセレクタ(MochiKit.Selector)を使うときに

ほとんどの仕事でIE6以上がサポート対象となるのだが、まれにIE5.5もサポートしなければならないこともある。

IE5.5で何が困るかと言うと、JavaScriptライブラリである。普段はもっぱら jQuery を使ってJavaScriptを書くのだが、残念ながらIE5.5ではエラーが発生してしまう。

ちょっとしたスクリプトなら組み込みのJavaScript関数だけで対応するが、それが効率的でない場合は jQueryに代わるライブラリを導入する。JavaScriptのいわゆる「軽量ライブラリ」はたくさんあるのだが、一体何を使うか? 自分の場合は MochiKit を使う(IE5.5でエラーが発生しないのと、むかし使ったゆえの惰性もある)。

そのような状況で、つまり MochiKit と IE5.5 の組み合わせで頻繁に犯してしまうミスがあるので備忘のため書いておく。

Classセレクタ(Class Selector)は要素もセットで記述するべし

たとえばHTMLドキュメントに foo というclassをもつ div 要素があったとして、

NG
  MochiKit.DOM.addLoadEvent(function(){
    alert($$('.foo').length);
  });

このコードでは結果が 0 になってしまう。なぜか .fooだけでは目的の要素が取得できないのだな。

OK
  MochiKit.DOM.addLoadEvent(function(){
    alert($$('div.foo').length);
  });

セレクタに、要素 div も指定すると期待通りに 1 が得られる。

2010年11月29日月曜日

Lisp の every と some 風の関数を JavaScript で

Common Lisp や Emacs Lisp 等では every や some という高階関数が提供されており、複数の値に対するテストを簡潔に記述することができる(述語関数/predicateの、リストへの適用)。

Lispの例

  (every #'evenp '(1 2 3)) => nil        ; 偶数でない要素があるため偽(nil)
  (some #'evenp '(1 2 3)) => t           ; 偶数の要素が少なくとも1個あるため真(t)

ここで、evenp は引数が偶数の場合に t を返す組み込み関数

JavaScriptの例

JavaScriptで同様のことを実現したいという場合は、次のような形になるはず。

// 関数定義
function every(p, arr){
  for (i = 0, n=arr.length; i < n; i++) {
    if(!p(arr[i])) return false;
  }
  return true;
}
function some(p, arr){
  for (i = 0, n=arr.length; i < n; i++) {
    if(p(arr[i])) return true;
  }
  return false;
}
function evenp(x){
  return (x%2==0) ? true : false;
}
// 実行
every(evenp, [1, 2, 3]);   // => false
some(evenp, [1, 2, 3]);    // => true

こんな感じ。

実は every, some が組み込まれているブラウザもある

念のため調べたところ、Firefox(Gecko 1.8b2 以降)にはこれらの関数が配列のメソッドとして実装されているとのこと。

everyの利用例(Mozilla Developer Center より転載)

function isBigEnough(element, index, array) {
  return (element >= 10);
}
var passed = [12, 5, 8, 130, 44].every(isBigEnough);
// passed は false

mozilla.orgのページにある「互換性」のコードを利用すれば、Firefox以外のブラウザでもeveryが利用できるようになる。

2010年11月22日月曜日

tableのtd要素にエクセルのセル番号のようなclassを設定するJavaScript

ここで「エクセルのセル番号」というのはつまり"A1"とか"AZ1"のような列と行を表す文字列のこと。

※「セル番号」よりももっと正式な名称があると思う。

Webサイト制作においては、tableを構成するすべてのセルに対してそれぞれCSSで柔軟にデザインを変えたいというケースが稀にある。そうした場合に手っ取り早いのは各セルにセル番号のような文字列をクラスとして設定してあげること。そして、css内で td.A1{ ...; } ... td.AZ1{ ...; } のようなスタイルを指定する。

小さいtableなら手動でやってもよいが、大きくなるとJavaScriptで動的に設定することになる。以下がそのためのJavaScriptの例。

<script type="text/javascript">
// jQuery等のライブラリ使わないコードです(仕事上、そういう前提だったので)
// 関数定義
function setClassToTd(){
  var alphas = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  var tbls = document.getElementsByTagName('table');

  // html内のすべてのtable要素を処理する
  for(var i=0, n=tbls.length; i<n; i++){
    var trs = tbls[i].getElementsByTagName('tr');
    for(var j=0, m=trs.length; j<m; j++){
      var tds = trs[j].getElementsByTagName('td');
      for(var k=0, l=tds.length, cls; k<l; k++){
        if(k < alphas.length){
          cls = alphas.charAt(k);
        }else{
          cls = charAt(Math.floor(k / alphas.length) - 1) + charAt(k % alphas.length);
        }
        cls += j + 1;
        // 既存のclass設定を消さないように
        var clsbuf = tds[k].getAttribute('class')||tds[k].getAttribute('className');
        if(clsbuf){
          cls += ' ' + clsbuf;
        }
        tds[k].setAttribute('class', cls);
        tds[k].setAttribute('className', cls); //IE
      }
    }
  }
}
// loadイベントで実行 
if (window.attachEvent) { //IE
  window.attachEvent("onload", setClassToTd);
}else{
  window.addEventListener('load', setClassToTd, false);
}
</script>

2010年10月29日金曜日

applyメソッドの素朴な実用例

2010年現在、JavaScript関連の仕事ではほとんどの場合においてjQueryライブラリを使うわけだが、いわゆるレガシー案件というのだろうか、環境がそれを許さない場合もある。

そういった場合、DOMの書き換え処理を素朴な方法、つまりgetElementByIDなどDOMの組み込みメソッドを使った方法で実現することになる。本当に小さいプログラムなら気にならないかもしれないが、似たようなことを何度も書くことになるので自分でちょっとしたユーティリティ関数を書いておき、いろんな仕事で使い回すことになる。

そんな中にapplyメソッドを使った実用的なものがいくつかあったのでここに記録しておく。一昔前まではapplyの意味を理解できなかったけれどもう大丈夫ですよ、という記念をかねて。あとクロージャも。

DOMユーティリティ関数

// 指定されたidをもつ要素に関数fを適用
function getByIdAndApply(id, f){
  var elm = document.getElementById(id);
  if(elm) f.apply(elm);
}
// 指定されたタグをもつ要素群に関数fを適用
function getByTagNameAndApply(tag, f){
  var elms = document.getElementsByTagName(tag);
  for(var i=0, n=elms.length; i<n; i++) f.apply(elms[i]);
}

利用例

htmlを書きかえる。スタイルも変更(無駄にタイマーを使う)。

<html>
<head>
  <script type="text/javascript" charset="utf-8" src="./js/myutil.js"></script>
  <script type="text/javascript">
  window.onload = function(){

    // id=p1 なる要素のテキストを変更
    getByIdAndApply('p1', function(){
      this.innerHTML = "foo";
    });

    // 全p要素のスタイルを変更
    getByTagNameAndApply('p', function(){
      // クロージャを使う例(setTimeout内でthisを参照するため仮引数elmとして保持)
      (function(elm){ setTimeout(function(){elm.style.color='#f00';}, 2000) })(this);
    });
  }

  </script>
</head>
<body>
  <p id="p1">text</p>
  <p id="p2">text</p>
  <p id="p3">text</p>
</body>
</html>

2010年10月26日火曜日

mousemoveイベントのブラウザごとの違い(IE&ChromeとFirefox)

マウスポインタをちょっと愛らしくしたい、なんて仕事がたまにあると思う。画像を変えて、マウスに少し遅れて追従するような動きをさせる、とか。

例えば以下のサイトのように。

これらのサイトをいろいろなブラウザで閲覧して気づいたのだが、Firefoxはほかと挙動が違う。具体的には、ページを読み込んでからマウスを動かすまで、マウスポインタに画像が追従しない。試しにFirefoxでリロードしてからマウスを動かさずにただ眺めてみよう。

これがなぜなのか少し試行錯誤してみたところ、Firefoxだけは人が実際にマウスを動かすまでは有効なmousemoveイベントオブジェクト(= clientXなどの座標値が正しくセットされたmousemoveイベントオブジェクト)が発生しないからのようだ。反対に、Firefox以外の場合は何もしなくても mousemoveイベントが定期的に発生するため、適当なタイミングでその座標値を取得し、画像を設定した要素のスタイル(position:top, position:left)を書き換えることで画像をマウスのところまで移動させることができる。

実験として次のようなhtml+スクリプトを読み込んでみる(jQuery利用)。マウスを動かさずにじっとしていれば、ブラウザの違いが確認できる。

<html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$(function(){
  var cnt = 0;
  function f(e){
    $('#info').text(cnt + ' : ' + e.clientY);
    cnt++;
  };
  // mousemoveイベントに上の関数をバインド
  $('body').mousemove(f);
});
</script>
</head>
<body>
<p id="info"></p>
</body>
</html>
IE, Chrome の場合は、
ロードしただけでmousemoveが定期的に発生(カウンタ表示が1秒ごとくらいに増えていく。マウスを動かしていないのに…)
したがってマウス座標(mousemoveイベントオブジェクトのclientYプロパティ)が取得できる
Firefox(3.6)の場合は、
待っていても mousemove が発生しない。座標が取れない。

待ってもイベントが発生しないのであれば、強制的に発生させてみたらどうか? そこで、上のスクリプトを1行だけ変えてtrigger()を追加してみる。

(略)
  // mousemoveイベントに上の関数をバインド。さらにtrigger()で強制的にイベントを発生させる
  $('body').mousemove(f).trigger("mousemove");
});
</script>
(略)
この結果は、
0 : undefined と表示されて終了。triggerで発生させたイベントオブジェクトには座標値がセットされないらしい。

というわけでFirefoxの場合だけ、マウスが動くまでマウス用画像は座標(0, 0)の位置で待機するという仕様になりました…

2010年10月25日月曜日

無名関数(匿名関数)を再帰的に呼び出す

JavaScriptでは、無名関数の中で "arguments.callee()"を実行することにより、無名関数を再帰的に呼び出すことができる。

※Javaをやる人の間では匿名関数と呼ぶのが主流だが、JavaScriptをやる人は無名関数と呼ぶ場合が多い?

例:ID "foo" をもつDOM要素の、5階層以上の親に当たるTABLE要素を探し出してstyle属性をクリアする。

<script type="text/javascript">
(function(id){
  var elm = document.getElementById(id); // 指定されたidをもつ要素

  if(elm){
    var cnt=0;
    var target = (function(o){ // 再帰的な無名関数
      if(cnt >= 5 && o.tagName == 'TABLE') {
        return o; // 条件を満たす要素
      } else {
        cnt++;
        return arguments.callee(o.parentNode); // 親要素に対し無名関数を適用
      }
    })(elm);
    target.style.cssText = ''; // 目的の要素のスタイルを変更
  }

})('foo');
</script>

2010年9月28日火曜日

JavascriptでUNIXタイムスタンプを得る

こちら↓の記事そのまんま。

Get a UNIX timestamp with Javascript

var ts = Math.round(new Date().getTime() / 1000);

new演算子とドット「.」の評価順序を陽に示したい場合は次のように書けばいい。

var ts = Math.round((new Date()).getTime() / 1000);

用途はいろいろあるだろうけど、今回はFlash(SWFファイル)をブラウザにキャッシュさせないために使用。クエリストリングにタイムスタンプを付加して、SWFファイルのURLを毎回変化させる。次のような感じで。

   // このコードは swfobject.js(http://code.google.com/p/swfobject/)に依存しています

   var serial = Math.round(new Date().getTime() / 1000); // タイムスタンプをシリアル番号として利用
   var flashvars = { };
   var params = { allowScriptAccess: 'sameDomain', allowFullScreen: 'false', scale: 'noscale', quality: 'autohigh', wmode: 'transparent', base: '/swf', menu: 'false', salign: 't' };
   var attributes = { id:'movie01', name:'movie01' };
   swfobject.embedSWF("/swf/movie.swf?s=" + serial, "myContent", "600", "300", "9.0.0",  "/swf/expressInstall.swf", flashvars, params, attributes );

もっとトリッキーな方法

Math.roundやMath.floorを使わずに、浮動小数点数から整数への暗黙の型変換(英語もついでに覚えよう:implicit type conversion)で切り捨てを実現することもできる。

補数演算(ビット反転)による切り捨て
var ts = ~~(new Date()/1000);    //~ が補数演算子。2回作用させれば元の値に戻る
論理和演算(OR演算)による切り捨て
var ts = new Date()/1000|0;    //| がOR演算子。ゼロとのORゆえ実質的には型変換のみ行われる

2010年8月17日火曜日

アコーディオンメニュー

L社のWebサイト案件にて。

jQuery UI を使わないで自作(プラグイン化)。

HTMLの構造は違うのだけど、下記サイトを参考にした。

参考
jQueryでアコーディオンメニューを作成する | TRIPLEXXX

HTML構造が変&要素が巨大なのでマウスオーバーイベント駆動かつアニメーションにすると振動が発生してしまった。よってクリックイベント駆動にしておいた。

2010年8月4日水曜日

2010年7月16日金曜日

JavaScript文字列検索のバリエーション

indexOfメソッドか正規表現(RegExpオブジェクト)を使う。

RegExpオブジェクトはnewせずにリテラルをスラッシュで囲む方法もある(静的な正規表現)。

for (var i = 0; i < 3000; i++) {
    //var m = str.indexOf('item:9999');           // (1) indexOf
    //var m = /item:9999/.exec(str);             // (2) 静的な正規表現
    //var m = new RegExp('item:9999').exec(str); // (3) 動的生成の正規表現
    //var m = /item:9999$/.exec(str);             // (4) 正規表現 (3/31 追加)
}

4番目は、わざと正規表現のメタキャラクタを入れて最適化を無効にしている。

RgeExpに関しては、存在確認だけならexecよりtestを使うほうが行儀がいい。