Jump to content

Recommended Posts

Posted
11 hours ago, kkelchev said:

Hi 

How to do quick search with keyboard in TuniListBox.

Thanks Kamen

 

qc1SY0u.gif

 

// Функция, которая выполняется после рендеринга компонента TUniListBox
function afterrender(sender, eOpts)
{
    var me = sender;
    
    // Проверяем, не было ли уже добавлено поле поиска
    if (me._searchFieldAdded) {
        return;
    }
    me._searchFieldAdded = true;
    
    // Находим основной элемент boundlist
    var boundlist = me.el;
    
    // Находим контейнер для элементов списка и сам список
    var listWrap = boundlist.down('.x-boundlist-list-ct');
    var listEl = boundlist.down('.x-list-plain');
    
    // Отключаем прокрутку в listWrap (чтобы не было двойной прокрутки)
    if (listWrap) {
        listWrap.setStyle('overflow', 'hidden');
    }
    
    // Создаем контейнер для поиска с кнопкой очистки
    var searchContainer = Ext.DomHelper.createDom({
        tag: 'div',
        cls: 'x-boundlist-search-container',
        style: 'border-bottom: 1px solid #d0d0d0; background: #f5f5f5; padding: 8px; width: 100%; box-sizing: border-box; flex-shrink: 0;',
        children: [{
            tag: 'div',
            style: 'position: relative; width: 100%;',
            children: [{
                tag: 'input',
                type: 'text',
                id: me.getId() + '_searchField',
                placeholder: 'Поиск...', 
                style: 'width: 100%; padding: 8px 30px 8px 8px; box-sizing: border-box; border: 1px solid #c0c0c0; border-radius: 4px; outline: none; font-size: 14px;'
            }, {
                tag: 'span',
                id: me.getId() + '_clearBtn',
                style: 'position: absolute; right: 8px; top: 50%; transform: translateY(-50%); cursor: pointer; font-size: 20px; color: #999; display: none; line-height: 1;',
                html: '×' // Символ "×" для кнопки очистки
            }]
        }]
    });
    
    // Вставляем контейнер поиска перед списком элементов
    if (listEl) {
        listWrap.dom.insertBefore(searchContainer, listEl.dom);
    } else {
        listWrap.dom.insertBefore(searchContainer, listWrap.dom.firstChild);
    }
    
    var searchField = Ext.get(me.getId() + '_searchField');
    var clearBtn = Ext.get(me.getId() + '_clearBtn');
    
    // Добавляем CSS стили для исправления различных проблем
    var style = document.createElement('style');
    style.textContent = `
        .x-boundlist-item:focus, .x-boundlist-item:active {
            outline: none !important;
        }
        .x-boundlist-item {
            outline: none !important;
        }
        .x-boundlist-floating {
            outline: none !important;
            overflow: hidden !important;
        }
        .x-boundlist-list-ct {
            overflow: hidden !important;
            display: flex !important;
            flex-direction: column !important;
            padding: 0 !important;
            height: 100% !important;
        }
        .x-list-plain {
            overflow-y: auto !important;
            overflow-x: hidden !important;
            width: 100% !important;
            box-sizing: border-box !important;
            flex: 1 !important;
            margin: 0 !important;
        }
        .x-boundlist-item {
            width: 100% !important;
            box-sizing: border-box !important;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .x-boundlist-search-container {
            width: 100%;
            box-sizing: border-box;
            flex-shrink: 0;
        }
    `;
    document.head.appendChild(style);
    
    // Функция фильтрации списка
    var filterList = function() {
        var searchText = searchField.dom.value.toLowerCase().trim();
        
        // Показываем/скрываем кнопку очистки
        if (searchText.length > 0) {
            clearBtn.setStyle('display', 'block');
        } else {
            clearBtn.setStyle('display', 'none');
        }
        
        // Получаем все элементы списка
        var items = listEl ? listEl.query('.x-boundlist-item') : [];
        
        // Если поле поиска пустое, показываем все элементы
        if (searchText === '') {
            items.forEach(function(item) {
                item.style.display = '';
            });
            return;
        }
        
        // Фильтруем элементы по тексту
        items.forEach(function(item) {
            var itemText = (item.textContent || item.innerText).toLowerCase();
            if (itemText.indexOf(searchText) > -1) {
                item.style.display = '';
            } else {
                item.style.display = 'none';
            }
        });
    };

    // Обработчик ввода текста для фильтрации
    searchField.on('keyup', filterList);
    
    // Обработчик клика по кнопке очистки
    clearBtn.on('click', function() {
        searchField.dom.value = '';
        clearBtn.setStyle('display', 'none');
        filterList();
        searchField.focus();
    });
    
    // Устанавливаем фокус на поле поиска
    setTimeout(function() {
        if (searchField) {
            searchField.focus();
        }
    }, 200);
    
    // Функция точной настройки высоты
    var adjustHeight = function() {
        setTimeout(function() {
            if (!me.rendered) return;
            
            var searchHeight = Ext.fly(searchContainer).getHeight() || 49;
            var totalHeight = me.getHeight();
            
            // Учитываем толщину границы при расчете высоты
            var borderWidth = 0;
            var boundlistStyle = window.getComputedStyle(boundlist.dom);
            if (boundlistStyle.borderWidth) {
                borderWidth = parseInt(boundlistStyle.borderTopWidth) + parseInt(boundlistStyle.borderBottomWidth);
            }
            
            var availableHeight = totalHeight - borderWidth;
            var listHeight = availableHeight - searchHeight;
            
            // Настраиваем высоту listWrap
            if (listWrap) {
                listWrap.setStyle('height', availableHeight + 'px');
                listWrap.setStyle('maxHeight', availableHeight + 'px');
            }
            
            // Настраиваем высоту listEl
            if (listEl) {
                listEl.setStyle('height', listHeight + 'px');
                listEl.setStyle('maxHeight', listHeight + 'px');
                listEl.setStyle('overflow-y', 'auto');
                listEl.setStyle('overflow-x', 'hidden');
            }
            
            // Убеждаемся, что нет пустого пространства
            boundlist.setStyle('overflow', 'hidden');
            
        }, 50);
    };
    
    // Выполняем настройку высоты после полного рендеринга
    setTimeout(adjustHeight, 250);
    me.on('resize', adjustHeight);
    
    // Перенастраиваем высоту при клике
    me.on('click', function() {
        setTimeout(adjustHeight, 10);
    });
    
    // Перенастраиваем высоту после полного отображения
    setTimeout(adjustHeight, 500);
}


This is very important.

procedure TMainForm.UniListBox1Click(Sender: TObject);
begin
 if UniListBox1.ItemIndex <> -1 then
   Caption := UniListBox1.Items.Strings[UniListBox1.ItemIndex];
end;

 

  • Upvote 1
Posted
1 hour ago, kkelchev said:

Спасибо ещё раз.

Подтверждено.

Работает идеально.

Пожалуйста, используйте этот код, я исправил автоматический фокус.

 

function afterrender(sender, eOpts)
{
    var me = sender;
    
    // Проверяем, не было ли уже добавлено поле поиска
    if (me._searchFieldAdded) {
        return;
    }
    me._searchFieldAdded = true;
    
    // Находим основной элемент boundlist
    var boundlist = me.el;
    
    // Находим контейнер для элементов списка и сам список
    var listWrap = boundlist.down('.x-boundlist-list-ct');
    var listEl = boundlist.down('.x-list-plain');
    
    // Отключаем прокрутку в listWrap
    if (listWrap) {
        listWrap.setStyle('overflow', 'hidden');
    }
    
    // Создаем уникальные ID для элементов
    var searchFieldId = me.getId() + '_searchField';
    var clearBtnId = me.getId() + '_clearBtn';
    
    // Создаем контейнер для поиска с кнопкой очистки
    var searchContainer = Ext.DomHelper.createDom({
        tag: 'div',
        cls: 'x-boundlist-search-container',
        style: 'border-bottom: 1px solid #d0d0d0; background: #f5f5f5; padding: 8px; width: 100%; box-sizing: border-box; flex-shrink: 0;',
        children: [{
            tag: 'div',
            style: 'position: relative; width: 100%;',
            children: [{
                tag: 'input',
                type: 'text',
                id: searchFieldId,
                placeholder: 'Поиск...',
                style: 'width: 100%; padding: 5px 30px 5px 8px; box-sizing: border-box; border: 1px solid #c0c0c0; border-radius: 4px; outline: none; font-size: 13px;'
            }, {
                tag: 'span',
                id: clearBtnId,
                style: 'position: absolute; right: 8px; top: 50%; transform: translateY(-50%); cursor: pointer; font-size: 20px; color: #999; display: none; line-height: 1;',
                html: '&times;' // Символ крестика для кнопки очистки
            }]
        }]
    });
    
    // Вставляем контейнер поиска в начало списка
    if (listEl) {
        listWrap.dom.insertBefore(searchContainer, listEl.dom);
    } else {
        listWrap.dom.insertBefore(searchContainer, listWrap.dom.firstChild);
    }
    
    // Получаем ссылки на созданные DOM-элементы
    var searchField = document.getElementById(searchFieldId);
    var clearBtn = document.getElementById(clearBtnId);
    
    // Если элементы не найдены, выходим из функции
    if (!searchField || !clearBtn) return;
    
    // Добавляем CSS стили для правильного отображения компонентов
    var style = document.createElement('style');
    style.textContent = `
        /* Убираем фокус с элементов списка */
        .x-boundlist-item:focus, .x-boundlist-item:active {
            outline: none !important;
        }
        
        /* Настройки контейнера списка */
        .x-boundlist-floating {
            outline: none !important;
            overflow: hidden !important;
        }
        
        /* Настройки контейнера с элементами */
        .x-boundlist-list-ct {
            overflow: hidden !important;
            display: flex !important;
            flex-direction: column !important;
            padding: 0 !important;
            height: 100% !important;
        }
        
        /* Настройки списка элементов */
        .x-list-plain {
            overflow-y: auto !important;
            overflow-x: hidden !important;
            width: 100% !important;
            box-sizing: border-box !important;
            flex: 1 !important;
            margin: 0 !important;
        }
        
        /* Контейнер поиска */
        .x-boundlist-search-container {
            width: 100%;
            box-sizing: border-box;
            flex-shrink: 0;
        }
        
        /* Убираем подсветку фокуса у boundlist */
        .x-boundlist:focus {
            outline: none !important;
        }
    `;
    document.head.appendChild(style);
    
    /**
     * Функция фильтрации элементов списка
     * Скрывает элементы, не соответствующие тексту поиска
     */
    var filterList = function() {
        var searchText = searchField.value.toLowerCase().trim();
        
        // Показываем или скрываем кнопку очистки
        if (searchText.length > 0) {
            clearBtn.style.display = 'block';
        } else {
            clearBtn.style.display = 'none';
        }
        
        // Получаем все элементы списка
        var items = listEl ? listEl.query('.x-boundlist-item') : [];
        
        // Если строка поиска пуста, показываем все элементы
        if (searchText === '') {
            items.forEach(function(item) {
                item.style.display = '';
            });
            return;
        }
        
        // Фильтруем элементы по тексту
        items.forEach(function(item) {
            var itemText = (item.textContent || item.innerText).toLowerCase();
            if (itemText.indexOf(searchText) > -1) {
                item.style.display = ''; // Показываем, если текст совпадает
            } else {
                item.style.display = 'none'; // Скрываем, если не совпадает
            }
        });
    };
    
    // Предотвращаем всплытие событий для поля ввода
    // Используем capturing phase, чтобы перехватить события до их обработки ExtJS
    searchField.addEventListener('mousedown', function(e) {
        e.stopPropagation(); // Останавливаем всплытие события
    }, true);
    
    // Обработчик клика на поле поиска
    searchField.addEventListener('click', function(e) {
        e.stopPropagation(); // Останавливаем всплытие
        this.focus(); // Устанавливаем фокус на поле
    }, true);
    
    // Основной обработчик ввода текста для фильтрации
    searchField.addEventListener('input', filterList);
    
    // Дополнительный обработчик для надежности
    searchField.addEventListener('keyup', filterList);
    
    // Обработчик для кнопки очистки поля поиска
    clearBtn.addEventListener('mousedown', function(e) {
        e.stopPropagation(); // Останавливаем всплытие
        e.preventDefault(); // Отменяем стандартное поведение
        
        searchField.value = ''; // Очищаем поле
        clearBtn.style.display = 'none'; // Скрываем кнопку очистки
        filterList(); // Обновляем фильтрацию (показываем все элементы)
        searchField.focus(); // Возвращаем фокус на поле поиска
    }, true);
    
    // Глобальный обработчик для всего boundlist
    // Предотвращает закрытие списка при клике на поле поиска или кнопку очистки
    boundlist.dom.addEventListener('mousedown', function(e) {
        // Если клик был по полю ввода или кнопке очистки, ничего не делаем
        // Позволяем событиям обрабатываться нормально
        if (e.target === searchField || e.target === clearBtn || 
            searchField.contains(e.target) || clearBtn.contains(e.target)) {
            return;
        }
    }, true);
    
    /**
     * Функция принудительной установки фокуса на поле поиска
     * Использует рекурсивные попытки с задержкой
     * @param {number} attempts - количество попыток
     */
    var forceFocus = function(attempts) {
        if (!attempts) attempts = 0;
        
        setTimeout(function() {
            if (searchField && document.body.contains(searchField)) {
                // Проверяем, видим ли элемент и доступен ли он
                var style = window.getComputedStyle(searchField);
                if (style.display !== 'none' && style.visibility !== 'hidden' && !searchField.disabled) {
                    try {
                        searchField.focus(); // Пытаемся установить фокус
                        
                        // Проверяем, удалось ли установить фокус
                        setTimeout(function() {
                            if (document.activeElement !== searchField && attempts < 10) {
                                forceFocus(attempts + 1); // Повторяем попытку
                            }
                        }, 50);
                    } catch(e) {
                        if (attempts < 10) {
                            forceFocus(attempts + 1); // Повторяем при ошибке
                        }
                    }
                } else if (attempts < 10) {
                    forceFocus(attempts + 1); // Повторяем, если элемент невидим
                }
            }
        }, 100);
    };
    
    // Запускаем несколько попыток установки фокуса с разными задержками
    setTimeout(function() { forceFocus(0); }, 200);
    setTimeout(function() { forceFocus(0); }, 500);
    setTimeout(function() { forceFocus(0); }, 1000);
    
    /**
     * Функция точной настройки высоты компонентов
     * Пересчитывает высоту списка с учетом поля поиска
     */
    var adjustHeight = function() {
        setTimeout(function() {
            if (!me.rendered) return;
            
            // Получаем высоту поля поиска
            var searchHeight = searchContainer.offsetHeight || 49;
            var totalHeight = me.getHeight(); // Общая высота boundlist
            
            // Учитываем толщину границы
            var borderWidth = 0;
            var boundlistStyle = window.getComputedStyle(boundlist.dom);
            if (boundlistStyle.borderWidth) {
                borderWidth = parseInt(boundlistStyle.borderTopWidth) + parseInt(boundlistStyle.borderBottomWidth);
            }
            
            // Доступная высота для контейнера списка
            var availableHeight = totalHeight - borderWidth;
            // Высота для списка элементов (общая минус поле поиска)
            var listHeight = availableHeight - searchHeight;
            
            // Устанавливаем высоту контейнера списка
            if (listWrap) {
                listWrap.dom.style.height = availableHeight + 'px';
                listWrap.dom.style.maxHeight = availableHeight + 'px';
            }
            
            // Устанавливаем высоту для списка элементов
            if (listEl) {
                listEl.dom.style.height = listHeight + 'px';
                listEl.dom.style.maxHeight = listHeight + 'px';
                listEl.dom.style.overflowY = 'auto';
                listEl.dom.style.overflowX = 'hidden';
            }
            
            // Отключаем прокрутку основного контейнера
            boundlist.dom.style.overflow = 'hidden';
            
        }, 50);
    };
    
    // Первоначальная настройка высоты
    setTimeout(adjustHeight, 250);
    
    // Используем ResizeObserver для отслеживания изменения размеров
    var resizeObserver = new ResizeObserver(function() {
        adjustHeight(); // Пересчитываем высоту при изменении размеров
    });
    
    if (boundlist.dom) {
        resizeObserver.observe(boundlist.dom); // Начинаем наблюдение
    }
    
    // Обработчики событий списка
    // При раскрытии списка
    me.on('expand', function() {
        setTimeout(function() { 
            adjustHeight(); // Корректируем высоту
            forceFocus(0); // Пытаемся установить фокус
        }, 150);
    });
    
    // При показе списка
    me.on('show', function() {
        setTimeout(function() { 
            adjustHeight(); // Корректируем высоту
            forceFocus(0); // Пытаемся установить фокус
        }, 150);
    });
}

 

Posted
14 hours ago, kkelchev said:

How to do quick search with keyboard in TuniListBox.

I thought you meant something like Windows Explorer behavior, where typing characters moves the selection to the first matching folder or file.

Posted
On 3/13/2026 at 2:35 PM, Sherzod said:

I thought you meant something like Windows Explorer behavior, where typing characters moves the selection to the first matching folder or file.

 

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...