Archive for the ‘JavaScript’ Category

‘Save This Page’ 件数表示の実装

Saturday, May 19th, 2007

今回の 表示機能 の実装は、

  • del.icio.us・はてブ の API をたたき、JSONP (JSON with Padding) 形式のデータを出力する Ruby スクリプト
  • JSONP 形式データを受け取り、整形・表示する JavaScript

の 2 つから構成される。実装にあたり、以下を参考にさせていただいた。

以下、個別にまとめておく。

はてなブックマーク の API をたたくスクリプト

はてなの場合、自分のブログに被ブックマーク数を表示する にあるような簡単な方法があるが、画像形式で表示され、デザインの調整がきかないことから、XML-RPC で API をたたく方法をとった。作成した getHatenaBookmarkCount.rbx の内容は以下のとおり。

#!/usr/bin/ruby -w
require 'cgi'
require 'xmlrpc/client'
require 'json/pure'

cgi = CGI.new
print cgi.header("type"=>"text/html")
end_point = 'http://b.hatena.ne.jp/xmlrpc'

url = cgi.params['url'][0] ? cgi.params['url'][0] : ""
callback = cgi.params['callback'][0] ? cgi.params['callback'][0] : nil

unless url =~ /^http:\\/\\/blog\\.shimazu\\.org\\//
	exit
end

urls = [ url ]
client = XMLRPC::Client.new2(end_point)
begin
	result = client.call('bookmark.getCount', *urls)
rescue XMLRPC::FaultException => e
	data = { 'error'=>e.faultCode, 'detailed'=>e.faultString }
	print data.to_json
	exit
end

data = [ { 'url'=>url, 'total_posts'=>result[url] } ]
if callback
	print callback ,'(', data.to_json ,')'
else
	print data.to_json
end

Ruby XMLRPC はてなブックマーク件数取得API というその名もずばりな参考資料があったので、それをベースにしている。

Ruby の json ライブラリは JSON implementation for Ruby から取得でき、rubygems でのインストールも可能。C で書かれたバージョン (json) と Pure Ruby のバージョン (json_pure) があるが、なぜか Debian GNU/Linux (Sarge) に前者 (json) がインストールできず、代わって後者 (json_pure) をインストールした。

del.icio.us の API をたたくスクリプト

del.icio.us / help / json / url にあるとおり、del.icio.us では普通に JSONP 形式の API が提供されてはいるものの、URL の指定に MD5 の暗号化が必要とのこと。Paul Johnson’s implementation of MD5 in JavaScript という凄腕ライブラリはあるものの、クライアント(ブラウザ)での処理は極力減らしたいので、MD5 処理する Ruby スクリプトをはさむかたちとした。作成した getDeliciousCount.rbx の内容は以下のとおり。

#!/usr/bin/ruby -w
require 'cgi'
require 'digest/md5'
require 'open-uri'

cgi = CGI.new
print cgi.header("type"=>"text/html")

api = 'http://badges.del.icio.us/feeds/json/url/data?hash='

url = cgi.params['url'][0] ? cgi.params['url'][0] : nil
callback = cgi.params['callback'][0] ? cgi.params['callback'][0] : nil

unless url =~ /^http:\\/\\/blog\\.shimazu\\.org\\//
	exit
end

request = api + Digest::MD5.hexdigest(url)
response = OpenURI.open_uri(request).read.chomp!

if callback
	print callback ,'(', response ,')'
else
	print response
end

出力結果

両者いずれも URL と callback 関数名 をパラメータ指定して、JSONP 形式のデータを出力するようになっている。出力例は以下のとおり(わかりやすくするため整形済み)。

例:getDeliciousCount?url=http://blog.shimazu.org/archives/95&callback=getDeliciousResponse

getDeliciousResponse([{
	"hash":"f021170bb35f553b70b7f392412babe4",
	"top_tags":{},"url":"http://blog.shimazu.org/archives/95",
	"total_posts":1
}])

例:getHatenaBookmarkCount?url=http://blog.shimazu.org/archives/89&callback=getHatenaBookmarkResponse

getHatenaBookmarkResponse([{
	"url":"http:\/\/blog.shimazu.org\/archives\/89",
	"total_posts":1
}])

なお、http://blog.shimazu.org/ で正規表現をかけているのは、http://blog.shimazu.org/ 以外のドメインでの利用を制限するため( XMLHttpRequest ではないので、クロスドメインでのアクセスが普通にできてしまうがそれを抑える目的)。

JSONP 形式データを受け取る JavaScript

データを受け取る処理を getcount.js としてまとめた。

function getDeliciousResponse(data){
if(!data||!data[0]||!data[0].total_posts){return false;}
var str = data[0].total_posts + (data[0].total_posts==1?'user':'users');
document.getElementById('delicious').innerHTML = '<a href="http://del.icio.us/url/' + data[0].hash + '">' + str + '</a>';
}
function getHatenaBookmarkResponse(data){
if(!data||!data[0]||!data[0].total_posts){return false;}
var str = data[0].total_posts + (data[0].total_posts==1?'user':'users');
document.getElementById('hatenabookmark').innerHTML = '<a href="http://b.hatena.ne.jp/entry/' + data[0].url + '">' + str + '</a>';
}
var el_delicious = document.createElement('script');
el_delicious.src = '/utils/getDeliciousCount?url=' + location.href + '&callback=getDeliciousResponse';
document.getElementsByTagName('head')[0].appendChild(el_delicious);
var el_hatena = document.createElement('script');
el_hatena.src = '/utils/getHatenaBookmarkCount?url=' + location.href + '&callback=getHatenaBookmarkResponse';
document.getElementsByTagName('head')[0].appendChild(el_hatena);

’Save This Page’ 件数表示

Sunday, May 13th, 2007

以前取り付けた ’Save This Page’ Button に「このページは何件ブックマークされているか」を表示させる仕組みをつけてみた。今回も del.icio.usはてなブックマーク の両方に対応している。

savebutton.png

目に見えるものはたったこれだけなのに、実装にやたら時間がかかってしまった。実装方法などについては、追ってまとめることにする。

追記
‘Save This Page’ 件数表示の実装 に実装詳細を記載。

JavaScript: How to build and deploy

Sunday, May 13th, 2007

1万行を超える比較的長大な JavaScript コードを deploy する際、どのような点に気をつけておくべきか、当たり前なこともあるけど、包括的にまとめている資料が見当たらないので、自分でまとめておく。

ファイルの統合

長大な JavaScript を書く際、機能別にファイルを分けることは、他の言語同様、有効な管理方法のひとつであるが、production 環境において同じファイル構成をとっていると、それが通信上のボトルネックになることがある。理由は、ページローディングの際に、大量の Request/Responce が発生するため。自分で計ったわけではないが、同じコード量でも、複数ファイルに分かれているよりも、単一のファイルにまとまっていたほうが、一回の Request/Responce で済み、処理が早いという。なので複数ファイルで構成される JavaScript の deploy にあたっては、事前にファイルの統合を行うことが望ましい。

コードの圧縮化・難読化

比較的長大な JavaScript コードは、圧縮化・難読化を行うことがよいとされている。圧縮化とは、コードの改行・インデント・コメント・余分なスペースをカットして、その名のとおりファイルサイズを圧縮することを指す。難読化とは、ファイル圧縮に加え、変数名の省略化(例えば A、B、C… といった文字に置換される)を行うことで、処理内容を読みづらくすることを指す。ツールを探すと、いくつか候補があるようで、すべて検証できたわけではないが、何点か掲載しておく。

検証済み

未検証

Build

JavaScript は interpreter 言語であり、当然 compiler は通常存在しないが、比較的長大な JavaScript コードを扱う際には、Linux/FreeBSD といった環境上で GNU make を使った build 環境を用意しておくと便利といわれている。理由は、上記に挙げた「ファイルの統合」「コードの圧縮化・難読化」を自動化させるため。ファイル修正のたびに手作業で行うのは、可能であるにしても、あまり効率的とはいえず、C/C++ や Java などと同様、Makefile というかたちで、必要な処理をまとめて自動化するほうが効率的で間違いがないとされている。

jsbuild.png

Build 環境の作り方

Debian GNU/Linux を前提に記すが、他の Linux あるいは FreeBSD ならば大差はないと思われる。圧縮化ツールには、jsmin (written by C) を使用した。

開発ツールとライブラリヘッダをインストール

# apt-get install gcc g++ make libc6-dev

開発者のサイトからソースコードをダウンロードする。(2007/05/13現在の最新版は、2007/03/27であり、比較的こまめに更新されていると思われる)

# wget http://www.crockford.com/javascript/jsmin.c

コンパイル

# gcc -o /usr/local/bin/jsmin jsmin.c

Makefile の作成

# vim Makefile

以下記述例

SRC = is1.js is2.js
COMP = is.compressed.js
COMB = /tmp/tmp.js
COMMENT = "Copyright (c) 2007 Yuki SHIMAZU. All rights reserved."
all: $(COMP)
$(COMB): $(SRC)
	cat $(SRC) > $(COMB)
$(COMP): $(COMB)
	jsmin < $(COMB) > $(COMP) $(COMMENT)

以下コマンドにて、必要な処理(ファイル統合・コード圧縮化・コード難読化)が行われたファイル(is.compressed.js)が生成される。

# make

以上でファイル生成が完了。このファイルを deploy することで、通信上の効率性を改善できると思われる。

参考ページ

スマートじゃない DOM 拡張

Sunday, February 25th, 2007

この数ヶ月、理解しがたい JavaScript の不思議の一つ。json.jsobject.toJSONString() ような感覚で、以下のような感じで DOM にメソッドを追加してみると、

HTMLElement.prototype.hogeHogeHoge = function() {
//  something procedure releted to HTMLElement
};

Firefox などでは問題ないが、IE だとうまくいかない( IE では HTMLElement という考え方がないため)。

prototype.js の場合、 この状況を次のように処理して問題解決している、ように見える。(まるごと JavaScript & AJAX より引用)

// Extending DOM object
var element = Element.extend(document.createElement('div'));

// observe() is extended function
element.observe('click', eventHandler);

// If the element isn't extended, it's able to use observe() by this code.
Element.observe(element, 'click', eventHandler);

Firefox、Safari、IE など主要ブラウザでこのコードで必要な処理ができてはいるが、毎回 Element.extend() するのが冗長な気がする。

それくらいなら document.createElement() で返す DOM そのものを拡張したものに、と思うのだけど、IE では document.createElement() のオーバーライドはできるものの、関連する document.getElementById() などがオーバーライドできないという問題があり(例外になる)、あまりスマートではない。

HTMLElement のラッパーオブジェクトをつくり、そこにメソッドを追加という方法もあるが、それはプロトタイプベースオブジェクト指向言語には冗長なつくりだし、ここはやはり既存オブジェクトの拡張で進めたい、ところではあるが、なかなかうまくいかない。

たとえば Ruby on Rails では、付属の ActiveSupport ライブラリで自動的に数字の型が拡張され、

puts 3.days.ago

みたいな書き方ができる(Integer=>Time の変換)。もちろん 3 は普通の Integer としても扱える。とても自然でスマートで、プログラミング言語とは思えないくらいの直感さがあるように思う。

これ厳密には IE の実装の問題なのだけど、こういった問題がなくならない限り、JavaScript の不可思議さはなくならない気がする。

JavaScript ライブラリ比較

Monday, November 27th, 2006

たまたま同じ時期に似たような比較記事(比較表)を目にしたので、勝手にリンク。

ウェブ制作の黎明期?から JavaScript に苦しめられてる自分としては、優れた汎用的なライブラリが比較するほどそろっていることにまず感動。記事内容的にも、観点はちょっと違うけれど、どちらもおもしろい内容になっている。

以下思いつきだけど自分なりのライブラリ選出基準。

  1. ブラウザごとの差異を吸収する
  2. コーディングパターンを強制しない
  3. ファイルサイズは重くて可、実行スピードが重いのは不可

1. は当然、ブラウザごとの差異を吸収することで、ライブラリ利用者に差異を意識させず、機能の実装に集中させる。そうやってブラウザごとの調整を他人任せにできる状態にさせることが大事だと思う。

2. については、実装機能によっては利用ライブラリが複数になることもありうるが、ライブラリ導入によりコーディングパターンが大きく変わってしまうのは、利用者にとっては負担になると思われる。jquery のコードはおもしろいくらいに JavaScript らしくないパターンだけど、それを取り入れるには相当勇気がいるような気がする。

3. のファイルサイズについては JavaScript のライブラリが 100kb を超えようが 500kb を超えようが、ページを飾る 1mb 超の flash に比べれば軽いわけで、世間が言うほどライブラリのファイルサイズは大きな問題にはならないと思う。それよりなにより実行スピード。これがすごく重要。当然、サーバサイドとは異なり実行環境のマシンスペックはばらつきがあるし、機能強化の一方で、ライブラリによっては単一の機能にものすごいステップ数のコードを挟み込んだりして、まだまだ改善の余地があるような気がする。というか単に重要視してないだけかもしれないけど、たとえば dojo とかこれを使っているところがあるのかと思っちゃうほど、重かったりする。

Graceful degradation

Tuesday, October 24th, 2006

品のある劣化? 直訳するとまたもよくわからないけれど、Progressive Enhancement に引き続き、JavaScript での実装におけるキーワードを見つけたので、メモ。

内容としては、JavaScript OFF の閲覧環境において、必要最低限の情報が得られる・必要最低限の機能が使えるように、実装上の配慮を行う(配慮した実装を行う)、といった感じみたい。

例えばポップアップウィンドウを呼び出すリンクの場合、

<a href="#" onclick="popUp('http://www.example.com/');return false;">Example</a>

ではなく

<a href="http://www.example.com/" onclick="popUp(this.href);return false;">Example</a>

のようにする、とか。

JavaScript で画面を作りこんでるときとか、href に正規の URL 入れてもあまり意味をなさない、といった問題はあるけど、実装時の心がけ?として大事になりそう。

余談だけど、Wikipedia で調べてみたら、Fault-tolerant system と同義語扱いになっていたのが興味深い。

Progressive Enhancement

Friday, September 29th, 2006

Progressive Enhancement という UI を考える上でのキーワードがあるらしい。
http://en.wikipedia.org/wiki/Progressive_Enhancement

進歩的な強化? 直訳すると全然よくわからないけれど、思いっきりカンタンにまとめると

  • document.write を使わずに、DOM Scripting に徹する
  • ブラウザ判別において、UA を見ずに関数(getElementByIdなど)実装の有無で判別する

といったことらしい。

進歩的な強化の真意はよくわからないけど、というかこの訳が正しいかも微妙だけど、このあたりを実践することで、JavaScript コードがより抽象的で汎用的になりそうな気がして、興味深い内容だと思う。

Ajax と一緒に使いたくなるプラットフォーム

Thursday, September 28th, 2006

Ajaxian.com 2006 Survey Results によると、

  1. PHP (50%)
  2. Java (37%)
  3. .NET (16%)
  4. Rails (14%)
  5. Python (6%)

ということで、Lightweight Language 全盛と思いきや、Java が健闘していて、逆に Perl がベスト5落ち、というのが興味深いところか。

関係ないけど、Ajaxian.com 、ほぼ毎日ほぼ Ajax ネタだけで更新されていて、いくらブームとはいえ、よくそんなに話題があるなと思ってしまう。

jQuery : New Wave Javascript

Sunday, September 24th, 2006

Beginning Javascript With DOM Scripting And Ajax という本で紹介されている jQuery というライブラリが興味深い。

prototype 同様、JavaScript の汎用的な機能を提供しているのだけど、記述頻度の高いコードが prototype 以上に整理されていて、パッと見、JavaScript ではない、独自言語のように見えてくる。

$(document).ready(function() {
    $("#orderedlist li:last").hover(function() {
        $(this).addClass("green");
    }, function() {
        $(this).removeClass("green");
    });
});

上記は、Getting Started with jQuery に記載されていたコード。prototype は Ruby のようだったけれど、jQuery は Lisp のよう? いや Lisp をそんなに知ってるクチではないけど、Lisp 並にカッコが多くなる割に、JavaScript の原型はとどめており、わかりやすくコンパクトになっている気がする。

prototype よりも後発ということで、prototype 並みに普及するか、prototype を凌駕する存在になるか、見所。

WordPress Plugin: WP-Amazon

Wednesday, September 13th, 2006

WordPress で Amazon の商品情報を参照できるプラグインを入れてみる。

割とカンタンに入ってしまった。手順としては、まず最新バージョンの 1.3.2 を下記URLから取得。

WP-Amazon 1.3.2
http://manalang.com/wp-content/wp-amazon-1.3.2.zip

上記ファイルを展開後、

  • wp-amazon-plugin.php
  • wp-amazon.php

を wp-content/plugins/ 配下にコピーすると、設定画面が表示されるので、そこで Default Countryと Associates ID を選択する。入力画面下部のリンクからポップアップウィンドウを呼び出し、商品検索→商品選択→Add ボタンクリックで商品画像が入力画面に、のハズなのだけど、なぜか機能しないので、wp-amazon.php の以下を書き換えて対応した。(リッチテキストON/OFF両対応)

function insertAtCursor(myField, myValue) {
    var win = window.opener ? window.opener : window.dialogArguments;
    if ( win.tinyMCE ) {
        if (!win) win = top;
        var tinyMCE = win.tinyMCE;
        richedit = ( typeof tinyMCE == 'object' && tinyMCE.configs.length > 0 );
        if ( richedit ) {
            tinyMCE.execCommand('mceInsertContent',false,myValue);
        }else{
            win.edInsertContent(win.edCanvas);
        }
    } else {
        //IE support
        if (document.selection) {
            // only insert text for IE (not at cursor)
            myField.value += myValue;
        }
        //MOZILLA/NETSCAPE support
        else if (myField.selectionStart || myField.selectionStart == '0') {
            var startPos = myField.selectionStart;
            var endPos = myField.selectionEnd;
            myField.value = myField.value.substring(0, startPos)+ myValue
                + myField.value.substring(endPos, myField.value.length);
        } else {
            myField.value += myValue;
        }
    }
}