Additional menu fields 🧩#

cs.megamenu ships one behavior with a menu field (IEditableMenu) and a viewlet for the main navigation. You can define additional menu fields in your own behavior and reuse the editor widget, validator, resolver, linkintegrity retriever, and viewlet base class.

The code in this guide is an example for a separate add-on (myaddon). It is not part of cs.megamenu; copy and adapt it in your own package.

To render your new field with custom HTML, see Custom menu rendering after defining it.

Use a distinct field name and behavior for each menu. The examples below add a footer_menu field.

Define the field and behavior#

Create a behavior schema with a JSONField that uses MENU_JSON_SCHEMA, and apply the menu widget to that field with plone.autoform.directives.widget:

# myaddon/behaviors/footer.py
from cs.megamenu.schema import MENU_JSON_SCHEMA
from cs.megamenu.widgets.menu import MegaMenuFieldWidget
from plone.autoform import directives
from plone.autoform.interfaces import IFormFieldProvider
from plone.schema import JSONField
from plone.supermodel import model
from zope.interface import provider


@provider(IFormFieldProvider)
class IFooterMenu(model.Schema):
    """Adds a footer menu field."""

    directives.widget("footer_menu", MegaMenuFieldWidget)

    footer_menu = JSONField(
        title="Footer menu",
        description="Menu rendered in the site footer.",
        required=False,
        schema=MENU_JSON_SCHEMA,
    )

Register the behavior in your package’s ZCML. Remember the plone namespace and the plone.behavior meta include:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:plone="http://namespaces.plone.org/plone"
    >

  <include package="plone.behavior" file="meta.zcml" />

  <plone:behavior
      name="myaddon.footer_menu"
      title="Footer menu"
      description="Adds a footer menu field."
      provides=".behaviors.footer.IFooterMenu"
      />
</configure>

Enable the behavior on the content types in your profile, for example:

<object meta_type="Dexterity FTI" name="Plone Site">
  <property name="behaviors" purge="false">
    <element value="myaddon.footer_menu" />
  </property>
</object>

The schema-only behavior also acts as the behavior marker, so the object provides IFooterMenu when the behavior is enabled.

Register the validator#

The semantic validator is reusable. cs.megamenu already registers the MenuValidator adapter; add a discriminator for your field so it is selected for footer_menu as well:

# myaddon/validators/menu.py
from cs.megamenu.validators.menu import MenuValidator
from myaddon.behaviors.footer import IFooterMenu
from z3c.form import validator

validator.WidgetValidatorDiscriminators(
    MenuValidator,
    field=IFooterMenu["footer_menu"],
)

Import this module from your package so the discriminator is registered.

Resolve the menu#

get_menu() accepts the field name and behavior:

from cs.megamenu.menu import get_menu
from myaddon.behaviors.footer import IFooterMenu

menu, owner = get_menu(
    context,
    field_name="footer_menu",
    behavior=IFooterMenu,
)

See Custom menu rendering for the return contract, filter_items(), and rendering examples.

Render with the built-in MegaMenuViewlet#

Subclass MegaMenuViewlet and point it at your field and behavior:

# myaddon/viewlets/footer.py
from cs.megamenu.viewlets.menu import MegaMenuViewlet
from myaddon.behaviors.footer import IFooterMenu


class FooterMenuViewlet(MegaMenuViewlet):
    menu_field = "footer_menu"
    menu_behavior = IFooterMenu

This reuses the built-in Barceloneta markup, resolution, permission filtering, and icon rendering. For fully custom HTML, use the standalone viewlet pattern in Custom menu rendering.

Register it under a new viewlet name and the manager used by your template, for example:

<browser:viewlet
    name="myaddon.footer_menu"
    manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
    class=".viewlets.footer.FooterMenuViewlet"
    permission="zope2.View"
    layer="myaddon.interfaces.IBrowserLayer"
    />

Add the browser namespace to the ZCML root (xmlns:browser="http://namespaces.zope.org/browser").

Checklist#

  • The field name must match in directives.widget(), get_menu(), the retriever’s field_name, and the viewlet’s menu_field.

  • Use a separate behavior and field for each menu.

  • Enable the behavior on the content types where the menu should be available.

  • Import the validator module so its field discriminator is registered.

  • Include the linkintegrity retriever only when plone.app.linkintegrity is installed.