|
| 1 | +.. _About XBlock Asides: |
| 2 | + |
| 3 | +################### |
| 4 | +About XBlock Asides |
| 5 | +################### |
| 6 | + |
| 7 | +.. tags:: developer, concept |
| 8 | + |
| 9 | +An XBlock aside is a class that injects content into the rendered views of |
| 10 | +existing XBlocks without modifying those XBlocks. Asides let you add behavior, |
| 11 | +data, and UI elements to many XBlock instances at once, across XBlock types you |
| 12 | +do not own, while preserving the host XBlock's code, fields, and Open Learning |
| 13 | +XML (OLX) representation. |
| 14 | + |
| 15 | +.. contents:: Contents |
| 16 | + :local: |
| 17 | + :depth: 1 |
| 18 | + |
| 19 | +What an Aside Is |
| 20 | +**************** |
| 21 | + |
| 22 | +An aside is a Python class that subclasses :class:`~xblock.core.XBlockAside`, |
| 23 | +declares one or more view-injection methods using the |
| 24 | +:func:`~xblock.core.XBlockAside.aside_for` decorator, and is registered with |
| 25 | +the platform through a Python entry point in the ``xblock_asides.v1`` group. |
| 26 | +When the platform renders an XBlock view, the runtime collects every |
| 27 | +applicable aside, invokes its matching aside view, and appends the resulting |
| 28 | +fragments to the host XBlock's rendered fragment. |
| 29 | + |
| 30 | +An aside is **not** a child XBlock. It does not appear in the course outline, |
| 31 | +it does not have its own URL, and it cannot be added to a course like a |
| 32 | +regular block. It exists only in relation to a host block, and its lifecycle |
| 33 | +is bound to that host block's lifecycle. |
| 34 | + |
| 35 | +For the precise API surface, see :ref:`XBlock Asides Reference`. |
| 36 | + |
| 37 | +The Problem Asides Solve |
| 38 | +************************ |
| 39 | + |
| 40 | +When you want to enhance the behavior of an XBlock that you did not write, |
| 41 | +you have three options: |
| 42 | + |
| 43 | +#. Fork the XBlock and modify it directly. |
| 44 | +#. Replace the XBlock with a new XBlock that wraps the original. |
| 45 | +#. Attach an aside to the existing XBlock. |
| 46 | + |
| 47 | +The first two options carry significant costs. Forking creates a parallel |
| 48 | +codebase that must be maintained against upstream changes. Replacing the |
| 49 | +XBlock requires every existing course that uses the original to migrate, and |
| 50 | +it does not scale when you want to enhance many different XBlock types in the |
| 51 | +same way. |
| 52 | + |
| 53 | +Asides solve this by externalizing the enhancement. The host XBlock is not |
| 54 | +modified. The same aside can apply to a Video block, a Problem block, or any |
| 55 | +other block type, by overriding a single classmethod. Course authors keep |
| 56 | +control over whether the enhancement is active for a given block, because |
| 57 | +asides expose their own scoped fields. The enhancement travels with the |
| 58 | +course in OLX export and import. |
| 59 | + |
| 60 | +Reach for an aside when all of the following are true: |
| 61 | + |
| 62 | +* You want to enhance one or more existing XBlock types without forking them. |
| 63 | +* The enhancement is conceptually layered on top of the block, not a |
| 64 | + replacement for any of its behavior. |
| 65 | +* The enhancement should apply to many block instances, possibly across |
| 66 | + block types, without per-instance configuration in the course outline. |
| 67 | +* The enhancement may need its own settings or stored data, scoped to the |
| 68 | + block instance. |
| 69 | + |
| 70 | +Reach for something else when: |
| 71 | + |
| 72 | +* You are creating a brand new piece of course content. Write an XBlock. |
| 73 | +* You need to change a behavior that is internal to a single block type and |
| 74 | + not visible in any view. Consider a runtime service or a filter from the |
| 75 | + Hooks Extension Framework. |
| 76 | +* You only need to react to platform events. Consider an Open edX event |
| 77 | + receiver. |
| 78 | + |
| 79 | +How an Aside Relates to Its Host Block |
| 80 | +************************************** |
| 81 | + |
| 82 | +The runtime maintains a many-to-many relationship between asides and host |
| 83 | +blocks at runtime, but each aside instance is bound to exactly one host block |
| 84 | +during a single render. The relationship is established in three stages. |
| 85 | + |
| 86 | +Discovery |
| 87 | +========= |
| 88 | + |
| 89 | +When the runtime renders an XBlock view, it asks the runtime for the set of |
| 90 | +applicable aside types. The default runtime returns every aside class |
| 91 | +registered through the ``xblock_asides.v1`` entry point. A runtime may |
| 92 | +override this to filter the set further, for example based on the current |
| 93 | +user or the course. |
| 94 | + |
| 95 | +Per-Block Filtering |
| 96 | +=================== |
| 97 | + |
| 98 | +For each candidate aside type, the runtime instantiates the aside and asks |
| 99 | +it whether it should apply to this specific block by calling its |
| 100 | +:meth:`~xblock.core.XBlockAside.should_apply_to_block` classmethod. The |
| 101 | +default implementation returns ``True``. Real-world asides almost always |
| 102 | +override this method to restrict themselves to specific block types, course |
| 103 | +contexts, or feature flags. |
| 104 | + |
| 105 | +Rendering and Layout |
| 106 | +==================== |
| 107 | + |
| 108 | +For each aside that survives filtering, the runtime invokes the aside method |
| 109 | +that was decorated with ``@XBlockAside.aside_for(view_name)`` for the view |
| 110 | +being rendered. The aside method returns a ``Fragment``, the runtime wraps |
| 111 | +that fragment with identifying markup, and the runtime appends the wrapped |
| 112 | +fragment to the host block's rendered output. A runtime can override |
| 113 | +:meth:`~xblock.runtime.Runtime.layout_asides` to control where and how the |
| 114 | +aside fragments are placed. |
| 115 | + |
| 116 | +Why Asides Are Worth the Trouble |
| 117 | +******************************** |
| 118 | + |
| 119 | +The framing above describes the trade-offs from the perspective of someone |
| 120 | +choosing among extension mechanisms. The deeper reasons asides exist, and |
| 121 | +remain useful, come from the production deployments that depend on them. |
| 122 | + |
| 123 | +Multiple Block Types, One Implementation |
| 124 | +======================================== |
| 125 | + |
| 126 | +A single aside class can decorate Video blocks, Problem blocks, and any |
| 127 | +other block type the author chooses, by checking ``block.category`` or |
| 128 | +``block.scope_ids.block_type`` inside ``should_apply_to_block``. The MIT |
| 129 | +Open Learning chat aside, for example, attaches an "AskTIM" chat button to |
| 130 | +both Video and Problem blocks from a single class, with one entry point. |
| 131 | +Without asides, the same outcome would require either two parallel forks |
| 132 | +or replacement blocks for both types. |
| 133 | + |
| 134 | +Course Author Control |
| 135 | +===================== |
| 136 | + |
| 137 | +An aside can declare its own scoped fields, just like an XBlock. By exposing |
| 138 | +those fields in an author view, an aside gives course authors a UI to enable |
| 139 | +or disable the enhancement on a per-block basis. The settings are stored |
| 140 | +under the aside's own scope, not the host block's, so they are preserved |
| 141 | +across exports and imports without any change to the host block's data |
| 142 | +model. |
| 143 | + |
| 144 | +OLX Export and Import |
| 145 | +===================== |
| 146 | + |
| 147 | +When a course is exported to OLX, the platform serializes each aside as an |
| 148 | +XML child element under its host block, named after the aside's entry point |
| 149 | +name. On import, the runtime reconstitutes the asides automatically. This |
| 150 | +means an aside-enhanced course is portable, with limitations described below. |
| 151 | + |
| 152 | +Real-World Examples |
| 153 | +******************* |
| 154 | + |
| 155 | +Three implementations in the wild illustrate the range of what asides can |
| 156 | +do. |
| 157 | + |
| 158 | +Rapid Response XBlock |
| 159 | +===================== |
| 160 | + |
| 161 | +The `rapid-response-xblock`_ from MIT Open Learning is a single aside that |
| 162 | +applies to Problem blocks. It overlays an instructor-only control on the |
| 163 | +problem in the LMS that lets a live instructor open and close response |
| 164 | +windows during a lecture, and it renders a real-time chart of student |
| 165 | +responses. Course authors enable it per problem in Studio. The repository |
| 166 | +name calls it an "xblock" but the implementation is purely an aside. |
| 167 | + |
| 168 | +Open Learning Chat Aside |
| 169 | +======================== |
| 170 | + |
| 171 | +The `ol-openedx-chat`_ aside, also from MIT Open Learning, attaches an |
| 172 | +"AskTIM" chat button to Video and Problem blocks. The button opens a |
| 173 | +context-aware chat drawer that streams messages to a backend large language |
| 174 | +model, passing block-specific context such as a video transcript identifier |
| 175 | +or a problem's siblings. A single aside class, registered as one entry |
| 176 | +point, handles both block types and uses ``should_apply_to_block`` to gate |
| 177 | +on a course-level waffle flag and per-course settings. |
| 178 | + |
| 179 | +Thumbs Sample Aside |
| 180 | +=================== |
| 181 | + |
| 182 | +The `xblock-sdk`_ repository contains a ``ThumbsAside`` class in |
| 183 | +``sample_xblocks/thumbs/thumbs.py``. It is **not functional** and is not |
| 184 | +registered through any entry point. The class comment in the source notes: |
| 185 | +"Asides aren't ready yet, so this is currently not being installed in |
| 186 | +setup.py." It exists as a syntactic example of the decorator pattern, not |
| 187 | +as a working aside. Treat it as illustrative only. |
| 188 | + |
| 189 | +Limitations |
| 190 | +*********** |
| 191 | + |
| 192 | +Asides are a real, working feature in production deployments, but the |
| 193 | +ecosystem around them is incomplete. The list below is drawn from the |
| 194 | +state of the codebase as of the Sumac release and from a 2025 Open edX |
| 195 | +Conference talk by Peter Pinch of MIT Open Learning. Read it before |
| 196 | +committing to an aside-based design. |
| 197 | + |
| 198 | +No Authoring Story in the Course Authoring MFE |
| 199 | +============================================== |
| 200 | + |
| 201 | +The Studio author view for an aside is rendered by the legacy course |
| 202 | +authoring frontend. The current Course Authoring micro-frontend has no |
| 203 | +defined location to display aside author UI. If your project depends on the |
| 204 | +new MFE for authoring, plan to render the aside's author UI through a |
| 205 | +different mechanism, or accept that authors will use the legacy Studio for |
| 206 | +this part of the workflow. |
| 207 | + |
| 208 | +Not All XBlocks Round-Trip Through OLX |
| 209 | +====================================== |
| 210 | + |
| 211 | +OLX export and import for asides depends on the host XBlock cooperating |
| 212 | +with the export process. Some XBlocks, including ORA2, do not preserve |
| 213 | +aside data through their export and import paths. If your aside must |
| 214 | +survive a course export and re-import on a course that uses one of these |
| 215 | +blocks, test the round trip end to end before depending on it. |
| 216 | + |
| 217 | +Multiple Asides on a Single Block Are Not Reliable |
| 218 | +================================================== |
| 219 | + |
| 220 | +The runtime supports multiple aside types decorating the same block in |
| 221 | +principle, but interactions between asides on the same block are not |
| 222 | +well-tested. Two asides that both decorate ``student_view`` on the same |
| 223 | +block may render correctly in isolation and break when combined. If you |
| 224 | +need this, build a single aside that composes both behaviors rather than |
| 225 | +relying on two independent asides to coexist. |
| 226 | + |
| 227 | +JavaScript Library Loading Is Limited |
| 228 | +===================================== |
| 229 | + |
| 230 | +Asides use the same fragment-based JavaScript loading mechanism as XBlocks, |
| 231 | +which assumes a single set of static assets. If your aside needs a JS |
| 232 | +library that is not already loaded by the host page, you must add it |
| 233 | +through the fragment, and you must handle ordering and conflicts yourself. |
| 234 | +There is no shared aside-level mechanism for declaring library dependencies. |
| 235 | + |
| 236 | +Documentation Has Historically Been Sparse |
| 237 | +========================================== |
| 238 | + |
| 239 | +XBlock Asides have been part of the platform for years but have had no |
| 240 | +user-facing documentation until this set of articles. The original work was |
| 241 | +done by Dave Ormsbee. Most of the institutional knowledge has lived in |
| 242 | +docstrings, test code, and the implementations of a handful of asides |
| 243 | +maintained outside the core platform. If you find this documentation lacks |
| 244 | +detail your project needs, the test file at ``xblock/test/test_asides.py`` |
| 245 | +in the XBlock repository is the most reliable source of behavioral truth. |
| 246 | + |
| 247 | +Where to Go Next |
| 248 | +**************** |
| 249 | + |
| 250 | +If you are ready to build an aside, start with |
| 251 | +:ref:`XBlock Aside Quickstart`. If you already have a target XBlock in mind |
| 252 | +and want a step-by-step recipe, read :ref:`Add an XBlock Aside`. For the |
| 253 | +complete list of classes, decorators, methods, and entry points, consult |
| 254 | +:ref:`XBlock Asides Reference`. |
| 255 | + |
| 256 | +.. _rapid-response-xblock: https://github.com/mitodl/rapid-response-xblock |
| 257 | +.. _ol-openedx-chat: https://github.com/mitodl/open-edx-plugins/tree/main/src/ol_openedx_chat |
| 258 | +.. _xblock-sdk: https://github.com/openedx/xblock-sdk |
| 259 | + |
| 260 | +.. seealso:: |
| 261 | + |
| 262 | + :ref:`XBlock Asides Reference` (reference) |
| 263 | + The complete API surface for ``XBlockAside`` and its runtime hooks. |
| 264 | + |
| 265 | + :ref:`Add an XBlock Aside` (how-to) |
| 266 | + A step-by-step recipe for adding an aside to existing XBlocks. |
| 267 | + |
| 268 | + :ref:`XBlock Aside Quickstart` (quickstart) |
| 269 | + A beginner-friendly walkthrough from zero to a running aside. |
| 270 | + |
| 271 | + :ref:`Hooks Extension Framework` (concept) |
| 272 | + An alternative extension mechanism for non-view-based behaviors. |
| 273 | + |
| 274 | +**Maintenance chart** |
| 275 | + |
| 276 | ++--------------+-------------------------------+----------------+--------------------------------+ |
| 277 | +| Review Date | Working Group Reviewer | Release |Test situation | |
| 278 | ++--------------+-------------------------------+----------------+--------------------------------+ |
| 279 | +| | | | | |
| 280 | ++--------------+-------------------------------+----------------+--------------------------------+ |
0 commit comments