/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2012 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.common.components.bandboxsearch;
import static org.libreplan.web.I18nHelper._;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.libreplan.web.common.components.finders.FilterPair;
import org.libreplan.web.common.components.finders.IMultipleFiltersFinder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.zkoss.zk.au.AuRequest;
import org.zkoss.zk.au.AuService;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.HtmlMacroComponent;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zul.Bandbox;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.Listhead;
import org.zkoss.zul.Listheader;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Listbox;
/**
* {@link Bandbox} allowing to choose more than one element from the drop down list separating them by "<code>;</code>".
* <br />
*
* When an element is selected {@link Events#ON_CHANGE} event over this component is launched.
*
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@SuppressWarnings("serial")
public class BandboxMultipleSearch extends HtmlMacroComponent {
private Listbox listbox;
private Listhead listhead;
private Bandbox bandbox;
private String widthBandbox;
private String widthListbox;
private String heightBbox;
private IMultipleFiltersFinder multipleFiltersFinder;
private List selectedFilters = new ArrayList();
private String selectedFiltersText = "";
public void afterCompose() {
super.afterCompose();
listbox = (Listbox) getFellowIfAny("listbox");
listhead = (Listhead) listbox.getFellowIfAny("listhead");
bandbox = (Bandbox) getFellowIfAny("bandbox");
initFinder();
updateWidth();
updateHeight();
}
private void initListbox() {
listbox.setModel(getSubModel());
listbox.setItemRenderer(multipleFiltersFinder.getItemRenderer());
addHeaders();
setAuService(new AuService() {
@Override
public boolean service(AuRequest request, boolean everError) {
String command = request.getCommand();
if ( command.equals("closeBandbox") ) {
pickElementFromListAndCloseBandbox();
return true;
}
return false;
}
});
listbox.addEventListener(Events.ON_OK, new EventListener() {
@Override
public void onEvent(Event event) {
pickElementFromListAndCloseBandbox();
}
});
}
private void initBandbox() {
/* Search for matching elements while typing on bandbox */
bandbox.addEventListener("onChanging", new EventListener() {
@Override
public void onEvent(Event event) {
final String inputText = ((InputEvent) event).getValue();
if ( (inputText == null) || (inputText.isEmpty()) ) {
clear();
listbox.setSelectedIndex(0);
Events.postEvent(Events.ON_CHANGE, listbox, null);
} else {
searchMultipleFilters(inputText);
}
}
});
bandbox.setCtrlKeys("#down");
bandbox.addEventListener(Events.ON_CTRL_KEY, new EventListener() {
@Override
public void onEvent(Event event) {
List<Listitem> items = listbox.getItems();
if ( !items.isEmpty() ) {
listbox.setSelectedIndex(0);
items.get(0).setFocus(true);
}
}
});
}
private void initFinder() {
if ( multipleFiltersFinder != null ) {
if ( listbox != null ) {
initListbox();
}
if ( bandbox != null ) {
initBandbox();
}
}
}
private void pickElementFromListAndCloseBandbox() {
if ( getSelectedItem() != null ) {
final Object object = getSelectedItem().getValue();
if ( multipleFiltersFinder.isValidNewFilter(selectedFilters, object) ) {
addSelectedElement(object);
clearListbox();
listbox.setModel(getSubModel());
} else {
bandbox.setValue(selectedFiltersText);
}
}
bandbox.close();
Events.postEvent(Events.ON_CHANGE, this, null);
}
private void searchMultipleFilters() {
listbox.setModel(getSubModel());
listbox.invalidate();
}
private void searchMultipleFilters(String inputText) {
// Update the filters list if some filter was deleted
boolean someRemoved = multipleFiltersFinder.updateDeletedFilters(selectedFilters, inputText);
if ( someRemoved ) {
updateSelectedFiltersText();
updateBandboxValue();
} else {
// Find the filter set to show it in the listbox
String newFilterText = multipleFiltersFinder.getNewFilterText(inputText);
if ( (newFilterText != null) && (!newFilterText.isEmpty()) ) {
listbox.setModel(getSubModel(newFilterText));
listbox.invalidate();
} else {
searchMultipleFilters();
}
}
}
private void clearSelectedElement() {
bandbox.setValue("");
selectedFiltersText = "";
selectedFilters.clear();
multipleFiltersFinder.reset();
searchMultipleFilters();
}
public void addSelectedElement(Object obj) {
if ( obj != null ) {
addFilter(obj);
updateSelectedFiltersText();
updateBandboxValue();
}
}
private void addFilter(Object obj) {
FilterPair newFilter = (FilterPair) obj;
for (FilterPair filter : (List<FilterPair>) selectedFilters) {
if ( (filter.getType().equals(newFilter.getType())) &&
(filter.getPattern().equals(newFilter.getPattern())) ) {
throw new WrongValueException(bandbox, _("filter already exists"));
}
}
selectedFilters.add(obj);
}
public List getSelectedElements() {
updateBandboxValue();
if ( this.multipleFiltersFinder != null ) {
if ( !multipleFiltersFinder.isValidFormatText(selectedFilters, bandbox.getValue()) ) {
throw new WrongValueException(bandbox, _("format filters are not valid"));
}
}
return selectedFilters;
}
/**
* Find the first ten filters.
*/
@SuppressWarnings("unchecked")
private ListModel<FilterPair> getSubModel() {
List<FilterPair> result = multipleFiltersFinder.getFirstTenFilters();
return new SimpleListModel<>(result);
}
/**
* Find filter which contains the expression.
* @param inputText
*/
@SuppressWarnings("unchecked")
private ListModel<FilterPair> getSubModel(String inputText) {
List<FilterPair> result = multipleFiltersFinder.getMatching(inputText);
return new SimpleListModel<>(result);
}
/**
* Append headers to listbox header list.
*/
@SuppressWarnings("unchecked")
private void addHeaders() {
clearHeaderIfNecessary();
final String[] headers = multipleFiltersFinder.getHeaders();
for (String header : headers) {
listhead.getChildren().add(new Listheader(_(header)));
}
}
private void clearHeaderIfNecessary() {
if ( listhead.getChildren() != null ) {
listhead.getChildren().clear();
}
}
private Listitem getSelectedItem() {
try {
return listbox.getSelectedItems().iterator().next();
}
catch (NoSuchElementException e) {
return null;
}
}
public void setDisabled(boolean disabled) {
bandbox.setDisabled(disabled);
}
private Object getBean(String beanName) {
HttpServletRequest servletRequest = (HttpServletRequest) Executions.getCurrent().getNativeRequest();
ServletContext servletContext = servletRequest.getSession().getServletContext();
WebApplicationContext webApplicationContext =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
return webApplicationContext.getBean(beanName);
}
public String getFinder() {
return multipleFiltersFinder.getClass().toString();
}
public void setFinder(String classname) {
multipleFiltersFinder = (IMultipleFiltersFinder) getBean(StringUtils.uncapitalize(classname));
initFinder();
}
/**
* Clears {@link Bandbox} Fills bandbox list model, clear bandbox textbox, and set selected label to null.
*/
public void clear() {
clearSelectedElement();
}
private void clearListbox() {
List<Object> list = new ArrayList<>();
listbox.setModel(new SimpleListModel<>(list));
listbox.invalidate();
}
public List<Object> asList(ListModel<Object> model) {
List<Object> result = new ArrayList<>();
for (int i = 0; i < model.getSize(); i++) {
result.add(model.getElementAt(i));
}
return result;
}
public void setListboxEventListener(String event, EventListener<Event> listener) {
listbox.addEventListener(event, listener);
}
public String getWidthBandbox() {
return widthBandbox;
}
public void setWidthBandbox(String widthBandbox) {
this.widthBandbox = widthBandbox;
}
public String getWidthListbox() {
return widthListbox;
}
public void setWidthListbox(String widthListbox) {
this.widthListbox = widthListbox;
}
private void updateWidth() {
if ( (widthBandbox != null) && (!widthBandbox.isEmpty()) ) {
this.bandbox.setWidth(widthBandbox);
this.listbox.setWidth(widthListbox);
}
}
private void updateHeight() {
if ( (heightBbox != null) && (!heightBbox.isEmpty()) ) {
this.bandbox.setHeight(heightBbox);
}
}
private void updateBandboxValue() {
bandbox.setValue(selectedFiltersText);
}
private void updateSelectedFiltersText() {
selectedFiltersText = "";
for (Object obj : selectedFilters) {
selectedFiltersText = selectedFiltersText.concat(multipleFiltersFinder.objectToString(obj));
}
}
public void setHeightBbox(String heightBbox) {
this.heightBbox = heightBbox;
}
public String getHeightBbox() {
return heightBbox;
}
public void addSelectedElements(List<FilterPair> sessionFilterPairs) {
selectedFilters.clear();
for (FilterPair filterPair : sessionFilterPairs) {
addFilter(filterPair);
}
updateSelectedFiltersText();
updateBandboxValue();
}
}