javaScriptでBloggerにページネーションを実装する

Bloggerにページネーション機能をつけたい。ページネーションとは記事下部にあって、次の記事、前の記事へのリンクを紹介するというブログパーツ。Bloggerのデフォルトのページネーション機能には、以下の画像のように、次の記事・前の記事のリンクのみで記事のタイトルを表示することはできない。

ページネーションで記事タイトルを取得するようにしたい。当記事では、ざっくりログさまの情報120%全面参考にさせていただいています。ざっくりログさま本当にありがとうございます。Display Post Titles in Blogger Page Navigation



<b:> Bloggerのネイティブ文法では、記事タイトルを取得できない

olderPageUrl: 過去の投稿がある場合、その投稿の URL。ページの種類に応じて表示されます(このリンクはすべてのページにあるとは限りません)。
olderPageTitle: 過去の投稿ページへのリンクのタイトル。
newerPageUrl: 新しい投稿がある場合、その投稿の URL。
newerPageTitle: 新しい投稿ページへのリンクのタイトル。
参考元URL:レイアウト用のデータタグ - Blogger ヘルプ

公式ヘルプを見ると、Bloggerの文法には、
  • olderPageTitle
  • newerPageTitle
といったちゃんとこのようなそれっぽい文法がある



よし!と思ってテーマのHTMLに記載するとこの通り

次の記事、前の記事として表示してくれない。なんだよもう。olderPageTitle = 前の記事、newerPageTitle = 次の記事 ということ。


ちょっとまぎらわしい。記事URLは取得できているので、タイトルを取得するJSコードを書く必要があります。




アクセスカウントされないページネーションコード

ざっくりログさまが上記リンク先の記事で問題があると言っているようにJSでタイトルを取得すると、Bloggerのアクセスカウンターに計上され、次・前のタイトルを取得する度に1PVとしてカウントされてしまいます。

アクセス数を水増ししたいならJSのコードでもいいけどね、そんな不正確なPVデータなんて意味ないしね。アクセスにカウントされないようにするために、ざっくりログさまが書いているようなBloggerAPIを利用したjsonファイルを利用するのがとっても良い方法だと思われます。ざっくりログさま本当にありがとうございます。
<script type='text/javascript'>
//<![CDATA[
// except root, labels, search and mobile pages
if (/.+\.html(\?m=0)?$/.test(location.href)) {
  var olderLink = document.getElementById('Blog1_blog-pager-older-link');
  if (olderLink) {
    getPageTitle(olderLink, setOlderPageTitle);
    function setOlderPageTitle(data){
      setPageTitle(data, olderLink, '', ' &#187;')
    };
  }
  var newerLink = document.getElementById('Blog1_blog-pager-newer-link');
  if (newerLink) {
    getPageTitle(newerLink, setNewerPageTitle);
    function setNewerPageTitle(data){
      setPageTitle(data, newerLink, '&#171; ', '')
    };
  }
  // set the page title from feed data
  function setPageTitle(data, pageLink, prefix, suffix) {
    if (data.feed.entry) {
      if (data.feed.entry.length > 0) {
        var title = data.feed.entry[0].title.$t;
      }
    }
    if (title) {
      pageLink.innerHTML = prefix + title + suffix;
    }
  }
  // get entry data from the feed
  function getPageTitle(pageLink, callback) {
      var pathname = pageLink.getAttribute('href').replace(location.protocol + '//' + location.hostname, '');
      var script = document.createElement('script');
      script.src = '/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path='+pathname+'&callback='+callback.name+'';
      document.body.appendChild(script);
  }
}
//]]>
</script>


コード(自分なり)解説

人様のコードをまるまる転載しておいて解説もクソもないんだけど、自分なりに注釈を入れてみた。
 <script type='text/javascript'>
if (/.+\.html(\?m=0)?$/.test(location.href)) {
  // ※1location.hrefの末尾にhtmlかhtml?m=0か調べる。末尾がhtmlかチェックする。

  var olderLink = document.getElementById('Blog1_blog-pager-older-link');
  if (olderLink) {  // olderLinkが存在するなら。ショートハンド記法
    getPageTitle(olderLink, setOlderPageTitle);
    //※2関数getPageTitleへ第一引数olderLink, 第2引数へ定義した関数setOlderPageTitleを渡す。

    function (data){
      // getPageTitle() で、feed.jsonを渡される。dataへ格納。
      setPageTitle(data, olderLink, '', ' &#187;') // ^#187; = »
    };
  }

  var newerLink = document.getElementById('Blog1_blog-pager-newer-link');
  if (newerLink) {
    // newerLinkが存在するなら
    getPageTitle(newerLink, setNewerPageTitle);
    //関数getPageTitleへ、第一引数newerLink, 第2引数へ定義した関数setNewerPageTitleを渡す。

    function setNewerPageTitle(data){
      // getPageTitle() で、feed.jsonを渡される。dataへ格納。
      setPageTitle(data, newerLink, '&#171; ', '')
    };
  }
  // set the page title from feed data // dataはjsonファイル。$tは添字の名前。
  function setPageTitle(data, pageLink, prefix, suffix) {
    if (data.feed.entry) {
      if (data.feed.entry.length > 0) {
        var title = data.feed.entry[0].title.$t;
      // jsonファイルの項目、feedの添字entry配列[0]のtitle.$tを取得して、変数titleへ格納
      }
    }
    if (title) {
      // 変数titleが存在するなら、
      pageLink.innerHTML = prefix + title + suffix;
      // suffixは "»" 。 pageLink.innerHTML = ''記事タイトル » となる。
    }
  }
  // get entry data from the feed
  function getPageTitle(pageLink, callback) {
      var pathname = pageLink.getAttribute('href').replace(location.protocol + '//' + location.hostname, '');
      // location.protocol = https: location.hostname = あなたのブログ.blogspot.com
      var script = document.createElement('script');
      script.src = '/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path='+pathname+'&callback='+callback.name+'';
      // ※3 記事pathnameで、その記事のjsonファイルを取得することができる。最後の&callback=あとの関数にjsonデータが渡されることになる。
      document.body.appendChild(script);
  }
}
//]]>
</script>

※1 Bloggerは、URLに注目すると現在表示している記事の種類を掴める
URLの末尾が.html: 個別記事、pageTypeはItem
URLに label を含む: ラベル一覧ページ pageTypeはIndex
URLに search を含む: 検索結果表示ページ pageTypeはIndex
URLに 2で始まる4桁の数字を含む(ex: 2020): アーカイブページ pageTypeはarchive


※2
 getPageTitle(olderLink, setOlderPageTitle)の、
  • 第一引数:olderLinkはdomオブジェクト(#Blog1_blog-pager-older-link)
  • 第二引数: 関数setOlderPageTitle
これわかりづらかったのが第二引数だけど、関数をsetPageTitleへ渡している。渡した先のgetPageTitleでは、GoogleBloggerAPIから取得したfeed.jsonデータを受け取るコルバック関数として指定される処理になっている。つまり、

「setOlderPageTitleにjsonを渡してね!だから、jsonを受けてほしい関数を渡すからさ!」
という感じ


※3
  • script.src = '/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path='+pathname+'&callback='+callback.name+'';
で、なんで<script>のsrc属性を記述しているかというと、BloggerFeedAPIを取得するためには<script>をhtmlに作成せよと書いてあるから。
Blogger Data APIが提供するJSON出力形式を使用して、Bloggerベータでホストされているブログからの最近の投稿のリストを表示する方法を示しています。これにより、ブログのパブリックフィードをクエリして、結果のエントリをJSONオブジェクトとして返すことができます。新しいJSONフィードを使用するには、src値が次のスクリプト要素を作成します 
https://blogname.blogspot.com/feeds/posts/default?alt=json-in-script&callback=myFunc
どこblognameあなたが取得したいブログで、 myFuncJSONオブジェクトが渡されるコールバック関数の名前です。<Google翻訳>
参考URL: Simple example of retrieving JSON feeds from Blogger Data API
この引用先の情報ってBloggerAPIのものではなくて、GoogleデータAPIのものなんだよね。なんで、BloggerAPIのセクションにない情報なのだろうか(こんらん)。

  • '/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path='+pathname+'&callback='+callback.name+'';
は、以下と同義。
  • https://www.themetrial.blogspot.com/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path=/2020/05/blogger.html =

つまり、
https://www.themetrial.blogspot.com + /feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path=/2020/05/blogger.html 
 ということ。最後のcallback関数へjsonデータを渡す。ここでjsonファイルを受け取るデータを、引数で渡された関数へ渡している。
https://www.themetrial.blogspot.com/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path=/2020/05/blogger.html&callback=callback.name (=関数setOlderPageTitle) ,
callback関数= setOlderPageTitleや setNewerPageTitleとなる。だからjsonを受け取れる。



受け取っているJsonの内容はどうなっているの?

末尾の&callback=callback.nameを消去して、BloggerのURLの部分(以下太字)を調節以下のように調節し、URLを打ち込むとJsonファイルを受け取ることができる。
https://blogname.blogspot.com/feeds/posts/default?alt=json-in-script&max-results=1&redirect=false&path=特定記事のパス.html 

私の場合、一つ前の記事の場合だと以下のようにするとjsonデータをブラウザに表示することができる
https://www.themetrial.blogspot.com/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path=/2020/05/blogger.html

当該部分はこんな感じのデータになる。
"feed": {
       "entry": [
               "title": {
                      "type": "text",
         "$t": "Blogger文法<b:loop>でラベルを呼び出す処理はこれを読めばわかる"
        },
         ]
}

 jsonデータを受け取り、当該箇所の値を取得して、関数に渡せているのがわかりました。

Blogger文法<b:loop>でラベルを呼び出す処理はこれを読めばわかる

Bloggerのラベルを<b:loop>で呼び出す処理ってどんなだろう。なんでloop文なのだろうか。以下では、メインのウィジェットである、投稿ウィジェットとラベルウィジェットのloopの処理の違いをまとめてみました。大きな違いは、なんのデータリストを参照しているか?という点です。


main投稿ウィジェットにおけるloop

    <b:loop values='data:post.labels' var='label'>
      <a expr:href='data:label.url' rel='tag'><data:label.name/></a><b:if cond='data:label.isLast != &quot;true&quot;'>,</b:if>
    </b:loop>
post.label = 記事それぞれのラベル、てかラベルリスト。例えば、この記事のラベルリストはBloggerHowToOS9_BloggerTheme作成記だから、<b:loop>で呼び出そうとしているデータリストの中身はこの2つのはず。もう一度コード。


    <b:loop values='data:post.labels' var='label'>
      <a expr:href='data:label.url' rel='tag'><data:label.name/></a><b:if cond='data:label.isLast != &quot;true&quot;'>,</b:if>
    </b:loop>
以下の2つの作業をおこなうことになる。
  1. label = ' BloggerHowTo'  ラベルURL、ラベル名を付与した<a>を生成
  2. label = 'OS9_BloggerTheme作成記 ' ラベルURL,ラベル名を付与<a>を生成
つまり、メイン投稿ウィジェットの<b:loop>では、記事に設定されているラベル数分びループ処理を行い、ラベルのURLとラベルのタイトルを<a>に格納して、htmlに記述する。ということをやっていることになる。data:label.isLastは、この例で言えば2のOS9_BloggerTheme作成記のときにtrueになります。isLastは、リストの最後のラベルを対象にした条件分岐をできるようにする構文です。



Labelウィジェットにおけるloop

main投稿ウィジェットでは、その記事に設定されているラベル(リスト)を<b:loop>で呼び出していましたが、対してLabelウィジェットでは、ブログ全体のラベルリストを呼び出します。つまり、全てのラベルを呼び出して、htmlへ記述する処理を行っています。
<ul>
<b:loop values='data:labels' var='label'> // data:labels = ブログ全体のラベルリスト
  <li>
    <a expr:href='data:label.url'><data:label.name/></a>
  </li>
</b:loop>
</ul>

やっていることは、ブログ全体のラベルリストを対象に、それぞれラベルのURL、タイトルを取得して<a>に格納、htmlに記述する。さっきのとの違いは、対象にしているリストが異なっていること。この、HTMLタグとBlogger独自タグが混在しているのってちょっと面白いですよね。

順番的には、1<b>Blogger文法タグ, 2HTMLタグの順番に処理されるようです。この短いコードでブログすべてのラベルを取得することができるのに驚き。こんなシンプルなのね。

Blogger ClassicMacOS9のテーマへ、続きを読むボタンを追加する短いコード

ClassicMacOS8.5~9くらいのテーマを自作している当ブログ。Topページやラベル、アーカイブページなどの一覧ページにて、個別記事のすべて本文が表示されてスクロールが大量に発生するので、つづきを読むページを設置することにしました。



続きを読む ボタンを<more>やjsを使用しないで表示する

こちらのコードを適当なところ(後述)へコピペください。
<b:if cond='data:blog.pageType == &quot;index&quot;  or data:blog.pageType == &quot;archive&quot;'>
  <span class='pageMore'>
    <!-- page more in index pages -->
    <style>
    .entry .post-body{
      height: 180px;
      overflow: hidden;
      }
    </style>
    <a expr:href='data:post.url'>続きをよむ</a>
  </span>
</b:if>     


とっても短いコードになりますがこのように機能しています。コードでやっていることは以下です。
  • トップページ、ラベルページ、アーカイブ、検索結果などのpageType = indexのページにて、
  • 記事要素の高さを180pxにして溢れたものを非表示にし、
  • <a href="記事URL">続きを読む</a>を追加

他の方が作成したBloggerThemeを色々眺めていて面白いと思ったのが、<b:if cond='data:blog.pageType == "index"'>(もし現在表示しているページがindexなら)でpageTypeを判定したあとに<style>を記述して、個別記事のみで適用させるcssを指定する方法。真似させていただきました。


CSSのセレクタを間違うと機能しないかも・・・・

<style>
    .entry .post-body{
      height: 180px;
      overflow: hidden;
 }
</style>
.post-bodyが私のテーマでは投稿本文になっているので、ちょうどよく記事を途中で端折ってくれています。もしお使いのテーマによっては本文のクラスが異なることがあるかもなので適宜読み替えください。おそらくですが、.post-bodyも.post-footerも、どのテーマにも存在すると思われます。




テンプレートのどこにコピペすればいいのか??

 上記コードはキチンと動作するのですがコピペする位置が難しい。私は<div class='post-footer'> 〜 </div>に上記コードを設置してみました。


「<a>続きを読む</a>タグが、どこにあれば適切か?」
を考えて.post-footerに。ブチャラティ厳しすぎ。htmlエディタを開き、エディタ内にフォーカスがある状態でctrl + f 。検索小窓にclass='post-footer'と打ち込めば当該箇所までジャンプ。御コピペ納くださいませ。さいごまでおよみいただきありがとうございました。

スプライト画像にしてしまおう