2012/01/22

hashchangeイベントをjQueryUIのtabより発生させて、DOMを非同期に切り替える

ファイル構成

  • js
    • south-street (jQueryUIのテーマファイル群)
    • index.js
    • exvalidation.js (exValidation プラグイン http://5509.me/log/exvalidation)
    • jquery.ba-hashchange.min.js (http://benalman.com/projects/jquery-hashchange-plugin/)
  • css
  • exValidation.css
  • first.html
  • first.js
  • fourth.html
  • index.html
  • second.html
  • third.html

機能

index.html上にdiv.contentsFrame要素を定義し、jQuery UIのタブ押下に応じて
内部のdiv.contentsを動的に切り替える。

コンテンツの切り替え処理はContentsManagerクラスが担う。ContentsManagerはDOMの摩り替えを行うが、hashchange自体は意識していない。hashchangeにかかる処理はhashchangeイベント内のみで処理をする。

div.contentsの中身は、リンク先のファイルからjQuery.loadで取得する。
遷移に際してはhashchangeイベントを用いて、history.back, forwardに対応する。

ファイルの取得成功時には、リンク先ファイル上の同名jsファイルを読み込む。


考慮不十分事項

エラーハンドリングがない

jsの名前空間がどうなっているのか分からない。

exValidationのエラーチップ初期化が#formError決めうち。

headにscriptをappendすることの危険性がよく分かってない。

URIはUItabがデフォルトでつけるハッシュURIのまま。

GETのためのクエリは考慮できていない。


処理の流れ

  1. 初期ページindex.jsでの初期化処理
  2. hashchangeイベントをトラップするためにjquery.hashchangeプラグインを導入し、初期化処理をする。
  3. UI Tabが生成するハッシュリンクを無視し、html上で指定したhref属性へのリクエストを発生させる。かつUI Tabの遷移先をhash化するようUI Tabのselectイベントを設定する。
  4. 上記によりUI Tabのリンク押下により '#' + href属性のhashchangeイベントを発火させる。
  5. hashchangeイベントの発火に呼応して処理されるコールバックに対して、ハッシュ化されたuriからハッシュを削除し、元々hrefに指定されていたリクエストからdiv.contensElementの内容を非同期ロードする
  6. 上記ロード完了に伴い、既存contentsの削除と新規contentsのappendを行う。ノリでfadeout/fadeinしているがそこはどうでもいい。
  7. ロード成功時に、リクエストしたurlをhtmlと仮定し、同パス上の同ファイル名のjsを読み込む。このときのscriptタグに特定の非同期読み込みされたスクリプトである旨を示すクラス名を付与する。
    (#/hoge/first.htmlへのリクエスト発生時にhoge/index.jsを読み込むscript#contentsScriptタグをheadにappendする。)
    jQuery.getScript(jsUri);する方がハンドリングがかけたりしてまだマシ。(追記: 2012/01/23)
  8. 上記処理によりjQueryのreadyイベントが発火する。(first.jsであればinputに対するexValidationの初期化が行われる。)
  9. 前回読み込んだscriptタグを削除する。(sciprt#contentsScriptをremoveする)
  10. 以上の処理によりfirst.htmlに単独で読み込んだ場合と同等の初期化が完了したコンテンツが表示され(ているといいな)
index.html
<div id="contentNavigation">
    <ol>
      <li><a href="#">HOME</a></li>
      <li><a href="index.html">Index</a></li>
      <li><a href="first.html">First</a></li>
      <li><a href="second.html">Second</a></li>
      <li><a href="third.html">Third</a></li>
      <li><a href="fourth.html">Fourth</a></li>
    </ol>
    <div id="contentsFrame" style="height:500px;">
      <div class="contents">
 <div class="contentsElement">
   <p>this is a index.html</p>
   <p>http://fdays.blogspot.com/2011/01/jquery-ui-tabs.html</p>
   <p>now</p>
 </div>
      </div>
    </div>
  </div>

first.html
<div id="contentsFrame">
      <div class="contents">
 <div class="contentsElement">
   <form id='loginForm'>
   <p id='first'>this is a first.html</p>
   <div>
   <label for='name'>Name:</label>
   <input id='name' type='text'></input>
   </div>
   <div>
   <label for='password'>Pass:</label>
   <input id='password' type='password'></input>
   </div>
   <input id='regist' type='submit' value='Regist' class='button'></input>
   </form>
   <div id='registedName'></div>
 </div>
      </div>
    </div>

first.js
$(function(){
 // 検証処理追加
 $('Form#loginForm').exValidation({
  rules:
  {
      name:'chkrequired',
      password:'chkrequired chkmin5'
  },
  errTipCloseLabel:'x',
  errInsertPos: 'body',
  customSubmit:function(){
      var registedName = $('#name').val();
      var registedPassword = $('#password').val();
      var appendedHtml = '
' + registedName + ':' + registedPassword + '
'; $('#registedName').append(appendedHtml); } }); });

index.js
// ContentsManagerClass
var ContentsManager = function(){
    this.contentsFrameId = 'contentsFrame';
    this.contentsClassName = 'contents';
    this.contentsElementClassName = 'contentsElement';
    this.contentsScriptClassName = 'contentsScript';
};
ContentsManager.prototype.preLoadInitialize = function(){
    // exValidation初期化処理
    $('.formError').remove();
};
ContentsManager.prototype.contentsReplace = function(uri){
    var contentsManager = this;
    if(uri){
 var contentsTemplateHtml = '
'; $('#' + contentsManager.contentsFrameId).append(contentsTemplateHtml); var newContents = $('.' + contentsManager.contentsClassName + ':last-child'); newContents.load(uri + ' .' + contentsManager.contentsElementClassName ,function(){ var oldContents = $('.' + contentsManager.contentsClassName + ':first-child'); oldContents.animate({opacity:0.0},500,function(){ contentsManager.preLoadInitialize(); contentsManager.unloadScript(); oldContents.remove(); contentsManager.loadScript(uri); newContents.animate({opacity:1.0},500); }); }); } }; ContentsManager.prototype.loadScript = function(htmlUri){ var jsUri = htmlUri.replace(/html$/g, 'js'); var className = htmlUri.replace(/\//g, '_'); //var scriptHtml = ''; //$('head').append(scriptHtml); jQuery.getScript(jsUri); }; ContentsManager.prototype.unloadScript = function(){ $('.' + this.contentsScriptClassName).remove(); }; var contentsManager = new ContentsManager(); //---------------------------------------------- $(function(){ $('#contentNavigation').tabs({ select: function(event, ui) { var url = "#/" + $.data(ui.tab, "load.tabs"); if (url) { location.href = url; return false; } return true; } }); $(window).hashchange(function() { var uriWithoutHash = location.hash.replace('#', ''); contentsManager.contentsReplace(uriWithoutHash); }); $(window).hashchange(); });

0 件のコメント:

コメントを投稿