144 lines
4.4 KiB
Markdown
144 lines
4.4 KiB
Markdown
# Checkbox Selection Refactor
|
|
|
|
## Overview
|
|
Refactored the AssetBrowser component to use a cleaner, more Vue-friendly approach for checkbox selection using object-based state instead of array-based state. This leverages the native v-model support in shadcn-vue Checkbox components (based on reka-ui).
|
|
|
|
## Changes Made
|
|
|
|
### 1. Selection State Structure
|
|
**Before:**
|
|
```typescript
|
|
const selectedAssets = ref<number[]>([]);
|
|
```
|
|
|
|
**After:**
|
|
```typescript
|
|
const selectedAssets = ref<Record<number, boolean>>({});
|
|
```
|
|
|
|
### 2. Select All Checkbox
|
|
**Before:**
|
|
```vue
|
|
<Checkbox
|
|
:checked="isAllSelected"
|
|
@update:checked="toggleSelectAll"
|
|
/>
|
|
```
|
|
|
|
**After:**
|
|
```vue
|
|
<Checkbox v-model="selectAllChecked" />
|
|
```
|
|
|
|
With computed property (getter/setter pattern):
|
|
```typescript
|
|
const selectAllChecked = computed({
|
|
get: () => {
|
|
return filteredAssets.value.length > 0 &&
|
|
filteredAssets.value.every(asset => selectedAssets.value[asset.id]);
|
|
},
|
|
set: (checked: boolean) => {
|
|
toggleSelectAll(checked);
|
|
}
|
|
});
|
|
```
|
|
|
|
The computed property's setter is automatically called by v-model, eliminating the need for a separate event handler.
|
|
|
|
### 3. Row Checkboxes
|
|
**Before:**
|
|
```vue
|
|
<Checkbox
|
|
:checked="selectedAssets.includes(asset.id)"
|
|
@update:checked="(checked) => toggleAssetSelection(asset.id, checked)"
|
|
@click.stop
|
|
/>
|
|
```
|
|
|
|
**After:**
|
|
```vue
|
|
<Checkbox
|
|
v-model="selectedAssets[asset.id]"
|
|
@click.stop
|
|
/>
|
|
```
|
|
|
|
### 4. Helper Methods
|
|
|
|
**New method to get selected IDs:**
|
|
```typescript
|
|
const getSelectedAssetIds = () => {
|
|
return Object.keys(selectedAssets.value)
|
|
.filter(id => selectedAssets.value[Number(id)])
|
|
.map(id => Number(id));
|
|
};
|
|
```
|
|
|
|
**Simplified toggleSelectAll:**
|
|
```typescript
|
|
const toggleSelectAll = (checked: boolean) => {
|
|
filteredAssets.value.forEach(asset => {
|
|
selectedAssets.value[asset.id] = checked;
|
|
});
|
|
};
|
|
```
|
|
|
|
### 5. Row Selection Logic
|
|
Updated to work with object-based state:
|
|
```typescript
|
|
const handleRowClick = (asset: Asset, event: MouseEvent) => {
|
|
if (event.ctrlKey || event.metaKey) {
|
|
// Multi-select with Ctrl/Cmd - toggle selection
|
|
selectedAssets.value[asset.id] = !selectedAssets.value[asset.id];
|
|
} else if (event.shiftKey && getSelectedAssetIds().length > 0) {
|
|
// Range select with Shift
|
|
const selectedIds = getSelectedAssetIds();
|
|
const lastSelectedId = selectedIds[selectedIds.length - 1];
|
|
// ... range selection logic
|
|
} else {
|
|
// Single select
|
|
selectAsset(asset);
|
|
}
|
|
};
|
|
```
|
|
|
|
## Benefits
|
|
|
|
1. **Direct v-model Binding**: The shadcn-vue Checkbox (based on reka-ui) supports `v-model` natively when bound to a boolean value ([reka-ui issue #1017](https://github.com/unovue/reka-ui/issues/1017))
|
|
2. **Simpler Code**: No need for manual event handlers - v-model handles everything
|
|
3. **Better Performance**: Direct property access (`selectedAssets[id]`) is faster than array operations (`.includes()`, `.push()`, `.splice()`)
|
|
4. **More Reactive**: Vue's reactivity system handles object property changes efficiently
|
|
5. **Cleaner API**: The checkbox state is directly tied to the data structure
|
|
6. **No Redundant Handlers**: Using v-model eliminates the need for separate event handlers
|
|
|
|
## Testing
|
|
|
|
A test file (`frontend/test-asset-selection.html`) has been created to demonstrate the pattern working with plain Vue and native HTML checkboxes.
|
|
|
|
To test:
|
|
1. Open `frontend/test-asset-selection.html` in a browser
|
|
2. Try selecting individual checkboxes
|
|
3. Try the "Select All" checkbox
|
|
4. Try the action buttons (Select All, Deselect All, Select First 3)
|
|
5. Verify the selection info updates correctly
|
|
|
|
## Migration Notes
|
|
|
|
Any code that previously used `selectedAssets.value` as an array needs to be updated:
|
|
- Use `getSelectedAssetIds()` to get an array of selected IDs
|
|
- Use `selectedAssets.value[id]` to check if an asset is selected
|
|
- Use `selectedAssets.value = {}` to clear all selections
|
|
|
|
## Important Notes
|
|
|
|
### v-model vs v-model:checked
|
|
|
|
According to [reka-ui issue #1017](https://github.com/unovue/reka-ui/issues/1017), the correct syntax is:
|
|
- ✅ Use `v-model` (not `v-model:checked`)
|
|
- ✅ Works with boolean values directly
|
|
- ✅ No need for `:checked` + `@update:checked` patterns
|
|
|
|
### When to Use Event Handlers
|
|
|
|
When using Checkbox with props that need to emit to parent (like in ColumnVisibilityControl), you may still need `@update:checked` handlers to emit changes to the parent component, since props are not directly mutable. However, for local state management (like in AssetBrowser), pure v-model is sufficient.
|