LinkDesk/frontend/docs/checkbox-selection-refactor.md

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.