package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui; import java.util.Date; import java.util.List; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.CloseCreationFormEvent; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.CloseCreationFormEventHandler; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui.utils.GcubeDialogExtended; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.MetadataFieldWrapper; import com.github.gwtbootstrap.client.ui.Button; import com.github.gwtbootstrap.client.ui.CheckBox; import com.github.gwtbootstrap.client.ui.ControlGroup; import com.github.gwtbootstrap.client.ui.ControlLabel; import com.github.gwtbootstrap.client.ui.Controls; import com.github.gwtbootstrap.client.ui.Icon; import com.github.gwtbootstrap.client.ui.ListBox; import com.github.gwtbootstrap.client.ui.Popover; import com.github.gwtbootstrap.client.ui.TextArea; import com.github.gwtbootstrap.client.ui.TextBox; import com.github.gwtbootstrap.client.ui.constants.ControlGroupType; import com.github.gwtbootstrap.client.ui.constants.IconType; import com.github.gwtbootstrap.datetimepicker.client.ui.DateTimeBox; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Cursor; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; public class MetaDataFieldSkeleton extends Composite{ private static MetaDataFieldSkeletonUiBinder uiBinder = GWT .create(MetaDataFieldSkeletonUiBinder.class); interface MetaDataFieldSkeletonUiBinder extends UiBinder { } @UiField Element mandatorySymbol; @UiField SpanElement name; @UiField SimplePanel elementPanel; @UiField FlowPanel noteFieldContainer; @UiField Popover noteFieldPopover; @UiField ControlLabel controlLabel; @UiField Controls controls; @UiField Icon infoIcon; @UiField FocusPanel focusPanelIconContainer; @UiField ControlGroup metafieldControlGroup; private static final String REGEX_IS_NUMBER = "[0-9]+[.]?[0-9]+"; // the element that holds the value (it could be a checkbox, textbox or listbox, textarea, calendar, two calendars, more calendars) private Widget holder; // the field this object represents private MetadataFieldWrapper field; // the dialog box for this metadata private GcubeDialogExtended dialog; // save event bus reference private HandlerManager eventBus; // the date formatter(s) to use private DateTimeFormat format1 = DateTimeFormat.getFormat("yyyy-MM-dd"); private DateTimeFormat format2 = DateTimeFormat.getFormat("yyyy-MM-dd hh:mm"); // labels private static final String INSERT_TIME_INSTANT_LABEL = "Insert a time instant"; private static final String INSERT_TIME_START_LABEL = "Insert a start time instant"; private static final String INSERT_TIME_END_LABEL = "Insert an end time instant"; // errors private static final String MANDATORY_ATTRIBUTE_MISSING = "a mandatory attribute cannot be empty"; private static final String MALFORMED_ATTRIBUTE = " the inserted value has a wrong format"; private static final String ADD_NEW_TIME_RANGE = "Add a new Time Range"; private static final String DELETE_TIME_RANGE = "Delete this Time Range"; // missing range value public static final String MISSING_RANGE_VALUE = "missing"; private static final String INSERT_MISSING_VALUE = " one or more range values missing"; private static final String INSERT_MISSING_VALUE_MANDATORY = " one or more range values missing in mandatory field(s)"; // time range separator public static final String RANGE_SEPARATOR_START_END = "/"; public static final String RANGE_SEPARATOR = ","; public MetaDataFieldSkeleton(final MetadataFieldWrapper field, HandlerManager eventBus) throws Exception{ initWidget(uiBinder.createAndBindUi(this)); // prepare information this.field = field; // event bus this.eventBus = eventBus; // bind bind(); switch(field.getType()){ case Boolean : // its a checkbox holder = new CheckBox(); if(field.getDefaultValue() != null) ((CheckBox)holder).setValue(Boolean.valueOf(field.getDefaultValue())); break; case Text: holder = new TextArea(); if(field.getDefaultValue() != null) ((TextArea)holder).setText(field.getDefaultValue()); break; case Time: holder = new DateTimeBox(); ((DateTimeBox)holder).setAutoClose(true); ((DateTimeBox)holder).setPlaceholder(INSERT_TIME_INSTANT_LABEL); // check the formatter to be used DateTimeFormat formatter = checkFormatterToUse(field.getDefaultValue()); ((DateTimeBox)holder).setFormat(formatter.getPattern()); if(field.getDefaultValue() != null && !field.getDefaultValue().isEmpty()) ((DateTimeBox)holder).setValue(formatter.parse(field.getDefaultValue())); else ((DateTimeBox)holder).setValue(null); break; case Time_Interval: holder = new FlowPanel(); DateTimeFormat formatterTimeInterval = checkFormatterToUse(field.getDefaultValue()); // start and end range date DateTimeBox start = new DateTimeBox(); DateTimeBox end = new DateTimeBox(); start.setWidth("40%"); start.getElement().getStyle().setMarginRight(10, Unit.PCT); end.setWidth("40%"); // add two calendars ((FlowPanel)holder).add(start); ((FlowPanel)holder).add(end); start.setFormat(formatterTimeInterval.getPattern()); end.setFormat(formatterTimeInterval.getPattern()); start.setPlaceholder(INSERT_TIME_START_LABEL); end.setPlaceholder(INSERT_TIME_END_LABEL); if(field.getDefaultValue() != null && !field.getDefaultValue().isEmpty()){ Date parsedTime = formatterTimeInterval.parse(field.getDefaultValue()); start.setValue(parsedTime); end.setValue(parsedTime); }else{ start.setValue(null); end.setValue(null); } break; case Times_ListOf: holder = new FlowPanel(); // start and end range date final VerticalPanel containerRanges = new VerticalPanel(); containerRanges.setWidth("100%"); FlowPanel panelFirstRange = new FlowPanel(); DateTimeBox startFirstRange = new DateTimeBox(); DateTimeBox endFirstRange = new DateTimeBox(); startFirstRange.setPlaceholder(INSERT_TIME_START_LABEL); endFirstRange.setPlaceholder(INSERT_TIME_END_LABEL); startFirstRange.setWidth("40%"); startFirstRange.getElement().getStyle().setMarginRight(10, Unit.PCT); endFirstRange.setWidth("40%"); final DateTimeFormat formatterTimeIntervalList = checkFormatterToUse(field.getDefaultValue()); startFirstRange.setFormat(formatterTimeIntervalList.getPattern()); endFirstRange.setFormat(formatterTimeIntervalList.getPattern()); if(field.getDefaultValue() != null && !field.getDefaultValue().isEmpty()){ Date formattedDefaultValueListOf = formatterTimeIntervalList.parse(field.getDefaultValue()); startFirstRange.setValue(formattedDefaultValueListOf); endFirstRange.setValue(formattedDefaultValueListOf); }else{ startFirstRange.setValue(null); endFirstRange.setValue(null); } // Add more button Button addRangeButton = new Button(); addRangeButton.setIcon(IconType.PLUS_SIGN); addRangeButton.setTitle(ADD_NEW_TIME_RANGE); addRangeButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { final FlowPanel newRange = new FlowPanel(); DateTimeBox startNewRange = new DateTimeBox(); DateTimeBox endNewRange = new DateTimeBox(); startNewRange.setWidth("40%"); startNewRange.getElement().getStyle().setMarginRight(10, Unit.PCT); endNewRange.setWidth("40%"); endNewRange.getElement().getStyle().setMarginRight(2, Unit.PCT); startNewRange.setFormat(formatterTimeIntervalList.getPattern()); endNewRange.setFormat(formatterTimeIntervalList.getPattern()); startNewRange.setPlaceholder(INSERT_TIME_START_LABEL); endNewRange.setPlaceholder(INSERT_TIME_END_LABEL); if(field.getDefaultValue() != null && !field.getDefaultValue().isEmpty()){ startNewRange.setValue(formatterTimeIntervalList.parse(field.getDefaultValue())); endNewRange.setValue(formatterTimeIntervalList.parse(field.getDefaultValue())); }else{ startNewRange.setValue(null); endNewRange.setValue(null); } // delete button Button deleteRangeButton = new Button("", IconType.MINUS_SIGN, new ClickHandler() { @Override public void onClick(ClickEvent event) { newRange.removeFromParent(); } }); deleteRangeButton.setTitle(DELETE_TIME_RANGE); newRange.add(startNewRange); newRange.add(endNewRange); newRange.add(deleteRangeButton); containerRanges.add(newRange); } }); // add calendars and plus sign panelFirstRange.add(startFirstRange); panelFirstRange.add(endFirstRange); containerRanges.add(panelFirstRange); // add the vertical panel first, then the button ((FlowPanel)holder).add(containerRanges); ((FlowPanel)holder).add(addRangeButton); break; case Number: holder = new TextBox(); if(field.getDefaultValue() != null) ((TextBox)holder).setText(field.getDefaultValue()); break; case String: // it could be a listbox or a textbox according to the vocabulary fields if(field.getVocabulary() == null || field.getVocabulary().isEmpty()){ // textbox holder = new TextBox(); if(field.getDefaultValue() != null) ((TextBox)holder).setText(field.getDefaultValue()); }else{ // listbox holder = new ListBox(field.isMultiSelection()); // if it is not mandatory, add a disabled option if(!field.getMandatory()){ ((ListBox)holder).addItem("Select " + field.getFieldName()); ((ListBox)holder).setSelectedValue("Select " + field.getFieldName()); ((ListBox)holder).getElement().getElementsByTagName("option").getItem(0).setAttribute("disabled", "disabled"); } // get vocabulary fields List vocabulary = field.getVocabulary(); for (String term : vocabulary) { ((ListBox)holder).addItem(term); } // set default value if(field.getDefaultValue() != null) ((ListBox)holder).setSelectedValue(field.getDefaultValue()); } break; default: return; } // add custom css properties controls.addStyleName("form-controls-custom"); controlLabel.addStyleName("form-control-label-custom"); // save the name name.setInnerText(field.getFieldName() + ":"); // check if it is mandatory if(!field.getMandatory()) mandatorySymbol.getStyle().setDisplay(Display.NONE); // add to the elementPanel elementPanel.add(holder); // set holder width if(holder.getClass().equals(ListBox.class)) holder.setWidth("96%"); else holder.setWidth("95%"); // set the notes, if any, and the popover if(field.getNote() != null && !field.getNote().isEmpty()){ noteFieldPopover.setText(new HTML("

" + field.getNote() +"

").getHTML()); noteFieldPopover.setHeading(new HTML("" + field.getFieldName() +"").getHTML()); infoIcon.getElement().getStyle().setCursor(Cursor.HELP); noteFieldPopover.setHtml(true); noteFieldContainer.setVisible(true); }else{ noteFieldContainer.setVisible(false); } // add a resize handler to center the dialog box if it's not null Window.addResizeHandler(new ResizeHandler() { @Override public void onResize(ResizeEvent event) { if(dialog != null) dialog.center(); } }); } /** * Check if we need to use format1 or format2 * @param defaultValue * @return format2 is returned as default */ private DateTimeFormat checkFormatterToUse(String defaultValue) { if(defaultValue == null || defaultValue.isEmpty()) return format2; else{ try{ format1.parse(defaultValue); return format1; }catch(Exception e){ GWT.log("Unable to parse default value with format 1", e); return format2; } } } /** * Bind on events */ private void bind() { // on close form eventBus.addHandler(CloseCreationFormEvent.TYPE, new CloseCreationFormEventHandler() { @Override public void onClose(CloseCreationFormEvent event) { if(dialog != null) dialog.hide(); } }); } @UiHandler("focusPanelIconContainer") void onInfoIconClick(ClickEvent c){ if(dialog == null){ // create the dialog box dialog = new GcubeDialogExtended(field.getFieldName(), field.getNote()); // set as non modal dialog.setModal(false); } // else just show and center dialog.center(); dialog.show(); } /** * Check if this field has valid values * @return a string with the occurred error on error, null otherwise */ public String isFieldValueValid(){ switch(field.getType()){ case Boolean : // nothing to validate return null; case Text: String textAreaValue = getFieldCurrentValue(); if(field.getMandatory()){ if(!textAreaValue.trim().isEmpty()) if(field.getValidator() == null || field.getValidator().isEmpty()) return null; // no further check else return checkValidator(textAreaValue, field.getValidator()) ? null : MALFORMED_ATTRIBUTE; else return MANDATORY_ATTRIBUTE_MISSING; }else{ if(textAreaValue.trim().isEmpty()) return null; else return checkValidator(textAreaValue, field.getValidator()) ? null : MALFORMED_ATTRIBUTE; } case Time: String dateValue = getFieldCurrentValue(); if(field.getMandatory()){ if(dateValue == null || dateValue.isEmpty()) return MANDATORY_ATTRIBUTE_MISSING; } return null; case Time_Interval: String dateValueInterval = getFieldCurrentValue(); if(field.getMandatory()){ if(dateValueInterval.contains(MISSING_RANGE_VALUE)) return INSERT_MISSING_VALUE_MANDATORY; // there cannot be "missing" values here else return null; }else{ String[] values = dateValueInterval.split(RANGE_SEPARATOR_START_END); if(values.length == 1) return null; // same instants else{ if(values[0].equals(values[1]) && values[0].equals(MISSING_RANGE_VALUE)) return null; // missing/missing if(!values[0].equals(MISSING_RANGE_VALUE) && !values[1].equals(MISSING_RANGE_VALUE)) return null; return INSERT_MISSING_VALUE; } } case Times_ListOf: String dateValuesInterval = getFieldCurrentValue(); if(field.getMandatory()){ if(dateValuesInterval.contains(MISSING_RANGE_VALUE)) // there cannot be "missing" values here return INSERT_MISSING_VALUE_MANDATORY; else return null; }else{ String[] values = dateValuesInterval.split(RANGE_SEPARATOR); for (int i = 0; i < values.length; i++) { String[] splitSingleRange = values[i].split(RANGE_SEPARATOR_START_END); if(splitSingleRange.length == 1) continue; // same instants else{ if(splitSingleRange[0].equals(splitSingleRange[1]) && splitSingleRange[0].equals(MISSING_RANGE_VALUE)) continue; // missing/missing, it's ok if(!splitSingleRange[0].equals(MISSING_RANGE_VALUE) && !splitSingleRange[1].equals(MISSING_RANGE_VALUE)) // value/value is ok too continue; return INSERT_MISSING_VALUE; // something like missing/x or x/missing } } return null; } case Number: String numberValue = ((TextBox)holder).getValue(); if(field.getMandatory()){ if(!numberValue.trim().isEmpty()) if(field.getValidator() == null || field.getValidator().isEmpty()) return checkValidator(numberValue, REGEX_IS_NUMBER) ? null : MALFORMED_ATTRIBUTE; else return checkValidator(numberValue, field.getValidator()) ? null : MALFORMED_ATTRIBUTE; else return " a mandatory attribute cannot be empty"; }else{ if(numberValue.trim().isEmpty()) return null; else { String validatorToUse = field.getValidator() == null || field.getValidator().isEmpty() ? REGEX_IS_NUMBER : field.getValidator(); return checkValidator(numberValue, validatorToUse) ? null : MALFORMED_ATTRIBUTE; } } case String: // just handle the case of textbox if(holder.getClass().equals(TextBox.class)){ String textBoxValue = getFieldCurrentValue(); if(field.getMandatory()){ if(!textBoxValue.trim().isEmpty()) if(field.getValidator() == null || field.getValidator().isEmpty()) return null; // no further check else return checkValidator(textBoxValue, field.getValidator()) ? null : MALFORMED_ATTRIBUTE; else return MANDATORY_ATTRIBUTE_MISSING; }else{ if(textBoxValue.trim().isEmpty()) return null; else return checkValidator(textBoxValue, field.getValidator()) ? null : MALFORMED_ATTRIBUTE; } } else{ String listBoxCurrentValue = getFieldCurrentValue(); // listbox case if(!field.getMandatory()){ if(field.getValidator() == null || field.getValidator().isEmpty()) return null; else return checkValidator(listBoxCurrentValue, field.getValidator()) ? null : MALFORMED_ATTRIBUTE; }else{ return listBoxCurrentValue == null || listBoxCurrentValue.isEmpty() ? MANDATORY_ATTRIBUTE_MISSING : null; } } default: return null; } } /** * Check if value matches validator (regex). In case validator is null, true is returned. * @param value * @param validator * @return true if validator is null OR value.matches(reges), false otherwise */ private boolean checkValidator(String value, String validator) { if(validator == null || validator.isEmpty()) return true; else return value.matches(validator); } /** * Returns the current value of the field (in case of multiselection Listbox returns * the values separated by column ','). In case of TimeInterval or TimeList see getTimeIntervalOrTimeListWithoutMissing() * @return */ public String getFieldCurrentValue(){ String toReturn = ""; switch(field.getType()){ case Boolean : toReturn = ((CheckBox)holder).getValue().toString(); break; case Text: toReturn = ((TextArea)holder).getText(); break; case Time: Date date = ((DateTimeBox)holder).getValue(); if(date == null) return null; else{ DateTimeFormat formatter = checkFormatterToUse(field.getDefaultValue()); return formatter.format(date); } case Time_Interval: DateTimeBox init = (DateTimeBox)((FlowPanel)holder).getWidget(0); DateTimeBox end = (DateTimeBox)((FlowPanel)holder).getWidget(1); Date initDate = init.getValue(); Date endDate = end.getValue(); DateTimeFormat formatter = checkFormatterToUse(field.getDefaultValue()); String initDateAsString = initDate == null ? MISSING_RANGE_VALUE : formatter.format(initDate); String endDateAsString = endDate == null ? MISSING_RANGE_VALUE : formatter.format(endDate); if(initDateAsString.equals(endDateAsString) && !initDateAsString.equals(MISSING_RANGE_VALUE)) toReturn = formatter.format(initDate); // an instant else toReturn = initDateAsString + RANGE_SEPARATOR_START_END + endDateAsString; GWT.log("Time_Interval " + toReturn); break; case Times_ListOf: // get container VerticalPanel containerRanges = (VerticalPanel)((FlowPanel)holder).getWidget(0); // get its children count int values = containerRanges.getWidgetCount(); for (int i = 0; i < values; i++) { // get FlowPanel panel FlowPanel line = (FlowPanel)containerRanges.getWidget(i); DateTimeBox initRangeInLine = (DateTimeBox)line.getWidget(0); DateTimeBox endRangeInLine = (DateTimeBox)line.getWidget(1); DateTimeFormat formatterRange = checkFormatterToUse(field.getDefaultValue()); String initRangeAsString = initRangeInLine.getValue() == null ? MISSING_RANGE_VALUE : formatterRange.format(initRangeInLine.getValue()); String endRangeAsString = endRangeInLine.getValue() == null ? MISSING_RANGE_VALUE : formatterRange.format(endRangeInLine.getValue()); if(initRangeAsString.equals(endRangeAsString) && !endRangeAsString.equals(MISSING_RANGE_VALUE)) toReturn += (i == 0) ? initRangeAsString : RANGE_SEPARATOR + initRangeAsString; // an instant else toReturn += (i == 0) ? initRangeAsString + RANGE_SEPARATOR_START_END + endRangeAsString : RANGE_SEPARATOR + initRangeAsString + RANGE_SEPARATOR_START_END + endRangeAsString; } break; case Number: case String: if(holder.getClass().equals(TextBox.class)) toReturn = ((TextBox)holder).getText(); else{// listbox case boolean first = true; // handle multiselected case for(int i = 0; i < ((ListBox)holder).getItemCount(); i++){ if(((ListBox)holder).isItemSelected(i)){ toReturn += first ? ((ListBox)holder).getItemText(i) : ", " + ((ListBox)holder).getItemText(i); first = false; } } // if it was not mandatory but there was no choice, returning empty string if(!field.getMandatory()) if(toReturn.equals("Select " + field.getFieldName())) toReturn = ""; } break; default: break; } return toReturn; } /** * Returns the current name of the field * @return */ public String getFieldName(){ return field.getFieldName(); } /** * Freeze this widget (after on create) */ public void freeze() { switch(field.getType()){ case Boolean : ((CheckBox)holder).setEnabled(false); break; case Text: ((TextArea)holder).setEnabled(false); break; case Time: ((DateTimeBox)holder).setEnabled(false); break; case Time_Interval: ((DateTimeBox)((FlowPanel)holder).getWidget(0)).setEnabled(false); ((DateTimeBox)((FlowPanel)holder).getWidget(1)).setEnabled(false); break; case Times_ListOf: // get container VerticalPanel containerRanges = (VerticalPanel)((FlowPanel)holder).getWidget(0); // freeze the button too ((Button)((FlowPanel)holder).getWidget(1)).setEnabled(false); // get its children count int values = containerRanges.getWidgetCount(); for (int i = 0; i < values; i++) { // get FlowPanel panel FlowPanel line = (FlowPanel)containerRanges.getWidget(i); ((DateTimeBox)line.getWidget(0)).setEnabled(false); ((DateTimeBox)line.getWidget(1)).setEnabled(false); } break; case Number: ((TextBox)holder).setEnabled(false); break; case String: if(holder.getClass().equals(ListBox.class)) ((ListBox)holder).setEnabled(false); else ((TextBox)holder).setEnabled(false); break; default: break; } } /** * Use it to retrieve TimeInterval or TimeListOf after isValidField() invocation returned true (and only for these types!) * @return */ public String getTimeIntervalOrTimeListWithoutMissing(){ String currentValue = getFieldCurrentValue(); switch(field.getType()){ case Time_Interval: String tempCurrentValue = currentValue.replaceAll(MISSING_RANGE_VALUE, "").replaceAll(RANGE_SEPARATOR_START_END, ""); if(tempCurrentValue.trim().equals("")) return ""; else return currentValue; case Times_ListOf: String toReturn = ""; String[] values = currentValue.split(RANGE_SEPARATOR); for (int i = 0; i < values.length; i++) { String[] splitSingleRange = values[i].split(RANGE_SEPARATOR_START_END); if(splitSingleRange.length == 1) toReturn += splitSingleRange + RANGE_SEPARATOR; // same instants else{ if(splitSingleRange[0].equals(splitSingleRange[1]) && splitSingleRange[0].equals(MISSING_RANGE_VALUE)) continue; // missing/missing, it's ok if(!splitSingleRange[0].equals(MISSING_RANGE_VALUE) && !splitSingleRange[1].equals(MISSING_RANGE_VALUE)) // value/value is ok too toReturn += splitSingleRange[0] + RANGE_SEPARATOR_START_END + splitSingleRange[1] + RANGE_SEPARATOR; } } if(toReturn.endsWith(RANGE_SEPARATOR)) toReturn = toReturn.substring(0, toReturn.length() - 1); return toReturn; default: return null; } } /** * Get the original MetadataFieldWrapper object * @return */ public MetadataFieldWrapper getField() { return field; } public void removeError() { metafieldControlGroup.setType(ControlGroupType.NONE); } public void showError() { metafieldControlGroup.setType(ControlGroupType.ERROR); } }