Getting "Home" ID from a "backofficeEntryPoint"

Hello - I have a back office entry point that excludes an extension, but also makes a call to the Content Delivery API to get some data which is passed to some functions.

I.e.

export const onInit: UmbEntryPointOnInit = async (host, extensionsRegistry) => {
	try {
		
		let data = await getContent(host, "/umbraco/delivery/api/v2/content/item/configuration");

		// Remove the Links panel from pages
		extensionsRegistry.exclude('Umb.WorkspaceInfoApp.Document.Links');

		// Colours
		getColours(data);

		// Fonts
		getFonts(data);

	} catch (error) {

		console.log("Error getting data", error);

	}
};

In my content tree I have multiple “Home” nodes, each of those having their own configuration files.

What I am trying to do is obtain a Site ID (the ID of the Home that is either selected / or closest parent to the current selected node).

My site set up is similar the the below.

  • Site 1 (Home)
    • About
    • Configuration
  • Site 2 (Home)
    • About
    • Configuration

My question is… Is it possible to use the context API to get ancestors of the current page?

In another one of my components I am using the UMB_ANCESTORS_ENTITY_CONTEXT, which allows access to getAncestors and returns an array of IDs, where the first item is always the “Home” node.

this.consumeContext(UMB_ANCESTORS_ENTITY_CONTEXT, (context) => {
    if(context) {
        this._siteId = context.getAncestors()[0].unique;
        this._getColours(this._siteId);
    }
});

I have tried using this, but the context does not exist :frowning:

I.e

host.consumeContext(UMB_ANCESTORS_ENTITY_CONTEXT, (context) => {
     if(context) {
          console.log(context.getAncestors());
     }
});

Thank you,
Rob

Hello Robert

you won’t be able to use UMB_ANCESTORS_ENTITY_CONTEXT inside a backoffice entry point because it isn’t attached to a workspace element in the DOM …. so that context simply doesn’t exist there.

Instead, you can consume UMB_ENTITY_WORKSPACE_CONTEXT and then use UmbDocumentTreeRepository to fetch the ancestors manually.

smth like this:

import { UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbDocumentTreeRepository, UMB_DOCUMENT_ENTITY_TYPE } from '@umbraco-cms/backoffice/document';

export const onInit: UmbEntryPointOnInit = async (host, extensionsRegistry) => {
  extensionsRegistry.exclude('Umb.WorkspaceInfoApp.Document.Links');

  host.consumeContext(UMB_ENTITY_WORKSPACE_CONTEXT, async (workspaceContext) => {
    if (!workspaceContext || workspaceContext.getEntityType() !== UMB_DOCUMENT_ENTITY_TYPE) {
      return;
    }

    const unique = workspaceContext.getUnique();
    if (!unique) return;

    const documentTreeRepository = new UmbDocumentTreeRepository(host);

    const { data: ancestors, error } = await documentTreeRepository.requestTreeItemAncestors({
      treeItem: { unique, entityType: UMB_DOCUMENT_ENTITY_TYPE }
    });

    if (error) {
      console.error('Failed to fetch ancestors', error);
      return;
    }

    // ancestors[0] is the root/home node.
    // If there are no ancestors, the current node is already the root.
    const homeId = ancestors?.[0]?.unique ?? unique;

    const data = await getContent(host, `/umbraco/delivery/api/v2/content/item/configuration?siteId=${homeId}`);
    getColours(data);
    getFonts(data);
  });
};

That way you’re basically recreating what UMB_ANCESTORS_ENTITY_CONTEXT would give you, but in a way that works from an entry point.

Hope that helps :+1:

1 Like

Hi @sm-rob and @BishalTimalsina12

Interesting topic, and if I read this right, you have it working as you expected in the ‘other component’. And your code seems right:

		this.consumeContext(UMB_ANCESTORS_ENTITY_CONTEXT, (context) => {
			console.log(context?.getAncestors()[0].unique);
		});

The ‘problem’ is that a Backoffice Entry Point is executed at the user’s entry of the backoffice.
— this happens before the user navigates to a Document.

As well, the Entry Point is in the scope(context) of the Application, so it has no way to know about Context APIs of a Worksoace(Document).

Additionally, it complicates things to have code that is initialized at startup, Instead I would recommend that you implement a Workspace Context, targeted for Documents.

To help you do just that, and get a better understanding of the Context API, I would recommend you read this article:

Replace the code of the example that sets a name with the code that retrieves the Root Document, then you should be good to go.

Notice how a Workspace Context is initialized every time you open a Document, meaning this also works with multiple documents being open at the same time. As the Workspace Contexts instance will be contextual to the Workspace.

You did not explain your goal of this customization, so whether there is an even better choice for this problem is hard for me to tell, but I hope you got what you needed to continue your journey.

Good luck :slight_smile:

3 Likes

Hello both (@BishalTimalsina12 & @nielslyngsoe).

Thank you both for your input! It’s certainly helped me on my journey and ultimately what I needed working… is working.

Whether it is the correct method, time will tell, but I’ve learnt many things along the way.

Thank you for the article, it was very useful and a good read!

For anyone else that might have a similar question to mine, here is what I am doing below.

Registering a “backofficeEntryPoint” via “umbraco-package.json”.

{
  "type": "backofficeEntryPoint",
  "name": "SM Core",
  "alias": "SM.Core",
  "js": "/App_Plugins/projectName/dist/my-backoffice-entry.js"
}

Exporting Manifest(s) (Inside “my-backoffice-entry.ts”)

export const manifests: Array<UmbExtensionManifest> = [
{
  type: "workspaceContext",
  name: "My Name",
  alias: "my.workspaceContext.example",
  api: () => import("./workspace-context.js")
}];

… an registering that “onInit”…

export const onInit: UmbEntryPointOnInit = async (host, extensionRegistry) => {
  extensionRegistry.registerMany(manifests);
};

Consuming some context(s) (Inside “workspace-context.ts”)**.
**
This is the part that I am not sure is correct. But it works.

export class MyWorkspaceExample extends UmbControllerBase {
  constructor(host: UmbControllerHost) {
    super(host);

    this.consumeContext(UMB_ANCESTORS_ENTITY_CONTEXT, async (context) => {
      if(!context) return;

      this.consumeContext(UMB_ENTITY_CONTEXT, async (entityContext) => {
        if (!entityContext) return;

          this.observe(context.ancestors, async (ancestors) => {
             
           let siteId: string | undefined;

           // I am checking to see if the ancestors array length is greater than 0. If we are in a Home/Root then the array is empty and we'll get the ID of the page via the entity context :(
           if(ancestors.length > 0) {
                        
             siteId = ancestors[0].unique;

           } else {
             
             const current = await entityContext.getUnique();
             siteId = current;
                        
           }

           if(!siteId) return;
           

           // Doing something with the Site ID.


         })
      });
    });
  }
}
export { MyWorkspaceExample as api };

@nielslyngsoe - I am certain there are many issues with the above approach, but as I get a better understanding of the Context API and what’s available and general processes - I can revisit.

Thank you both again!
Rob

Hi @sm-rob

Great to hear that you got something working.

So for this case, of having one thing that registeres other things, then you do not need to use a backofficeEntryPoint, but can use a ‘bundle’, that simplifies it a bit. Otherwise correct :+1:

Regarding the double consumption, I would split them into two separate consumptions. So each of them is calling the same method to handle your logic. In this manner:

this.consumeContext(UMB_ANCESTORS_ENTITY_CONTEXT, async (context) => {
      this.#ancestorContext = context;
      this.#handleContexts();
});
this.consumeContext(UMB_ENTITY_CONTEXT, async (context) => {
      this.#entityContext = context;
      this.#handleContexts();
});

#handleContexts() {
 this.observe(this.#ancestorContext?.ancestors, async (ancestors) => {
           if(!ancestors) return;
           if(!this.#entityContext) return;
             
           let siteId: string | undefined;

           // I am checking to see if the ancestors array length is greater than 0. If we are in a Home/Root then the array is empty and we'll get the ID of the page via the entity context :(
           if(ancestors.length > 0) {
                        
             siteId = ancestors[0].unique;

           } else {
             
             const current = await this.#entityContext.getUnique();
             siteId = current;
                        
           }

           if(!siteId) return;
           

           // Doing something with the Site ID.


         })
}

if this.observe gets undefined as its first argument, then it stops the observation. So though this is a detail that may not be relevant for your particular project. the point is now its able to disassemble the state observation it the context is no longer present.

I hope that helps with your further work :slight_smile:

1 Like

Thank you again, @nielslyngsoe ! Really appreciate the feedback/advice.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.