PowerCMSのテンプレート実装をしての雑感

今年前半に担当したCMSのテンプレート実装がどうも上手くいかなかったことような印象を持っていて、今週から始めたPowerCMS 5のテンプレート実装では少し考える時間を増やして実装を進めています。先日もつぶやいたのですが、上手くいかなかったように感じる原因は自分の至らなさに起因するものと、自分だけではどうにもならないものがあるように感じています。自分の至らなさに起因するものは経験を重ね、ひたすら腕を磨いたりプロセスを改善したりするのみですね。

誰がJavaScriptの実装をするのか問題

さて、PowerCMSのテンプレート実装を担当する時遭遇することの1つに「このボタンのJavaScriptが実装されていないけど僕が実装するの?」ということがあるでしょうか。ボタンの他にカレンダーなどもありましたね。確かにJavaScriptを実装するには期待する動作の理解に加えCMSの仕様もきちんと理解している必要があって、特に他社さんからコーディングデータをもらうケースでコーダーさんにJavaScriptを実装してもらうのは難しい可能性があるかもしれません。とはいえ、テンプレート実装の段階で「あれ?」「えー」とならないように事前に誰が実装するのかを明確にしなければならないと改めて感じました。

記事リストを10件ごと分割して表示する

今担当している案件では最初に記事リストを10件表示しておいて、「もっと見る」をクリックする毎に10件ずつ記事リストを表示する機能が求められました。CMSサーバーとフロントサーバーは分離しています。記事リストは日付・カテゴリ・タイトルのみでdisplay: none;にしても画像が読みこまれるといったことはなく、記事数もそれほど多くなさそうなので、追加で読みこむ記事をわざわざJSONに持たせてもあまり意味はないか…と考えました。結果できあがったコードは次のコードです。

class ReadMoreEntries {
    private elem: HTMLElement | null;
    private list: Array<HTMLLIElement>;
    private button: HTMLButtonElement | null;
    private clickEventListener: EventListener;
    private nEntries: number;
    private defaults: {[key: string]: any} = {
        numOfLoad: 10,
        enabledClassName: '-readmore-enabled',
        hideClassName: '-hide',
        listItemQuery: 'ul > li',
        buttonQuery: '.btn-block button',
    };
    private options: {[key: string]: any};

    constructor(id: string, settings: {[key: string]: any} = {}) {
        this.options = Object.assign(this.defaults, settings);
        this.elem = document.getElementById(id);
        this.list = Array.from(this.elem?.querySelectorAll(this.options.listItemQuery) ?? []);
        this.button = this.elem?.querySelector(this.options.buttonQuery);
        this.nEntries = this.list.length;
        this.clickEventListener = () => this.showEntries();
    }

    removeButton() {
        this.button?.removeEventListener('click', this.clickEventListener);
        this.button?.parentNode?.removeChild(this.button);
    }

    private showEntries() {
        let counter: number = 0;
        let firstItem: HTMLLIElement | undefined;
        while(counter < this.options.numOfLoad) {
            if (this.nEntries > 0) {
                const elem: HTMLLIElement | undefined = this.list.shift();
                elem?.classList.remove(this.options.hideClassName);
                this.nEntries -= 1;
                if (counter === 0) {
                    firstItem = elem;
                }
            } else {
                this.removeButton();
                break;
            }
            counter += 1;
        }
        const focusableElem: HTMLAnchorElement | undefined =
            firstItem?.getElementsByTagName('a')[0];
        focusableElem?.focus();
    }

    init() {
        if (this.list.length === 0) {
            return;
        }
        this.elem?.classList.add(this.options.enabledClassName)
        this.list = this.list.slice(this.options.numOfLoad);
        this.nEntries = this.nEntries - this.options.numOfLoad;
        this.list.forEach(elem => {
            elem.classList.add(this.options.hideClassName);
        });
        this.button?.addEventListener('click', this.clickEventListener);
    }
}

const buttons: NodeListOf<HTMLButtonElement> = document.querySelectorAll('.page-infomation .btn-block__btn');
buttons.forEach((elem: HTMLButtonElement) => {
    const id: string | null = elem.getAttribute('data-target');
    if (id) {
        const readMoreEntries: ReadMoreEntries = new ReadMoreEntries(id);
        readMoreEntries.init();
    }
});

今回はTypeScriptで書きました。タブで記事リストが分かれていたので汎用的に使えるように書いています。ループを回した上で$('li').eq(n).show();みたいにしているコードも見かけるのですが、記事リストを配列で持っておきArray.prototype.shift()を使えばクラスを付けるのも簡単ですし、ボタンを押した際に表示した記事リストの先頭にフォーカスを移すことも簡単でした。

他社さんからコーディングデータをもらっておりjQueryも読みこまれていましたが、これぐらいであればjQueryを使わずともさらさら書けます。(TypeScriptのnoImplicitAnyオプションとstrictNullChecksオプションは後から勉強しましたが…。)$('selector')駆動でその案件でしか使えないコードはあまり書きたくないなと。

記事リストをJavaScript+JSONで表示する実装

Movable Type Advent Calendar 2020に寄稿されている「ディレクターが知るべきMovable Type 7の設計のポイント(大規模サイト編)|3rdFocus」を拝見し、「記事のリスト表示等は基本は非同期読み込みに」というのは大変参考になりました。サイトホームに記事リストを表示していても、Copy2Publicでステージング環境・本番環境に同期する際にお知らせブログだけ同期すれば良いことになりますし、サイトホームの記事リスト部分以外を改変している途中でもお知らせを更新することが容易になるかな、と考えました。

ワイヤーフレームなどに書かれていないことに気を配る

本来はワイヤーフレームなどにしっかり書かれているべきなのでは…という気もしますが、それにこだわり過ぎると紛争が勃発しそうですね。

  • オブジェクトが全くない時
  • 必須ではないフィールドに入力がない時
  • 文章が長い時短い時

上記のようなシーンも想定してテンプレートを書けると良いです。なおかつでき上がったときにそれが確認できる状態にしておくと確認もしてもらえます。後から「ワイヤーフレームにないのに何でこう出るの?」と言われても困りますからね。

srcset崩壊

レスポンシブWebデザインを採用とされつつもPCデザイン・SPデザインというのが上がってきて800px前後でパチッと切り替わるのは正直嫌いなのですが、まぁあるあるですね。この時に画面幅が大きい場合と画面幅が中ぐらいの時に表示する画像の幅が大きく違うケースがあって、srcsetにサムネイルの表示をするタグを書くのに苦戦しました。むしろ崩壊したかも。いやな思い出の1つです。ビジュアルデザインをする時にどう考えられたのかは聞いてみたい気がします。

結び

考えたままを書き綴ったので取り留めの無い文章になってしまいました。来年も精進します。