ビューヘルパー - Express.js

  • 作成日:
  • 最終更新日:2025/06/25

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

次のように、アプリケーションの中に、「 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>