BIT-101 [2003-2017]

Design Tactics – Select Single


The other day I was coding a particular UI implementation and realized that I had coded the same thing in multiple languages multiple times. I knew exactly how I was going to go about it and did what I usually do and, as usual, it worked just right. I started wondering how many examples like that exist, and that it would be good to occasionally document them, if not for my own sake, then for the sake of others.

These things aren’t necessarily so broad in scope that I’d call them design patterns. I might call them a design strategy, but that still has the connotation of being broad in scope, and could be confused with the strategy pattern. So I thought of naming them design tactics. Kind of like hand-to-hand combat with your code.

The first one, and the one that sparked my interest in the subject, I call the Select Single Tactic. I’m sure you’ve done this plenty of times yourself. It’s basically the functionality of a radio button, a list, a menu or other navigation. You have a number of items, of which only one can be selected. When the user selects one, it usually changes its state to show that it is selected, and the other associated items will change their state as needed to show that they are unselected.

The most common scenario is that the user will click on an item to select it, so we’ll go with that idea. A common first start is to code the item so that it responds to the click directly, changing its state to selected. See the following snippet, kept in pseudocode as it can apply to just about any language:

[php]
// constructor
Item() {
this.addEventListener(click, this.onClick);
}

void onClick() {
this.setSelected(true);
}
[/php]

Here, the item responds to its own click by changing its visual state to show that it has been selected. In some cases, this is fine, but there’s probably a more elegant way. However, we’ll leave it like this for now.

The next part is to deselect all the other items. A first pass at this might be to store all the items in an array. and when any item is clicked, set all the items in the array to show unselected. This would be done in some code external to the items themselves.

[php]
void onItemClicked(clickedItem) {
for(item in itemList) {
item.setSelected(false);
}
}
[/php]

The problem with this is that often the code internal to the item will run first, setting the clicked item to selected. Then this external code will run, setting ALL the items to unselected, including the one that was just set as selected. So we have to check that we are not unselecting the item that was clicked:

[php]
void onItemClicked(clickedItem) {
for(item in itemList) {
if(item != clickedItem) {
item.setSelected(false);
}
}
}
[/php]

Now, all the items are set to unselected EXCEPT the one that was just clicked. So this works, but it’s just starting to get a bit ugly. Another issue is that we now have some code INSIDE the items setting the clicked one to selected, and some other code OUTSIDE the items setting the others to unselected. It’d be a lot cleaner if all the selection/unselection code was in one place.

So another option is to remove the onClick and selection code from the items themsleves, and A. unselect all, B. select the clicked item.

[php]
void onItemClicked(clickedItem) {
for(item in itemList) {
item.setSelected(false);
}
clickedItem.setSelected(true);
}
[/php]

This is better. Now items just dispatch clicks and have their state set externally. But we can do better.

First of all, why the hell are we looping through this array of items at all? The use case specifies that only one item will be selected at any given time. So why go through 2 or 5 or 100 items, setting them all to unselected, when at most we only need to do it for one?

Second, for most implementations, we will need to keep track of which item is selected, so that we can show some specific data, go to a particular section, or whatever. So when an item is clicked, we’ll probably want to store it as the selected item. This kills two birds with one stone. The only item we need to unselect is the one that is currently selected. Actually, when the system first initializes, it could be the case that no items are selected, so we’ll check for that case too.

[php]
void onItemClicked(clickedItem) {
if(selectedItem) selectedItem.setSelected(false);
selectedItem = clickedItem;
selectedItem.setSelected(true);
}
[/php]

That’s about as elegant as it gets. At this point, unless you are using it for some other purpose, we don’t even need the array of items any more. As long as we know which item is selected, we just need to unselect it.

For something where you might have multiple groups of objects, like radio button groups, you can extend this fairly easily. Each item would have something like a group name, and you would have a map of different groups, each keeping track of the currently selected item in that group:

[php]
void onItemClicked(clickedItem) {
var group = groups[clickedItem.groupName];
if(group.selectedItem) group.selectedItem.setSelected(false);
group.selectedItem = clickedItem;
group.selectedItem.setSelected(true);
}
[/php]

Moving back out of groups for the last example, we’ll go back to just a single selection. If we really want to compartmentalize this, we can move it all back into the Item class itself, storing the selected item as a static member of the class, removing the need for any external code to run at all. We’ll go back to having items listen for their own clicks.

[php]
// constructor
Item() {
this.addEventListener(click, this.onClick);
}

void onClick() {
if(Item.selectedItem) Item.selectedItem.setSelected(false);
Item.selectedItem = this;
Item.selectedItem.setSelected(true);
}
[/php]

Now, rather than the external view having to worry about this logic, the item class itself takes care of it all. You’d still want to listen for clicks on the items externally most likely though, to update other aspects of the UI such as showing the data related to an item, changing sections, etc. Another twist on this would be to move the selection code into a static method like so:

[php]
// constructor
Item() {
this.addEventListener(click, this.onClick);
}

void onClick() {
Item.selectItem(this);
}

static selectItem(item) {
if(Item.selectedItem) Item.selectedItem.setSelected(false);
Item.selectedItem = item;
Item.selectedItem.setSelected(true);
}
[/php]

This feels a little cleaner to me, as the class method is doing the classy stuff and the instance method is just telling the class what to do.

Summary

No rocket science here. But nice to think these things out logically. I think even in just describing this stuff, I’ve streamlined it a bit in my own mind. If you have any improvements, or other tactics you use or would like to see discussed, let me know.

« Previous Post
Next Post »