10

This question is about a potential Lightning platform bug I ran across today. In my search for a resolution I ran across this post from last year that was never answered, that seems to have the identical problem: $A.createComponents() with lightning:buttonMenu and lightning:menuItem

If I'm doing something wrong with my code please let me know. That would certainly be preferred, as the fix will be a lot faster...

The error happens when you click on the menu...you get a popup that says the following:

Assertion Failed!: Descriptor for Config required for registration : undefined

Here is a stripped down reproduction of the bug.

Static Instantiation: Working

Markup:

<aura:application extends="force:slds">

    <lightning:buttonMenu aura:id="menu">
        <lightning:menuItem />
    </lightning:buttonMenu>

</aura:application>

Dynamic Instantiation: Broken

Markup:

<aura:application extends="force:slds">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <lightning:buttonMenu aura:id="menu" />

</aura:application>

Controller:

({
    doInit : function(component, event, helper) {

        $A.createComponent('lightning:menuItem', {}, function(newComponent){

            let menu = component.find('menu');

            let body = menu.get('v.body');
            body.push(newComponent);
            menu.set('v.body', body);
        });
    }
});

Working Solution

Thanks to great answers and some digging on my own, I've got a working solution that isn't as elegant as I would like but it gets the job done.

Because you can feed ComponentDefRef definitions to a buttonMenu as you instantiate it, you can solve this platform gap by always building your buttonMenu and your menuItem children at the same time. Here is some sample code from the project I was working on this for:

/*
 * Create representations of each lightning:buttonMenu that we would like to instantiate
 */
let menusToCreate = [];

// for each column we need to filter
for (let fieldName in filterOptions) {
  if (filterOptions.hasOwnProperty(fieldName)) {

    let filterData = filterOptions[fieldName],
      children = [{
        attributes: {
          values: {
            checked: false,
            label: '-- All --',
            value: '--all--'
          }
        },
        componentDef: 'markup://lightning:menuItem'
      }];

    // for each possible filter choice on this column, create a child that represents a lightning:menuItem
    filterData.forEach(function (choice) {

      // this JSON structure is very specific and represents a ComponentDefRef to the Aura framework
      children.push({
        attributes: {
          values: {
            checked: choice.checked,
            label: choice.label,
            value: choice.value
          }
        },
        componentDef: 'markup://lightning:menuItem'
      });
    });

    // now that we have all the children, add this menu to our list of dynamic components to build
    menusToCreate.push(['lightning:buttonMenu', {
      'aura:id': fieldName,
      'onselect': component.getReference('c.filterChosen'),
      'variant': 'bare',
      'body': children
    }]);
  }
}
  • What do you think @kris-gray? – Grekker Aug 25 '18 at 21:41
  • 1
    @glls Sadly, in this case, it's more complicated than that. I've been debugging this thing for the past hour... – sfdcfox Aug 25 '18 at 23:54
  • :( haven’t been able to check, out camping. But thought your answer could have been helpful – glls Aug 25 '18 at 23:56
10

Currently no.

The issue is that the body attribute for buttonMenu is of type Aura.ComponentDefRef[] vs the usual Aura.Component[]

Because of that, items passed to v.body are expected to be templates to create components vs actual components.

So you can't dynamically create the menuItem instances.

If you explain more why you want to do that, I could explain some workarounds.

|improve this answer|||||
  • I was/am trying to build a custom datatable component that allows for column headers offering filter selections with a button menu, and those selection options being dynamic based on the records available to the table. This is for an open source project here: github.com/open-force/grid – Grekker Aug 28 '18 at 3:40
  • I did come up with a working solution using ComponentDefRef on purpose, which I've been learning more about and trying to use with various levels of success (it's hard not having createComponentFromConfig accessible to us normal people). – Grekker Aug 28 '18 at 3:40
  • Gotcha. Yea, createComponentFromConfig is on the path to dying, so its good you're making due without it. – Kris Gray Aug 28 '18 at 4:27
  • 1
    FYI, Developers taking a look at it. Maybe next release we can improve this. – Kris Gray Aug 28 '18 at 4:28
8

After all this debugging, it appears to be a bug in the framework. Essentially, what's happening is that by the time the menu gets around to rendering the body in changeVisibility, the attribute componentDef on $A.createComponent(s) created lightning:menuItem components happens to be undefined instead of being markup://lightning:menuItem.

This ultimately causes a crash in Aura's $createComponentFromConfig$ method, as it cannot find the definition for this component. The error itself specifically happens on a line that reads something like if(config['descriptor']), which ends up crashing because config is undefined.

Note that I did find you can query the body of an existing menu and manipulate the public attributes of those items, or even remove them from the list. If possible, create a list of as many items as you need in markup, and then use your init method to hide/shuffle/update values as you see fit. I realize it's not ideal, and hopefully this bug will get fixed, but for now, a direct solution appears to be impossible.

|improve this answer|||||
  • 2
    You never cease to amaze me..... – Eric Aug 26 '18 at 3:16
  • @sfdcfox Thanks for digging so deep on this one. I ended up building the menu and the items together at the same time and that works. I'll update my question with some solution code. – Grekker Aug 28 '18 at 3:38
0

Anyone coming into this and trying to make the workaround work, here's what worked for me....

Simpler Solution:

Just hiding with CSS isn't exactly great, but it's incredibly simple:

<aura:attribute access="public" name="allowDelete" type="Boolean" required="true" />

<lightning:buttonMenu aura:id="menu" iconName="utility:settings" alternativeText="Edit/Undo/Delete menu" onselect="{!c.menuSelected}">
    <lightning:menuItem label="Edit" value="Edit"/>
    <lightning:menuItem label="Undo" value="Undo" />
    <lightning:menuItem label="Delete" value="Delete" class="{!v.allowDelete ? '' : 'slds-hide'}"/>
</lightning:buttonMenu>

My First Solution:

Component:

<aura:attribute access="public" name="allowDelete" type="Boolean" required="true" />
<aura:handler name="render" value="{!this}" action="{!c.modifyMenuItems}" />

<lightning:buttonMenu aura:id="menu" iconName="utility:settings" alternativeText="Edit/Undo/Delete menu" onselect="{!c.menuSelected}">
    <lightning:menuItem label="Edit" value="Edit" />
    <lightning:menuItem label="Undo" value="Undo" />
    <lightning:menuItem label="Delete" value="Delete" />
</lightning:buttonMenu>

Controller (part of):

modifyMenuItems : function(component, event, helper) {
    const menu = component.find('menu');
    let menuItems = menu.get('v.body');

    menuItems = menuItems.filter(function(thisMenuItem) {
        return thisMenuItem.attributes.values.value.value !== 'Delete' || component.get('v.allowDelete')
    });

    menu.set('v.body', menuItems);
},

The attribute allowDelete controls whether or not the Delete value should appear in the menu.

Seems a bit smelly, but works in Winter 19.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.