Alpine.js x-bound: Binding indeterminate property

by rameel

HTML

<div x-data="tableData()">
  <table>
    <thead>
      <tr>
        <th class="checkbox-cell">
          <input type="checkbox" &indeterminate="isPartialSelected" &checked="isAllSelected" />
        </th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <template x-for="(item, index) in items" :key="index">
        <tr :class="{ 'selected': item.selected }" x-format>
          <td class="checkbox-cell">
            <input type="checkbox" &checked="item.selected" />
          </td>
          <td>{{ item.name }}</td>
        </tr>
      </template>
    </tbody>
  </table>
</div>

<script src="https://cdn.jsdelivr.net/npm/@ramstack/alpinegear-bound@1/alpinegear-bound.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/@ramstack/alpinegear-format@1/alpinegear-format.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js" defer></script>

CSS

html {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  padding: 12px 15px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

th {
  background-color: #adadad;
  color: white;
}

tr:nth-child(even) {
  background-color: #f9f9f9;
}

tr:hover {
  background-color: #f1f1f1;
}

.checkbox-cell {
  width: 40px;
  text-align: center;
}

input[type="checkbox"] {
  transform: scale(1.25);
  cursor: pointer;
}

.selected {
  background-color: #f1f1f1 !important;
}

JavaScript

function tableData() {
  return {
    items: Array.from({ length: 4 }, (_, i) => ({
      name: `Element ${i + 1}`,
      selected: i % 2 !== 0
    })),

    get isAllSelected() {
      return this.items.length > 0 && this.items.every(item => item.selected);
    },

    set isAllSelected(value) {
      this.items.forEach(item => item.selected = value);
    },

    get isPartialSelected() {
      let selected = this.items.reduce((count, item) => (item.selected ? count + 1 : count), 0);
      return selected > 0 && selected < this.items.length
    }
  }
}