Java tutorial
package org.openxdata.designer.client.view; import com.google.gwt.core.client.GWT; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import org.openxdata.designer.client.Context; import org.openxdata.designer.client.FormDesignerWidget; import org.openxdata.designer.client.controller.ItemSelectionListener; import org.openxdata.designer.client.util.FormDesignerUtil; import org.openxdata.designer.client.widget.skiprule.FieldWidget; import org.openxdata.sharedlib.client.model.DynamicOptionDef; import org.openxdata.sharedlib.client.model.FormDef; import org.openxdata.sharedlib.client.model.OptionDef; import org.openxdata.sharedlib.client.model.PageDef; import org.openxdata.sharedlib.client.model.QuestionDef; import org.openxdata.sharedlib.client.util.FormUtil; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.PushButton; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; import com.google.gwt.xml.client.Node; import org.openxdata.designer.client.DesignerMessages; import org.openxdata.sharedlib.client.locale.FormsConstants; import org.openxdata.sharedlib.client.model.QuestionType; /** * This widget enables creation of dynamic selection lists. * * www.openxdata.org - Licensed as written in license.txt and original sources of this file and its authors are found in sources.txt. * */ public class DynamicListsView extends Composite implements ItemSelectionListener, ClickHandler { private final FormsConstants i18n = GWT.create(FormsConstants.class); private final DesignerMessages messages = GWT.create(DesignerMessages.class); /** The main or root widget. */ private VerticalPanel verticalPanel = new VerticalPanel(); /** Widget to display the "Values for" text. */ private Label lblValuesFor = new Label(i18n.valuesFor()); /** Widget to display the is equal to text. */ private Label lblEqual = new Label(" " + i18n.isEqualTo()); /** The widget for selection of the parent questions. * The parent question is the one on which the single select dynamic question depends. */ private FieldWidget fieldWidget; /** Widget to display the list of parent question options to select from. */ private ListBox lbOption = new ListBox(false); /** Table to hold the list of dynamic options. */ private FlexTable table = new FlexTable(); /** Button to add a new dynamic selection list option. */ private Button btnAdd = new Button(i18n.addNew()); /** The form definition object that this dynamic list belongs to. */ private FormDef formDef; /** The question that we are building this dynamic selection list for. * For the Continent and Country questions, this would be the Country question. */ private QuestionDef questionDef; /** Flag determining whether to enable this widget or not. */ private boolean enabled; /** The dynamic option definition object that we are building. */ private DynamicOptionDef dynamicOptionDef; /** Contains the list of child options for the parent question's selected option. */ private List<OptionDef> optionList; /** The parent question whose selected option determines the list of child options. * For the Continent and Country questions, this would be the Continent question. */ private QuestionDef parentQuestionDef; /** * Creates a new instance of the dynamic lists widget. */ public DynamicListsView() { setupWidgets(); } /** * Sets up widgets. */ private void setupWidgets() { fieldWidget = new FieldWidget(this); fieldWidget.setForDynamicOptions(true); HorizontalPanel horizontalPanel = new HorizontalPanel(); horizontalPanel.add(lblValuesFor); horizontalPanel.add(fieldWidget); horizontalPanel.add(lblEqual); horizontalPanel.add(lbOption); horizontalPanel.setSpacing(5); verticalPanel.add(horizontalPanel); verticalPanel.add(table); lbOption.addChangeHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { updateOptionList(); } }); btnAdd.addClickHandler(this); table.setStyleName("cw-FlexTable"); table.setWidget(0, 0, new Label(i18n.text())); table.setWidget(0, 1, new Label(messages.binding())); table.setWidget(0, 2, new Label(i18n.action())); table.getFlexCellFormatter().setColSpan(0, 2, 3); table.setWidth("100%"); table.setHeight("100%"); table.getCellFormatter().setStyleName(0, 0, "getting-started-label"); table.getCellFormatter().setStyleName(0, 1, "getting-started-label"); table.getCellFormatter().setStyleName(0, 2, "getting-started-label"); initWidget(verticalPanel); } /** * Sets the dynamic selection list question, whose list of options we set on this widget. * * @param questionDef the question. */ public void setQuestionDef(QuestionDef questionDef) { optionList = null; dynamicOptionDef = null; parentQuestionDef = null; setEnabled(true); clear(); if (questionDef == null) { lblValuesFor.setText(i18n.valuesFor()); } if (questionDef != null) { if (questionDef.getDataType() != QuestionType.LIST_EXCLUSIVE_DYNAMIC) { setEnabled(false); return; } if (questionDef.getParent() instanceof PageDef) formDef = ((PageDef) questionDef.getParent()).getParent(); else formDef = ((PageDef) ((QuestionDef) questionDef.getParent()).getParent()).getParent(); lblValuesFor.setText(i18n.valuesFor() + questionDef.getDisplayText() + " " + i18n.whenAnswerFor()); } this.questionDef = questionDef; fieldWidget.setDynamicQuestionDef(questionDef); fieldWidget.setFormDef(formDef); QuestionDef parentQuestionDef = formDef.getDynamicOptionsParent(questionDef.getId()); if (parentQuestionDef != null) fieldWidget.selectQuestion(parentQuestionDef); if (Context.isStructureReadOnly()) { lbOption.setEnabled(false); fieldWidget.setEnabled(false); } } /** * Sets the form definition object that this dynamic selection list belongs to. * * @param formDef the form definition object. */ public void setFormDef(FormDef formDef) { updateDynamicLists(); this.formDef = formDef; questionDef = null; parentQuestionDef = null; optionList = null; dynamicOptionDef = null; clear(); } /** * Removes all dynamic selection list values for any previous widget, if any. */ private void clear() { if (questionDef != null) updateDynamicLists(); questionDef = null; lblValuesFor.setText(i18n.valuesFor()); lbOption.clear(); while (verticalPanel.getWidgetCount() > 4) verticalPanel.remove(verticalPanel.getWidget(3)); clearChildOptions(); fieldWidget.setQuestion(null); } /** * Removes all options from the options list table. */ private void clearChildOptions() { //Removes all options apart from the header which is at index 0. while (table.getRowCount() > 1) table.removeRow(1); } /** * Sets whether to enable this widget or not. * * @param enabled set to true to enable, else false. */ public void setEnabled(boolean enabled) { this.enabled = enabled; lbOption.setEnabled(enabled); fieldWidget.setEnabled(enabled); if (!enabled) clear(); } /** * Checks whether this widget is enabled or not. * * @return true of enabled, else false. */ public boolean isEnabled() { return enabled; } /** * @see org.openxdata.designer.client.controller.ItemSelectionListener#onItemSelected(Object, Object) */ public void onItemSelected(Object sender, Object item) { //This is only useful for us when a new parent question has been selected. if (sender != fieldWidget) return; //Clear all parent and child options. lbOption.clear(); clearChildOptions(); parentQuestionDef = (QuestionDef) item; //we only allow option lists for single select and single select dynamic types. QuestionType type = parentQuestionDef.getDataType(); if (!(type == QuestionType.LIST_EXCLUSIVE || type == QuestionType.LIST_EXCLUSIVE_DYNAMIC)) return; //Get the dynamic option definition object for which the selected //question acts as the parent of the relationship. dynamicOptionDef = formDef.getDynamicOptions(parentQuestionDef.getId()); //As for now, we do not allow the a parent question to map to more //than once child question. if (dynamicOptionDef != null && dynamicOptionDef.getQuestionId() != questionDef.getId()) return; //Populate the list of parent options from a single select question. if (type == QuestionType.LIST_EXCLUSIVE) { if (!(parentQuestionDef.getOptionCount() > 0)) { //we are creating new DynamicOptionDef() because we want to allow //one specify type to be single select dynamic without specifying any //options for cases where they will be got from the server using the //external source widget filter property. dynamicOptionDef = new DynamicOptionDef(); dynamicOptionDef.setQuestionId(questionDef.getId()); return; } List<OptionDef> options = parentQuestionDef.getOptions(); for (int i = 0; i < options.size(); i++) { OptionDef optionDef = (OptionDef) options.get(i); lbOption.addItem(optionDef.getText(), String.valueOf(optionDef.getId())); } } //Populate the list of parent options from a dynamic selection list question. if (type == QuestionType.LIST_EXCLUSIVE_DYNAMIC) { if (dynamicOptionDef == null) { //we are creating new DynamicOptionDef() because we want to allow //one specify type to be single select dynamic without specifying any //options for cases where they will be got from the server using the //external source widget filter property. dynamicOptionDef = new DynamicOptionDef(); dynamicOptionDef.setQuestionId(questionDef.getId()); } DynamicOptionDef options = formDef.getChildDynamicOptions(parentQuestionDef.getId()); if (options != null && options.getParentToChildOptions() != null) { Iterator<Entry<Integer, List<OptionDef>>> iterator = options.getParentToChildOptions().entrySet() .iterator(); while (iterator.hasNext()) { Entry<Integer, List<OptionDef>> entry = iterator.next(); List<OptionDef> list = entry.getValue(); for (int index = 0; index < list.size(); index++) { OptionDef optionDef = list.get(index); lbOption.addItem(optionDef.getText(), String.valueOf(optionDef.getId())); } } } } //If there is any selection, update the table of options. if (lbOption.getSelectedIndex() >= 0) updateOptionList(); } /** * @see org.openxdata.designer.client.controller.ItemSelectionListener#onStartItemSelection(Object) */ public void onStartItemSelection(Object sender) { } /** * Updates the form definition object with the dynamic option definition object * that is being edited on this widget. */ public void updateDynamicLists() { //dynamicOptionDef.size() == 0 is commented out because we want to allow //one specify type to be single select dynamic without specifying any //options for cases where they will be got from the server using the //external source widget filter property. if (dynamicOptionDef == null) { if (parentQuestionDef != null) formDef.removeDynamicOptions(parentQuestionDef.getId()); return; } formDef.setDynamicOptionDef(parentQuestionDef.getId(), dynamicOptionDef); } /** * Populates the table of dynamic options with those options that are allowed * for the currently selected option for the parent question. */ public void updateOptionList() { clearChildOptions(); if (dynamicOptionDef == null) { dynamicOptionDef = new DynamicOptionDef(); dynamicOptionDef.setQuestionId(questionDef.getId()); } int optionId = Integer.parseInt(lbOption.getValue(lbOption.getSelectedIndex())); optionList = dynamicOptionDef.getOptionList(optionId); if (optionList == null) { optionList = new ArrayList<OptionDef>(); dynamicOptionDef.setOptionList(optionId, optionList); } for (int index = 0; index < optionList.size(); index++) { OptionDef optionDef = optionList.get(index); addOption(optionDef.getText(), optionDef.getVariableName(), table.getRowCount()); } addAddButton(); } /** * Called when any of the add new, delete, move up or move down * button has been clicked. * * @sender the button which was clicked. */ public void onClick(ClickEvent event) { Object sender = event.getSource(); if (sender == btnAdd) addNewOption().setFocus(true); else { int rowCount = table.getRowCount(); for (int row = 1; row < rowCount; row++) { //Delete button if (sender == table.getWidget(row, 2)) { OptionDef optionDef = optionList.get(row - 1); if (!Window.confirm(i18n.removeRowPrompt() + " [" + optionDef.getText() + " - " + optionDef.getVariableName() + "]")) return; table.removeRow(row); optionList.remove(row - 1); if (optionDef.getControlNode() != null && optionDef.getControlNode().getParentNode() != null) optionDef.getControlNode().getParentNode().removeChild(optionDef.getControlNode()); break; } else if (sender == table.getWidget(row, 3)) { //Move up button. if (row == 1) return; moveOptionUp(optionList.get(row - 1)); OptionDef optionDef = optionList.get(row - 1); addOption(optionDef.getText(), optionDef.getVariableName(), row); optionDef = optionList.get(row - 2); addOption(optionDef.getText(), optionDef.getVariableName(), row - 1); break; } else if (sender == table.getWidget(row, 4)) { //Move down button. if (row == (rowCount - 2)) return; moveOptionDown(optionList.get(row - 1)); OptionDef optionDef = optionList.get(row - 1); addOption(optionDef.getText(), optionDef.getVariableName(), row); optionDef = optionList.get(row); addOption(optionDef.getText(), optionDef.getVariableName(), row + 1); break; } } } } /** * Adds a new dynamic list option to the table. */ private TextBox addNewOption() { table.removeRow(table.getRowCount() - 1); TextBox textBox = addOption("", "", table.getRowCount()); textBox.setFocus(true); textBox.selectAll(); addAddButton(); addNewOptionDef(); return textBox; } /** * Adds a new option to the table of dynamic options list. * * @param text the option text. * @param binding the option binding. * @param row the index of the row to add. * @return the widget for editing text of the new option. */ private TextBox addOption(String text, String binding, int row) { TextBox txtText = new TextBox(); TextBox txtBinding = new TextBox(); txtText.setText(text); txtBinding.setText(binding); table.setWidget(row, 0, txtText); table.setWidget(row, 1, txtBinding); txtBinding.setEnabled(!Context.isStructureReadOnly()); PushButton button = new PushButton(FormUtil.createImage(FormDesignerWidget.images.delete())); button.setTitle(i18n.deleteItem()); button.addClickHandler(this); table.setWidget(row, 2, button); button = new PushButton(FormUtil.createImage(FormDesignerWidget.images.moveup())); button.setTitle(i18n.moveUp()); button.addClickHandler(this); table.setWidget(row, 3, button); button = new PushButton(FormUtil.createImage(FormDesignerWidget.images.movedown())); button.setTitle(i18n.moveDown()); button.addClickHandler(this); table.setWidget(row, 4, button); table.getFlexCellFormatter().setWidth(row, 0, "45%"); table.getFlexCellFormatter().setWidth(row, 1, "45%"); table.getFlexCellFormatter().setWidth(row, 2, "3.3%"); table.getFlexCellFormatter().setWidth(row, 3, "3.3%"); table.getFlexCellFormatter().setWidth(row, 4, "3.3%"); table.getWidget(row, 0).setWidth("100%"); table.getWidget(row, 1).setWidth("100%"); txtText.addChangeHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { updateText((TextBox) event.getSource()); } }); txtText.addKeyDownHandler(new KeyDownHandler() { public void onKeyDown(KeyDownEvent event) { int keyCode = event.getNativeKeyCode(); if (keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_DOWN) moveToNextWidget((Widget) event.getSource(), 0, keyCode == KeyCodes.KEY_DOWN); else if (keyCode == KeyCodes.KEY_UP) moveToPrevWidget((Widget) event.getSource(), 0); } }); txtText.addKeyUpHandler(new KeyUpHandler() { public void onKeyUp(KeyUpEvent event) { int keyCode = event.getNativeKeyCode(); if (!(keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_DOWN || keyCode == KeyCodes.KEY_DOWN || keyCode == KeyCodes.KEY_UP)) updateText((TextBox) event.getSource()); } }); txtBinding.addChangeHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { updateBinding((TextBox) event.getSource()); } }); txtBinding.addKeyDownHandler(new KeyDownHandler() { public void onKeyDown(KeyDownEvent event) { int keyCode = event.getNativeKeyCode(); if (keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_DOWN) moveToNextWidget((Widget) event.getSource(), 1, keyCode == KeyCodes.KEY_DOWN); else if (keyCode == KeyCodes.KEY_UP) moveToPrevWidget((Widget) event.getSource(), 1); } }); return txtText; } /** * Updates the selected object with the new text as typed by the user. */ private void updateText(TextBox txtText) { int rowCount = table.getRowCount(); for (int row = 1; row < rowCount; row++) { if (txtText == table.getWidget(row, 0)) { OptionDef optionDef = null; if (optionList.size() > row - 1) optionDef = optionList.get(row - 1); if (optionDef == null) optionDef = addNewOptionDef(); String orgTextDefBinding = FormDesignerUtil.getXmlTagName(optionDef.getText()); optionDef.setText(txtText.getText()); if (!Context.isStructureReadOnly()) { //automatically set the binding, if empty. TextBox txtBinding = (TextBox) table.getWidget(row, 1); String binding = txtBinding.getText(); if (binding == null || binding.trim().length() == 0 || binding.equals(orgTextDefBinding)) { txtBinding.setText(FormDesignerUtil.getXmlTagName(optionDef.getText())); optionDef.setVariableName(txtBinding.getText()); } } break; } } } /** * Adds a new option definition object. * * @return the new option definition object. */ private OptionDef addNewOptionDef() { OptionDef optionDef = new OptionDef(parentQuestionDef); optionDef.setId(dynamicOptionDef.getNextOptionId()); dynamicOptionDef.setNextOptionId(optionDef.getId() + 1); optionList.add(optionDef); return optionDef; } /** * Updates the selected object with the new binding as typed by the user. */ private void updateBinding(TextBox txtBinding) { int rowCount = table.getRowCount(); for (int row = 1; row < rowCount; row++) { if (txtBinding == table.getWidget(row, 1)) { OptionDef optionDef = null; if (optionList.size() > row - 1) optionDef = optionList.get(row - 1); if (optionDef == null) optionDef = addNewOptionDef(); optionDef.setVariableName(txtBinding.getText()); break; } } } /** * Adds the add new button to the table widget. */ private void addAddButton() { FlexCellFormatter cellFormatter = table.getFlexCellFormatter(); int row = table.getRowCount(); cellFormatter.setColSpan(row, 0, 5); cellFormatter.setHorizontalAlignment(row, 0, HasHorizontalAlignment.ALIGN_CENTER); table.setWidget(row, 0, btnAdd); } /** * Moves input focus to the next widget. * * @param sender the widget after which to move the input focus. * @param col the index of the column which currently has input focus. * @param sameCol set to true to move to the next widget in the same column. */ private void moveToNextWidget(Widget sender, int col, boolean sameCol) { if (sameCol) { int rowCount = table.getRowCount(); for (int row = 1; row < rowCount; row++) { if (sender == table.getWidget(row, col)) { if (row == (rowCount - 2)) return; TextBox textBox = ((TextBox) table.getWidget(row + 1, col)); textBox.setFocus(true); textBox.selectAll(); break; } } } else { int rowCount = table.getRowCount(); for (int row = 1; row < rowCount; row++) { if (sender == table.getWidget(row, col)) { TextBox textBox = ((TextBox) table.getWidget(row, col)); if (col == 1) { if (row == (rowCount - 2)) { if (textBox.getText() != null && textBox.getText().trim().length() > 0) addNewOption(); return; } row++; col = 1; //0; } else { if (textBox.getText() == null || textBox.getText().trim().length() == 0) return; else if (row == (rowCount - 2)) { addNewOption(); return; } else row++; col = 0; //1; } textBox = ((TextBox) table.getWidget(row, col)); textBox.setFocus(true); textBox.selectAll(); break; } } } } /** * Moves input focus to the widget before. * * @param sender the widget before which to move the input focus. * @param col the index of the column which currently has input focus. */ private void moveToPrevWidget(Widget sender, int col) { int rowCount = table.getRowCount(); //Starting from index 1 since 0 is the header row. for (int row = 1; row < rowCount; row++) { if (sender == table.getWidget(row, col)) { if (row == 1) return; TextBox textBox = ((TextBox) table.getWidget(row - 1, col)); textBox.setFocus(true); textBox.selectAll(); break; } } } /** * Moves an option one position upwards. * * @param optionDef the option to move. */ public void moveOptionUp(OptionDef optionDef) { List<OptionDef> optns = optionList; int index = optns.indexOf(optionDef); optns.remove(optionDef); Node parentNode = null; if (optionDef.getControlNode() != null) { parentNode = optionDef.getControlNode().getParentNode(); parentNode.removeChild(optionDef.getControlNode()); } OptionDef currentOptionDef; List<OptionDef> list = new ArrayList<OptionDef>(); //Remove all from index before selected all the way downwards while (optns.size() >= index) { currentOptionDef = (OptionDef) optns.get(index - 1); list.add(currentOptionDef); optns.remove(currentOptionDef); } optns.add(optionDef); for (int i = 0; i < list.size(); i++) { if (i == 0) { OptionDef optnDef = (OptionDef) list.get(i); if (parentNode != null && optnDef.getControlNode() != null && optionDef.getControlNode() != null) parentNode.insertBefore(optionDef.getControlNode(), optnDef.getControlNode()); } optns.add(list.get(i)); } } /** * Moves an option one position downwards. * * @param optionDef the option to move. */ public void moveOptionDown(OptionDef optionDef) { List<OptionDef> optns = optionList; int index = optns.indexOf(optionDef); optns.remove(optionDef); Node parentNode = null; if (optionDef.getControlNode() != null) { parentNode = optionDef.getControlNode().getParentNode(); parentNode.removeChild(optionDef.getControlNode()); } OptionDef currentItem; List<OptionDef> list = new ArrayList<OptionDef>(); //Remove all otions below selected index while (optns.size() > 0 && optns.size() > index) { currentItem = (OptionDef) optns.get(index); list.add(currentItem); optns.remove(currentItem); } for (int i = 0; i < list.size(); i++) { if (i == 1) { optns.add(optionDef); //Add after the first item but before the current (second). OptionDef optnDef = getNextSavedOption(list, i); if (optnDef.getControlNode() != null && optionDef.getControlNode() != null) parentNode.insertBefore(optionDef.getControlNode(), optnDef.getControlNode()); else if (parentNode != null) parentNode.appendChild(optionDef.getControlNode()); } optns.add(list.get(i)); } //If was second last and hence becoming last if (list.size() == 1) { optns.add(optionDef); if (optionDef.getControlNode() != null) parentNode.appendChild(optionDef.getControlNode()); } } /** * Gets the next option which has been converted to xforms and * hence attached to an xforms document node, starting at a given * index in a list of options. * * @param options the list of options. * @param index the index to start from in the option list. * @return the option. */ private OptionDef getNextSavedOption(List<OptionDef> options, int index) { for (int i = index; i < options.size(); i++) { OptionDef optionDef = (OptionDef) options.get(i); if (optionDef.getControlNode() != null) return optionDef; } return (OptionDef) options.get(index); } }