01 | 2017/03 |  03

  1. 無料サーバー

User forum-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut

CSSやJavascript自習の苦闘史を綴っていきたい。恐縮ですがJavascriptを有効にしてご覧ください。
2005年12月から社会問題も掲載!

jquery.js を利用した Ajax 通信の一例(改訂版コードについて)

コード改訂に至る経緯

約 2 年程前に上記関連リストに掲載したエントリイを投稿しました。

そこではjquery.js の Ajax コードを活用する一例として、今閲覧しているエントリイの直前 10 エントリイ、直後 10 エントリイ及び最新の 10 エントリイの、それぞれのタイトルを Ajax 通信で取得してみました。

その後ずっとそれを使い続けてきたのですが、あることからコードを見直す必要に迫られ、今回改めてコードを全面的に見直し、要所をいくつか改訂しました。

改訂を思い立ったのは、直前/最新/直後エントリイタイトルの表示位置が、ブラウザによっては、どうも思ったように描画されていないことに最近気がついたからです。(セットしてから 2 年も経っているのに今更かい?!(^^;; )

IE と Firefox では意図したとおりになっていたのですが、safari や chrome ではずれていたのです。

そしてその原因は、自作コードにおける offsetParent に対する曖昧な対処方法であろう、と推測できました。

しかし、これだけのことでは 2 年前のコードを改訂する動機として弱すぎました。2 年も経つと自作コードであっても可読性はかなり低下するので、思い出す時間と、当時の到達点まで改めて登る努力が必要となります。

重い腰を上げたのは、先月に解読した jQuery(~).offset メソッドや jQuery(~).position メソッドを、今後様々に活用したいと思ったからです。このブログ内で、これらのメソッドや Animation に関するメソッドを活用して、様々な実験を行いたいと思っているのですが、そのためには HTML 要素の位置が正確に把握できなければなりませんし、CSS の決まり事をしっかり押さえなければなりません。

こうして 「offsetParent の扱いを曖昧にしたままでは適切な実験が行えない」 と判断し、曖昧なコードを改善しなければならなくなったのです。

▲ToTop

今回の作業(コード改訂作業を含む)の苦労談(戒めのためのメモ)

1. offsetParent 問題

今年 1 月迄はこのブログの container 要素 には position 指定を行ってきませんでしたが、今回の一連の改訂作業を通じて container 要素には position:relative を指定しました。(※ このブログの container要素は body 要素直下にあり、ブログタイトルや時計、カレンダーそしてエントリイ等を包含する要素 )

その container の中に Javascript コードによって、10項目ずつの以前 / 以後 / 最新エントリイタイトルを表示する 3 つの要素( 以後「popup要素」と呼ぶ )を絶対配置するのですが、container の親要素及び祖先要素に position 指定をしていない状態では、W3C 勧告に従えば、popup 要素の offsetParent は body となります。

しかし、IE( ver 8 の標準モード以外の IE )では、祖先要素に明示的に width や height 値が指定されている要素があると、それが offsetParent となってしまいます。そしてこれまで position 指定をしていなかった container には width が指定されています。この結果 IE 7 以前で閲覧すると、popup 要素が container 内に意図したとおりに表示されていたのです。

他方、safari や chrome では、popup 要素の表示位置が意図した位置から「ずれて」いました。W3C 勧告に従っているこれらのブラウザでは、container 要素内に置かれた popup 要素の offsetParent が body となっていたために、思惑と異なってずれていたのです。(但し、同じように W3C に従っているはずの Firefox では、IE 同様に container 内に収まっていました。その理由を未解明です。)

さて、このようなブラウザによる「ぶれ」を放置したままでは、要素を自在に配置することは出来ません。当然画面内で要素を任意の位置に移動させるアニメーションも思うように操れません。

しかし、offsetParent や offsetTop、offsetLeftプロパティは、ブラウザによって解釈が異なる場合があるだけではなく、そもそも W3C 標準ですらありませんHTML要素の位置取得 - Rails で行こう!)。そのためそのまま使うだけでは、クロスブラウザ対応にはなりません。そこで利用したいと考えているメソッドが、jquery.js の offset と position インスタンスメソッドです。

ところで、W3C 勧告には至っていませんが、offsetParent などについては 「 CSSOM View Module 」という草案が存在しています。そして、jquery.js はそれに準拠するよう配慮されています。

そこで、Ajax コードを改定する前に、まず container 要素に position:relative を指定し、当該要素内に配置される要素の offsetParent が container 要素になるようにしました。これにより CSSOM View Module( W3C Working Draft 04 August 2009) に従って、要素を配置する前提条件が整うことになります。

こうしておいて、jquery.js の offset と position インスタンスメソッドを活用して、 CSSOM View Module に従って、どのブラウザでも同じ位置に要素を配置することを目指します。但し、この問題はエントリイを改めて述べます。

▲ToTop

2. Ajax 通信が終わったことを Javascript インタープリタに知らせる方法

案の定、Ajax 通信コードの改訂作業には時間が掛りました。

久しく Ajax コードを弄っていなかったため、コード内での Ajax 通信独特の「時間差」への配慮も、最初は全く忘れていました。また jqquery.js における Ajax 通信処理の理解が半可通であることも思い知らされました。そこで、自戒を込めて再学習した jquery.js の Ajax 通信コードの要点を整理しておくこととします。

2 年前に完成を見て、つい数日前まで使ってきた従前バージョンの get3modeEntryTitles.js では、setInterval タイマー関数と jQuery.active プロパティ( このプロパティは Ajax 通信の終了を記録する flag の役割を果たしている )を組み合わせて、Ajax 通信の終了を Javascript インタープリタに認識させていました。

しかし、今回改訂した最新の get3modeEntryTitles.js では、ajaxStop イベントハンドラーを使って同じ処理をさせることにしました。この方がコードがすっきりするし、そもそのそのような用途として開発された Ajax イベントハンドラー関数を利用しない手はないからです。

3. 先行する Ajax 通信による取得値を利用する後発 Ajax 通信の起動タイミングの設定

後発 Ajax 通信が先発 Ajax 通信結果を利用する場合、後発 Ajax 通信の開始タイミングがコードの正否を分けます。

勿論、単に後発通信コードを先発通信コードの後に記述するだけでは、何の解決にもなりません。先発通信結果を利用するのですから、それが終わらない限り後発通信を開始してはいけないのに、単にコード記述上前後の順番通りに書くだけでは後発通信は成功しません。

コード進行速度は Ajax 通信速度を遙かに上回るので、先行通信が終わらないうちに後発通信が開始されてしまい、後発通信は必要な情報を先発通信から取得できないからです。

そこで改訂版では、setInterval 関数を起動して、後発通信で使用する先発通信結果の変数が存在するまでループさせ、その存在を確認できてから後発通信を開始するようにしました。

改訂前は jQuery.active 値がゼロになるまでタイマーを繰り返し起動していたのですが、必要な変数の存在確認の方が直接的・具体的であり、可読性も高まります。

▲ToTop

改訂した get3modeEntryTitles.js コード

HTML 文や CSS 文は、jQuery Ajax を活用したエントリイタイトルの各種取得法──集大成! に掲載したものを大部分踏襲しました。但し、CSS 文において visibility 属性の使用はやめ、display属性で統一することにしました。今回 faseIn/Out メソッドによって popup 要素の表示/隠蔽を行うように変えたのですが、これは jquery.js の 各種 animation メソッドは、表示/隠蔽の切り替えに display 属性を利用しているためです。

■改訂した get3modeEntryTitles.js コード
/*
 * fileName : get3modeEntryTitles.js
 * released : 2008/03/01
 * verup : 2008/07/04, 2008/08/08, 2008/08/17, 2010/02/14, 2010/05/15
 */
  1:// 個別エントリイ表示モードでない場合には、何もしないでコード進行を終える。
  2:if (location.href.indexOf("entry")==-1) (function (){return;})();
  3:else{ //個別エントリイ表示の時
  4:(function($){
  5:  var now=function(){return +new Date;};  // 現在時刻取得
  6:  $.tr={ //所要時間計測・待機起動回数記録用 jQuery 拡張オブジェクト
  7:    start:now(),
  8:    registerEvent:"",
  9:    ajax:{ recent:{},before:{},after:{} },
 10:    end:"",
 11:    setEndingCnt:0,waitRecentAjaxCnt:0
 12:  };
 13:  var loading="<div id='loading' class='ta_c'><img src='http://blog-imgs-31.fc2.com/h/k/o/hkom/loading_16.gif' alt='' border='0' width='16' height='16' /> Now Loading...</div>";
 14:  $(function(){
 15:    $("#container").append(
 16:      "<div id='popup_before' class='popup'>" +loading+"</div>",
 17:      "<div id='popup_recent' class='popup'>"  +loading+"</div>",
 18:      "<div id='popup_after' class='popup'>"  +loading+"</div>"
 19:    );
 20:
 21:    // イベント発生要素に沿って popup 要素を配置する(表示へ別メソッドで行う))
 22:    var $popup = $(".popup"),$navi=$(".navi_container"),left,top;
 23:    // 各 popup の縦位置(ナビゲブロックの高さだけ下に)指定
 24:    top = $navi.position().top + (parseInt($navi.css("marginTop")) || 0)+ $navi.outerHeight();
 25:    $popup.width(600);
 26:    var space = $("#container").innerWidth()-$popup.outerWidth();
 27:    // 各 popup の横位置指定
 28:    $.each( ["before","recent","after"] ,function(i,item){
 29:        left= !i ? 0 : i===1 ? space/2: space;
 30:        $('#popup_'+item).css({left:left+"px", top:top+"px"});
 31:    });
 32:
 33:    // 前・最新・後リストの表示/隠蔽イベントハンドラー
 34:    $.each( ["before","recent","after"] ,function(i,item){
 35:      $("#"+ item + "EntryTitle" ).hover(
 36:        function(){  $('#popup_'+item).fadeIn(); },
 37:        function(e){
 38:          var itsIdName = e ? e.relatedTarget.id : window.event.toElement.id;
 39:          if (itsIdName.indexOf("popup") < 0)
 40:            $popup.fadeOut();
 41:        }
 42:      );
 43:    });
 44:
 45:    // 表示された popup の hover イベントハンドラー
 46:    $popup.hover(
 47:      function(e){if (!$(this).is(":hidden")) return;},
 48:      function(e){ $(this).fadeOut(); }
 49:    );
 50:  });  //End of $(function(){})
 51:
 52:  // variable
 53:  var html = {recent:[],before:[],after:[]}, getStr={recent:0,before:0,after:0},
 54:    lastNo, regExpr,thisEntryNo, complement,itval,border={before:0, after:0},realElm = {before:0,after:0},flagStopHandler = false;
 55:
 56:  // 最近のタイトルを取得する関数を定義
 57:  var makeRecentEntryList = function (limit){
 58:    var No, subject, date, iter=0, ret=[], $chd;
 59:    var target ={    //数字は xml ファイル内で登場する順番
 60:          link:[],    //0
 61:          title:[],    //1
 62:      //  description:[],  //2
 63:        //  content:[],    //3
 64:          subject:[],    //4
 65:          date:[]      //5
 66:      };
 67:    $.ajax({
 68:      url: /http:.+fc2\.com\//.exec(location.href)[0] + "?xml" || null,
 69:      type: "GET",
 70:      dataType: "xml",
 71:      global:false,
 72:      success: function(xml){
 73:        var tmpStr = '<div>'+decodeURI(encodeURI("最新のエントリイ情報がありません。"))+'</div>';
 74:        if (xml==undefined || xml==null) {getStr.recent = tmpStr; return;}
 75:        $.tr.ajax.recent["start"]=now();
 76:        $.each(target,function(key){
 77:          $.each($("item",xml), function(i,n){
 78:            $chd = $(n).children();
 79:            ret[i]= [$chd.eq(0), $chd.eq(1), $chd.eq(4),$chd.eq(5)];
 80:            target[key].push( ret[i][iter].text() );
 81:          });
 82:          iter++;
 83:        });
 84:        for (var i=0; i < limit; i++) {
 85:          No = /entry-([0-9]+)/.exec(target.link[i])[1];
 86:          i==0 && (lastNo = Number(No));
 87:          subject =" , " +target.subject[i];
 88:          date =" , " +target.date[i].substring(0,10);
 89:          html.recent.push( "<li><a href='" + target.link[i] + "' target='_blank'>" +  target.title[i] + "</a> (No." + No + subject + date + ")</li>");
 90:        }
 91:        $.tr.ajax.recent["end"]=now();
 92:        getStr.recent =  "<div class='ta_c'><em>Recent " + limit + " Entries</em></div><ul class='ml_1_5'>" + html.recent.join('') + "</ul>";
 93:      }  //End of success()
 94:    });  //End of ajax()
 95:  };  //End of makeRecentEntryTitle func
 96:
 97:  // 前後のタイトルを取得するための準備を行う
 98:  var makeEntryList = function(beforeafter,limit){
 99:    var thisHTTP, getEntryNos=[], thisURL=[];
100:    regExpr = /(http:.+entry-)([0-9]+)/;
101:    thisEntryNo = Number(regExpr.exec(location.href)[2]);
102:    thisHTTP = regExpr.exec(location.href)[1];
103:    border[beforeafter] = Math.min(limit+1, beforeafter == "before" ? thisEntryNo :(Number(lastNo)-thisEntryNo+1) );
104:    if (border[beforeafter]==1) thisURL.length=0;
105:    else {
106:      for (var i=1; i < border[beforeafter]; i++)
107:        thisURL.push(thisHTTP + (thisEntryNo - (beforeafter == "before" ? i : -i)) +".html" );
108:    }
109:    // 準備完了! Ajax 通信開始
110:    getTitlesByAjax.call(this, beforeafter,thisURL);
111:  };
112:
113:  // Ajax 通信により前後のエントリイタイトル等を取得する関数の定義
114:  function getTitlesByAjax(tense,thisURL){
115:    if ( thisURL.length != 0 ) {
116:    $.each(thisURL,function(i,aryitem){
117:      $.tr.ajax[tense][i]={};
118:      $.tr.ajax[tense][i]["start"]=now();
119:      $.get(aryitem,function(data){  //data は thisURL[i] の html テキスト文
120:        regExpr = /<title>(.*)<\/title>/;
121:        var titleStr = regExpr.exec(data)[1].substring(19);
122:        if ( /\S+/.test(titleStr)){  //空白だけのタイトル名は補足しない。
123:          ++realElm[tense];
124:          html[tense][i] = "<li><a href='" + aryitem + "' target='_blank'>" + titleStr +" (EntryNo." + /entry-([0-9]+)/.exec(aryitem)[1] + ")</a></li>";
125:        }
126:        $.tr.ajax[tense][i]["end"]=now();
127:      },"text");
128:    });
129:    }
130:  };
131:
132:  function ajaxStopHandler(){
133:    flagStopHandler = true;
134:    $.each(["before","after"],function(i,tense){
135:      if (realElm[tense]==0)
136:        getStr[tense]="<div class='ta_c'>"+ (tense=='before' ? "Before " : "After ") + "Entry "+ decodeURI(encodeURI('はありません。')) + "</div>";
137:      else {
138:        complement = (border[tense]-1 -realElm[tense]!==0) ? 
139:          " ( " +decodeURI(encodeURI('欠番があります。')) +" )" : "";
140:        getStr[tense] = "<div class='ta_c'><strong>"+ (tense==='before' ? "Before " : "After ") + realElm[tense] + " Entries" + complement +"</strong></div><ul style='margin-left:1.5em;list-style-type:disc'>" + html[tense].join('') + "</ul>";
141:      }
142:    });
143:    var beforeTime= realElm.before!==0 ? "<li>以前タイトル取得 Ajax 通信所要時間: "+ ($.tr.ajax.before[realElm.before-1].end-$.tr.ajax.before[0].start)/1000 +" 秒</li>" : "";
144:    var afterTime= realElm.after!==0 ? "<li>以後タイトル取得 Ajax 通信所要時間: "+ ($.tr.ajax.after[realElm.after-1].end-$.tr.ajax.after[0].start)/1000 +" 秒</li>" : "";
145:    var AjaxLog ="<ul style='margin:0.5em;border-top:white dotted 1px;padding:0.5em 0.5em 0 1em;list-style-type:circle'>"+
146:      "<li>クリック後 Ajax 通信開始迄の所要時間: "+($.tr.ajax.recent.start-$.tr.start)/1000 +" 秒</li>"+
147:      "<li>最新タイトル取得 Ajax 通信所要時間: "+ ($.tr.ajax.recent.end-$.tr.ajax.recent.start)/1000 +" 秒</li>"+ beforeTime + afterTime +
148:      "<li>このプロジェクト全体の所要時間: "+ (($.tr.end=now())-$.tr.start)/1000 +" 秒</li></ul>"+
149:      "<div style='text-align:right;margin:-2em 1em 0.5em 0'><button style='display:block;margin:-1em 0 0 auto;width:16em' title='このボタンをクリックするとここで行った Ajax 通信に関するこのブログのエントリイが開きます。' onclick='this.blur();window.open(\"http://hkom.blog1.fc2.com/blog-entry-626.html\",target=\"_blank\")'>この Ajax 通信や所要時間について</button></div>";
150:    $(function(){
151:      $("#popup_before").html(getStr.before + AjaxLog);
152:      $("#popup_after").html(getStr.after + AjaxLog);
153:      $("#popup_recent").html(getStr.recent + AjaxLog);
154:    });
155:  }
156:
157:  $(document).ajaxStop(function(){
158:    ajaxStopHandler();
159:  });
160:  // 前後及び最新のタイトルリスト作成
161:  makeRecentEntryList(10);  //最近リスト取得実行
162:  makeEntryList("before",10);  // 以前エントリイ
163:  function nextAjaxTimer(){
164:    $.tr.waitRecentAjaxCnt++;
165:    // makeRecentEntryList により lastNo 値を取得するまでは起動しない。
166:    if (!!lastNo) {
167:      if (ival) {clearInterval(ival);ival=null;}
168:      makeEntryList("after",10); // 以後エントリイ
169:    }
170:  };
171:  var ival = setInterval(nextAjaxTimer,100);
172:})(jQuery);
173:}

 

● コメント ●

承認待ちコメント ()

このコメントは管理者の承認待ちです

■ コメントの投稿 ■

管理者にだけ表示を許可する

●トラックバック●

■トラックバックURLはこちら■
http://hkom.blog1.fc2.com/tb.php/762-1e05bb1b

●参照元一覧●

<provided Fc2>
<provided i2i>

▲ToTop

 90%近いシェアを握っているインターネットエクスプローラの描画エンジンを利用したタブbrowser。沢山のタブbrowserがあるが、多機能、カスタマイズフリー、スクリプト利用等で一日の長がある。Gekkoエンジンへの対応も行われ、IEからの自立独立の方向に向かっている。2005年7月にはIE7が登場する見通しの中で、今後の発展が望まれる。

 多様なCSS作成支援機能を備えた、タグ入力式 HTML&CSS作成支援エディタ。スキンデザインもすっきりしている。テキストエディター上で作成するよりも確実で安全にタグ打ちが出来る。
文字コードを選べないのが欠点。

 StyleNote同様のタグ入力式 HTML&CSS 作成支援エディタ。長年使用してきたが現在StyleNoteに乗り換えつつある。

 クリップボード履歴情報を活用する為のソフト。画像まで履歴を取ってくれるのが嬉しい。このソフトを使わない日は絶対ない程に重宝し、愛用している。

 起動中のウィンドウの「コピーできない」説明文などの文字列を取得し、コピー可能な文字データにするツール。何かと便利。

 ストリーミングデータを保存することが出来るソフト。動画利用には不可欠なソフトだ。

 無料ながらレイヤー機能を有し、スクリプトによる拡張も可能な、sleipnir作者が提供している優れもの画像編集ソフト。

 画面キャプチャソフトと言えばこれに勝るものなし、ではないだろうか? 様々な取得方法を有しており、ブログ作成にもHomepage作成に不可欠だ。Jtrimと並んでWoodyBellsの作品。

 複数ファイルの同時編集は出来ないが、透過pngも作れる画像編集ソフト。
(以下当該サイトから抜粋)初心者にも簡単に操作が出来るフォトレタッチソフトです。多くの加工機能で画像に様々な効果を与えることができます。非常に軽快に動作するため、ストレスなく操作できます。

 Animation Gifファイルを作れる無料ソフト。

 キャプチャソフト。画面内にサイト全体が表示しきれない場合でも、これを使えば全体をキャプチャすることが出来る。

 画像処理。画像のフォーマット変換のみならず、色数やサイズ、圧縮率の変更まで一括処理できてしまう『BatchGOO!』は、大量の画像をまとめて処理したいときに大変便利なソフト。BMP, TIFF, JPEG, PCX, PNG の相互変換をはじめ、色数・サイズ・解像度の統一、JPEG圧縮率の調節など、ホームページ用の画像や携帯電話用の壁紙を揃えるのに抜群の相性を見せる。(Vectorの当該ソフト紹介頁より抜粋引用)

 名前から直ぐに想像が付くように画像のサイズを測るためのソフトだ。Homepage作成には欠かせない。2カラム、3カラムのレイアウトを行う場合に大変重宝する。

 ランチャーソフトは沢山あるが、中でもこれが一押しだ。2年以上使ってきたがその操作性には毎日満足している。これを使い始めてからデスクトップには一切のアイコンを表示することをやめてしまった。

 AdobeReader7によって、起動時間が長すぎるという長年のユーザーの不満はある程度解消した。そのためこの高速化ソフトは存在価値が低下してしまったかもしれない。AdobeReader6迄はこのソフトによる起動高速化で恩恵を受けてきた。

 IE専用が難点だが、様々なサイト内でIDやパスワードを入力するのに重宝するソフト。コンテキストメニューから簡単に起動できるのがGood! sleipnir等のIEの描画エンジンを利用しているブラウザでも使える。

 利用しているパソコンの諸元値を取得するには、このソフトがベストだ。インストール済みソフトの一覧が取得できるのも嬉しい。

 WMPは機能が豊富なだけ重い。RealPlayerも同様だ。そこでMedia Player Classicを使いたい。動作が軽快なだけではなく、対応しているファイル形式もすこぶる多く、これひとつで、wmvもrmも表示できてしまうのだから凄い! 数多あるMedia Playerの王様と言えるだろう。

 自宅でPCを起動しているときには必ず起動しているメディアプレーヤー。何かと過剰なWinampよりも、起動も速くスキンはシンプルだ。

 DivX, Xvid, Mov, Vob, Mpeg, Mpeg4, avi, wmv, dv, などの動画をDVD-Video形式に変換できるフリーソフト。クリックするとDVD関連ソフト紹介サイト=「DVDなToolたち」なるHomepageが開きます。

201002150132