package org.sigmah.client.ui.view.pivot;
/*
* #%L
* Sigmah
* %%
* Copyright (C) 2010 - 2016 URD
* %%
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import org.sigmah.client.event.SiteEvent;
import org.sigmah.client.i18n.I18N;
import org.sigmah.shared.command.GetIndicators;
import org.sigmah.shared.command.GetSites;
import org.sigmah.shared.command.result.IndicatorListResult;
import org.sigmah.shared.command.result.SiteResult;
import org.sigmah.shared.dto.IndicatorDTO;
import org.sigmah.shared.dto.ProjectDTO;
import org.sigmah.shared.dto.SiteDTO;
import org.sigmah.shared.dto.pivot.model.PivotTableElement;
import com.allen_sauer.gwt.log.client.Log;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.store.StoreEvent;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Info;
import com.extjs.gxt.ui.client.widget.Label;
import com.extjs.gxt.ui.client.widget.form.CheckBox;
import com.extjs.gxt.ui.client.widget.form.ComboBox;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.toolbar.FillToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.SeparatorToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.sigmah.client.dispatch.CommandResultHandler;
import org.sigmah.client.dispatch.DispatchAsync;
import org.sigmah.client.event.EventBus;
import org.sigmah.client.event.UpdateEvent;
import org.sigmah.client.event.handler.SiteHandler;
import org.sigmah.client.event.handler.UpdateHandler;
import org.sigmah.client.page.Page;
import org.sigmah.client.page.RequestParameter;
import org.sigmah.client.ui.notif.ConfirmCallback;
import org.sigmah.client.ui.notif.N10N;
import org.sigmah.client.ui.view.pivot.table.PivotGridCellEvent;
import org.sigmah.client.ui.view.pivot.table.PivotGridHeaderEvent;
import org.sigmah.client.ui.view.pivot.table.PivotGridHeaderEvent.IconTarget;
import org.sigmah.client.ui.view.pivot.table.PivotGridPanel;
import org.sigmah.client.ui.view.pivot.table.PivotGridPanel.PivotTableRow;
import org.sigmah.client.ui.widget.Loadable;
import org.sigmah.client.ui.widget.button.SplitButton;
import org.sigmah.client.ui.widget.form.Forms;
import org.sigmah.client.util.GWTDates;
import org.sigmah.shared.command.GenerateElement;
import org.sigmah.shared.command.result.Authentication;
import org.sigmah.shared.command.result.ListResult;
import org.sigmah.shared.command.result.Result;
import org.sigmah.shared.dto.pivot.content.EntityCategory;
import org.sigmah.shared.dto.pivot.content.IStateManager;
import org.sigmah.shared.dto.pivot.content.MonthCategory;
import org.sigmah.shared.dto.pivot.content.PivotContent;
import org.sigmah.shared.dto.pivot.content.PivotTableData;
import org.sigmah.shared.dto.referential.DimensionType;
import org.sigmah.shared.dto.referential.GlobalPermissionEnum;
import org.sigmah.shared.util.Filter;
import org.sigmah.shared.util.ProfileUtils;
public class ProjectPivotContainer extends ContentPanel implements Loadable {
private final EventBus eventBus;
private final DispatchAsync dispatcher;
private final List<HandlerRegistration> registrations;
private Authentication authentication;
private final PivotGridPanel gridPanel;
private final IStateManager stateManager;
private int currentDatabaseId;
private final HistorySelector historySelector;
private LayoutComposer composer;
private ToolBar toolBar;
private SplitButton saveButton;
private SeparatorToolItem saveButtonSeparator;
private ComboBox<SiteDTO> siteFilter;
private ComboBox<IndicatorDTO> indicatorFilter;
private ComboBox<DateRangeModel> dateFilter;
private CheckBox defaultViewCheckBox;
private boolean updated;
private boolean loading;
/**
* Keeps track of the user's last choice regarding axis swapping, to be used
* when a new date is filtered
*/
private boolean lastAxesSwapped = false;
private PivotLayout currentLayout;
private PivotTableElement currentPivot;
@Inject
public ProjectPivotContainer(EventBus eventBus, DispatchAsync dispatcher, PivotGridPanel gridPanel, IStateManager stateManager) {
this.dispatcher = dispatcher;
this.eventBus = eventBus;
this.stateManager = stateManager;
this.gridPanel = gridPanel;
this.updated = false;
this.registrations = new ArrayList<HandlerRegistration>();
ProjectPivotResources.INSTANCE.style().ensureInjected();
setLayout(new FitLayout());
setHeadingText(I18N.CONSTANTS.indicators());
gridPanel.setHeaderVisible(false);
gridPanel.addListener(Events.HeaderClick, new Listener<PivotGridHeaderEvent>() {
@Override
public void handleEvent(PivotGridHeaderEvent be) {
onHeaderClicked(be);
}
});
gridPanel.addListener(Events.AfterEdit, new Listener<PivotGridCellEvent>() {
@Override
public void handleEvent(PivotGridCellEvent event) {
onCellEdited(event);
}
});
gridPanel.getStore().addListener(Events.Update, new Listener<StoreEvent<PivotGridPanel.PivotTableRow>>() {
@Override
public void handleEvent(StoreEvent<PivotTableRow> be) {
setDirty(true);
setUpdated(true);
}
});
add(gridPanel);
historySelector = new HistorySelector();
createToolBar();
createFilterListeners();
gridPanel.addListener(Events.CellClick, new Listener<PivotGridCellEvent>() {
@Override
public void handleEvent(PivotGridCellEvent event) {
if(!ProfileUtils.isGranted(authentication, GlobalPermissionEnum.EDIT_INDICATOR)) {
event.stopEvent();
event.getGrid().addListener(Events.BeforeEdit, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
be.setCancelled(true);
}
});
}
}
});
setTopComponent(toolBar);
}
// --
// Loadable implementation.
// --
@Override
public void setLoading(boolean loading) {
this.loading = loading;
if(loading) {
mask(I18N.CONSTANTS.loading());
} else {
unmask();
}
}
@Override
public boolean isLoading() {
return loading;
}
// --
// Public API.
// --
public void onPageRequest(Authentication authentication, ProjectDTO project) {
this.authentication = authentication;
setDirty(false);
setUpdated(false);
loadProject(project);
registerChangeListeners();
}
public void onPageChange() {
unregisterChangeListeners();
}
public SplitButton getSaveButton() {
return saveButton;
}
public SeparatorToolItem getSaveButtonSeparator() {
return saveButtonSeparator;
}
public PivotGridPanel getGridPanel() {
return gridPanel;
}
public void setDirty(boolean dirty) {
saveButton.setEnabled(dirty);
}
public void setUpdated(boolean updated) {
this.updated = updated;
}
// ---
// Private methods.
// ---
private void createToolBar() {
// Save button.
saveButton = Forms.saveSplitButton();
saveButtonSeparator = new SeparatorToolItem();
// Site filter combo box.
siteFilter = new SiteFilterCombo(dispatcher);
// Indicator filter combo box.
indicatorFilter = new IndicatorFilterCombo(dispatcher);
// Date filter combo box.
dateFilter = new DateFilterCombo();
// Set as default view checkbox.
defaultViewCheckBox = new CheckBox();
defaultViewCheckBox.addListener(Events.OnClick, new Listener<FieldEvent>() {
@Override
public void handleEvent(FieldEvent event) {
onDefaultCheck(event);
}
});
final Label defaultViewLabel = new Label(I18N.CONSTANTS.defaultView());
defaultViewLabel.setLabelFor(defaultViewCheckBox.getId());
// Creating the tool bar.
toolBar = new ToolBar();
toolBar.addStyleName(ProjectPivotResources.INSTANCE.style().toolbar());
toolBar.add(saveButton);
toolBar.add(saveButtonSeparator);
toolBar.add(new Label(I18N.CONSTANTS.site()));
toolBar.add(siteFilter);
toolBar.add(new Label(I18N.CONSTANTS.indicator()));
toolBar.add(indicatorFilter);
toolBar.add(new Label(I18N.CONSTANTS.indicatorFilterToolBarLabel()));
toolBar.add(dateFilter);
toolBar.add(new FillToolItem());
toolBar.add(historySelector.getPrevButton());
toolBar.add(historySelector.getNextButton());
toolBar.add(defaultViewLabel);
toolBar.add(defaultViewCheckBox);
}
private void createFilterListeners() {
indicatorFilter.addListener(Events.Select, new Listener<FieldEvent>() {
@Override
public void handleEvent(FieldEvent event) {
onIndicatorSelected();
}
});
siteFilter.addListener(Events.Select, new Listener<FieldEvent>() {
@Override
public void handleEvent(FieldEvent be) {
onSiteSelected();
}
});
dateFilter.addListener(Events.Select, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
onDateSelected();
}
});
historySelector.addValueChangeHandler(new ValueChangeHandler<Integer>() {
@Override
public void onValueChange(ValueChangeEvent<Integer> event) {
historyValueChange(historySelector.getLayouts().get(event.getValue()));
}
});
defaultViewCheckBox.addListener(Events.OnClick, new Listener<FieldEvent>() {
@Override
public void handleEvent(FieldEvent event) {
onDefaultCheck(event);
}
});
}
private void registerChangeListeners() {
registrations.add(eventBus.addHandler(SiteEvent.getType(), new SiteHandler() {
@Override
public void handleEvent(SiteEvent siteEvent) {
refresh();
}
}));
registrations.add(eventBus.addHandler(UpdateEvent.getType(), new UpdateHandler() {
@Override
public void onUpdate(UpdateEvent event) {
if(event.concern(UpdateEvent.INDICATOR_UPDATED)) {
refresh();
}
}
}));
}
private void unregisterChangeListeners() {
for(final HandlerRegistration registration : registrations) {
registration.removeHandler();
}
registrations.clear();
}
public void loadProject(ProjectDTO project) {
this.currentDatabaseId = project.getId();
final Date startDate = project.getStartDate() != null ? project.getStartDate() : new Date();
((IndicatorFilterCombo)indicatorFilter).setDatabaseId(currentDatabaseId);
((SiteFilterCombo)siteFilter).setDatabaseId(currentDatabaseId);
((DateFilterCombo)dateFilter).fillMonths(startDate);
composer = new LayoutComposer(new GWTDates(), project);
pivotToDefault();
}
private void pivotToDefault() {
String defaultLayoutId = (String) stateManager.get(defaultPivotStateKey());
if (defaultLayoutId == null) {
pivotToImplictDefault();
} else {
PivotLayout.deserialize(dispatcher, currentDatabaseId, defaultLayoutId, new AsyncCallback<PivotLayout>() {
@Override
public void onSuccess(PivotLayout result) {
historySelector.onNewLayout(result);
pivotTo(result);
}
@Override
public void onFailure(Throwable caught) {
pivotToImplictDefault();
}
});
}
}
private void pivotToImplictDefault() {
dateFilter.setValue(dateFilter.getStore().getAt(0));
onDateSelected();
}
/**
* Handle when the user selects an indicator from the filter
*/
private void onIndicatorSelected() {
final IndicatorDTO indicator = indicatorFilter.getValue();
IndicatorLayout layout = new IndicatorLayout(indicator);
historySelector.onNewLayout(layout);
pivotTo(layout);
}
/**
* Handle user selection of the site filter combo
*/
private void onSiteSelected() {
final SiteDTO site = (SiteDTO) siteFilter.getValue();
SiteLayout layout = new SiteLayout(site);
historySelector.onNewLayout(layout);
pivotTo(layout);
}
/**
* Handle user selection of the date combo
*/
private void onDateSelected() {
final DateRangeModel dateRangeModel = dateFilter.getValue();
DateLayout dateLayout = new DateLayout(dateRangeModel, lastAxesSwapped);
historySelector.onNewLayout(dateLayout);
pivotTo(dateLayout);
}
private void pivotTo(IndicatorLayout layout) {
siteFilter.clear();
dateFilter.clear();
setHeadingText(I18N.MESSAGES.projectPivotByIndicator(layout.getIndicator().getName()));
gridPanel.setShowSwapIcon(false);
PivotTableElement pivot = composer.fixIndicator(layout.getIndicator().getId());
loadPivot(pivot);
onLayoutChanged(layout);
}
private void pivotTo(SiteLayout layout) {
indicatorFilter.clear();
dateFilter.clear();
PivotTableElement pivot = composer.fixSite(layout.getSite().getId());
setHeadingText(I18N.MESSAGES.projectPivotBySite(layout.getSite().getLocationName()));
gridPanel.setShowSwapIcon(false);
loadPivot(pivot);
onLayoutChanged(layout);
}
private void pivotTo(DateLayout layout) {
indicatorFilter.clear();
siteFilter.clear();
setHeadingText(I18N.MESSAGES.projectPivotByMonth(layout.getModel().getLabel()));
gridPanel.setShowSwapIcon(true);
PivotTableElement pivot = composer.fixDateRange(layout.getDateRange(), layout.getAxesSwapped());
loadPivot(pivot);
lastAxesSwapped = layout.getAxesSwapped();
onLayoutChanged(layout);
}
private void onLayoutChanged(PivotLayout layout) {
currentLayout = layout;
defaultViewCheckBox.setValue(
layout.serialize().equals(stateManager.get(defaultPivotStateKey()))
);
}
private void onHeaderClicked(PivotGridHeaderEvent event) {
if (event.getIconTarget() == IconTarget.ZOOM) {
onZoom(event);
} else if (event.getIconTarget() == IconTarget.EDIT) {
PivotTableData.Axis axis = event.getAxis();
if (axis.getDimension().getType() == DimensionType.Indicator) {
editIndicator(((EntityCategory) axis.getCategory()).getId());
}
} else if (event.getIconTarget() == IconTarget.SWAP) {
swapAxes();
}
}
private void swapAxes() {
if (currentLayout instanceof DateLayout) {
DateLayout newLayout = ((DateLayout) currentLayout).swapAxes();
historySelector.onNewLayout(newLayout);
pivotTo(newLayout);
}
}
private void onZoom(PivotGridHeaderEvent event) {
PivotTableData.Axis axis = event.getAxis();
if (axis.getDimension().getType() == DimensionType.Site) {
SiteDTO site = new SiteDTO();
site.setId(((EntityCategory) axis.getCategory()).getId());
site.setLocationName(axis.getLabel());
siteFilter.setValue(site);
onSiteSelected();
} else if (axis.getDimension().getType() == DimensionType.Indicator) {
IndicatorDTO indicator = new IndicatorDTO();
indicator.setId(((EntityCategory) axis.getCategory()).getId());
indicator.setName(axis.getLabel());
indicatorFilter.setValue(indicator);
onIndicatorSelected();
} else if (axis.getDimension().getType() == DimensionType.Date) {
if (axis.getCategory() instanceof MonthCategory) {
DateRangeModel model = DateFilterCombo.monthModel((MonthCategory) axis.getCategory());
dateFilter.setValue(model);
onDateSelected();
}
}
}
private void editIndicator(final int id) {
if (!ProfileUtils.isGranted(authentication, GlobalPermissionEnum.MANAGE_INDICATOR)) {
return;
}
dispatcher.execute(new GetIndicators(this.currentDatabaseId), new CommandResultHandler<IndicatorListResult>() {
@Override
protected void onCommandSuccess(IndicatorListResult result) {
for (IndicatorDTO indicator : result.getData()) {
if (indicator.getId() == id) {
editIndicator(indicator);
}
}
}
}, this);
}
protected void editIndicator(IndicatorDTO indicator) {
eventBus.navigateRequest(Page.INDICATOR_EDIT.request()
.addParameter(RequestParameter.ID, currentDatabaseId)
.addData(RequestParameter.MODEL, indicator), this);
}
private void onCellEdited(final PivotGridCellEvent event) {
if (ProfileUtils.isGranted(authentication, GlobalPermissionEnum.EDIT_INDICATOR)) {
setUpdated(true);
setDirty(true);
if (event.getCell() != null) {
event.getRecord().set(event.getProperty(), event.getCell().getValue());
}
}
}
private void historyValueChange(PivotLayout layout) {
pivotTo(layout);
}
private void pivotTo(PivotLayout layout) {
if (layout instanceof SiteLayout) {
siteFilter.setValue(((SiteLayout) layout).getSite());
pivotTo((SiteLayout) layout);
} else if (layout instanceof IndicatorLayout) {
indicatorFilter.setValue(((IndicatorLayout) layout).getIndicator());
pivotTo((IndicatorLayout) layout);
} else if (layout instanceof DateLayout) {
dateFilter.setValue(((DateLayout) layout).getModel());
pivotTo((DateLayout) layout);
}
}
private void refresh() {
loadPivot(currentPivot);
}
private void onDefaultCheck(FieldEvent event) {
final boolean newValue = defaultViewCheckBox.getValue();
N10N.confirmation(I18N.CONSTANTS.defaultView(), I18N.CONSTANTS.confirmDefaultViewChange(), new ConfirmCallback() {
@Override
public void onAction() {
persistDefaultView(newValue);
}
}, new ConfirmCallback() {
@Override
public void onAction() {
defaultViewCheckBox.setValue(!newValue);
}
});
}
private void persistDefaultView(boolean newValue) {
if (newValue) {
stateManager.set(defaultPivotStateKey(), currentLayout.serialize());
Info.display(I18N.CONSTANTS.saved(), I18N.CONSTANTS.defaultViewChanged());
} else {
// don't allow unchecking: there is not really a logical action to
// take because there always needs to be a default view
defaultViewCheckBox.setValue(true);
}
}
private String defaultPivotStateKey() {
return "ProjectPivotDefault" + currentDatabaseId;
}
private void loadPivot(final PivotTableElement pivot) {
currentPivot = pivot;
dispatcher.execute(new GenerateElement<PivotContent>(pivot), new AsyncCallback<PivotContent>() {
@Override
public void onFailure(Throwable caught) {
gridPanel.clear();
Log.debug("Pivot failed", caught);
}
@Override
public void onSuccess(PivotContent content) {
if(content != null) {
pivot.setContent(content);
gridPanel.setValue(pivot);
}
}
}, this);
}
public void save() {
dispatcher.execute(gridPanel.composeSaveCommand(), new CommandResultHandler<ListResult<Result>>() {
@Override
protected void onCommandSuccess(ListResult<Result> result) {
gridPanel.getStore().commitChanges();
setDirty(false);
setUpdated(false);
eventBus.fireEvent(new UpdateEvent(UpdateEvent.INDICATOR_UPDATED, ProjectPivotContainer.this));
}
}, this);
}
public void discard() {
gridPanel.getStore().rejectChanges();
setDirty(false);
setUpdated(false);
}
public void viewDidAppear() {
if (gridPanel.hasIndicatorsInStore()) {
Filter filter = new Filter();
filter.addRestriction(DimensionType.Database, currentDatabaseId);
GetSites request = new GetSites();
request.setFilter(filter);
dispatcher.execute(request, new CommandResultHandler<SiteResult>() {
@Override
protected void onCommandSuccess(SiteResult result) {
if (result.getData().isEmpty()) {
N10N.info(I18N.CONSTANTS.projectWithNoSitesWarning());
}
}
}, this);
}
}
public boolean hasValueChanged() {
return updated;
}
public void forgetAllChangedValues() {
updated = false;
discard();
}
}