This tutorial will guide you through creating a dropdown menu using a PolyList widget. The finished product will be able to display an array of data, even if the array itself contains more arrays. The completed stack should look like the below example, where you can drag a button on, give it a name and click it to create and build the PolyList. You can learn more about the PolyList widget here. Note, Polylist is an addon for LiveCode, if you don’t already own it you can get it here.
Creating a PolyList
To use a PolyList to create dropdown menus, you first need to understand how to create and use a PolyList. To create a PolyList you can either drag it from the tools palette or create it with LiveCode Script by typing
create widget “myPolyList” as "com.livecode.widget.polylist"
Next, you need to know how to set the dataLayout and tsvData, these both have to be done with the script, and simple examples of each are shown below.
Data Layout Example
local tDataLayout
put "name" into tDataLayout[1]["name"]
put "90" into tDataLayout[1]["width"]
put “90" into tDataLayout[1]["height"]
put "10" into tDataLayout[1]["top"]
put "10" into tDataLayout[1]["left"]
put "text" into tDataLayout[1]["content-type"]
set the dataLayout of widget “myPolyList” to tDataLayout
TSV Data Example
local tTsvData
put “name” & return into tTsvData
put “John Smith” & return after tTsvData
put “Jane Doe” & return after tTsvData
set the tsvData of widget “myPolyList” to tTsvData
This will create a simple PolyList that just shows someone’s name, but PolyList can be used for so much more, and this is going to cover one example of that.
Creating the Dropdown
This tutorial is going to guide you to create a dropdown PolyList, similar to the one that will be featured in the new script editor.
Looking at this PolyList it may appear that there are just 2 pieces of data on each row (the key and the value), but there are actually several more. Each row also contains the ability to have an arrow, and the supporting vertical lines. In total there are 5 subitems on each row, and a function can be created to set this up which will look like the below:
function getPolyListDataLayout
local rDataLayout
put "line" into rDataLayout[1]["name"]
put "100" into rDataLayout[1]["height"]
put "0" into rDataLayout[1]["top"]
put "34px" into rDataLayout[1]["left"]
put "1px" into rDataLayout[1]["width"]
put "color" into rDataLayout[1]["content-type"]
put "halfline" into rDataLayout[2]["name"]
put "30" into rDataLayout[2]["height"]
put "70" into rDataLayout[2]["top"]
put "34px" into rDataLayout[2]["left"]
put "1px" into rDataLayout[2]["width"]
put "color" into rDataLayout[2]["content-type"]
put "arrow" into rDataLayout[3]["name"]
put "14px" into rDataLayout[3]["width"]
put "14px" into rDataLayout[3]["height"]
put "9px" into rDataLayout[3]["top"]
put "27px" into rDataLayout[3]["left"]
put "icon-path" into rDataLayout[3]["content-type"]
put "102,102,102,255" into rDataLayout[3]["color"]
put "name" into rDataLayout[4]["name"]
put "24px" into rDataLayout[4]["height"]
put "4px" into rDataLayout[4]["top"]
put "55px" into rDataLayout[4]["left"]
put "50" into rDataLayout[4]["width"]
put "text" into rDataLayout[4]["content-type"]
put "value" into rDataLayout[5]["name"]
put "24px" into rDataLayout[5]["height"]
put "4px" into rDataLayout[5]["top"]
put "50" into rDataLayout[5]["left"]
put "50" into rDataLayout[5]["width"]
put "text" into rDataLayout[5]["content-type"]
return rDataLayout
end getPolyListDataLayout
Line and half line are the depth lines to show when a section is opened, half line is the line going from the dropdown arrow to the bottom of the row, and line covers the whole height of the row. The next subitem is arrow, which will contain the SVG path for the arrow, showing whether it is opened or not, and the final two subitems are the name (key) and the value, positioned on the left half and right half of the row respectively.
Now that we have the data layout set, we need to work on generating the TSV data. This may seem simple at first as you just need to convert your array to a TSV string, but you also need to account for nested arrows, which means this TSV generation function needs to support recursion. On top of this, the function needs to check whether it should recurse or not based on whether the array is opened or not.
So to start we should create a global variable to store which keys have been opened, this can be created by putting
local sOpenVariables
at the top of your script file, and a second global variable needs to be created that will be the array to inspect, this can be created using
local sArray
but will need to be populated in code. Once this is done, a method needs to be created to update the PolyList. This will handle setting the TSV data of the PolyList and will be called every time a dropdown is opened or closed.
command update
local tTsvData
put "arrow" & tab & "name" & tab & "value" & tab & "line" & tab & "halfline" & tab & "_namePath" & return into tTsvData
put generateTsvData(sArray, 0, empty) after tTsvData
if the last character of tTsvData is return then
delete the last character of tTsvData
end if
set the tsvData of widget "myPolyList" to tTsvData
end update
In the header line of the TSV data you may notice there is a key defined which doesn’t reference a subitem, “_namePath”. This is metadata for the row and will be used to uniquely reference this specific row, and will be stored in the sOpenVariables variable to know whether the row is opened or not. On top of this, the update method references a function that we haven’t created yet, generateTsvData, which will do a lot of the heavy lifting for this dropdown.
In the generateTsvData function, we need to do several things initially. The first is to create a prefix for the key, this will show that the key is a child of a parent by indenting it, like in the above example. Next, each key in the array needs to be iterated over to check whether the value for this key is an array itself. If it is not then the TSV string can just be created, otherwise, we will need to check whether the dropdown is opened, and create an arrow that reflects this. As well as this, if the dropdown is opened, this function needs to be called recursively, passing the value to inspect, increasing the depth level, and passing the current “_namePath” to be used as a parent. The finished function should look like this:
function generateTsvData pData, pDepth, pParents
local rTsvData, tDepthPrefix
put empty into tDepthPrefix
repeat for pDepth times
put " " after tDepthPrefix
end repeat
repeat for each key tKey in pData
local tNamePath
put pParents & tKey into tNamePath
if the number of elements of pData[tKey] is 0 then
local tLineColor
put "102,102,102,0" into tLineColor
if pDepth > 0 then
put "102,102,102,102" into tLineColor
end if
put empty & tab & tDepthPrefix & tKey & tab & pData[tKey] & tab & tLineColor & tab & "255,255,255,0" & tab & tNamePath & return after rTsvData
else
local tHalfLineColor, tArrow
if tNamePath is not among the items of sOpenVariables then
put "m 1.8024106,0.1431135 a 1.046,1.046 0 0 0 -0.576172,0.296875 l -0.990234,0.988282 a 1.046,1.046 0 0 0 0,1.4843749 l 4.21875,4.21875 -4.21875,4.2226556 a 1.046,1.046 0 0 0 0,1.482422 l 0.988281,0.990234 a 1.046,1.046 0 0 0 1.484375,0 L 8.659833,7.8735824 a 1.041,1.041 0 0 0 0,-1.478515 L 2.7086606,0.4399885 a 1.046,1.046 0 0 0 -0.90625,-0.296875 z" into tArrow
put "255,255,255,0" into tHalfLineColor
else
put "M6.7,20.1.327,13.72a1.121,1.121,0,0,1,0-1.59l1.06-1.06a1.121,1.121,0,0,1,1.59,0l4.52,4.52,4.52-4.52a1.121,1.121,0,0,1,1.59,0l1.06,1.06a1.121,1.121,0,0,1,0,1.59L8.289,20.1a1.115,1.115,0,0,1-1.585,0Z" into tArrow
put "102,102,102,102" into tHalfLineColor
end if
put tArrow & tab & tDepthPrefix & tKey & tab & empty & tab & "255,255,255,0" & tab & tHalfLineColor & tab & tNamePath & return after rTsvData
if tNamePath is among the items of sOpenVariables then
put generateTsvData(pData[tKey], pDepth + 1, tNamePath & colon) after rTsvData
end if
end if
end repeat
return rTsvData
end generateTsvData
With each recursion, the variable pDepth will increase by 1, and the variable pParents will contain the current path to the key excluding the key itself. For example, if you have an array
tArray[“key1”][“key2”][“key3”] = “value”
then the pParents at the time when the TSV data for key3 is created will be “key1:key2” but the name path for key3 will be “key1:key2:key3”. To decide whether the lines are shown or not, the color of them is changed. The color given is an RGBA value, and so the 4th piece of data (the alpha) can be 0, which means the line will be invisible. This is used to hide the lines.
The dropdown is almost complete, finally, some code just needs to be created to allow the dropdowns to function. This will rely on the subItemClick method of the PolyList, which is called whenever a subitem is clicked. Within this method, we first need to check whether the subitem clicked was an arrow or not, as we don’t want a dropdown to toggle whenever a user clicks any subitem on the row. Next we need to make sure that the value of the arrow isn’t empty, as even when an arrow is not visible, the subitem is still there and can still be clicked. If an arrow does exist, then we just need to update the variable sOpenVariables to either add or remove the current key path. This may sound simple, but we need to ensure that when a parents dropdown is closed, it closes all the childrens dropdowns as well. This can be done by iterating over each item in sOpenVariables and deleting all occurrences that start with the current name path. The finished method should look like this:
on subItemClick pItemIndex, pSubItemName
if pSubItemName is "arrow" then
local tItemData, tVarName
/* Set data pointer */
set the itemPointer of the target to pItemIndex
put the itemContent of the target into tItemData
if tItemData[pSubItemName] is empty then
exit subItemClick
end if
if tItemData["_namePath"] is not among the items of sOpenVariables then
put tItemData["_namePath"] & comma before sOpenVariables
if the last character of sOpenVariables is comma then
delete the last character of sOpenVariables
end if
else
set wholematches to true
repeat for each item tItem in sOpenVariables
if tItem begins with tItemData["_namePath"] then
delete item itemoffset(tItem,sOpenVariables) of sOpenVariables
end if
end repeat
end if
update
end if
end subItemClick
Now that this is all done, all we need to do is create a PolyList with the name “myPolyList” and populate the sArray variable with data. To help set this up create a button with the name “setupButton”, put this code in your script, and click the button:
on mouseUp
if the short name of the target is "setupButton" then
if there is not a widget "myPolyList" then
create widget "myPolyList" as "com.livecode.widget.polylist"
set the dataLayout of widget "myPolyList" to getPolyListDataLayout()
end if
end if
set the width of control "setupButton" to the width of me
set the height of control "setupButton" to 60
set the topLeft of control "setupButton" to the topLeft of me
set the width of widget "myPolyList" to the width of me
set the height of widget "myPolyList" to the height of me - 60
set the topLeft of widget "myPolyList" to the bottomleft of control "setupButton"
set the itemheight of widget "myPolyList" to 32
set the strokeBody of widget "myPolyList" to false
set the strokeItems of widget "myPolyList" to false
set the vScrollBar of widget "myPolyList" to "Off"
set the plItemColor of widget "myPolyList" to "255,255,255,0"
set the plHiliteColor of widget "myPolyList" to "228,239,243,255"
set the hoverWithMouse of widget "myPolyList" to true
set the mouseHoverHilite of widget "myPolyList" to true
set the margin of widget "myPolyList" to 0
set the borderWidth of widget "myPolyList" to 0
put empty into sArray
put "No Depth Value" into sArray["No Depth Key"]
put "1 Depth Value 1" into sArray["Depth Key"]["1 Depth Key 1"]
put "1 Depth Value 2" into sArray["Depth Key"]["1 Depth Key 2"]
put "2 Depth Value 1" into sArray["Depth Key"]["2 Depth Key 1"]["2 Depth Key 1"]
put "2 Depth Value 2" into sArray["Depth Key"]["2 Depth Key 1"]["2 Depth Key 2"]
update
end mouseUp
Once all this is created, then you should have a working dropdown PolyList. This just outlines a single example, but this can be modified to suit whatever purpose you need. Try making changes to the code you’ve produced to get it to fit your needs, or just to learn more about how it works.
Exercises
- Allow values to support a return in them. Currently, this will throw an error, test this with
put “Value line 1” & return & “Value line 2” into sArray[“No Depth Key”] - Allow the divide between key and value to be moved like this:
- Create a filter field, to search the keys or values (or allow the user to pick between keys, values, or both)
- Create a sort order (alphabetical, reverse alphabetical)
3 comments
Join the conversationJames Hale - May 2, 2023
Great post.
You mention creating 2 global variables sOpenVariables and sArray, but show the script examples as local.
I.e. “local sOpenVariables“ and “local sArray”
Rather than,
‘global sOpenVariables“ and “global sArray”
Ben - May 2, 2023
Hi James,
Sorry for the confusion, when I’m talking about those variables I meant global as can be accessed by any function within the file, but not actual global variables. By defining the “sOpenVariables” and “sArray” outside a function it means the whole script can access them 🙂
Thanks!
James Hale - May 2, 2023
Thanks Ben, you might want to change the script snippets you have in the lesson though.