Add clickable preview popover to filter group toggles
- Add showFilterGroupPreviewPopover method in _preview.js - Make toggle-count badges clickable with data attributes - Add event binding for .toggle-count.clickable in _events.js - Add hover/active/loading styles for clickable toggle-count - Requires previewFilterGroupProducts AJAX handler in PHP backend Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -231,6 +231,23 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Filter group toggle count badge click for preview popover
|
||||||
|
$(document).on('click', '.filter-group-toggle .toggle-count.clickable', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $badge = $(this);
|
||||||
|
var groupId = $badge.data('groupId');
|
||||||
|
var groupType = $badge.data('type');
|
||||||
|
var groupName = $badge.data('groupName');
|
||||||
|
|
||||||
|
if ($badge.hasClass('popover-open')) {
|
||||||
|
self.hidePreviewPopover();
|
||||||
|
} else {
|
||||||
|
self.showFilterGroupPreviewPopover($badge, groupId, groupType, groupName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Close popover when clicking outside
|
// Close popover when clicking outside
|
||||||
$(document).on('click', function(e) {
|
$(document).on('click', function(e) {
|
||||||
if (!$(e.target).closest('.target-preview-popover').length &&
|
if (!$(e.target).closest('.target-preview-popover').length &&
|
||||||
@@ -238,7 +255,8 @@
|
|||||||
!$(e.target).closest('.condition-match-count').length &&
|
!$(e.target).closest('.condition-match-count').length &&
|
||||||
!$(e.target).closest('.group-count-badge').length &&
|
!$(e.target).closest('.group-count-badge').length &&
|
||||||
!$(e.target).closest('.group-modifiers').length &&
|
!$(e.target).closest('.group-modifiers').length &&
|
||||||
!$(e.target).closest('.group-preview-badge').length) {
|
!$(e.target).closest('.group-preview-badge').length &&
|
||||||
|
!$(e.target).closest('.toggle-count.clickable').length) {
|
||||||
self.hidePreviewPopover();
|
self.hidePreviewPopover();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3501,10 +3519,10 @@
|
|||||||
|
|
||||||
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
||||||
this.filterableData.attributes.forEach(function(group) {
|
this.filterableData.attributes.forEach(function(group) {
|
||||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute">';
|
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>';
|
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||||
if (group.count !== undefined) {
|
if (group.count !== undefined) {
|
||||||
html += '<span class="toggle-count"><i class="icon-eye"></i> (' + group.count + ')</span>';
|
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '"><i class="icon-eye"></i> ' + group.count + '</span>';
|
||||||
}
|
}
|
||||||
html += '</button>';
|
html += '</button>';
|
||||||
$attrContainer.append(html);
|
$attrContainer.append(html);
|
||||||
@@ -3518,10 +3536,10 @@
|
|||||||
|
|
||||||
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
||||||
this.filterableData.features.forEach(function(group) {
|
this.filterableData.features.forEach(function(group) {
|
||||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature">';
|
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>';
|
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||||
if (group.count !== undefined) {
|
if (group.count !== undefined) {
|
||||||
html += '<span class="toggle-count"><i class="icon-eye"></i> (' + group.count + ')</span>';
|
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '"><i class="icon-eye"></i> ' + group.count + '</span>';
|
||||||
}
|
}
|
||||||
html += '</button>';
|
html += '</button>';
|
||||||
$featContainer.append(html);
|
$featContainer.append(html);
|
||||||
@@ -7580,6 +7598,118 @@
|
|||||||
price = parseFloat(price) || 0;
|
price = parseFloat(price) || 0;
|
||||||
}
|
}
|
||||||
return price.toFixed(2) + ' €';
|
return price.toFixed(2) + ' €';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show preview popover for filter group toggle (attribute/feature groups)
|
||||||
|
*/
|
||||||
|
showFilterGroupPreviewPopover: function($badge, groupId, groupType, groupName) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.hidePreviewPopover();
|
||||||
|
|
||||||
|
$badge.addClass('popover-open loading');
|
||||||
|
this.$activeBadge = $badge;
|
||||||
|
|
||||||
|
var trans = this.config.trans || {};
|
||||||
|
var entityLabelPlural = 'products';
|
||||||
|
|
||||||
|
// Fetch products matching this attribute/feature group
|
||||||
|
$.ajax({
|
||||||
|
url: this.config.ajaxUrl,
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
ajax: 1,
|
||||||
|
action: 'previewFilterGroupProducts',
|
||||||
|
trait: 'EntitySelector',
|
||||||
|
group_id: groupId,
|
||||||
|
group_type: groupType,
|
||||||
|
limit: 10
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$badge.removeClass('loading');
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
var items = response.items || [];
|
||||||
|
var totalCount = response.count || 0;
|
||||||
|
var hasMore = response.hasMore || false;
|
||||||
|
|
||||||
|
self.showFilterGroupItemsPopover($badge, items, totalCount, hasMore, entityLabelPlural, groupName, groupType);
|
||||||
|
} else {
|
||||||
|
$badge.removeClass('popover-open');
|
||||||
|
self.$activeBadge = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$badge.removeClass('loading popover-open');
|
||||||
|
self.$activeBadge = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show popover for filter group preview items
|
||||||
|
*/
|
||||||
|
showFilterGroupItemsPopover: function($badge, items, totalCount, hasMore, entityLabel, groupName, groupType) {
|
||||||
|
var self = this;
|
||||||
|
var trans = this.config.trans || {};
|
||||||
|
|
||||||
|
var typeLabel = groupType === 'attribute' ? (trans.attribute || 'Attribute') : (trans.feature || 'Feature');
|
||||||
|
var html = '<div class="target-preview-popover preview-type-filter-group">';
|
||||||
|
|
||||||
|
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 += '</div>';
|
||||||
|
|
||||||
|
if (items.length > 0) {
|
||||||
|
html += '<div class="preview-list">';
|
||||||
|
html += this.renderPreviewItems(items);
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
if (hasMore) {
|
||||||
|
var remaining = totalCount - items.length;
|
||||||
|
html += '<div class="preview-footer">';
|
||||||
|
html += '<span class="preview-more-info">+ ' + remaining + ' ' + (trans.more || 'more') + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html += '<div class="preview-empty">' + (trans.no_preview || 'No items to preview') + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
var $popover = $(html);
|
||||||
|
$('body').append($popover);
|
||||||
|
|
||||||
|
$popover.find('.preview-close').on('click', function() {
|
||||||
|
self.hidePreviewPopover();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
var topPos = badgeOffset.top + badgeHeight + 8;
|
||||||
|
|
||||||
|
$popover.css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: topPos,
|
||||||
|
left: leftPos,
|
||||||
|
zIndex: 10000
|
||||||
|
});
|
||||||
|
|
||||||
|
$popover.addClass('show');
|
||||||
|
|
||||||
|
this.$previewPopover = $popover;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
2
assets/js/admin/entity-selector.min.js
vendored
2
assets/js/admin/entity-selector.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -91,6 +91,23 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Filter group toggle count badge click for preview popover
|
||||||
|
$(document).on('click', '.filter-group-toggle .toggle-count.clickable', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $badge = $(this);
|
||||||
|
var groupId = $badge.data('groupId');
|
||||||
|
var groupType = $badge.data('type');
|
||||||
|
var groupName = $badge.data('groupName');
|
||||||
|
|
||||||
|
if ($badge.hasClass('popover-open')) {
|
||||||
|
self.hidePreviewPopover();
|
||||||
|
} else {
|
||||||
|
self.showFilterGroupPreviewPopover($badge, groupId, groupType, groupName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Close popover when clicking outside
|
// Close popover when clicking outside
|
||||||
$(document).on('click', function(e) {
|
$(document).on('click', function(e) {
|
||||||
if (!$(e.target).closest('.target-preview-popover').length &&
|
if (!$(e.target).closest('.target-preview-popover').length &&
|
||||||
@@ -98,7 +115,8 @@
|
|||||||
!$(e.target).closest('.condition-match-count').length &&
|
!$(e.target).closest('.condition-match-count').length &&
|
||||||
!$(e.target).closest('.group-count-badge').length &&
|
!$(e.target).closest('.group-count-badge').length &&
|
||||||
!$(e.target).closest('.group-modifiers').length &&
|
!$(e.target).closest('.group-modifiers').length &&
|
||||||
!$(e.target).closest('.group-preview-badge').length) {
|
!$(e.target).closest('.group-preview-badge').length &&
|
||||||
|
!$(e.target).closest('.toggle-count.clickable').length) {
|
||||||
self.hidePreviewPopover();
|
self.hidePreviewPopover();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -161,10 +161,10 @@
|
|||||||
|
|
||||||
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
||||||
this.filterableData.attributes.forEach(function(group) {
|
this.filterableData.attributes.forEach(function(group) {
|
||||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute">';
|
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>';
|
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||||
if (group.count !== undefined) {
|
if (group.count !== undefined) {
|
||||||
html += '<span class="toggle-count"><i class="icon-eye"></i> (' + group.count + ')</span>';
|
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '"><i class="icon-eye"></i> ' + group.count + '</span>';
|
||||||
}
|
}
|
||||||
html += '</button>';
|
html += '</button>';
|
||||||
$attrContainer.append(html);
|
$attrContainer.append(html);
|
||||||
@@ -178,10 +178,10 @@
|
|||||||
|
|
||||||
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
||||||
this.filterableData.features.forEach(function(group) {
|
this.filterableData.features.forEach(function(group) {
|
||||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature">';
|
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>';
|
html += '<span class="toggle-name">' + group.name + '</span>';
|
||||||
if (group.count !== undefined) {
|
if (group.count !== undefined) {
|
||||||
html += '<span class="toggle-count"><i class="icon-eye"></i> (' + group.count + ')</span>';
|
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '"><i class="icon-eye"></i> ' + group.count + '</span>';
|
||||||
}
|
}
|
||||||
html += '</button>';
|
html += '</button>';
|
||||||
$featContainer.append(html);
|
$featContainer.append(html);
|
||||||
|
|||||||
@@ -650,6 +650,118 @@
|
|||||||
price = parseFloat(price) || 0;
|
price = parseFloat(price) || 0;
|
||||||
}
|
}
|
||||||
return price.toFixed(2) + ' €';
|
return price.toFixed(2) + ' €';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show preview popover for filter group toggle (attribute/feature groups)
|
||||||
|
*/
|
||||||
|
showFilterGroupPreviewPopover: function($badge, groupId, groupType, groupName) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.hidePreviewPopover();
|
||||||
|
|
||||||
|
$badge.addClass('popover-open loading');
|
||||||
|
this.$activeBadge = $badge;
|
||||||
|
|
||||||
|
var trans = this.config.trans || {};
|
||||||
|
var entityLabelPlural = 'products';
|
||||||
|
|
||||||
|
// Fetch products matching this attribute/feature group
|
||||||
|
$.ajax({
|
||||||
|
url: this.config.ajaxUrl,
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
ajax: 1,
|
||||||
|
action: 'previewFilterGroupProducts',
|
||||||
|
trait: 'EntitySelector',
|
||||||
|
group_id: groupId,
|
||||||
|
group_type: groupType,
|
||||||
|
limit: 10
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$badge.removeClass('loading');
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
var items = response.items || [];
|
||||||
|
var totalCount = response.count || 0;
|
||||||
|
var hasMore = response.hasMore || false;
|
||||||
|
|
||||||
|
self.showFilterGroupItemsPopover($badge, items, totalCount, hasMore, entityLabelPlural, groupName, groupType);
|
||||||
|
} else {
|
||||||
|
$badge.removeClass('popover-open');
|
||||||
|
self.$activeBadge = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$badge.removeClass('loading popover-open');
|
||||||
|
self.$activeBadge = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show popover for filter group preview items
|
||||||
|
*/
|
||||||
|
showFilterGroupItemsPopover: function($badge, items, totalCount, hasMore, entityLabel, groupName, groupType) {
|
||||||
|
var self = this;
|
||||||
|
var trans = this.config.trans || {};
|
||||||
|
|
||||||
|
var typeLabel = groupType === 'attribute' ? (trans.attribute || 'Attribute') : (trans.feature || 'Feature');
|
||||||
|
var html = '<div class="target-preview-popover preview-type-filter-group">';
|
||||||
|
|
||||||
|
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 += '</div>';
|
||||||
|
|
||||||
|
if (items.length > 0) {
|
||||||
|
html += '<div class="preview-list">';
|
||||||
|
html += this.renderPreviewItems(items);
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
if (hasMore) {
|
||||||
|
var remaining = totalCount - items.length;
|
||||||
|
html += '<div class="preview-footer">';
|
||||||
|
html += '<span class="preview-more-info">+ ' + remaining + ' ' + (trans.more || 'more') + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html += '<div class="preview-empty">' + (trans.no_preview || 'No items to preview') + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
var $popover = $(html);
|
||||||
|
$('body').append($popover);
|
||||||
|
|
||||||
|
$popover.find('.preview-close').on('click', function() {
|
||||||
|
self.hidePreviewPopover();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
var topPos = badgeOffset.top + badgeHeight + 8;
|
||||||
|
|
||||||
|
$popover.css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: topPos,
|
||||||
|
left: leftPos,
|
||||||
|
zIndex: 10000
|
||||||
|
});
|
||||||
|
|
||||||
|
$popover.addClass('show');
|
||||||
|
|
||||||
|
this.$previewPopover = $popover;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1242,6 +1242,38 @@ body > .target-search-dropdown,
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: $es-primary;
|
color: $es-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clickable preview badge
|
||||||
|
&.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.125rem 0.25rem;
|
||||||
|
border-radius: $es-radius-sm;
|
||||||
|
transition: all $es-transition-fast;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba($es-primary, 0.1);
|
||||||
|
color: $es-primary;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $es-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.popover-open {
|
||||||
|
background: $es-primary;
|
||||||
|
color: $es-white;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $es-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
i {
|
||||||
|
animation: spin 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user