JavaScriptでpageのinclude

デモ画面を作ってるときに思った。
サーバーなしで、ローカルディスクからHTMLを表示するときにもTilesやPage Includeのような機能がほしいなーって。


そこでちょっとJavaScriptの勉強をしてみた。Ajaxとかはやってるし。っと思って。


勉強してて思ったけど、XMLHttpRequest#open()でローカルファイル指定してもやっぱり読み込めない。あたりまえといえばあたりまえだけど。

どうしようかなーって迷ったけど、適当にframeに分けて、読み込み対象となるようにすることにした。


ディレクトリ構成はこんな感じ

    page\layout\
        controlFrame.html <- frameに分けたりする。処理の中心
        layout.html       <- レイアウトを定義
        header.html       <- include対象のHTML。ヘッダ情報を定義
        footer.html       <- include対象のHTML。フッタ情報を定義
        one.html          <- メインのHTML。1つ目
        two.html          <- メインのHTML。2つ目
    page\js\
        includeDefaultConf.js <- ちょっとした設定情報
        includeFrame.js       <- frame内容を作るときに利用
        includePage.js        <- HTMLのincludeを行う
        includeJump.js        <- controlFrameに飛ばす


まずはそれぞれのHTMLから
controlFrame.htmlはわかりづらい。各frameタグのname属性は大切。

<html>
<head>
<title id="include_title"></title>
</head>
<script type="text/javascript" src="../js/includeDefaultConf.js"></script>
<script type="text/javascript" src="../js/includeFrame.js"></script>
<script type="text/javascript">
<!--
var control = new PageController();
control.addIncludePage("header.html", "headerPage");
control.addIncludePage("footer.html", "footerPage");
control.addIncludePage("header.html", "headerPage");
control.setLayoutPageURL("layout.html");
control.setMainPageName("mainPage");
control.write();

// 上と同じことをしてる。どっちがわかりやすいか。
//document.open();
//document.writeln('<frameset rows="0,0,0,*" frameborder="0">');
//document.writeln('  <frame src="header.html" name="headerPage">');
//document.writeln('  <frame src="footer.html" name="footerPage">');
//document.writeln('  <frame src="' + getMainPageURL() + '" name="mainPage">');
//document.writeln('  <frame src="layout.html" name="' + LAYOUT_PAGE_NAME + '">');
//document.writeln('</frameset>');
//document.close();
// -->
</script>
</html>


layout.htmlは普通。divタグのid属性がポイント。

<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=Shift-JIS">
  <title>Layout Page</title>
  <script type="text/javascript" src="../js/includeDefaultConf.js"></script>
  <script type="text/javascript" src="../js/includePage.js"></script>
</head>
<body onload="javascript:doExecute();">
<table border="1">
  <tr><td>
    <div id="include_headerPage#header"></div>
  </td></tr>
  <tr><td>
    <div id="include_mainPage#contents"></div>
  </td></tr>
  <tr><td>
    <div id="include_footerPage#footer"></div>
  </td></tr>
</body>
</html>

header.htmlとfooter.htmlはそのまま。

<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=Shift-JIS">
</head>

<body>
<div id="header">
    これは、デモ画面(紙芝居)作成時にでもInclude等の機能により画面の共通化を促進するためのJavaScriptです。<br>
    ローカルで動くことを期待しています。<br>
</div>
</body>
</html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=Shift-JIS">
</head>

<body>
<div id="footer">
    Copyright (C) 2005 とか表示する。
</div>
</body>
</html>


one.htmlとtwo.htmlは中心となる画面。それぞれこんな感じ

<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=Shift-JIS">
  <title>One Page</title>
  <script type="text/javascript" src="../js/includeDefaultConf.js"></script>
  <script type="text/javascript" src="../js/includeJump.js"></script>
</head>

<body>
<div id="contents">
     ここはオリジナルの部分。<br>
     <a href="two.html">2つ目へ</a>
</div>
</body>
</html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=Shift-JIS">
  <title>Two Page</title>
  <script type="text/javascript" src="../js/includeDefaultConf.js"></script>
  <script type="text/javascript" src="../js/includeJump.js"></script>
</head>

<body>
<div id="contents">
     2つ目のページ。<br>
     <a href="one.html">1つ目へ</a>
</div>
</body>
</html>

HTMLはこんな感じ。


次はJavaScript
includeDefaultConf.jsは

var CONTROL_PAGE_PATH = "controlFrame.html";
var CONTROL_SIGN      = "MARK";
var MAIN_PAGE_NAME    = "mainPageName";
var LAYOUT_PAGE_NAME  = "layoutPageName";

で、includeFrame.jsは、本当はこんなに必要ないと思うけど作ってしまった。

function PageController() {

    this.includePages = new Array();
    this.mainPage = new FrameTag(getMainPageURL(), MAIN_PAGE_NAME);
    this.layoutPage = null;

    this.addIncludePage = function(url, name) {
        this.includePages.push(new FrameTag(url, name));
    }

    this.setMainPageName = function(name) {
        this.mainPage = new FrameTag(getMainPageURL(), name);
    }

    this.setLayoutPageURL = function(url) {
        this.layoutPage = new FrameTag(url, LAYOUT_PAGE_NAME);
    }

    this.write = function() {
        document.open();
        document.writeln('<frameset rows="' + this.getRowsStr() + '" frameborder="0">');
        for (var i = 0; i < this.includePages.length; i++) {
            this.includePages[i].write();
        }
        this.mainPage.write();
        if (this.layoutPage != null) {
            this.layoutPage.write();
        }
        document.writeln('</frameset>');
        document.close();
    }

    this.getRowsStr = function() {
        var result = "";
        for (var i = 0; i < this.includePages.length; i++) {
            result += ", 0";
        }
        if (this.layoutPage != null) {
            result += ", 0";
        }
        result += ", *";
        return result.substring(1);
    }
}

function FrameTag(url, name) {

    this.url = url;
    this.name = name;

    this.write = function() {
        document.writeln('  <frame src="' + this.url + '" name="' + this.name + '">');
    }
}

function getMainPageURL() {
    return window.location.hash.substring(1) + window.location.search + "#" + CONTROL_SIGN;
}

includePage.jsは中心となる大切なjs

function doExecute() {
    // Include先のページをきちんと読み込むまで待つ方法がわからなかったので、
    // 適当にSleepすることにした。
    sleep();

    var divElements = document.getElementsByTagName("DIV");
    for (var i = 0; i < divElements.length; i++) {
        if (isIncludeTarget(divElements[i])) {
            var includeTag = new IncludeTag(divElements[i]);
            includeTag.display();
        }
    }
}

function sleep() {
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 1000; j++) {
        }
    }
}

function isIncludeTarget(element) {
    var targetId = element.id;
    var result = targetId.match(/^include_/);
    return result != null;
}

function IncludeTag(element) {
    this.targetId = element.id;

    this.display = function() {
        document.getElementById(this.targetId).innerHTML =
                window.top[this.getName()].document.getElementById(this.getId()).innerHTML;
    }

    this.getName = function() {
        var str = this.targetId.match(/^include_/);
        var result = this.targetId.replace(str, "");
        str = result.match(/#.*$/);
        return result.replace(str, "");
    }

    this.getId = function() {
        var str = this.targetId.match(/^include_.*#/);
        return this.targetId.replace(str, "");
    }
}

includeJump.jsはframe分割するための画面に飛ばす役目

if (!isExistControlPage()) {
    window.location.href = CONTROL_PAGE_PATH + window.location.search + "#" + getContentsURL();
} else {
    if (!isPageFromControlPage()) {
        window.open(getContentsURL() + window.location.search, "_top");
    }
}

function isExistControlPage() {
    return isExistMainPage() || isExistLayoutPage();
}

function isExistMainPage() {
    return window.top[MAIN_PAGE_NAME] != undefined;
}

function isExistLayoutPage() {
    return window.top[LAYOUT_PAGE_NAME] != undefined;
}

function isPageFromControlPage() {
    var mark = window.location.hash;
    return mark == ("#" + CONTROL_SIGN);
}

function getContentsURL() {
    return window.location.protocol + "//" + window.location.host + window.location.pathname;
}


これで、one.htmlかtwo.htmlをダブルクリックすればlayoutを適用した画面が表示される。



結局、Ajaxの勉強にはならなかった・・・。けど、目的はとりあえず達成!!
frame以外の方法でも実現できればいいけど、histry.back()に依存したデモ画面でなければ問題ないから、気にしないっと。