
Fig.1. Simple Search and Filters widget with an added filter (source: Simple Search and Filters Widget Official Documentation)
<div id="fullTextSearchContainer" sclass="yw-fulltextsearch-container">
<div class="yw-fulltextsearch-wrapper yw-fulltextsearch-filter-wrapper">
<div id="fieldQueryFiltersCounterLabelWrapper" sclass="yw-fulltextsearch-filters-counter-wrapper">
<label id="fieldQueryFiltersCounterLabel" sclass="yw-fulltextsearch-filters-counter"/>
</div>
<button id="fieldQueryButton" sclass="yw-fulltextsearch-fieldquery-button" visible="false"/>
<popup id="fieldQueryPopup" sclass="yw-fulltextsearch-fieldquery-popup"/>
</div>
</div>
.yw-fulltextsearch-container {
text-align: left !important;
}
.yw-fulltextsearch-wrapper {
margin-top: 5px;
}
.yw-fulltextsearch-filters-counter-wrapper {
min-width: 0 !important;
margin-left: 16px !important;
}
.yw-fulltextsearch-filters-counter {
line-height: 7px !important;
color: white !important;
font-size: small !important;
}
@import "scss/components/_advancedsearch-fulltextsearch.scss";
public class CustomAdvancedSearchController extends AdvancedSearchController {
@Wire
private Div fullTextSearchContainer;
@Wire
private Popup fieldQueryPopup;
@Wire
private Button fieldQueryButton;
@Wire
private Label fieldQueryFiltersCounterLabel;
private transient WidgetComponentRenderer<Popup, FulltextSearch, AdvancedSearchData> fieldQueryPopupRenderer;
@Override
public void initialize(Component comp) {
super.initialize(comp);
initializeFieldQueryComponents();
initializeFieldQueryFiltersCounterLabel();
}
private void initializeFieldQueryComponents() {
getFieldQueryButton().setVisible(true);
getFieldQueryButton().setTooltiptext(getLabel("fieldquerybutton.tooltip"));
getFieldQueryButton().addEventListener("onClick", (e) -> {
adjustFieldQuery();
getFieldQueryPopup().open(e.getTarget(), "after_end");
});
}
private void adjustFieldQuery() {
getFieldQueryPopup().getChildren().clear();
if (getCurrentDataType() != null) {
FulltextSearch configuration = loadFullTextConfiguration(getCurrentDataType().getCode());
AdvancedSearchData searchData = getSearchModel().orElse(null);
getFieldQueryPopupRenderer().render(getFieldQueryPopup(), configuration, searchData, getCurrentDataType(), getWidgetInstanceManager());
}
}
public DataType getCurrentDataType() {
return getValue("dataType", DataType.class);
}
private FulltextSearch loadFullTextConfiguration(String type) {
String configCtxCode = StringUtils.defaultIfBlank(getWidgetSettings().getString("fulltextSearchConfigCtxCode"), "fulltext-search");
DefaultConfigContext configContext = new DefaultConfigContext(configCtxCode, StringUtils.trim(type));
return super.loadConfiguration(configContext, FulltextSearch.class);
}
private void initializeFieldQueryFiltersCounterLabel() { getFieldQueryFiltersCounterLabel().setSclass("yw-fulltextsearch-filters-counter");
Integer numberOfFilters = getModel().getValue("filtersCounter", Integer.class);
if (numberOfFilters == null) {
getFieldQueryFiltersCounterLabel().setValue("0");
} else {
getFieldQueryFiltersCounterLabel().setValue(String.valueOf(numberOfFilters));
}
}
@ViewEvent(
componentID = "fieldQueryPopup",
eventName = "onApplyFilters"
)
public void onApplyFilters(Event event) {
if (canProcessFilterChangeEvent(event)) {
Map<String, FullTextSearchFilter> filters = (Map)event.getData();
setValue("fieldQueryFilters", filters);
List<SearchConditionData> conditions = buildSearchConditionData(filters);
updateFilterCounter(filters);
setValue("fieldQueries", conditions);
doSearch();
}
}
private boolean canProcessFilterChangeEvent(Event event) {
return event != null && event.getData() != null && event.getData() instanceof Map;
}
private List<SearchConditionData> buildSearchConditionData(Map<String, FullTextSearchFilter> filters) {
List<SearchConditionData> conditions = Lists.newArrayList();
filters.values().stream()
.filter(FullTextSearchFilter::isEnabled)
.filter(this::isNotEmptyFilterConditions)
.forEach((filter) -> {
FieldType fieldType = new FieldType();
fieldType.setName(filter.getName());
SearchConditionData condition = new SearchConditionData(fieldType, filter.getValue(), filter.getOperator());
clearLocalizedValues(filter.getValue(), filter.getLocale());
conditions.add(condition);
});
return conditions;
}
private boolean isNotEmptyFilterConditions(FullTextSearchFilter filter) {
if (filter.getValue() instanceof Map) {
Map<Locale, Object> localizedValue = (Map)filter.getValue();
return localizedValue.values().stream().noneMatch(""::equals);
} else {
return !filter.getOperator().isRequireValue() || filter.getValue() != null;
}
}
private void clearLocalizedValues(Object value, Locale locale) {
if (locale != null && value != null && value instanceof Map) {
Map<Locale, Object> localizedValues = (Map)value;
if (!localizedValues.isEmpty()) {
localizedValues.entrySet().removeIf((entry) -> !entry.getKey().equals(locale));
}
}
}
private void updateFilterCounter(Map<String, FullTextSearchFilter> filters) {
int numberOfFilters = (int)filters.values().stream().filter(FullTextSearchFilter::isEnabled).count();
setFiltersCounterLabelValue(numberOfFilters);
saveFiltersCounterModelValue(numberOfFilters);
}
private void setFiltersCounterLabelValue(int numberOfFilters) {
String numberOfFiltersAsString = String.valueOf(numberOfFilters);
getFieldQueryFiltersCounterLabel().setValue(numberOfFiltersAsString);
}
private void saveFiltersCounterModelValue(int numberOfFilters) {
getModel().setValue("filtersCounter", numberOfFilters);
}
@Override
protected boolean doSimpleSearch() {
if (searchBox == null) {
return false;
} else {
Optional<AdvancedSearchData> searchData = getSearchModel();
if (searchData.isPresent()) {
String query = StringUtils.defaultIfBlank(getSearchText(), "");
setValue("simpleSearchTextQuery", query);
AdvancedSearchData queryData = buildQueryData(query, searchData.get().getTypeCode());
queryData.setTokenizable(true);
Map<String, Set<String>> selectedFacets = new HashMap<>();
Map<String, Set<String>> queryDataFacets = queryData.getSelectedFacets();
if (queryDataFacets != null) {
selectedFacets.putAll(queryDataFacets);
}
Map<String, Set<String>> rendererFacets = getDefaultFacetRenderer().getSelectedFacets();
if (rendererFacets != null) {
selectedFacets.putAll(rendererFacets);
}
queryData.setSelectedFacets(selectedFacets);
queryData.setAdvancedSearchMode(AdvancedSearchMode.SIMPLE);
queryData.setGlobalOperator(ValueComparisonOperator.OR);
applyFilters(queryData);
sendOutput("searchData", queryData);
return true;
} else {
return false;
}
}
}
private void applyFilters(AdvancedSearchData queryData) {
List<SearchConditionData> conditions = getValue("fieldQueries", List.class);
if (conditions != null) {
conditions.forEach(condition -> queryData.addFilterQueryRawCondition(condition.getFieldType(),
condition.getOperator(),
condition.getValue()));
}
}
@Override
@SocketEvent(socketId = "type")
public void changeType(String typeCode) {
if (!typeCode.equals(ProductModel._TYPECODE)) {
clearAppliedFilters();
clearFieldQueryCounterLabel();
}
super.changeType(typeCode);
}
private void clearAppliedFilters() {
setValue("fieldQueryFilters", Collections.emptyMap());
setValue("fieldQueries", Collections.emptyList());
doSearch();
}
private void clearFieldQueryCounterLabel() {
getFieldQueryFiltersCounterLabel().setValue("0");
}
@Override
protected void performChangeType(String typeCode, boolean rootTypeChanged) {
DataType dataType = loadDataTypeForCode(typeCode);
if (dataType != null) {
AdvancedSearch advancedSearch = loadAdvancedConfiguration(typeCode);
AdvancedSearchData searchData = initOrLoadAdvancedSearchModel(advancedSearch, dataType);
adjustWidgetModel(advancedSearch, searchData, rootTypeChanged, dataType);
setActionSlotTypeCode(dataType.getCode());
setValue("dataType", dataType);
} else {
setValue("searchModel", null);
updateSearchMode(null);
sendOutput("reset", null);
getNotificationService().notifyUser(getNotificationSource(), "TypeChange", NotificationEvent.Level.FAILURE, new Object[]{typeCode});
}
}
@Override
protected void updateSearchMode(AdvancedSearch config) {
boolean simpleSearchVisible = shouldShowSimpleSearch(config);
boolean simpleSearchDisabled = !isAttributesContainerCollapsed();
boolean isCurrentTypeSearchable = isCurrentTypeSearchable();
boolean simpleSearchDisabledByInitCtx = isSimpleSearchDisabledByInitCtx();
setValue("simpleSearchModeActive", simpleSearchVisible && !simpleSearchDisabled && !simpleSearchDisabledByInitCtx);
if (isCurrentTypeSearchable) {
setSearchModeCaptionContainer(simpleSearchVisible, simpleSearchDisabled, simpleSearchDisabledByInitCtx);
getAttributesGrid().setVisible(isDisplayInNonCollapsibleContainer() || simpleSearchDisabled || !simpleSearchVisible);
updateOpenStateSClass(getAttributesGrid().isVisible());
Optional<AdvancedSearchData> searchModel = getSearchModel();
if (simpleSearchVisible && !simpleSearchDisabled && searchModel.isPresent() && searchModel.get().getTypeCode().equals(ProductModel._TYPECODE)) {
getSearchModeCaptionContainer().appendChild(getFullTextSearchContainer());
getFullTextSearchContainer().setVisible(true);
} else {
getFullTextSearchContainer().setVisible(false);
}
} else {
Optional<AdvancedSearchData> advData = getSearchModel();
boolean hasTypeSelected = advData.isPresent() && StringUtils.isNotBlank(((AdvancedSearchData)advData.get()).getTypeCode());
if (hasTypeSelected) {
getSearchTitle().setValue(getLabel("non.searchable.type", new Object[]{((AdvancedSearchData)advData.get()).getTypeCode()}));
} else {
getSearchTitle().setValue(getLabel("no.type.selected.info"));
}
getAttributesGrid().setVisible(false);
getActionSlot().setVisible(false);
}
getSearchTitle().setVisible(!isCurrentTypeSearchable);
getSearchModeCaptionContainer().setVisible(isCurrentTypeSearchable);
getSearchButton().setVisible(isCurrentTypeSearchable);
getSearchModeToggleButton().setVisible(isCurrentTypeSearchable && simpleSearchVisible);
}
<controller class="com.custom.backoffice.widgets.advancedsearch.CustomAdvancedSearchController"/>
<setting key="fulltextSearchConfigCtxCode" default-value="fulltext-search" type="String"/>
fieldquerybutton.tooltip=Filters
fieldquerypopup.title=Filters
fieldquerypopup.button.addfilter=Add more filters
fieldquerypopup.button.apply=Apply
<widget-extension widgetId="collapsibleContainer">
<remove widgetId="advancedSearch"/>
<widget id="advancedSearch" widgetDefinitionId="com.novarto.backoffice.advancedsearch"
slotId="center" template="false">
...
</widget>
</widget-extension>
<config xmlns="http://www.hybris.com/cockpit/config"
xmlns:ful="http://www.hybris.com/cockpitng/config/fulltextsearch">
<context type="Product" component="fulltext-search">
<ful:fulltext-search>
<ful:field-list>
<ful:field name="code"/>
<ful:field name="name"/>
</ful:field-list>
<ful:preferred-search-strategy>flexible</ful:preferred-search-strategy>
<ful:operator>OR</ful:operator>
</ful:fulltext-search>
</context>
import com.hybris.backoffice.solrsearch.core.config.SolrFullTextSearchConfigurationFallbackStrategy;
import com.hybris.cockpitng.config.fulltextsearch.jaxb.FulltextSearch;
import com.hybris.cockpitng.core.config.ConfigContext;
public class CustomFullTextSearchConfigurationFallbackStrategy extends SolrFullTextSearchConfigurationFallbackStrategy {
@Override
public FulltextSearch loadFallbackConfiguration(ConfigContext context, Class<FulltextSearch> configurationType) {
// CUSTOM IMPLEMENTATION GOES HERE
}
}
FacetSearchConfig searchConfig = super.getFacetSearchConfigService().getFacetSearchConfig(typeCode);
String typeCode = super.getTypeFromContext(context);
<alias name="customSolrFullTextSearchConfigurationFallbackStrategy"
alias="solrFullTextSearchConfigurationFallbackStrategy"/>
<bean name="customSolrFullTextSearchConfigurationFallbackStrategy" class="com.custom.backoffice.config.CustomFullTextSearchConfigurationFallbackStrategy"
parent="defaultSolrFullTextSearchConfigurationFallbackStrategy"/>
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.