package org.activityinfo.ui.client.component.form.field; import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.gwt.cell.client.ValueUpdater; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Widget; import org.activityinfo.model.resource.ResourceId; import org.activityinfo.model.type.Cardinality; import org.activityinfo.model.type.FieldType; import org.activityinfo.model.type.enumerated.EnumValue; import org.activityinfo.model.type.enumerated.EnumItem; import org.activityinfo.model.type.enumerated.EnumType; import org.activityinfo.promise.Promise; import org.activityinfo.ui.client.widget.RadioButton; import java.util.ArrayList; import java.util.List; import java.util.Set; public class EnumFieldWidget implements FormFieldWidget<EnumValue> { public interface Templates extends SafeHtmlTemplates { @Template("{0} <span class='enum-edit-controls'>[ <span class='enum-edit'>Edit</span> | <span class='enum-remove'>Delete</span> ]</span>") SafeHtml designLabel(String label); @Template("{0} <span class='enum-edit-controls'/>") SafeHtml normalLabel(String label); @Template("<span class='enum-add'>+ {0}</span>") SafeHtml addChoice(String label); } private static int nextId = 1; private static final Templates TEMPLATES = GWT.create(Templates.class); private EnumType enumType; private String groupName; private final FlowPanel panel; private final FlowPanel boxPanel; private final List<CheckBox> controls; private final FieldWidgetMode fieldWidgetMode; public EnumFieldWidget(EnumType enumType, final ValueUpdater<EnumValue> valueUpdater, FieldWidgetMode fieldWidgetMode) { this.enumType = enumType; this.groupName = "group" + (nextId++); this.fieldWidgetMode = fieldWidgetMode; boxPanel = new FlowPanel(); controls = new ArrayList<>(); ValueChangeHandler<Boolean> changeHandler = new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { valueUpdater.update(updatedValue()); } }; for (final EnumItem instance : enumType.getValues()) { CheckBox checkBox = createControl(instance); checkBox.addValueChangeHandler(changeHandler); boxPanel.add(checkBox); } panel = new FlowPanel(); panel.add(boxPanel); if (this.fieldWidgetMode == FieldWidgetMode.DESIGN) { panel.add(new HTML(TEMPLATES.addChoice("Add option"))); } panel.sinkEvents(Event.MOUSEEVENTS); panel.addDomHandler(new MouseUpHandler() { @Override public void onMouseUp(MouseUpEvent event) { if(event.getNativeButton() == NativeEvent.BUTTON_LEFT) { onClick(event); } } }, MouseUpEvent.getType()); } private void onClick(MouseUpEvent event) { Element target = event.getNativeEvent().getEventTarget().cast(); if(target.getClassName().contains("enum-remove")) { remove(findId(target)); } else if(target.getClassName().contains("enum-edit")) { ResourceId id = findId(target); editLabel(id); } else if(target.getClassName().contains("enum-add")) { addOption(); } } private void addOption() { String newLabel = Window.prompt("Enter a new label for this option", ""); if(!Strings.isNullOrEmpty(newLabel)) { EnumItem newValue = new EnumItem(EnumItem.generateId(), newLabel); enumType.getValues().add(newValue); boxPanel.add(createControl(newValue)); } } private void editLabel(ResourceId id) { EnumItem enumItem = enumValueForId(id); String newLabel = Window.prompt("Enter a new label for this option", enumItem.getLabel()); if(!Strings.isNullOrEmpty(newLabel)) { enumItem.setLabel(newLabel); controlForId(id).setHTML(TEMPLATES.designLabel(newLabel)); } } private CheckBox controlForId(ResourceId id) { for(CheckBox checkBox : controls) { if(checkBox.getFormValue().equals(id.asString())) { return checkBox; } } throw new IllegalArgumentException(id.asString()); } private EnumItem enumValueForId(ResourceId id) { for(EnumItem value : enumType.getValues()) { if(value.getId().equals(id)) { return value; } } throw new IllegalArgumentException(id.asString()); } private void remove(ResourceId id) { controlForId(id).removeFromParent(); enumType.getValues().remove(enumValueForId(id)); } private ResourceId findId(Element target) { Element container = target; while(Strings.isNullOrEmpty(container.getAttribute("data-id"))) { container = container.getParentElement(); } return ResourceId.valueOf(container.getAttribute("data-id")); } private SafeHtml label(String label) { return fieldWidgetMode == FieldWidgetMode.DESIGN ? TEMPLATES.designLabel(label) : TEMPLATES.normalLabel(label); } private CheckBox createControl(EnumItem instance) { CheckBox checkBox; if(enumType.getCardinality() == Cardinality.SINGLE) { checkBox = new RadioButton(groupName, label(instance.getLabel())); } else { checkBox = new org.activityinfo.ui.client.widget.CheckBox(label(instance.getLabel())); } checkBox.setFormValue(instance.getId().asString()); checkBox.getElement().setAttribute("data-id", instance.getId().asString()); controls.add(checkBox); return checkBox; } @Override public void setReadOnly(boolean readOnly) { for (CheckBox control : controls) { control.setEnabled(!readOnly); } } private EnumValue updatedValue() { final Set<ResourceId> value = Sets.newHashSet(); for (CheckBox control : controls) { if(control.getValue()) { value.add(ResourceId.valueOf(control.getFormValue())); } } return new EnumValue(value); } @Override public Promise<Void> setValue(EnumValue value) { for (CheckBox entry : controls) { entry.setValue(containsIgnoreCase(value.getResourceIds(), entry.getFormValue())); } return Promise.done(); } /** * Check with ignoreCase, trick is that values from html form elemns sometimes are lowercased * @param resourceIds * * @return */ private boolean containsIgnoreCase(Set<ResourceId> resourceIds, String resourceId) { for (ResourceId resource : resourceIds) { if (resource.asString().equalsIgnoreCase(resourceId)) { return true; } } return false; } @Override public void clearValue() { setValue(EnumValue.EMPTY); } @Override public void setType(FieldType type) { } @Override public Widget asWidget() { return panel; } }