feat: icon abstraction layer for PS 1.6-9.x compatibility
Add esIcon() JS helper and renderIcon() PHP helper that auto-detect the icon framework (Material Icons on PS 8+/9+, FontAwesome 4 on PS 1.6/1.7) and render appropriate HTML. Includes bidirectional mapping between FA4 class names and Material Icons names. - Replace all hardcoded <i class="material-icons"> with esIcon()/renderIcon() - Add FA4_MAP (Material→FA4) and reverse FA4→Material mapping in both JS and PHP - Add detectIconMode() with PHP data-attribute hint and font-family probe fallback - Fix 4 self/this scope bugs in esIcon calls across _methods.js, _tree.js, _preview.js - Add esIconUpdate() for dynamically updating existing icon elements - Add normalizeIconName() to handle both FA4 and Material input formats Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,7 +81,7 @@
|
||||
|
||||
// Country: show flag
|
||||
if (isCountry && data && data.iso_code) {
|
||||
html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + this.escapeAttr(data.iso_code.toLowerCase()) + '.png" alt="' + this.escapeAttr(data.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';"><i class="icon-flag flag-fallback" style="display:none;"></i></span>';
|
||||
html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + this.escapeAttr(data.iso_code.toLowerCase()) + '.png" alt="' + this.escapeAttr(data.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';">' + this.esIcon('flag', 'flag-fallback').replace('>', ' style="display:none">') + '</span>';
|
||||
} else if (data && data.image) {
|
||||
html += '<span class="chip-icon"><img src="' + this.escapeAttr(data.image) + '" alt=""></span>';
|
||||
}
|
||||
@@ -90,10 +90,10 @@
|
||||
|
||||
// Country: add holiday preview button
|
||||
if (isCountry) {
|
||||
html += '<button type="button" class="chip-preview-holidays" title="Preview holidays"><i class="material-icons">visibility</i></button>';
|
||||
html += '<button type="button" class="chip-preview-holidays" title="Preview holidays">' + this.esIcon('visibility') + '</button>';
|
||||
}
|
||||
|
||||
html += '<button type="button" class="chip-remove" title="Remove"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="chip-remove" title="Remove">' + this.esIcon('close') + '</button>';
|
||||
html += '</span>';
|
||||
|
||||
$chips.append(html);
|
||||
@@ -183,7 +183,7 @@
|
||||
'<button type="button" class="btn-collapse-chips" style="' +
|
||||
'background:transparent;border:1px solid #dee2e6;border-radius:4px;' +
|
||||
'padding:0.25rem 0.75rem;font-size:12px;color:#6c757d;cursor:pointer;">' +
|
||||
'<i class="icon-chevron-up"></i> ' + collapseText +
|
||||
this.esIcon('expand_less') + ' ' + collapseText +
|
||||
'</button>'
|
||||
).show();
|
||||
} else {
|
||||
@@ -211,7 +211,7 @@
|
||||
'</select>' +
|
||||
'<span class="chips-count"></span>' +
|
||||
'<button type="button" class="btn-chips-clear" title="' + (trans.clear_all || 'Clear all') + '">' +
|
||||
'<i class="icon-trash"></i> <span class="clear-text">' + (trans.clear || 'Clear') + '</span>' +
|
||||
this.esIcon('delete') + ' <span class="clear-text">' + (trans.clear || 'Clear') + '</span>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<div class="chips-load-more" style="display:none;"></div>' +
|
||||
@@ -422,20 +422,16 @@
|
||||
console.log('[EntitySelector] Making bulk AJAX request for entities:', JSON.stringify(bulkRequest));
|
||||
|
||||
// Single bulk AJAX call for all entity types
|
||||
var bulkAjaxData = {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIdsBulk',
|
||||
trait: 'EntitySelector',
|
||||
entities: JSON.stringify(bulkRequest)
|
||||
};
|
||||
if (self.config.productSelectionLevel && self.config.productSelectionLevel !== 'product') {
|
||||
bulkAjaxData.product_selection_level = self.config.productSelectionLevel;
|
||||
}
|
||||
$.ajax({
|
||||
url: self.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: bulkAjaxData,
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIdsBulk',
|
||||
trait: 'EntitySelector',
|
||||
entities: JSON.stringify(bulkRequest)
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('[EntitySelector] AJAX response:', response);
|
||||
if (!response.success || !response.entities) {
|
||||
@@ -481,7 +477,7 @@
|
||||
|
||||
// Country: show flag
|
||||
if (isCountry && entity.iso_code) {
|
||||
html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + self.escapeAttr(entity.iso_code.toLowerCase()) + '.png" alt="' + self.escapeAttr(entity.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';"><i class="icon-flag flag-fallback" style="display:none;"></i></span>';
|
||||
html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + self.escapeAttr(entity.iso_code.toLowerCase()) + '.png" alt="' + self.escapeAttr(entity.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';">' + self.esIcon('flag', 'flag-fallback').replace('>', ' style="display:none">') + '</span>';
|
||||
} else if (entity.image) {
|
||||
html += '<span class="chip-icon"><img src="' + self.escapeAttr(entity.image) + '" alt=""></span>';
|
||||
}
|
||||
@@ -490,10 +486,10 @@
|
||||
|
||||
// Country: add holiday preview button
|
||||
if (isCountry) {
|
||||
html += '<button type="button" class="chip-preview-holidays" title="Preview holidays"><i class="material-icons">visibility</i></button>';
|
||||
html += '<button type="button" class="chip-preview-holidays" title="Preview holidays">' + self.esIcon('visibility') + '</button>';
|
||||
}
|
||||
|
||||
html += '<button type="button" class="chip-remove" title="Remove"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="chip-remove" title="Remove">' + self.esIcon('close') + '</button>';
|
||||
html += '</span>';
|
||||
|
||||
$loadingChip.replaceWith(html);
|
||||
@@ -587,7 +583,7 @@
|
||||
$chip.append($('<button>', {
|
||||
type: 'button',
|
||||
class: 'btn-remove-range',
|
||||
html: '<i class="icon-times"></i>'
|
||||
html: self.esIcon('close')
|
||||
}));
|
||||
|
||||
$chipsContainer.append($chip);
|
||||
@@ -640,7 +636,7 @@
|
||||
// Show loading placeholders with entity-specific icons
|
||||
values.forEach(function(id) {
|
||||
var html = '<span class="entity-chip entity-chip-loading" data-id="' + self.escapeAttr(id) + '">';
|
||||
html += '<span class="chip-icon"><i class="' + entityIcon + ' icon-spin-pulse"></i></span>';
|
||||
html += '<span class="chip-icon">' + self.esIcon(entityIcon, 'es-spin-pulse') + '</span>';
|
||||
html += '<span class="chip-name">Loading...</span>';
|
||||
html += '</span>';
|
||||
$chips.append(html);
|
||||
@@ -701,7 +697,7 @@
|
||||
$chip.append($('<button>', {
|
||||
type: 'button',
|
||||
class: 'btn-remove-range',
|
||||
html: '<i class="icon-times"></i>'
|
||||
html: self.esIcon('close')
|
||||
}));
|
||||
|
||||
$chipsContainer.append($chip);
|
||||
@@ -754,22 +750,18 @@
|
||||
|
||||
// Handle entity_search type - load via AJAX
|
||||
var searchEntity = $picker.attr('data-search-entity') || blockType;
|
||||
var pickerAjaxData = {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIds',
|
||||
trait: 'EntitySelector',
|
||||
entity_type: searchEntity,
|
||||
ids: JSON.stringify(values)
|
||||
};
|
||||
if (this.config.productSelectionLevel && this.config.productSelectionLevel !== 'product') {
|
||||
pickerAjaxData.product_selection_level = this.config.productSelectionLevel;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: pickerAjaxData,
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'getTargetEntitiesByIds',
|
||||
trait: 'EntitySelector',
|
||||
entity_type: searchEntity,
|
||||
ids: JSON.stringify(values)
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success && response.entities) {
|
||||
// Track which IDs were actually found (entities may have been deleted)
|
||||
@@ -818,7 +810,7 @@
|
||||
html += '<span class="case-icon">' + (isCaseSensitive ? 'Aa' : 'aa') + '</span>';
|
||||
html += '</button>';
|
||||
html += '<span class="pattern-tag-text">' + this.escapeHtml(pattern) + '</span>';
|
||||
html += '<button type="button" class="btn-remove-pattern" title="' + this.escapeAttr(trans.remove_pattern || 'Remove pattern') + '"><i class="icon-trash"></i></button>';
|
||||
html += '<button type="button" class="btn-remove-pattern" title="' + this.escapeAttr(trans.remove_pattern || 'Remove pattern') + '">' + this.esIcon('delete') + '</button>';
|
||||
html += '</div>';
|
||||
$chipsContainer.append(html);
|
||||
},
|
||||
@@ -857,7 +849,7 @@
|
||||
var entityType = $block.data('blockType') || 'products';
|
||||
|
||||
// Show loading - keep eye icon, update count value
|
||||
$countValue.html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countValue.html(this.esIcon('progress_activity', 'es-spin'));
|
||||
$matchCount.show();
|
||||
|
||||
// Store pattern for click handler
|
||||
@@ -956,7 +948,7 @@
|
||||
var blockType = $block.data('blockType') || 'products';
|
||||
|
||||
// Show loading
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
|
||||
$countEl.removeClass('clickable no-matches').show();
|
||||
|
||||
// Store condition data on badge for popover
|
||||
@@ -1022,7 +1014,7 @@
|
||||
var entityType = $block.data('blockType') || 'products';
|
||||
|
||||
// Show loading state
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
|
||||
$countEl.removeClass('clickable no-matches').show();
|
||||
|
||||
$.ajax({
|
||||
@@ -1164,7 +1156,7 @@
|
||||
var blockType = $block.data('blockType') || 'products';
|
||||
|
||||
// Show loading
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
|
||||
$countEl.removeClass('clickable no-matches').show();
|
||||
|
||||
// Store condition data on badge for popover
|
||||
@@ -1252,7 +1244,7 @@
|
||||
}
|
||||
|
||||
// Show loading
|
||||
$badge.html('<i class="icon-spinner icon-spin"></i>').show();
|
||||
$badge.html(this.esIcon('progress_activity', 'es-spin')).show();
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
@@ -1271,7 +1263,7 @@
|
||||
var excludeCount = response.exclude_count || 0;
|
||||
|
||||
// Update badge with eye icon and count
|
||||
var badgeHtml = '<i class="icon-eye"></i> ' + finalCount;
|
||||
var badgeHtml = self.esIcon('visibility') + ' ' + finalCount;
|
||||
if (excludeCount > 0) {
|
||||
badgeHtml += ' <span class="exclude-info">(-' + excludeCount + ')</span>';
|
||||
}
|
||||
|
||||
@@ -127,16 +127,11 @@
|
||||
// Add fullwidth class to parent form-group (skip for form-group layout)
|
||||
var hasLayoutFormGroup = this.$wrapper.hasClass('layout-form-group');
|
||||
var $entitySelectorFormGroup = this.$wrapper.closest('.entity-selector-form-group');
|
||||
console.log('[EntitySelector] hasLayoutFormGroup:', hasLayoutFormGroup);
|
||||
console.log('[EntitySelector] closest .entity-selector-form-group:', $entitySelectorFormGroup.length);
|
||||
|
||||
if (!hasLayoutFormGroup && !$entitySelectorFormGroup.length) {
|
||||
console.log('[EntitySelector] Adding condition-trait-fullwidth to form-group');
|
||||
var $formGroup = this.$wrapper.closest('.form-group');
|
||||
$formGroup.addClass('condition-trait-fullwidth');
|
||||
$formGroup.find('.col-lg-offset-3').removeClass('col-lg-offset-3');
|
||||
} else {
|
||||
console.log('[EntitySelector] SKIPPING fullwidth - form-group layout detected');
|
||||
}
|
||||
|
||||
this.createDropdown();
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
|
||||
// Select all / Clear buttons with keyboard shortcuts
|
||||
html += '<button type="button" class="btn-select-all" title="' + (trans.select_all || 'Select all visible') + '">';
|
||||
html += '<i class="icon-check-square-o"></i> ' + (trans.all || 'All') + ' <kbd>Ctrl+A</kbd>';
|
||||
html += this.esIcon('check_box') + ' ' + (trans.all || 'All') + ' <kbd>Ctrl+A</kbd>';
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="btn-clear-selection" title="' + (trans.clear_selection || 'Clear selection') + '">';
|
||||
html += '<i class="icon-square-o"></i> ' + (trans.clear || 'Clear') + ' <kbd>Ctrl+D</kbd>';
|
||||
html += this.esIcon('check_box_outline_blank') + ' ' + (trans.clear || 'Clear') + ' <kbd>Ctrl+D</kbd>';
|
||||
html += '</button>';
|
||||
|
||||
// Sort controls - options with data-entities attribute for entity-specific filtering
|
||||
@@ -50,7 +50,7 @@
|
||||
html += '<option value="product_count" data-entities="categories,manufacturers,suppliers">' + (trans.sort_product_count || 'Products') + '</option>';
|
||||
html += '</select>';
|
||||
html += '<button type="button" class="btn-sort-dir" data-dir="ASC" title="Sort direction">';
|
||||
html += '<i class="icon-sort-alpha-asc"></i>';
|
||||
html += this.esIcon('sort_by_alpha');
|
||||
html += '</button>';
|
||||
|
||||
// View mode selector - Tree option always present, shown for categories
|
||||
@@ -69,19 +69,19 @@
|
||||
|
||||
// Refine search
|
||||
html += '<div class="refine-compact">';
|
||||
html += '<button type="button" class="btn-refine-negate" title="' + (trans.exclude_matches || 'Exclude matches (NOT contains)') + '"><i class="icon-ban"></i></button>';
|
||||
html += '<button type="button" class="btn-refine-negate" title="' + (trans.exclude_matches || 'Exclude matches (NOT contains)') + '">' + this.esIcon('block') + '</button>';
|
||||
html += '<input type="text" class="refine-input" placeholder="' + (trans.refine_short || 'Refine...') + '">';
|
||||
html += '<button type="button" class="btn-clear-refine" style="display:none;"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-refine" style="display:none;">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Filter toggle button
|
||||
html += '<button type="button" class="btn-toggle-filters" title="' + (trans.toggle_filters || 'Filters') + '">';
|
||||
html += '<i class="icon-filter"></i>';
|
||||
html += this.esIcon('filter_list');
|
||||
html += '</button>';
|
||||
|
||||
// History button
|
||||
html += '<button type="button" class="btn-show-history" title="' + (trans.recent_searches || 'Recent searches') + '">';
|
||||
html += '<i class="icon-clock-o"></i>';
|
||||
html += this.esIcon('schedule');
|
||||
html += '</button>';
|
||||
|
||||
html += '</div>'; // End dropdown-actions
|
||||
@@ -104,13 +104,13 @@
|
||||
html += '</div>';
|
||||
|
||||
html += '<button type="button" class="btn-clear-filters" title="' + (trans.clear_filters || 'Clear filters') + '">';
|
||||
html += '<i class="icon-times"></i>';
|
||||
html += this.esIcon('close');
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Attribute/Feature filter toggles for products
|
||||
html += '<div class="filter-row filter-row-attributes" data-entity="products" style="display:none;">';
|
||||
html += '<span class="filter-row-label"><i class="icon-tags"></i> ' + (trans.attributes || 'Attributes') + ':</span>';
|
||||
html += '<span class="filter-row-label">' + this.esIcon('label') + ' ' + (trans.attributes || 'Attributes') + ':</span>';
|
||||
html += '<div class="filter-attributes-container"></div>';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-row filter-row-values filter-row-attr-values" data-type="attribute" style="display:none;">';
|
||||
@@ -118,7 +118,7 @@
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="filter-row filter-row-features" data-entity="products" style="display:none;">';
|
||||
html += '<span class="filter-row-label"><i class="icon-list-ul"></i> ' + (trans.features || 'Features') + ':</span>';
|
||||
html += '<span class="filter-row-label">' + this.esIcon('list') + ' ' + (trans.features || 'Features') + ':</span>';
|
||||
html += '<div class="filter-features-container"></div>';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-row filter-row-values filter-row-feat-values" data-type="feature" style="display:none;">';
|
||||
@@ -129,19 +129,19 @@
|
||||
html += '<div class="filter-row filter-row-entity-categories filter-row-multi" data-entity="categories" style="display:none;">';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-cubes"></i> ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-shopping-cart"></i> ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-money"></i> ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
@@ -150,7 +150,7 @@
|
||||
html += '</div>';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-select-group">';
|
||||
html += '<span class="filter-select-label"><i class="icon-sitemap"></i> ' + (trans.depth || 'Depth') + ':</span>';
|
||||
html += '<span class="filter-select-label">' + this.esIcon('account_tree') + ' ' + (trans.depth || 'Depth') + ':</span>';
|
||||
html += '<select class="filter-depth-select">';
|
||||
html += '<option value="">' + (trans.all_levels || 'All levels') + '</option>';
|
||||
html += '<option value="1">' + (trans.level || 'Level') + ' 1 (' + (trans.root || 'Root') + ')</option>';
|
||||
@@ -162,7 +162,7 @@
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-products"> ' + (trans.has_products || 'Has products') + '</label>';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-description"> ' + (trans.has_description || 'Has description') + '</label>';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-image"> ' + (trans.has_image || 'Has image') + '</label>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -170,19 +170,19 @@
|
||||
html += '<div class="filter-row filter-row-entity-manufacturers filter-row-multi" data-entity="manufacturers" style="display:none;">';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-cubes"></i> ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-shopping-cart"></i> ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-money"></i> ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
@@ -191,18 +191,18 @@
|
||||
html += '</div>';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-date-group">';
|
||||
html += '<span class="filter-date-label"><i class="icon-calendar"></i> ' + (trans.date_added || 'Added') + ':</span>';
|
||||
html += '<span class="filter-date-label">' + this.esIcon('event') + ' ' + (trans.date_added || 'Added') + ':</span>';
|
||||
html += '<input type="date" class="filter-date-add-from" title="' + (trans.from || 'From') + '">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="date" class="filter-date-add-to" title="' + (trans.to || 'To') + '">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-date-group">';
|
||||
html += '<span class="filter-date-label"><i class="icon-clock-o"></i> ' + (trans.last_product || 'Last product') + ':</span>';
|
||||
html += '<span class="filter-date-label">' + this.esIcon('schedule') + ' ' + (trans.last_product || 'Last product') + ':</span>';
|
||||
html += '<input type="date" class="filter-last-product-from" title="' + (trans.from || 'From') + '">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="date" class="filter-last-product-to" title="' + (trans.to || 'To') + '">';
|
||||
html += '</div>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -210,19 +210,19 @@
|
||||
html += '<div class="filter-row filter-row-entity-suppliers filter-row-multi" data-entity="suppliers" style="display:none;">';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-cubes"></i> ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-shopping-cart"></i> ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-money"></i> ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
@@ -231,18 +231,18 @@
|
||||
html += '</div>';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-date-group">';
|
||||
html += '<span class="filter-date-label"><i class="icon-calendar"></i> ' + (trans.date_added || 'Added') + ':</span>';
|
||||
html += '<span class="filter-date-label">' + this.esIcon('event') + ' ' + (trans.date_added || 'Added') + ':</span>';
|
||||
html += '<input type="date" class="filter-date-add-from" title="' + (trans.from || 'From') + '">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="date" class="filter-date-add-to" title="' + (trans.to || 'To') + '">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-date-group">';
|
||||
html += '<span class="filter-date-label"><i class="icon-clock-o"></i> ' + (trans.last_product || 'Last product') + ':</span>';
|
||||
html += '<span class="filter-date-label">' + this.esIcon('schedule') + ' ' + (trans.last_product || 'Last product') + ':</span>';
|
||||
html += '<input type="date" class="filter-last-product-from" title="' + (trans.from || 'From') + '">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="date" class="filter-last-product-to" title="' + (trans.to || 'To') + '">';
|
||||
html += '</div>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -250,19 +250,19 @@
|
||||
html += '<div class="filter-row filter-row-entity-attributes filter-row-multi" data-entity="attributes" style="display:none;">';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-cubes"></i> ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-shopping-cart"></i> ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-money"></i> ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
@@ -270,13 +270,13 @@
|
||||
html += '</div>';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-select-group">';
|
||||
html += '<span class="filter-select-label"><i class="icon-tags"></i> ' + (trans.attribute_group || 'Group') + ':</span>';
|
||||
html += '<span class="filter-select-label">' + this.esIcon('label') + ' ' + (trans.attribute_group || 'Group') + ':</span>';
|
||||
html += '<select class="filter-attribute-group-select">';
|
||||
html += '<option value="">' + (trans.all_groups || 'All groups') + '</option>';
|
||||
html += '</select>';
|
||||
html += '</div>';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-is-color"> ' + (trans.color_only || 'Color attributes') + '</label>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -284,19 +284,19 @@
|
||||
html += '<div class="filter-row filter-row-entity-features filter-row-multi" data-entity="features" style="display:none;">';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-cubes"></i> ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-shopping-cart"></i> ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
html += '</div>';
|
||||
html += '<div class="filter-range-group">';
|
||||
html += '<span class="filter-range-label"><i class="icon-money"></i> ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
||||
html += '<span class="filter-range-sep">-</span>';
|
||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
||||
@@ -304,13 +304,13 @@
|
||||
html += '</div>';
|
||||
html += '<div class="filter-subrow">';
|
||||
html += '<div class="filter-select-group">';
|
||||
html += '<span class="filter-select-label"><i class="icon-list-ul"></i> ' + (trans.feature_group || 'Group') + ':</span>';
|
||||
html += '<span class="filter-select-label">' + this.esIcon('list') + ' ' + (trans.feature_group || 'Group') + ':</span>';
|
||||
html += '<select class="filter-feature-group-select">';
|
||||
html += '<option value="">' + (trans.all_groups || 'All groups') + '</option>';
|
||||
html += '</select>';
|
||||
html += '</div>';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-is-custom"> ' + (trans.custom_only || 'Custom values') + '</label>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -318,13 +318,13 @@
|
||||
html += '<div class="filter-row filter-row-entity-cms" data-entity="cms" style="display:none;">';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-indexable"> ' + (trans.indexable || 'Indexable') + '</label>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Entity-specific filters: CMS Categories
|
||||
html += '<div class="filter-row filter-row-entity-cms-categories" data-entity="cms_categories" style="display:none;">';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Entity-specific filters: Countries
|
||||
@@ -333,12 +333,12 @@
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-holidays"> ' + (trans.has_holidays || 'Has holidays') + '</label>';
|
||||
html += '<label class="filter-label"><input type="checkbox" class="filter-contains-states"> ' + (trans.contains_states || 'Has states') + '</label>';
|
||||
html += '<div class="filter-select-group">';
|
||||
html += '<span class="filter-select-label"><i class="icon-globe"></i> ' + (trans.zone || 'Zone') + ':</span>';
|
||||
html += '<span class="filter-select-label">' + this.esIcon('language') + ' ' + (trans.zone || 'Zone') + ':</span>';
|
||||
html += '<select class="filter-zone-select">';
|
||||
html += '<option value="">' + (trans.all_zones || 'All zones') + '</option>';
|
||||
html += '</select>';
|
||||
html += '</div>';
|
||||
html += '<button type="button" class="btn-clear-filters"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>'; // End filter-panel
|
||||
@@ -373,8 +373,8 @@
|
||||
|
||||
// Right side: action buttons
|
||||
html += '<div class="dropdown-footer-right">';
|
||||
html += '<button type="button" class="dropdown-action-btn btn-cancel"><i class="icon-times"></i> ' + (trans.cancel || 'Cancel') + ' <span class="btn-shortcut">Esc</span></button>';
|
||||
html += '<button type="button" class="dropdown-action-btn btn-save"><i class="icon-check"></i> ' + (trans.save || 'Save') + ' <span class="btn-shortcut">⏎</span></button>';
|
||||
html += '<button type="button" class="dropdown-action-btn btn-cancel">' + this.esIcon('close') + ' ' + (trans.cancel || 'Cancel') + ' <span class="btn-shortcut">Esc</span></button>';
|
||||
html += '<button type="button" class="dropdown-action-btn btn-save">' + this.esIcon('check') + ' ' + (trans.save || 'Save') + ' <span class="btn-shortcut">⏎</span></button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
@@ -107,9 +107,9 @@
|
||||
!$(e.target).closest('.group-count-badge').length &&
|
||||
!$(e.target).closest('.group-modifiers').length &&
|
||||
!$(e.target).closest('.group-preview-badge').length &&
|
||||
!$(e.target).closest('.toggle-count.clickable').length &&
|
||||
!$(e.target).closest('.trait-total-count').length &&
|
||||
!$(e.target).closest('.chip-preview-holidays').length &&
|
||||
!$(e.target).closest('.chip-preview-btn').length) {
|
||||
!$(e.target).closest('.chip-preview-holidays').length) {
|
||||
self.hidePreviewPopover();
|
||||
// Also close holiday popover
|
||||
$('.holiday-preview-popover').remove();
|
||||
@@ -139,7 +139,7 @@
|
||||
this.$wrapper.on('click', '.btn-toggle-blocks', function(e) {
|
||||
e.preventDefault();
|
||||
var $blocksContent = self.$wrapper.find('.entity-selector-blocks-content');
|
||||
var $icon = $(this).find('.material-icons');
|
||||
var $icon = $(this).find('.es-icon');
|
||||
$blocksContent.stop(true, true);
|
||||
if ($blocksContent.is(':visible')) {
|
||||
$blocksContent.slideUp(200);
|
||||
@@ -152,29 +152,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Filter chip/group preview eye button (unified handler)
|
||||
this.$wrapper.on('click', '.chip-preview-btn', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var $btn = $(this);
|
||||
|
||||
if ($btn.hasClass('popover-open')) {
|
||||
self.hidePreviewPopover();
|
||||
} else {
|
||||
var valueId = $btn.data('id');
|
||||
var valueType = $btn.data('type');
|
||||
var valueName = $btn.data('name');
|
||||
var groupId = $btn.data('groupId');
|
||||
|
||||
if (valueId) {
|
||||
// Value-level preview (specific attribute/feature value)
|
||||
self.showFilterValuePreviewPopover($btn, valueId, valueType, valueName, groupId);
|
||||
} else if (groupId) {
|
||||
// Group-level preview (entire attribute/feature group)
|
||||
self.showFilterGroupPreviewPopover($btn, groupId, valueType, valueName);
|
||||
}
|
||||
}
|
||||
// Custom block input changes — update tab badge when value changes
|
||||
this.$wrapper.on('input change', '.custom-block-content input, .custom-block-content textarea, .custom-block-content select', function() {
|
||||
self.updateTabBadges();
|
||||
});
|
||||
|
||||
// Group-level collapse toggle (click on group header or toggle icon)
|
||||
@@ -190,23 +170,27 @@
|
||||
});
|
||||
|
||||
// Toggle all groups (single button that switches between expand/collapse)
|
||||
this.$wrapper.on('click', '.trait-header-actions .btn-toggle-groups', function(e) {
|
||||
console.log('[ES-DEBUG] Binding .btn-toggle-groups click on wrapper:', self.$wrapper.attr('id'), 'found buttons:', self.$wrapper.find('.btn-toggle-groups').length);
|
||||
this.$wrapper.on('click', '.btn-toggle-groups', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var $btn = $(this);
|
||||
var currentState = $btn.attr('data-state') || 'collapsed';
|
||||
var trans = self.config.trans || {};
|
||||
console.log('[ES-DEBUG] .btn-toggle-groups CLICKED! currentState:', currentState, 'btn parent:', $btn.parent().attr('class'), 'groups found:', self.$wrapper.find('.selection-group').length);
|
||||
|
||||
if (currentState === 'collapsed') {
|
||||
self.$wrapper.find('.selection-group').removeClass('collapsed');
|
||||
$btn.attr('data-state', 'expanded');
|
||||
$btn.attr('title', trans.collapse_all || 'Collapse all groups');
|
||||
$btn.find('i').removeClass('icon-expand').addClass('icon-compress');
|
||||
$btn.find('i').text('close_fullscreen');
|
||||
console.log('[ES-DEBUG] Expanded all groups');
|
||||
} else {
|
||||
self.$wrapper.find('.selection-group').addClass('collapsed');
|
||||
$btn.attr('data-state', 'collapsed');
|
||||
$btn.attr('title', trans.expand_all || 'Expand all groups');
|
||||
$btn.find('i').removeClass('icon-compress').addClass('icon-expand');
|
||||
$btn.find('i').text('open_in_full');
|
||||
console.log('[ES-DEBUG] Collapsed all groups');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -526,8 +510,8 @@
|
||||
var currentPattern = $tag.data('pattern');
|
||||
|
||||
var $editInput = $('<input type="text" class="pattern-tag-edit">').val(currentPattern);
|
||||
var $saveBtn = $('<button type="button" class="btn-pattern-save" title="Save"><i class="icon-check"></i></button>');
|
||||
var $cancelBtn = $('<button type="button" class="btn-pattern-cancel" title="Cancel"><i class="icon-times"></i></button>');
|
||||
var $saveBtn = $('<button type="button" class="btn-pattern-save" title="Save">' + this.esIcon('check') + '</button>');
|
||||
var $cancelBtn = $('<button type="button" class="btn-pattern-cancel" title="Cancel">' + this.esIcon('close') + '</button>');
|
||||
var $editActions = $('<span class="pattern-edit-actions"></span>').append($saveBtn, $cancelBtn);
|
||||
|
||||
$tag.addClass('editing').find('.pattern-tag-text').hide();
|
||||
@@ -576,50 +560,6 @@
|
||||
$tag.removeClass('editing').find('.pattern-tag-text, .btn-remove-pattern').show();
|
||||
});
|
||||
|
||||
// Handle mpr-info-wrapper tooltip with fixed positioning
|
||||
this.$wrapper.on('mouseenter', '.mpr-info-wrapper[data-details]', function() {
|
||||
var $wrapper = $(this);
|
||||
if ($wrapper.data('tooltip-active')) return;
|
||||
|
||||
var content = $wrapper.attr('data-details');
|
||||
var tooltipClass = $wrapper.attr('data-tooltip-class') || '';
|
||||
var $tooltip = $('<div>', { class: 'mpr-tooltip mpr-tooltip-fixed ' + tooltipClass, html: content });
|
||||
|
||||
$('body').append($tooltip);
|
||||
$wrapper.data('tooltip-active', true);
|
||||
|
||||
var offset = $wrapper.offset();
|
||||
var triggerWidth = $wrapper.outerWidth();
|
||||
var tooltipWidth = $tooltip.outerWidth();
|
||||
var tooltipHeight = $tooltip.outerHeight();
|
||||
|
||||
var left = offset.left + (triggerWidth / 2) - (tooltipWidth / 2);
|
||||
var top = offset.top - tooltipHeight - 10;
|
||||
|
||||
if (left < 10) left = 10;
|
||||
if (left + tooltipWidth > $(window).width() - 10) {
|
||||
left = $(window).width() - tooltipWidth - 10;
|
||||
}
|
||||
|
||||
$tooltip.css({
|
||||
position: 'fixed',
|
||||
left: left + 'px',
|
||||
top: (top - $(window).scrollTop()) + 'px'
|
||||
});
|
||||
|
||||
$wrapper.data('tooltip-el', $tooltip);
|
||||
});
|
||||
|
||||
this.$wrapper.on('mouseleave', '.mpr-info-wrapper[data-details]', function() {
|
||||
var $wrapper = $(this);
|
||||
var $tooltip = $wrapper.data('tooltip-el');
|
||||
if ($tooltip) {
|
||||
$tooltip.remove();
|
||||
}
|
||||
$wrapper.data('tooltip-active', false);
|
||||
$wrapper.data('tooltip-el', null);
|
||||
});
|
||||
|
||||
// Handle numeric range input changes
|
||||
this.$wrapper.on('change', '.range-min-input, .range-max-input', function() {
|
||||
var $row = $(this).closest('.group-include, .exclude-row');
|
||||
@@ -715,7 +655,7 @@
|
||||
$chip.append($('<button>', {
|
||||
type: 'button',
|
||||
class: 'btn-remove-range',
|
||||
html: '<i class="icon-times"></i>'
|
||||
html: self.esIcon('close')
|
||||
}));
|
||||
|
||||
$chipsContainer.append($chip);
|
||||
@@ -871,11 +811,7 @@
|
||||
$btn.attr('data-dir', newDir);
|
||||
|
||||
var $icon = $btn.find('i');
|
||||
if (newDir === 'ASC') {
|
||||
$icon.removeClass('icon-sort-amount-desc').addClass('icon-sort-amount-asc');
|
||||
} else {
|
||||
$icon.removeClass('icon-sort-amount-asc').addClass('icon-sort-amount-desc');
|
||||
}
|
||||
$icon.replaceWith(this.esIcon('sort'));
|
||||
|
||||
self.serializeAllBlocks();
|
||||
self.refreshGroupPreviewIfOpen($group);
|
||||
@@ -1056,7 +992,7 @@
|
||||
if (isSelected) {
|
||||
// Remove from pending selections
|
||||
self.pendingSelections = self.pendingSelections.filter(function(s) {
|
||||
return String(s.id) !== String(id);
|
||||
return parseInt(s.id, 10) !== parseInt(id, 10);
|
||||
});
|
||||
self.removeSelection($picker, id);
|
||||
$item.toggleClass('selected');
|
||||
@@ -1070,9 +1006,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var currentSelection = self.getCurrentSingleSelection();
|
||||
var activeBlockType = self.activeGroup.blockType;
|
||||
var currentSelection = self.getCurrentSingleSelection(activeBlockType);
|
||||
if (currentSelection) {
|
||||
var newEntityType = self.activeGroup.blockType;
|
||||
var newEntityType = activeBlockType;
|
||||
self.showReplaceConfirmation(currentSelection, { name: name, entityType: newEntityType }, function() {
|
||||
// Add to pending selections
|
||||
self.pendingSelections.push({ id: id, name: name, data: $item.data() });
|
||||
@@ -1083,7 +1020,7 @@
|
||||
} else {
|
||||
// Add to pending selections
|
||||
var exists = self.pendingSelections.some(function(s) {
|
||||
return String(s.id) === String(id);
|
||||
return parseInt(s.id, 10) === parseInt(id, 10);
|
||||
});
|
||||
if (!exists) {
|
||||
self.pendingSelections.push({ id: id, name: name, data: $item.data() });
|
||||
@@ -1108,7 +1045,7 @@
|
||||
// Also remove from pending selections if dropdown is open
|
||||
if (self.pendingSelections) {
|
||||
self.pendingSelections = self.pendingSelections.filter(function(s) {
|
||||
return String(s.id) !== String(id);
|
||||
return parseInt(s.id, 10) !== parseInt(id, 10);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1235,7 +1172,7 @@
|
||||
|
||||
// Add to pending selections for Save button
|
||||
var exists = self.pendingSelections.some(function(s) {
|
||||
return String(s.id) === String(id);
|
||||
return parseInt(s.id, 10) === parseInt(id, 10);
|
||||
});
|
||||
if (!exists) {
|
||||
self.pendingSelections.push({
|
||||
@@ -1369,7 +1306,7 @@
|
||||
var currentDir = $btn.data('dir');
|
||||
var newDir = currentDir === 'ASC' ? 'DESC' : 'ASC';
|
||||
$btn.data('dir', newDir);
|
||||
$btn.find('i').attr('class', newDir === 'ASC' ? 'icon-sort-alpha-asc' : 'icon-sort-alpha-desc');
|
||||
$btn.find('i').replaceWith(this.esIcon('sort_by_alpha'));
|
||||
self.currentSort.dir = newDir;
|
||||
self.refreshSearch();
|
||||
});
|
||||
@@ -1383,8 +1320,7 @@
|
||||
$item.toggleClass('collapsed');
|
||||
var isCollapsed = $item.hasClass('collapsed');
|
||||
|
||||
$(this).find('i').toggleClass('icon-caret-down', !isCollapsed)
|
||||
.toggleClass('icon-caret-right', isCollapsed);
|
||||
$(this).find('i').text(isCollapsed ? 'arrow_right' : 'arrow_drop_down');
|
||||
|
||||
var descendants = self.findTreeDescendants($item, $allItems);
|
||||
for (var i = 0; i < descendants.length; i++) {
|
||||
@@ -1515,7 +1451,7 @@
|
||||
$child.removeClass('selected');
|
||||
}
|
||||
|
||||
$btn.find('i').removeClass('icon-minus-square').addClass('icon-plus-square');
|
||||
$btn.find('i').replaceWith(self.esIcon('add_box'));
|
||||
$btn.attr('title', trans.select_with_children || 'Select with all children');
|
||||
} else {
|
||||
var section = self.activeGroup.section;
|
||||
@@ -1553,7 +1489,7 @@
|
||||
self.showValidationError(skipMsg);
|
||||
}
|
||||
|
||||
$btn.find('i').removeClass('icon-plus-square').addClass('icon-minus-square');
|
||||
$btn.find('i').replaceWith(self.esIcon('indeterminate_check_box'));
|
||||
$btn.attr('title', trans.deselect_with_children || 'Deselect with all children');
|
||||
}
|
||||
|
||||
@@ -1574,7 +1510,7 @@
|
||||
this.$dropdown.on('click', '.category-tree .btn-expand-all', function(e) {
|
||||
e.preventDefault();
|
||||
self.$dropdown.find('.tree-item').removeClass('collapsed').show();
|
||||
self.$dropdown.find('.tree-toggle i').removeClass('icon-caret-right').addClass('icon-caret-down');
|
||||
self.$dropdown.find('.tree-toggle i').replaceWith(this.esIcon('arrow_drop_down'));
|
||||
});
|
||||
|
||||
// Tree view: Collapse all
|
||||
@@ -1594,7 +1530,7 @@
|
||||
if (level === minLevel) {
|
||||
if (hasChildren) {
|
||||
$item.addClass('collapsed');
|
||||
$item.find('.tree-toggle i').removeClass('icon-caret-down').addClass('icon-caret-right');
|
||||
$item.find('.tree-toggle i').replaceWith(self.esIcon('arrow_right'));
|
||||
}
|
||||
$item.show();
|
||||
} else {
|
||||
@@ -1850,6 +1786,10 @@
|
||||
|
||||
// Toggle filter group - show values
|
||||
this.$dropdown.on('click', '.filter-group-toggle', function(e) {
|
||||
// Ignore clicks on the preview badge
|
||||
if ($(e.target).closest('.toggle-count.clickable').length) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var $btn = $(this);
|
||||
var groupId = $btn.data('group-id');
|
||||
@@ -1866,26 +1806,20 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Filter preview eye button (dropdown-level, since dropdown is appended to body)
|
||||
this.$dropdown.on('click', '.chip-preview-btn', function(e) {
|
||||
e.preventDefault();
|
||||
// Filter group toggle count badge click for preview popover
|
||||
this.$dropdown.on('click', '.filter-group-toggle .toggle-count.clickable', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
var $btn = $(this);
|
||||
var $badge = $(this);
|
||||
var groupId = $badge.data('groupId');
|
||||
var groupType = $badge.data('type');
|
||||
var groupName = $badge.data('groupName');
|
||||
|
||||
if ($btn.hasClass('popover-open')) {
|
||||
if ($badge.hasClass('popover-open')) {
|
||||
self.hidePreviewPopover();
|
||||
} else {
|
||||
var valueId = $btn.data('id');
|
||||
var valueType = $btn.data('type');
|
||||
var valueName = $btn.data('name');
|
||||
var groupId = $btn.data('groupId');
|
||||
|
||||
if (valueId) {
|
||||
self.showFilterValuePreviewPopover($btn, valueId, valueType, valueName, groupId);
|
||||
} else if (groupId) {
|
||||
self.showFilterGroupPreviewPopover($btn, groupId, valueType, valueName);
|
||||
}
|
||||
self.showFilterGroupPreviewPopover($badge, groupId, groupType, groupName);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1994,104 +1928,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Tooltip hover events
|
||||
this.$wrapper.on('mouseenter', '.mpr-info-wrapper:not(.pinned)', function() {
|
||||
var $wrapper = $(this);
|
||||
var content = $wrapper.attr('data-tooltip');
|
||||
if (!content) return;
|
||||
|
||||
// Don't show hover tooltip if another is pinned
|
||||
if ($('.mpr-tooltip-fixed.pinned').length) return;
|
||||
|
||||
// Remove any existing non-pinned tooltip
|
||||
$('.mpr-tooltip-fixed:not(.pinned)').remove();
|
||||
|
||||
// Create tooltip
|
||||
var $tooltip = $('<div>', { class: 'mpr-tooltip-fixed' }).html(content);
|
||||
$('body').append($tooltip);
|
||||
|
||||
// Use getBoundingClientRect for viewport-relative positioning (fixed)
|
||||
var rect = $wrapper[0].getBoundingClientRect();
|
||||
var tooltipWidth = $tooltip.outerWidth();
|
||||
var tooltipHeight = $tooltip.outerHeight();
|
||||
|
||||
var left = rect.left + (rect.width / 2) - (tooltipWidth / 2);
|
||||
var top = rect.top - tooltipHeight - 8;
|
||||
|
||||
// Keep tooltip within viewport
|
||||
if (left < 10) left = 10;
|
||||
if (left + tooltipWidth > window.innerWidth - 10) {
|
||||
left = window.innerWidth - tooltipWidth - 10;
|
||||
}
|
||||
if (top < 10) {
|
||||
top = rect.bottom + 8;
|
||||
}
|
||||
|
||||
$tooltip.css({ top: top, left: left });
|
||||
});
|
||||
|
||||
this.$wrapper.on('mouseleave', '.mpr-info-wrapper:not(.pinned)', function() {
|
||||
$('.mpr-tooltip-fixed:not(.pinned)').remove();
|
||||
});
|
||||
|
||||
// Click to pin tooltip
|
||||
this.$wrapper.on('click', '.mpr-info-wrapper', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var $wrapper = $(this);
|
||||
|
||||
// If already pinned, unpin and close
|
||||
if ($wrapper.hasClass('pinned')) {
|
||||
$wrapper.removeClass('pinned');
|
||||
$wrapper.find('.material-icons').text('info');
|
||||
$('.mpr-tooltip-fixed.pinned').remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any other pinned tooltips
|
||||
$('.mpr-info-wrapper.pinned').removeClass('pinned').find('.material-icons').text('info');
|
||||
$('.mpr-tooltip-fixed').remove();
|
||||
|
||||
var content = $wrapper.attr('data-tooltip');
|
||||
if (!content) return;
|
||||
|
||||
// Pin this one
|
||||
$wrapper.addClass('pinned');
|
||||
$wrapper.find('.material-icons').text('close');
|
||||
|
||||
// Create pinned tooltip with close button
|
||||
var $tooltip = $('<div>', { class: 'mpr-tooltip-fixed pinned' });
|
||||
var $closeBtn = $('<button>', { class: 'mpr-tooltip-close', type: 'button' })
|
||||
.append($('<i>', { class: 'material-icons', text: 'close' }));
|
||||
$tooltip.append($closeBtn).append(content);
|
||||
$('body').append($tooltip);
|
||||
|
||||
// Close button click
|
||||
$closeBtn.on('click', function() {
|
||||
$wrapper.removeClass('pinned');
|
||||
$wrapper.find('.material-icons').text('info');
|
||||
$tooltip.remove();
|
||||
});
|
||||
|
||||
// Position
|
||||
var rect = $wrapper[0].getBoundingClientRect();
|
||||
var tooltipWidth = $tooltip.outerWidth();
|
||||
var tooltipHeight = $tooltip.outerHeight();
|
||||
|
||||
var left = rect.left + (rect.width / 2) - (tooltipWidth / 2);
|
||||
var top = rect.top - tooltipHeight - 8;
|
||||
|
||||
if (left < 10) left = 10;
|
||||
if (left + tooltipWidth > window.innerWidth - 10) {
|
||||
left = window.innerWidth - tooltipWidth - 10;
|
||||
}
|
||||
if (top < 10) {
|
||||
top = rect.bottom + 8;
|
||||
}
|
||||
|
||||
$tooltip.css({ top: top, left: left });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -228,7 +228,6 @@
|
||||
if (!this.$dropdown || !this.filterableData) return;
|
||||
|
||||
var self = this;
|
||||
var previewLabel = self.config.trans && self.config.trans.preview || 'Preview';
|
||||
|
||||
// Render attribute group toggle buttons
|
||||
var $attrContainer = this.$dropdown.find('.filter-attributes-container');
|
||||
@@ -236,17 +235,12 @@
|
||||
|
||||
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
||||
this.filterableData.attributes.forEach(function(group) {
|
||||
var html = '<span class="filter-chip-wrapper">';
|
||||
html += '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||
if (group.count !== undefined) {
|
||||
html += '<span class="toggle-count">(' + group.count + ')</span>';
|
||||
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">' + self.esIcon('visibility') + ' ' + group.count + '</span>';
|
||||
}
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="chip-preview-btn" data-group-id="' + group.id + '" data-type="attribute" data-name="' + self.escapeAttr(group.name) + '" title="' + previewLabel + '">';
|
||||
html += '<i class="icon-eye"></i>';
|
||||
html += '</button>';
|
||||
html += '</span>';
|
||||
$attrContainer.append(html);
|
||||
});
|
||||
this.$dropdown.find('.filter-row-attributes').show();
|
||||
@@ -258,17 +252,12 @@
|
||||
|
||||
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
||||
this.filterableData.features.forEach(function(group) {
|
||||
var html = '<span class="filter-chip-wrapper">';
|
||||
html += '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">';
|
||||
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||
if (group.count !== undefined) {
|
||||
html += '<span class="toggle-count">(' + group.count + ')</span>';
|
||||
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">' + self.esIcon('visibility') + ' ' + group.count + '</span>';
|
||||
}
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="chip-preview-btn" data-group-id="' + group.id + '" data-type="feature" data-name="' + self.escapeAttr(group.name) + '" title="' + previewLabel + '">';
|
||||
html += '<i class="icon-eye"></i>';
|
||||
html += '</button>';
|
||||
html += '</span>';
|
||||
$featContainer.append(html);
|
||||
});
|
||||
this.$dropdown.find('.filter-row-features').show();
|
||||
@@ -306,7 +295,6 @@
|
||||
var colorStyle = val.color ? ' style="--chip-color: ' + val.color + '"' : '';
|
||||
var colorClass = val.color ? ' has-color' : '';
|
||||
|
||||
html += '<span class="filter-chip-wrapper">';
|
||||
html += '<button type="button" class="filter-chip ' + chipClass + activeClass + colorClass + '" data-id="' + val.id + '" data-group-id="' + groupId + '"' + colorStyle + '>';
|
||||
if (val.color) {
|
||||
html += '<span class="chip-color-dot"></span>';
|
||||
@@ -316,17 +304,13 @@
|
||||
html += '<span class="chip-count">(' + val.count + ')</span>';
|
||||
}
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="chip-preview-btn" data-id="' + val.id + '" data-group-id="' + groupId + '" data-type="' + type + '" data-name="' + self.escapeAttr(val.name) + '" title="' + (self.config.trans && self.config.trans.preview || 'Preview') + '">';
|
||||
html += '<i class="icon-eye"></i>';
|
||||
html += '</button>';
|
||||
html += '</span>';
|
||||
});
|
||||
|
||||
$valuesContainer.html(html);
|
||||
|
||||
// Add close button as sibling (outside filter-values-container, inside filter-row-values)
|
||||
$filterRowValues.find('.btn-close-values').remove();
|
||||
$filterRowValues.append('<button type="button" class="btn-close-values"><i class="icon-times"></i></button>');
|
||||
$filterRowValues.append('<button type="button" class="btn-close-values">' + this.esIcon('close') + '</button>');
|
||||
$filterRowValues.show();
|
||||
|
||||
// Scroll into view if needed
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
|
||||
// Group header
|
||||
html += '<div class="group-header">';
|
||||
html += '<span class="group-collapse-toggle"><i class="icon-chevron-up"></i></span>';
|
||||
html += '<span class="group-collapse-toggle">' + this.esIcon('expand_less') + '</span>';
|
||||
html += '<span class="group-name-wrapper">';
|
||||
html += '<input type="text" class="group-name-input" value="" placeholder="' + defaultGroupName + '" title="' + (trans.click_to_name || 'Click to name this group') + '">';
|
||||
html += '<span class="group-count-badge" style="display:none;"><i class="icon-spinner icon-spin"></i></span>';
|
||||
html += '<span class="group-count-badge" style="display:none;">' + this.esIcon('progress_activity', 'es-spin') + '</span>';
|
||||
html += '</span>';
|
||||
html += '<button type="button" class="btn-remove-group" title="' + (trans.remove_group || 'Remove group') + '">';
|
||||
html += '<i class="icon-trash"></i>';
|
||||
html += this.esIcon('delete');
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -65,16 +65,16 @@
|
||||
html += '<div class="section-row">';
|
||||
html += '<div class="method-selector-wrapper">';
|
||||
html += '<select class="include-method-select">' + methodOptions + '</select>';
|
||||
html += '<span class="condition-match-count no-matches"><i class="icon-eye"></i> <span class="preview-count">0</span></span>';
|
||||
html += '<span class="condition-match-count no-matches">' + this.esIcon('visibility') + ' <span class="preview-count">0</span></span>';
|
||||
html += '<span class="method-info-placeholder"></span>';
|
||||
html += '</div>';
|
||||
var noItemsText = trans.no_items_selected || 'No items selected - use search below';
|
||||
html += '<div class="value-picker include-picker" style="display:none;" data-search-entity="' + blockType + '">';
|
||||
html += '<div class="entity-chips include-chips" data-placeholder="' + noItemsText + '"></div>';
|
||||
html += '<div class="entity-search-box">';
|
||||
html += '<i class="icon-search entity-search-icon"></i>';
|
||||
html += this.esIcon('search', 'entity-search-icon');
|
||||
html += '<input type="text" class="entity-search-input" placeholder="' + (trans.search_placeholder || 'Search by name, reference, ID...') + '" autocomplete="off">';
|
||||
html += '<span class="search-loading" style="display:none;"><i class="icon-spinner icon-spin"></i></span>';
|
||||
html += '<span class="search-loading" style="display:none;">' + this.esIcon('progress_activity', 'es-spin') + '</span>';
|
||||
html += '</div>';
|
||||
html += '<input type="hidden" class="include-values-data" value="[]">';
|
||||
html += '</div>';
|
||||
@@ -84,7 +84,7 @@
|
||||
// Excludes section (collapsed by default)
|
||||
html += '<div class="group-excludes">';
|
||||
html += '<button type="button" class="btn-add-exclude">';
|
||||
html += '<i class="icon-plus"></i> ' + (trans.add_exceptions || 'Add exceptions');
|
||||
html += this.esIcon('add') + ' ' + (trans.add_exceptions || 'Add exceptions');
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -92,11 +92,11 @@
|
||||
html += '<div class="group-modifiers">';
|
||||
html += '<span class="modifier-inline modifier-limit">';
|
||||
html += '<span class="modifier-label">' + (trans.limit || 'Limit') + '</span>';
|
||||
html += '<input type="number" class="group-modifier-limit" placeholder="–" min="1" step="1" title="' + (trans.limit_tooltip || 'Max items to return (empty = all)') + '">';
|
||||
html += '<input type="number" class="group-modifier-limit mpr-input-compact" placeholder="–" min="1" step="1" title="' + (trans.limit_tooltip || 'Max items to return (empty = all)') + '">';
|
||||
html += '</span>';
|
||||
html += '<span class="modifier-inline modifier-sort">';
|
||||
html += '<span class="modifier-label">' + (trans.sort || 'Sort') + '</span>';
|
||||
html += '<select class="group-modifier-sort">';
|
||||
html += '<select class="group-modifier-sort mpr-input-compact">';
|
||||
html += '<option value="sales" selected>' + (trans.sort_bestsellers || 'Best sellers') + '</option>';
|
||||
html += '<option value="date_add">' + (trans.sort_newest || 'Newest') + '</option>';
|
||||
html += '<option value="price">' + (trans.sort_price || 'Price') + '</option>';
|
||||
@@ -106,11 +106,11 @@
|
||||
html += '<option value="random">' + (trans.sort_random || 'Random') + '</option>';
|
||||
html += '</select>';
|
||||
html += '<button type="button" class="btn-sort-dir" data-dir="DESC" title="' + (trans.sort_direction || 'Sort direction') + '">';
|
||||
html += '<i class="icon-sort-amount-desc"></i>';
|
||||
html += this.esIcon('sort');
|
||||
html += '</button>';
|
||||
html += '</span>';
|
||||
html += '<span class="group-preview-badge clickable" title="' + (trans.preview_results || 'Preview results') + '">';
|
||||
html += '<i class="icon-eye"></i> <span class="preview-count"></span>';
|
||||
html += this.esIcon('visibility') + ' <span class="preview-count"></span>';
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -213,12 +213,32 @@
|
||||
if (groupCount > 0) {
|
||||
// Show loading state first
|
||||
if ($badge.length) {
|
||||
$badge.addClass('loading').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$badge.addClass('loading').html(self.esIcon('progress_activity', 'es-spin'));
|
||||
} else {
|
||||
$tab.append('<span class="tab-badge loading"><i class="icon-spinner icon-spin"></i></span>');
|
||||
$tab.append('<span class="tab-badge loading">' + self.esIcon('progress_activity', 'es-spin') + '</span>');
|
||||
}
|
||||
$tab.addClass('has-data');
|
||||
blockTypesWithData.push(blockType);
|
||||
} else if ($block.hasClass('custom-block')) {
|
||||
// Custom blocks: check if any input/textarea/select has a non-empty value
|
||||
var hasCustomValue = false;
|
||||
$block.find('.custom-block-content').find('input, textarea, select').each(function() {
|
||||
if ($(this).val() && $(this).val().trim() !== '') {
|
||||
hasCustomValue = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (hasCustomValue) {
|
||||
if ($badge.length) {
|
||||
$badge.removeClass('loading').html(self.esIcon('check'));
|
||||
} else {
|
||||
$tab.append('<span class="tab-badge">' + self.esIcon('check') + '</span>');
|
||||
}
|
||||
$tab.addClass('has-data');
|
||||
} else {
|
||||
$badge.remove();
|
||||
$tab.removeClass('has-data');
|
||||
}
|
||||
} else {
|
||||
$badge.remove();
|
||||
$tab.removeClass('has-data');
|
||||
@@ -312,7 +332,7 @@
|
||||
var $badge = $tab.find('.tab-badge');
|
||||
|
||||
if ($badge.length) {
|
||||
$badge.removeClass('loading').html('<i class="icon-eye"></i> ' + count);
|
||||
$badge.removeClass('loading').html(self.esIcon('visibility') + ' ' + count);
|
||||
// Store preview data for later popover use
|
||||
$tab.data('previewData', { count: count, success: true });
|
||||
}
|
||||
@@ -377,10 +397,10 @@
|
||||
// Show loading state
|
||||
var $badge = $tab.find('.tab-badge');
|
||||
if (!$badge.length) {
|
||||
$badge = $('<span class="tab-badge loading"><i class="icon-spinner icon-spin"></i></span>');
|
||||
$badge = $('<span class="tab-badge loading">' + this.esIcon('progress_activity', 'es-spin') + '</span>');
|
||||
$tab.append($badge);
|
||||
} else {
|
||||
$badge.addClass('loading').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$badge.addClass('loading').html(this.esIcon('progress_activity', 'es-spin'));
|
||||
}
|
||||
$tab.addClass('has-data');
|
||||
|
||||
@@ -401,7 +421,7 @@
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
var $badge = $tab.find('.tab-badge');
|
||||
$badge.removeClass('loading').html('<i class="icon-eye"></i> ' + response.count);
|
||||
$badge.removeClass('loading').html(self.esIcon('visibility') + ' ' + response.count);
|
||||
|
||||
// Store preview data for popover
|
||||
$tab.data('previewData', response);
|
||||
@@ -979,7 +999,7 @@
|
||||
}
|
||||
|
||||
// Show loading spinner
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
|
||||
$countEl.removeClass('clickable no-matches').show();
|
||||
|
||||
// Store condition data on badge for popover
|
||||
@@ -1063,7 +1083,7 @@
|
||||
// Special case: "All countries" method - fetch holidays for all countries
|
||||
if (valueType === 'none' && blockType === 'countries' && method === 'all') {
|
||||
console.log('[updateConditionCount] All countries method - fetching all country holidays');
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
|
||||
$countEl.removeClass('clickable no-matches country-holidays').show();
|
||||
|
||||
// First fetch all active country IDs, then get holidays
|
||||
@@ -1170,7 +1190,7 @@
|
||||
var isCountrySelection = (searchEntity === 'countries' && valueType === 'entity_search');
|
||||
console.log('[updateConditionCount] isCountrySelection:', isCountrySelection, 'values:', values);
|
||||
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
|
||||
$countEl.removeClass('clickable no-matches country-holidays').show();
|
||||
|
||||
// For countries, fetch holiday count
|
||||
@@ -1283,7 +1303,7 @@
|
||||
}
|
||||
|
||||
// Show loading
|
||||
$badge.html('<i class="icon-spinner icon-spin"></i>').show();
|
||||
$badge.html(this.esIcon('progress_activity', 'es-spin')).show();
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
@@ -1302,7 +1322,7 @@
|
||||
var excludeCount = response.exclude_count || 0;
|
||||
|
||||
// Update badge with eye icon and count
|
||||
var badgeHtml = '<i class="icon-eye"></i> ' + finalCount;
|
||||
var badgeHtml = self.esIcon('visibility') + ' ' + finalCount;
|
||||
if (excludeCount > 0) {
|
||||
badgeHtml += ' <span class="exclude-info">(-' + excludeCount + ')</span>';
|
||||
}
|
||||
@@ -1343,7 +1363,7 @@
|
||||
|
||||
// Build the full excludes structure with first row
|
||||
var html = '<div class="except-separator">';
|
||||
html += '<span class="except-label"><i class="icon-ban"></i> ' + (trans.except || 'EXCEPT') + '</span>';
|
||||
html += '<span class="except-label">' + this.esIcon('block') + ' ' + (trans.except || 'EXCEPT') + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="exclude-rows-container">';
|
||||
@@ -1351,7 +1371,7 @@
|
||||
html += '</div>';
|
||||
|
||||
html += '<button type="button" class="btn-add-another-exclude">';
|
||||
html += '<i class="icon-plus"></i> ' + (trans.add_another_exception || 'Add another exception');
|
||||
html += this.esIcon('add') + ' ' + (trans.add_another_exception || 'Add another exception');
|
||||
html += '</button>';
|
||||
|
||||
$excludesDiv.addClass('has-excludes').html(html);
|
||||
@@ -1422,11 +1442,11 @@
|
||||
html += '<div class="exclude-header-row">';
|
||||
html += '<div class="method-selector-wrapper">';
|
||||
html += '<select class="exclude-method-select">' + excludeMethodOptions + '</select>';
|
||||
html += '<span class="condition-match-count no-matches"><i class="icon-eye"></i> <span class="preview-count">0</span></span>';
|
||||
html += '<span class="condition-match-count no-matches">' + this.esIcon('visibility') + ' <span class="preview-count">0</span></span>';
|
||||
html += '<span class="method-info-placeholder"></span>';
|
||||
html += '</div>';
|
||||
html += '<button type="button" class="btn-remove-exclude-row" title="' + (trans.remove_this_exception || 'Remove this exception') + '">';
|
||||
html += '<i class="icon-trash"></i>';
|
||||
html += this.esIcon('delete');
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -1452,7 +1472,7 @@
|
||||
var $excludesDiv = $group.find('.group-excludes');
|
||||
$excludesDiv.removeClass('has-excludes').html(
|
||||
'<button type="button" class="btn-add-exclude">' +
|
||||
'<i class="icon-plus"></i> ' + (trans.add_exceptions || 'Add exceptions') +
|
||||
this.esIcon('add') + ' ' + (trans.add_exceptions || 'Add exceptions') +
|
||||
'</button>'
|
||||
);
|
||||
// Unlock the method selector since no excludes exist
|
||||
@@ -1558,9 +1578,9 @@
|
||||
var noItemsText = trans.no_items_selected || 'No items selected - use search below';
|
||||
html += '<div class="entity-chips ' + chipsClass + '" data-placeholder="' + this.escapeAttr(noItemsText) + '"></div>';
|
||||
html += '<div class="entity-search-box">';
|
||||
html += '<i class="icon-search entity-search-icon"></i>';
|
||||
html += this.esIcon('search', 'entity-search-icon');
|
||||
html += '<input type="text" class="entity-search-input" placeholder="' + this.escapeAttr(trans.search_placeholder || 'Search by name, reference, ID...') + '" autocomplete="off">';
|
||||
html += '<span class="search-loading" style="display:none;"><i class="icon-spinner icon-spin"></i></span>';
|
||||
html += '<span class="search-loading" style="display:none;">' + this.esIcon('progress_activity', 'es-spin') + '</span>';
|
||||
html += '</div>';
|
||||
html += '<input type="hidden" class="' + dataClass + '" value="[]">';
|
||||
break;
|
||||
@@ -1587,11 +1607,11 @@
|
||||
html += '<div class="pattern-tag draft-tag" data-case-sensitive="0">';
|
||||
html += '<button type="button" class="btn-toggle-case" title="' + this.escapeAttr(trans.case_insensitive || 'Case insensitive - click to toggle') + '"><span class="case-icon">aa</span></button>';
|
||||
html += '<input type="text" class="pattern-input" value="" placeholder="' + this.escapeAttr(trans.enter_pattern || 'e.g. *cotton*') + '">';
|
||||
html += '<span class="pattern-match-count" title="' + this.escapeAttr(trans.click_to_preview || 'Click to preview matches') + '"><i class="icon-eye"></i> <span class="count-value"></span></span>';
|
||||
html += '<button type="button" class="btn-add-pattern" title="' + this.escapeAttr(trans.add_pattern || 'Add pattern (Enter)') + '"><i class="icon-plus"></i></button>';
|
||||
html += '<span class="pattern-match-count" title="' + this.escapeAttr(trans.click_to_preview || 'Click to preview matches') + '">' + this.esIcon('visibility') + ' <span class="count-value"></span></span>';
|
||||
html += '<button type="button" class="btn-add-pattern" title="' + this.escapeAttr(trans.add_pattern || 'Add pattern (Enter)') + '">' + this.esIcon('add') + '</button>';
|
||||
html += '</div>';
|
||||
html += '<span class="mpr-info-wrapper" data-details="' + this.escapeAttr(tooltipContent) + '">';
|
||||
html += '<span class="mpr-icon icon-info link"></span>';
|
||||
html += this.esIcon('info');
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
html += '<input type="hidden" class="' + dataClass + '" value="[]">';
|
||||
@@ -1613,7 +1633,7 @@
|
||||
html += '<input type="number" class="range-min-input" value="" placeholder="' + this.escapeAttr(trans.min || 'Min') + '" step="0.01">';
|
||||
html += '<span class="range-separator">-</span>';
|
||||
html += '<input type="number" class="range-max-input" value="" placeholder="' + this.escapeAttr(trans.max || 'Max') + '" step="0.01">';
|
||||
html += '<button type="button" class="btn-add-range" title="' + this.escapeAttr(trans.add_range || 'Add range') + '"><i class="icon-plus"></i></button>';
|
||||
html += '<button type="button" class="btn-add-range" title="' + this.escapeAttr(trans.add_range || 'Add range') + '">' + this.esIcon('add') + '</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '<input type="hidden" class="' + dataClass + '" value="[]">';
|
||||
@@ -1681,7 +1701,7 @@
|
||||
html += '</div>';
|
||||
}
|
||||
html += '<div class="combination-groups-container">';
|
||||
html += '<span class="combination-loading"><i class="icon-spinner icon-spin"></i> ' + this.escapeHtml(trans.loading || 'Loading...') + '</span>';
|
||||
html += '<span class="combination-loading">' + this.esIcon('progress_activity', 'es-spin') + ' ' + this.escapeHtml(trans.loading || 'Loading...') + '</span>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
// Store mode along with attributes: { mode: 'products'|'combinations', attributes: { groupId: [valueIds] } }
|
||||
@@ -1727,28 +1747,14 @@
|
||||
}
|
||||
},
|
||||
|
||||
getSortIconClass: function(sortBy, sortDir) {
|
||||
var isAsc = (sortDir === 'ASC');
|
||||
|
||||
getSortIconName: function(sortBy, sortDir) {
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
return isAsc ? 'icon-sort-alpha-asc' : 'icon-sort-alpha-desc';
|
||||
case 'price':
|
||||
case 'quantity':
|
||||
case 'product_count':
|
||||
return isAsc ? 'icon-sort-numeric-asc' : 'icon-sort-numeric-desc';
|
||||
case 'date_add':
|
||||
case 'newest_products':
|
||||
return isAsc ? 'icon-sort-numeric-asc' : 'icon-sort-numeric-desc';
|
||||
case 'sales':
|
||||
case 'total_sales':
|
||||
return isAsc ? 'icon-sort-amount-asc' : 'icon-sort-amount-desc';
|
||||
case 'position':
|
||||
return isAsc ? 'icon-sort-numeric-asc' : 'icon-sort-numeric-desc';
|
||||
return 'sort_by_alpha';
|
||||
case 'random':
|
||||
return 'icon-random';
|
||||
return 'shuffle';
|
||||
default:
|
||||
return isAsc ? 'icon-sort-amount-asc' : 'icon-sort-amount-desc';
|
||||
return 'sort';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1793,7 +1799,7 @@
|
||||
$btn.attr('data-sort', newSort);
|
||||
$btn.attr('data-dir', newDir);
|
||||
$btn.attr('title', newLabel + ' ' + (newDir === 'DESC' ? '↓' : '↑'));
|
||||
$btn.find('i').attr('class', this.getSortIconClass(newSort, newDir));
|
||||
$btn.find('i').replaceWith(this.esIcon(this.getSortIconName(newSort, newDir)));
|
||||
},
|
||||
|
||||
// Validation
|
||||
@@ -1833,7 +1839,7 @@
|
||||
// Add error message after header
|
||||
var $error = $('<div>', {
|
||||
class: 'trait-validation-error',
|
||||
html: '<i class="icon-warning"></i> ' + message
|
||||
html: this.esIcon('warning') + ' ' + message
|
||||
});
|
||||
this.$wrapper.find('.condition-trait-header').after($error);
|
||||
|
||||
@@ -1852,8 +1858,7 @@
|
||||
clearValidationError: function() {
|
||||
this.$wrapper.removeClass('has-validation-error');
|
||||
this.$wrapper.find('.trait-validation-error').remove();
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
@@ -93,13 +93,13 @@
|
||||
$select.addClass('method-select-hidden');
|
||||
|
||||
var $selectedOption = $select.find('option:selected');
|
||||
var selectedIcon = $selectedOption.data('icon') || 'icon-caret-down';
|
||||
var selectedIcon = $selectedOption.data('icon') || 'arrow_drop_down';
|
||||
var selectedLabel = $selectedOption.text();
|
||||
|
||||
var triggerHtml = '<div class="method-dropdown-trigger">';
|
||||
triggerHtml += '<i class="' + this.escapeAttr(selectedIcon) + ' method-trigger-icon"></i>';
|
||||
triggerHtml += this.esIcon(selectedIcon, 'method-trigger-icon');
|
||||
triggerHtml += '<span class="method-trigger-label">' + this.escapeHtml(selectedLabel) + '</span>';
|
||||
triggerHtml += '<i class="icon-caret-down method-trigger-caret"></i>';
|
||||
triggerHtml += this.esIcon('arrow_drop_down', 'method-trigger-caret');
|
||||
triggerHtml += '</div>';
|
||||
|
||||
var $trigger = $(triggerHtml);
|
||||
@@ -127,10 +127,10 @@
|
||||
*/
|
||||
updateMethodTrigger: function($select, $trigger) {
|
||||
var $selectedOption = $select.find('option:selected');
|
||||
var selectedIcon = $selectedOption.data('icon') || 'icon-caret-down';
|
||||
var selectedIcon = $selectedOption.data('icon') || 'arrow_drop_down';
|
||||
var selectedLabel = $selectedOption.text();
|
||||
|
||||
$trigger.find('.method-trigger-icon').attr('class', selectedIcon + ' method-trigger-icon');
|
||||
$trigger.find('.method-trigger-icon').replaceWith(this.esIcon(selectedIcon, 'method-trigger-icon'));
|
||||
$trigger.find('.method-trigger-label').text(selectedLabel);
|
||||
},
|
||||
|
||||
@@ -194,16 +194,16 @@
|
||||
// Render ungrouped options first
|
||||
$select.children('option').each(function() {
|
||||
var $el = $(this);
|
||||
var icon = $el.data('icon') || 'icon-asterisk';
|
||||
var icon = $el.data('icon') || 'star';
|
||||
var label = $el.text();
|
||||
var value = $el.val();
|
||||
var isSelected = $el.is(':selected');
|
||||
|
||||
html += '<div class="method-dropdown-item' + (isSelected ? ' selected' : '') + '" data-value="' + self.escapeAttr(value) + '">';
|
||||
html += '<i class="' + self.escapeAttr(icon) + ' method-item-icon"></i>';
|
||||
html += self.esIcon(icon, 'method-item-icon');
|
||||
html += '<span class="method-item-label">' + self.escapeHtml(label) + '</span>';
|
||||
if (isSelected) {
|
||||
html += '<i class="icon-check method-item-check"></i>';
|
||||
html += self.esIcon('check', 'method-item-check');
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
@@ -219,16 +219,16 @@
|
||||
|
||||
$optgroup.children('option').each(function() {
|
||||
var $el = $(this);
|
||||
var icon = $el.data('icon') || 'icon-cog';
|
||||
var icon = $el.data('icon') || 'settings';
|
||||
var label = $el.text();
|
||||
var value = $el.val();
|
||||
var isSelected = $el.is(':selected');
|
||||
|
||||
html += '<div class="method-dropdown-item' + (isSelected ? ' selected' : '') + '" data-value="' + self.escapeAttr(value) + '">';
|
||||
html += '<i class="' + self.escapeAttr(icon) + ' method-item-icon"></i>';
|
||||
html += self.esIcon(icon, 'method-item-icon');
|
||||
html += '<span class="method-item-label">' + self.escapeHtml(label) + '</span>';
|
||||
if (isSelected) {
|
||||
html += '<i class="icon-check method-item-check"></i>';
|
||||
html += self.esIcon('check', 'method-item-check');
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
@@ -384,13 +384,13 @@
|
||||
type: 'button',
|
||||
class: 'comb-toolbar-btn comb-select-all',
|
||||
title: trans.select_all || 'Select all',
|
||||
html: '<i class="icon-check-square-o"></i>'
|
||||
html: self.esIcon('check_box')
|
||||
}));
|
||||
$toolbar.append($('<button>', {
|
||||
type: 'button',
|
||||
class: 'comb-toolbar-btn comb-select-none',
|
||||
title: trans.clear || 'Clear',
|
||||
html: '<i class="icon-square-o"></i>'
|
||||
html: self.esIcon('check_box_outline_blank')
|
||||
}));
|
||||
$toolbar.append($('<input>', {
|
||||
type: 'text',
|
||||
@@ -404,7 +404,7 @@
|
||||
});
|
||||
$valuesContainer.append($('<span>', {
|
||||
class: 'comb-attr-loading',
|
||||
html: '<i class="icon-spinner icon-spin"></i>'
|
||||
html: self.esIcon('progress_activity', 'es-spin')
|
||||
}));
|
||||
|
||||
$groupDiv.append($groupHeader);
|
||||
@@ -584,10 +584,15 @@
|
||||
if (helpContent) {
|
||||
var $infoWrapper = $('<span>', {
|
||||
class: 'mpr-info-wrapper',
|
||||
'data-tooltip': helpContent
|
||||
'data-details': helpContent
|
||||
});
|
||||
$infoWrapper.append($('<i>', { class: 'material-icons', text: 'info' }));
|
||||
$infoWrapper.append($(this.esIcon('info')));
|
||||
$placeholder.append($infoWrapper);
|
||||
|
||||
// Let prestashop-admin info-tooltip.js handle this element
|
||||
if (window.MPRInfoTooltip) {
|
||||
window.MPRInfoTooltip.init();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -846,7 +851,7 @@
|
||||
$wrapper.addClass('selector-locked');
|
||||
if (!$wrapper.find('.lock-indicator').length) {
|
||||
var lockHtml = '<span class="mpr-info-wrapper lock-indicator">' +
|
||||
'<i class="icon-lock"></i>' +
|
||||
this.esIcon('lock') +
|
||||
'<span class="mpr-tooltip">' +
|
||||
(trans.remove_excludes_first || 'Remove all exceptions to change selection type') +
|
||||
'</span>' +
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
$countValue.text(total);
|
||||
} else {
|
||||
// Fallback: set HTML with icon
|
||||
$totalBadge.html('<i class="icon-eye"></i> <span class="count-value">' + total + '</span>');
|
||||
$totalBadge.html(self.esIcon('visibility') + ' <span class="count-value">' + total + '</span>');
|
||||
}
|
||||
$totalBadge.show();
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@
|
||||
// Header with count and close button
|
||||
html += '<div class="preview-header">';
|
||||
html += '<span class="preview-count">' + totalCount + ' ' + entityLabel + '</span>';
|
||||
html += '<button type="button" class="preview-close"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="preview-close">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Filter input
|
||||
@@ -119,7 +119,7 @@
|
||||
html += '<option value="' + remaining + '">' + (trans.all || 'All') + ' (' + remaining + ')</option>';
|
||||
html += '</select>';
|
||||
html += '<span class="load-more-of">' + (trans.of || 'of') + ' <span class="remaining-count">' + remaining + '</span> ' + (trans.remaining || 'remaining') + '</span>';
|
||||
html += '<button type="button" class="btn-load-more"><i class="icon-plus"></i></button>';
|
||||
html += '<button type="button" class="btn-load-more">' + self.esIcon('add') + '</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
@@ -181,7 +181,7 @@
|
||||
if ($btn.hasClass('loading')) return;
|
||||
|
||||
$btn.addClass('loading');
|
||||
$btn.find('i').removeClass('icon-plus').addClass('icon-spinner icon-spin');
|
||||
$btn.find('i').replaceWith(self.esIcon('progress_activity', 'es-spin'));
|
||||
$select.prop('disabled', true);
|
||||
|
||||
// Get selected load count
|
||||
@@ -192,8 +192,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Position popover relative to badge (handles viewport overflow)
|
||||
this.positionPopover($popover, $badge);
|
||||
// Position popover below badge
|
||||
var badgeOffset = $badge.offset();
|
||||
var badgeHeight = $badge.outerHeight();
|
||||
var badgeWidth = $badge.outerWidth();
|
||||
var popoverWidth = $popover.outerWidth();
|
||||
|
||||
var leftPos = badgeOffset.left + (badgeWidth / 2) - (popoverWidth / 2);
|
||||
var minLeft = 10;
|
||||
var maxLeft = $(window).width() - popoverWidth - 10;
|
||||
leftPos = Math.max(minLeft, Math.min(leftPos, maxLeft));
|
||||
|
||||
$popover.css({
|
||||
position: 'absolute',
|
||||
top: badgeOffset.top + badgeHeight + 8,
|
||||
left: leftPos,
|
||||
zIndex: 10000
|
||||
});
|
||||
|
||||
// Show with transition
|
||||
$popover.addClass('show');
|
||||
@@ -201,61 +216,6 @@
|
||||
return $popover;
|
||||
},
|
||||
|
||||
/**
|
||||
* Position a popover relative to a trigger element.
|
||||
* Handles horizontal and vertical viewport overflow.
|
||||
* @param {jQuery} $popover - The popover element
|
||||
* @param {jQuery} $trigger - The trigger element to position against
|
||||
* @param {number} [zIndex=10000] - Optional z-index
|
||||
*/
|
||||
positionPopover: function($popover, $trigger, zIndex) {
|
||||
var triggerRect = $trigger[0].getBoundingClientRect();
|
||||
var scrollTop = $(window).scrollTop();
|
||||
var scrollLeft = $(window).scrollLeft();
|
||||
var popoverWidth = $popover.outerWidth();
|
||||
var popoverHeight = $popover.outerHeight();
|
||||
var windowWidth = $(window).width();
|
||||
var windowHeight = $(window).height();
|
||||
var gap = 8;
|
||||
|
||||
// Horizontal: center on trigger, then clamp to viewport
|
||||
var left = triggerRect.left + scrollLeft + (triggerRect.width / 2) - (popoverWidth / 2);
|
||||
left = Math.max(10, Math.min(left, windowWidth - popoverWidth - 10));
|
||||
|
||||
// Vertical: prefer below, flip above if it would overflow
|
||||
var top;
|
||||
var positionAbove = false;
|
||||
if (triggerRect.bottom + popoverHeight + gap > windowHeight - 10) {
|
||||
// Position above the trigger
|
||||
top = triggerRect.top + scrollTop - popoverHeight - gap;
|
||||
positionAbove = true;
|
||||
} else {
|
||||
// Position below the trigger
|
||||
top = triggerRect.bottom + scrollTop + gap;
|
||||
}
|
||||
|
||||
$popover.css({
|
||||
position: 'absolute',
|
||||
top: top,
|
||||
left: left,
|
||||
zIndex: zIndex || 10000
|
||||
});
|
||||
|
||||
// Toggle arrow direction class
|
||||
$popover.toggleClass('position-above', positionAbove);
|
||||
|
||||
// Determine horizontal arrow position class
|
||||
var triggerCenter = triggerRect.left + (triggerRect.width / 2);
|
||||
var popoverLeft = left;
|
||||
var popoverCenter = popoverLeft + (popoverWidth / 2);
|
||||
$popover.removeClass('position-left position-right');
|
||||
if (triggerCenter - popoverLeft < popoverWidth * 0.3) {
|
||||
$popover.addClass('position-right');
|
||||
} else if (triggerCenter - popoverLeft > popoverWidth * 0.7) {
|
||||
$popover.addClass('position-left');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update popover after loading more items
|
||||
*/
|
||||
@@ -276,7 +236,7 @@
|
||||
|
||||
// Reset button state
|
||||
$btn.removeClass('loading');
|
||||
$btn.find('i').removeClass('icon-spinner icon-spin').addClass('icon-plus');
|
||||
$btn.find('i').replaceWith(this.esIcon('add'));
|
||||
$select.prop('disabled', false);
|
||||
|
||||
// Update remaining count
|
||||
@@ -326,7 +286,7 @@
|
||||
if (item.image) {
|
||||
html += '<img src="' + this.escapeAttr(item.image) + '" class="preview-item-image" alt="">';
|
||||
} else {
|
||||
html += '<div class="preview-item-icon"><i class="material-icons">inventory_2</i></div>';
|
||||
html += '<div class="preview-item-icon">' + self.esIcon('inventory_2') + '</div>';
|
||||
}
|
||||
|
||||
// Info section
|
||||
@@ -418,7 +378,7 @@
|
||||
$list.addClass('filtering');
|
||||
// Add overlay if not exists
|
||||
if (!$list.find('.filter-loading-overlay').length) {
|
||||
$list.append('<div class="filter-loading-overlay"><i class="icon-spinner icon-spin"></i></div>');
|
||||
$list.append('<div class="filter-loading-overlay">' + this.esIcon('progress_activity', 'es-spin') + '</div>');
|
||||
}
|
||||
} else {
|
||||
$list.removeClass('filtering');
|
||||
@@ -470,7 +430,7 @@
|
||||
var $select = $controls.find('.load-more-select');
|
||||
|
||||
$btn.removeClass('loading');
|
||||
$btn.find('i').removeClass('icon-spinner icon-spin').addClass('icon-plus');
|
||||
$btn.find('i').replaceWith(self.esIcon('add'));
|
||||
$select.prop('disabled', false);
|
||||
$controls.find('.remaining-count').text(remaining);
|
||||
|
||||
@@ -493,7 +453,7 @@
|
||||
footerHtml += '<option value="' + remaining + '">' + (trans.all || 'All') + ' (' + remaining + ')</option>';
|
||||
footerHtml += '</select>';
|
||||
footerHtml += '<span class="load-more-of">' + (trans.of || 'of') + ' <span class="remaining-count">' + remaining + '</span> ' + (trans.remaining || 'remaining') + '</span>';
|
||||
footerHtml += '<button type="button" class="btn-load-more"><i class="icon-plus"></i></button>';
|
||||
footerHtml += '<button type="button" class="btn-load-more">' + self.esIcon('add') + '</button>';
|
||||
footerHtml += '</div>';
|
||||
footerHtml += '</div>';
|
||||
|
||||
@@ -511,7 +471,7 @@
|
||||
if ($btn.hasClass('loading')) return;
|
||||
|
||||
$btn.addClass('loading');
|
||||
$btn.find('i').removeClass('icon-plus').addClass('icon-spinner icon-spin');
|
||||
$btn.find('i').replaceWith(self.esIcon('progress_activity', 'es-spin'));
|
||||
$select.prop('disabled', true);
|
||||
|
||||
var loadCount = parseInt($select.val(), 10) || 20;
|
||||
@@ -783,7 +743,7 @@
|
||||
var $controls = $btn.closest('.load-more-controls');
|
||||
var $select = $controls.find('.load-more-select');
|
||||
$btn.removeClass('loading');
|
||||
$btn.find('i').removeClass('icon-spinner icon-spin').addClass('icon-plus');
|
||||
$btn.find('i').replaceWith(self.esIcon('add'));
|
||||
$select.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
@@ -932,7 +892,7 @@
|
||||
var $controls = $btn.closest('.load-more-controls');
|
||||
var $select = $controls.find('.load-more-select');
|
||||
$btn.removeClass('loading');
|
||||
$btn.find('i').removeClass('icon-spinner icon-spin').addClass('icon-plus');
|
||||
$btn.find('i').replaceWith(self.esIcon('add'));
|
||||
$select.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
@@ -1220,144 +1180,6 @@
|
||||
});
|
||||
},
|
||||
|
||||
// =========================================================================
|
||||
// FILTER VALUE PREVIEW (individual attribute/feature value chip)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Show preview popover for a specific filter value (attribute or feature value)
|
||||
* @param {jQuery} $badge - The preview button on the chip
|
||||
* @param {number} valueId - The attribute/feature value ID
|
||||
* @param {string} valueType - 'attribute' or 'feature'
|
||||
* @param {string} valueName - Display name of the value
|
||||
* @param {number} groupId - The parent group ID
|
||||
*/
|
||||
showFilterValuePreviewPopover: function($badge, valueId, valueType, valueName, groupId) {
|
||||
var self = this;
|
||||
|
||||
this.hidePreviewPopover();
|
||||
|
||||
$badge.addClass('popover-open loading');
|
||||
this.$activeBadge = $badge;
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'previewFilterValueProducts',
|
||||
trait: 'EntitySelector',
|
||||
value_id: valueId,
|
||||
value_type: valueType,
|
||||
group_id: groupId,
|
||||
limit: 10
|
||||
},
|
||||
success: function(response) {
|
||||
$badge.removeClass('loading');
|
||||
|
||||
if (response.success) {
|
||||
self.createPreviewPopover({
|
||||
$badge: $badge,
|
||||
items: response.items || [],
|
||||
totalCount: response.count || 0,
|
||||
hasMore: response.hasMore || false,
|
||||
entityLabel: 'products',
|
||||
previewType: 'filter-value',
|
||||
context: { valueId: valueId, valueType: valueType, groupId: groupId, valueName: valueName },
|
||||
onLoadMore: function($btn) {
|
||||
self.loadMoreFilterValueItems($btn);
|
||||
},
|
||||
onFilter: function(query) {
|
||||
self.filterFilterValueItems(query);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$badge.removeClass('popover-open');
|
||||
self.$activeBadge = null;
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$badge.removeClass('loading popover-open');
|
||||
self.$activeBadge = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* AJAX filter handler for filter value preview
|
||||
*/
|
||||
filterFilterValueItems: function(query) {
|
||||
var self = this;
|
||||
var ctx = this.previewContext;
|
||||
|
||||
if (!ctx || !ctx.valueId) {
|
||||
self.showFilterLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'previewFilterValueProducts',
|
||||
trait: 'EntitySelector',
|
||||
value_id: ctx.valueId,
|
||||
value_type: ctx.valueType,
|
||||
group_id: ctx.groupId,
|
||||
filter: query,
|
||||
limit: 20
|
||||
},
|
||||
success: function(response) {
|
||||
self.updatePreviewPopoverFiltered(response);
|
||||
},
|
||||
error: function() {
|
||||
self.showFilterLoading(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadMoreFilterValueItems: function($btn) {
|
||||
var self = this;
|
||||
var ctx = this.previewContext;
|
||||
|
||||
if (!ctx || !ctx.valueId) return;
|
||||
|
||||
var loadCount = this.previewLoadCount || 20;
|
||||
|
||||
var ajaxData = {
|
||||
ajax: 1,
|
||||
action: 'previewFilterValueProducts',
|
||||
trait: 'EntitySelector',
|
||||
value_id: ctx.valueId,
|
||||
value_type: ctx.valueType,
|
||||
group_id: ctx.groupId,
|
||||
limit: self.previewLoadedCount + loadCount
|
||||
};
|
||||
if (self.previewCurrentFilter) {
|
||||
ajaxData.filter = self.previewCurrentFilter;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: ajaxData,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
self.previewTotalCount = response.count;
|
||||
self.updatePreviewPopover(response.items || [], response.hasMore);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$btn.removeClass('loading');
|
||||
$btn.find('i').removeClass('icon-spinner icon-spin').addClass('icon-plus');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// =========================================================================
|
||||
// CATEGORY ITEMS PREVIEW (products/pages in a category)
|
||||
// =========================================================================
|
||||
@@ -1425,7 +1247,7 @@
|
||||
var isProducts = (ctx.entityType === 'categories');
|
||||
var action = isProducts ? 'previewCategoryProducts' : 'previewCategoryPages';
|
||||
|
||||
$btn.prop('disabled', true).find('i').addClass('icon-spin');
|
||||
$btn.prop('disabled', true).find('i').addClass('es-spin');
|
||||
|
||||
$.ajax({
|
||||
url: this.config.ajaxUrl,
|
||||
@@ -1441,7 +1263,7 @@
|
||||
query: this.previewFilterQuery || ''
|
||||
},
|
||||
success: function(response) {
|
||||
$btn.prop('disabled', false).find('i').removeClass('icon-spin');
|
||||
$btn.prop('disabled', false).find('i').removeClass('es-spin');
|
||||
|
||||
if (response.success && response.items) {
|
||||
self.appendPreviewItems(response.items);
|
||||
@@ -1453,7 +1275,7 @@
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$btn.prop('disabled', false).find('i').removeClass('icon-spin');
|
||||
$btn.prop('disabled', false).find('i').removeClass('es-spin');
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -1513,13 +1335,13 @@
|
||||
html += '<div class="pattern-preview-modal">';
|
||||
html += '<div class="pattern-preview-header">';
|
||||
html += '<span class="pattern-preview-title">';
|
||||
html += '<i class="icon-eye"></i> ' + (trans.preview || 'Preview') + ': <code>' + this.escapeHtml(pattern) + '</code>';
|
||||
html += this.esIcon('visibility') + ' ' + (trans.preview || 'Preview') + ': <code>' + this.escapeHtml(pattern) + '</code>';
|
||||
html += '</span>';
|
||||
html += '<span class="pattern-preview-count">' + count + ' ' + (count === 1 ? entityLabelSingular : entityLabelPlural) + '</span>';
|
||||
html += '<button type="button" class="pattern-preview-close"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="pattern-preview-close">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
html += '<div class="pattern-preview-content">';
|
||||
html += '<div class="pattern-preview-loading"><i class="icon-spinner icon-spin"></i> ' + (trans.loading || 'Loading...') + '</div>';
|
||||
html += '<div class="pattern-preview-loading">' + this.esIcon('progress_activity', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
@@ -1650,7 +1472,7 @@
|
||||
|
||||
if (count > 0) {
|
||||
var blockConfig = self.config.blocks && self.config.blocks[blockType] ? self.config.blocks[blockType] : {};
|
||||
var icon = $tab.find('.tab-label').prev('i').attr('class') || 'icon-cube';
|
||||
var icon = $tab.find('.tab-label').prev('i').text() || 'widgets';
|
||||
var label = $tab.find('.tab-label').text() || blockType;
|
||||
|
||||
summaryItems.push({
|
||||
@@ -1675,7 +1497,7 @@
|
||||
for (var i = 0; i < summaryItems.length; i++) {
|
||||
var item = summaryItems[i];
|
||||
popoverHtml += '<li class="total-summary-item" data-block-type="' + item.blockType + '">';
|
||||
popoverHtml += '<i class="' + self.escapeAttr(item.icon) + '"></i>';
|
||||
popoverHtml += this.esIcon(item.icon);
|
||||
popoverHtml += '<span class="summary-item-label">' + self.escapeHtml(item.label) + '</span>';
|
||||
popoverHtml += '<span class="summary-item-count">' + item.count + '</span>';
|
||||
popoverHtml += '</li>';
|
||||
@@ -1695,9 +1517,28 @@
|
||||
self.switchToBlock(blockType);
|
||||
});
|
||||
|
||||
// Position popover relative to badge (handles viewport overflow)
|
||||
// Position popover
|
||||
$('body').append($popover);
|
||||
this.positionPopover($popover, $badge);
|
||||
var badgeOffset = $badge.offset();
|
||||
var badgeHeight = $badge.outerHeight();
|
||||
var popoverWidth = $popover.outerWidth();
|
||||
|
||||
$popover.css({
|
||||
position: 'absolute',
|
||||
top: badgeOffset.top + badgeHeight + 5,
|
||||
left: badgeOffset.left - (popoverWidth / 2) + ($badge.outerWidth() / 2),
|
||||
zIndex: 10000
|
||||
});
|
||||
|
||||
// Adjust if off screen
|
||||
var windowWidth = $(window).width();
|
||||
var popoverRight = $popover.offset().left + popoverWidth;
|
||||
if (popoverRight > windowWidth - 10) {
|
||||
$popover.css('left', windowWidth - popoverWidth - 10);
|
||||
}
|
||||
if ($popover.offset().left < 10) {
|
||||
$popover.css('left', 10);
|
||||
}
|
||||
|
||||
$popover.hide().fadeIn(150);
|
||||
},
|
||||
@@ -1729,10 +1570,10 @@
|
||||
}
|
||||
popoverHtml += this.escapeHtml(countryName) + ' - ' + (trans.holidays || 'Holidays');
|
||||
popoverHtml += '</span>';
|
||||
popoverHtml += '<button type="button" class="popover-close"><i class="material-icons">close</i></button>';
|
||||
popoverHtml += '<button type="button" class="popover-close">' + this.esIcon('close') + '</button>';
|
||||
popoverHtml += '</div>';
|
||||
popoverHtml += '<div class="popover-body">';
|
||||
popoverHtml += '<div class="holiday-preview-loading"><i class="material-icons icon-spin">sync</i> ' + (trans.loading || 'Loading...') + '</div>';
|
||||
popoverHtml += '<div class="holiday-preview-loading">' + this.esIcon('sync', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</div>';
|
||||
popoverHtml += '</div>';
|
||||
popoverHtml += '</div>';
|
||||
|
||||
@@ -1819,7 +1660,7 @@
|
||||
$popover.find('.popover-body').html(listHtml);
|
||||
} else {
|
||||
var noDataHtml = '<div class="holiday-preview-empty">';
|
||||
noDataHtml += '<i class="material-icons">event_busy</i>';
|
||||
noDataHtml += self.esIcon('event_busy');
|
||||
noDataHtml += '<p>' + (trans.no_holidays || 'No holidays found') + '</p>';
|
||||
noDataHtml += '</div>';
|
||||
$popover.find('.popover-body').html(noDataHtml);
|
||||
@@ -1836,7 +1677,7 @@
|
||||
},
|
||||
error: function() {
|
||||
var errorHtml = '<div class="holiday-preview-empty">';
|
||||
errorHtml += '<i class="material-icons">error_outline</i>';
|
||||
errorHtml += self.esIcon('error');
|
||||
errorHtml += '<p>' + (trans.error_loading || 'Error loading holidays') + '</p>';
|
||||
errorHtml += '</div>';
|
||||
$popover.find('.popover-body').html(errorHtml);
|
||||
@@ -1864,15 +1705,15 @@
|
||||
// Create popover HTML with placeholder title (will update after AJAX)
|
||||
var popoverHtml = '<div class="holiday-preview-popover target-preview-popover show">';
|
||||
popoverHtml += '<div class="popover-header">';
|
||||
popoverHtml += '<span class="popover-title"><i class="material-icons icon-spin">sync</i> ' + (trans.loading || 'Loading...') + '</span>';
|
||||
popoverHtml += '<button type="button" class="popover-close"><i class="material-icons">close</i></button>';
|
||||
popoverHtml += '<span class="popover-title">' + this.esIcon('sync', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</span>';
|
||||
popoverHtml += '<button type="button" class="popover-close">' + this.esIcon('close') + '</button>';
|
||||
popoverHtml += '</div>';
|
||||
popoverHtml += '<div class="popover-filter">';
|
||||
popoverHtml += '<i class="material-icons">search</i>';
|
||||
popoverHtml += this.esIcon('search');
|
||||
popoverHtml += '<input type="text" class="holiday-filter-input" placeholder="' + (trans.filter_holidays || 'Filter by country, date, name...') + '">';
|
||||
popoverHtml += '</div>';
|
||||
popoverHtml += '<div class="popover-body">';
|
||||
popoverHtml += '<div class="holiday-preview-loading"><i class="material-icons icon-spin">sync</i> ' + (trans.loading || 'Loading...') + '</div>';
|
||||
popoverHtml += '<div class="holiday-preview-loading">' + this.esIcon('sync', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</div>';
|
||||
popoverHtml += '</div>';
|
||||
popoverHtml += '</div>';
|
||||
|
||||
@@ -2060,7 +1901,7 @@
|
||||
$popover.find('.popover-title').html('0 ' + (trans.holidays || 'Holidays'));
|
||||
|
||||
var noDataHtml = '<div class="holiday-preview-empty">';
|
||||
noDataHtml += '<i class="material-icons">event_busy</i>';
|
||||
noDataHtml += self.esIcon('event_busy');
|
||||
noDataHtml += '<p>' + (trans.no_holidays || 'No holidays found') + '</p>';
|
||||
noDataHtml += '</div>';
|
||||
$popover.find('.popover-body').html(noDataHtml);
|
||||
@@ -2077,10 +1918,10 @@
|
||||
},
|
||||
error: function() {
|
||||
// Update header for error state
|
||||
$popover.find('.popover-title').html('<i class="material-icons">error_outline</i> ' + (trans.error || 'Error'));
|
||||
$popover.find('.popover-title').html(self.esIcon('error') + ' ' + (trans.error || 'Error'));
|
||||
|
||||
var errorHtml = '<div class="holiday-preview-empty">';
|
||||
errorHtml += '<i class="material-icons">error_outline</i>';
|
||||
errorHtml += self.esIcon('error');
|
||||
errorHtml += '<p>' + (trans.error_loading || 'Error loading holidays') + '</p>';
|
||||
errorHtml += '</div>';
|
||||
$popover.find('.popover-body').html(errorHtml);
|
||||
|
||||
@@ -40,11 +40,6 @@
|
||||
sort_dir: this.currentSort ? this.currentSort.dir : 'ASC'
|
||||
};
|
||||
|
||||
// Add product_selection_level if set
|
||||
if (this.config.productSelectionLevel && this.config.productSelectionLevel !== 'product') {
|
||||
requestData.product_selection_level = this.config.productSelectionLevel;
|
||||
}
|
||||
|
||||
// Add refine query if present
|
||||
if (this.refineQuery) {
|
||||
requestData.refine = this.refineQuery;
|
||||
@@ -366,14 +361,12 @@
|
||||
|
||||
var html = '';
|
||||
if (visibleResults.length === 0 && !appendMode) {
|
||||
html = '<div class="no-results"><i class="icon-search"></i> ' + (trans.no_results || 'No results found') + '</div>';
|
||||
html = '<div class="no-results">' + this.esIcon('search') + ' ' + (trans.no_results || 'No results found') + '</div>';
|
||||
} else {
|
||||
visibleResults.forEach(function(item) {
|
||||
var isSelected = selectedIds.indexOf(String(item.id)) !== -1;
|
||||
var itemClass = 'dropdown-item' + (isSelected ? ' selected' : '');
|
||||
if (item.type === 'product') itemClass += ' result-item-product';
|
||||
if (item.is_combination) itemClass += ' is-combination';
|
||||
if (item.is_parent) itemClass += ' is-parent-product';
|
||||
|
||||
html += '<div class="' + itemClass + '" ';
|
||||
html += 'data-id="' + self.escapeAttr(item.id) + '" ';
|
||||
@@ -381,30 +374,29 @@
|
||||
if (item.image) html += ' data-image="' + self.escapeAttr(item.image) + '"';
|
||||
if (item.subtitle) html += ' data-subtitle="' + self.escapeAttr(item.subtitle) + '"';
|
||||
if (item.iso_code) html += ' data-iso="' + self.escapeAttr(item.iso_code) + '"';
|
||||
if (item.attributes) html += ' data-attributes="' + self.escapeAttr(item.attributes) + '"';
|
||||
html += '>';
|
||||
|
||||
html += '<span class="result-checkbox"><i class="icon-check"></i></span>';
|
||||
html += '<span class="result-checkbox">' + self.esIcon('check') + '</span>';
|
||||
|
||||
var searchEntity = self.activeGroup ? self.activeGroup.searchEntity : null;
|
||||
|
||||
// Countries show flags
|
||||
if (searchEntity === 'countries' && item.iso_code) {
|
||||
var flagUrl = 'https://flagcdn.com/w40/' + item.iso_code.toLowerCase() + '.png';
|
||||
html += '<div class="result-image result-flag"><img src="' + self.escapeAttr(flagUrl) + '" alt="' + self.escapeAttr(item.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\';"><span class="flag-fallback" style="display:none;"><i class="icon-flag"></i></span></div>';
|
||||
html += '<div class="result-image result-flag"><img src="' + self.escapeAttr(flagUrl) + '" alt="' + self.escapeAttr(item.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\';"><span class="flag-fallback" style="display:none;">' + self.esIcon('flag') + '</span></div>';
|
||||
} else if (item.image) {
|
||||
html += '<div class="result-image"><img src="' + self.escapeAttr(item.image) + '" alt=""></div>';
|
||||
} else {
|
||||
// Entity-specific icons
|
||||
var iconClass = 'icon-cube'; // default
|
||||
if (searchEntity === 'categories') iconClass = 'icon-folder';
|
||||
else if (searchEntity === 'manufacturers') iconClass = 'icon-building';
|
||||
else if (searchEntity === 'suppliers') iconClass = 'icon-truck';
|
||||
else if (searchEntity === 'attributes') iconClass = 'icon-paint-brush';
|
||||
else if (searchEntity === 'features') iconClass = 'icon-list-ul';
|
||||
else if (searchEntity === 'cms') iconClass = 'icon-file-text-o';
|
||||
else if (searchEntity === 'cms_categories') iconClass = 'icon-folder-o';
|
||||
html += '<div class="result-icon"><i class="' + iconClass + '"></i></div>';
|
||||
var iconName = 'widgets'; // default
|
||||
if (searchEntity === 'categories') iconName = 'folder';
|
||||
else if (searchEntity === 'manufacturers') iconName = 'business';
|
||||
else if (searchEntity === 'suppliers') iconName = 'local_shipping';
|
||||
else if (searchEntity === 'attributes') iconName = 'brush';
|
||||
else if (searchEntity === 'features') iconName = 'list';
|
||||
else if (searchEntity === 'cms') iconName = 'description';
|
||||
else if (searchEntity === 'cms_categories') iconName = 'folder';
|
||||
html += '<div class="result-icon">' + self.esIcon(iconName) + '</div>';
|
||||
}
|
||||
|
||||
html += '<div class="result-info">';
|
||||
@@ -596,10 +588,10 @@
|
||||
for (var i = 0; i < history.length; i++) {
|
||||
var query = history[i];
|
||||
html += '<div class="history-item" data-query="' + this.escapeAttr(query) + '">';
|
||||
html += '<i class="icon-clock-o"></i>';
|
||||
html += this.esIcon('schedule');
|
||||
html += '<span class="history-query">' + this.escapeHtml(query) + '</span>';
|
||||
html += '<button type="button" class="btn-delete-history" title="' + (trans.remove || 'Remove') + '">';
|
||||
html += '<i class="icon-times"></i>';
|
||||
html += this.esIcon('close');
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
var searchEntity = this.activeGroup ? this.activeGroup.searchEntity : 'categories';
|
||||
|
||||
// Show loading
|
||||
$results.html('<div class="tree-loading"><i class="icon-spinner icon-spin"></i> ' +
|
||||
$results.html('<div class="tree-loading">' + this.esIcon('progress_activity', 'es-spin') + ' ' +
|
||||
this.escapeHtml(trans.loading || 'Loading...') + '</div>');
|
||||
|
||||
// Fetch tree data
|
||||
@@ -115,11 +115,11 @@
|
||||
html += '<div class="tree-toolbar">';
|
||||
html += '<button type="button" class="btn-expand-all" title="' +
|
||||
this.escapeAttr(trans.expand_all || 'Expand all') + '">';
|
||||
html += '<i class="icon-plus-square-o"></i> ' + this.escapeHtml(trans.expand_all || 'Expand all');
|
||||
html += this.esIcon('add_box') + ' ' + this.escapeHtml(trans.expand_all || 'Expand all');
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="btn-collapse-all" title="' +
|
||||
this.escapeAttr(trans.collapse_all || 'Collapse all') + '">';
|
||||
html += '<i class="icon-minus-square-o"></i> ' + this.escapeHtml(trans.collapse_all || 'Collapse all');
|
||||
html += this.esIcon('indeterminate_check_box') + ' ' + this.escapeHtml(trans.collapse_all || 'Collapse all');
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
|
||||
@@ -178,21 +178,21 @@
|
||||
|
||||
// Toggle button (expand/collapse)
|
||||
if (hasChildren) {
|
||||
html += '<span class="tree-toggle"><i class="icon-caret-down"></i></span>';
|
||||
html += '<span class="tree-toggle">' + self.esIcon('arrow_drop_down') + '</span>';
|
||||
// Select with children button (next to toggle on the left)
|
||||
html += '<button type="button" class="btn-select-children" title="' +
|
||||
self.escapeAttr(trans.select_with_children || 'Select with all children') + '">';
|
||||
html += '<i class="icon-check-square-o"></i>';
|
||||
html += self.esIcon('check_box');
|
||||
html += '</button>';
|
||||
} else {
|
||||
html += '<span class="tree-toggle tree-leaf"></span>';
|
||||
}
|
||||
|
||||
// Checkbox indicator
|
||||
html += '<span class="tree-checkbox"><i class="icon-check"></i></span>';
|
||||
html += '<span class="tree-checkbox">' + self.esIcon('check') + '</span>';
|
||||
|
||||
// Category icon
|
||||
html += '<span class="tree-icon"><i class="icon-folder"></i></span>';
|
||||
html += '<span class="tree-icon">' + self.esIcon('folder') + '</span>';
|
||||
|
||||
// Name
|
||||
html += '<span class="tree-name">' + self.escapeHtml(node.name) + '</span>';
|
||||
@@ -203,7 +203,7 @@
|
||||
var countLabel = node.page_count ? (trans.pages || 'pages') : (trans.products || 'products');
|
||||
html += '<span class="tree-count clickable" data-category-id="' + node.id + '" ';
|
||||
html += 'title="' + self.escapeAttr(itemCount + ' ' + countLabel) + '">';
|
||||
html += '<i class="icon-eye"></i> ' + itemCount;
|
||||
html += self.esIcon('visibility') + ' ' + itemCount;
|
||||
html += '</span>';
|
||||
}
|
||||
|
||||
@@ -346,10 +346,10 @@
|
||||
});
|
||||
|
||||
if (isParentSelected && allChildrenSelected) {
|
||||
$btn.find('i').removeClass('icon-plus-square').addClass('icon-minus-square');
|
||||
$btn.find('i').replaceWith(self.esIcon('indeterminate_check_box'));
|
||||
$btn.attr('title', trans.deselect_with_children || 'Deselect with all children');
|
||||
} else {
|
||||
$btn.find('i').removeClass('icon-minus-square').addClass('icon-plus-square');
|
||||
$btn.find('i').replaceWith(self.esIcon('add_box'));
|
||||
$btn.attr('title', trans.select_with_children || 'Select with all children');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,6 +19,97 @@
|
||||
// Create mixin namespace
|
||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Icon framework detection & FA4 mapping (module-level singleton)
|
||||
// ---------------------------------------------------------------
|
||||
var _iconMode = null;
|
||||
|
||||
/**
|
||||
* Material Icons → FontAwesome 4 class mapping.
|
||||
* FA4 uses class-based icons (icon-name), Material uses text content.
|
||||
*/
|
||||
var FA4_MAP = {
|
||||
'account_tree': 'icon-sitemap',
|
||||
'add': 'icon-plus',
|
||||
'add_box': 'icon-plus-square',
|
||||
'arrow_downward': 'icon-sort-desc',
|
||||
'arrow_drop_down': 'icon-caret-down',
|
||||
'arrow_right': 'icon-chevron-right',
|
||||
'arrow_upward': 'icon-sort-asc',
|
||||
'block': 'icon-ban',
|
||||
'brush': 'icon-paint-brush',
|
||||
'business': 'icon-building',
|
||||
'check': 'icon-check',
|
||||
'check_box': 'icon-check-square',
|
||||
'check_box_outline_blank': 'icon-square-o',
|
||||
'check_circle': 'icon-check-circle',
|
||||
'close': 'icon-times',
|
||||
'delete': 'icon-trash',
|
||||
'description': 'icon-file-text',
|
||||
'error': 'icon-exclamation-circle',
|
||||
'event': 'icon-calendar',
|
||||
'event_busy': 'icon-calendar-times-o',
|
||||
'expand_less': 'icon-chevron-up',
|
||||
'expand_more': 'icon-chevron-down',
|
||||
'filter_list': 'icon-filter',
|
||||
'flag': 'icon-flag',
|
||||
'folder': 'icon-folder',
|
||||
'folder_open': 'icon-folder-open',
|
||||
'indeterminate_check_box': 'icon-minus-square',
|
||||
'info': 'icon-info-circle',
|
||||
'inventory_2': 'icon-archive',
|
||||
'label': 'icon-tag',
|
||||
'language': 'icon-globe',
|
||||
'lightbulb': 'icon-lightbulb-o',
|
||||
'list': 'icon-list',
|
||||
'list_alt': 'icon-list-alt',
|
||||
'local_shipping': 'icon-truck',
|
||||
'lock': 'icon-lock',
|
||||
'my_location': 'icon-crosshairs',
|
||||
'open_in_full': 'icon-expand',
|
||||
'payments': 'icon-credit-card',
|
||||
'progress_activity': 'icon-circle-o-notch',
|
||||
'schedule': 'icon-clock-o',
|
||||
'search': 'icon-search',
|
||||
'shopping_cart': 'icon-shopping-cart',
|
||||
'shuffle': 'icon-random',
|
||||
'sort': 'icon-sort',
|
||||
'sort_by_alpha': 'icon-sort-alpha-asc',
|
||||
'star': 'icon-star',
|
||||
'sync': 'icon-refresh',
|
||||
'tune': 'icon-sliders',
|
||||
'visibility': 'icon-eye',
|
||||
'warning': 'icon-warning',
|
||||
'widgets': 'icon-th-large'
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect icon framework: 'material' (PS 8+/9+) or 'fa4' (PS 1.6/1.7).
|
||||
* Checks PHP-set data attribute first, falls back to font detection.
|
||||
*/
|
||||
function detectIconMode() {
|
||||
if (_iconMode !== null) return _iconMode;
|
||||
|
||||
// 1. PHP sets data-icon-mode on the wrapper
|
||||
var $w = $('.entity-selector-trait[data-icon-mode], .target-conditions-trait[data-icon-mode]').first();
|
||||
if ($w.length && $w.data('icon-mode')) {
|
||||
_iconMode = $w.data('icon-mode');
|
||||
return _iconMode;
|
||||
}
|
||||
|
||||
// 2. Fallback: probe whether Material Icons font is loaded
|
||||
var test = document.createElement('i');
|
||||
test.className = 'material-icons';
|
||||
test.style.cssText = 'position:absolute;left:-9999px;top:-9999px;font-size:16px;pointer-events:none';
|
||||
test.textContent = 'check';
|
||||
(document.body || document.documentElement).appendChild(test);
|
||||
var family = (window.getComputedStyle(test).fontFamily || '').toLowerCase();
|
||||
test.parentNode.removeChild(test);
|
||||
|
||||
_iconMode = (family.indexOf('material') !== -1) ? 'material' : 'fa4';
|
||||
return _iconMode;
|
||||
}
|
||||
|
||||
// Utility functions mixin
|
||||
window._EntitySelectorMixins.utils = {
|
||||
|
||||
@@ -60,18 +151,62 @@
|
||||
.replace(/'/g, ''');
|
||||
},
|
||||
|
||||
/**
|
||||
* Icon helper — returns HTML for an icon that works on PS 1.6 through 9.x.
|
||||
* Automatically uses Material Icons (PS 8+/9+) or FontAwesome 4 (PS 1.6/1.7).
|
||||
*
|
||||
* @param {string} name - Canonical icon name (Material Icons name, e.g. 'lock', 'search', 'delete')
|
||||
* @param {string} [extraClass] - Additional CSS class(es) (e.g. 'es-spin', 'method-trigger-icon')
|
||||
* @returns {string} HTML string for an <i> element
|
||||
*/
|
||||
esIcon: function(name, extraClass) {
|
||||
var mode = detectIconMode();
|
||||
if (mode === 'material') {
|
||||
var cls = 'material-icons es-icon';
|
||||
if (extraClass) cls += ' ' + extraClass;
|
||||
return '<i class="' + cls + '">' + name + '</i>';
|
||||
}
|
||||
// FA4: icon is encoded in the class name, no text content
|
||||
var mapped = FA4_MAP[name] || 'icon-circle';
|
||||
var cls = mapped + ' es-icon';
|
||||
if (extraClass) cls += ' ' + extraClass;
|
||||
return '<i class="' + cls + '"></i>';
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an existing <i> icon element to show a different icon.
|
||||
* Handles both Material Icons and FA4 modes.
|
||||
*
|
||||
* @param {jQuery} $el - The <i> element to update
|
||||
* @param {string} name - Canonical icon name
|
||||
* @param {string} [extraClass] - Additional CSS class(es) to preserve
|
||||
*/
|
||||
esIconUpdate: function($el, name, extraClass) {
|
||||
var mode = detectIconMode();
|
||||
if (mode === 'material') {
|
||||
var cls = 'material-icons es-icon';
|
||||
if (extraClass) cls += ' ' + extraClass;
|
||||
$el.attr('class', cls).text(name);
|
||||
} else {
|
||||
var mapped = FA4_MAP[name] || 'icon-circle';
|
||||
var cls = mapped + ' es-icon';
|
||||
if (extraClass) cls += ' ' + extraClass;
|
||||
$el.attr('class', cls).text('');
|
||||
}
|
||||
},
|
||||
|
||||
getEntityTypeIcon: function(entityType) {
|
||||
var icons = {
|
||||
'products': 'icon-shopping-cart',
|
||||
'categories': 'icon-folder-open',
|
||||
'manufacturers': 'icon-building',
|
||||
'suppliers': 'icon-truck',
|
||||
'attributes': 'icon-list-alt',
|
||||
'features': 'icon-tags',
|
||||
'cms': 'icon-file-text',
|
||||
'cms_categories': 'icon-folder'
|
||||
'products': 'shopping_cart',
|
||||
'categories': 'folder_open',
|
||||
'manufacturers': 'business',
|
||||
'suppliers': 'local_shipping',
|
||||
'attributes': 'list_alt',
|
||||
'features': 'label',
|
||||
'cms': 'description',
|
||||
'cms_categories': 'folder'
|
||||
};
|
||||
return icons[entityType] || 'icon-cube';
|
||||
return icons[entityType] || 'widgets';
|
||||
},
|
||||
|
||||
getEntityTypeLabel: function(entityType) {
|
||||
@@ -116,7 +251,7 @@
|
||||
this.$wrapper.find('.trait-validation-error').remove();
|
||||
var $error = $('<div>', {
|
||||
class: 'trait-validation-error',
|
||||
html: '<i class="icon-warning"></i> ' + message
|
||||
html: this.esIcon('warning') + ' ' + message
|
||||
});
|
||||
this.$wrapper.find('.condition-trait-header').after($error);
|
||||
$('html, body').animate({ scrollTop: this.$wrapper.offset().top - 100 }, 300);
|
||||
|
||||
@@ -297,12 +297,12 @@
|
||||
|
||||
// Create toast HTML
|
||||
var html = '<div class="es-validation-toast">';
|
||||
html += '<div class="es-toast-icon"><i class="icon-exclamation-triangle"></i></div>';
|
||||
html += '<div class="es-toast-icon">' + this.esIcon('warning') + '</div>';
|
||||
html += '<div class="es-toast-content">';
|
||||
html += '<div class="es-toast-title">' + this.escapeHtml(title) + '</div>';
|
||||
html += '<div class="es-toast-message">' + this.escapeHtml(message) + '</div>';
|
||||
html += '</div>';
|
||||
html += '<button type="button" class="es-toast-close"><i class="icon-times"></i></button>';
|
||||
html += '<button type="button" class="es-toast-close">' + this.esIcon('close') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
var $toast = $(html);
|
||||
|
||||
Reference in New Issue
Block a user