diff --git a/16/umbraco-cms/SUMMARY.md b/16/umbraco-cms/SUMMARY.md index c715b132715..647d1ee19b0 100644 --- a/16/umbraco-cms/SUMMARY.md +++ b/16/umbraco-cms/SUMMARY.md @@ -205,6 +205,7 @@ * [Umbraco Element](customizing/foundation/umbraco-element/README.md) * [Lit Element](customizing/foundation/lit-element.md) * [Context API](customizing/foundation/context-api/README.md) + * [Context API Fundamentals](customizing/foundation/context-api/context-api-fundamentals.md) * [Consume a Context](customizing/foundation/context-api/consume-a-context.md) * [Provide a Context](customizing/foundation/context-api/provide-a-context.md) * [Repositories](customizing/foundation/repositories.md) diff --git a/16/umbraco-cms/customizing/foundation/context-api/README.md b/16/umbraco-cms/customizing/foundation/context-api/README.md index 8376f56399e..9cb1a5391cf 100644 --- a/16/umbraco-cms/customizing/foundation/context-api/README.md +++ b/16/umbraco-cms/customizing/foundation/context-api/README.md @@ -1,9 +1,35 @@ --- -description: The Context API is your tool to integrate with the application +description: >- + Learn about using the Context API for sharing data and functionality between + backoffice extensions through the component hierarchy. --- # Context API -The Context API enables receiving APIs. Depending on where your code is executed from, it affects which and what instances of APIs can be received. The DOM structure defines the scope of who can gain access to what APIs. This is because the APIs are provided via an element and can then only be consumed by code running on itself or descendant elements. +The Context API is a powerful communication system in Umbraco's backoffice. It enables extensions to share data and functionality through the component hierarchy without tight coupling. Think of it as a way for different parts of your UI to talk to each other and access shared services. + +Contexts are used throughout the Umbraco backoffice to provide access to workspace data, notifications, user information, and many other services. When building custom extensions, you will often need to consume existing contexts or create your own to share functionality between your components. + +## Key Concepts + +The Context API is built on a few core principles: + +* **Provider-Consumer Pattern**: Parent elements provide contexts that descendant elements can consume +* **Loose Coupling**: Components don't need direct references to each other +* **Hierarchical**: Contexts flow down through the DOM tree +* **Type-Safe**: Context Tokens ensure you get the right context + +The Context API provides a structured way to access and share functionality when building property editors, workspace extensions, dashboards, or any other backoffice UI. + +## [Context API Fundamentals](context-api-fundamentals.md) + +Learn the core concepts, terminology, and flow mechanisms of the Context API. Understand how contexts are provided and consumed through the element hierarchy, and explore common context types used throughout Umbraco. + +## [Consume a Context](consume-a-context.md) + +Learn how to consume contexts in your extensions using one-time references or subscriptions. This guide covers consuming contexts in UI elements, services, and non-UI classes, with practical code examples for each scenario. + +## [Provide a Context](provide-a-context.md) + +Learn how to create and provide your own custom contexts. Make your data and functionality available to descendant elements in the component hierarchy. -## diff --git a/16/umbraco-cms/customizing/foundation/context-api/consume-a-context.md b/16/umbraco-cms/customizing/foundation/context-api/consume-a-context.md index aa981dfacc2..4dc8189441a 100644 --- a/16/umbraco-cms/customizing/foundation/context-api/consume-a-context.md +++ b/16/umbraco-cms/customizing/foundation/context-api/consume-a-context.md @@ -1,199 +1,405 @@ --- description: >- - Consuming a Context via the Context API is the way to start the communication - with the rest of the application + Learn how to consume contexts in Umbraco elements using one-time references + or subscriptions to access data and functionality through the Context API. --- - # Consume a Context +There are two ways to consume a context: get a __one-time reference__ to the context, or get a __subscription__ for handling context changes. The Context API is a flexible system where contexts can get disconnected or replaced. A subscription allows for the handling of these changes. However, subscriptions use more resources. They are typically consumed in the constructor, a time when the computer is already processing a lot. Which way to go depends on your use case. -## Start consuming - -There are different ways to consume a Context API. The most straightforward implementation is done on an [Umbraco Element](../umbraco-element/) with a Context Token. +A one-time reference approach is suitable for fire-and-forget events. The key here is that the context is not needed on initialization, but is only needed when a specific criteria is met. For instance, events that occur after user interaction or when a specific function is called. In that case, you need to get a context, do something and forget about the context after that. -All Umbraco Context APIs have a Context Token which can be imported and used for consumption, for example: - -```typescript -import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +If you need a context during initialization which is then set as a variable, you should always use a subscription. Otherwise, you risk holding on to a context that could be disconnected or replaced without you knowing. -... - -this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => { - // Notice this is a subscription, meaning the context can change and even disappear again. - if (context) { - console.log("I've got the Notification Context: ", context); - } else { - console.log("The Notification Context is gone, I will make sure my code disassembles properly.") - } -}); -``` +## Consuming contexts in an element +An [Umbraco Element](../umbraco-element/) is **any web component** that extends `UmbLitElement` or uses the `UmbElementMixin` to wrap its base class. Whether you are building with Lit, vanilla JavaScript, or any other web component framework, you can make it an Umbraco Element. This gives it full access to the Context API. -The above example takes place in an Umbraco Element or Umbraco Controller. +Umbraco Elements provide two methods for consuming contexts: -### Alternative solutions +- **`getContext(token)`** - Retrieves a one-time reference to a context +- **`consumeContext(token, callback)`** - Creates a reactive subscription to a context -The above examples utilize an Umbraco Controller to hook into an element's life cycle. This Controller is named `UmbContextConsumerController`. +Both methods accept a Context Token (or string alias) to identify which context to consume. -If you need to consume a Context API from a non-controller host, then look at the `UmbContextConsumer`. +### Get as one-time reference +The first example uses Lit and that is the way Umbraco builds their elements. If you do not want to use Lit, there is also an example using vanilla JavaScript. Both examples do not have any TypeScript specific code. You can use them in either a JavaScript or a TypeScript file. -## Get a context once - -You can retrieve a Context without getting updated if the Context disconnects or another better Context matches your request. - -This is useful if your code is a one-time execution, for example, when the user triggers an action that needs to communicate with a context: +{% tabs %} +{% tab title="Lit element" %} +```javascript +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import { html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +//The example element extends the UmbLitElement (which is the same as UmbElementMixin(LitElement)) +//This gives us all the helpers we need to get or consume contexts +export default class ExampleElement extends UmbLitElement { + + /** Notification handler for the notification button */ + async #notificationButtonClick() { + //We try to get an instance of the context + const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); + if (!notificationContext) { + throw new Error('Notification context not found!'); + } + + notificationContext?.peek("positive", { + data: { + headline: "Success", + message: "The notification button was clicked successfully!" + } + }); + + //The notification is sent, now forget the context + } -
async execute() {
-    const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
-    if (!notificationContext) {
-	throw new Error('Notification context not found');
+    /**
+     * Renders the lit component
+     * @see https://lit.dev/docs/components/rendering/
+     */
+    render() {
+        return html`
+            
+                Click me for a notification!
+            
+        `;
     }
-    const notification = { data: { message: `High five, you executed this method!` } };
-    notificationContext.peek('positive', notification);
 }
-
- -## **Write your own Context Token** -A Context Token is a context identifier and is generally a string matched with a type. In this way, users of the token can be sure to get the right type of context. +// Register the custom element +customElements.define('example-element', ExampleElement); +``` +{% endtab %} +{% tab title="HTML element" %} +```javascript +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +// The example element extends UmbElementMixin with HTMLElement +// This gives us all the helpers we need to get or consume contexts +export default class ExampleElement extends UmbElementMixin(HTMLElement) { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.#render(); + } -```typescript -import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + /** Renders the element **/ + #render() { + if (!this.shadowRoot) + return; + + this.shadowRoot.innerHTML = ` + + Click me for a notification! + + `; + + const button = this.shadowRoot.querySelector('#notificationButton'); + button?.addEventListener('click', this.#notificationButtonClick.bind(this)); + } -type MyContext = { - foo: string; - bar: number; -}; + /** Notification handler for the notification button */ + async #notificationButtonClick() { + + //We try to get an instance of the context + const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); + if (!notificationContext) { + throw new Error('Notification context not found!'); + } + + notificationContext?.peek("positive", { + data: { + headline: "Success", + message: "The notification button was clicked successfully!" + } + }); + + //The notification is sent, now forget the context + } +} -const MY_CONTEXT = new UmbContextToken ("My.Context.Token"); +// Register the custom element +customElements.define('example-element', ExampleElement); ``` +{% endtab %} +{% endtabs %} + +### Get as a subscription +When you are dealing with a subscription, it is good practice to consume the context in the constructor for the following reasons: + +* The constructor runs once when the element is created. This ensures your context subscription is set up before the element connects to the DOM. This guarantees you will not miss any context updates that occur during the element's initialization. +* Context consumers created in the constructor are automatically connected when the element enters the DOM (`connectedCallback`). They are disconnected when it is removed (`disconnectedCallback`). You do not need to manually manage this lifecycle as Umbraco's controller system handles it for you. +* By establishing context subscriptions in the constructor, your element's state is consistent from the moment it is created. This prevents race conditions where the element might render or perform actions before its required contexts are available. +* Creating context consumers in the constructor is more efficient than creating them in lifecycle methods that are called multiple times. For example, `connectedCallback` fires every time the element is added to the DOM. + +The first example uses Lit and that is the way Umbraco builds their elements. If you do not want to use Lit, there is also an HTML element example. Both examples do not have any TypeScript specific code. You can use them in either a JavaScript or a TypeScript file. + +{% tabs %} +{% tab title="Lit element" %} +```javascript +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; + +// The example element extends the UmbLitElement (which is the same as UmbElementMixin(LitElement)) +// This gives us all the helpers we need to get or consume contexts +export default class ExampleElement extends UmbLitElement { + #workspaceContext; + + constructor() { + super(); + + // This is a subscription that gets executed if: + // - The context gets connected + // - The context instance changes (replaced) + // - The context instance disconnects + this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { + if (context) { + console.log("I've got the document workspace context: ", context); + this.#workspaceContext = context; + } else { + console.log("The document workspace context is gone, I will make sure my code disassembles properly.") + this.#workspaceContext = null; + } + }); + } +} -### **Context Token with an API Alias** +// Register the custom element +customElements.define('example-element', ExampleElement); +``` +{% endtab %} +{% tab title="HTML element" %} +```javascript +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; + +// The example element extends UmbElementMixin with HTMLElement +// This gives us all the helpers we need to get or consume contexts +export default class ExampleElement extends UmbElementMixin(HTMLElement) { + #workspaceContext; + + constructor() { + super(); + + // This is a subscription that gets executed if: + // - The context gets connected + // - The context instance changes (replaced) + // - The context instance disconnects + this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { + if (context) { + console.log("I've got the document workspace context: ", context); + this.#workspaceContext = context; + } else { + console.log("The document workspace context is gone, I will make sure my code disassembles properly.") + this.#workspaceContext = null; + } + }); + } +} -For additions to already existing Contexts, the API Aliases should be used to identify the additional API. Using the same Context Alias for additional APIs will ensure that such API must be present with the first encounter of that Context Alias. Otherwise, a request will be rejected. In other words, if the addition is not part of the nearest matching Context, the request will be rejected. +// Register the custom element +customElements.define('example-element', ExampleElement); +``` +{% endtab %} +{% endtabs %} -For a concrete example of this in practice, read the [Extension Type Workspace Context](../../extending-overview/extension-types/workspaces/workspace-context.md) article. +## Consuming contexts in non-UI elements +Not all code that needs contexts lives in UI elements (web components). Services, managers, repositories, and helper classes often need access to contexts. These may include notifications, workspaces, or application state. However, they do not exist as elements in the DOM. -{% hint style="info" %} -Using API Alias is highlight recommended when implementing Additional Contexts to Existing Contexts. Most Context extensions should do this. -{% endhint %} +For these non-UI classes, extend `UmbControllerBase` to gain the same context consumption capabilities as elements. This base class provides `getContext()` and `consumeContext()` methods. This allows any class with a controller host to access the Context API. -```typescript -import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; +### Get as one-time reference +This example creates an example service that can show a notification in the backoffice of Umbraco based on the given text. -type MyAdditionalContext = { - additional: string; -}; +```javascript +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; -const MY_ADDITIONAL_API_TOKEN = new UmbContextToken( - "My.ContextFrame.Alias", - "My.API.Alias" -); +// This service class extends UmbControllerBase +// This gives us access to getContext() and consumeContext() +export default class ExampleService extends UmbControllerBase { + async showNotification(notificationText) { + + // We try to get an instance of the context + const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); + if (!notificationContext) { + throw new Error('Notification context not found!'); + } + + notificationContext?.peek("positive", { + data: { + headline: "Success", + message: notificationText + } + }); + + // The notification is sent, now forget the context + } +} ``` -The Token declared above can be used to provide an additional Context API at the same Element as another Context API is provided at. Below is an example of how the two APIs are made available. - -```typescript -const contextElement = new UmbLitElement(); -contextElement.provideContext( - MY_API_TOKEN, - new MyAPiFromSomewhereNotPartOfThisExample() -); -contextElement.provideContext( - MY_ADDITIONAL_API_TOKEN, - new MyAdditionalAPiFromSomewhereNotPartOfThisExample() -); - -const consumerElement = new UmbLitElement(); -contextElement.appendChild(consumerElement); -consumerElement.consumeContext(MY_API_TOKEN, (context) => { - console.log("I've got the default api", context); -}); -consumerElement.consumeContext(MY_ADDITIONAL_API_TOKEN, (context) => { - console.log("I've got the additional api", context); -}); +### Get as a subscription +This example consumes the document workspace context and saves it to a variable to be used later. + +```javascript +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; + +// This service class extends UmbControllerBase +// This gives us access to getContext() and consumeContext() +export class ExampleService extends UmbControllerBase { + #workspaceContext; + + constructor(host) { + super(host); + + // Subscribe to the document workspace context + this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { + if (context) { + console.log("I've got the document workspace context: ", context); + this.#workspaceContext = context; + } else { + console.log("The document workspace context is gone, I will make sure my code disassembles properly.") + this.#workspaceContext = null; + } + }); + } +} ``` -This is no different than using two different Context Aliases. But it has an important effect on what happens if one of them is not provided. This is demonstrated in the example below: - -```typescript -const upperContextElement = new UmbLitElement(); - -const contextElement = new UmbLitElement(); -upperContextElement.appendChild(contextElement); -contextElement.provideContext( - MY_API_TOKEN, - new MyAPiFromSomewhereNotPartOfThisExample() -); - -const consumerElement = new UmbLitElement(); -contextElement.appendChild(consumerElement); -consumerElement.consumeContext(MY_API_TOKEN, (context) => { - console.log("I've got the default api", context); -}); -consumerElement.consumeContext(MY_ADDITIONAL_API_TOKEN, (context) => { - // This will never happen - console.log("I will just get undefined: ", context); -}); -``` +## Manual context control +In rare cases, you may need complete manual control over context consumption. This means not extending `UmbControllerBase` or using element mixins. This is typically necessary when: -The consumption of the Additional API will never happen as the token uses the same Context Alias as `MY_API_TOKEN`. This means that any request containing this Context Alias will be stopped at the first API it encounters. To ensure addition to a specific context, do it locally at the nearest API that uses the same Context Alias. +- Integrating with third-party libraries or frameworks +- Working with legacy code that cannot be refactored +- Building custom architectural patterns outside Umbraco's standard controller system -### **Context Token with a Type Discriminator** +For these scenario's, use `UmbContextConsumer` directly. This low-level API gives you full control but requires manual lifecycle management. You must call `hostConnected()`, `hostDisconnected()`, and `destroy()`. -{% hint style="info" %} -This is only relevant if you are going to make multiple context API for the same context. Discriminator only gives value for consumption of Context APIs that have a varying interface. The backoffice uses this for the different types of Workspace Contexts. +{% hint style="warning" %} +**Use this approach only when necessary.** The methods shown in previous sections handle lifecycle management automatically. These include `UmbLitElement`, `UmbElementMixin`, and `UmbControllerBase`. They are suitable for most use cases. {% endhint %} -In some cases, it is needed to have different APIs for the same context. For example, the [Workspace Contexts](../../extending-overview/extension-types/workspaces/workspace-context.md). - -If someone wants the workspace name, they might not care about the specific API of the Workspace Context. These implementations can use a standard Context Token with a type of generic Workspace Context. +### Get as one-time reference +To create the one-time reference, you don't provide a callback when calling the UmbContextConsumer. This makes it destroy itself when going out of scope. +```javascript +import { UmbContextConsumer } from '@umbraco-cms/backoffice/context-api'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; -The features related to Publishing in the **Document Workspace Context** do not require a new Context. However, we should not accidentally retrieve the workspace context of a parent workspace when in a Workspace. Therefore, we need to provide a workspace context in each workspace, and the one we retrieve is the one we will be using. Since Publishing is not part of the generic Workspace Context, check if the context is a Document Workspace Context and recast it accordingly. +export class NotificationService { + #hostElement; -To avoid each implementation taking care of this, Context Tokens can be extended with a **Type Discriminator**. This will discard the given API if it does not meet the necessary requirements. When it is the desired type, the API will be converted to the appropriate type. + constructor(hostElement) { + this.#hostElement = hostElement; + } -This example shows how to create a discriminator Context Token that will discard the API if it is not a Publishable Context: + async showSuccess(message) { + // Create a consumer WITHOUT a callback + const consumer = new UmbContextConsumer( + this.#hostElement, + UMB_NOTIFICATION_CONTEXT + // No callback = one-time use + ); + + try { + // Trigger connection and get result as Promise + consumer.hostConnected(); + + // The promise will reject if the context is not found within one animation frame + // You can prevent this with: consumer.asPromise({ preventTimeout: true }) + const notificationContext = await consumer.asPromise(); + + if (!notificationContext) { + throw new Error('Notification context not found!'); + } + + notificationContext.peek("positive", { + data: { + headline: "Success", + message: message + } + }); + } finally { + // Manually clean up to ensure proper disconnection + consumer.hostDisconnected(); + consumer.destroy(); + } + } +} +``` -**Context Token Example:** +### Get as a subscription +In contrast to the one-time reference, a callback is provided. This makes it a subscription. You need to disconnect and destroy the context consumer yourself. This example creates a custom `DocumentService` that consumes the Document Workspace Context. + +```javascript +import { UmbContextConsumer } from '@umbraco-cms/backoffice/context-api'; +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; + +export class DocumentService { + #workspaceContext; + #workspaceContextConsumer; + + constructor(hostElement) { + // Manually create the context consumer + this.#workspaceContextConsumer = new UmbContextConsumer( + hostElement, + UMB_DOCUMENT_WORKSPACE_CONTEXT, + (context) => { + if (context) { + // Context is available + console.log("I've got the document workspace context: ", context); + this.#workspaceContext = context; + } else { + // Context not yet available OR has been removed + // This can happen on initial creation or when unprovided + console.log("The document workspace context is gone, I will make sure my code disassembles properly."); + this.#workspaceContext = null; + } + } + ); + } -```typescript -import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + // Public method that should be called by the host element's connectedCallback + hostConnected() { + this.#workspaceContextConsumer.hostConnected(); + } -interface MyBaseContext { - foo: string; - bar: number; -} + // Public method that should be called by the host element's disconnectedCallback + hostDisconnected() { + this.#workspaceContextConsumer.hostDisconnected(); + } -interface MyPublishableContext extends MyBaseContext { - publish(); + destroy() { + // Manually clean up + this.#workspaceContextConsumer.destroy(); + } } - -const MY_PUBLISHABLE_CONTEXT = new UmbContextToken< - - MyContext, - MyPublishableContext ->("My.Context.Token", undefined, (context): context is MyPublishableContext => { - return "publish" in context; -}); ``` -**Implementation of Context Token Example:** +To use this service, the host element must call the lifecycle methods: -```typescript -const contextElement = new UmbLitElement(); -contextElement.provideContext( - MY_PUBLISHABLE_CONTEXT, - new MyPublishableContext() -); +```javascript +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { DocumentService } from './document-service.js'; -const consumerElement = new UmbLitElement(); -contextElement.appendChild(contextElement); -consumerElement.consumeContext(MY_PUBLISHABLE_CONTEXT, (context) => { +export default class MyElement extends UmbLitElement { + #documentService; - // context is of type 'MyPublishableContext' - console.log("I've got the context of the right type", context); -}); -``` + constructor() { + super(); + this.#documentService = new DocumentService(this); + } -This allows implementers to request a publishable context without needing to know the Type or how to identify the context. + connectedCallback() { + super.connectedCallback(); + // Notify the service that the host is connected + this.#documentService.hostConnected(); + } -In detail, the Context API will search for the first API that matches the alias `My.Context.Token`, and not look further. If the API meets the type discriminator, it will be returned, otherwise the consumer will never reply. + disconnectedCallback() { + super.disconnectedCallback(); + // Notify the service that the host is disconnected + this.#documentService.hostDisconnected(); + } +} +``` \ No newline at end of file diff --git a/16/umbraco-cms/customizing/foundation/context-api/context-api-fundamentals.md b/16/umbraco-cms/customizing/foundation/context-api/context-api-fundamentals.md new file mode 100644 index 00000000000..a2016991d9e --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/context-api/context-api-fundamentals.md @@ -0,0 +1,74 @@ +--- +description: >- + Learn about the Context API fundamentals, terminology, and how it enables + communication between elements in the Umbraco backoffice through hierarchy. +--- + +# Context API fundamentals +The Context API is a powerful communication system in Umbraco's backoffice. It enables elements to share data and functionality without tight coupling. This article covers the core concepts, terminology, and flow mechanisms you need to understand before working with contexts. + +Whether you're building custom property editors, workspace extensions, or complex UI components, understanding the Context API is essential. It provides a structured way to access shared state, services, and functionality throughout the element hierarchy. + +## What is the Context API? +The Umbraco backoffice is a collection of DOM elements like any web application. Elements can be anything: a button, a property editor, a section, a menu option, or a tree. These elements have a hierarchy and form the entire DOM tree that makes up the Umbraco application. + +The Context API in Umbraco is a communication system. It allows elements to share data and functionality through their hierarchy in a `context`. Parent elements can provide contexts that their descendant elements can request and use. + +When an element needs access to some data or functionality, it requests the appropriate context. It does this by using the context's identifier. The system finds the nearest provider up the element hierarchy. This creates loose coupling between elements. Descendants don't need direct references to their dependencies as they can declare what type of context they need and the system handles the connection. + +This approach is similar to dependency injection in managing dependencies automatically. However, the Context API works specifically through the element structure rather than a central container. For example, a custom property editor can request the `workspace context` to access information about the current document. Information that can be accessed includes the document's name, content type, or publication status. + +The Context API exists to solve common problems in complex user interfaces: + +* Avoiding prop drilling: Instead of passing data through multiple layers of components, child elements can directly request what they need. +* Loose coupling: Elements don't need direct references to their dependencies. This makes the codebase more modular and maintainable. +* Shared state management: Multiple elements can access and react to the same state without complex wiring. + +## Terminology +To understand the Context API, it's important to understand the terminology that is used in the rest of the documentation. + +### Context +An object that encapsulates both data and methods to interact with that data. This object can be provided to descending DOM elements. A context represents a specific capability or state that multiple elements might need to access. Examples include workspace context, content data, user permissions, or specialized services. Contexts encapsulate both data and methods, making them more than data containers. Unlike repositories, a context is always only available within the scope of a certain element and its descendants. + +### Context provider +An element that creates and makes a context available to its descending elements. The provider is responsible for the context's lifecycle. One element can provide multiple different contexts if needed. + +### Context consumer +Any element that requests and consumes a context provided by one of its ancestor elements. An element becomes a consumer by requesting a context. The element does not need to know which specific ancestor provides the context nor implement any special interfaces. The consuming element receives callbacks when the requested context becomes available or unavailable. This allows the element to react appropriately to changes in the element hierarchy. + +### Context Token +A unique identifier used to request a specific context. Context tokens serve as contracts between providers and consumers. They define exactly which context is being requested and ensure that the right provider responds. Using a context token prevents conflicts when multiple contexts might have similar names and makes clear what functionality is being shared. + +## Context consuming flow +Each DOM element can be a context provider. Each descendant in the DOM hierarchy can consume that context if desired. When an element wants to consume a context, the following happens: + +1. An element requests a context by a given Context Token. +2. The Context API dispatches an event that starts at the element that requested the context. The event bubbles up the DOM tree to each parent element until an element is found that responds to the event. +3. An instance of the context is provided back to the element that requested the context. + +![Context API Flow](images/umbraco_context_api_flow.png) + +If no context could be found and the event reaches the top-level element (the document), no context is consumed. + +## Common contexts +Although every element can be a context provider, the most important contexts are registered at specific hierarchy levels. These levels are also explicit extension points in the Umbraco manifest. + +The most common hierarchy levels to which the contexts can be registered are: + +* Global +* Section +* Workspace +* Property + +**Global contexts** are registered at the highest level and are always available anywhere in the backoffice. Examples of global contexts: +* `Notification context`: used for displaying notifications in the backoffice. This context is consumable in elements anywhere in the DOM tree. +* `Current user context`: has information about the currently logged in user. This context is consumable anywhere in the DOM tree. + +**Section contexts** are available in the context of a section. That is everything in the backoffice except the menubar. Examples of section contexts: +* `Section context`: provides information about the section, like path, alias, and label. +* `Sidebar menu section context`: holds information about the sidebar menu, like which menu is currently selected. + +**Workspace contexts** work on a workspace, the part of Umbraco that is next to the tree. Example for this level: +* `Workspace context`: holds information about the current entity being edited in the workspace. This holds minimal information about an entity and the entity type. There are specific workspace contexts per entity type. For instance, the `Document workspace context` for documents and `Media workspace context` for media. + +**Property contexts** are contexts that work at the property level. They can work on one or more property editors. An example is the clipboard functionality where blocks can be copied and pasted between block grids and block lists. Because these contexts are scoped at the property level, they are typically not consumed directly. \ No newline at end of file diff --git a/16/umbraco-cms/customizing/foundation/context-api/images/umbraco_context_api_flow.png b/16/umbraco-cms/customizing/foundation/context-api/images/umbraco_context_api_flow.png new file mode 100644 index 00000000000..adb44ad0f24 Binary files /dev/null and b/16/umbraco-cms/customizing/foundation/context-api/images/umbraco_context_api_flow.png differ diff --git a/16/umbraco-cms/customizing/foundation/context-api/provide-a-context.md b/16/umbraco-cms/customizing/foundation/context-api/provide-a-context.md index af871da36c4..690bf40bc80 100644 --- a/16/umbraco-cms/customizing/foundation/context-api/provide-a-context.md +++ b/16/umbraco-cms/customizing/foundation/context-api/provide-a-context.md @@ -7,7 +7,6 @@ description: >- # Provide a Context ## Provide a Context API - The recommended approach is to base your Context API on the `UmbContextBase` class, which provides automatic context registration. The following example shows how it's used: ```typescript