4.4 KiB
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:
const selectedAssets = ref<number[]>([]);
After:
const selectedAssets = ref<Record<number, boolean>>({});
2. Select All Checkbox
Before:
<Checkbox
:checked="isAllSelected"
@update:checked="toggleSelectAll"
/>
After:
<Checkbox v-model="selectAllChecked" />
With computed property (getter/setter pattern):
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:
<Checkbox
:checked="selectedAssets.includes(asset.id)"
@update:checked="(checked) => toggleAssetSelection(asset.id, checked)"
@click.stop
/>
After:
<Checkbox
v-model="selectedAssets[asset.id]"
@click.stop
/>
4. Helper Methods
New method to get selected IDs:
const getSelectedAssetIds = () => {
return Object.keys(selectedAssets.value)
.filter(id => selectedAssets.value[Number(id)])
.map(id => Number(id));
};
Simplified toggleSelectAll:
const toggleSelectAll = (checked: boolean) => {
filteredAssets.value.forEach(asset => {
selectedAssets.value[asset.id] = checked;
});
};
5. Row Selection Logic
Updated to work with object-based state:
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
- Direct v-model Binding: The shadcn-vue Checkbox (based on reka-ui) supports
v-modelnatively when bound to a boolean value (reka-ui issue #1017) - Simpler Code: No need for manual event handlers - v-model handles everything
- Better Performance: Direct property access (
selectedAssets[id]) is faster than array operations (.includes(),.push(),.splice()) - More Reactive: Vue's reactivity system handles object property changes efficiently
- Cleaner API: The checkbox state is directly tied to the data structure
- 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:
- Open
frontend/test-asset-selection.htmlin a browser - Try selecting individual checkboxes
- Try the "Select All" checkbox
- Try the action buttons (Select All, Deselect All, Select First 3)
- 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, the correct syntax is:
- ✅ Use
v-model(notv-model:checked) - ✅ Works with boolean values directly
- ✅ No need for
:checked+@update:checkedpatterns
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.