/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.rrm.ehour.ui.common.panel.entryselector;
import net.rrm.ehour.exception.ObjectNotFoundException;
import net.rrm.ehour.ui.common.border.GreyBlueRoundedBorder;
import net.rrm.ehour.ui.common.component.XlsxLink;
import net.rrm.ehour.ui.common.decorator.LoadingSpinnerDecorator;
import net.rrm.ehour.ui.common.model.DateModel;
import net.rrm.ehour.ui.common.panel.AbstractBasePanel;
import net.rrm.ehour.ui.common.report.excel.ExcelWorkbook;
import net.rrm.ehour.ui.common.report.excel.IWriteBytes;
import net.rrm.ehour.ui.common.session.EhourWebSession;
import net.rrm.ehour.ui.common.wicket.Container;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.UUID;
import static net.rrm.ehour.ui.common.panel.entryselector.EntrySelectorData.EntrySelectorRow;
/**
* Selector with autocompletion filter
*/
public class EntrySelectorPanel extends AbstractBasePanel<EntrySelectorData> {
private static final JavaScriptResourceReference JS = new JavaScriptResourceReference(EntrySelectorPanel.class, "entrySelector.js");
private static final String ITEM_LIST_ID = "itemList";
private static final String HEADER_ID = "headers";
private final ClickHandler clickHandler;
private final boolean wide;
private IModel<String> hideInactiveLinkTooltip;
private boolean showHideInactiveLink = false;
private WebMarkupContainer listContainer;
private final String frameDomId;
private final String jsObjectId;
public EntrySelectorPanel(String id, EntrySelectorData entrySelectorData, ClickHandler clickHandler) {
this(id, entrySelectorData, clickHandler, null);
}
public EntrySelectorPanel(String id, EntrySelectorData entrySelectorData, ClickHandler clickHandler, IModel<String> hideInactiveLinkTooltip) {
this(id, entrySelectorData, clickHandler, hideInactiveLinkTooltip, false);
}
public EntrySelectorPanel(String id, EntrySelectorData entrySelectorData, ClickHandler clickHandler, IModel<String> hideInactiveLinkTooltip, boolean wide) {
super(id, new Model<>(entrySelectorData));
frameDomId = "entrySelectorFrame_" + randomId();
jsObjectId = "entrySelector_" + randomId();
this.clickHandler = clickHandler;
this.wide = wide;
if (hideInactiveLinkTooltip != null) {
this.hideInactiveLinkTooltip = hideInactiveLinkTooltip;
showHideInactiveLink = true;
}
}
private static String randomId() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
@Override
public void onEvent(IEvent<?> eventWrapper) {
Object event = eventWrapper.getPayload();
if (event instanceof EntryListUpdatedEvent) {
EntryListUpdatedEvent entryListUpdatedEvent = (EntryListUpdatedEvent) event;
reRender(entryListUpdatedEvent.target());
}
}
public void updateData(EntrySelectorData entrySelectorData) {
setDefaultModelObject(entrySelectorData);
listContainer.addOrReplace(createListView(ITEM_LIST_ID));
}
public void reRender(AjaxRequestTarget target) {
target.add(listContainer);
target.appendJavaScript(jsRefresh());
}
private String jsRefresh() {
return "window." + jsObjectId + ".refresh();";
}
@Override
public void renderHead(IHeaderResponse response) {
response.render(JavaScriptHeaderItem.forReference(JS));
response.render(OnDomReadyHeaderItem.forScript("window." + jsObjectId + " = new EntrySelector('#" + frameDomId + "', '#listFilter', '.entrySelectorTable');"));
}
@Override
protected void onInitialize() {
super.onInitialize();
WebMarkupContainer selectorFrame = new WebMarkupContainer("entrySelectorFrame");
selectorFrame.setMarkupId(frameDomId);
selectorFrame.setOutputMarkupId(true);
if (wide) {
selectorFrame.add(AttributeModifier.append("style", "width: 97%;"));
}
addOrReplace(selectorFrame);
selectorFrame.add(createForm());
GreyBlueRoundedBorder border = new GreyBlueRoundedBorder("blueBorder") {
@Override
protected WebMarkupContainer createComponent() {
WebMarkupContainer component = super.createComponent();
component.add(AttributeModifier.append("class", "inline"));
if (wide) {
component.add(AttributeModifier.append("style", "width: 100%;"));
}
return component;
}
};
if (wide) {
border.add(AttributeModifier.append("style", "width: 100%;"));
}
border.setOutputMarkupId(true);
selectorFrame.add(border);
listContainer = new Container("listScroll");
listContainer.setMarkupId("listContents");
if (!wide) {
listContainer.add(AttributeModifier.append("class", "limitWidth"));
} else {
listContainer.add(AttributeModifier.append("style", "width: 99%"));
}
border.add(listContainer);
listContainer.add(createHeaders(HEADER_ID));
listContainer.add(createListView(ITEM_LIST_ID));
}
private RepeatingView createHeaders(String id) {
RepeatingView cells = new RepeatingView(id);
for (EntrySelectorData.Header header : getPanelModelObject().getColumnHeaders()) {
Label label = new Label(cells.newChildId(), new ResourceModel(header.getResourceLabel()));
if (header.getColumnType() == EntrySelectorData.ColumnType.NUMERIC ||
header.getColumnType() == EntrySelectorData.ColumnType.DATE) {
label.add(AttributeModifier.replace("class", "numeric"));
}
cells.add(label);
}
return cells;
}
private EntrySelectorListView createListView(String id) {
return new EntrySelectorListView(id, getPanelModelObject().getRows(), clickHandler) {
@Override
protected void onPopulate(ListItem<EntrySelectorRow> item, IModel<EntrySelectorRow> itemModel) {
EntrySelectorRow object = item.getModelObject();
RepeatingView cells = new RepeatingView("child");
int index = 0;
for (Serializable serializable : object.getCells()) {
EntrySelectorData.Header header = getPanelModelObject().getColumnHeaders().get(index);
EntrySelectorData.ColumnType columnType = header.getColumnType();
Label label;
if (columnType == EntrySelectorData.ColumnType.DATE) {
Date date = (Date) serializable;
DateModel dateModel;
dateModel = new DateModel(date, EhourWebSession.getEhourConfig(), DateModel.DATESTYLE_FULL_SHORT);
label = new Label(cells.newChildId(), dateModel);
label.setEscapeModelStrings(false);
} else {
label = new Label(cells.newChildId(), serializable);
}
if (columnType == EntrySelectorData.ColumnType.HTML) {
label.setEscapeModelStrings(false);
}
cells.add(label);
if (columnType == EntrySelectorData.ColumnType.NUMERIC) {
label.add(AttributeModifier.replace("class", "numeric"));
} else if (columnType == EntrySelectorData.ColumnType.DATE) {
label.add(AttributeModifier.replace("class", "numeric"));
}
if (!object.isActive()) {
label.add(AttributeModifier.append("class", "inactive"));
}
index++;
}
item.add(cells);
}
};
}
private Form<Void> createForm() {
Form<Void> form = new Form<>("filterForm");
WebMarkupContainer filterInputContainer = new WebMarkupContainer("filterInputContainer");
add(filterInputContainer);
form.add(filterInputContainer);
WebMarkupContainer listFilter = new WebMarkupContainer("listFilter");
listFilter.setMarkupId("listFilter");
listFilter.setOutputMarkupId(true);
listFilter.add(AttributeModifier.replace("placeholder", new ResourceModel("report.filter").getObject()));
filterInputContainer.add(listFilter);
addHideInactiveFilter(form);
XlsxLink excelLink = createExcelLink();
form.add(excelLink);
onFiltersCreated(filterInputContainer);
return form;
}
protected void onFiltersCreated(WebMarkupContainer filterInputContainer) {
}
private XlsxLink createExcelLink() {
return new XlsxLink("toExcel", "out.xlsx", new IWriteBytes() {
@Override
public void write(OutputStream stream) throws IOException {
EntrySelectorExcelGenerator excelGenerator = new EntrySelectorExcelGenerator();
ExcelWorkbook workbook = excelGenerator.create(getPanelModelObject(), "Export");
workbook.write(stream);
}
});
}
private void addHideInactiveFilter(Form<Void> form) {
final HideInactiveFilter hideInactiveFilter = new HideInactiveFilter();
hideInactiveFilter.setHideInactive(getEhourWebSession().getHideInactiveSelections());
final WebMarkupContainer filterIcon = new WebMarkupContainer("filterIcon");
addFilterIconAttributes(filterIcon, getEhourWebSession().getHideInactiveSelections());
filterIcon.setOutputMarkupId(true);
final AjaxLink<Void> hideInactiveLink = new AjaxLink<Void>("filterToggle") {
@Override
public void onClick(AjaxRequestTarget target) {
Boolean hideInactiveSelections = getEhourWebSession().toggleHideInactiveSelections();
HideInactiveFilter inactiveFilter = new HideInactiveFilter(hideInactiveSelections);
send(getPage(), Broadcast.DEPTH, new InactiveFilterChangedEvent(inactiveFilter, target));
target.appendJavaScript(jsRefresh());
filterIcon.removeAll();
addFilterIconAttributes(filterIcon, getEhourWebSession().getHideInactiveSelections());
target.add(filterIcon);
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new LoadingSpinnerDecorator());
}
};
hideInactiveLink.setVisible(showHideInactiveLink);
hideInactiveLink.add(filterIcon);
form.add(hideInactiveLink);
}
private void addFilterIconAttributes(WebMarkupContainer filterIcon, boolean active) {
filterIcon.add(AttributeModifier.replace("title", hideInactiveLinkTooltip));
filterIcon.add(AttributeModifier.replace("class", active ? "fa fa-toggle-on" : "fa fa-toggle-off"));
}
private static final long serialVersionUID = -7928428437664050056L;
public interface ClickHandler extends Serializable {
void onClick(EntrySelectorData.EntrySelectorRow row, AjaxRequestTarget target) throws ObjectNotFoundException;
}
public static class EntrySelectorBuilder {
private EntrySelectorData entrySelectorData;
private final String id;
private ClickHandler clickHandler;
private IModel<String> hideInactiveLinkTooltip;
private boolean wide = false;
public EntrySelectorBuilder(String id) {
this.id = id;
}
public static EntrySelectorBuilder startAs(String id) {
return new EntrySelectorBuilder(id);
}
public EntrySelectorBuilder withData(EntrySelectorData entrySelectorData) {
this.entrySelectorData = entrySelectorData;
return this;
}
public EntrySelectorBuilder onClick(ClickHandler clickHandler) {
this.clickHandler = clickHandler;
return this;
}
public EntrySelectorBuilder withInactiveTooltip(IModel<String> hideInactiveLinkTooltip) {
this.hideInactiveLinkTooltip = hideInactiveLinkTooltip;
return this;
}
public EntrySelectorBuilder isWide() {
this.wide = true;
return this;
}
public EntrySelectorPanel build() {
return new EntrySelectorPanel(id, entrySelectorData, clickHandler, hideInactiveLinkTooltip, wide);
}
}
}