OpenerView
can be nested to create a hierarchical structure such as this:
One great aspect is that it remembers which lists are open and which are closed even after the parent has been closed and then opened again.
The two primary problems with OpenerView
are:
- Cannot be customized stylistically
- To build the nested hierarchy all content has to be evaluated upfront, it cannot do lazy evaluation. i.e. it gets all regions at once, instead of getting lists of regions as they are requested.
I built a version of OpenerView
to address this:
The animation shows that it does not remember which lists have been opened and which have not. If I close the parent list and then open it again, all children lists will be closed. However, with it I fully control how it looks (in this case I added ornamental typography to the end of each list), and it does lazy evaluation, i.e. does not request all region names at once. For example, if I open Asia, only then does it get the list of countries in Asia.
Question: How can I get the advantages of custom styling and lazy evaluation, while also having the property that it remembers which lists are open and which are not?
(In reality I am working with much deeper recursive structures than this, that's why I cannot load all lists at once.)
Sample data and OpenerView version
continents = {
EntityClass["Country", "Asia"],
EntityClass["Country", "Africa"],
EntityClass["Country", "Europe"],
EntityClass["Country", "SouthAmerica"],
EntityClass["Country", "NorthAmerica"],
EntityClass["Country", "Oceania"]
};
children[reg_EntityClass] := EntityList[reg]
children[reg_Entity] := reg["RegionNames"]
children[reg_String] := {}
heading[reg : (_EntityClass | _Entity)] := CommonName[reg]
heading[reg_String] := reg
display[data_] := OpenerView[{
heading[data],
Column[display /@ children[data]]
}]
display[data_String] := data
OpenerView[{"Continents", Column[display /@ continents]}]
CustomOpenerView version
SetAttributes[CustomOpenerView, HoldAll]
CustomOpenerView[{heading_, content_, prolog_, epilog_}] :=
DynamicModule[{x = False},
Dynamic@CustomPaneSelector[x,
Grid[{
{Opener[Dynamic[x]], Item[prolog, Alignment -> Left]},
{"", Item[content, Alignment -> Left]},
{"", Item[epilog, Alignment -> Left]}
}],
Grid[{
{Opener[Dynamic[x]], Item[heading, Alignment -> Left]}
}]
]
]
SetAttributes[CustomPaneSelector, HoldRest]
CustomPaneSelector[x_, true_, false_] := If[x, true, false]
display[data_] := CustomOpenerView[{
heading[data],
Column[display /@ children[data]],
heading[data],
"~*~*~*~*~*~*~*~"
}]
display[data_String] := data
CustomOpenerView[{
"Continents",
Column[display /@ continents],
"Continents",
"~*~*~*~*~*~*~*~"
}]