diff --git a/docs/ZMI.rst b/docs/ZMI.rst
index 8f37c2fbbc..c04152a32c 100644
--- a/docs/ZMI.rst
+++ b/docs/ZMI.rst
@@ -28,3 +28,30 @@ value should be one of the names listed on `available icons`_.
.. _`zmi.icons` : https://github.com/zopefoundation/zmi.icons
.. _`available icons` : http://htmlpreview.github.io/?https://github.com/zopefoundation/zmi.icons/blob/master/zmi/icons/resources/demo.html
+
+Use custom icons
+++++++++++++++++
+
+To use custom icons (which are not part of `zmi.icons`), you need to subscribe
+to ``App.interfaces.IRenderZMIEvent`` to need your resources.
+
+Example from `Products.CMFCore/Products/CMFCore/zmi.py`::
+
+ import App.interfaces
+ import cmf.icons
+ import zope.component
+
+
+ @zope.component.adapter(App.interfaces.IRenderZMIEvent)
+ def load_assets(event):
+ """Load the CMS icons for the ZMI."""
+ cmf.icons.cmf_icons.need()
+
+The subscriber is registered in
+`Products.CMFCore/Products/CMFCore/event.zcml` like this::
+
+
+
+You can can `need` arbitrary fanstatic resources in the event subscriber, not
+only icon fonts, but also custom CSS and JavaScript.
diff --git a/src/App/Management.py b/src/App/Management.py
index bfdf2ef7b9..4b1417c058 100644
--- a/src/App/Management.py
+++ b/src/App/Management.py
@@ -18,13 +18,13 @@
from AccessControl.class_init import InitializeClass
from AccessControl.Permissions import view_management_screens
from App.interfaces import INavigation
+from App.interfaces import RenderZMIEvent
from App.special_dtml import DTMLFile
from ExtensionClass import Base
from six.moves.urllib.parse import quote, unquote
from zExceptions import Redirect
from zope.interface import implementer
-import js.bootstrap
-import zmi.icons
+import zope.event
try:
from html import escape
@@ -156,13 +156,11 @@ class Navigation(Base):
manage_form_title._setFuncSignature(
varnames=('form_title', 'help_product', 'help_topic'))
-
_manage_page_header = DTMLFile('dtml/manage_page_header', globals())
security.declareProtected(view_management_screens, 'manage_page_header')
def manage_page_header(self, *args, **kw):
"""manage_page_header."""
- js.bootstrap.bootstrap.need()
- zmi.icons.zmi_icons.need()
+ zope.event.notify(RenderZMIEvent())
return self._manage_page_header(*args, **kw)
security.declarePublic('manage_zmi_logout')
diff --git a/src/App/interfaces.py b/src/App/interfaces.py
index 06d09ddb2e..5a0841ad04 100644
--- a/src/App/interfaces.py
+++ b/src/App/interfaces.py
@@ -15,6 +15,7 @@
from zope.interface import Attribute
from zope.interface import Interface
+from zope.interface import implementer
class INavigation(Interface):
@@ -30,3 +31,12 @@ def manage_zmi_logout(REQUEST, RESPONSE):
"""Logout current user"""
INavigation.setTaggedValue('manage_page_style.css', Attribute(""" """))
+
+
+class IRenderZMIEvent(Interface):
+ """ZMI is rendered."""
+
+
+@implementer(IRenderZMIEvent)
+class RenderZMIEvent(object):
+ """Event fired when the ZMI is rendered."""
diff --git a/src/Zope2/App/configure.zcml b/src/Zope2/App/configure.zcml
index e99c1debfb..20a872e11e 100644
--- a/src/Zope2/App/configure.zcml
+++ b/src/Zope2/App/configure.zcml
@@ -17,4 +17,7 @@
+
+
diff --git a/src/Zope2/App/zmi.py b/src/Zope2/App/zmi.py
new file mode 100644
index 0000000000..4b9b81d9fa
--- /dev/null
+++ b/src/Zope2/App/zmi.py
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# Copyright (c) 2018 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+from __future__ import absolute_import
+
+import App.interfaces
+import js.bootstrap
+import zmi.icons
+import zope.component
+
+
+@zope.component.adapter(App.interfaces.IRenderZMIEvent)
+def load_assets(event):
+ """Load the static assets needed to render the ZMI."""
+ js.bootstrap.bootstrap.need()
+ zmi.icons.zmi_icons.need()