背景
稼働中のアプリケーションを変更するにあたって、職人的なDOM生成コードを読み取ることが困難なことがあります。
例えば、次のように職人的にHTML Document Objectを作り
function createElement(id, data) {
const element = document.createElement('div')
element.setAttribute('id', id)
element.setAttribute('title', 'id: ' + data.id + ', pred: ' + data.pred + ', value: ' + data.value)
element.setAttribute('origin-id', data.id)
element.setAttribute('type', data.value)
element.setAttribute('pred', data.pred)
element.classList.add('textae-editor__attribute')
element.innerText = data.value
const popUpEditorElement = document.createElement('div')
popUpEditorElement.classList.add('textae-editor__attribute-buttons')
const editButtonElement = document.createElement('div')
editButtonElement.classList.add('textae-editor__attribute-button')
editButtonElement.classList.add('textae-editor__attribute-button--edit')
editButtonElement.setAttribute('title', 'Edit this attribute.')
const deleteButtonElement = document.createElement('div')
deleteButtonElement.classList.add('textae-editor__attribute-button')
deleteButtonElement.classList.add('textae-editor__attribute-button--delete')
deleteButtonElement.setAttribute('title', 'Delete this attribute.')
popUpEditorElement.appendChild(editButtonElement)
popUpEditorElement.appendChild(deleteButtonElement)
element.appendChild(popUpEditorElement)
}
jQuery.append()を使ってDOMを追加するソースコードがあるとします。
$(parent).append(createElement(id, data))
課題
このソースコードからレンダリングされるHTMLを直感的に読み取るのは難しいです。
DOMの構造を修正したいときに変更箇所の特定が困難です。
よくやる対応では、ソースコードを少し変えてレンダリングされる内容の変化を確認しながら、変更箇所を特定していきます。
対応できますが、変更頻度が高い時は手間が馬鹿になりません。
リファクタリング手法
テンプレートエンジンを導入し、ソースコード上にレンダリング後のHTMLのイメージをそのまま記述します。
ここではHandlebars.jsを使います。
手順1. Google Chromeの開発コンソールを使ってレンダリング後のHTMLを取得します。
Google Chromeで対象となるDOMを選択肢、コンテキストメニューから Copy > Copy outerHTML を選択します。
次のHTMLが取得できます。
<div id="editor1__AA1" title="id: A1, pred: example_predicate_1, value: attr1" origin-id="A1" type="attr1" pred="example_predicate_1" class="textae-editor__attribute">attr1
<div class="textae-editor__attribute-buttons">
<div class="textae-editor__attribute-button textae-editor__attribute-button--edit" title="Edit this attribute."></div>
<div class="textae-editor__attribute-button textae-editor__attribute-button--delete" title="Delete this attribute."></div>
</div>
</div>
手順2. テンプレートで置き換える場所を設定する
Handlebars.jsでは{{
と}}
で括られた文字列を指定の値で置き換えます。
JavaScriptではテンプレート文字列をつかうと、改行を含んだ文字列をソースコード中に記述できます。
次のようなソースコードになります。
const source = `
<div id="{{id}}" title="{{title}}" origin-id="{{originId}}" type="{{value}}" pred="{{pred}}" class="textae-editor__attribute">{{value}}
<div class="textae-editor__attribute-buttons">
<div class="textae-editor__attribute-button textae-editor__attribute-button--edit" title="Edit this attribute."></div>
<div class="textae-editor__attribute-button textae-editor__attribute-button--delete" title="Delete this attribute."></div>
</div>
</div>
`
手順3. HTMLを生成する
これらを組み合わせてHTMLを生成するコードを作成します。
例えば次のようなソースコードになります。
import Handlebars from 'handlebars'
const source = `
<div id="{{id}}" title="{{title}}" origin-id="{{originId}}" type="{{value}}" pred="{{pred}}" class="textae-editor__attribute">{{value}}
<div class="textae-editor__attribute-buttons">
<div class="textae-editor__attribute-button textae-editor__attribute-button--edit" title="Edit this attribute."></div>
<div class="textae-editor__attribute-button textae-editor__attribute-button--delete" title="Delete this attribute."></div>
</div>
</div>
`
const template = Handlebars.compile(source)
function createHtml(id, attribute) {
return template({
id,
title: `id: ${attribute.id}, pred: ${attribute.pred}, value: ${attribute.value}`,
originId: attribute.id,
value: attribute.value,
pred: attribute.pred
})
}
この関数はcreateElement
関数と、同等の内容を生成します。
違いは生成するものがDOM ElementかHTML文字列かの違いです。
手順4. createElementをcreateHtmlに置き換える
jQuery.appendは引数にHTML文字列を受け取れます。
呼び出す関数を置き換えるだけで対応が完了します。
$(parent).append(createHtml(id, data))
おまけ
jQueryを使わずにNode.appendChild()を使っている場合、例えば、次のソースコードの場合
parent.appendChild(createElement(id, data))
Node.appendChild()は引数にDOM要素しか受け付けません。
DOMの任意の場所にHTML文字列を挿入するにはelement.insertAdjacentHTMLを使います。
parent.insertAdjacentHTML('beforeend', createHtml(id, data))