/* * Copyright (c) 2003,2009 Declarative Engineering LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Declarative Engineering LLC * verson 1 which accompanies this distribution, and is available at * http://declarativeengineering.com/legal/DE_Developer_License_v1.txt */ package com.foundation.tcv.swt.client; import com.common.util.LiteList; import com.foundation.tcv.swt.*; import com.foundation.tcv.view.*; public abstract class IndexedCollectionComponent extends CollectionComponent implements IIndexedCollectionComponent { /** The currently selected index (on the server) if allowing only a single selection. A negative one indicates no selection. */ private int selectedObjectIdOnServer = -1; /** The custom selection last sent to the server or received by the server. This will be null if there is no custom selection or there is no selection at all. */ private String customSelectionOnServer = null; /** Maps the row objects given the index in the control. An object can be represented more than once in the control if the same row object exists in multiple indices. */ private LiteList rowObjectByIndexMap = new LiteList(100, 200); protected static class IndexedRowObject extends RowObject { public Object value = null; public IndexedRowObject(int objectId, int hiddenDataCount) { super(objectId, hiddenDataCount); }//IndexedRowObject()// }//IndexedRowObject// /** * IndexedCollectionComponent constructor. */ public IndexedCollectionComponent() { super(); }//IndexedCollectionComponent()// /** * Gets the row object for the given display collection index. * @param index The zero based index of the row object in the display collection. * @return The row object for the display index. */ protected RowObject getRowObjectAtIndex(int index) { return (RowObject) rowObjectByIndexMap.get(index); }//getRowObjectAtIndex()// /* (non-Javadoc) * @see com.foundation.view.IViewComponent#viewInitialize() */ protected void internalViewInitialize() { super.internalViewInitialize(); }//internalViewInitialize()// /* (non-Javadoc) * @see com.foundation.view.IViewComponent#viewRelease() */ protected void internalViewRelease() { super.internalViewRelease(); }//internalViewRelease()// /* (non-Javadoc) * @see com.foundation.view.IViewComponent#viewSynchronize() */ protected void internalViewSynchronize() { super.internalViewSynchronize(); internalViewSynchronizeSelection(); }//internalViewSynchronize()// /** * Synchronizes the selection or forces the auto synchronize task to run if one is pending. */ protected void internalViewSynchronizeSelection() { if(!getAutoSynchronizeSelection()) { synchronizeSelection(); }//if// else { synchronized(this) { if(autoSynchronizeSelectionTask != null) { removeTask(autoSynchronizeSelectionTask); autoSynchronizeSelectionTask.execute(); }//if// }//synchronized// }//else// }//internalViewSynchronizeSelection()// /** * Synchronizes the selection to the model from the view. *
This method has no logic to examine the auto synchronization state. It simply synchronizes the selection immediatly.
*/ protected void synchronizeSelection() { if(getAllowMultiSelection()) { int[] selectionIndices = controlGetSelections(); //Convert each selection index into an object id.// for(int index = 0; index < selectionIndices.length; index++) { selectionIndices[index] = getRowObjectAtIndex(selectionIndices[index]).objectId; }//for// if(selectionIndices.length == 0) { selectionIndices = null; }//if// sendMessage(MESSAGE_VIEW_SYNCHRONIZE_SELECTION, (Object) selectionIndices); }//if// else if(hasCustomSelection()) { String text = controlGetText(); if(!text.equals(customSelectionOnServer)) { selectedObjectIdOnServer = -1; customSelectionOnServer = text; sendMessage(MESSAGE_VIEW_SYNCHRONIZE_SELECTION, text); }//if// }//else if// else { int selectionIndex = controlGetSelection(); //Convert from the index to the object identifier.// selectionIndex = selectionIndex != -1 ? getRowObjectAtIndex(selectionIndex).objectId : -1; if(selectionIndex != selectedObjectIdOnServer) { selectedObjectIdOnServer = selectionIndex; customSelectionOnServer = null; sendMessage(MESSAGE_VIEW_SYNCHRONIZE_SELECTION, selectionIndex != -1 ? new Integer(selectionIndex) : null); }//if// }//else// }//synchronizeSelection()// /** * Gets the set of selected object identifiers. * @return The set of selected object identifiers, or null if nothing is selected. */ protected int[] getSelectedObjectIds() { int[] selectionIndices = controlGetSelections(); //Convert each selection index into an object id.// for(int index = 0; index < selectionIndices.length; index++) { selectionIndices[index] = getRowObjectAtIndex(selectionIndices[index]).objectId; }//for// if(selectionIndices.length == 0) { selectionIndices = null; }//if// return selectionIndices; //sendMessage(MESSAGE_VIEW_SYNCHRONIZE_SELECTION, (Object) selectionIndices); }//getSelectedObjectIds()// /** * Determines whether there is a custom selection. * @return Whether the user has typed a value in the control that is not in the set to select from. */ protected boolean hasCustomSelection() { return getAllowUserItems() && (controlGetSelection() == -1) && (controlGetText() != null) && (controlGetText().length() > 0); }//hasCustomSelection()// /** * Gets the set of selected object identifiers. * @return The set of selected object identifiers, or null if nothing is selected. */ protected int getSelectedObjectId() { int selectionIndex = controlGetSelection(); /* if((getAllowUserItems()) && (selectionIndex == -1)) { //TODO: Track whether the text has changed? //sendMessage(MESSAGE_VIEW_SYNCHRONIZE_SELECTION, controlGetText()); currentlySelectedObjectId = -2; }//if// else { //sendMessage(MESSAGE_VIEW_SYNCHRONIZE_SELECTION, selectionIndex != -1 ? new Integer(selectionIndex) : null); }//else// */ return selectionIndex != -1 ? getRowObjectAtIndex(selectionIndex).objectId : -1; }//getSelectedObjectId()// /** * Gets the currently selected object identifier on the server. * @return The object identifier for the selection known by the server, or -1 for no selection. */ protected int getSelectedObjectIdOnServer() { return selectedObjectIdOnServer; }//getSelectedObjectIdOnServer()// /** * Sets the currently selected object identifier on the server. * @param objectId The object identifier for the selection known by the server, or -1 for no selection. */ protected void setSelectedObjectIdOnServer(int objectId) { this.selectedObjectIdOnServer = objectId; }//setSelectedObjectIdOnServer()// /** * Gets the current custom selection text on the server. * @return The selection text, or null if there isn't a custom selection. */ protected String getCustomSelectionOnServer() { return customSelectionOnServer; }//getCustomSelectionOnServer()// /** * Sets the current custom selection text on the server. * @param customSelection The selection text, or null if there isn't a custom selection. */ protected void setCustomSelectionOnServer(String customSelection) { this.customSelectionOnServer = customSelection; }//setCustomSelectionOnServer()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.CollectionComponent#updateSelectionLinks() */ protected void updateSelectionLinks() { super.updateSelectionLinks(); if(getHiddenDataCount() > 0) { if(getAllowMultiSelection()) { int[] selectedIndices = controlGetSelections(); RowObject[] selectedObjects = selectedIndices != null && selectedIndices.length > 0 ? new RowObject[selectedIndices.length] : null; if(selectedObjects != null) { //Collect the selected values.// for(int selectionIndex = 0; selectionIndex < selectedIndices.length; selectionIndex++) { selectedObjects[selectionIndex] = getRowObjectAtIndex(selectedIndices[selectionIndex]); }//for// }//if// //Update the hidden data linkages.// for(int hiddenDataIndex = 0; hiddenDataIndex < getHiddenDataCount(); hiddenDataIndex++) { getHiddenData(hiddenDataIndex).invokeLinkage(selectedObjects); }//for// }//if// else { int selectedIndex = controlGetSelection(); RowObject selectedObject = selectedIndex != -1 ? getRowObjectAtIndex(selectedIndex) : null; //Update the hidden data linkages.// for(int hiddenDataIndex = 0; hiddenDataIndex < getHiddenDataCount(); hiddenDataIndex++) { getHiddenData(hiddenDataIndex).invokeLinkage(selectedObject); }//for// }//else// }//if// }//updateSelectionLinks()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.CollectionComponent#createRowObject(int) */ protected RowObject createRowObject(int objectId) { return new IndexedRowObject(objectId, getHiddenDataCount()); }//createRowObject()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalProcessMessage(com.foundation.tcv.model.ViewMessage) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object retVal = null; switch(viewMessage.getMessageNumber()) { // case MESSAGE_SET_ITEMS: { //A string array and an int array. The int array is the object ID's.// // Object[] data = (Object[]) viewMessage.getMessageData(); // // if(data == null) { // controlRemoveAll(); // removeRowObjects(); // rowObjectByIndexMap.removeAll(); // forceSelectionEvent(); // }//if// // else { // Object[] items = (Object[]) data[0]; // int[] objectIds = (int[]) data[1]; // Object[] hiddenData = (Object[]) data[2]; // // if(((items == null) && (objectIds != null)) || ((items != null) && (objectIds == null)) || ((items != null) && (items.length != objectIds.length))) { // Debug.log("Error: Invalid parameters."); // }//if// // // //Remove all the mappings.// // removeRowObjects(); // rowObjectByIndexMap.removeAll(); // // if(objectIds != null) { // //Initialize the mappings between indexes and object IDs.// // for(int index = 0; index < objectIds.length; index++) { // IndexedRowObject rowObject = (IndexedRowObject) getRowObject(objectIds[index]); // // //Create a row object or increment its reference count.// // if(rowObject == null) { // rowObject = (IndexedRowObject) createRowObject(objectIds[index]); // rowObject.value = items[index]; // // if(rowObject.hiddenData != null && hiddenData != null && hiddenData[index] instanceof Object[]) { // //Copy the hidden data to the row object.// // System.arraycopy(hiddenData[index], 0, rowObject.hiddenData, 0, rowObject.hiddenData.length); // }//if// // }//if// // else { // rowObject.referenceCount++; // items[index] = rowObject.value; // }//else// // // addRowObject(rowObject); // rowObjectByIndexMap.add(rowObject); // }//for// // }//if// // // //Set the items in the control.// // controlSetItems(items); // }//else// // break; // }//case// case MESSAGE_ADD_ITEM: { //Sends item data and object ID and an optional index.// Object itemData = viewMessage.getMessageData(); Object[] hiddenData = (Object[]) viewMessage.getMessageSecondaryData(); int objectId = viewMessage.getMessageInteger(); IndexedRowObject rowObject = (IndexedRowObject) getRowObject(objectId); //Create a row object or increment its reference count.// if(rowObject == null) { rowObject = (IndexedRowObject) createRowObject(objectId); rowObject.value = itemData; if(hiddenData != null && rowObject.hiddenData != null) { //Copy the hidden data to the row object.// System.arraycopy(hiddenData, 0, rowObject.hiddenData, 0, rowObject.hiddenData.length); }//if//. }//if// else { rowObject.referenceCount++; itemData = rowObject.value; }//else// //If an index was provided then place the item at the requested index.// if(viewMessage.getMessageSecondaryInteger() != -1) { int index = viewMessage.getMessageSecondaryInteger(); //A little error checking just in case.// if(index > rowObjectByIndexMap.getSize()) { index = rowObjectByIndexMap.getSize(); }//if// //Add the new value to the control.// controlAddItem(itemData, index); //Update the mappings.// rowObjectByIndexMap.add(index, rowObject); }//if// else { //Add the new value to the control.// controlAddItem(itemData); //Update the mappings.// rowObjectByIndexMap.add(rowObject); }//else// //Update the mappings.// addRowObject(rowObject); break; }//case// case MESSAGE_REMOVE_ITEM: { //Note: Since removals are most often of selected items we will first try removing selected objects with the same object id.// int objectId = viewMessage.getMessageInteger(); //Handle multi-selection capable controls differently since single selection controls may not implement the controlGetSelections method.// if(getAllowMultiSelection()) { int[] selectedIndices = controlGetSelections(); boolean isRemoved = false; //First pass, remove from only selected items. (Most removals are selected.)// for(int index = selectedIndices.length - 1; ((!isRemoved) && (index >= 0)); index--) { if(getRowObjectAtIndex(selectedIndices[index]).objectId == objectId) { IndexedRowObject rowObject; //TODO: Is this really necessary?// controlRemoveSelection(selectedIndices[index]); //Remove the item from the collection.// controlRemoveItem(selectedIndices[index]); //Remove the item from the mappings.// rowObject = (IndexedRowObject) rowObjectByIndexMap.remove(selectedIndices[index]); //Remove the row object from the object id map if it is no longer referenced.// if(--rowObject.referenceCount == 0) { removeRowObject(rowObject.objectId); }//if// isRemoved = true; }//if// }//for// //Second pass, remove from all items.// for(int index = rowObjectByIndexMap.getSize() - 1; ((!isRemoved) && (index >= 0)); index--) { if(getRowObjectAtIndex(index).objectId == objectId) { IndexedRowObject rowObject; //Remove the item from the collection.// controlRemoveItem(index); //Remove the item from the mappings.// rowObject = (IndexedRowObject) rowObjectByIndexMap.remove(index); //Remove the row object from the object id map if it is no longer referenced.// if(--rowObject.referenceCount == 0) { removeRowObject(rowObject.objectId); }//if// isRemoved = true; }//if// }//for// if(!isRemoved) { //TODO: Unable to remove the value! Should we notify the server?// }//if// }//if// else { int selectedIndex = controlGetSelection(); if((selectedIndex != -1) && (getRowObjectAtIndex(selectedIndex).objectId == objectId)) { IndexedRowObject rowObject; //TODO: Is this really necessary?// controlRemoveSelection(selectedIndex); //Remove the item from the collection.// controlRemoveItem(selectedIndex); //Remove the item from the mappings.// rowObject = (IndexedRowObject) rowObjectByIndexMap.remove(selectedIndex); //Remove the row object from the object id map if it is no longer referenced.// if(--rowObject.referenceCount == 0) { removeRowObject(rowObject.objectId); }//if// }//if// else { //Second pass, remove from all items.// for(int index = rowObjectByIndexMap.getSize() - 1; index >= 0; index--) { if(getRowObjectAtIndex(index).objectId == objectId) { IndexedRowObject rowObject; //Remove the item from the collection.// controlRemoveItem(index); //Remove the item from the mappings.// rowObject = (IndexedRowObject) rowObjectByIndexMap.remove(index); //Remove the row object from the object id map if it is no longer referenced.// if(--rowObject.referenceCount == 0) { removeRowObject(rowObject.objectId); }//if// break; }//if// }//for// }//else// }//else// break; }//case// case MESSAGE_REMOVE_ALL: { controlRemoveAll(); removeRowObjects(); rowObjectByIndexMap.removeAll(); //This should not be necessary since the server should send a selection event immediatly following the remove all.// //forceSelectionEvent(); break; }//case// case MESSAGE_SET_ITEM: { Object[] data = (Object[]) viewMessage.getMessageData(); int objectId = ((Integer) data[0]).intValue(); Object itemData = data[1]; IndexedRowObject rowObject = (IndexedRowObject) getRowObject(objectId); //Set the row object's value.// rowObject.value = itemData; //Find all instances of the object and apply the new display data.// for(int index = 0; index < rowObjectByIndexMap.getSize(); index++) { if(getRowObjectAtIndex(index) == rowObject) { //TODO: Do we need to do anything to preserve the selection? controlSetItem(index, itemData); }//if// }//for// break; }//case// case MESSAGE_SET_SELECTION: { Object messageData = viewMessage.getMessageData(); if(messageData instanceof int[]) { int[] selectionObjectIds = (int[]) messageData; //For non-multi-selection collections, set the currently selected id.// if(!getAllowMultiSelection()) { if((selectionObjectIds != null) && (selectionObjectIds.length > 1)) { selectedObjectIdOnServer = -1; //Error: Invalid message value!// throw new RuntimeException("Error: Invalid message parameter! MESSAGE_SET_SELECTION"); }//if// else { selectedObjectIdOnServer = ((selectionObjectIds == null) || (selectionObjectIds.length == 0)) ? -1 : selectionObjectIds[0]; }//else// if(selectedObjectIdOnServer != -1) { boolean found = false; int controlSelection = controlGetSelection(); if((controlSelection == -1) || (getRowObjectAtIndex(controlSelection).objectId != selectedObjectIdOnServer)) { //Locate an entry matching the selection.// for(int index = rowObjectByIndexMap.getSize() - 1; (!found) && (index >= 0); index--) { if(getRowObjectAtIndex(index).objectId == selectedObjectIdOnServer) { controlSetSelection(index); found = true; }//if// }//for// if(!found) { //Error: Could not find an object matching the server's description!// //TODO: Should we tell the server to deselect the value? }//if// }//if// }//if// else { controlSetSelection(-1); }//else// }//if// else { //Allow multiple selections.// if((selectionObjectIds == null) || (selectionObjectIds.length == 0)) { //Clear all selections.// controlSetSelection(-1); }//if// else { //TODO: Creating an array of flags the size of the entire collection seems inefficient. Perhaps we can make this more efficient.// boolean[] selectionFlags = new boolean[rowObjectByIndexMap.getSize()]; //Initialize the bit field.// for(int index = 0; index < selectionFlags.length; index++) { selectionFlags[index] = false; }//for// //Apply all selections.// for(int selectionIndex = 0; selectionIndex < selectionObjectIds.length; selectionIndex++) { boolean hasSelected = false; //Since an object can occur multiple times in the control we will the one already selected.// for(int index = 0; (!hasSelected) && (index < selectionFlags.length); index++) { if((!selectionFlags[index]) && (getRowObjectAtIndex(index).objectId == selectionObjectIds[selectionIndex]) && (controlIsSelected(index))) { hasSelected = true; selectionObjectIds[selectionIndex] = index; }//if// }//for// //TODO: Looping twice sucks... we could combine the loops with a little extra code.// //If an object was not already selected then pick the first one in the control.// for(int index = 0; (!hasSelected) && (index < selectionFlags.length); index++) { if((!selectionFlags[index]) && (getRowObjectAtIndex(index).objectId == selectionObjectIds[selectionIndex]) && (!controlIsSelected(index))) { hasSelected = true; selectionObjectIds[selectionIndex] = index; }//if// }//for// //We were not able to apply the selection since there were not enough instances of the object in the control.// if(!hasSelected) { int[] temp = new int[selectionObjectIds.length]; //Fix the array of selections since it now has one too many positions.// System.arraycopy(selectionObjectIds, 0, temp, 0, selectionIndex - 1); System.arraycopy(selectionObjectIds, selectionIndex - 1, temp, selectionIndex - 1, selectionObjectIds.length - selectionIndex); selectionObjectIds = temp; //TODO: Should we notify the server that the selection is not possible? We could simply remove the selection.// }//if// }//for// controlSetSelections(selectionObjectIds); }//else// }//else// //Update the linkages that connect to the selection.// updateSelectionLinks(); }//if// else { String selectionText = (String) messageData; controlSetSelection(selectionText); selectedObjectIdOnServer = -1; customSelectionOnServer = selectionText; }//else// break; }//case// case MESSAGE_ADD_SELECTION: { int[] selectionObjectIds = (int[]) viewMessage.getMessageData(); //For each additional selection, find the first non-selected item with the given object id and select it.// for(int selectionIndex = 0; selectionIndex < selectionObjectIds.length; selectionIndex++) { boolean hasSelected = false; for(int index = 0; (!hasSelected) && (index < rowObjectByIndexMap.getSize()); index++) { if((getRowObjectAtIndex(index).objectId == selectionObjectIds[selectionIndex]) && (!controlIsSelected(index))) { hasSelected = true; controlAddSelection(index); }//if// }//for// if(!hasSelected) { //TODO: Should we notify the server that the selection is not possible? We could simply remove the selection.// }//if// }//for// break; }//case// case MESSAGE_REMOVE_SELECTION: { int[] selectionObjectIds = (int[]) viewMessage.getMessageData(); //For each additional selection, find the first non-selected item with the given object id and select it.// for(int selectionIndex = 0; selectionIndex < selectionObjectIds.length; selectionIndex++) { boolean hasDeselected = false; for(int index = rowObjectByIndexMap.getSize(); (!hasDeselected) && (index >= 0); index--) { if((getRowObjectAtIndex(index).objectId == selectionObjectIds[selectionIndex]) && (controlIsSelected(index))) { hasDeselected = true; controlRemoveSelection(index); }//if// }//for// if(!hasDeselected) { //I don't think any action is necessary, though this should not occur.// }//if// }//for// break; }//case// case MESSAGE_REMOVE_SELECTIONS: { selectedObjectIdOnServer = -1; controlRemoveAllSelections(); break; }//case// default: { retVal = super.internalProcessMessage(viewMessage); }//default// }//switch// return retVal; }//internalProcessMessage()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.CollectionComponent#updateHiddenData(com.foundation.tcv.swt.client.CollectionComponent.RowObject, int, java.lang.Object) */ protected void updateHiddenData(RowObject object, int hiddenDataIndex, Object value) { object.hiddenData[hiddenDataIndex] = value; if(getAllowMultiSelection()) { boolean updateSelectionLinks = false; int[] selectedIndices = controlGetSelections(); RowObject[] selectedObjects = new RowObject[selectedIndices.length]; if((selectedIndices != null) && (selectedIndices.length > 0)) { //Determine whether any of the selections match with the hidden data's object to see whether we need to update any hidden data linkages.// for(int selectionIndex = 0; (!updateSelectionLinks) && (selectionIndex < selectedIndices.length); selectionIndex++) { RowObject selection = getRowObjectAtIndex(selectedIndices[selectionIndex]); updateSelectionLinks = selection == object; selectedObjects[selectionIndex] = selection; }//for// }//if// if(updateSelectionLinks) { getHiddenData(hiddenDataIndex).invokeLinkage(selectedObjects); }//if// }//if// else { int selectedIndex = controlGetSelection(); if(selectedIndex != -1) { RowObject selectedObject = getRowObjectAtIndex(selectedIndex); if(selectedObject == object) { getHiddenData(hiddenDataIndex).invokeLinkage(selectedObject); }//if// }//if// }//else// }//updateHiddenData()// /** * Removes all displayed items from the control. */ protected abstract void controlRemoveAll(); //removeAll// /** * Sets the control's displayed collection values ordered by index. * @param items The indexed values displayed by the control. */ protected abstract void controlSetItems(Object[] items); //setItems// /** * Sets an item in the control's collection display. * @param index The index at which the item should be set. * @param itemData The value displayed by the control. */ protected abstract void controlSetItem(int index, Object itemData); //setItem// /** * Adds an item to the control's collection at a given index. * @param itemData The value displayed by the control. * @param index The index at which the item should be added. */ protected abstract void controlAddItem(Object itemData, int index); //add// /** * Adds an item to the control's collection at the end. * @param itemData The value displayed by the control. */ protected abstract void controlAddItem(Object itemData); //add// /** * Removes an item from the control's collection display. * @param index The index of the item to be removed. */ protected abstract void controlRemoveItem(int index); //remove// /** * Gets the indices selected within the control. * @return The collection of indices selected. */ protected abstract int[] controlGetSelections(); //getSelectionIndices// /** * Gets the index selected within the control. * @return The index currently selected. */ protected abstract int controlGetSelection(); //getSelectionIndex// /** * Sets the selected values in the control. * @param indices The indices of the selected values. */ protected abstract void controlSetSelections(int[] indices); //setSelection// /** * Sets the selected value in the control. *Note: Not all controls support custom selected text. Currently only combo box does.
* @param text The text of the selected value. */ protected abstract void controlSetSelection(String text); //setSelection// /** * Sets the selected value in the control. * @param index The index of the selected value. */ protected abstract void controlSetSelection(int index); //setSelection// /** * Adds a selected value in the control. * @param index The index of the selected value. */ protected abstract void controlAddSelection(int index); //select// /** * Deselects a value in the control. * @param index The index of the selected value. */ protected abstract void controlRemoveSelection(int index); //deselect// /** * Deselects all values in the control. */ protected abstract void controlRemoveAllSelections(); //deselectAll// /** * Checks to see if a value is selected. * @param index The index of the checked value. * @return Whether the value is currently selected. */ protected abstract boolean controlIsSelected(int index); //isSelected// /** * Gets the current text for the control. *Some controls such as comboboxes have a text which may not be one of the listed items.
* @return The control's current text. */ protected abstract String controlGetText(); //getText// /** * Sets the current text for the control. *Some controls such as comboboxes have a text which may not be one of the listed items.
* @param text The control's new text. */ protected abstract void controlSetText(String text); //setText// /** * Forces the selection event handler to be called. */ protected abstract void forceSelectionEvent(); //Should call widgetSelected implemented by the subclass. This name may need to change.// }//IndexedCollectionComponent//