🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

ページ遷移なしでコンテンツを更新したいとき

どう実装するのがベターなんやろうと思って。
まず最初に思いついたのが、jQuery Mobileです。

というわけで、どういう実装になってるか調べたのでそのメモ。

コード

参考:code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.js

↑のリンクのコードの、4292行あたり。

$.ajax({
  url: fileUrl,
  type: settings.type,
  data: settings.data,
  contentType: settings.contentType,
  dataType: "html",
  success: function( html, textStatus, xhr ) {
    //pre-parse html to check for a data-url,
    //use it as the new fileUrl, base path, etc
    var all = $( "<div></div>" ),

      //page title regexp
      newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,

      // TODO handle dialogs again
      pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
      dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" );


    // data-url must be provided for the base tag so resource requests can be directed to the
    // correct url. loading into a temprorary element makes these requests immediately
    if ( pageElemRegex.test( html ) &&
        RegExp.$1 &&
        dataUrlRegex.test( RegExp.$1 ) &&
        RegExp.$1 ) {
      url = fileUrl = path.getFilePath( $( "<div>" + RegExp.$1 + "</div>" ).text() );
    }
    //dont update the base tag if we are prefetching
    if ( base && ( typeof options === "undefined" || typeof options.prefetch === "undefined" )) {
      base.set( fileUrl );
    }

    //workaround to allow scripts to execute when included in page divs
    all.get( 0 ).innerHTML = html;
    page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();

    //if page elem couldn't be found, create one and insert the body element's contents
    if ( !page.length ) {
      page = $( "<div data-" + $.mobile.ns + "role='page'>" + ( html.split( /<\/?body[^>]*>/gmi )[1] || "" ) + "</div>" );
    }

    if ( newPageTitle && !page.jqmData( "title" ) ) {
      if ( ~newPageTitle.indexOf( "&" ) ) {
        newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text();
      }
      page.jqmData( "title", newPageTitle );
    }

    //rewrite src and href attrs to use a base url
    if ( !$.support.dynamicBaseTag ) {
      var newPath = path.get( fileUrl );
      page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() {
        var thisAttr = $( this ).is( '[href]' ) ? 'href' :
            $( this ).is( '[src]' ) ? 'src' : 'action',
          thisUrl = $( this ).attr( thisAttr );

        // XXX_jblas: We need to fix this so that it removes the document
        //            base URL, and then prepends with the new page URL.
        //if full path exists and is same, chop it - helps IE out
        thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );

        if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
          $( this ).attr( thisAttr, newPath + thisUrl );
        }
      });
    }

    //append to page and enhance
    // TODO taging a page with external to make sure that embedded pages aren't removed
    //      by the various page handling code is bad. Having page handling code in many
    //      places is bad. Solutions post 1.0
    page
      .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
      .attr( "data-" + $.mobile.ns + "external-page", true )
      .appendTo( settings.pageContainer );

    // wait for page creation to leverage options defined on widget
    page.one( 'pagecreate', $.mobile._bindPageRemove );

    enhancePage( page, settings.role );

    // Enhancing the page may result in new dialogs/sub pages being inserted
    // into the DOM. If the original absUrl refers to a sub-page, that is the
    // real page we are interested in.
    if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
      page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" );
    }

    // Remove loading message.
    if ( settings.showLoadMsg ) {
      hideMsg();
    }

    // Add the page reference and xhr to our triggerData.
    triggerData.xhr = xhr;
    triggerData.textStatus = textStatus;
    triggerData.page = page;

    // Let listeners know the page loaded successfully.
    settings.pageContainer.trigger( "pageload", triggerData );

    deferred.resolve( absUrl, options, page, dupCachedPage );
  },

要約

  • ajaxでhtmlを丸ごと取得
  • 空のdivを作る
  • titleやらdata属性やら、新しいページに必要な情報を正規表現で抜く
  • srcやらhrefやらの体裁を整える
  • さっきのdivのinnerHTMLに新しいコンテンツをつっこむ(ここ重要)
  • つっこむことでfind()とかできるようになるので必要なものゴニョゴニョ
  • ローディング完了
  • 諸処のトリガーの発火やらdeferredの処理やら

結構強引なことやってるんかと思ったけど、これが正攻法なんかねー。
js使えなくても単純にリンクにもなるし。

ふーむ、なるほど。