PDA

View Full Version : Merging multiple nodes into single windowlist



Vroomfogle
September 16th, 2008, 14:13
I have a few different data nodes in a module that are split for organizational purposes. e.g. Undead, Demons, Animals, etc. I then have links in my library module like so:



<creatures>
<librarylink type="windowreference">
<class>creaturelist</class>
<recordname>reference.creatures.animals</recordname>
</librarylink>
<name type="string">Animals</name>
</creatures>


But I would also like to include a master listing..i.e. a windowlist containing Undead, Demons & Animals. Multiple recordname tags aren't allowed, so how can I merge these nodes together to provide the source for a windowlist? I thought there might be a way from within the library module itself to create a new node that merges together multiple nodes by simply using some references, but couldn't find anything off hand to do so.

Any suggestions?

Thanks in advance !

Goblin-King
September 16th, 2008, 15:13
I'm assuming you're using a custom ruleset so you can affect the window types available. Here are a couple of ideas to point you in the right direction:

Use multiple indexes

The d20 spells module simply uses multiple indexes for this purpose. Have a look there for a simple but verbose solution. The basic idea is that you'd have a list node, e.g. reference.creatures.all, that simply points to the relevant locations containing the data itself.

Script a windowlist to monitor several child nodes

You can pass the list root (i.e. reference.creatures) to a scripted windowlist, and populate the list using windowlist.createWindow initially, enumerating through the child trees. For fancy operation, have the script use databasenode.onChildAdded and databasenode.onDelete to keep track of the changes to the subtrees (maybe not necessary if it's just for reference modules).

Create multiple lists in one window

Make a window that points to reference.creatures that contains a list of lists, all with the <noscroll /> definition. You wouldn't get all of them in one alphabetized list, but you wouldn't need a master index or scripting. You could also filter them all with a single filter control.

Vroomfogle
September 16th, 2008, 16:19
Thanks GB, the multiple index idea from d20 spells is exactly the type of thing I was originally thinking of...but it's a bit too verbose for my liking. It would really make the conversion of the data into XML a lot harder.

The scripting solution seems like the most flexible and powerful solution and shouldn't be too difficult. As you say I don't really need the functions for adding and deleting since it's just a static reference module.

This will keep me busy for a while, thanks again.

- V

Vroomfogle
September 16th, 2008, 18:20
Hmm,

How do I prevent a specific node from being displayed in the windowlist?

In my above example I have:


<creatures>
<undeads>
<undead001>
<!--creaturedatahere-->
</undead002>
</undeads>
<demons>
<demon001>
<!--creaturedatahere-->
</demon001>
</demons>
<creatures>


So as per my top post I can display each of these individual groups. But now I want to show a master listing so I create a new link pointing to my same windowclass with a recordname of reference.creatures. The list displays 2 (empty) default entries (for the two groups - undead and demons). How can I prevent these from being in my windowlist?

On a related note, I can't seem to retrieve the children node in my onInit function for my windowlist:



function onInit()
local nodes = window.getDatabaseNode();
print("node name = "..nodes.getName());
print("windows = "..#getWindows());
print("nodes = "..(#nodes.getChildren()));
end


Now in the above code I've verified I have the correct node by printing the node name. No matter what I pass in for a datasource I get the correct number reported for number of windows but the number of children is always 0.

For instance if I pass in creatures.undead with one creature in it my windowlist displays the one creature and reports 1 window but 0 children. If I pass in creatures (with the two groups under it: undead and demons), then I get 2 windows (default blank ones) and also 0 children.

These two problems seem related somehow but I'm having trouble figuring out the proper syntax to do what I want.

Goblin-King
September 16th, 2008, 19:59
One option would be to override the entire built-in logic for data source handling in the window list. Rather, use the window's data source. To do this, omit the <datasource> element in the window list, and use:


function onInit()
-- Get the window data source
local mainnode = window.getDatabaseNode();

-- For each group
for groupname, groupnode in pairs(mainnode.getChildren()) do
-- Add each entry
for entryname, entrynode in pairs(groupnode.getChildren()) do
createWindow(entrynode);
end
end
end
That's written on the fly, apologies for any typos.

Foen
September 16th, 2008, 20:17
Another alternative would be to have all the critters in a single list, with an attribute which marks their type. Then use onFilter to show only the type you want. If the filter compares the type code to a string, and allows all items to match if the string is empty, you can achieve the multiple-view method without having to disconnect the window list from the data source.

Just an idea.

Stuart

Vroomfogle
September 16th, 2008, 21:22
Foen, I'm not sure which way would be easier but your method I think may be more intuitive. The other thing is that in reality I really have a more complex grouping structure...you know what I'm talking about here. Animals is a large group, but under that are multiple smaller groups, and under that are the actual creatures. Adding in two attributes for 'group' and 'subgroup' and filtering is probably easier then having to iteratively go through multiple levels of data nodes.

I'm not sure how the filtering would work automatically though. If I have librarylinks that all point to the same parent node (reference.creatures), how do I attach custom filter text to that?

Foen
September 16th, 2008, 21:27
The library links should point to themselves and hence can have a filter text node which is unique. The link should also have a node containing the db source for the list.

I think I used something similar previously, so I'll email you the sample code.

Cheers

Stuart

Bidmaron
September 16th, 2008, 23:12
Any chance you could post it here so those of us following your discussion can learn what you're doing?

Goblin-King
September 17th, 2008, 06:16
Foen, I'm not sure which way would be easier but your method I think may be more intuitive. The other thing is that in reality I really have a more complex grouping structure...you know what I'm talking about here. Animals is a large group, but under that are multiple smaller groups, and under that are the actual creatures. Adding in two attributes for 'group' and 'subgroup' and filtering is probably easier then having to iteratively go through multiple levels of data nodes.

I'm not sure how the filtering would work automatically though. If I have librarylinks that all point to the same parent node (reference.creatures), how do I attach custom filter text to that?
The fact that you can't pass parameters with the library link is the reason I didn't suggest just entering in a list of nodes you want to populate this master list.

You could also expand my script snippet and loop until you're done (thus handling any number of branches), and detect a leaf node (a creature) by checking if a certain field is present (e.g. "name").

Foen
September 17th, 2008, 06:25
I lied: when I went back and looked at my code I found I had used a mixture of Tero's approach and my suggestion.

Context

The ruleset has a large array of spells, which fall into six classes (Contact, Enchantment, etc) and I want to show either a single class of spells, or all spells. The list which displays the spells then has a filter control (magnifying glass) to allow you to search for a sub-string in the spell name.

Approach

The database has a single list of spells. Each spell has a 'type' string node, and the library shortcuts include a 'source' node (which points to the single list) and a 'spelltype' node (which is either empty, to show the full list, or specifies which type of spell to display).

There is a custom reference window class which loads the spell data from its onInit handler by iterating over all of the nodes in the spell list and only adding those which match the spelltype criterion. The onFilter handler is then used to manage the filter control and only display entries which match the user's search string.

Example Data

The link entry in the reference module looks like this:


<id-00002>
<listlink type='windowreference'>
<class>referencespells</class>
<recordname>..</recordname>
</listlink>
<spelltype type='string'>Contact</spelltype>
<source type='string'>reference.spells@Reference</source>
</id-00002>


You can see that the list link points to the current node, not to the node which contains the list of spells. That means the reference window class is instantiated at this point. The window class therefore has access to the spelltype argument, but a source node also tells it where to find the actual spells.

The spells themselves look like this:


<reference>
<spells>
:
<id-00069>
<name type='string'>Contact Ghoul</name>
<type type='string'>Contact</type>
<text type='formattedtext'>
<p>The spell costs 8 magic points to cast, and 1D3 Sanity points.Unless there are no ghouls nearby, it succeeds automatically. Ghouls are found wherever large concentrations of humans are, especially near graveyards and crypts. Places of burial more than a century old are propitious locations for this spell. Moonlit nights are best.</p>
</text>
</id-00069>
:
</spells>
</reference>


Window Class

The onInit event handler in the window class loads the spells from the source node that match the given spelltype:


function onInit()
local src = window.getDatabaseNode().createChild("source","string").getValue();
local source = nil;
local spelltype = window.getDatabaseNode().createChild("spelltype","string").getValue();
if src and src~="" then
source = DB.findNode(src);
end
if source then
for k,node in pairs(source.getChildren()) do
if spelltype=="" or node.createChild("type","string").getValue()==spelltype then
createWindow(node);
end
end
end
end


I was reluctant to post this here, as it is a fairly thorough copy of code from a DA ruleset which hasn't even been published yet. Vroomfogle is working on another DA project and I would have preferred to contact him direct.

That said, this stuff will be under OGL and therefore available for folks to examine and re-use once it hits the streets, provided you acknowledge DA's and SW's contribution.

Stuart