ビューヘルパー - Express.js

  • 作成日:
  • 最終更新日:2025/10/11

ビューヘルパーを作成する

次のように、アプリケーションの中に、「 helpers/viewhelper.js 」というディレクトリとファイルを作成します。

  • example
    • ...
    • helpers
      • viewhelper.js
    • ...
    • app.js

ファイルの内容

helpers/viewhelper.js

module.exports = {
  hello: () => {
  return "hello, World";
  }
}

「 var app = express(); 」より後に、以下を追加します。

app.js

app.locals.viewhelper = require('./helpers/viewhelper');

ビューヘルパーの呼び出し

テンプレート上で次のように記述すると、ビューヘルパーが呼び出せます。

<%= viewhelper.hello() %>

自作クラス

以下は、フォームタグの自作のビューヘルパーのクラスです。

helpers/FormHelpers.js
class FormHelpers {
  _buildAttributes(attributes) {
return Object.entries(attributes)
  .filter(([_, value]) => value !== false && value != null)
  .map(([key, value]) => {
    if (value === true) return key;
    return `${key}="${this._escapeHtml(value)}"`; // ← ここでもエスケープすべき
  })
  .join(' ');
  }

  _normalizeClass(attr = '') {
return attr.split(' ').filter(Boolean).join(' ');
  }

  _escapeHtml(str) {
return String(str ?? '').replace(/[&<>"']/g, s => ({
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#39;',
}[s]));
  }

  textInput(name, attributes = {}) {
  const {
    value = '',
    class: classAttr = '',
    ...restAttributes
  } = attributes;

  restAttributes.class = this._normalizeClass(classAttr);

  return `<input type="text" name="${this._escapeHtml(name)}" value="${this._escapeHtml(value)}" ${this._buildAttributes(restAttributes)}>`;
  }

  passwordInput(name, attributes = {}) {
  const {
    value = '',
    class: classAttr = '',
    ...restAttributes
  } = attributes;

  restAttributes.class = this._normalizeClass(classAttr);

  return `<input type="password" name="${this._escapeHtml(name)}" value="${this._escapeHtml(value)}" ${this._buildAttributes(attributes)}>`;
  }

  textArea(name, attributes = {}) {
const {
  value = '',
  class: classAttr = '',
  ...restAttributes
} = attributes;
  
restAttributes.class = this._normalizeClass(classAttr);
  
return `<textarea name="${this._escapeHtml(name)}" ${this._buildAttributes(restAttributes)}>${this._escapeHtml(value)}</textarea>`;
  }

  hiddenInput(name, attributes = {}) {
const {
  value = '',
  ...restAttributes
} = attributes;
return `<input type="hidden" name="${this._escapeHtml(name)}" value="${this._escapeHtml(value)}" ${this._buildAttributes(restAttributes)}>`;
  }

  checkBox(name, attributes = {}) {
  const {
      value = '',
      class: classAttr = '',
      checked = false,
      ...restAttributes
  } = attributes;

  restAttributes.class = this._normalizeClass(classAttr);
  const checkedAttr = checked === true ? 'checked' : '';

  return `<input type="checkbox" name="${this._escapeHtml(name)}" value="${this._escapeHtml(value)}" ${this._buildAttributes(restAttributes)} ${checkedAttr}>`;
  }

   radioButton(name, attributes = {}) {
  const {
      value = '',
      class: classAttr = '',
      checked = false,
      ...restAttributes
  } = attributes;

  restAttributes.class = this._normalizeClass(classAttr);
  const checkedAttr = checked === true ? 'checked' : '';

  return `<input type="radio" name="${this._escapeHtml(name)}" value="${this._escapeHtml(value)}" ${this._buildAttributes(restAttributes)} ${checkedAttr}>`;
  }

  selectBox(name, optionsArray = [], attributes = {}) {
const {
  class: classAttr = '',
  value: selectedValue,
  placeholder = '--選択してください--',
  includePlaceholder = true, // ← 明示的に出すかどうか切り替えられる
  ...restAttributes
} = attributes;
  
restAttributes.class = this._normalizeClass(classAttr);

if (!Array.isArray(optionsArray)) {
  throw new TypeError('optionsArray must be an array');
}
  
const optionsHtml = [
  // プレースホルダーの option(先頭に追加)
  includePlaceholder ? `<option value="">${this._escapeHtml(placeholder)}</option>` : null,
  
  // その他の option
  ...optionsArray.map(option => {
    const isSelected = selectedValue !== undefined
      ? String(option.value) === String(selectedValue)
      : option.selected === true;
  
    const selectedAttr = isSelected ? 'selected' : '';
    return `<option value="${this._escapeHtml(option.value)}" ${selectedAttr}>${this._escapeHtml(option.text)}</option>`;
  })
].filter(Boolean).join('');
  
return `<select name="${this._escapeHtml(name)}" ${this._buildAttributes(restAttributes)}>${optionsHtml}</select>`;
  }

  label(forId, text, attributes = {}) {
  return `<label for="${this._escapeHtml(forId)}" ${this._buildAttributes(attributes)}>${this._escapeHtml(text)}</label>`;
  }
}

module.exports = FormHelpers;

すべてのルーターで利用するために、app.jsに以下の設定を追加します。

app.js

const FormHelpers = require('./helpers/FormHelpers');
app.use((req, res, next) => {
  res.locals.form = new FormHelpers();
  next()
});

ejs ファイル上で、ヘルパー関数を使うには、以下のようにします。

<div>
<p><%- form.textInput('username', { class: 'form-control' }) %></p>
<p><%- form.passwordInput('password', { id: 'password', class: 'form-control' }) %></p>
<p><%- form.textArea('textarea', { class: 'form-control textare', value: "hoge" }) %></p>
<p><%- form.hiddenInput('hidden_input', { id: 'hidden_input', rows: 5, class: 'form-control' }) %></p>
<p><%- form.checkBox('role', { id: 'role', class: 'form-check-input', checked: true, value: "admin" }) %></p>
<p><%- form.radioButton('gender', { id: 'gender_male', class: 'form-check-input', value: 'maile', checked: true }) %></p>
<p><%- form.radioButton('gender', { id: 'gender_female', class: 'form-check-input', value: 'female' }) %></p>
<p>
  <%- form.selectBox('country', [
{ value: '', text: '選択してください' },
{ value: 'JP', text: '日本' },
{ value: 'US', text: 'アメリカ', selected: true },
{ value: 'CA', text: 'カナダ' }
], { id: 'country', class: 'form-control' }) %>
</p>
  </div>