日々のコンピュータ情報の集積と整理

Dr.ウーパのコンピュータ備忘録

2014年12月10日水曜日

Blogger:「次の投稿・前の投稿」にページタイトルを付与する-Feedから情報取得-コード解説

イントロダクション

Google のブログサービス「Blogger」において、「次の投稿・前の投稿」にページタイトルを付与する JavaScript コードを作成しました。

ここでは、その JavaScript コードについて、詳細に解説します。




なお、作成した JavaScript コードについては、以下のページをご覧ください。

Blogger:「次の投稿・前の投稿」にページタイトルを付与する-Feedから情報取得
http://upa-pc.blogspot.com/2014/12/blogger-prev-next-page-title-script.html


コード解説

以下に、「次の投稿・前の投稿」にページタイトルを付与する JavaScript コードの解説を記載します。

表記として、次のような装飾を行っています。

解説対象のコード:水色のマーカーで表示
解説対象外のコード:灰色のマーカーで表示
解説:通常表示(黒文字・白背景)


説明はコードの上に記載しています。


<!-- set next prev page title start -->
<script type='text/javascript'>
//<![CDATA[
<!--


今回の JavaScript コードの変数名、関数名の名前空間が、他の JavaScript コードと衝突しないようにするため、可能な限り (function () {}) で処理を囲っています。

但し、フィードのデータ読み込みに使用する callback については、異なる <script> タグ内で実行する為、callback で呼び出される関数についてはグローバルに参照可能な位置に記載しています。

(function () {


次の投稿、前の投稿のフィードを json-in-script 形式で読み込むため、動的に JavaScript を読み込めるようにするために、以下の関数(addScript)を用意しました。

動的に JavaScript を読み込む方法として、document.write を使用して、<script> タグを生成する方法もありますが、その方法ではその処理の実行タイミングを任意の位置に変更できない(*1)ため、以下の動的に JavaScript を読み込む処理を利用しました。

*1:
document.write を含むコードを HTML 構文解析後に実行しようとすると、意図した動作にならないため。

詳細は以下の文献を参照。

読み込みのタイミングによっては外部 script のdocument.writeは無視される - はこべブログ ♨
http://hakobe932.hatenablog.com/entry/2013/03/26/215755

    // --- Util ---

    // JavaScript動的挿入
    function addScript(src) {

        var script = document.createElement('script');
        script.setAttribute("type", "text/javascript");
        script.setAttribute("src", src);

        header_setChild(script);
    }

    // <head>取得
    function getHeader() {
        return document.getElementsByTagName("head")[0];
    }

    // <head>に子要素を追加
    function header_setChild(child) {
        var head = getHeader();
        head.appendChild(child);
    }

    // --- Main ---


前の投稿、次の投稿にタイトルを付与する処理の開始位置です。

なお、ページによっては、前の投稿、次の投稿が無い場合(*2)があるので、要素が取得できなかった場合は、処理をスキップしています。

*2:
前の投稿 が無い場合:
ブログの最初の投稿

次の投稿 が無い場合:
ブログの最後の投稿


ここでは、前の投稿、次の投稿のページ URL を取得するために、それらの要素にある<a>タグの href 属性を取り出しています。

最終的に、addPageTitleFromFeed(ins_obj_id, page_url, callback)  の第一引数 ins_obj_id の id 属性を持つ要素に対して、ページタイトルが挿入されます。

    // 前の投稿の要素が存在したら、タイトル設定
    var prev_page_link_obj = document.getElementById("Blog1_blog-pager-older-link");
    if (prev_page_link_obj) {
        var prev_page_link_url = prev_page_link_obj.getAttribute("href");
        addPageTitleFromFeed("blog-pager-older-link", prev_page_link_url, "addPrevPageTitle");
    }

    // 次の投稿の要素が存在したら、タイトル設定
    var next_page_link_obj = document.getElementById("Blog1_blog-pager-newer-link");
    if (next_page_link_obj) {
        var next_page_link_url = next_page_link_obj.getAttribute("href");
        addPageTitleFromFeed("blog-pager-newer-link", next_page_link_url, "addNextPageTitle");
    }


    /*
       ページのタイトルをフィードから取得し、対象のオブジェクトへ追加する

       ins_obj_id : 対象のオブジェクトの id
       page_url : タイトルをフィードから取得するページの URL
        callback : フィードのデータを取得するためのコールバック
    */
    function addPageTitleFromFeed(ins_obj_id, page_url, callback) {

        if (!ins_obj_id) return;
        if (!page_url) return; 


Blogger では、トップページ、ブログアーカイブ、ラベル、検索などのページにおいては、前の投稿、次の投稿のリンク先ページでは、複数の投稿が表示されます。

従って、それらのページでは、ページタイトルを取得する意味がないので、それらのページが前の投稿、次の投稿のリンク先として指定されている場合には、処理をスキップするようにしています。


トップページ、ブログアーカイブ、ラベル、検索などのページの前の投稿、次の投稿なのかどうかは、リンク先にクエリ文字列(URL に?以下のデータが付与されているかどうか)が含まれているかどうかで判断しています。

そのため、URL に クエリ文字列を意味する ? が含まれていた場合には、処理をスキップしています。

       if (page_url.indexOf("?") != -1) return;        // クエリ文字列が含まれているURLは対象外


Blogger のフィードにて、特定のページのフィードのみを取得するためには、クエリ文字列に path パラメータを付与します。

その path パラメータに指定するページの URL では、最後のドメインより先の URL 文字列を指定します。

例)
ページ URL:
http://upa-pc.blogspot.com/2013/03/blog-post.html

path パラメータに指定する値:
/2013/03/blog-post.html


前の投稿、次の投稿 に設定されている URL には、ブログのドメイン部(例:http://upa-pc.blogspot.com)が含まれているため、その URL を加工して、path パラメータに指定する値のみを作成しています。

なお、ページによっては、次の投稿 にブログのトップページの URL が指定されていることがある為(ブログアーカイブの最新月などの場合)、その場合には「if (!page_url_remove_home) return;」にて、処理をスキップするようにしています。

        // Feed の path に指定する URL を作成
        var home_url = location.protocol + "//" + location.hostname + "/";      // ブログのホームの URL
        var page_url_remove_home = page_url.replace(home_url, "");              // ブログのホームの URLを除いた、タイトルをフィードから取得するページの URL
        if (!page_url_remove_home) return;              // 対象とするページのURLが無い場合は対象外
        page_url_remove_home = "/" + page_url_remove_home;


ページタイトルの挿入先を事前に作成しておきます。

あとで作成しても良いのですが、前の投稿、次の投稿 のページへのリンクを張る都合上、フィードのタイトルを取得したあとにページタイトルの挿入先を作成しようとすると、そこまでページの URL を保持していないといけないため、処理が煩雑になります。
(ページのタイトルの取得は、動的に読み込まれた JavaScript (ページのフィード取得)にて、コールバックされないと行えないため、ページタイトル取得前後で処理が分断されるため)


そのため、事前にページタイトルの挿入先を作成しておき、後でページタイトルのみ設定すればよい状態にしておきます。

ここでは、ページタイトルの取得が行われるまで、「now loading...」と表示されるようにしてあります。

        // ページタイトルの挿入先を事前に生成
        var div = document.createElement("div");
        var title_obj_id = ins_obj_id + "-title";
        div.innerHTML = "<a href=\"" + page_url + "\" id=\"" + title_obj_id + "\">" + "now loading..." + "</a>";


        var obj = document.getElementById(ins_obj_id);
        if (!obj) return;
        obj.appendChild(div);


今回の要は、次のフィードを取得する部分です。

上記で作成した、path パラメータに指定するページの URL を使用して、そのページのみのフィードを取得しています。

フィードのデータは、callback に指定した関数に渡されます。
ここでは、前の投稿の場合には addPrevPageTitle(data) 、次の投稿の場合には addNextPageTitle(data)が呼び出されます。


なお、フィードのURLの各種パラメータの意味などは、以下のページに記載してあります。

Blogger:URLを指定して特定のページのフィード(Atom/RSS)を取得する方法 - Dr.ウーパのコンピュータ備忘録
http://upa-pc.blogspot.com/2014/12/blogger-feed-from-path.html


なお、実際にどのようなデータが生成されるのかは、一例として作成した以下のリンク先を見てみるとよくわかります。

例)

生成されたFeedのデータを受け渡すためのスクリプト:
// API callback
callBackFunction({・・・省略・・・});


Feed のパラメータの callback に指定した関数に、Feed のデータを引数として渡して、呼び出していることが分かります。

        // フィードを取得し、コールバック
        addScript(home_url + "feeds/posts/summary?alt=json-in-script&callback=" + callback + "&max-results=1&path=" + page_url_remove_home + "&redirect=false");
    }

})();


ここに記載されている addPrevPageTitle(data)(前の投稿の場合)  、addNextPageTitle(data)(次の投稿の場合)が、ページのフィード情報を受け取るために呼び出されます。

上記で記載したように、フィードのデータを受け渡すために呼び出されるこれらの関数は、<head> 内の <script> で実行されるため、そこから関数を参照できるように、(function () {}) の外に記載しています。

/*
 前の投稿のページタイトルを設定する
 data : コールバックで渡される Feed のデータ
*/
function addPrevPageTitle(data) {
    addPageTitle(data, "blog-pager-older-link-title");
}

/*
 次の投稿のページタイトルを設定する
 data : コールバックで渡される Feed のデータ
*/
function addNextPageTitle(data) {
    addPageTitle(data, "blog-pager-newer-link-title");
}


 addPrevPageTitle(data)(前の投稿の場合)  、addNextPageTitle(data)(次の投稿の場合)から共通で呼び出される関数です。

フィードのデータからタイトルを取得し、第2引数 id で指定した id に対して、そのタイトルを設定します。

/*
 ページのタイトルを設定する
 data : コールバックで渡される Feed のデータ
 id : タイトルの設定先
*/
function addPageTitle(data, id) {


フィードのデータが正常に取得できていれば、フィードのエントリーがある状態でかつ、要素数が 1 であるはずです。

その条件を満たしていた場合、ページのタイトルを取得しています。


なお、ページのタイトル内にエスケープしないといけない文字が含まれていた場合に備えて、元のタイトルに対して文字のエスケープ処理を行っています。

    // ページのタイトルを取得
    var title = "";
    if (data.feed.entry) {
        if (data.feed.entry.length > 0) {
            title = escapeHTML(data.feed.entry[0].title.$t);
        }
    }


ページのタイトルを要素に対して設定しています。
ページの要素には、"now loading..." が設定されているので、それが上書きされて、ページのタイトルになります。

なお、タイトルが取得できなかった場合には、"now loading..." が空白"" になるようにしています。

    // ページのタイトルを設定
    var obj = document.getElementById(id);
    if (!obj) return;
    obj.innerHTML = title;


テキストをエスケープ処理しています。
要素の innerText(Firefox の場合には、textContent)にテキストを設定して、innerHTML を取得すると、設定したテキストがエスケープ処理される仕組みを利用しています。

    // テキストをエスケープ処理する
    function escapeHTML(html) {

        var div = document.createElement("div");
        if (div.innerText !== void 0) div.innerText = html;          // innerText が定義されていれば innerText へ設定
        else div.textContent = html;                                 // Firefox のように innerText がないブラウザ向け

        return div.innerHTML;
    }
}
//-->
//]]>
</script>
<!-- set next prev page title end -->


まとめ

以上で、今回作成した「次の投稿・前の投稿」にページタイトルを付与する JavaScript コードの解説は終了です。

コード自体はそれほど複雑なものではないので、各部分の処理内容がわかれば、全体としてどんな処理を行っているのか理解しやすいと思います。


なお、今回のコード作成において、検討を必要とした箇所を以下のページにまとめました。

Blogger:「次の投稿・前の投稿」にページタイトルを付与する-Feedから情報取得-発生した困難
http://upa-pc.blogspot.com/2014/12/blogger-prev-next-page-title-script-difficult.html





関連記事

関連記事を読み込み中...

同じラベルの記事を読み込み中...