[ カテゴリー » 開発日誌 » JavaScript ]

動的スタイルシートの作成

category-icon

 こんばんは。だいぶ日も長くなってきましたね。

 さて、小生のブログで、「エクセルデータをHTMLテーブルに変換」というエントリーを書いて、そこでppBlog用に見栄えの良いTABLEを作成してくれるページLink を公開していました。早速、使って下さったユーザーの方から、ppBlogをリスト表示にしていると、テーブルのスタイルシートが有効になっていないとのご指摘がありました。確かに、リスト表示での個別スタイルシート指定はすっかり対応を忘れていました。

 というわけで、ppBlogでは記事毎にスタイルシートやJavaScriptを指定することが可能ですが、あまり知られていない気もするので簡単に使い方を書いておきます。スタイルシート(CSS)を指定したければ、記事のどこでも良いので、[style][/style]で囲った中の記述がCSSとして認識されます。記述はまんまCSSと同じです。具体例を挙げると以下のような感じ。

[style]
.article-content table { border-color: tomato; }
[/style]

とか。.article-contentは、記事内容要素に対するクラス名です。記事の個別表示のときは、記事ボックスはひとつしかないので問題ありませんが、複数の記事が表示されている場合、上の例だと、別の記事にテーブルが含まれていたら(クラス名が同じため)そこにも反映されてしまいます。なので、これを回避するには、指定したい記事の固有IDを利用すると良いです。ppBlogでは、すべての記事にUID1234567890のようなIDが割り振られます。

[style]
#UID1234567890 .article-content table {
  border-color: tomato; /* IDを利用して適応先の .article-content を絞り込む */
} 
[/style]

追記 2009/02/19 18:56:29 考えてみると、このUIDを付加する作業は、ppBlogの方で自動的にやるべきかなぁ。次バージョンではそうしようと思います。

 記事ごとのJavaScript指定も同じように、[script][/script]で囲った中に記述すればOKです。

[script]
alert("Bonjour, tout le monde!"); /* 普通にJavaScriptを記述 */
[/script]

 前置きが長くなりましたが、本題。最初に挙げたリンク先Link のソースを見れば分かりますが、動的にスタイル指定を適応させる処理を入れています。同じような処理は、ppBlogで使っているソースハイライト表示用のsyntax.jsLink でも使っていますが、ここでは、上記エクセルコピペのテーブル変換の部分を抜粋してみます。

var sheet = d.styleSheets && d.styleSheets[0]; /* 最初のスタイルシートを取得 */

var rulesCSS = { /* JSON形式で要素毎のスタイルを指定 */
  'table' : 'border: solid 1px gray; margin: 2em auto; border-collapse: collapse;',
  'table td' : 'border: solid 1px gray; padding: 2px; text-align: right;',
  'table th' : 'border: solid 1px gray; padding: 2px; text-align: center;',
  'thead tr th' : 'background: #eeffff;',
  'tbody tr th' : 'background: #ffeeee;'
};

for (var el in rulesCSS){
 if(sheet.addRule){ /* IE 向け */
  sheet.addRule(el, rulesCSS[el]);
 } else {  /* Firefox, Safari, Opera 向け */
  sheet.insertRule(el + '{' + rulesCSS[el] + '}', sheet.cssRules.length);
 }
}

 このように、予めJSON配列などに適応させたいCSSを記述している時は、こんな感じで動的に変更させることが出来るのですが、「普通のCSSの記述」つまり

<style type="text/css">
  table { border: solid 1px gray; margin: 2em auto; border-collapse: collapse; }
  table td { border: solid 1px gray; padding: 2px; text-align: right; }
  table th { border: solid 1px gray; padding: 2px; text-align: center; }
  thead tr th { background: #eeffff; }
  tbody tr th { background: #ffeeee; }
</style>

を、このまま適応させたいときもあると思います。ppBlogでは、リスト表示モードというのがあり、記事の読み込みにはAjaxを活用しています。この際に、最初の方で紹介した、記事ごとのCSS記述があると、まさにこんな感じで、テキストとしてCSSの記述も読み込みます。で、それを処理するのを忘れていました、という最初の話に戻るのですが、その辺の処理は以下のようにしてみました。lib.jsの256行目あたりからの部分ですが、IEとMozilla系で処理が違うので条件付きコンパイルを使っています。

if(!o("#css4list")){ /* ID#css4list を持つオブジェクトがなければ */
 oParts.create("style#css4list", null, o("head")); /* ID名css4listを持つSTYLE要素をHEAD要素の子要素として追加 */
 newCSS = o("#css4list").$; /* HTMLStyleElementオブジェクトの取得 */
 newCSS.type = "text/css";
} else newCSS = o("#css4list").$;
 /*@cc_on @*//*@if(1) newCSS.styeSheet.cssText = css[1]; @else@*/
 newCSS.textContent = ""; /* 前に適応させたCSSをクリア */
 newCSS.appendChild(d.createTextNode(css[1])); /* 新たにCSSを設定 */
/*@end@*/

 リストモードでは記事は常にひとつしか表示されないので、ひとつ動的スタイルシート用のHTMLStyleElementを用意しておけば十分です。IEとMozilla系では、そこから先の処理が違います。IEでは、スタイルシートオブジェクトから操作。

HTMLStyleElement.styleSheet.cssText = (先に挙げたような素のCSSテキスト);

 Mozilla系では、DOMオブジェクトとして操作。

HTMLStyleElement.appendChild(document.createTextNode(先に挙げたような素のCSSテキスト));

 複数行にわたるCSSテキストを一気に適応させたいときは、こんな感じでしょうか。

ちなみに、[object HTMLStyleElement]から[object CSSStyleSheet]にアクセス?する際もIEとMozilla系では違いますね。IE8では、だいぶ歩み寄りが見られたような気もするけど。

[object HTMLStyleElement].styleSheet = [object CSSStyleSheet] /* IE */
[object HTMLStyleElement].sheet = [object CSSStyleSheet] /* Mozilla */

なので上のスクリプトの例では、IEの処理部分は以下のように書いても同じです。

document.styleSheets[document.styleSheets.length - 1].cssText = css[1];

つまり

newCSS.styleSheet == document.styleSheets[document.styleSheets.length - 1];

まぁこんなとこですかねぇ。そろそろアップデートせねば。。


— posted by martin at 11:48 am   commentComment [12]  pingTrackBack [0]

IE8βでもVMLを使えるように

category-icon

 こんばんは。Windows 7βを試用中ですが、写真をお洒落に見せるスクリプトLink (photoeffect.js)が、IE8βでエラーとなり意図した効果が得られないのを発見。調べてみると、IE8ベータがVMLをサポートしていないのが原因っぽいです。おそらく、正式版ではVMLはサポートされるとは思いますが、確証はないので(Silverlightとかあるし)、現時点での回避策を考えてみました。

 IE8には、3つのレンダリングモードがあり、(紆余曲折ありましたが)デフォルトではフル標準モードでレンダリングされます。じゃ、どんな時にQuirksモード(後方互換[1]の非標準モード)になるかというと、以下の場合です。

  1. 文書中に DOCTYPE宣言がない場合
  2. 文書型が HTML3.0以下の場合
  3. HTML4.0 TransitionalまたはFramesetのDOCTYPE宣言にURLを含まない場合

の3つです。詳しくは、SummerWind - IE8のレンダリングモードに関するまとめLink あたりを参照。

 今どきのブログシステムでは、これらに該当しない可能性が高く、IE8は標準モードで動きます。なので(少なくとも現時点では)VMLのスクリプトが動かないわけです。じゃ、どうするか? 簡単そうなのは、METAスイッチGを使うこと。とりあえずVMLはIE7モードでは問題なく作動するので、以下のようなmetaタグをwebページに追加すれば良いです。

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />

 確かに、これは簡単だし現実的な解決法ですが、こうしちゃうと、JavaScriptもIE7モードになっちゃいます。IE8では、querySelectorAllなどのSelectors APIやらDOMオブジェクトに対するgetter/setter APILink など、面白そうなAPIがサポートされるのに、IE7モードで走らせると、それらが使えません。

 出来れば、IE8モードのままでVMLも有効にしたいです。こんなときに使えるのが、インラインフレーム<iframe>。最近、ちっとも更新されていないのが気がかりな Dean EdwardsのエントリーLink が参考になります。

 つまり、IE8なページの中で、sandbox化されたIE後方互換の環境を構築し、その中でVMLを走らせるわけです。実際のデモを見たほうが早いと思うので、IE8βでアクセスしてみて下さい。IE7でも動きます。

  → http://p2b.jp/demo/vml-IE8.htmlLink

 IE8やIE7では、どう見えるかイメージショットも載せておきます。

IE8-VML
画像の回転も簡単に出来る。

 ここでは、単に画像を出力させるVMLを、以下のように関数化しています。

function vmlImg(src, w, h, r){
 /*
 @src: 画像ファイルを指定 
 @  w: 表示させる横幅
 @  h: 縦幅
 @  r: 回転角度(オプション)、-360~360
 */
 var ifr = document.createElement("iframe");
 ifr.marginWidth= "0"; ifr.marginHeight= "0";
 ifr.scrolling = "no"; ifr.frameBorder = "0";
 ifr.style.width = w + "px"; ifr.style.height = h + "px";
 
 document.body.appendChild(ifr);
 var idoc = ifr.contentWindow.document;
 idoc.write(""); idoc.close(); // これがないと document.body は null になる
 if(idoc.namespaces){
  if(!idoc.namespaces.v){
   idoc.namespaces.add("v", "urn:schemas-microsoft-com:vml", "#default#VML" );
  }
 }
 idoc.body.style.backgroundColor = document.body.currentStyle.backgroundColor;
 var img = idoc.createElement('<v:image src="'+ src +'" />');
 var imgcss = img.style;
 imgcss.width = w + "px"; imgcss.height = h + "px";
 if(r){
  imgcss.rotation = r;
  ifr.style.width = 2 * w + "px"; ifr.style.height = 2 * h + "px";
 }
 idoc.body.appendChild(img);
}

 IE8版ではVMLが正式にサポートされるのを期待して、深追いはしません。

  • [1] 言葉の問題なので、本文とは関係ないですが、前方互換なのか後方互換なのか、上位互換なのか下位互換なのか訳わかめ。視点をどこに置くかの差ですし。IE8から見て、IE7以下は過去のものだから、それらに対する互換性ということで、後方互換みたいに使いましたが、何か違うような。まぁ、言いたいことが分かれば良いのかなって。[^]

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

チェックボックスを一度に複数選択したい

category-icon

 こんばんは。イベントに関するJavaScriptの話をば。

 ppBlogでは、管理画面で記事の削除やコメントの削除が出来ますが、現状、ひとつずつしか削除できません。削除したいものが沢山あるときは大変なので(あまりそういう機会はないと思うけれど)、次回アップデート版では、チェックボックスでチェックした複数の記事などを1度に削除出来るようにする予定です。実際のスクリーンショットは以下。

shot1
開発版ですよ

 チェックボックスをチェックする操作というのは、数が少ない内は良いけど、数が多くなるとマウスで逐一クリックしていくのが億劫になります。なので、シフトキー(SHIFT)を活用できるようにします。つまり、最初の方のチェックボックスをチェックしておいて、シフトキーを押した状態で、下の方のチェックボックスをクリックすると、その間のチェックボックスも自動的にチェックされた状態になるというものです。この手のインターフェイスはよく使われていて、例えば、Gメールでも、そういう操作が可能です。以下に実際のデモを載せておきます(下のデモはスクリーンショットじゃなくて、ちゃんと操作できますよ)。

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
選択: すべて  なし

 さて、この手のことを実装するには、シフトキー(SHIFT)が押された状態というのをJavaScriptで検知する必要があります。最初は、

var shift, e = oParts.evt, k = e.keyCode ? e.keyCode : e.which;
shift = e.shiftKey ? e.shiftKey : (k == 16 ? true : false);
if(shift){ // シフトキーが押されていれば

なんてのを思いつきましたが、キーボードイベントは、以前の知識しかないなぁ、と思って、新しい情報をwebで探してみました。したら、えらく新しいのがありました(今年七夕の記事です:))。

 JavaScript Madness: Keyboard EventsLink

 もはや、event.whichなんてのは不要で、event.keyCodeでこと足りるんですね。しかも、どのブラウザでも、event.shiftKeyを認識してくれそうです。なので、最終的には、この部分は、

if(oParts.evt.shiftKey){ // シフトキーが押されていれば

と非常に短く記述することが出来ますね。ppBlog向けの、上に上げたデモのソースコードを載せておきます。

o(document).on("click", function(){ // Event delegation
  var tar = oParts.evt.target; 
  if(tar.nodeName.toLowerCase() == "input" && tar.type.toLowerCase() == "checkbox"){
   var tr = tar.parentNode.parentNode;
   if(tar.checked == false){
    tr.style.backgroundColor = (tr.rowIndex % 2 == 0) ? "#eff1f3" : "#fff";
   } else tr.style.backgroundColor = "highlight";
   if(oParts.evt.shiftKey){
    var i = tr.rowIndex - 1, item, cb = o("td > input[type=checkbox]");
    for(i; i > 0; i--){
     item = cb.item(i).$;
     if(item.checked == true) break;
     item.checked = true;     
     item.parentNode.parentNode.style.backgroundColor = "highlight";
    }
   }
  }
 });

function resetAll(){ // すべてのチェックボックスを解除する
 o("td > input[type=checkbox]").each(function(e, i){
  if(e.checked){
   e.checked = false;
   e.parentNode.parentNode.style.backgroundColor = (i % 2 == 0) ? "#eff1f3" : "#fff";
  }
 });
}

function setAll(){ // すべて選択する
 o("td > input[type=checkbox]").each(function(e){
  e.checked = true;
  e.parentNode.parentNode.style.backgroundColor = "highlight";
 });
}

 ppBlogでは、多くのマウスイベント処理がありますが、ほとんどはEvent DelegationGを 活用しています。上のデモでも、チェックボックス1つひとつにイベントを登録していくのではなくて、クリックされた要素がチェックボックスであれば・・・、という捉え方をしています。イベントは、バブリング(伝達)していくものなので、最上位要素でモニターして、そのターゲットさえ掴めれば良いというわけです。

 Event Delegation versus Event HandlingLink  

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

Google Adsenseの表示タイミングを制御する

category-icon

 日本を出る前後、昨年の9月頃から、このページでグーグルのAdsenseを利用しています。使いこなしていないせいか、広告収入が100$に達するのに、あと数年はかかりそうなペースです。さて、この広告表示は、JavaScriptのdocument.writeを使ってインラインフレーム(IFRAME)で書き出すということをしています。前回のエントリーで使用しているBLOCKQUOTEのボーダー枠を演出するJavaScriptの実行タイミングが遅いなと感じていたんですが、原因は、このdocument.writeの存在でした。ちょうど2年前に、これに関するエントリーLinkdocument.write()の実行タイミングをずらす方法)を書いていたのを思い出し、早速、このサイトにも適用させてみました。

 効果はてきめんで、先にBLOCKQUOTEの装飾が終わり、その後、Google Adsenseが表示されるようになりました。ppBlogでは、DOMの構築が終わるタイミングで実行されるJavaScriptが多いので(これはドキュメントの読み込み完了前に実行される)、document.write()に起因する描画遅延対策は大切ですね。

 具体的に行った対処法は、シンプルです。テーマのtemplate.phpを弄ります。このサイトのベーシックなテーマでは、左側のカラム(サイドバー)にGoogle Adsense用のDIVタグ(IDはgoogle-ads)を用意してます。

<div id="google-ads">
 /* この中にグーグルからの広告用javascriptを貼り付けている */
</div><!--#google_ads-->

で、この中身を以下のようにします。既存のグーグルのコードをいじることはないです。

<div id="google-ads">
 <script type="text/javascript">
  (function (){
   var alts = [];
   d._write = d.write;    /* オリジナルのdocument.writeをコピーしておく */
   d.write = function(s){ alts.push(s);};  /* 新たにdocument.writeを定義。ここでは、単に配列に入れるだけ */
   oParts.start(function(){  /* DOM構築完了のタイミングで実行されるように登録する */
    o("#google-ads").html(alts.join("")); /* 配列に入れた本来のdocument.writeの中身を書き出す */
    d.write = d._write;   /* オリジナルのdocument.writeに戻す */
   });
  })();
 </script>
   /* 以下、既存のコード */
 <script type="text/javascript"><!--
   google_ad_client = "pub-XXXXXXXXXXXX";
   google_ad_slot = "XXXXXXXX";
   google_ad_width = 180;       /* 広告の幅 */
   google_ad_height = 150;     /* 広告の高さ */
 //--></script>
 <script type="text/javascript"
  src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
 </div><!--#google_ads-->

 基本的には、これでOKです。後は、予め広告のサイズが分かっているので、見栄えとしてCSSファイルを少しいじるだけです。このサイトでは以下のように指定しています。

#google-ads { 
  margin: 70px auto 0 10px;
  padding: 0;
  width: 180px;    /* 広告の幅に合わせる */
  height: 150px;   /* 上と同様に */
}

 ちなみに、このサイトのwebRingLink は、「ページ作成機能」を使って作っていますが、表示スクリプトはBlogPeopleさんLink のを利用しています。ここでもdocument.writeが使われているので、それに対処した記述をしています。その中身をさらしておきます。

<div class="center" style="color:#2f4f4f;">
<h2>ppBlog's webRing</h2>
<p style="padding:1em;font-size:14px;width:300px;margin:auto;">
ppBlog使いの方々です。随時募集中。
<br />多分に見落としなどもあると思いますので,このサイトもお願いしますというのがあれば
気兼ねにメール下さい。自薦他薦問いません。</p>
<div id="_webRing" style="margin: 2em auto;width:300px;text-align:left;"></div>
  /* ここから */
<script type="text/javascript">
 (function (){
   var alts = [];
   d._write = d.write;
   d.write = function(s){ alts.push(s);}
   oParts.start(function(){
    o("#_webRing").html(alts.join(""));  /* 上で用意した DIV#_webRing に流し込む */
    d.write = d._write;
   });
  })();
</script>
  /* ここまでがポイント */
<script type="text/javascript" charset="utf-8"
 src="http://www.blogpeople.net/display/usr/0f0d40535b5b4103.js"></script>
</div>

 

— posted by martin at 11:08 pm   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.0080 sec.
prev
2017.10
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