入力の追加

同じ種類の情報グループを任意で追加できるようにするコントロールです。紙のフォームではあらかじめ空行を並べておくようなものが見られますが、紙の帳票をリプレイスするようなケースでも、同じようにただ空の行を並べるようなことはせず、必要に応じて追加できるようにしたUIが有用です。追加可能な数に上限を設けて、あらかじめ適切な初期値を入れた状態で入力を追加したり、上限に達したら追加ボタンが表示されないように制限したりできます。

HTML
<div class="js-addcontrol1--wrap">
  <div class="row js-addcontrol1--1">
    <div class="col-md-2">
      <div class="form-group required">
        <label class="label--code"><span class="label-text">コード1</span></label>
        <input type="text" class="form-control" placeholder="000000" value="">
      </div>
    </div>
    <div class="col-md-6">
      <div class="form-group required">
        <label class="label--name"><span class="label-text">商品名1</span></label>
        <input type="text" class="form-control">
      </div>
    </div>
    <div class="col-md-2">
      <div class="form-group">
        <label for="code1" class="label--price"><span class="label-text">単価1</span></label>
        <input type="text" class="form-control text-right" placeholder="000000">
      </div>
    </div>
    <div class="col-md-2">
      <div class="form-group pull-right">
        <label><span class="label-text"> </span></label>
        <button class="btn btn-flat-danger btn-block js-addcontrol--del" data-toggle="modal" data-target=".modal--del"><span class="icon-abui-cross"></span></button>
      </div>
    </div>
  </div>
  <div class="row row--addcontrol">
    <div class="col-md-12">
      <div class="form-group">
        <button class="btn btn-flat-default js-addcontrol1"><span class="icon-abui-plus prx"></span>商品を追加</button>
        </div>
    </div>
  </div>
</div>
jQuery
/**
* 商品を追加ボタン
* @type {Object}
*/
const $btnAddControl = $('.js-addcontrol1').parents('.row--addcontrol');

/**
* 入力グループをコピー
* @type {Object}
*/
const $getInputGroup = $('.js-addcontrol1--1').clone();

/**
* 項目が1つの時の削除ボタンはdisabledに
* @type {Object}
*/
const $firstDelBtn = $('.js-addcontrol1--1').find('.js-addcontrol--del');
$firstDelBtn.prop('disabled', true);

/**
* 商品を追加ボタンを押した時
*/
$('.js-addcontrol1').off('click.addcontrol');
$('.js-addcontrol1').on('click.addcontrol', function() {
  /**
  * ターゲットとなる入力グループ
  * @type {Object}
  */
  let $tgtInputGroup = $getInputGroup.clone();

  $('.js-addcontrol1--wrap > div:not(.row--addcontrol)').each(function(i) {
    i = i + 2;
    $firstDelBtn.prop('disabled', false);
    $tgtInputGroup.attr('class', 'js-addcontrol1--' + i).addClass('row');
    $tgtInputGroup.find('.label--code').html('<span class="label-text">コード' + i + '</span>');
    $tgtInputGroup.find('.label--name').html('<span class="label-text">商品名' + i + '</span>');
    $tgtInputGroup.find('.label--price').html('<span class="label-text">単価' + i + '</span>');
    $($btnAddControl).before($tgtInputGroup);

    /**
    * 項目が5個になったら、追加ボタンを消去
    */
    if(i == 5) {
      $('.js-addcontrol1').parent().hide();
    }
  });

  if($('.js-addcontrol1--wrap > div:not(.row--addcontrol)').length < 2) {
    $('.js-addcontrol1--1').find('.js-addcontrol--del').prop('disabled', true);
  } else {
    $('.js-addcontrol1--1').find('.js-addcontrol--del').prop('disabled', false);
  }
});

/**
* 削除ボタンを押した時
*/
$(document).on('click.addcontrol', '.js-addcontrol--del', function(event) {
  let $tgtDelInputGroup = $(event.currentTarget).closest('[class*="js-addcontrol1--"]');
  $('.js-btn--del').off('click.hide-modal');
  $('.js-btn--del').on('click.hide-modal', function() {
    $('#modal--del').modal('hide');
    $tgtDelInputGroup.remove();
    $('.js-addcontrol1--wrap > div:not(.row--addcontrol)').each(function(i) {
      i = i + 1;
      $(this).attr('class', 'js-addcontrol1--' + i).addClass('row');
      $(this).find('.label--code').html('<span class="label-text">コード' + i + '</span>');
      $(this).find('.label--name').html('<span class="label-text">商品名' + i + '</span>');
      $(this).find('.label--price').html('<span class="label-text">単価' + i + '</span>');

      /**
      * 項目が5個以下、追加ボタンを表示
      */
      if(i < 5) {
        $('.js-addcontrol1').parent().show();
      }
    });

    if($('.js-addcontrol1--wrap > div:not(.row--addcontrol)').length < 2) {
      $('.js-addcontrol1--1').find('.js-addcontrol--del').prop('disabled', true);
    } else {
      $('.js-addcontrol1--1').find('.js-addcontrol--del').prop('disabled', false);
    }
  });
});
表組に入力行を追加

表組に新規の入力行を追加していくこともできます。

氏名 所属 役職
1
HTML
<table class="table table-bordered table-hover js-addcontrol-item-1--table">
  <thead>
    <tr>
      <th style="width: 1em;"> </th>
      <th>氏名</th>
      <th>所属</th>
      <th>役職</th>
      <th style="width: 44px;"></th>
    </tr>
  </thead>
  <tbody id="js-addcontrol-item-1--tbody">
    <tr class="js-addcontrol-item-1--row1">
      <td class="js-addcontrol-item-1--row--num">1</td>
      <td class="cell-control"><input type="text" class="form-control input-sm" placeholder="氏名" value="佐野 結衣"/></td>
      <td class="cell-control">
        <select name="input" class="select-block select-sm" title="所属">
          <option value="0" selected>情報システム部</option>
          <option value="1">技術企画部</option>
          <option value="2">モバイル技術部</option>
          <option value="3">プラットフォーム開発部</option>
          <option value="4">ネットワーク技術部</option>
          <option value="5">運用部</option>
        </select>
      </td>
      <td class="cell-control">
        <select name="input" class="select-block select-sm" title="役職">
          <option value="0" selected>なし</option>
          <option value="1">主任</option>
          <option value="2">係長</option>
          <option value="3">課長</option>
          <option value="4">次長</option>
          <option value="5">部長</option>
        </select>
      </td>
      <td class="cell-control">
        <a class="btn btn-flat-danger btn-sm delrow" href="#fakelink" data-toggle="modal" data-target=".modal--del"><span class="icon-abui-cross"></span></a>
      </td>
    </tr>
  </tbody>
  <tfoot>
    <tr class="addrow js-addcontrol-item" id="js-addcontrol-item-1">
      <td colspan="4"><a class="btn" href="#fakelink"><span class="icon-abui-plus"></span></a></td>
    </tr>
  </tfoot>
</table>
jQuery
/**
* ターゲットとなるjQueryオブジェクト
* @type {Object}
*/
const $tgtAddControlItem = $('.js-addcontrol-item');

/**
* 最初の行をコピー
* @type {String}
*/
const tempItem = $('#js-addcontrol-item-1--tbody').html();

$tgtAddControlItem.off('.addcontrol');
$tgtAddControlItem.on('click.addcontrol', function(event) {
  /**
  * クリックした要素のID名を取得
  * @type {String}
  */
  const id = $(this).attr('id');

  /**
  * 対象となる表組のjQueryオブジェクト
  * @type {Object}
  */
  const $table = $('.' + id + '--table');

  /**
  * 元の表組の行数
  * @type {Number}
  */
  const old_num = $table.find('tbody').children().length;

  /**
  * 追加された行のクラス名
  * @type {String}
  */
  let tgtTableRowInit;

  /**
  * 表組の行数
  * @type {Number}
  */
  let new_num;

  /**
  * 追加された行数
  * @type {Number}
  */
  let new_row_class_num;

  /**
  * 追加する行のテンプレート
  * @type {String}
  */
  const $tgtTableRow = $table.find('tbody').find('tr');
  let new_item = $($tgtTableRow[0]).html();

  if(!$tgtTableRow.length) {
    $table.find('tbody').append(tempItem);

    /**
    * 追加された行のクラス名
    */
    tgtTableRowInit = $table.find('tbody').find('tr').first().attr('class');
    tgtTableRowInit = '.' + tgtTableRowInit;
  } else {
    /**
    * 1つ前の行のクラス名を取得
    * @type {String}
    */
    const last_row_class_name = $($tgtTableRow[old_num - 1]).context.className;

    /**
    * 表組の行数
    */
    new_num = old_num + 1;

    /**
    * 追加された行数
    * @type {Number}
    */
    new_row_class_num = Number(last_row_class_name.split('row')[1]) + 1;

    new_item = '<tr class="' + id + '--row' + new_row_class_num + '">' + new_item + '</tr>';
    $table.find('tbody').append(new_item);

    /**
    * 追加された行のクラス名
    */
    tgtTableRowInit = '.' + id + '--row' + new_row_class_num;
  }

  /**
  * 行数が上限になったら、追加ボタンを消去
  */
  if(new_row_class_num == 5) {
    $(this).parent().hide();
  }

  /**
  * ナンバリング
  */
  autoNumbering(tgtTableRowInit, id, new_num);

  /**
  * 入力値のリセット
  */
  initComp(tgtTableRowInit);
});

/**
* 削除ボタンを押した時
*/
$('.delrow').off('.addcontrol');
$(document).on('click.addcontrol', '.delrow', function(event) {
  let $tgtDelInputGroup = $(event.currentTarget).closest('tr');
  $('.js-btn--del').off('click.hide-modal');
  $('.js-btn--del').on('click.hide-modal', function() {
    $('#modal--del').modal('hide');
    delAddControl($tgtDelInputGroup);
  });
});

/**
* 自動ナンバリング 関数
* @param  {String} tgtTableRowInit 追加された行のクラス名
* @param  {String} id              クリックした要素のID名
* @param  {Number} new_num         表組の行数
* @return {Undefined}
*/
function autoNumbering(tgtTableRowInit, id, new_num) {
  'use strict';
  $(tgtTableRowInit).find('.' + id + '--row--num').html(new_num);
}

/**
* コンポーネントの入力値リセット 関数
* @param  {String} tgtTableRowInit 追加された行のクラス名
*/
function initComp(tgtTableRowInit) {
  'use strict';
  /**
  * 値のリセット
  */
  $(tgtTableRowInit).find('input').val('');
  $(tgtTableRowInit).find('option').removeAttr('selected');

  /**
  * ドロップダウン
  */
  $(tgtTableRowInit).find('td').each(function() {
    $($(this).find('.dropdown-toggle')[0]).unwrap().remove();
    $($(this).find('.dropdown-menu')[0]).remove();
    $(this).find('select[name="input"]').selectpicker({
      style: 'btn-input'
    }).on('show.bs.select', function(e) {
      $(this).parents('.bootstrap-select').find($('.dropdown-menu')).addClass('open');
    }).on('hidden.bs.select', function(e) {
      $(this).parents('.bootstrap-select').find($('.dropdown-menu')).removeClass('open');
    });
  });
}

/**
* 削除の制御 関数
* @param  {Object} $tgtDelInputGroup ターゲットの行
*/
function delAddControl($tgtDelInputGroup) {
  'use strict';
  $tgtDelInputGroup.remove();
  $('#js-addcontrol-item-1--tbody').find('tr').each(function(i) {
    i = i + 1;
    $(this).attr('class', 'js-addcontrol-item-1--row' + i).find('.js-addcontrol-item-1--row--num').text(i);

    /**
    * 項目が5個以下、追加ボタンを表示
    */
    if(i < 5) {
      $('#js-addcontrol-item-1').parent().show();
    }
  });
}
モーダルを開いて追加

入力が複合的であったり、選択肢が多く検索や絞り込みが必要な場合には、表組内には直接入力を置かずにモーダルを開いて入力するかたちも有効です。モーダルが出現することで、いったんもとの表組が視界から消えるため、モーダルを閉じて戻ったときに新しく追加した行を明滅させて、追加箇所をユーザーに知らせます

氏名 所属
1 佐野 結衣 技術統括本部 > 情報システム部