CoffeeScript でフォームのバリデーションを作ってみる！

## 📝 **はじめに**  
フォームのバリデーション、面倒ですよね？  
JavaScript の `if` 文で1つずつチェックしていると、コードが増えて管理が大変になります。  
そこで、 **CoffeeScript で「簡単にバリデーションできるクラス」** を作ってみました！  

✅ **特徴**  
- **必須入力、文字数制限、数値範囲、正規表現、メール・日付チェック** などの一般的なルールを実装  
- **カスタムバリデーション** を追加可能  
- **英語 / 日本語のエラーメッセージ対応**（カスタムメッセージもOK）  
- **1つのフィールド or 全体のフォームを一括チェック可能**  

---

## 🎯 **バリデーション仕様**  

### ✅ **共通ルール**  
| ルール名      | 内容  |  
|--------------|------------------------------------------------------|  
| `required`   | 必須入力（text, select, radio）                      |  
| `maxLength`  | 最大文字数                                         |  
| `minLength`  | 最小文字数                                         |  
| `rangeLength` | 文字数の範囲（例: `rangeLength: [3, 10]`）          |  
| `min`        | 最小値（数値）                                      |  
| `max`        | 最大値（数値）                                      |  
| `between`    | 指定範囲の数値（例: `between: [10, 100]`）           |  
| `pattern`    | 正規表現チェック                                   |  
| `equalsTo`   | 他の入力値と一致するか（例: パスワード確認）         |  

### 🎯 **特定の入力タイプ向けバリデーション**  
| ルール名        | 内容  |  
|--------------|-----------------------------------|  
| `email`     | メールアドレス形式 (`xxx@yyy.zzz`) |  
| `date`      | 日付形式 (`YYYY/MM/DD`)           |  
| `alphaNumeric` | 英数字のみ                    |  
| `alpha`      | 英字のみ                          |  
| `numeric`    | 数字のみ                          |  
| `checkboxMin` | チェックボックスの最低選択数     |  
| `checkboxMax` | チェックボックスの最大選択数     |  

### 🎯 **カスタムバリデーション対応**  
好きなルールを追加できます！  
例えば「A で始まる文字列のみ許可する」バリデーションを作成 👇  
```coffeescript
customValidators =
  startsWithA: (value, ruleValue) ->
    if ruleValue and not value.startsWith("A")
      return { valid: false, message: "Value must start with 'A'.\n'A' で始まる必要があります。" }
    return { valid: true }
```

---
### 🚀 実装（CoffeeScript）

window.validation クラス を作成し、
指定のルールに基づいてフィールドをチェックします！

#### 📌 `validate(fieldName, value, rules)`
1つのフィールドをバリデーション


``` coffeescript
result = validator.validate("username", "abc", { required: true, minLength: 5 })
console.log result 
# { valid: false, errors: ["[username] must be at least 5 characters.\n[username] は最小 5 文字です。"] }
```
#### 📌 `validateAll(fields)`
複数のフィールドをまとめてバリデーション

``` coffeescript
fields = 
  username: { value: "abc", rules: { required: true, minLength: 5 } }
  email: { value: "test@example.com", rules: { email: true } }

resultAll = validator.validateAll(fields)
console.log resultAll
```

### 📜 バリデーションクラスのコード

``` coffeescript
window.validation = class Validation
  ###*
  * バリデーションエラーメッセージ
  ###
  messages:
    required: "[{field}] is required.\n[{field}] は必須です。"
    maxLength: "[{field}] must be at most {ruleValue} characters.\n[{field}] は最大 {ruleValue} 文字です。"
    minLength: "[{field}] must be at least {ruleValue} characters.\n[{field}] は最小 {ruleValue} 文字です。"
    rangeLength: "[{field}] must be between {min} and {max} characters.\n[{field}] は {min} から {max} 文字の範囲です。"
    min: "[{field}] must be at least {ruleValue}.\n[{field}] は {ruleValue} 以上である必要があります。"
    max: "[{field}] must be at most {ruleValue}.\n[{field}] は {ruleValue} 以下である必要があります。"
    between: "[{field}] must be between {min} and {max}.\n[{field}] は {min} から {max} の範囲です。"
    pattern: "[{field}] is invalid format.\n[{field}] の形式が正しくありません。"
    equalsTo: "[{field}] must be the same as {target}.\n[{field}] は {target} と一致する必要があります。"
    email: "[{field}] must be a valid email address.\n[{field}] は有効なメールアドレスである必要があります。"
    date: "[{field}] must be a valid date (YYYY/MM/DD).\n[{field}] は有効な日付形式 (YYYY/MM/DD) である必要があります。"
    alphaNumeric: "[{field}] must be alphanumeric.\n[{field}] は英数字のみです。"
    alpha: "[{field}] must contain only letters.\n[{field}] は英字のみです。"
    numeric: "[{field}] must contain only numbers.\n[{field}] は数字のみです。"
    checkboxMin: "[{field}] must select at least {ruleValue} options.\n[{field}] は最低 {ruleValue} 個選択する必要があります。"
    checkboxMax: "[{field}] must select at most {ruleValue} options.\n[{field}] は最大 {ruleValue} 個まで選択できます。"

  ###*
  * カスタムバリデーションの定義
  ###
  customValidators: {}

  ###*
  * バリデーション実行
  * @param {string} fieldName - フィールド名
  * @param {string|Array} value - 入力値
  * @param {Object} rules - バリデーションルール
  * @return {Object} - `{ valid, errors }`
  ###
  validate: (fieldName, value, rules) ->
    errors = []

    # ルール適用
    for rule, ruleValue of rules
      switch rule
        when "required"
          if not value or (Array.isArray(value) and value.length is 0)
            errors.push(@formatMessage("required", fieldName))

        when "maxLength"
          if value.length > ruleValue
            errors.push(@formatMessage("maxLength", fieldName, { ruleValue }))

        when "minLength"
          if value.length < ruleValue
            errors.push(@formatMessage("minLength", fieldName, { ruleValue }))

        when "rangeLength"
          if value.length < ruleValue[0] or value.length > ruleValue[1]
            errors.push(@formatMessage("rangeLength", fieldName, { min: ruleValue[0], max: ruleValue[1] }))

        when "min"
          if parseFloat(value) < parseFloat(ruleValue)
            errors.push(@formatMessage("min", fieldName, { ruleValue }))

        when "max"
          if parseFloat(value) > parseFloat(ruleValue)
            errors.push(@formatMessage("max", fieldName, { ruleValue }))

        when "between"
          if parseFloat(value) < parseFloat(ruleValue[0]) or parseFloat(value) > parseFloat(ruleValue[1])
            errors.push(@formatMessage("between", fieldName, { min: ruleValue[0], max: ruleValue[1] }))

        when "pattern"
          if not ruleValue.test(value)
            errors.push(@formatMessage("pattern", fieldName))

        when "equalsTo"
          if value isnt document.querySelector("[name='#{ruleValue}']").value
            errors.push(@formatMessage("equalsTo", fieldName, { target: ruleValue }))

        when "email"
          if not /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
            errors.push(@formatMessage("email", fieldName))

        when "date"
          if not /^\d{4}\/\d{2}\/\d{2}$/.test(value)
            errors.push(@formatMessage("date", fieldName))

        when "alphaNumeric"
          if not /^[a-zA-Z0-9]+$/.test(value)
            errors.push(@formatMessage("alphaNumeric", fieldName))

        when "alpha"
          if not /^[a-zA-Z]+$/.test(value)
            errors.push(@formatMessage("alpha", fieldName))

        when "numeric"
          if not /^\d+$/.test(value)
            errors.push(@formatMessage("numeric", fieldName))

        when "checkboxMin"
          if value.length < ruleValue
            errors.push(@formatMessage("checkboxMin", fieldName, { ruleValue }))

        when "checkboxMax"
          if value.length > ruleValue
            errors.push(@formatMessage("checkboxMax", fieldName, { ruleValue }))

      # カスタムバリデーション
      if rule of @customValidators
        result = @customValidators[rule](value, ruleValue)
        unless result.valid
          errors.push(result.message)

    return { valid: errors.length is 0, errors }

  ###*
  * 複数のフィールドをバリデーション
  * @param {Object} fields - `{ fieldName: { value, rules } }`
  * @return {Object} - `{ valid, results }`
  ###
  validateAll: (fields) ->
    results = {}
    valid = true

    for fieldName, data of fields
      result = @validate(fieldName, data.value, data.rules)
      results[fieldName] = result
      valid = false unless result.valid

    return { valid, results }

  ###*
  * メッセージフォーマット
  * @param {string} key - メッセージキー
  * @param {string} fieldName - フィールド名
  * @param {Object} [params] - 追加の置換パラメータ
  * @return {string} - フォーマット済みメッセージ
  ###
  formatMessage: (key, fieldName, params = {}) ->
    message = @messages[key].replace("{field}", fieldName)
    for param, value of params
      message = message.replace("{#{param}}", value)
    return message

```

---
### 🎉 まとめ

CoffeeScript で **フォームバリデーションをシンプルに実装** できました！

- **バリデーションを追加するのが超簡単！**
- **エラーメッセージも日本語 / 英語対応！**
- **カスタムルールも拡張可能！**

✨ **CoffeeScript** を使う機会があるなら、ぜひ試してみてください！ ✨

👉 感想やフィードバックがあれば、ぜひコメントしてください！
🚀 Qiita のストックもよろしくお願いします！ 🚀