A menu item in a flyout menu has an enabled property, a checked property, a label property. When I’ve got a menu item, I really don’t understand why I can’t simply say myMenuItem.enabled = false
(to disable it), or myMenuItem.checked = true
(to check it), or myMenuItem.label = 'Whatever'
(to set its label).
The only way I have found to do any of these things is using this function that is available on the collection of menu items that contain the menu item:
insertAt(index, newItem)
It’s a slightly curious function. If a menu item exists at the index you pass, it replaces the existing item with the new item you’re passing. If you pass an index that is 1 greater than the index of the last item, it appends the new item. In other words, with this function you can replace menu items and you can add menu items to the end of a collection of menu items. (There is no function that I have found to, for instance, insert a new menu item at the beginning of a collection or in the middle of a collection – before or after an existing item for example.)
Using this function in its replacing form, my original idea was to get the current properties of the existing menu item, create a new menu item with the same properties, change the property I am interested in (e.g. change ‘enabled’ from true to false), and then insert the new menu item back into the same place.
That essentially is the way I have found to do it, but it is a bit more complicated . . .
The function requires a new menu item to be passed (as the second parameter). What I have discovered is that you can’t replace a menu item with a new menu item if they both have the same id (an error to that effect is thrown).
So, as well as changing the particular property I want to change, I have to change the id each time I call the function.
I’ve done that by appending a number to the end of my id which I increment before I pass it to the function. This is less than ideal because you normally handle the invocation of a menu item in a switch statement on the id. That can’t be done as normally if the id is constantly changing but can be got round by stripping off the incrementing numbers and performing the switch on the original id which remains the same.
The other parameter that this function requires is the index. If your menu item is at the root level of the flyout menu, the index will be its index in relation to the root, but if your menu item is in a submenu, the index is the index in relation to the submenu containing it, and the function itself needs to be called on the submenu menu items (not on the menu items of the flyout as a whole).
Calculating the index to pass to the function is somewhat tortuous, albeit logical. If you’re starting with just the id (which you do when starting from the invokeMenu function), you need to first get all the menu items in the flyout menu (you can do that from the panel with entrypoints.getPanel(id).menuItems
, passing the panel id). From that you can get the menu item itself (with the getItem(id)
function, passing the menu item id). The menu item has a parent
property which gives you the containing menu items (might be the menu items of a submenu). Menu items have an outputObject
property which, being an array, gives you a way to calculate the index.
I have written the following (proof-of-concept) code. When the user selects a menu item in the flyout menu, if the menu item is checked, it is unchecked, and vice versa. It is proof-of-concept code – it is just to show the only way I have found to change the properties of a menu item in a flyout menu (in InDesign). It is changing the checked property but could equally well change the enabled or label properties. If I were to use this in real code, I would split out parts of the code into discrete functions (in particular the code used to generate a new menu item id each time).
This code should work with any configuration of menu items and submenus, but FWIW here is what I have in the menuItems section of the panel section of the entry points.setup function:
menuItems: [
{id:'one', label:'One'},
{id:'submenu-hello', label:'Hello', submenu:[
{id:'hello-fench', label:'Bonjour'},
{id:'hello-spanish', label:'Hola'}
]},
{id:'two', label:'Two'}
],
My invokeMenu function (in the same place) looks like this:
invokeMenu(menuItemID) {
const panelID = this.id;
toggleChecked(menuItemID, panelID);
}
I am passing the id of the menu item and the id of the panel to this function which checks or unchecks the invoked menu item:
function toggleChecked(theMenuItemID, thePanelID) {
// we get all the menu items of the panel flyout menu
const panelMenuItems = entrypoints.getPanel(thePanelID).menuItems;
// with that we can get the menu item (that has been invoked)
const menuItem = panelMenuItems.getItem(theMenuItemID);
// to calculate the index, we get the containing menu items with the parent property of the menu item
const parentMenuItems = menuItem.parent;
// the outputObject property of menu items is an array, so we can use that to find the index of our menu item
const idx = parentMenuItems.outputObject.findIndex(obj => obj.id === theMenuItemID);
// we can't insert a menu item that has the same id as an already existing menu item
// so we create a new id ...
if (!theMenuItemID.includes('||')) theMenuItemID = `${theMenuItemID}||0`;
let [baseName, incrementingVersion] = theMenuItemID.split('||');
incrementingVersion = (parseInt(incrementingVersion) + 1).toString();
const newMenuItemID = `${baseName}||${incrementingVersion}`;
// if our menu item is checked we want to uncheck it, and vice versa
const newCheckedState = (menuItem.checked) ? false : true;
// now we can create a new menu item (as a simple object) for passing to the insertAt function
const newMenuItem = {
id:newMenuItemID,
label:menuItem.label,
checked:newCheckedState,
enabled:menuItem.enabled
};
// finally we call the insertAt function (on the menu items that contain our menu item)
parentMenuItems.insertAt(idx, newMenuItem);
}
It works, but it is an absurd amount of code to have to write when all I want to do is check or uncheck a menu item! Surely there’s a simple way!
Philip