package org.activityinfo.ui.client.page.config.design; /* * #%L * ActivityInfo Server * %% * Copyright (C) 2009 - 2013 UNICEF * %% * 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 com.extjs.gxt.ui.client.data.ModelData; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.store.Record; import com.extjs.gxt.ui.client.store.Store; import com.extjs.gxt.ui.client.store.TreeStore; import com.extjs.gxt.ui.client.widget.ContentPanel; import com.extjs.gxt.ui.client.widget.menu.Menu; import com.extjs.gxt.ui.client.widget.menu.MenuItem; import com.google.common.base.Function; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.inject.ImplementedBy; import com.google.inject.Inject; import org.activityinfo.i18n.shared.I18N; import org.activityinfo.i18n.shared.UiConstants; import org.activityinfo.legacy.client.Dispatcher; import org.activityinfo.legacy.client.callback.SuccessCallback; import org.activityinfo.legacy.client.monitor.MaskingAsyncMonitor; import org.activityinfo.legacy.client.state.StateProvider; import org.activityinfo.legacy.shared.command.*; import org.activityinfo.legacy.shared.command.result.CreateResult; import org.activityinfo.legacy.shared.command.result.VoidResult; import org.activityinfo.legacy.shared.model.*; import org.activityinfo.model.resource.ResourceId; import org.activityinfo.ui.client.AppEvents; import org.activityinfo.ui.client.EventBus; import org.activityinfo.ui.client.page.NavigationEvent; import org.activityinfo.ui.client.page.NavigationHandler; import org.activityinfo.ui.client.page.PageId; import org.activityinfo.ui.client.page.PageState; import org.activityinfo.ui.client.page.common.dialog.FormDialogCallback; import org.activityinfo.ui.client.page.common.dialog.FormDialogTether; import org.activityinfo.ui.client.page.common.grid.AbstractEditorGridPresenter; import org.activityinfo.ui.client.page.common.grid.TreeGridView; import org.activityinfo.ui.client.page.common.toolbar.UIActions; import org.activityinfo.ui.client.page.config.DbPage; import org.activityinfo.ui.client.page.config.DbPageState; import org.activityinfo.ui.client.page.config.design.dialog.NewFormDialog; import org.activityinfo.ui.client.page.config.design.importer.SchemaImportDialog; import org.activityinfo.ui.client.page.config.design.importer.SchemaImporter; import org.activityinfo.ui.client.page.instance.InstancePage; import org.activityinfo.ui.client.page.instance.InstancePlace; import javax.annotation.Nullable; import java.util.List; import java.util.Objects; /** * Presenter for the Design Page, which enables the user to define UserDatabases * and their Activities, Attributes, and Indicators. * * @author Alex Bertram */ public class DesignPresenter extends AbstractEditorGridPresenter<ModelData> implements DbPage { public static final PageId PAGE_ID = new PageId("design"); @ImplementedBy(DesignView.class) public interface View extends TreeGridView<DesignPresenter, ModelData> { public void init(DesignPresenter presenter, UserDatabaseDTO db, TreeStore store); public FormDialogTether showNewForm(EntityDTO entity, FormDialogCallback callback); public Menu getNewMenu(); public MenuItem getNewAttributeGroup(); public MenuItem getNewAttribute(); public MenuItem getNewIndicator(); public void showForm(ModelData model); } private final EventBus eventBus; private final Dispatcher service; private final View view; private final UiConstants messages; private UserDatabaseDTO db; private TreeStore<ModelData> treeStore; @Inject public DesignPresenter(EventBus eventBus, Dispatcher service, StateProvider stateMgr, View view, UiConstants messages) { super(eventBus, service, stateMgr, view); this.eventBus = eventBus; this.service = service; this.view = view; this.messages = messages; } @Override public void go(UserDatabaseDTO db) { this.db = db; treeStore = new TreeStore<>(); fillStore(messages); initListeners(treeStore, null); this.view.init(this, db, treeStore); this.view.setActionEnabled(UIActions.DELETE, false); this.view.setActionEnabled(UIActions.EDIT, false); this.view.setActionEnabled(UIActions.OPEN_TABLE, false); initMenu(); } private void initMenu() { Menu newMenu = this.view.getNewMenu(); if (newMenu == null) { return; } newMenu.addListener(Events.BeforeShow, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { ModelData sel = DesignPresenter.this.view.getSelection(); IsActivityDTO activity = DesignPresenter.this.getSelectedActivity(sel); DesignPresenter.this.view.getNewAttributeGroup().setEnabled(activity != null && activity.getClassicView()); DesignPresenter.this.view.getNewAttribute().setEnabled(activity != null && (sel instanceof AttributeGroupDTO || sel instanceof AttributeDTO) && activity.getClassicView()); DesignPresenter.this.view.getNewIndicator().setEnabled(activity != null && activity.getClassicView()); } }); } public void refresh() { service.execute(new GetSchema(), new MaskingAsyncMonitor((ContentPanel)view, I18N.CONSTANTS.loading()), new AsyncCallback<SchemaDTO>() { @Override public void onFailure(Throwable caught) { //handled by masking async monitor } @Override public void onSuccess(SchemaDTO result) { db = result.getDatabaseById(db.getId()); fillStore(messages); DesignPresenter.this.view.showForm(DesignPresenter.this.view.getSelection()); } }); } @Override public void shutdown() { } private void fillStore(UiConstants messages) { treeStore.removeAll(); for (ActivityDTO activity : db.getActivities()) { ActivityDTO activityNode = new ActivityDTO(activity); treeStore.add(activityNode, false); if (!activityNode.getClassicView()) { continue; // skip indicators and attributes in tree if activity is not classicView=true } final AttributeGroupFolder attributeFolder = new AttributeGroupFolder(messages.attributes()); treeStore.add(activityNode, attributeFolder, false); final IndicatorFolder indicatorFolder = new IndicatorFolder(messages.indicators()); treeStore.add(activityNode, indicatorFolder, false); service.execute(new GetActivityForm(activity.getId())).then(new SuccessCallback<ActivityFormDTO>() { @Override public void onSuccess(ActivityFormDTO activityForm) { for (AttributeGroupDTO group : activityForm.getAttributeGroups()) { if (group != null) { AttributeGroupDTO groupNode = new AttributeGroupDTO(group); treeStore.add(attributeFolder, groupNode, false); for (AttributeDTO attribute : group.getAttributes()) { AttributeDTO attributeNode = new AttributeDTO(attribute); treeStore.add(groupNode, attributeNode, false); } } } for (IndicatorGroup group : activityForm.groupIndicators()) { for (IndicatorDTO indicator : group.getIndicators()) { IndicatorDTO indicatorNode = new IndicatorDTO(indicator); treeStore.add(indicatorFolder, indicatorNode, false); } } } }); } for(LocationTypeDTO locationType : db.getCountry().getLocationTypes()) { if(Objects.equals(locationType.getDatabaseId(), db.getId())) { treeStore.add(locationType, false); } } } @Override public Store<ModelData> getStore() { return treeStore; } public TreeStore<ModelData> getTreeStore() { return treeStore; } @Override public boolean navigate(PageState place) { return place instanceof DbPageState && place.getPageId().equals(PAGE_ID) && ((DbPageState) place).getDatabaseId() == db.getId(); } @Override public void onUIAction(String actionId) { super.onUIAction(actionId); if (UIActions.EXPORT.equals(actionId)) { Window.open("/resources/database/" + db.getId() + "/schema.csv", "_blank", null); } else if (UIActions.IMPORT.equals(actionId)) { SchemaImporter importer = new SchemaImporter(service, db); SchemaImportDialog dialog = new SchemaImportDialog(importer); dialog.show().then(new Function<Void, Object>() { @Nullable @Override public Object apply(@Nullable Void input) { refresh(); return null; } }); } else if(UIActions.EDIT.equals(actionId)) { ResourceId formClassId = getSelectedActivity(view.getSelection()).getFormClassId(); eventBus.fireEvent(new NavigationEvent( NavigationHandler.NAVIGATION_REQUESTED, new InstancePlace(formClassId, InstancePage.DESIGN_PAGE_ID))); } else if(UIActions.OPEN_TABLE.equals(actionId)) { IsFormClass formClass = (IsFormClass) view.getSelection(); eventBus.fireEvent(new NavigationEvent( NavigationHandler.NAVIGATION_REQUESTED, new InstancePlace(formClass.getResourceId(), InstancePage.TABLE_PAGE_ID))); } } public void onNodeDropped(ModelData source) { // update sortOrder ModelData parent = treeStore.getParent(source); List<ModelData> children = parent == null ? treeStore.getRootItems() : treeStore.getChildren(parent); for (int i = 0; i != children.size(); ++i) { Record record = treeStore.getRecord(children.get(i)); record.set("sortOrder", i); } } public void onNew(String entityName) { final EntityDTO newEntity; ModelData parent; ModelData selected = view.getSelection(); if ("Activity".equals(entityName)) { final NewFormDialog newFormDialog = new NewFormDialog(); newFormDialog.show(); newFormDialog.setSuccessHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { createNewActivity(newFormDialog); } }); return; } else if ("LocationType".equals(entityName)) { newEntity = new LocationTypeDTO(); newEntity.set("databaseId", db.getId()); parent = null; } else if ("AttributeGroup".equals(entityName)) { IsActivityDTO activity = findActivityFolder(selected); newEntity = new AttributeGroupDTO(); newEntity.set("activityId", activity.getId()); parent = treeStore.getChild((ModelData) activity, 0); } else if ("Attribute".equals(entityName)) { AttributeGroupDTO group = findAttributeGroupNode(selected); newEntity = new AttributeDTO(); newEntity.set("attributeGroupId", group.getId()); parent = group; } else if ("Indicator".equals(entityName)) { IsActivityDTO activity = findActivityFolder(selected); IndicatorDTO newIndicator = new IndicatorDTO(); newIndicator.setAggregation(IndicatorDTO.AGGREGATE_SUM); if (activity instanceof ActivityFormDTO) { newIndicator.set("sortOrder", ((ActivityFormDTO)activity).getIndicators().size() + 1); } newEntity = newIndicator; newEntity.set("activityId", activity.getId()); parent = treeStore.getChild((ModelData) activity, 1); } else { return; // TODO log error } createEntity(parent, newEntity); } private void createNewActivity(NewFormDialog newFormDialog) { final ActivityFormDTO newActivity = new ActivityFormDTO(db); newActivity.set("databaseId", db.getId()); newActivity.setName(newFormDialog.getName()); newActivity.setCategory(newFormDialog.getCategory()); if (newFormDialog.getViewType() == NewFormDialog.ViewType.CLASSIC || newFormDialog.getViewType() == NewFormDialog.ViewType.CLASSIC_MONTHLY) { newActivity.setClassicView(true); newActivity.setReportingFrequency(newFormDialog.getViewType() == NewFormDialog.ViewType.CLASSIC ? ActivityFormDTO.REPORT_ONCE : ActivityFormDTO.REPORT_MONTHLY); createEntity(null, newActivity); return; } else if (newFormDialog.getViewType() == NewFormDialog.ViewType.NEW_FORM_DESIGNER) { newActivity.setClassicView(false); newActivity.setReportingFrequency(ActivityFormDTO.REPORT_ONCE); newActivity.setLocationType(newActivityLocationTypeForModernView()); service.execute(new CreateEntity(newActivity), new SuccessCallback<CreateResult>() { @Override public void onSuccess(CreateResult result) { newActivity.setId(result.getNewId()); eventBus.fireEvent(new NavigationEvent( NavigationHandler.NAVIGATION_REQUESTED, new InstancePlace(newActivity.getResourceId(), InstancePage.DESIGN_PAGE_ID))); } }); return; } throw new RuntimeException("Unsupported view type of activity: " + newFormDialog.getViewType()); } private LocationTypeDTO newActivityLocationTypeForModernView() { for (LocationTypeDTO dto : db.getCountry().getLocationTypes()) { if (dto.isNationwide()) { return dto; } } // throw new RuntimeException("Failed to find nationwide location type, db:" + db + ", country:" + db.getCountry()); // return first location, test db doesn't have nationwide location type for country return db.getCountry().getLocationTypes().get(0); } private void createEntity(final ModelData parent, final EntityDTO newEntity) { view.showNewForm(newEntity, new FormDialogCallback() { @Override public void onValidated(final FormDialogTether tether) { service.execute(new CreateEntity(newEntity), tether, new AsyncCallback<CreateResult>() { @Override public void onFailure(Throwable caught) { GWT.log(caught.getMessage()); } @Override public void onSuccess(CreateResult result) { newEntity.set("id", result.getNewId()); // todo add // setId to // EntityDTO // interface if (parent == null) { treeStore.add(newEntity, false); } else { treeStore.add(parent, newEntity, false); } if (newEntity instanceof IsActivityDTO) { treeStore.add(newEntity, new AttributeGroupFolder(messages.attributes()), false); treeStore.add(newEntity, new IndicatorFolder(messages.indicators()), false); } tether.hide(); eventBus.fireEvent(AppEvents.SCHEMA_CHANGED); } }); } }); } protected IsActivityDTO findActivityFolder(ModelData selected) { while (!(selected instanceof IsActivityDTO)) { selected = treeStore.getParent(selected); } return (IsActivityDTO) selected; } protected AttributeGroupDTO findAttributeGroupNode(ModelData selected) { if (selected instanceof AttributeGroupDTO) { return (AttributeGroupDTO) selected; } if (selected instanceof AttributeDTO) { return (AttributeGroupDTO) treeStore.getParent(selected); } throw new AssertionError("not a valid selection to add an attribute !"); } @Override protected void onDeleteConfirmed(final ModelData model) { service.execute(new Delete((EntityDTO) model), view.getDeletingMonitor(), new AsyncCallback<VoidResult>() { @Override public void onFailure(Throwable caught) { } @Override public void onSuccess(VoidResult result) { treeStore.remove(model); eventBus.fireEvent(AppEvents.SCHEMA_CHANGED); } }); } @Override protected String getStateId() { return "Design" + db.getId(); } @Override protected Command createSaveCommand() { BatchCommand batch = new BatchCommand(); for (ModelData model : treeStore.getRootItems()) { prepareBatch(batch, model); } return batch; } protected void prepareBatch(BatchCommand batch, ModelData model) { if (model instanceof EntityDTO) { Record record = treeStore.getRecord(model); if (record.isDirty()) { batch.add(new UpdateEntity((EntityDTO) model, this.getChangedProperties(record))); } } for (ModelData child : treeStore.getChildren(model)) { prepareBatch(batch, child); } } @Override public void onSelectionChanged(ModelData selectedItem) { view.setActionEnabled(UIActions.EDIT, this.db.isDesignAllowed() && canEditWithFormDesigner(selectedItem)); view.setActionEnabled(UIActions.DELETE, this.db.isDesignAllowed() && selectedItem instanceof EntityDTO); view.setActionEnabled(UIActions.OPEN_TABLE, getSelectedActivity(selectedItem) != null); } private boolean canEditWithFormDesigner(ModelData selectedItem) { IsActivityDTO activity = getSelectedActivity(selectedItem); return activity != null && activity.getReportingFrequency() == ActivityFormDTO.REPORT_ONCE; } private IsActivityDTO getSelectedActivity(ModelData selectedItem) { if (selectedItem instanceof IsActivityDTO) { return (IsActivityDTO) selectedItem; } else if (selectedItem instanceof AttributeGroupFolder || selectedItem instanceof IndicatorFolder || selectedItem instanceof AttributeGroupDTO || selectedItem instanceof IndicatorDTO || selectedItem instanceof LocationTypeDTO || selectedItem instanceof AttributeDTO) { return getSelectedActivity(treeStore.getParent(selectedItem)); } return null; } @Override public PageId getPageId() { return PAGE_ID; } @Override public Object getWidget() { return view; } @Override protected void onSaved() { eventBus.fireEvent(AppEvents.SCHEMA_CHANGED); refresh(); } }