|
function afterrender(sender, eOpts) { |
|
var me = sender; |
|
|
|
// === Labels (PT-BR text) === |
|
var LABEL_SELECT_ALL = 'Todos'; |
|
var LABEL_DESELECT_ALL = 'Nenhum'; |
|
var LABEL_SEARCH = 'Pesquisar...'; |
|
|
|
// === Picker limits === |
|
var MAX_PICKER_HEIGHT = 300; // px |
|
var MIN_PICKER_WIDTH = 300; // px (new) |
|
|
|
if (me._customHeaderAdded) { |
|
return; |
|
} |
|
me._customHeaderAdded = true; |
|
|
|
var showCount = 1; // 1 or 0 -> show selected count |
|
var showTotal = 1; // 1 or 0 -> show total count |
|
|
|
var _insertEditField = function () { |
|
var picker = me.getPicker && me.getPicker(); |
|
if (!picker || picker.destroyed || !picker.id || !picker.el) return; |
|
|
|
var _id = picker.id; |
|
me._customPickerId = _id; |
|
|
|
// --- Enforce minimum picker width (NEW) ------------------------------- |
|
var enforcePickerMinWidth = function () { |
|
try { |
|
var fieldW = (me.getWidth && me.getWidth()) || 0; |
|
if (picker && picker.setWidth && fieldW > 0 && fieldW < MIN_PICKER_WIDTH) { |
|
picker.setWidth(MIN_PICKER_WIDTH); |
|
picker.minWidth = MIN_PICKER_WIDTH; |
|
if (picker.updateLayout) picker.updateLayout(); |
|
} |
|
} catch (e) { /* silent */ } |
|
}; |
|
enforcePickerMinWidth(); |
|
if (picker.on) picker.on('show', enforcePickerMinWidth); |
|
if (me.on) me.on('resize', enforcePickerMinWidth); |
|
// --------------------------------------------------------------------- |
|
|
|
// --- Determine if an item is selected (define BEFORE using it) ------- |
|
var isItemSelected = function (item) { |
|
if (!item) return false; |
|
var checkbox = item.querySelector && item.querySelector('input[type="checkbox"]'); |
|
if (checkbox) return !!checkbox.checked; |
|
|
|
if (item.classList.contains('x-boundlist-selected') || |
|
item.classList.contains('x-checkbox-checked') || |
|
item.getAttribute('aria-selected') === 'true' || |
|
item.getAttribute('aria-checked') === 'true') { |
|
return true; |
|
} |
|
|
|
var styles = window.getComputedStyle(item); |
|
if (styles.backgroundColor && |
|
styles.backgroundColor !== 'transparent' && |
|
styles.backgroundColor !== 'rgba(0, 0, 0, 0)') { |
|
return true; |
|
} |
|
return false; |
|
}; |
|
// --------------------------------------------------------------------- |
|
|
|
var getSelectedCount = function () { |
|
var count = 0; |
|
var allItems = picker.el.query('.x-boundlist-item'); |
|
for (var i = 0; i < allItems.length; i++) { |
|
if (isItemSelected(allItems[i])) count++; |
|
} |
|
return count; |
|
}; |
|
|
|
var getTotalCount = function () { |
|
return picker.el.query('.x-boundlist-item').length; |
|
}; |
|
|
|
var updateSelectAllButtonText = function () { |
|
var selectAllButton = Ext.get(_id + '_selectAllButton'); |
|
if (!selectAllButton || !selectAllButton.dom) return; |
|
|
|
if (showCount === 1) { |
|
var selectedCount = getSelectedCount(); |
|
if (showTotal === 1) { |
|
var totalCount = getTotalCount(); |
|
selectAllButton.dom.innerHTML = LABEL_SELECT_ALL + '(' + selectedCount + '/' + totalCount + ')'; |
|
} else { |
|
selectAllButton.dom.innerHTML = LABEL_SELECT_ALL + '(' + selectedCount + ')'; |
|
} |
|
} else { |
|
selectAllButton.dom.innerHTML = LABEL_SELECT_ALL; |
|
} |
|
}; |
|
|
|
var selectAllFlex = (showCount === 1) ? '2' : '1'; |
|
var deselectAllFlex = '1'; |
|
|
|
// --- Build custom header (search + buttons) at the top of the picker --- |
|
var headerContainer = Ext.DomHelper.insertFirst(_id, { |
|
tag: 'div', |
|
cls: 'custom-header-container', |
|
style: 'border-bottom: 1px solid #ddd; background: white;', |
|
children: [{ |
|
tag: 'div', |
|
style: 'padding: 5px;', |
|
children: [{ |
|
tag: 'div', |
|
style: 'position: relative; margin-bottom: 5px;', |
|
children: [{ |
|
tag: 'input', |
|
type: 'text', |
|
id: _id + '_filterEdit', |
|
placeholder: LABEL_SEARCH, |
|
style: 'width: 100%; padding: 4px; padding-right: 25px; box-sizing: border-box;' |
|
}, { |
|
tag: 'div', |
|
html: '×', |
|
id: _id + '_clearButton', |
|
style: 'position: absolute; right: 10px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #999; font-size: 16px; font-weight: bold; display: none;' |
|
}] |
|
}, { |
|
tag: 'div', |
|
style: 'display: flex; justify-content: space-between; gap: 4px;', |
|
children: [{ |
|
tag: 'button', |
|
type: 'button', |
|
id: _id + '_selectAllButton', |
|
html: (showCount === 1) |
|
? ((showTotal === 1) ? LABEL_SELECT_ALL + '(0/0)' : LABEL_SELECT_ALL + '(0)') |
|
: LABEL_SELECT_ALL, |
|
style: 'flex: ' + selectAllFlex + '; min-width: 0; padding: 4px 2px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;' |
|
}, { |
|
tag: 'button', |
|
type: 'button', |
|
id: _id + '_deselectAllButton', |
|
html: LABEL_DESELECT_ALL, |
|
style: 'flex: ' + deselectAllFlex + '; min-width: 0; padding: 4px 2px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;' |
|
}] |
|
}] |
|
}] |
|
}); |
|
// ---------------------------------------------------------------------- |
|
|
|
var filterField = Ext.get(_id + '_filterEdit'); |
|
var clearButton = Ext.get(_id + '_clearButton'); |
|
var selectAllButton = Ext.get(_id + '_selectAllButton'); |
|
var deselectAllButton = Ext.get(_id + '_deselectAllButton'); |
|
|
|
me._customComponents = { |
|
filterField: filterField, |
|
clearButton: clearButton, |
|
selectAllButton: selectAllButton, |
|
deselectAllButton: deselectAllButton, |
|
headerContainer: headerContainer |
|
}; |
|
|
|
// header height may not be accurate immediately; read after a tick |
|
var headerHeight = headerContainer && headerContainer.offsetHeight || 0; |
|
|
|
// --- Adjust picker height based on content and header ------------------- |
|
var adjustPickerHeight = function () { |
|
setTimeout(function () { |
|
var p = me.getPicker && me.getPicker(); |
|
if (!p || !p.el) return; |
|
|
|
var listEl = p.el.down('.x-boundlist-list-ct'); |
|
if (!listEl) return; |
|
|
|
var currentScrollTop = listEl.getScrollTop(); |
|
|
|
listEl.setStyle({ |
|
'max-height': (MAX_PICKER_HEIGHT - headerHeight) + 'px', |
|
'height': 'auto', |
|
'overflow-y': 'auto' |
|
}); |
|
|
|
var contentHeight = listEl.getHeight(); |
|
var calculatedHeight = Math.min(contentHeight + headerHeight, MAX_PICKER_HEIGHT); |
|
|
|
if (p.setHeight) p.setHeight(calculatedHeight); |
|
if (currentScrollTop > 0) listEl.setScrollTop(currentScrollTop); |
|
}, 50); |
|
}; |
|
// ---------------------------------------------------------------------- |
|
|
|
// --- Clear search and restore all items -------------------------------- |
|
var clearFilter = function () { |
|
if (filterField && filterField.dom) { |
|
filterField.dom.value = ''; |
|
filterField.focus(); |
|
} |
|
if (clearButton) clearButton.hide(); |
|
|
|
var items = picker.el.query('.x-boundlist-item'); |
|
for (var i = 0; i < items.length; i++) { |
|
items[i].style.display = ''; |
|
} |
|
adjustPickerHeight(); |
|
}; |
|
// ---------------------------------------------------------------------- |
|
|
|
// --- Bulk selection/deselection for visible items only ----------------- |
|
var bulkSelection = function (select) { |
|
var visibleItems = picker.el.query('.x-boundlist-item:not([style*="display: none"])'); |
|
var store = me.getStore && me.getStore(); |
|
if (!store) return; |
|
|
|
var values = me.getValue && (me.getValue() || []) || []; |
|
var newValues = select ? [] : values.slice(); |
|
|
|
if (select) { |
|
for (var i = 0; i < visibleItems.length; i++) { |
|
var item = visibleItems[i]; |
|
var recordIndex = item.getAttribute('data-recordIndex') || |
|
Array.prototype.indexOf.call(item.parentNode.children, item); |
|
var record = store.getAt(recordIndex); |
|
if (record) { |
|
var value = record.get(me.valueField || me.displayField); |
|
if (newValues.indexOf(value) === -1) newValues.push(value); |
|
} |
|
} |
|
} else { |
|
for (var j = 0; j < visibleItems.length; j++) { |
|
var it = visibleItems[j]; |
|
var idx = it.getAttribute('data-recordIndex') || |
|
Array.prototype.indexOf.call(it.parentNode.children, it); |
|
var rec = store.getAt(idx); |
|
if (rec) { |
|
var v = rec.get(me.valueField || me.displayField); |
|
var vi = newValues.indexOf(v); |
|
if (vi !== -1) newValues.splice(vi, 1); |
|
} |
|
} |
|
} |
|
|
|
if (me.setValue) me.setValue(newValues); |
|
|
|
setTimeout(function () { |
|
updateSelectAllButtonText(); |
|
if (filterField && filterField.dom) filterField.focus(); |
|
}, 50); |
|
}; |
|
|
|
var selectAllVisible = function () { bulkSelection(true); }; |
|
var deselectAllVisible = function () { bulkSelection(false); }; |
|
// ---------------------------------------------------------------------- |
|
|
|
var handleClearButtonClick = function () { |
|
setTimeout(updateSelectAllButtonText, 10); |
|
}; |
|
|
|
// --- Bind ExtJS clear triggers to keep counter in sync ----------------- |
|
var findAndBindClearButton = function () { |
|
var pickerEl = picker.el; |
|
if (!pickerEl) return; |
|
|
|
var clearButtons = pickerEl.query('.x-form-clear-trigger'); |
|
if (clearButtons.length > 0) { |
|
for (var i = 0; i < clearButtons.length; i++) { |
|
var clearBtn = clearButtons[i]; |
|
if (!clearBtn._customClearHandlerAdded) { |
|
Ext.get(clearBtn).on('click', handleClearButtonClick); |
|
clearBtn._customClearHandlerAdded = true; |
|
} |
|
} |
|
} |
|
}; |
|
// ---------------------------------------------------------------------- |
|
|
|
if (clearButton) clearButton.on('click', clearFilter); |
|
if (selectAllButton) selectAllButton.on('click', selectAllVisible); |
|
if (deselectAllButton) deselectAllButton.on('click', deselectAllVisible); |
|
|
|
me.on && me.on('change', function () { setTimeout(updateSelectAllButtonText, 10); }); |
|
|
|
// --- Live filtering on keyup ------------------------------------------- |
|
if (filterField) { |
|
filterField.on('keyup', function () { |
|
var searchText = (filterField.dom.value || '').toLowerCase(); |
|
|
|
if (searchText.length > 0) { |
|
clearButton && clearButton.show(); |
|
} else { |
|
clearButton && clearButton.hide(); |
|
} |
|
|
|
var items = picker.el.query('.x-boundlist-item'); |
|
for (var i = 0; i < items.length; i++) { |
|
var text = items[i].textContent || items[i].innerText; |
|
items[i].style.display = (text.toLowerCase().indexOf(searchText) !== -1) ? '' : 'none'; |
|
} |
|
|
|
adjustPickerHeight(); |
|
}); |
|
} |
|
// ---------------------------------------------------------------------- |
|
|
|
// --- Keep counter synced when clicking inside the list ----------------- |
|
if (picker.el) { |
|
picker.el.on('click', function (event) { |
|
var target = event.target; |
|
var item = target.closest && target.closest('.x-boundlist-item'); |
|
if (item) setTimeout(updateSelectAllButtonText, 10); |
|
|
|
if (target.classList && target.classList.contains('x-form-clear-trigger')) { |
|
handleClearButtonClick(); |
|
} |
|
}); |
|
} |
|
// ---------------------------------------------------------------------- |
|
|
|
// ====== Auto-close picker when clicking outside (BIND/UNBIND) ========= |
|
var bindOutsideCloser = function () { |
|
Ext.defer(function () { |
|
me._outsideCloser = function (e) { |
|
var p = me.getPicker && me.getPicker(); |
|
if (!p || p.destroyed || !p.el) return; |
|
|
|
var t = e.target; |
|
var clickInsidePicker = p.el.contains(t); |
|
var clickInsideField = me.el && me.el.contains(t); |
|
|
|
if (!clickInsidePicker && !clickInsideField) { |
|
me.collapse && me.collapse(); |
|
} |
|
}; |
|
|
|
Ext.getDoc().on('mousedown', me._outsideCloser); |
|
Ext.getDoc().on('touchstart', me._outsideCloser); |
|
}, 50); |
|
}; |
|
|
|
var unbindOutsideCloser = function () { |
|
if (me._outsideCloser) { |
|
Ext.getDoc().un('mousedown', me._outsideCloser); |
|
Ext.getDoc().un('touchstart', me._outsideCloser); |
|
me._outsideCloser = null; |
|
} |
|
}; |
|
// ====== /Auto-close picker when clicking outside ====================== |
|
|
|
me.on && me.on('expand', function () { |
|
setTimeout(function () { |
|
updateSelectAllButtonText(); |
|
findAndBindClearButton(); |
|
if (filterField && filterField.dom) filterField.focus(); |
|
adjustPickerHeight(); |
|
enforcePickerMinWidth(); // ensure min width after showing |
|
}, 100); |
|
bindOutsideCloser(); |
|
}); |
|
|
|
me.on && me.on('collapse', function () { |
|
unbindOutsideCloser(); |
|
|
|
if (filterField && filterField.dom) filterField.dom.value = ''; |
|
clearButton && clearButton.hide(); |
|
|
|
var items = picker.el.query('.x-boundlist-item'); |
|
for (var i = 0; i < items.length; i++) items[i].style.display = ''; |
|
}); |
|
|
|
setTimeout(findAndBindClearButton, 200); |
|
|
|
adjustPickerHeight(); |
|
me.on && me.on('expand', adjustPickerHeight); |
|
picker.un && picker.un('show', _insertEditField); |
|
|
|
// keep a reference for destroy cleanup |
|
me._unbindOutsideCloserFn = unbindOutsideCloser; |
|
}; |
|
|
|
var p = me.getPicker && me.getPicker(); |
|
if (p && p.on) p.on('show', _insertEditField); |
|
|
|
me.on && me.on('destroy', function () { |
|
// ensure global listeners are removed |
|
if (me._unbindOutsideCloserFn) { |
|
me._unbindOutsideCloserFn(); |
|
me._unbindOutsideCloserFn = null; |
|
} |
|
|
|
if (me._customComponents) { |
|
try { |
|
if (me._customComponents.headerContainer) { |
|
var hc = me._customComponents.headerContainer; |
|
if (hc.dom && hc.remove) hc.remove(); |
|
else if (hc.parentNode) hc.parentNode.removeChild(hc); |
|
} |
|
} catch (e) { |
|
if (window.console) console.log('Error during cleanup:', e); |
|
} |
|
me._customComponents = null; |
|
} |
|
me._customHeaderAdded = false; |
|
}); |
|
} |