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.

This entry was posted in General, Technology. Bookmark the permalink.

14 Responses to Design Tactics – Select Single

  1. Pavel fljot says:

    How about a bit more solid solution? https://gist.github.com/8a6fb474eb83d5c06327
    *Don’t remember if I completely finished that… any bugs maybe? Improvements?

  2. Tronster says:

    If you use the static implementation though, you are limited to one group of items. If two groups exist, selecting an item from one group would remove the selection from the other. Is there a more eloquent way to get around this other than pass a group id around and keep an array of selected items?

    static selectItem(item) {
    int groupId = item.groupId;
    if(Item.selectedItems[groupId]) Item.selectedItems[groupId].setSelected(false);
    Item.selectedItems[groupId] = item;
    Item.selectedItems[groupId].setSelected(true);
    }

  3. keith says:

    Pavel, sure. I was looking at the general tactics that you would in many cases to have just a single item selected. Your example is a highly engineered, highly reusable class specific to radio buttons, but underneath it all uses the same basic tactic.

    Tronster, yes, as I said in the static example, I was going back to a single selection, but the same concept could be applied for groups in the static case. Or you could build up a larger group abstraction like Pavel did in the earlier comment.

  4. Matita says:

    For performance reasons you can say, at the very start of the selectItem method:

    if (item == Item.selectedItem) return;

    And if you put another control on the last line of the same method:

    if (Item.selectedItem) Item.selectedItem.setSelected(true);

    you can also manage the case selectItem(null)

  5. I often loop over the items unless it is perf sensitive (deselect does more than deselect, or there are many, or it happens often) because I don’t trust Flash to do the right thing. There have just been so many little gotchas over the years it is easier to just do the insurance thing and not think about it further.

    Thinking things like wonky stuff when the mouse enters the swf pressed, double firing getters, objects dynamically created appearing at the mouse, hit areas changing, reflow, visible states, loaded code, FlexUI madness, death from CSS etc etc. I know a lot of this has stabilized over the years, but my trust hasn’t returned.

    I think the key either way is to be very sure you don’t get chain reactions with this stuff. Usually that means a dirty flag and an update per clock/frame tick for me.

  6. tonyWen says:

    great ! peter,i am one of your reader in China,in my own daily work, i have the same feeling in those things ,The name : design tactics ,it is wonderful,because it is not really a design pattern, and your summary is also good,thank you very much!

  7. E. says:

    I was thinking about that this too this week too.

    In flash, but in a quite different context: Low-end machine, where I have a very restricted cpu and memory budget where I really have to think twice before coding anything. Even the code count in memory budget! It’s really compromise over compromise.

    The select single is actually within a list (that can be hundred of items)

    So the solution I’m heading for is quite context specific.

    I have a somewhat classic virtual list that display N items at at a time.

    When a item get displayed, there is a lookup in the data provider to check the attributes including the checked mode. I can only know then what’s the checked item value (if it’s visible in first page of data by exemple at list inialisation, I could find it then but it would not be defined otherwise ).

    If the user select something else, I don’t really need to reset the other flags in the data provider, just keep a variable to track the id of single-checked value/item (using the item id).

    It adds a bit of processing when a new item get displayed as I will have to do a lookup to see if the item “checked” is the one that was redefined (or not). I could do the check when processing the original data really the idea here is not to save all cpu calculation but really spread it over time so that it’s less perceivable by the user. So in the end, the only special case I have to handle is the first page, if there is a item checked I need to be able to uncheck it more directly as there will not be natural item refresh (when virtual item get in physical view). I have not picked the final solution yet but the best would probably be to check at first display of list.. but even if I do the check at every user click it will be at worst the number of visible items…

    It work well in my scenario as there are no action to be run that depend on selected state of a item that is not visible.

    Anyway, it’s probably doesn’t mean much out of context…

    Otherwise I also often also use the suggested general approach:
    cleanCurrent
    setCurrent

    When you think about it, a radioBox is not really different than keeping track of the selected item in a list.

  8. Bjorn says:

    Hi Keith,

    I would say the only thing this is missing is some separation between the view and the logic.
    I’m not really a fan of writing unit tests for views.
    Perhaps the selection logic should be in another class that is passed in as an arg in the view’s constructor.

  9. Adam says:

    Hi Keith,
    you called me a troll when I said it was the end of flash more then a year ago.
    I was a visionary and I was right and you were so wrong and I would say even rude in insulting me and calling me a troll. You should acknowledge you were in denial and at least apologize for your clueleness. rip flash. Good luck in developing with HTML5 and JAVASCRIPT!!!!!

    http://www.cnn.com/2011/11/09/tech/mobile/adobe-mobile-flash-wired/index.html?hpt=hp_t3

  10. Romu says:

    ahahah! so funny 😀

  11. Romu says:

    Btw, Keith has always said that he was not tied to a language, which is good. I don’t know how you can get so angry!

Leave a Reply