Friday, April 21, 2023

Unlocking the Potential of Sitecore Content Hub: Filtering Locked Assets with a Custom Azure Function

Introduction

Sitecore Content Hub is a powerful and flexible platform that helps organizations manage their digital assets efficiently. However, like any software, it may have certain limitations or quirks that users need to work around. In this blog post, we'll explore a common problem faced by Sitecore Content Hub users and provide an elegant solution that leverages the power of Azure Functions to filter locked assets from download orders.

The Problem

Imagine you're managing a large repository of digital assets in Sitecore Content Hub. You need to exclude assets marked as "isLocked = True" from download results and zip packages. However, you notice that the default filters on the "Download Order" page don't always produce consistent results. Further investigation reveals that in Sitecore Content Hub 4.2, the Selection on M.Asset using Order is unavailable, causing it to default to Archive.

The Solution

Fear not, dear content managers and developers! We've got a custom solution that will not only resolve the issue but also make your Sitecore Content Hub experience smoother. We'll create a custom filter to remove Assets with the field value "isLocked" set to True by modifying the entity through entity management ("/admin/entitymgmt/entity/51935"). We'll copy the default "System settings" and paste them into "Custom Settings" before saving the search component. Then, we'll add our filter for locked items. This workaround solves the problem, but we can make it even better by employing an Azure Function to ensure that locked items are not included in the zip output.

The Code: Unleashing the Power of Azure Functions

Our custom Azure Function, H_RemoveLockedAssetsFromDownloadOrder, is designed to filter out locked assets from download orders. It inherits from BaseTrigger and is triggered by an Action in response to a specific event. The function retrieves the target entity and its related job description, extracting the download order job description configuration as a JObject. It then iterates through the JArray in reverse, checking the lock status of each asset. If an asset is locked, it is removed from the array. The modified JArray is saved back into the job description configuration, and the processing job is resumed. By leveraging the power of Azure Functions, our custom solution provides a more reliable and efficient way to manage assets in Sitecore Content Hub. It's a win-win situation for both developers and managers alike.

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

using Microsoft.Azure.WebJobs;

using Microsoft.Azure.WebJobs.Extensions.Http;

using Microsoft.AspNetCore.Http;

using Microsoft.Extensions.Logging;

using Sitecore.CH.Base.Services;

using Stylelabs.M.Sdk.Contracts.Base;

using Stylelabs.M.Framework.Essentials.LoadConfigurations;

using Sitecore.CH.Implementation.Services;

using System.Net.Http;

using Newtonsoft.Json.Linq;

using Stylelabs.M.Framework.Essentials.LoadOptions;

using System.Text;

using Microsoft.Identity.Client;


namespace Sitecore.CH.Implementation.AzFunctions.Functions

{

    public class H_RemoveLockedAssetsFromDownloadOrder : BaseTrigger

    {

        public H_RemoveLockedAssetsFromDownloadOrder(IMClientFactory mClientFactory,

                                        ILoggerService<H_SampleTriggerFunction> logger,

                                        ILoggingContextService loggingContextService) : base(mClientFactory, logger, loggingContextService)

        {

        }


        [FunctionName("H_RemoveLockedAssetsFromDownloadOrder")]

        public Task<IActionResult> EntryPoint([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)

        {

            return base.Run(req);

        }


        protected override async Task<IActionResult> Execute(HttpRequest req, IEntity entity)

        {

            _logger.LogInformation("Starting execution of H_RemoveLockedAssetsFromDownloadOrder...");


            var targetEntity = await _mClientFactory.Client.Entities.GetAsync(entity.Id.Value);

            await targetEntity.LoadRelationsAsync(new RelationLoadOption("JobToJobDescription")).ConfigureAwait(false);

            IParentToOneChildRelation jobToJobDescription = targetEntity.GetRelation<IParentToOneChildRelation>("JobToJobDescription");


            var id = jobToJobDescription.GetId().Value;

            IEntity jobDescription = await _mClientFactory.Client.Entities.GetAsync(id);


            // get download order job description configuration

            var config = await jobDescription.GetPropertyValueAsync("Job.Configuration"as JObject; ;

            JArray jarrayObj = config["entity"].ToObject<JArray>();


            _logger.LogInformation($"Initial assets count: {jarrayObj.Count}");



            // Remove the locked assets

            for (int i = jarrayObj.Count - 1; i >= 0; i--)

            {

                var assetID = jarrayObj[i].Value<long>();

                var asset = await _mClientFactory.Client.Entities.GetAsync(assetID);


                bool isLocked = await GetAssetLockStatus(asset);


                if (isLocked)

                {

                    _logger.LogInformation($"Removing locked asset with ID: {assetID}");

                    jarrayObj.RemoveAt(i);

                }

            }


            _logger.LogInformation($"Filtered out locked assets. Remaining assets count: {jarrayObj.Count}");


            config["entity"] = jarrayObj;

            jobDescription.SetPropertyValue("Job.Configuration", config);

            await _mClientFactory.Client.Entities.SaveAsync(jobDescription);


            // resume processing job

            targetEntity.SetPropertyValue("Job.State""Pending");

            await _mClientFactory.Client.Entities.SaveAsync(targetEntity);


            _logger.LogInformation("Execution of H_RemoveLockedAssetsFromDownloadOrder completed.");


            return new OkResult();

        }

        private async Task<bool> GetAssetLockStatus(IEntity asset)

        {

            await asset.LoadPropertiesAsync(new PropertyLoadOption("Dml.IsLocked")).ConfigureAwait(false);

            var isLocked = await asset.GetPropertyValueAsync<bool>("Dml.IsLocked");

            return isLocked;

        }

        protected override IEntityLoadConfiguration GetEntityLoadConfiguration()

        {

            return EntityLoadConfiguration.Default;

        }


        protected override Task<bool> IsValid(IEntity entity)

        {

            return Task.FromResult(true);

        }

    }

}

Conclusion

In the ever-evolving world of digital asset management, it's crucial to have the flexibility to work around limitations in software platforms. Our custom Azure Function solution for filtering locked assets in Sitecore Content Hub is a testament to the power of innovative thinking and the potential of cloud-based serverless computing. So, whether you're a developer seeking a more efficient way to manage assets or a manager looking for a seamless Sitecore Content Hub experience, this custom solution is a game-changer. With Azure Functions at your disposal, the possibilities are truly endless. Happy asset management!


Thursday, March 2, 2023

Use Visual Studio Code to debug the React components that you create for Sitecore Content Hub DAM 4.2.


To set up debugging, you will need to create a webpack configuration that generates a source map, which maps the compiled code back to the original source code. This allows you to use the Chrome DevTools or the Visual Studio Code debugger to debug your code as if you were debugging the original source.

Here are the steps to set up debugging for your Sitecore Content Hub DAM 4.2 React components:

Install the source-map-loader package:

npm install --save-dev source-map-loader

Update your webpack configuration to generate a source map:

const path = require('path');

module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', 'source-map-loader'],
},
],
},
};

In this example, we set the devtool property to 'source-map' to generate a source map. We also added the source-map-loader to the use property of the rules array, which instructs webpack to use the loader to load the source map.

Run webpack to generate the bundle:

webpack --mode development

This generates a bundle.js file in the dist directory.

Create a launch configuration for Visual Studio Code:

{
"version": "0.2.0",
"configurations": [
{
"name": "Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///*": "${webRoot}/*",
"webpack:///src/*": "${webRoot}/src/*",
"webpack:///./src/*": "${webRoot}/src/*",
"webpack:///*": "*",
"webpack:///./~/*": "${webRoot}/node_modules/*"
}
}
]
}

In this example, we create a launch configuration for Chrome, which opens the Sitecore Content Hub DAM 4.2 application in a new Chrome window. We set the webRoot property to the workspace folder, which is the directory that contains the bundle.js file. We also add the sourceMapPathOverrides property to map the source maps to the original source files.

Start the Sitecore Content Hub DAM 4.2 application in development mode:

webpack --mode development

This starts the Sitecore Content Hub DAM 4.2 application in development mode on port 3000.

Start the Visual Studio Code debugger:

In Visual Studio Code, open the Debug panel (Ctrl+Shift+D), select the Chrome configuration, and click the green arrow to start the debugger. This should open a new Chrome window that loads the Sitecore Content Hub DAM 4.2 application. You can now use the Chrome DevTools or the Visual Studio Code debugger to debug your React components.

Breakdown of how the launch configuration interacts with Sitecore Content Hub:

The launch configuration specifies the type of debug configuration to use (in this case, the Chrome debug configuration).

The request property in the launch configuration specifies that the debugger should launch a new instance of Chrome with a debugger attached.

The url property in the launch configuration specifies the URL of the Sitecore Content Hub DAM 4.2 application that you want to debug.

The webRoot property in the launch configuration specifies the root directory of your project, which is used as the base directory for resolving source maps.

The sourceMapPathOverrides property in the launch configuration specifies the mappings between the source map URLs and the corresponding local file paths.

When you start the debugger with the launch configuration, Visual Studio Code launches a new instance of Chrome and connects to the Sitecore Content Hub DAM 4.2 application running on your local machine. Visual Studio Code uses the source maps generated by webpack to map the compiled code back to the original source code, allowing you to debug your React components as if you were debugging the original source code.

In summary, the launch configuration is used to specify the settings for launching the debugger and connecting to the Sitecore Content Hub DAM 4.2 application, while the source maps generated by webpack are used to map the compiled code back to the original source code, enabling you to debug your React components.

Revolutionize Your Asset Management with Sitecore Content Hub DAM 4.1 API: Everything You Need to Know



The API for Sitecore Content Hub DAM 4.1 provides a powerful set of tools that can be used to interact with the DAM, allowing you to upload, search for, and modify assets.

One of the most useful features of the API is the ability to search for assets. By entering a search term, you can quickly locate the assets you need. You can also filter your search results based on specific criteria, such as file type, creation date, or metadata. This feature is particularly useful for large DAMs with thousands of assets, as it saves time and streamlines the asset management process. An example URL for searching for assets using the API is:

https://<DAM host>/api/search/v1/query?searchTerm=<search_term> &searchIn=<search_in_field>&filters=[{"field":"<filter_field>","values":["<filter_value>"]}] &skip=<skip>&take=<take>


Another useful feature of the API is the ability to retrieve metadata for assets. This can be done by simply entering the asset ID into the API call. Metadata provides important information about an asset, such as its file size, creation date, and any custom metadata that has been added. With this information, you can quickly determine if an asset is suitable for your needs, and you can also use it to organize your assets more effectively. An example URL for retrieving metadata for an asset using the API is:

https://<DAM host>/api/assetmanagement/v1/assets/<asset_id>/metadata


If you need to create new renditions of an asset, the API can help with that as well. Simply enter the asset ID into the API call, along with the desired format and any metadata you want to add. The API will create the new rendition and add it to the asset's metadata. An example URL for creating a new rendition for an asset using the API is:

https://<DAM host>/api/assetmanagement/v1/assets/<asset_id>/renditions


with a payload, example:

{
"renditionName": "<rendition_name>",
"renditionFormat": "<rendition_format>",
"metadata": {
"<metadata_key>": "<metadata_value>",
"<metadata_key>": "<metadata_value>"
}
}


Finally, the API provides access to the audit trail events, which can be used to track changes made to assets. This feature is particularly useful for compliance purposes, as it allows you to see who made changes to an asset and when those changes were made. An example URL for retrieving audit trail events using the API is:

https://<DAM host>/api/audit/raw/query?eventTypes=<event_type>&fullText=<search_text>


Overall, the Sitecore Content Hub DAM 4.1 API is a powerful tool for managing digital assets. Whether you need to search for assets, retrieve metadata, create new renditions, or track changes, the API provides a straightforward way to interact with the DAM. By leveraging the API, you can save time, streamline your workflows, and ensure that your assets are organized and easy to manage. So why not give it a try and see how it can help you manage your digital assets more effectively?

Sunday, February 19, 2023

Advanced Techniques for Including JavaScript and CSS Files in Sitecore Content Hub


Just as a chef needs to add different ingredients to create a delicious dish, as a developer, you may need to include custom JavaScript and CSS files in your Sitecore Content Hub implementation. These files may contain functions, styles, or other code that you want to use across multiple pages or components.

In this article, we'll show you how to include these essential ingredients in your Sitecore Content Hub dishes and determine the position to inject them in the page header. This approach will help you keep your recipe organized, reduce duplication, and make it easier to maintain your custom code.

Step 1: Upload your files as assets

The first step is to upload your custom JavaScript and CSS files as assets in Sitecore Content Hub. You can do this by navigating to the Digital Asset Management section of your Content Hub instance and creating a new asset. Upload your JavaScript or CSS file, add any metadata or tags as needed, and save the asset.

Make a note of the asset ID for each file. You'll use this ID to fetch the asset and include it in your pages.

Step 2: Include your files in your pages

To include your JavaScript and CSS files in your pages, you'll need to use the getAsset function. This function fetches the asset by its ID, creates a new script or link element with the appropriate attributes, and inserts the element into the page header.

Here's an example of how to include a JavaScript file in your page:

const jsAssetId = "976160"; // replace with your asset ID
const position = 1; // replace with the desired position in the header, or remove for default position

$(document).ready(function() {
if (!jsAssetId || jsAssetId.length === 0) {
return;
}
getAsset(`/api/entities/${jsAssetId}`, 'downloadOriginal',position, type);
});


This example fetches the asset by its ID using the getAsset function, creates a new script element with the src attribute set to the asset URL, and inserts the element into the page header. The position parameter specifies the position to insert the element, or leave it undefined for the default position at the end of the header.

Here's an example of how to include a CSS file in your page:

const cssAssetId = "976169"; // replace with your asset ID
const position = 1; // replace with the desired position in the header, or remove for default position

$(document).ready(function() {
if (!cssAssetId || cssAssetId.length === 0) {
return;
}
getAsset(`/api/entities/${cssAssetId}`, 'downloadOriginal',position, type);
});

getAsset function

We have extended the getAsset function described in this blog post read blog post to handle the injection of JavaScript and CSS files. 


window.getAsset = async function getAsset(endpoint, rendition, position, type) {
try {
const response = await $.ajax({
url: endpoint,
method: 'GET',
});
let href = null;
if (response[rendition] && response[rendition][0] && response[rendition][0].href) {
href = response[rendition][0].href;
} else if (response.renditions && response.renditions[rendition] && response.renditions[rendition][0] && response.renditions[rendition][0].href) {
href = response.renditions[rendition][0].href;
} else if (response.original && response.original[0] && response.original[0].href && !type) {
// Use the default rendition if the requested rendition is not found and the type is not specified
href = response.original[0].href;
}
console.log("Include: " + href);
if (href && type) {
let asset = null;
if (type === "css") {
asset = document.createElement("link");
asset.rel = "stylesheet";
asset.href = href;
} else if (type === "js") {
asset = document.createElement("script");
asset.src = href;
}
const head = document.getElementsByTagName("head")[0];

if (position !== undefined) {
const existingElement = head.children[position];
head.insertBefore(asset, existingElement);
} else {
head.appendChild(asset);
}
}
return href;
} catch (error) {
console.error(`An error occurred while fetching the asset from '${endpoint}': ${error}`);
return null;
}
}

window.getAsset is a way of defining a function that is accessible from anywhere in your code, regardless of the scope in which it was defined.

In JavaScript, the global window object is always present in the browser environment and provides a way to define properties and functions that are accessible throughout your code. By defining getAsset as a property of the window object (window.getAsset), you are essentially making it a global function that can be called from anywhere in your code, even if it's defined within a smaller, more contained scope.

Using window.getAsset can be useful in situations where you need to define a function that needs to be accessed from multiple places in your codebase or across different files. It can also help to make your code more modular and easier to maintain, as it allows you to keep related functions and variables grouped together in logical, self-contained units.

Conclusion

By following this approach, you can include custom JavaScript and CSS files in your Sitecore Content Hub implementation and determine the position to insert them in the page header. This can help you keep your code organized, reduce duplication, and make it easier to maintain your custom code.

Make sure to upload your files as assets in the Digital Asset Management section of your Content Hub instance and make a note of the asset IDs. Then, use the getAsset function to fetch and include the assets in your pages. You can specify the position to insert the elements in the header, or leave it undefined for the default position at the end of the header.

We hope this article helps you include custom JavaScript and CSS files in your Sitecore Content Hub implementation. Let us know if you have any questions or feedback.

From SDL Tridion to Sitecore Content Hub DAM: Our Chef's Journey of Challenges and Rewards


Making a career transition is never easy, but sometimes it's the best thing you can do to advance your career and expand your skill set. This was the case for Our Chef, who spent 10 years working with SDL Tridion CMS before making the switch to Sitecore Content Hub DAM 1.5 years ago. In this blog post, we'll explore Our Chef's journey, the challenges he faced, and the rewards he's experienced as a Sitecore Content Hub DAM developer.

Our Chef was initially apprehensive about the move from SDL Tridion CMS to Sitecore Content Hub DAM. He was used to working with a content management system that had a narrow focus, while Sitecore Content Hub DAM required him to develop a more holistic understanding of how digital asset management fits into the broader context of content strategy and marketing.

At first, it was a bit overwhelming for Our Chef to have to learn new technology and processes, but with time and effort, he quickly adapted to the new environment. He discovered that Sitecore Content Hub DAM offered him a more comprehensive set of tools to work with, which allowed him to be more creative and efficient in his work. He found that Sitecore Content Hub DAM made it easier to collaborate with other teams, as the platform provided a centralized location for all digital assets and a streamlined process for asset review and approval.

One of the biggest challenges of the transition was adjusting to the level of automation that Sitecore Content Hub DAM provides. Compared to SDL Tridion CMS, Sitecore Content Hub DAM is more automated, which meant that Our Chef had to learn how to write automated tests and work with APIs. He also had to learn new programming languages and how to use the latest web development tools.

Despite the challenges, the transition to Sitecore Content Hub DAM has been a rewarding one for Our Chef. He's been able to work on more complex projects that require a higher level of technical expertise, which has helped him grow professionally. He's also been able to collaborate with other teams and departments, which has given him a broader perspective on how digital assets fit into the larger picture of content strategy and marketing.

Conclusion

Making the transition from SDL Tridion CMS to Sitecore Content Hub DAM requires hard work and dedication, but it's a journey that can lead to great rewards. For Our Chef, the switch has provided new challenges and opportunities to grow and develop professionally.

If you're considering a similar transition, don't be afraid to take the leap and explore the exciting possibilities that Sitecore Content Hub DAM can offer.


Thursday, February 16, 2023

From Code to Cuisine: A Day in the Life of a Sitecore Content Hub DAM Chef


Imagine you're a Sitecore Content Hub DAM developer, and your latest project involves working with the Restaurant of Mistaken Orders. The restaurant has hired you to help manage their digital assets, including the images and videos they use on their website and social media channels. As the restaurant's go-to technology expert, it's your job to ensure that everything runs smoothly and that their online presence is consistent with their brand image.

At 9:00 am, you arrive at your office and start your day with a cup of coffee. You check your email, and there it is: a new requirement from the Restaurant of Mistaken Orders. Their marketing team wants all social media assets to be reviewed and approved by the marketing department before being published. It's your job to make it happen.

At 9:30 am, you dive into the code, creating a new workflow in Sitecore Content Hub DAM that requires the review and approval of social media assets. It's a complex task, but you know that you need to get it right to ensure the success of the restaurant's online presence.

At 11:00 am, you create a custom validation rule that checks for specific brand guidelines. The restaurant's visual identity is critical to their success, so this is an essential step to ensure that all assets meet their branding standards.

At 12:30 pm, it's time for lunch. You could go out to eat with your colleagues or bring your own food from home. But today, you're feeling inspired by the Restaurant of Mistaken Orders, so you decide to order their signature dish: a miso-glazed salmon that's both tasty and satisfying.

At 1:30 pm, you start working on a custom report that provides the marketing team with detailed information on the status of social media assets in the validation process. The report is a crucial component of the workflow, as it helps the team to stay on top of the review process and ensure that assets are published on time and on-brand.

At 3:00 pm, you test the workflow and validation rule that you created. It's always nerve-wracking when you're testing a new system, but you're confident that you've done everything right. Sure enough, everything works as expected, and you're able to sign off on the project with confidence.

At 4:00 pm, you wrap up for the day. You've accomplished a lot, and you're proud of the work you've done for the Restaurant of Mistaken Orders. You're already looking forward to the next challenge that the restaurant will throw your way.

In conclusion, a career as a Sitecore Content Hub DAM developer is an exciting and rewarding opportunity for anyone with a passion for technology, problem-solving, and collaboration. By working closely with stakeholders like the Restaurant of Mistaken Orders, you can help ensure that their digital assets are always on-brand and of high quality. Just like the chefs at the restaurant, you need to pay attention to detail, anticipate problems, and deliver high-quality results to ensure that the digital assets are consistent with the restaurant's image and goals.

Wednesday, February 15, 2023

Get Your Video and Subtitle Fix with Sitecore Content Hub DAM - A Tasty Technical Treat



Welcome to the "Restaurant of Mistaken Orders" blog! Today, we're serving up a technical dish with a side of humor.

Our dish for today is a JavaScript function that can fetch any asset, not just videos and subtitles. It's a generic function that you can reuse in your own recipes.

Let's take a closer look at the ingredients in this code:

function getAsset(endpoint, rendition) {
return $.ajax({
url: endpoint,
method: 'GET',
})
.then(asset => {
if (asset && asset[rendition] && asset[rendition][0] && asset[rendition][0].href) {
return asset[rendition][0].href;
} else if (asset && asset.renditions && asset.renditions[rendition] &&
asset.renditions[rendition][0] && asset.renditions[rendition][0].href) {

return asset.renditions[rendition][0].href;
} else {
return null;
}
})
.catch(error => {
console.error(`An error occurred while fetching the asset from '${endpoint}': ${error}`);
return null;
});
}

The getAsset function takes two parameters: endpoint and rendition. The endpoint is the URL of the asset you want to fetch, and the rendition is the specific format of the asset, such as video_mp4 or webvtt.

The function uses the $.ajax method to fetch the asset from the endpoint. The $.ajax method returns a Promise that resolves to the asset data.

Once the asset is fetched, the function checks if the asset has a rendition property and if it has a href property. If it does, the function returns the href property, which is the public URL of the asset. If the asset doesn't have the required properties, the function returns null.

In case of an error while fetching the asset, the function logs an error message to the console and returns null.

Next, we have the following code:

var currentUrl = window.location.href;
var assetId = currentUrl.split("/").pop().split("?")[0];

$(document).ready(async function() {
if (!assetId || assetId.length === 0) {
return;
}

const videoHref = await getAsset(`/api/entities/${assetId}`, 'video_mp4');
if (videoHref) {
console.log(`Video URL: ${videoHref}`);
}

const relationHref = await getAsset(`/api/entities/${assetId}/relations/AssetToSubtitleAsset`, 'children');
if (relationHref) {
const subtitleAssetId = relationHref.split("/").pop().split("?")[0];
if (subtitleAssetId && subtitleAssetId.length > 0) {
const subtitleHref = await getAsset(`/api/entities/${subtitleAssetId}`, 'webvtt');
if (subtitleHref) {
console.log(`Subtitle URL: ${subtitleHref}`);
}
}
}
});

The `currentUrl` variable holds the current URL of the page, and the `assetId` variable is the ID of the asset we want to fetch. 

The code inside the `$(document).ready` function is executed when the document is ready. The function first checks if the `assetId` variable is set and if it's not empty. If it's not set or empty, the function returns without doing anything. 

Next, the function uses the `getAsset` function to fetch the video asset and the subtitle asset. The `videoHref` variable holds the public URL of the video asset, and the `subtitleHref` variable holds the public URL of the subtitle asset. If the URLs are set, the function logs the URLs to the console. 

And that's it! With this code, you can easily get the public URL of any asset in Sitecore Content Hub DAM. 

So, the next time you receive an order for a video with subtitles, don't worry! Just whip up this code and serve it up like a pro! 

Bon appétit!

Sunday, February 12, 2023


Welcome to the "Restaurant of Mistaken Orders", where our Chef serves up the finest code snippets for developers. Today, the Chef is proud to present a new creation that's been a lifesaver for many developers, and it's called waitForElm.

Let's take a closer look at the waitForElm recipe created by our Chef and see how it works under the hood.

The function starts by returning a Promise, which is a key ingredient in handling asynchronous operations in JavaScript. Promises are used to represent the eventual completion or failure of an asynchronous operation. In this case, the Promise represents the completion of finding the element we're looking for.

The Promise constructor takes in a function as an argument, which will be executed immediately. This function is called a resolver, and its purpose is to determine when the Promise should resolve. In our case, the resolver checks if the element we're looking for is already present in the document. If it is, the Promise resolves immediately with the element.

If the element is not found, we create a MutationObserver, which is an object that can be used to observe changes in the DOM (Document Object Model). The observer is passed a callback function that will be called whenever a mutation occurs in the document. In our case, the callback function checks if the element we're looking for is present in the document. If it is, the Promise resolves with the element, and the observer is disconnected.

A breakdown of the code:

// Use the function to wait for the external component to load

waitForElm('.component-container').then(elm => {
        console.log('Cook up some delicious code with the component');
});

//waitForElem function

function waitForElm(selector) {

    return new Promise(resolve => {

        // Check if the element is already present in the document

        if (document.querySelector(selector)) {

            return resolve(document.querySelector(selector));

        }

        // Create a MutationObserver to continuously monitor the document

        const observer = new MutationObserver(mutations => {

            // Check if the element is present in the document

            if (document.querySelector(selector)) {

                // Resolve the Promise with the element

                resolve(document.querySelector(selector));

                // Disconnect the observer

                observer.disconnect();

            }

        });

        // Start observing the document
        observer.observe(document.body, {
            childList: true,
            subtree: true

        });
    });
}

The observe method takes in two arguments: the target node to observe and an options object. In this case, we're observing the document.body node and setting the childList and subtree options to true. This means that the observer will be notified of changes to the body of the document, including changes to its children and descendants.

So, now you have a better understanding of how the waitForElm recipe works. It's a simple but powerful tool that can help you write more efficient and reliable code when working with dynamic web pages and external components.

In conclusion, the waitForElm function is a must-have recipe in every developer's kitchen. Whether you're working with Sitecore Content Hub or any other dynamic web platform, this function can save you time and effort by ensuring that you can interact with elements only when they are ready. So, next time you're working on a project that requires waiting for elements to load, remember the waitForElm function created by our Chef, and avoid the frustration of mistaken orders!