はてなブログでページ読み込み時のDOM操作をモジュール化する

はてなブログでページ読み込み時のDOM操作をモジュール化する


はてなブログで自分の思うようにカスタマイズしようとしますとDOM操作が避けられなくなります。そのコードを毎回コピペするのではなく、モジュール化して github.io などに上げておき、オプション指定で する/しない を選択するようにできないかという話です。


クラス、コンストラクタ、インスタンス、難しいね

カスタマイズのためのDOM操作は、ページを読み込んだときに1回行えばいいわけですから即時関数で書けばいいのですが、考えてみれば、新しくテーマを作ろうとカスタマイズする度にコピペしたりと無駄なことをしているようにも感じます。

これまでいくつか書いてきた、たとえば、トップページを一覧表示にした時のサムネイル画像をオリジナルのものに変えたり、ページトップにスムーズスクロールするボタンを加えたりするコードをひとつにまとめてモジュール化出来ないかなあということです。

ただ、プログラミングの基本を学んでおらず、見よう見まねで書いていますので、なんとなく概念はわかっていても、こういう場合はどう書けばいいんだろうと、現実局面になると壁にぶつかってしまいます。

クラス、コンストラクタ、インスタンスってなんやねん? というほどではありませんが、具体的にどう書けばいいのかとなりますと難しいですね。


やりたいことのサンプル

やりたいことをよくあるサンプルコードで示しますとこういうことになります。

var Person = function(name, age) {
    this.name = name;
    this.age  = age;

    // メソッド
    this.setName = function(name) {
        this.name = name;
    }
    this.getName = function() {
        return this.name;
    }
}

var taro = new Person('太郎', 20);
console.log(taro.getName()); // 太郎


これを応用して、

// コンストラクタ関数
var imzModules = function(){
    this.chgFeturedImg = arguments[0].chgFeturedImg || false;
        ・
        ・
    // メソッド
    this.init = function(){
        var page = document.body.classList;
        // トップページならサムネール画像を元画像に入れ替える
        // 実際には一覧表示かどうかも判断が必要
        if(this.chgFeturedImg && page.contains('page-index')){
            var elements = document.getElementsByClassName('entry-thumb');
            var re = /https%3A%2F%2F.+\.(jpg|jpeg|gif|png|bmp)/i;
            Array.prototype.forEach.call(elements, function(element) {
                var imageUri = re.exec(element.getAttribute('style'));
                if(imageUri !== null) element.style.backgroundImage = 'url(' + decodeURIComponent(imageUri[0]) + ')';
            });
        }
        ・
        ・
    }
};

//  呼び出し
(function(){
    var initialSetting = new imzModules({
        chgFeturedImg: true,
        ・
        ・
    });
    document.addEventListener("DOMContentLoaded", initialSetting.init(), false);
})();

とすればモジュール化できそうに思います。

で、このコンストラクタ関数をネット上に上げておき、それぞれのブログでそれを読み込み、プロパティで何を使うかを指定してインスタンスを作成し、初期化メソッドを呼べばいいのではないかということです。

なお、イベントリスナーはHTMLドキュメントの読み込みが完了したらとしていますが、はてなブログの場合はフッタに入れますので、それ以前のドキュメントは読み込まれていると考えれば、initialSetting.init() を呼ぶだけでも問題なく動くはずです。


プロトタイプ

ということで、おおよそ構想はできたのですが、ただ、これですとイニシャライズメソッドを呼び出す度に、といってもこの場合1回だけですが、新しく関数が定義されメモリを消費してしまいますので、prototype を使いなさいということらしいです。


// コンストラクタ
var imzModules = function(){
    this.chgFeturedImg = arguments[0].chgFeturedImg || false;
        ・
        ・
};
// メソッド
imzModules.prototype.init = function(){
    var page = document.body.classList;
    // トップページならサムネール画像を元画像に入れ替える
    // 実際には一覧表示かどうかも判断が必要
    if(this.chgFeturedImg && page.contains('page-index')){
        var elements = document.getElementsByClassName('entry-thumb');
        var re = /https%3A%2F%2F.+\.(jpg|jpeg|gif|png|bmp)/i;
        Array.prototype.forEach.call(elements, function(element) {
            var imageUri = re.exec(element.getAttribute('style'));
            if(imageUri !== null) element.style.backgroundImage = 'url(' + decodeURIComponent(imageUri[0]) + ')';
        });
    }
        ・
        ・
};

こうすれば、imzModules.prototype.init は新しく定義されるのではなく参照で実行されることになるようです。


参考サイト

JavaScript 手軽にモジュール化する方法メモ - Qiita

JavaScript (ES5) でクラスを実現するための基本 - Qiita

オブジェクトモデルの詳細 | MDN


さらに改良

で、メソッドにプロトタイプを使っていますので、なんとなく関連性が見えにくいかなと思いいろいろググっていましたら、こちらのサイトがヒットしました。

【javascript】やさしいクラスの作り方 - Qiita


こういう書き方もできるということで、

var imzModules = (function(){
    var imzModules = function(){
        (略)
    };

    imzModules.prototype.init = function(){
        (略)
    };
    return imzModules;
})();

これでいこうと思います。

で、今モジュール化しようと思っているDOM操作は、サムネール画像をオリジナルに変えるもの、カスタマイズ > ヘッダ > タイトル画像に貼った画像を付け替えるもの、トップへスムーズスクロールするボタン、カテゴリーの自動メニュー化、トップページに前に戻るボタンの追加などです。

完成しましたら、また記事にしようと思います。


ES6(ES2015)

ES6(ES2015)ではクラス構文が実装されているのですが、IE11 は対応していないようです。


ECMAScript 6 compatibility table