innerHTMLとremoveChild()

IEとfirefoxでinnerHTMLとremoveChildを使用した際の挙動が違っていたのでメモ。

以下のソースは、DIV要素を作成してoBodyオブジェクトに挿入した後で、oBodyの中身を空にする処理を行っている。(実際にはこんなことありませんが。。。)

var oDiv = document.createElement('DIV');
oDiv.innerHTML = 'おはよう';
oBody.insertBefore(oDiv, null);
oBody.innerHTML = '';
alert(oDiv.innerHTML); // IEでは何も出ない

この処理の後、firefoxではoDivはまだDIV要素として存在しているが、IEでは空のオブジェクトになってしまう。

一方で、oBody.innerHTMLの行を以下のように変更してやると、IEでもoDivオブジェクトの値が残る。

var oDiv = document.createElement('DIV');
oDiv.innerHTML = 'おはよう';
oBody.insertBefore(oDiv, null);
oBody.removeChild(oBody.firstChild);
alert(oDiv.innerHTML); // おはよう

意味分かりにくいですよね?

とにかく、ある要素の中身を空にしたい場合は、inerHTML = ”みたいな安易な方法を使うよりも、まじめにremoveChild()を使用した方がいいということです。

prototype.jsと連想配列

Ajaxアプリケーションの開発においてprototype.jsを使用すると様々な恩恵をえられる。

私自身も話題のJQueryよりもPrototype.jsを使用する機会の方が多いのだが、よく言われるように、配列やオブジェクトの汚染にも悩まされることが多い。

prototype.jsを使用していない場合の配列、連想配列、オブジェクトの初期化

prototype.jsを使用していない場合は、以下のように初期化することが一般的である。

var array = new Array(); // 配列の初期化
var hash = new Array(); // 連想配列の初期化
var obj = new Object(); // オブジェクトの初期化

prototype.jsを使用していない場合この方法で何も問題は生じないが、prototype.jsを使用するとループで処理した際などに意図していない値が出力されてしまうことがある。

これが、よくいわれるprototype.jsによる汚染である。

prototype.jsを使用した場合の初期化

上記のような汚染を免れる場合には以下のように初期化する必要がある。

var array = []; // 配列の初期化
var hash = {}; // 連想配列の初期化
var obj = {}; // オブジェクトの初期化

上記の例で注意する必要があるのは、連想配列である。

通常の場合、連想配列は配列と同じようにnew Array()とすればよいので、prototype.jsを使用する場合でも、hash = [];としてしまいがちだが、この方法では実際には汚染されてしまうので注意。

JavaScriptでは、連想配列とオブジェクトは同じ扱いであり、たとえばfoo.barという値にはfoo[bar]という方法でもアクセスできる。

そんなわけで、prototype.jsを使用する場合には、連想配列もオブジェクトとして初期化する必要がある。

ちなみに連想配列またはオブジェクトをループで処理する場合は以下のような感じ

for (var i in hash) {
  alert(hash[i]);
}

簡単なサンプルなので分かりにくいかもしれないが、iにはプロパティ名(連想配列で言えばキー)が、hash[i]には値が格納されている。

htmlparser.jsが動作しない

弊社で独自に制作したWordPressテーマにいくつかのSEO上有用と思われる情報を掲載しようと思い、あれこれ探していたところ、JavaScriptのHTMLパーサーをライブラリを見つけた。

John Resig – Pure JavaScript HTML Parser

JavaScriptは元々Domパーサーできるじゃんと思われる方も多いと思うが、今回やりたかったのは外部のHTMLをパースしたかったので、このようなライブラリが必要だった。

で、結論。

動きませんでした。

原因は、prototype.jsによるものらしい。

prototype.jsはその機能上配列やオブジェクトを汚染してしまうので、どうしても相性というのが生じてしまう。

素直にPHP側でパースして処理を行うことで解決した。

ちなみにやりたかったことはこんなこと。

wp

WordPressを企業サイトに納入する際に、うちではダッシュボードもカスタマイズしているのですが、今回はこのダッシュボードの右側の部分。

XMLに対するSafariのgetElementsByTagName()

JavaScriptでRSSをパースするスクリプトを作成してテストしていたら、Safariでだけうまく動作しなかった。

いろいろテストしてみたら、以下のような部分に問題があったことがわかった。

var siteLink = chnl.getElementsByTagName('link')[0].childNodes[0].nodeValue;

RSSのほうでは以下のような感じ。

-- 中略 --
<channel>
 <title>デジタルカタログ制作のデジパン</title>
 <atom:link href="http://www.digipan.jp/feed" rel="self" type="application/rss+xml" />
 <link>http://www.digipan.jp</link>
-- 中略 --

どうやら、Safariは名前空間atomのlink(atom:linkという要素)もgetElementsByTagName(‘link’)で拾ってしまうらしい。

IEやFirefoxではこのような問題はなかったが、どちらの解釈が正しいのかは不明。

そういえば、昔はFirefoxもこういう解釈だった気がするけど気のせい?

XML文字列をDOMエレメントに変換する

先日公開したJavaScriptでクロスドメインでのXMLアクセスを可能にするライブラリ「CrossOver」は、コールバック関数への引数がXML形式のテキストデータです。

DOMオブジェクトで戻せば余計な処理をゴリゴリ書く必要がなくて、親切だったのかもしれませんが、XML.ObjTreeなどの他の優秀なライブラリとの連携を前提にすることで、最小限の機能だけを提供することが可能になり、信頼性の確保が可能になると考えました。

それに実は私、アクションスクリプトは詳しくありません。

ただ、そうはいっても、やっぱりDOM操作をゴリゴリやりたいという声も多いようなので、XML形式のテキストデータをDOMオブジェクトに変換する関数を書いておきます。

// xmlStringをDOMオブジェクトに変換
function parseFromString( xml ){
  var root;
  if ( window.DOMParser ) {
    var xmldom = new DOMParser();
    xmldom.async = false;
    var dom = xmldom.parseFromString( xml, "application/xml" );
    if ( ! dom ) return;
    root = dom.documentElement;
  } else if ( window.ActiveXObject ) {
    xmldom = new ActiveXObject('Microsoft.XMLDOM');
    xmldom.async = false;
    xmldom.loadXML( xml );
    root = xmldom.documentElement;
  }
  return root;
}

CrossOverで定義したコールバック関数に渡された値を上記関数に渡してやると、戻り値がDOMオブジェクトに変身します。

CrossOver – JavaScriptでクロスドメイン

JavaScriptでは、セキュリティ上の制限により、違うドメインのXMLを読み込むことができません。

一方Flashでは、crossdomain.xmlにより許可されたドメインからはXMLの取得が可能です。

そこで、FlashのExternalInterfaceを利用して、JavaScript側で指定したURLからFlashでXMLを取得して、その結果をJavaScriptに返すだけの小さなswfファイルを作成しましたので公開します。

まずは、こちらのサンプルページをご覧ください。

ダウンロード

ダウンロード(CrossOver.zip)

パッケージ内容

PHPやCGIが不要です!

  1. CrossOver.swf
  2. CrossOver.js
  3. swfobject.js

利用方法

</body>の直前あたりに以下のソースを貼り付けてください。
パスは環境にあわせて修正願います。

<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript" src="CrossOver.js"></script>

ボタンクリックなどのイベントでloadCrossOver()という関数を実行してください。
引数には、コールバック関数名及びXMLのURLを渡してください。
(コールバック関数名はダブルクォーテーション等で囲む必要があります。)

また、XMLリクエストが成功した際に実行されるコールバック関数を定義してください。

詳細は、サンプルページなどもご参照ください。

// XMLHttpリクエストをFlashにしてもらう関数
oButton.onClick = function{
  loadCrossOver( 'callback', 'http://example.com/feed.xml' );
}
// 上記関数の第一引数と同じ名前の関数を定義
function callback( XMLString ){
.......... // XMLに対する処理
}

コールバック関数の引数には、取得したXMLのString型データが渡されますので、XML.ObjTree等と併用することで、オブジェクトとして利用可能です。

ライセンス

MITライセンスとします。

ご注意

crossdomain.xmlで許可されていないサイトのXMLにはアクセスできません。
他の手を考えましょう。

Flickr APIを使う

すぐに忘れてしまうので、ソースをメモ

実際のデモ

以下の画像は実際にFlickr APIを利用して、natureで検索してヒットした画像を人気順に8件表示するという処理を行っています。
JavaScriptなので、このBloggerでも動きます。

設置方法

以下は、flickr_photosという値のID属性をもつDIV要素内に写真をだーっと並べるだけのサンプルです。

Flickr APIでJSONデータを取得する

JSON Response Formatのページなどを参考にしてJSONを取得する。

以下のソースを適当に設置する

JSONを取得するためのコードより前に記述しておく必要があります。

function jsonFlickrApi( obj ){
  if( obj.stat != 'ok' ){
    return false;
  }

  document.getElementById('flickr_photos').innerHTML = '';
  document.getElementById('flickr_photos').style.textAlign = 'center';
  var p = obj.photos;

  for( var i=0; i<p.photo.length; i++ ){
    var id = p.photo[i].id;
    var owner = p.photo[i].owner;
    var secret = p.photo[i].secret;
    var server = p.photo[i].server;
    var farm = p.photo[i].farm;
    var title = p.photo[i].title;
    var ispublic = p.photo[i].ispublic;
    var isfriend = p.photo[i].isfriend;
    var isfamily = p.photo[i].isfamily;

    var url = 'http://www.flickr.com/photos/'+owner+'/'+id+'/';
    var oimg = 'http://static.flickr.com/'+server+'/'+id+'_'+secret+'.jpg';
    var simg = 'http://static.flickr.com/'+server+'/'+id+'_'+secret+'_s.jpg';

    var img = document.createElement('IMG');
    img.src = simg;

    var link = document.createElement('A');
    link.title = title;
    link.href = url;
    link.insertBefore(img, null);

    document.getElementById('flickr_photos').insertBefore(link, null);
  }
}

弊社が運営するエコ生活でも使ってます。

ieでこのページを表示できません。

ieでHTMLの処理が完了する前に、JavaScriptでDomの操作などを行うと「このページを表示できません。」と言われる。

このような場合はそのコードをwindowオブジェクトのloadイベントに引っ越してやることで回避できる。

ふーーーーん。確かに、理解できなくもないが、Firefoxでは動く。

ちなみにモバイル向けASPはじめました。

JavaSCriptを圧縮 – yuicompressor

JavaScriptやCSSを圧縮してくれるyuicompressorは、それ自体はとても便利なのですが、コマンドが長くて若干めんどくさくないですか?

alias yuicomp="java -jar ~/yuicompressor-2.4.1/build/yuicompressor-2.4.1.jar --charset UTF-8 -o"

以上のような行を.bash_profileに入れると、コマンド入力が簡単になります。

適用前

java -jar ~/yuicompressor-2.4.1/build/yuicompressor-2.4.1.jar --charset UTF-8 -o out.js in.js

適用後

yuicomp out.js in.js

住所からGoogle MAPに変換する(JavaScript編)

このサイトでは、PHPのSmartyプラグインで、住所からGoogle MAPに変換するプラグインを作成して公開しています。

あちらこちらで宣伝させていただいたら、なかなか好評で、ちょっと自己満足に浸ってたのですが、よく考えたらこのプログラムって、やってることのほとんどはJavaScriptじゃんということに気がついてしまいました。

そういうわけで、JavaScriptのみで同様の機能を提供するクラスを作成したので公開します。

ダウンロード

このクラスを使用すると、以下のようにid属性にaddrが指定された要素内のテキストを住所と見なして、id属性がgmapの要素内にGoogle Mapを挿入します。

<div id="addr">東京都千代田区永田町1-7</div>
<div id="gmap"></div>

JavaScript側の記述は、<head>〜</head>内で、「Google Maps API」、「Google Ajax Search API」、「prototype.js」と、このクラスファイルを<script src=”…”></script>して、bodyの下の方に以下の記述を入れれば完了です。

<script type="text/javascript">//<![CDATA[
var gmap = new addr2gmap();
gmap.display();
//]]></script>

PHP+Smarty版と比較すると若干めんどくさいのですが、ほとんどの環境で手軽に利用できるのがメリットです。

先に紹介した、PHP+Smarty版も同じくですが、CMSなどの会社概要のページに地図を表示させたい際に手軽にGoogle MAPが利用可能なように作成していますので、あえて低機能です。

マッシュアップなんて大それた用途ではなく軽い感じで使いたいという方にご利用いただけると嬉しい限りです。

ライセンス

MITライセンスとします。
ただし、気に入っていただけた方からの寄付は歓迎します。