Intercept Interface Changes with Mutation Observer
DayBack offers many triggers that let you intercept and modify its behavior using App Actions and Event Actions. However, there are instances where you need to adjust the user interface before or immediately after an element is drawn and displayed to the user.
Examples include modifying the Event Popover or sidebar right after they open, or responding to the opening or closing of a utility panel or dropdown menu.
To handle these scenarios, you can use DayBack's dbk.observe()
helper function. This function is part of a suite of available functions you can call in any app action. The dbk.observe()
function allows you to monitor specific elements in the DOM and trigger custom code when certain criteria are met.
Watching for Changes
You can monitor a specified element for changes. For example, you might watch the calendar element for the appearance of the edit event popover. Once a specific condition is met, you can execute custom code. Starting an observer requires four parameters:
let observer = dbk.observe({ name: "uniqueNameForThisObserverInstance", watch: document.getElementById("The Dom Element We are Observering"), until: ".query-selector .condition-that-triggers .custom-code-injection", then: functionToRunWhenConditionIsMet }); function functionToRunWhenConditionIsMet(observer) { // The most important functions available include // stop(), destroy(), and restart() observer.stop(); // Stops further observation, if you only need // to run the first time something happens observer.destroy(); // Stops further observation, and destroys // named observer, in case you want to reuse // the same name in the future. observer.restart(); // If you expect your injection to go away, // you can restart observeration on the same // node. // You also have access to the entire node tree and the last // mutation that triggered the runtime condition observer.lastMutation; observer.mutationList; // If you used a query selector for your 'until' clause, and you want // to retreive it, you can reference it using the foundNode parameter observer.foundNode; }
Example Scenario:
As a simple example, you could watch the sidebar for the text filter to be rendered, and then change its background color to red. To do this simple change, you can add the following code to an After Calendar Rendered action.
// Change Text Filter in sidebar to have a red background let observerInstance = dbk.observe({ name: "modifySidebar", watch: document.getElementById("sidebar"), until: "#sidebar .filters-popover-container text-filter .header-block-content", then: runCustomCode, }); function runCustomCode(observer) { // Check to make sure we haven't made this modification already if (!observer.foundNode.classList.contains("observerModified")) { // Stop observing the filter for new mutations, as we are // going to add our own mutations observer.stop(); // Add a CSS class to the node, indicating we've modified it observer.foundNode.classList.add("observerModified"); // Change the background color observer.foundNode.style.backgroundColor = "red"; // Start observering the sidebar again, since our modification // may disappear if the user closes and reopenes the sidebar. observer.restart(); } }
Configuration Options
Here are the basic configuration parameters for the dbk.observe()
function:
name: "uniqueName"
Specify a unique short string that identifies the unique observer instance. Naming your observers prevents the DayBack from starting more than one observer with the same name. This could potentially happen if you start observers from multiple app actions or event actions that may run more than one time in a row. Calling observer.destory()
will clear out a previously created instance, and will allow you to create a new observer using the same name in the future.
watch: querySelector string literal or node object
Specify the DOM element that is being observed. This has to be a querySelector string or a valid node object obtained from document.querySelector()
or document.getElementById()
call:
let observerInstance = dbk.observe({ name: "modifySidebar", watch: "#sidebar" // or document.querySelector('#sidebar') until: "#sidebar .filters-popover-container text-filter .header-block-content", then: runCustomCode, });
If you specify a node, the function will immediately start watching that node for the stop condition specified in the until
parameter.
If you specify a querySelector string, the dbk.observe()
function will first check if the node exists in the DOM. If found, it will start observing that node immediately. If the element does not exist, the observer will first observe the root calendar node (#calendar
) and wait for the creation of the node specified by the querySelector string. When the node is found, it will start observing that node for the stop condition.
Using string literals allows you to start your mutation observers before the calendar is fully loaded, as the observed node doesn't need to exist before observer instantiation. This method is as effective as using a nested sequence of observers that first observe the calendar node for a sub-node and subsequently start a new observer on this sub-node. The use of a querySelector string is a convenient way to reduce the number of observer objects you need to create.
until: querySelector string literal or conditionCheckFunction
Specify the stop condition you want to watch for. The most common stop condition is a query selector returning true, indicating that DayBack has added your desired object to the DOM. Alternatively, you can specify a custom function that returns true when a stop condition is met. You can use this to check whether an event
or editEvent
object contains a certain value that constitutes a valid stop condition.
then: functionToRun
The function to run when the stop condition is met. The function will be passed the observer object as its only parameter. The object contains the configuration collection, including the stop
, start
, restart
, and destroy
functions, as well as the last matched mutation (observer.foundNode
).
Optional Runtime Parameters
The following parameters provide advanced flow control for your mutation observer and can be optimized to reduce CPU cycles and tweak mutation observer performance. All of these have defaults, so you may not need to use these settings.
checkStopConditionOnStart: true
By default, the observer will immediately check the stop condition when it starts. This is useful when the element might already be present in the DOM, allowing the custom code to run immediately. Alternatively, you may start the observer and then trigger a DayBack action by initiating a click event on an element. For example, simulating a mouse click on the gear icon in the Event Popover to open a utility panel you wish to modify. In this scenario, the on-screen elements are not drawn yet but will be soon after the click event.
whenFoundStopObserving: false
By default, the observer will continue monitoring for future mutations even if a mutation is found, unless the stop condition is met and you call observer.stop()
within the then
function. Always call observer.stop()
inside your then
function if you are injecting code into the observed node to avoid an infinite loop of mutation changes. You can use observer.restart()
if you wish to continue observing the node after applying changes.
whenFoundStopProcessing: true
Each DOM change will result in an array of all mutations. If we find a valid stop condition, we very likely do not need to loop through the remaining mutations. Leave this set to true to save CPU cycles.
autoStart: true
startDelay: 0
By default, we will start observing right away and will not wait. You can optionally have the mutation observer start after a delay of a set number of milliseconds. Alternatively, you can start your observer manually after you have created it, as follows.
let observer = seedcodeCalendar.get("dbkObserver").new({ name: ..., watch: ..., until: ..., then: ..., autoStart: false }); ... do stuff ... observer.start();
debug: false
If set to true
, the observer will print debug statements to the JavaScript console at each major stage of mutation observation.
options: { attributes: false, childList: true, subtree: true}
The options
object specifies what type of DOM changes should trigger a condition check. The most common scenario is that we want to monitor changes in immediate child nodes, as well as the subtree
of nodes. You may only need to change this if you want to observe an attribute of a node rather than the entire childList
or subtree
.
mutationType: "childList" || "attribute"|| "both"
By default, the condition check runs on childList
mutation types only, regardless of the options
specified. You can override this by specifying the mutationType
that should trigger a stop condition check.
Example Extension that use the Mutation Observer
Here are a few examples from our extensions library illustrating how to use the DayBack mutation observer library to override the user interface.
This After Calender Rendered app action watches for the opening and closing of the sidebar and for changes to the tabs that are selected when a user navigates to different portions of the sidebar. When the appropriate sidebar condition is true, the action adds several custom buttons and dropdowns. |
|
Add Buttons to the Event Popover This On Event Click event action watches for the Edit Event popover to load. Once loaded, it modifies the button bar to add custom buttons next to DayBack's standard Delete and Save & Close buttons. |