[ Tags :: JavaScript ]

IE9ではDOMContentLoaded をサポートするようだ

category-icon

 こんにちは、martinです。久しぶりの書き庫な気がします。

 IE9のプラットフォームプレビューLink が出ていたんで、インストールしてみました。最初に試したことは、他の主要なブラウザでは実装済みのDOMContentLoadedがサポートされているかなぁ、ということでした。現時点では、まだでしたが、IEBlogLink によると今後のロードマップに、DOMContentLoadedのサポートがあるようです。DOMContentLoaded自体は、DOM Level 3 Eventsには定義されていないので、IE9が標準仕様に忠実であろうとするならば実装は微妙だなぁ、と思っていたんですが、HTML5には定義されていますLink (注意: リンク先はかなりページが重い)ね。

 IE9でDOMContentLoadedがサポートされるのはまことに喜ばしいことですが、IE9が普及するのはまだまだずっと先の話でしょう。なので現状では、IE向けにはDOMContentLoaded相当の機能を自前で実装する必要があります。世の中には、jQueryを始めとした便利なJavaScriptライブラリーがいくつかありますが、猫も杓子もDiego Perini氏が発見したdoScrollメソッドを使う手法Link に則っています。ppBlogでのoParts.jsでも、doScrollの実行をもって、DOMパース完了とみなすやり方で実装しています。

 さて、このdoScrollメソッドですが、MSDNの説明にあるように、

The doScroll method is available on all objects, regardless of whether they support scrollbars.

引用元: doScroll Method (A, ABBR, ACRONYM, ...)Link

 ほぼすべての要素について適応可能です。多くのJavaScriptライブラリーでは、

document.documentElement.doScroll('left');

 というふうに、document.documentElementに適用しているケースが殆どですが、何でも良いので、以下のようなやつもOKです。

(function(){
  if(navigator.userAgent.match(/MSIE/)){ // IEなら
   try {
     new Image().doScroll(); // new Imageオブジェクトについて適用
     onReadyFunc(); // DOM構築後に最初に呼び出す関数
   } catch(e){ setTimeout(arguments.callee, 1);}
  } else { // IE以外
   document.addEventListener("DOMContentLoaded", onReadyFunc, false); // DOM Level 3 Events相当
  }
})();

 別にnew Imageオブジェクトをdocument.bodyappendChildする必要もありませんし、メモリーリークも起こしません。以下のようにコメント文を生成するメソッドcreateComment()を用いても良いでしょう。これも、既存の文書に作ったコメントをappendする必要はなく、ただ宣言するだけで動きます。

(function(){
  if(navigator.userAgent.match(/MSIE/)){ // IEなら
   try {
     document.createComment().doScroll(); // document.createCommentについて適用
     onReadyFunc(); // DOM構築後に最初に呼び出す関数
   } catch(e){ setTimeout(arguments.callee, 1);}
  } else { // IE以外
   document.addEventListener("DOMContentLoaded", onReadyFunc, false); // DOM Level 3 Events相当
  }
})();

 doScroll()の引数は省略可能です。省略するとscrollbarDownが適用されるので、document.documentElement.doScroll()だと、実際に画面がスクロールしてしまうかもしれません(なのでたいてい”left”を指定している)。new ImageやcreateCommentだと、DOMツリーに追加するわけではないので引数は省略して構いません。

 new Image().doScroll()なんて、短い記述で済むんでいいなぁ、と思うけれど。

 


— posted by martin at 09:50 am   commentComment [2]  pingTrackBack [0]

DocumentFragmentsの考察サイトの考察

 便利なので oParts.jsでも使っているdocument.createDocumentFragment()ですが、JavaScript EvangelistであるJohn Resig氏の先月のブログにDOM DocumentFragmentsLink というエントリーがあり、DocumentFragmentのパフォーマンスの良さを検証しています。でも、これって、随分昔にそふぃあさん(どんな人なのか知らないけれど、この方のサイトのコンテンツには目を見張るものがあって、よく覗いてました)が、通ってきた道なんだよなぁ。

 →DocumentFragmentの考察Link

 

— posted by martin at 06:43 am   commentComment [0]  pingTrackBack [0]

差分アップデート

category-icon

 こんばんは。v1.7.5になってだいぶ安定した気はしますが・・・と言いつつアップデートです。

 ひとつは、自動モブログを有効にしてると、アクセス解析の画面などでエラーが出る場合があると思うので、それに対処するためのutils.phpと(後、ソーシャルブックマークのAPIが変わったのがあるようでそれは今回見送り)、後は、JavaScript関連です。oParts.jsLink に関してですが、サーバーのセキュリティーの設定によっては、IEでの外部HTCファイルの読み込みが出来なくなる可能性もあるので、それに対処しました(うちのところがそうだったんですが)。具体的には、外部HTCファイルの読み込みを止めました。代わりに、みんなが使っているdoScrollメソッドを利用することにしました。もともとは、Diego Perini氏Link が見つけた手法です。右に倣えは好きではないけど、この場合、デメリットが見あたらない。

if(client.MSIE){ // IE向けDOMContentLoaded
 (function(){
   try {
     document.documentElement.doScroll("left");
   } catch(e){
     setTimeout(arguments.callee, 1); return;
   }
    F(); // 発火!
 })();
}

 後は、IE8やらSafari3で使えるdocument.querySelectorAllを利用できるなら、そうするようにしました。Firefox3.1でも使えるようになるでしょうし。Nativeな実装なので、ずっと速いです。oParts.query()関数の中で指定しています。

 次。エディタ用のスクリプトeditor.jsです。テキストエリアの高さ(縦幅)を文章の長さにフィットさせる関数をブラッシュアップして、結構正確に高さを合わせるようにしました。

function resizeTextArea(e){
 var MSIE = /*@cc_on!@*/false;
 var cssValue = function(p){ // 実際の諸々のサイズやらを取得する関数
  if(window.opera) e.style.lineHeight = "130%"; // Operaでは、1.3みたいな指定を読んでくれない
  return MSIE ? e.currentStyle[p.replace(/-¥D/, function(m){ return m.charAt(1).toUpperCase()})]
              : getComputedStyle(e, '').getPropertyValue(p);
 }
 var ruler = d.createElement("SPAN"); // テキストエリアのシャドウを作成
 ruler.textData = function(data){ if(MSIE) ruler.innerText = data; else ruler.textContent = data; };
 /* 以下でテキストエリアのサイズに関する情報を取得 */
 ruler.style.cssText += "position: absolute; left: 0; visibility: hidden; white-space: pre;"
                     +  "padding: " + cssValue('padding') + ";"
                     +  "font-size:"+ cssValue('font-size') + ";"
                     +  "line-height:" + cssValue('line-height') + ";"
                     +  "font-family:" + cssValue('font-family') + ";";
 document.body.appendChild(ruler);
 var rows = 1, lines = e.value.split(/¥n/), lineHeight = 17, curWidth = e.clientWidth;
 for (var i = 0, len = lines.length; i < len; i++){
  ruler.textData(lines[i]);
  if(ruler.offsetWidth > curWidth) rows += parseInt(ruler.offsetWidth / curWidth);
 }
 ruler.textData("A¥nZ"); // テキストエリア1行分の高さを知りたい
 lineHeight = ruler.offsetHeight/2; /* これでゲット*/
 document.body.removeChild(ruler);
 e.style.height = lineHeight * (rows + len + 1) + "px"; // rows+lenが見た目のほぼ正確な行数。遊びのために1行足す
}

挙動に関して、どういうのが使い勝手が良いかは、各人の好みによりますが、文字入力に応じてテキストエリアのサイズを自動的に変えたい場合は、editor.jsの778行目にあるコメントアウトを外して

ed.on('keyup', function(){
 resizeTextArea(ed);
});

として下さい。一応、このデモサイトLink を挙げておきます。

例によって、最新版との差分を添付しておきます。単なる上書きで良いです。最新版のjs/oParts.jsにしたら、もはやjs/ie.htcは不要ですので削除して下さい。

添付ファイル: DIFF080801.zipattachedIcon 

 


— posted by martin at 01:58 am   commentComment [10]  pingTrackBack [0]

記事編集時に表示しているページに簡単にアクセスしたい

category-icon

 こんばんは。この週末は時間が取れそうなので、マイナーアップデートを予定しています。さて、自分のブログを書いていて、不便に思ったところを改善してみたのでメモ。

 この旅日記Link のようにページ数が10ページとかなると、記事の編集時には、テキストエリアが縦にずらっと10個並ぶわけです。で、修正したいページのテキストエリアにたどり着くのに、ちょっと間を取られるので、(ログインモードで表示される)編集アイコンをクリックしたら、直接、表示しているページのテキストエリアにフォーカスが行き、かつ、その他のテキストエリアは、縮めた状態で表示させるようにしてみました。修正は、jsファイルのみで済みます。

 記事の各ページはAjaxを利用して、該当ページの部分のみ動的に読み込んでいるので、ログイン中に記事タイトルの右横に表示される編集アイコンEDITのリンクは、最初のページのままで変わりません。なので、記事ページを読み込んだタイミングで、この編集アイコンのリンクも書き換えるようにします。これはlib.jsのloadPage()をいじります。簡単でして、

if(o('a[href*=edit]', ownerDiv)){
 o('a[href*=edit]', ownerDiv).each(function(a){a.href = (a.href.replace(/&?page=¥d+$/, '') + "&page=" + page);});
}

というのを追加するだけです。やってることは、記事DIV要素の中で、リンク先にeditを含むA要素を探し出して(これは2つあります)、それぞれのリンクhrefに表示しているページを追加してあげる、という単純なものです。

 次に、editor.jsの方をいじります。この中のInitEditor()関数の最後の方に、以下のやつを追加。

var targetPage = /page=(¥d+)$/.exec(location.href);
if(targetPage){
 o('textarea[id^=Page]').each(function(page, index){
  if(index + 1 == targetPage[1]){
   resizeTextArea(page);
   ed = page; ed.focus(); o('#Page'+targetPage[1]+'Tab').view(1);
  } else o(page).css('height: 20px;');
 });
}

やってることは、記事編集画面でのリンク先がpage=3とかで終わっていたら、該当する3番目のテキストエリアのみを適切な縦幅で表示させて(フォーカスも合わせる)、他のテキストエリアは縦幅20ピクセルに縮めておく、ということです。

 尚、各テキストエリアの高さは、それぞれのリサイズバーをマウスでドラッグすることで自由に変えることが出来ますが、昔のマックのウィンドウシェード(MacOSXでも残っているのかな?)みたいに、ダブルクリックでトグルさせるようにするには、やはりInitEditor()関数の中に、次の記述を追加します。

o('.textareaHandler').on('dblclick', function(){
 var tar = oParts.target().sib(-1);  /* リサイズバーのひとつ前の要素、つまりテキストエリア要素を取得 */
 if(tar.css('height') == 20) resizeTextArea(tar.$); else tar.css('height: 20px');
});

 自分でいじりながら、JSファイルの修正だけで実現できたのは、ちょっと意外だったり;-) この修正を施したjsファイルを添付しておきます。特に問題がなければ、次回アップデートに反映されるでしょう。

添付ファイル: editor.jsattachedIcon  lib.jsattachedIcon 

 

— posted by martin at 10:42 am   commentComment [0]  pingTrackBack [0]

window.onloadの代替スクリプトその2

category-icon

 以前に、window.onloadの代替スクリプトGの記事を書いていたんですが、今はちょっと違ったスクリプトになっているので、この辺で、まとめもかねてメモを。

 今ではどのJavaScrptライブラリーも、画像も含めたページ読み込み完了まで待って、つまりwindow.onloadのタイミングで初期化関数を走らせるなんてことはせずに、もっと早い段階、つまりブラウザがDOMの構造を把握してパースしたタイミングを見計らって、初期化関数を実行するようにしています。Firefox、Operaでは、ブラウザ側がDOMContentLoadedというイベントハンドラを用意してくれているので、これを使います。Safariがこれを採用するのも時間の問題だろな、と前々から思っていたんですが、昨日、Safari最新版(3.1.1)も対応しているっぽいことに気が付きました。こういうスクリプトLink を書いて、イベントの発火順序を調べていたんですが、何気にSafariで動いているんでちょっとビックリしました。

 イベントの発火順序を調べる→http://p2b.jp/demo/events-order.phpLink

 IEに関しては、DOMContentLoadedというイベントハンドラはないので、以前から色々なアプローチが取られてましたが、今ではbase2Link のDean Edwardsも、jqueryLink のJohn ResigもDiego Periniが見つけたdoScrollLink を採用しています。確かに、これでも良いのですが、自分は天の邪鬼なので、HTCファイルでondocumentreadyを監視するというアプローチにしています。上に挙げたイベントの発火順序を調べるページの結果を見ると、IEに関しては、次のようなイベント順位で実行されてます。

  1. oncontentready     /* この時点でDOMのパースが終了 */
  2. deferred script      /* defer指定された外部スクリプトが実行される */
  3. ondocumentready    /* 次にHTCでのこのイベント。oParts.jsではこの時点で初期化。*/
  4. doScroll         /* 次にdoScroll */
  5. window.onload     /* 最後にこれ */

 このサイトのように、ソースをハイライト表示するスクリプトなどを使ってる場合は、数ミリ秒でも早くハイライト関数を起動させたいので、ondocumentreadyのタイミングで実行させてます。doScrollのタイミングでも良いんですが、十数ミリ秒程度遅れるんですよねぇ。oncontentreadyが一番早いですが、極々まれにこれが評価されないことがあるので、確実なondocumentreadyにしてます。ondocumentreadyとかoncontentreadyってBehavior絡みでしか使えないのが玉に瑕です。普通のイベントハンドラとして使えるようにしてくれれば良いのに、マイクロソフトさん。

 というわけで、ppBlogで使っているJSライブラリ oParts.jsLink での初期化関数は以下のような感じになってます。

oParts.start = function(F){
  if(client.Gecko || client.Opera || client.Safari){
   document.addEventListener('DOMContentLoaded', F, false);
  } else if(client.MSIE){
   $IEHTC = document.documentElement.addBehavior('js/ie.htc');
  } else o(window).on('load', F);
};

 SafariでもDOMContentLoadedが使えるようになったので記述が少なくて良いですね。IE向けのie.htcファイルの中身は以下のような感じ。

<public:attach
  event="ondocumentready"
  onevent="oParts.callee[oParts.callee.length-1]();
           document.documentElement.removeBehavior($IEHTC);
           $IEHTC=null;" />

 ちなみにイベント発火順位を調べるスクリプトですが、以下のように指定すると、このサイト以外のページも読み込めます。Yahoo!の例を挙げておきます。

http://p2b.jp/demo/events-order.php?site=yahoo.co.jpLink

 余談ですが、jquery-1.2.3のテストをしていて、気になった点がありました。以下のようなスクリプトを書くと、$()関数がwindow.onloadの後にしか実行されません(IE7の場合)。jqueryに関しては素人なので使い方が間違っているのかもしれませんが、非常に簡単な例なので気になります。

<script type="text/javascript">
  window.onload = function(){
   document.getElementById("test").innerHTML = '<p>window onloaded!</p>';
  }
  $(function(){
   alert($("#test").html());
  });
</script>

 実際の実行サンプルLink 。FirefoxとIE7で挙動が違います。Firefoxでは意図したように、window.onloadよりも先に$()が評価され、<p>original</p>とアラート表示されますが、IE7ではなぜか、window.onloadに割り当てた関数が先に実行されて、<p>window onloaded!</p>って表示されるんですよねぇ。何でかなぁ。doScroll絡み?

 

— posted by martin at 05:08 pm   commentComment [0]  pingTrackBack [0]

T: Y: ALL: Online:
Created in 0.0187 sec.
prev
2017.5
next
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31