チェックボックスの全チェック・全解除

# 概要
- jQueryのfilterを活用して、まとめて取得したチェックボックスから、特定のグループだけを抽出する。
- ボタンの状態に応じて、全チェック・全解除。
- グループ内のチェックが1つでも外れた場合「全てチェック・解除ボタン」のチェック状態を外す。
- グループ内のチェックが全てついた場合「全てチェック・解除ボタン」にチェックを付ける。

# 実行サンプル
**CSSなし**

- http://jsfiddle.net/rxxcs73f/4/

**CSSあり**

- http://jsfiddle.net/rxxcs73f/3/ （オブジェクトリテラル）
- http://codepen.io/yama-t/pen/VvxqvP （プロトタイプベース）

# コード
## オブジェクトリテラル
```html:HTML
<h1>グループ1</h1>
<ul>
	<li><label><input type="checkbox" name="group1">チェック1</label></li>
	<li><label><input type="checkbox" name="group1">チェック2</label></li>
	<li><label><input type="checkbox" name="group1">チェック3</label></li>
	<li><label><input type="checkbox" name="group1">チェック4</label></li>
	<li><label><input type="checkbox" name="group1">チェック5</label></li>
</ul>

<div><label><input type="checkbox" data-checked-all="group1" class="jscBtnCheckAll">グループ1を全チェック/解除</label></div>

<h1>グループ2</h1>
<ul>
	<li><label><input type="checkbox" name="group2">チェック1</label></li>
	<li><label><input type="checkbox" name="group2">チェック2</label></li>
	<li><label><input type="checkbox" name="group2">チェック3</label></li>
	<li><label><input type="checkbox" name="group2">チェック4</label></li>
	<li><label><input type="checkbox" name="group2">チェック5</label></li>
</ul>

<div><label><input type="checkbox" data-checked-all="group2" class="jscBtnCheckAll">グループ2を全チェック/解除</label></div>
```

```js:JS
var SAMPLE = SAMPLE || {}

SAMPLE.CheckAll = {
	init: function() {
		this.setParameter();
		this.bindEvent();
	},
	setParameter: function(){
		this.$checkbox = $('input[type=checkbox]');
		this.$btnCheckAll = $('.jscBtnCheckAll');
	},
	bindEvent: function(){
		var _self = this;

		this.$btnCheckAll.on('click', function() {
			_self.toggleCheckedAll(this);
		});

		this.$checkbox.on('click', function() {
			_self.toggleBtnCheckedAll(this);
		});
	},
	toggleCheckedAll: function(btnCheckAll){
		// 同グループのチェックボックスの全チェック・全解除
		var $btnCheckAll = $(btnCheckAll),
			groupName = $btnCheckAll.attr('data-checked-all'),
			$targetCheckbox = this.$checkbox.filter(function() {
				return $(this).attr('name') === groupName;
			});

		if($btnCheckAll.is(':checked')){
			$targetCheckbox.prop('checked', true);
		} else {
			$targetCheckbox.prop('checked', false);
		}
	},
	toggleBtnCheckedAll: function(checkbox){
		var $checkbox = $(checkbox),
			groupName = $checkbox.attr('name'),
			$targetBtnCheckAll = this.$btnCheckAll.filter(function() {
				return $(this).attr('data-checked-all') === groupName;
			});

		// グループ内のチェックが1つでも外れたら、ボタンのチェックも外す
		if(!$checkbox.is(':checked')){
			$targetBtnCheckAll.prop('checked', false);
			return;
		}

		// グループ内のボタンが全てチェックされたら、ボタンにチェックをつける
		var $checkboxGroup = this.$checkbox.filter(function() {
				return $(this).attr('name') === groupName;
			}),
			$checkboxGroupChecked = $checkboxGroup.filter(function() {
				return $(this).prop('checked') === true;
			});

		if($checkboxGroup.length === $checkboxGroupChecked.length){
			$targetBtnCheckAll.prop('checked', true);
		}
	}
}

$(function(){
	SAMPLE.CheckAll.init();
})
```

## プロトタイプベース
全てチェックボタン1つと、対応するチェックボックスのグループごとにインスタンスを生成する感じ。
読み込み時にグループ化するので、イベント発生時にやることが減る（チェック状態の確認と、チェックされている数の確認、それに応じた操作だけ）。

```js:JS
var SAMPLE = SAMPLE || {}

SAMPLE.CheckAll = function(btnCheckAll){
	this.$btnCheckAll = $(btnCheckAll);
	this.init();	
};

SAMPLE.CheckAll.prototype = {
	init: function() {
		this.setParameter();
		this.bindEvent();
	},
	setParameter: function(){
		this.groupName = this.$btnCheckAll.attr('data-checked-all');
		this.$checkbox = this.getCheckboxGroup();
		this.checkboxLength = this.$checkbox.length;
	},
	getCheckboxGroup: function(){
		var groupName = this.groupName,
			$result;

		/* グループ化のための属性が設定されていない場合の例外処理。*/
		if(!groupName){
			return $('');
		}

		$result = $('input[type=checkbox]').filter(function() {
			return $(this).attr('name') === groupName;
		});

		return $result;
	},
	bindEvent: function(){
		var _self = this;

		this.$btnCheckAll.on('click', function() {
			_self.toggleCheckedAll();
		});

		this.$checkbox.on('click', function() {
			_self.toggleBtnCheckedAll();
		});
	},
	toggleCheckedAll: function(){
		// 同グループのチェックボックスの全チェック・全解除
        var checked = this.$btnCheckAll.is(':checked');
	    this.$checkbox.prop('checked', checked);
	},
	toggleBtnCheckedAll: function(){
        // チェック項目が外れた時に全選択ボタンのチェック状態の解除
		var checkedLength = this.$checkbox.filter(':checked').length,
        checked = false;

		// グループ内のボタンが全てチェックされたら、ボタンにチェックをつける
		if(checkedLength === this.checkboxLength){
			checked = true;
		}
        this.$btnCheckAll.prop('checked', checked);
	}
}

$(function(){
	$('.jscBtnCheckAll').each(function() {
		new SAMPLE.CheckAll(this);
	});
})
```

# 補足
```js:同グループのチェックボックスの抽出（全チェック・解除ボタン除く）
var $checkboxGroup = this.$checkbox.filter(function() {
	return $(this).attr('name') === groupName;
}),
```
全チェック・解除ボタンを除いた、同グループのチェックボックス全てを抽出する。

```js:同グループのチェックボックスの中から、さらにchecked状態のものだけを抽出
$checkboxGroupChecked = $checkboxGroup.filter(function() {
    return $(this).prop('checked') === true;
});
```

一度抽出したものからさらにフィルターを掛けて、チェック中のチェックボックスのみ抽出。

```js:抽出した数をそれぞれ比較し、一致した場合にのみ全チェック・解除ボタンにチェックをつける
if($checkboxGroup.length === $checkboxGroupChecked.length){
	$targetBtnCheckAll.prop('checked', true);
}
```

lengthを利用して両方の数を比較して、数が一致した時＝グループ全てにチェックが付けられた時なので、そのタイミングで全チェック・解除ボタンにチェックを付ける。

# 修正
- サーバーの処理と絡める時、全チェック・解除ボタンにチェックボックスと同じname属性があると改竄エラーが出るというFBがあったので、全チェック・解除ボタンにはname属性ではなく、data-checked-all属性を持たせて、その名前をチェックボックスのグループ名と同じにした。