Presenting the Account hierarchy in an apex:selectList

PS As well as usability issues when the number of Accounts gets into the hundreds, the algorithm used here is unfortunately exponentially expensive and so at around 500 Accounts the 200,000 script statement governor limit is likely to be hit. So I have replaced this algorithm with a linearly expensive one (not shown here) that can handle many thousands of Accounts.

Accounts can have a parent Account reference set so that a hierarchy is formed and this hierarchy can be displayed using the “View Hierarchy” link. With a bit of code the hierarchy (and similar hierarchies) can be represented in a Visualforce select list that it is visible to the user at the time they make a selection:

This sort of presentation is obviously not appropriate if there are a large number of Accounts.

Here is an implementation. Most of the work is done by an inner class called Node that represents the hierarchy. The Accounts are queried in alphabetic order, then (iteratively) added according to their ParentId into the Node hierarchy, then converted into a list of items for presentation in the select list:

public with sharing class MyController {
    public List<SelectOption> accountOptions {
        get {
            Node root = new Node();
            List<Account> nextPass = [select Id, ParentId, Name from Account order by Name];
            while (!nextPass.isEmpty()) {
                List<Account> currentPass = nextPass;
                nextPass = new List<Account>();
                for (Account account : currentPass) {
                    if (!root.add(account)) {
                        nextPass.add(account);
                    }
                }
            }
            List<SelectOption> options = new List<SelectOption>();
            options.add(new SelectOption('', '--None--'));
            root.buildOptions(options);
            return options;
        }
    }
    private class Node {
        private Account account;
        private List<Node> children = new List<Node>();
        private Integer depth = -1;
        public Node() {
        }
        private Node(Account account, Integer depth) {
            this.account = account;
            this.depth = depth;
        }
        public Boolean add(Account candidate) {
            return add(candidate, 0);
        }
        private Boolean add(Account candidate, Integer depth) {
            if ((isRoot() && candidate.ParentId == null) || (!isRoot() && candidate.ParentId == account.Id)) {
                children.add(new Node(candidate, depth));
                return true;
            } else {
                for (Node child : children) {
                    if (child.add(candidate, depth + 1)) {
                        return true;
                    }
                }
            }
            return false;
        }
        private Boolean isRoot() {
            return account == null;
        }
        public void buildOptions(List<SelectOption> options) {
            if (account != null) {
                options.add(createSelectOption());
            }
            for (Node child : children) {
                child.buildOptions(options);
            }
        }
        private SelectOption createSelectOption() {
            String label = '';
            for (Integer i = 0; i < depth; i++) {
                label += '    ';
            }
            label += account.Name;
            SelectOption option = new SelectOption(account.Id, label);
            option.setEscapeItem(false);
            return option;
        }
    }
}

To present the list:

<apex:selectList value="{!accountId}" size="1">
    <apex:selectOptions value="{!accountOptions}"/>
</apex:selectList>

And here is a basic test for the code:

@isTest
private class MyControllerTest {
    @isTest
    static void testAccountTree() {
        Long t = System.currentTimeMillis();
        Account child = new Account(Name = 'Child-' + t);
        insert child;
        Account parent = new Account(Name = 'Parent-' + t);
        insert parent;
        Account grandChild = new Account(Name = 'GrandChild-' + t);
        insert grandChild;
        child.ParentId = parent.Id;
        grandChild.ParentId = child.Id;
        update new Account[] {child, grandChild};
        
        MyController controller = new MyController();
        List<SelectOption> options = controller.accountOptions;
        
        Integer parentPosition = null;
        Integer childPosition = null;
        Integer grandChildPosition = null;
        for (Integer i = 0; i < options.size(); i++) {
            SelectOption option = options[i];
            if (isMatch(option, parent)) {
                parentPosition = i;
            }
            if (isMatch(option, child)) {
                childPosition = i;
            }
            if (isMatch(option, grandChild)) {
                grandChildPosition = i;
            }
        }
        // All there
        System.assertNotEquals(null, parentPosition);
        System.assertNotEquals(null, childPosition);
        System.assertNotEquals(null, grandChildPosition);
        // In right order
        System.assert(childPosition > parentPosition);
        System.assert(grandChildPosition > childPosition);
    }
}
Advertisements

3 thoughts on “Presenting the Account hierarchy in an apex:selectList

  1. Do you think it would be easy enough to do something similar with a JSON WebService call to return a similar set of information to be used for building a dynamic list of Campaign IDs for Web2Lead form?

  2. Hey Force201,
    interesting approach that you chose here, thanks for that!
    We built a tree list, too, and used first “String placeholder= ‘ ‘; ” as you did here. Later on we switched to “&nbsp” instead of normal spaces, since the structure got lost after Visualforce rerender of the tree.
    Our tree was based on a table, maybe it is different for a picklist. I just wanted to mention that, in case you haven’t tried 🙂

    Cheers,
    Nisse

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s