Suppose you have two enumerated attributes, one called “Category” and the other called “Sub Category”. You want the enumerations available for “Sub Category” to be dependant on the value of “Category”. This cannot be done in doors using enumerated attributes.
However, with some simple DXL, you can create the illusion of this functionality. Instead of using enumerations, use ordinary string attributes and use a dialog to edit the values.
This utility implements a dialog box that allows the user to select values for “Category” and “Sub Category”. Whenever the “Category” value is changed, the available values for “Sub Category” are automatically updated.
The “pseudo enumerations” are implemented as string arrays. Each row contains a “Category” value in the first position and available “Sub Category” values in subsequent positions.
The following implements the dialog and contains the definitions of the category and sub category values.
/* Using pseudo enumerations to implement category and sub-category attribute values. Dialog displays two choice elements. The first contains choices for the main category. The second contains choices for the sub-category. The choices available for the sub-category are dependant on the selected main category. This script assumes the existance of two ordinary string attributes, the names of which are defined below. Enumeration values are defined in a string array. Tony Goodman. */ /*************************************** Attribute Names These must exist - no check is made. ***************************************/ const string ATTR_MAIN_CATEGORY = "Category" const string ATTR_SUB_CATEGORY = "Sub Category" /*************************************** Indices into string array of enumeration values By defining these here we can change the number of categories without changing the rest of the code. ***************************************/ const int NUM_MAIN_CATEGORIES = 3 const int NUM_SUB_CATEGORIES = 10 const int NUM_ENTRIES = NUM_MAIN_CATEGORIES + (NUM_MAIN_CATEGORIES * NUM_SUB_CATEGORIES) const int MAIN_CATEGORY_OFFSET = NUM_SUB_CATEGORIES + 1 /*************************************** "Enumeration" Values First entry on the left is the main category. This is followed by the sub category values. It is important that there are exactly the same number entries in this array for each category. Empty strings are ignored - this allows us to have a varying number of sub categories for each category. ***************************************/ const string CATEGORIES[NUM_ENTRIES] = { "Cheeses", "Brie", "Cheddar", "Stilton", "Camembert", "Edam", "", "", "", "", "", "Meats", "Chicken", "Lamb", "Beef", "Pork", "", "", "", "", "", "", "Fruits", "Orange", "Banana", "Apple", "Mango", "", "", "", "", "", "" } /*************************************** Dialog elements ***************************************/ DB dbMain = null DBE dbeLabel = null DBE dbeMainCategory = null DBE dbeSubCategory = null /*************************************** Global variables ***************************************/ Module currModule = null Object currObject = null /*************************************** isMainCategory returns true if the index to the string array is a main category. e.g. if the number of sub-categories is 10, then main categories are at indices 0, 11, 22, 33 ... ***************************************/ bool isMainCategory(int i) { return(i % MAIN_CATEGORY_OFFSET == 0) } /*************************************** getMainChoiceIndex given the choice selected in the dialog, returns the index to the string array. ***************************************/ int getMainChoiceIndex(string mainCategory) { int i = 0 for (i = 0; i < NUM_MAIN_CATEGORIES; i++) { if (CATEGORIES[i * MAIN_CATEGORY_OFFSET] == mainCategory) { return(i) } } return(-1) } /*************************************** getSubChoiceIndex given the choices selected in the dialog, returns the index to the string array for the selected sub category. ***************************************/ int getSubChoiceIndex(int mainSelection, string subCategory) { int i = 0 int startIndex = 0 int endIndex = 0 // convert into index to string array startIndex = mainSelection + 1 endIndex = startIndex + NUM_SUB_CATEGORIES // find matching sub category for (i = startIndex; i < endIndex; i++) { if (CATEGORIES[i] == subCategory) { return(i - startIndex) } } return(-1) } /*************************************** printCategories ***************************************/ void printCategories() { int i = 0 for (i = 0; i < NUM_ENTRIES; i++) { if (isMainCategory(i)) { print("\nCategory: " CATEGORIES[i] "\n") } else { print("\t" CATEGORIES[i] "\t") } } } /*************************************** initSubCategoryChoices initialise the choices displayed in the sub category DBE. If the current object has a value for this attribute then this value is selected, but only if it is a legal choice based on the current main category. ***************************************/ void initSubCategoryChoices() { int i = 0 int mainSelection = 0 string subCategory = "" empty(dbeSubCategory) mainSelection = get(dbeMainCategory) // if there is no selection in the main category DBE // then leave this DBE empty. if (mainSelection < 0) { return } // convert into index to string array mainSelection = mainSelection * MAIN_CATEGORY_OFFSET // extract string values from array for (i = mainSelection + 1; i < mainSelection + NUM_SUB_CATEGORIES; i++) { if (CATEGORIES[i] == "") { break } insert(dbeSubCategory, noElems(dbeSubCategory), CATEGORIES[i]) } // check current object for current atribute value subCategory = currObject.ATTR_SUB_CATEGORY "" set(dbeSubCategory, getSubChoiceIndex(mainSelection, subCategory)) } /*************************************** initMainCategoryChoices initialise the choices displayed in the main category DBE. If the current object has a value for this attribute then this value is selected. The sub category DBE is also updated. ***************************************/ void initMainCategoryChoices() { int i = 0 string mainCategory = "" // populate main category DBE for (i = 0; i < NUM_ENTRIES; i += MAIN_CATEGORY_OFFSET) { insert(dbeMainCategory, noElems(dbeMainCategory), CATEGORIES[i]) } // check current object for current atribute value mainCategory = currObject.ATTR_MAIN_CATEGORY "" // convert mainCategory to choice index and set selection in DBE set(dbeMainCategory, getMainChoiceIndex(mainCategory)) // initialise sub category choices based on setting of main category initSubCategoryChoices() } /*************************************** doEditCategory Callback when user clicks OK button. ***************************************/ void doEditCategory(DB db) { int mainSelection = 0 int subSelection = 0 // get user selections from dialog elements mainSelection = get(dbeMainCategory) subSelection = get(dbeSubCategory) // enforce selection of main category if (mainSelection < 0) { infoBox("Please select main category") return } // enforce selection of sub category if (subSelection < 0) { infoBox("Please select sub category") return } // adjust selections for for real index into string array mainSelection = mainSelection * MAIN_CATEGORY_OFFSET subSelection = mainSelection + subSelection + 1 // set object attributes currObject.ATTR_MAIN_CATEGORY = CATEGORIES[mainSelection] currObject.ATTR_SUB_CATEGORY = CATEGORIES[subSelection] // refresh display to show changes refresh(currModule) // we are finished hide(dbMain) destroy(dbMain) dbMain = null } /*************************************** doSelectMainCategory Callback when user makes a selection in the main category DBE. Call function to update the choices available for sub category. ***************************************/ void doSelectMainCategory(DBE dbe) { initSubCategoryChoices() } /*************************************** MAIN ***************************************/ currModule = current Module if (null currModule) { infoBox("This utility can only be run from a module") halt } currObject = current Object if (null currObject) { infoBox("No current object.") halt } dbMain = create(currModule, "Edit Category") dbeLabel = label(dbMain, "Description of edit category utility.\n") dbeMainCategory = choice(dbMain, "Main Category: ", dummy, 0) dbeSubCategory = choice(dbMain, "Sub Category: ", dummy, 0) apply(dbMain, "OK", doEditCategory) set(dbeMainCategory, doSelectMainCategory) realize(dbMain) initMainCategoryChoices() show(dbMain)
This code allows you to add the script to the popup menu of a formal module.
// Edit Category Menu Option /* Creates a menu option to invoke the editCategory.dxl script. Install this file in DOORSHOME/lib/dxl/config/formalPopupFiles for this menu option to appear on the formal module popup menu. Tony Goodman. */ createItem(alwaysOn, "Edit Category ...", null, null, modKeyCtrl, null, null, "Edit Category.", "", doorsHome "lib/dxl/addins/editCategory.dxl")