Fix search bar behaviour (step 1)
This commit is contained in:
parent
4e5274c6f0
commit
e588df9fca
@ -94,7 +94,7 @@
|
||||
:data-vm-name="vm.name"
|
||||
class="vm-list-item"
|
||||
:class="{ 'active': selectedVM === vm.name || vmFromUrl === vm.name }"
|
||||
@click="() => { console.log('VM clicked:', vm); handleVMSelect(vm); }"
|
||||
@click="handleVMSelect(vm)"
|
||||
>
|
||||
<div v-if="selectedVM === vm.name || vmFromUrl === vm.name" class="active-indicator"></div>
|
||||
<div class="vm-item-content">
|
||||
@ -140,15 +140,6 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
// Direct debug log of props
|
||||
console.log('VMSearchBar props:', {
|
||||
modelValue: props.modelValue,
|
||||
showList: props.showList,
|
||||
selectedVM: props.selectedVM,
|
||||
vmFromUrl: props.vmFromUrl,
|
||||
vmListLength: props.vmList.length
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'search',
|
||||
@ -161,71 +152,187 @@ const emit = defineEmits([
|
||||
'reset-filters'
|
||||
]);
|
||||
|
||||
// UI state refs
|
||||
const showFilterMenu = ref(false);
|
||||
const filterDropdown = ref(null);
|
||||
const filterMenu = ref(null);
|
||||
const controlsBar = ref(null);
|
||||
const vmListContainer = ref(null);
|
||||
|
||||
// Add local state for filters
|
||||
// Search state refs
|
||||
const inputValue = ref('');
|
||||
const searchText = ref('');
|
||||
const isFilterActive = ref(false);
|
||||
|
||||
// Filter state
|
||||
const appliedFilters = ref({
|
||||
states: {},
|
||||
nodes: {}
|
||||
});
|
||||
|
||||
// Add new ref for tracking search active state
|
||||
const searchActive = ref(false);
|
||||
// Initialize the component
|
||||
onMounted(() => {
|
||||
// Set up click outside handler
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
|
||||
// Initialize input value based on selected VM
|
||||
if (!props.showList && (props.selectedVM || props.vmFromUrl)) {
|
||||
inputValue.value = props.selectedVM || props.vmFromUrl;
|
||||
} else if (props.showList && props.modelValue) {
|
||||
inputValue.value = props.modelValue;
|
||||
searchText.value = props.modelValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Local ref to track input value
|
||||
const inputValue = ref('');
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
});
|
||||
|
||||
// Add a ref to the VM list container
|
||||
const vmListContainer = ref(null);
|
||||
// Handle search input
|
||||
const handleSearch = (event) => {
|
||||
const value = event.target.value;
|
||||
inputValue.value = value;
|
||||
searchText.value = value;
|
||||
isFilterActive.value = true;
|
||||
|
||||
emit('update:modelValue', value);
|
||||
emit('search', value);
|
||||
};
|
||||
|
||||
// Add a ref to store the last search query
|
||||
const lastSearchQuery = ref('');
|
||||
|
||||
// Add a flag to track if filtering should be applied
|
||||
const filterActive = ref(false);
|
||||
|
||||
// Add a flag to track if a search has been performed
|
||||
const hasSearched = ref(false);
|
||||
|
||||
// Add a flag to track if we should maintain focus after list refresh
|
||||
const maintainFocusAfterRefresh = ref(false);
|
||||
|
||||
// Watch both showList and selectedVM to update input value
|
||||
watch([() => props.showList, () => props.selectedVM, () => props.vmFromUrl, () => props.modelValue],
|
||||
([showList, selectedVM, vmFromUrl, modelValue]) => {
|
||||
console.log('Watch triggered:', { showList, selectedVM, vmFromUrl, modelValue });
|
||||
// Handle focus on search input
|
||||
const handleFocus = (event) => {
|
||||
// If the list is not shown, show it
|
||||
if (!props.showList) {
|
||||
emit('toggle-list');
|
||||
} else {
|
||||
// If the list is already shown, activate filtering
|
||||
isFilterActive.value = true;
|
||||
|
||||
if (!showList) {
|
||||
// When list is closed, show selected VM or VM from URL
|
||||
const effectiveVM = selectedVM || vmFromUrl;
|
||||
if (effectiveVM) {
|
||||
inputValue.value = effectiveVM;
|
||||
}
|
||||
} else {
|
||||
// When list is open, show search query if it exists
|
||||
if (modelValue) {
|
||||
inputValue.value = modelValue;
|
||||
searchActive.value = true;
|
||||
} else {
|
||||
// If no search query, show placeholder
|
||||
inputValue.value = '';
|
||||
}
|
||||
// Restore search text if available
|
||||
if (searchText.value) {
|
||||
inputValue.value = searchText.value;
|
||||
emit('update:modelValue', searchText.value);
|
||||
}
|
||||
}, { immediate: true }
|
||||
);
|
||||
|
||||
// Watch modelValue to update input when searching
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal) {
|
||||
lastSearchQuery.value = newVal;
|
||||
}
|
||||
|
||||
emit('focus', event);
|
||||
};
|
||||
|
||||
// Handle blur on search input
|
||||
const handleBlur = (event) => {
|
||||
emit('blur', event);
|
||||
};
|
||||
|
||||
// Handle click on search input
|
||||
const handleSearchClick = () => {
|
||||
if (props.showList) {
|
||||
inputValue.value = newVal || '';
|
||||
// When clicking the search input while list is open, activate filtering
|
||||
isFilterActive.value = true;
|
||||
|
||||
// Restore search text if available
|
||||
if (searchText.value && searchText.value !== inputValue.value) {
|
||||
inputValue.value = searchText.value;
|
||||
emit('update:modelValue', searchText.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle the VM list
|
||||
const toggleList = () => {
|
||||
if (props.showList) {
|
||||
// If we're closing the list, save the search text
|
||||
if (props.modelValue) {
|
||||
searchText.value = props.modelValue;
|
||||
}
|
||||
} else {
|
||||
// If we're opening the list, deactivate filtering
|
||||
isFilterActive.value = false;
|
||||
|
||||
// Restore search text in the input, but don't apply filtering
|
||||
if (searchText.value) {
|
||||
inputValue.value = searchText.value;
|
||||
emit('update:modelValue', searchText.value);
|
||||
}
|
||||
}
|
||||
|
||||
emit('toggle-list');
|
||||
};
|
||||
|
||||
// Clear search
|
||||
const clearSearch = () => {
|
||||
inputValue.value = '';
|
||||
searchText.value = '';
|
||||
isFilterActive.value = false;
|
||||
emit('update:modelValue', '');
|
||||
emit('clear');
|
||||
};
|
||||
|
||||
// Select a VM
|
||||
const handleVMSelect = (vm) => {
|
||||
console.log('Selecting VM:', vm);
|
||||
emit('select-vm', vm);
|
||||
};
|
||||
|
||||
// Handle click outside
|
||||
const handleClickOutside = (event) => {
|
||||
if (showFilterMenu.value &&
|
||||
filterMenu.value &&
|
||||
!filterMenu.value.contains(event.target) &&
|
||||
!filterDropdown.value.contains(event.target)) {
|
||||
showFilterMenu.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle filter menu
|
||||
const toggleFilterMenu = () => {
|
||||
showFilterMenu.value = !showFilterMenu.value;
|
||||
};
|
||||
|
||||
// Filter toggle
|
||||
const handleFilterToggle = (type, value) => {
|
||||
appliedFilters.value[type][value] = !appliedFilters.value[type][value];
|
||||
};
|
||||
|
||||
// Reset filters
|
||||
const handleResetFilters = () => {
|
||||
Object.keys(appliedFilters.value.states).forEach(state => {
|
||||
appliedFilters.value.states[state] = false;
|
||||
});
|
||||
Object.keys(appliedFilters.value.nodes).forEach(node => {
|
||||
appliedFilters.value.nodes[node] = false;
|
||||
});
|
||||
showFilterMenu.value = false;
|
||||
};
|
||||
|
||||
// Computed properties
|
||||
const filteredVMs = computed(() => {
|
||||
let filtered = [...props.vmList];
|
||||
|
||||
// Apply text filter only if filtering is active
|
||||
if (isFilterActive.value && props.modelValue) {
|
||||
const query = props.modelValue.toLowerCase();
|
||||
filtered = filtered.filter(vm =>
|
||||
vm.name.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply state filters
|
||||
const activeStates = Object.entries(appliedFilters.value.states)
|
||||
.filter(([_, isActive]) => isActive)
|
||||
.map(([state]) => state);
|
||||
if (activeStates.length > 0) {
|
||||
filtered = filtered.filter(vm => activeStates.includes(vm.state));
|
||||
}
|
||||
|
||||
// Apply node filters
|
||||
const activeNodes = Object.entries(appliedFilters.value.nodes)
|
||||
.filter(([_, isActive]) => isActive)
|
||||
.map(([node]) => node);
|
||||
if (activeNodes.length > 0) {
|
||||
filtered = filtered.filter(vm => activeNodes.includes(vm.node));
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
|
||||
// Calculate available states from vmList
|
||||
@ -234,23 +341,7 @@ const availableStates = computed(() => {
|
||||
props.vmList.forEach(vm => {
|
||||
if (vm.state) states.add(vm.state);
|
||||
});
|
||||
const statesArray = Array.from(states);
|
||||
|
||||
// Remove 'start' if it exists
|
||||
const startIndex = statesArray.indexOf('start');
|
||||
if (startIndex !== -1) {
|
||||
statesArray.splice(startIndex, 1);
|
||||
}
|
||||
|
||||
// Sort remaining states
|
||||
statesArray.sort();
|
||||
|
||||
// Add 'start' back at beginning if it was present
|
||||
if (startIndex !== -1) {
|
||||
statesArray.unshift('start');
|
||||
}
|
||||
|
||||
return statesArray;
|
||||
return Array.from(states).sort();
|
||||
});
|
||||
|
||||
// Calculate available nodes from vmList
|
||||
@ -279,24 +370,7 @@ watch([availableStates, availableNodes], ([states, nodes]) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Update handleFilterToggle to work with local state
|
||||
const handleFilterToggle = (type, value) => {
|
||||
searchActive.value = true;
|
||||
appliedFilters.value[type][value] = !appliedFilters.value[type][value];
|
||||
};
|
||||
|
||||
// Update handleResetFilters
|
||||
const handleResetFilters = () => {
|
||||
Object.keys(appliedFilters.value.states).forEach(state => {
|
||||
appliedFilters.value.states[state] = false;
|
||||
});
|
||||
Object.keys(appliedFilters.value.nodes).forEach(node => {
|
||||
appliedFilters.value.nodes[node] = false;
|
||||
});
|
||||
showFilterMenu.value = false;
|
||||
};
|
||||
|
||||
// Computed
|
||||
// Count active filters
|
||||
const activeFiltersCount = computed(() => {
|
||||
let count = 0;
|
||||
Object.values(appliedFilters.value.states).forEach(isActive => {
|
||||
@ -308,307 +382,47 @@ const activeFiltersCount = computed(() => {
|
||||
return count;
|
||||
});
|
||||
|
||||
const filteredVMs = computed(() => {
|
||||
let filtered = [...props.vmList];
|
||||
|
||||
// Apply search filter if filtering is active OR if a search has been performed
|
||||
if ((filterActive.value || hasSearched.value) && props.modelValue) {
|
||||
const query = props.modelValue.toLowerCase();
|
||||
filtered = filtered.filter(vm =>
|
||||
vm.name.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
// Only apply other filters if search is active
|
||||
if (searchActive.value) {
|
||||
// Apply state filters
|
||||
const activeStates = Object.entries(appliedFilters.value.states)
|
||||
.filter(([_, isActive]) => isActive)
|
||||
.map(([state]) => state);
|
||||
if (activeStates.length > 0) {
|
||||
filtered = filtered.filter(vm => activeStates.includes(vm.state));
|
||||
}
|
||||
|
||||
// Apply node filters
|
||||
const activeNodes = Object.entries(appliedFilters.value.nodes)
|
||||
.filter(([_, isActive]) => isActive)
|
||||
.map(([node]) => node);
|
||||
if (activeNodes.length > 0) {
|
||||
filtered = filtered.filter(vm => activeNodes.includes(vm.node));
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const handleSearch = (event) => {
|
||||
// Always update the model value to preserve search
|
||||
searchActive.value = true;
|
||||
filterActive.value = true; // Activate filtering when typing in search
|
||||
lastSearchQuery.value = event.target.value;
|
||||
hasSearched.value = true; // Mark that a search has been performed
|
||||
maintainFocusAfterRefresh.value = true; // Maintain focus after refresh
|
||||
emit('update:modelValue', event.target.value);
|
||||
emit('search', event.target.value);
|
||||
};
|
||||
|
||||
const handleFocus = (event) => {
|
||||
searchActive.value = true;
|
||||
filterActive.value = true; // Activate filtering when focusing the search bar
|
||||
maintainFocusAfterRefresh.value = true; // Maintain focus after refresh
|
||||
|
||||
// If there's a saved search, make sure it's applied
|
||||
if (props.modelValue) {
|
||||
inputValue.value = props.modelValue;
|
||||
|
||||
// Force a re-evaluation of the filteredVMs computed property
|
||||
nextTick(() => {
|
||||
// This is a hack to force Vue to re-evaluate the computed property
|
||||
const temp = filteredVMs.value.length;
|
||||
console.log('Forcing filter application, filtered count:', temp);
|
||||
});
|
||||
}
|
||||
emit('focus', event);
|
||||
};
|
||||
|
||||
const handleBlur = (event) => {
|
||||
// Only deactivate filtering if no search has been performed
|
||||
if (!hasSearched.value) {
|
||||
filterActive.value = false;
|
||||
}
|
||||
|
||||
// Check if the blur was caused by a click outside the component
|
||||
const isClickOutside = !controlsBar.value?.contains(event.relatedTarget);
|
||||
if (isClickOutside) {
|
||||
maintainFocusAfterRefresh.value = false;
|
||||
}
|
||||
|
||||
emit('blur', event);
|
||||
};
|
||||
|
||||
const clearSearch = () => {
|
||||
emit('update:modelValue', '');
|
||||
emit('clear');
|
||||
};
|
||||
|
||||
const toggleList = () => {
|
||||
console.log('toggleList called, showList:', props.showList);
|
||||
|
||||
// If we're opening the list (currently closed)
|
||||
if (!props.showList) {
|
||||
// When opening the list via List VMs button, don't apply filtering initially
|
||||
// Only deactivate filtering if no search has been performed
|
||||
if (!hasSearched.value) {
|
||||
filterActive.value = false;
|
||||
}
|
||||
|
||||
// But always restore the previous search text
|
||||
if (lastSearchQuery.value) {
|
||||
// Ensure the model value is preserved
|
||||
if (props.modelValue !== lastSearchQuery.value) {
|
||||
emit('update:modelValue', lastSearchQuery.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we're closing the list (currently open)
|
||||
else {
|
||||
// When closing, save the current search
|
||||
if (props.modelValue) {
|
||||
lastSearchQuery.value = props.modelValue;
|
||||
// Don't clear the model value when closing
|
||||
}
|
||||
}
|
||||
|
||||
emit('toggle-list');
|
||||
};
|
||||
|
||||
const handleVMSelect = (vm) => {
|
||||
console.log('Selecting VM:', vm);
|
||||
|
||||
// Don't clear the search query or change filter state
|
||||
// Just emit the select-vm event
|
||||
emit('select-vm', vm);
|
||||
};
|
||||
|
||||
const toggleFilterMenu = () => {
|
||||
showFilterMenu.value = !showFilterMenu.value;
|
||||
};
|
||||
|
||||
// Status class helper
|
||||
const getStatusClass = (state) => {
|
||||
if (!state) return '';
|
||||
|
||||
if (!state) return 'status-unknown';
|
||||
switch(state.toLowerCase()) {
|
||||
case 'start':
|
||||
return 'status-running';
|
||||
case 'stop':
|
||||
return 'status-stopped';
|
||||
case 'disable':
|
||||
return 'status-disabled';
|
||||
default:
|
||||
return '';
|
||||
case 'start': return 'status-running';
|
||||
case 'stop': return 'status-stopped';
|
||||
case 'disable': return 'status-paused';
|
||||
default: return 'status-unknown';
|
||||
}
|
||||
};
|
||||
|
||||
// Update click outside handling to close both filter menu and list
|
||||
const handleClickOutside = (event) => {
|
||||
// If click is outside the controls bar
|
||||
if (!controlsBar.value?.contains(event.target)) {
|
||||
if (props.showList) {
|
||||
emit('toggle-list'); // Close the list
|
||||
}
|
||||
showFilterMenu.value = false; // Close filter menu
|
||||
}
|
||||
// If click is inside controls bar but outside filter dropdown
|
||||
else if (showFilterMenu.value && !filterDropdown.value?.contains(event.target)) {
|
||||
showFilterMenu.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
|
||||
// Add click handler for search input
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('click', () => {
|
||||
if (props.showList && props.modelValue) {
|
||||
filterActive.value = true;
|
||||
applyFilter();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
});
|
||||
|
||||
// Add a watcher for selectedVM with a debug log
|
||||
watch(() => props.selectedVM, (newVal, oldVal) => {
|
||||
console.log('VMSearchBar selectedVM changed:', { newVal, oldVal });
|
||||
// Update input value when selectedVM changes
|
||||
if (!props.showList && newVal) {
|
||||
inputValue.value = newVal;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// Add a function to scroll to the selected VM
|
||||
const scrollToSelectedVM = () => {
|
||||
if (!props.showList || (!props.selectedVM && !props.vmFromUrl)) return;
|
||||
|
||||
nextTick(() => {
|
||||
const selectedVMName = props.selectedVM || props.vmFromUrl;
|
||||
const activeElement = document.querySelector(`.vm-list-item[data-vm-name="${selectedVMName}"]`);
|
||||
if (activeElement && vmListContainer.value) {
|
||||
activeElement.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Update the watch for showList to handle search state
|
||||
watch(() => props.showList, (isOpen) => {
|
||||
if (isOpen) {
|
||||
// When list opens, restore the last search query
|
||||
nextTick(() => {
|
||||
if (lastSearchQuery.value) {
|
||||
inputValue.value = lastSearchQuery.value;
|
||||
|
||||
// If the user clicked on the search bar to open it, apply the filter
|
||||
if (document.activeElement &&
|
||||
document.activeElement.classList.contains('search-input')) {
|
||||
filterActive.value = true;
|
||||
applyFilter();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also scroll to selected VM
|
||||
scrollToSelectedVM();
|
||||
} else {
|
||||
// When list closes, show the selected VM but preserve search
|
||||
// Watch for changes in showList
|
||||
watch(() => props.showList, (isVisible) => {
|
||||
if (!isVisible) {
|
||||
// When list is closed, show selected VM
|
||||
const effectiveVM = props.selectedVM || props.vmFromUrl;
|
||||
if (effectiveVM) {
|
||||
inputValue.value = effectiveVM;
|
||||
}
|
||||
// Reset filter active state when closing
|
||||
filterActive.value = false;
|
||||
} else {
|
||||
// When list is opened, show search text if available
|
||||
if (searchText.value) {
|
||||
inputValue.value = searchText.value;
|
||||
} else {
|
||||
inputValue.value = '';
|
||||
}
|
||||
|
||||
// Only activate filtering if opened via search input, not List VMs button
|
||||
// This is handled in handleFocus and toggleList
|
||||
}
|
||||
});
|
||||
|
||||
// Also call it when selectedVM or vmFromUrl changes
|
||||
watch([() => props.selectedVM, () => props.vmFromUrl], () => {
|
||||
if (props.showList) {
|
||||
scrollToSelectedVM();
|
||||
}
|
||||
});
|
||||
|
||||
// Add a method to explicitly apply the filter
|
||||
const applyFilter = () => {
|
||||
filterActive.value = true;
|
||||
// Force computed property re-evaluation
|
||||
nextTick(() => {
|
||||
const temp = filteredVMs.value.length;
|
||||
console.log('Filter applied, filtered count:', temp);
|
||||
});
|
||||
};
|
||||
|
||||
// Update the watch for props.vmList to maintain focus
|
||||
watch(() => props.vmList, () => {
|
||||
// When VM list refreshes, ensure search is preserved
|
||||
if (props.showList && lastSearchQuery.value) {
|
||||
// Make sure the model value is preserved
|
||||
if (props.modelValue !== lastSearchQuery.value) {
|
||||
emit('update:modelValue', lastSearchQuery.value);
|
||||
}
|
||||
|
||||
// If a search has been performed, keep filtering active
|
||||
if (hasSearched.value && props.modelValue) {
|
||||
filterActive.value = true;
|
||||
|
||||
// If we should maintain focus, refocus the search input
|
||||
if (maintainFocusAfterRefresh.value) {
|
||||
nextTick(() => {
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
applyFilter();
|
||||
});
|
||||
// Watch for changes in selectedVM or vmFromUrl
|
||||
watch([() => props.selectedVM, () => props.vmFromUrl], ([selectedVM, vmFromUrl]) => {
|
||||
if (!props.showList) {
|
||||
const effectiveVM = selectedVM || vmFromUrl;
|
||||
if (effectiveVM) {
|
||||
inputValue.value = effectiveVM;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update handleSearchClick to set maintainFocusAfterRefresh
|
||||
const handleSearchClick = () => {
|
||||
if (props.showList) {
|
||||
// When clicking the search input while list is open, always apply filtering
|
||||
filterActive.value = true;
|
||||
maintainFocusAfterRefresh.value = true; // Maintain focus after refresh
|
||||
|
||||
// If there's a saved search, make sure it's applied to the model
|
||||
if (lastSearchQuery.value && !props.modelValue) {
|
||||
emit('update:modelValue', lastSearchQuery.value);
|
||||
}
|
||||
|
||||
// Force filter application
|
||||
applyFilter();
|
||||
}
|
||||
};
|
||||
|
||||
// Add a method to force focus on the search input
|
||||
const focusSearchInput = () => {
|
||||
nextTick(() => {
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
Loading…
x
Reference in New Issue
Block a user