@linked-planet/loader-tools
v0.9.0
Published
Tools for to loader apps and observe fields
Keywords
Readme
Loader-Tools Library
This library is to ease the replacement of fields in the Jira UI / Service Desk UI and also contains a loader to easily load custom app modules.
Usage
Installation and Setup
npm i -D @linked-planet/loader-tools
It is important that the final bundle also includes all requirements as Jira bundles everything in one huge batch.js and cannot load outside modules.
Either use the AppLoader to load an app directly, use the Observer tools to observe and handle field changes, or use registerReplaceElementObserver or replaceElement to replace HTMLElements in pages.
At first, a plugin definition is required, which follows the PluginSettings type:
export const pluginSettings: PluginSettings = {
pluginName: "plugin-name",
pluginGroupId: "com.linked-planet.plugin.jira"
}The plugin settings and the app name are used by the App Loader to load the app, and by the Config tools to get the REST API URL, which can be retrieved using Config.getRestApiBaseUrl(pluginSettings), and looks like:
const restApiBaseUrl = `${window.location.protocol}//${window.location.host}/rest/${pluginSettings.pluginName}/1.0`BatchJS and Jira Resources
The ultimate goal is to produce a Javascript Loader bundle which can be bundled by Jira. This means the Javascript bundle is copied by the plugin package process to target/classes/js and included from there in the batch.js of Jira.
This has certain requirements:
- must be a commonJS bundle
- is not allowed to have any exports
Tools
Several categories of tools are available.
Configs
The Config tools provide the option to fetch and cache configs from a set REST endpoint and provide that globally by using getProjectConfig with the help of the plugin settings, which are used to determine the correct endpoint address for the Jira plugin. The REST baseURL can also be obtained by getRestApiBaseUrl. In many cases we do not need a project config in the observer and app loading.
App Loader
The app loader loads an application. The method for it is initFrontendApp, and it requires the plugin settings and the app name. It automatically determines which mode the app runs in (dev, dev-productionbundle, production).
Optionally, a starter function can be provided, which is used to start the app. This is only required if the app does not start when the bundle loading is complete.
Observer
The observer tools are a simple library to ease the creation of MutationObservers on DOM elements, and to organize and handle callbacks used on mutation events of observed elements.
A simple use case could be:
Observer.registerObserverCallback("#content", () => console.log("content was modified"))
Observer.registerObserverCallback("#content", () => console.log("content was modified, callback 2"))
Observer.registerObserver("#content", {
childList: true,
subtree: false
})
This can be used in combination with the App Loader to replace Jira fields with custom apps.
Handling Of Fields
Jira recreates elements, which renders connected mutation observers useless and potentially leaks memory. A way around this is to use a mutation observer on a high-level element (i.e. body), which does not get recreated, and re-register the mutation observers using the corresponding selectors of the elements. The Observer library will automatically disconnect old mutation observers and create new ones if the underlying element has changed.
Replacing Fields
In most cases we want to replace a field rendered by Jira with a custom element. For this the registerReplaceElementObserver is a one stop solution.
registerReplaceElementObserver("#content", {
target: () =>
document.querySelector<HTMLElement>(selector),
replacement:
reactAppDiv, // The replacement element MUST have an ID
cssBundleSelector:
"link[rel='stylesheet'][data-lp-css-bundle='true']",
shouldSkipReplacement: () => {
return (
ServiceDesk.isCurrentPageInServiceDeskPortal(
serviceDeskPortalId
) ?? false
)
},
onReplacementComplete: () => {
// add a bit more bottom margin to the help text
const element =
document.querySelector<HTMLElement>(selector)
const divs = element.getElementsByTagName("div")
for (const div of divs) {
if (div.id.endsWith("-helper")) {
div.style.marginBottom = "14px"
}
}
}
})Fields
The Fields part includes helper utilities to handle fields.
Utils
Utils currently only contain the frontend mode indicator handling, which displays a small hint on the bottom right corner of a page if the app is in dev or dev-productionbundle mode.
Documentation Overview
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools v0.9.0
Namespaces
Interfaces
Type Aliases
Variables
Functions
API Documentation
AppLoader Namespace
@linked-planet / namespaces / AppLoader / type-aliases / InitFrontendAppOptions
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / AppLoader / InitFrontendAppOptions
Type Alias: InitFrontendAppOptions
InitFrontendAppOptions =
InitFrontendAppOptions
Defined in: loader/index.ts:11
@linked-planet / namespaces / AppLoader / variables / initFrontendApp
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / AppLoader / initFrontendApp
Variable: initFrontendApp()
constinitFrontendApp: (options) =>void=_initFrontendApp
Defined in: loader/index.ts:10
Initializes and loads a frontend application module with automatic mode detection and DOM readiness handling.
This is the main entry point for loading frontend applications. It:
- Automatically detects the appropriate loading mode based on hostname (localhost = development, others = production)
- Handles DOM readiness states (waits for DOMContentLoaded if needed)
- Sets up Shadow DOM if requested
- Manages fallback loading strategies (dev → dev-productionbundle → production)
Parameters
options
Configuration object for frontend app initialization
Returns
void
Throws
Error if appName is not provided
Throws
Error if pluginSettings are not provided
Throws
Error if no valid app root element can be found
Example
'''typescript
initFrontendApp({
appName: "my-react-app",
pluginSettings: { pluginName: "my-plugin", pluginGroupId: "com.example" },
developmentPort: 3000,
appRootElementSelector: "#app-container",
useShadowRoot: true,
starterFunc: (rootElement) => {
console.log("App loaded in:", rootElement);
}
});
'''Fields Namespace
@linked-planet / namespaces / Fields / type-aliases / ReplaceElementObserverOptions
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Fields / ReplaceElementObserverOptions
Type Alias: ReplaceElementObserverOptions
ReplaceElementObserverOptions =
ReplaceElementObserverOptions
Defined in: fields/index.ts:20
@linked-planet / namespaces / Fields / variables / getCustomFieldIDByLabel
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Fields / getCustomFieldIDByLabel
Variable: getCustomFieldIDByLabel()
constgetCustomFieldIDByLabel: (labelText) =>string|null=_getCustomFieldIDByLabel
Defined in: fields/index.ts:15
Get a custom field's ID by its visible label text, automatically handling optional field suffixes. Supports both and wrapping label patterns.
If a matching form control element doesn't have an ID, one will be generated and assigned.
Parameters
labelText
string
The text content of the label to search for (automatically checks with and without "(optional)" suffix)
Returns
string | null
The ID of the associated input/select/textarea element, or null if not found
Example
// Finds labels with text "Priority" or "Priority (optional)"
const fieldId = getCustomFieldIDByLabel("Priority");
if (fieldId) {
const field = document.getElementById(fieldId);
}@linked-planet / namespaces / Fields / variables / registerReplaceElementObserver
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Fields / registerReplaceElementObserver
Variable: registerReplaceElementObserver()
constregisterReplaceElementObserver: (observingElementSelector,options) =>void=_registerReplaceElementObserver
Defined in: fields/index.ts:16
Registers a DOM observer that monitors for changes and triggers element replacement when conditions are met.
This function sets up a MutationObserver that watches for DOM changes on the specified element and automatically calls replaceElement when changes occur. It handles both immediate execution (if DOM is ready) and deferred execution (waiting for DOMContentLoaded).
Parameters
observingElementSelector
string
CSS selector for the element to observe for DOM changes
options
Configuration object for element replacement (same as replaceElement options)
Returns
void
@linked-planet / namespaces / Fields / variables / replaceElement
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Fields / replaceElement
Variable: replaceElement()
constreplaceElement: (options) =>void=_replaceElement
Defined in: fields/index.ts:18
Replaces an element in the DOM with a replacement element and manages CSS bundle state.
This function handles the common pattern of:
- Finding a target element in the DOM
- Checking if a replacement element is already present (by checking if the replacement element has an id)
- Managing CSS bundle enabling/disabling based on element presence
- Optionally executing additional operations after the replacement
The original element is hidden rather than removed, and the replacement is inserted before it.
Parameters
options
Configuration object for element replacement
Returns
void
Example
const reactApp = document.createElement("div");
reactApp.id = "my-react-app";
replaceElement({
target: () => document.querySelector(".original-field"),
shouldSkipReplacement: () => {
return document.querySelector(".original-field") === null;
},
replacement: reactApp,
cssBundleSelector: "link[data-plugin-name='my-plugin-name'][data-app-name='my-app-name']",
onReplacementComplete: () => {
console.log("Replacement completed");
}
});@linked-planet / namespaces / Fields / variables / replaceInputWithSpan
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Fields / replaceInputWithSpan
Variable: replaceInputWithSpan()
constreplaceInputWithSpan: (id,replacementId) =>boolean=_replaceInputWithSpan
Defined in: fields/index.ts:13
Replaces form input elements within a field group with read-only span elements containing their current values. Also hides any buttons within the field group.
Parameters
id
The ID of a custom field element within the target field group (null values are handled gracefully)
string | null
replacementId
string
The ID to assign to the replacement span elements
Returns
boolean
True if the field group was found and processed successfully, false otherwise
Example
'''typescript
const success = replaceInputWithSpan("custom-field-123", "readonly-display-123");
if (success) {
console.log("Field successfully converted to read-only");
}
'''@linked-planet / namespaces / Fields / variables / setInputToDate
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Fields / setInputToDate
Variable: setInputToDate()
constsetInputToDate: (fieldSelector,date,dateFormat) =>void=_setInputToDate
Defined in: fields/index.ts:14
Sets the value of an input element to a formatted date string.
Parameters
fieldSelector
string
CSS selector for the input element to update
date
Date
The Date object to format and set as the input value
dateFormat
string
The format string pattern (supports DD, MM, YYYY tokens, e.g., "DD.MM.YYYY")
Returns
void
Example
setInputToDate("#my-date-field", new Date(), "DD.MM.YYYY");ServiceDesk Namespace
@linked-planet / namespaces / ServiceDesk / variables / isCurrentPageInServiceDeskPortal
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / ServiceDesk / isCurrentPageInServiceDeskPortal
Variable: isCurrentPageInServiceDeskPortal()
constisCurrentPageInServiceDeskPortal: (serviceDeskPortalId) =>boolean=_isCurrentPageInServiceDeskPortal
Defined in: servicedesk/index.ts:4
Validates if the Service Desk portal ID in the current URL matches the expected portal ID.
Extracts the portal ID from the URL path and compares it with the provided Service Desk portal ID. The portal ID is expected to be the 5th segment in the URL path (index 4).
Parameters
serviceDeskPortalId
number
The expected Service Desk portal ID to validate against
Returns
boolean
True if portal IDs match or if portal ID not found in URL, false if they don't match
Example
// URL: /servicedesk/customer/portal/123/...
const result = isCurrentPageInServiceDeskPortal(123); // returns true
const result2 = isCurrentPageInServiceDeskPortal(456); // returns falseInterfaces
interfaces / InitFrontendAppOptions
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / InitFrontendAppOptions
Interface: InitFrontendAppOptions
Defined in: loader/app-loader.ts:9
Configuration options for initializing a frontend application module.
Properties
appName
appName:
string| (mode) =>string
Defined in: loader/app-loader.ts:13
The name identifier of the frontend module to load.
appRootElement?
optionalappRootElement:HTMLElement|ShadowRoot|null
Defined in: loader/app-loader.ts:23
The shadow root to use for the app. If not provided, the app will be loaded into the document head.
appRootElementSelector?
optionalappRootElementSelector:string
Defined in: loader/app-loader.ts:21
The element ID containing the app, if not provided, the app will be loaded into the document head, else it is loaded as shadow DOM attached to this element.
cssBundleFile?
optionalcssBundleFile:string
Defined in: loader/app-loader.ts:33
The file name of the css bundle to load.
Default
${pluginSettings.pluginName}.css
Example
cssBundleFile: "my-plugin.css"developmentPort
developmentPort:
number
Defined in: loader/app-loader.ts:19
The port to use for the development server.
mode?
optionalmode:FrontendModeType
Defined in: loader/app-loader.ts:17
The mode to load the module in.
pluginSettings
pluginSettings:
PluginSettings
Defined in: loader/app-loader.ts:11
The settings of the plugin.
starterFunc()?
optionalstarterFunc: (appRootElement?) =>void
Defined in: loader/app-loader.ts:15
The function to call once the module's script has loaded successfully.
Parameters
appRootElement?
HTMLElement | ShadowRoot
Returns
void
useShadowRoot?
optionaluseShadowRoot:boolean
Defined in: loader/app-loader.ts:25
Whether to use a shadow root for the app.
interfaces / PluginSettings
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / PluginSettings
Interface: PluginSettings
Defined in: configs/project-config.ts:6
Configuration interface for plugin settings containing Maven artifact information.
Properties
pluginGroupId
pluginGroupId:
string
Defined in: configs/project-config.ts:10
The Maven group ID of the plugin providing the module.
pluginName
pluginName:
string
Defined in: configs/project-config.ts:8
The Maven artifact ID (plugin name) of the plugin.
interfaces / ProjectConfig
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / ProjectConfig
Interface: ProjectConfig
Defined in: configs/project-config.ts:18
Base interface for project configuration. Extend this interface to add project-specific settings.
Properties
serviceDeskPortalId?
optionalserviceDeskPortalId:number
Defined in: configs/project-config.ts:19
Type Aliases
type-aliases / FrontendModeType
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / FrontendModeType
Type Alias: FrontendModeType
FrontendModeType =
"production"|"development"|"dev-productionbundle"
Defined in: utils/frontend-mode.ts:17
type-aliases / ObserverCallback
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / ObserverCallback
Type Alias: ObserverCallback()
ObserverCallback = (
mutations?) =>void
Defined in: observer/observer.ts:8
Callback type for MutationObserver events.
Parameters
mutations?
MutationRecord[]
Optional array of mutation records that occurred
Returns
void
type-aliases / ReplaceElementObserverOptions
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / ReplaceElementObserverOptions
Type Alias: ReplaceElementObserverOptions
ReplaceElementObserverOptions =
object
Defined in: fields/replace-element.ts:104
Configuration options for element replacement operations.
Example
const replacement = document.createElement("div");
replacement.id = "my-react-app";
const options: ReplaceElementObserverOptions = {
target: () => document.querySelector("#my-field"),
replacement,
cssBundleSelector: "link[data-my-css='true']",
shouldSkipReplacement: () => false,
onReplacementComplete: () => console.log("Done")
};Properties
cssBundleSelector?
optionalcssBundleSelector:string
Defined in: fields/replace-element.ts:109
Optional CSS selector for the CSS bundle link element to enable/disable
onReplacementComplete()?
optionalonReplacementComplete: () =>void
Defined in: fields/replace-element.ts:110
Optional callback function to execute additional operations during replacement
Returns
void
replacement
replacement:
HTMLElement&object| () =>HTMLElement&object
Defined in: fields/replace-element.ts:106
The HTML element that will replace the target element, requires an Id
shouldSkipReplacement()?
optionalshouldSkipReplacement: () =>boolean
Defined in: fields/replace-element.ts:111
Optional function to check if replacement should proceed; if returns true, replacement is skipped
Returns
boolean
target
target:
string| () =>HTMLElement|null
Defined in: fields/replace-element.ts:105
Function that returns the target HTML element to be replaced, or a CSS selector string
Variables
variables / Config
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Config
Variable: Config
constConfig:object
Defined in: configs/project-config.ts:92
Type Declaration
getProjectConfig()
getProjectConfig: <
T>(projectConfigUrl,pluginSettings) =>Promise<T>
Fetches and caches the project configuration from the REST API.
The configuration is cached globally after the first successful fetch. Subsequent calls return the cached configuration without making additional requests.
Type Parameters
T
T extends ProjectConfig
Parameters
projectConfigUrl
string
The URL path to fetch config from (absolute, relative to REST base, or sub-path)
pluginSettings
Plugin configuration for constructing the full REST API URL
Returns
Promise<T>
Promise resolving to the project configuration
Throws
Error if projectConfigUrl is empty or if the fetch request fails
Example
'''typescript
const config = await getProjectConfig<MyProjectConfig>(
"/project/PROJ",
{ pluginName: "my-plugin", pluginGroupId: "com.example" }
);
'''getRestApiBaseUrl()
getRestApiBaseUrl: (
pluginSettings) =>string
Constructs the REST API base URL using plugin settings and current window location.
Parameters
pluginSettings
Plugin configuration containing Maven artifact information
Returns
string
The base URL for REST API calls (e.g., "https://example.com/rest/my-plugin/1.0")
Throws
Error if pluginSettings is not provided
variables / FrontendMode
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / FrontendMode
Variable: FrontendMode
constFrontendMode:object
Defined in: utils/frontend-mode.ts:80
Utility namespace for managing frontend development modes.
Type Declaration
getFrontendMode()
getFrontendMode: () =>
FrontendModeType
Retrieves the current frontend development mode.
Returns
The currently active frontend mode
setFrontendMode()
setFrontendMode: (
devMode) =>void
Sets the frontend development mode and displays a visual indicator.
Updates the global frontend mode and shows a non-intrusive indicator in the bottom-right corner for non-production modes. The indicator appears after a 500ms delay to prevent flickering.
Parameters
devMode
The frontend mode to set (production, development, or dev-productionbundle)
Returns
void
variables / Observer
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Observer
Variable: Observer
constObserver:object
Defined in: observer/observer.ts:164
Type Declaration
hasObserver()
hasObserver: (
selector) =>boolean
Checks whether an observer is currently registered for the given selector.
Parameters
selector
string
CSS selector to check
Returns
boolean
True if an observer exists for this selector, false otherwise
registerObserver()
registerObserver: (
selector,observerOptions) =>null|undefined
Creates and registers a MutationObserver for the specified element.
Note: If you need to change observer options, unregister the existing observer first.
Parameters
selector
string
CSS selector for the target element (only the first matching element is observed)
observerOptions
MutationObserverInit
MutationObserver configuration options
Returns
null | undefined
The created MutationObserver instance, or null if element not found or observer already exists
See
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit for observerOptions details
registerObserverCallback()
registerObserverCallback: (
selector,cb) =>void
Registers a callback function to be executed when DOM mutations occur on the specified element.
Parameters
selector
string
CSS selector for the target element to observe
cb
Callback function to execute when mutations are detected
Returns
void
unregisterObserver()
unregisterObserver: (
selector,removeCallbacks) =>void
Disconnects and removes an observer for the specified element.
Parameters
selector
string
CSS selector for the target element
removeCallbacks
boolean = true
Whether to also remove all registered callbacks for this selector (default: true)
Returns
void
unregisterObserverCallback()
unregisterObserverCallback: (
selector,cb) =>void
Removes a previously registered callback from the observer system.
Parameters
selector
string
CSS selector for the target element
cb
The specific callback function to unregister
Returns
void
variables / Utils
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / Utils
Variable: Utils
constUtils:object
Defined in: utils/index.ts:6
Type Declaration
fetchFromPlugin()
fetchFromPlugin: <
T>(url,pluginSettings,options?) =>Promise<T>
Fetches data from a plugin's REST API endpoint with automatic URL construction and error handling.
Automatically constructs the full REST API URL using the plugin settings and handles JSON response parsing and HTTP error checking.
Type Parameters
T
T
Parameters
url
string
The REST API endpoint path (can be absolute, relative to REST base, or sub-path)
pluginSettings
Plugin configuration containing Maven artifact information for URL construction
options?
RequestInit
Optional fetch request configuration (headers, method, body, etc.)
Returns
Promise<T>
Promise resolving to the parsed JSON response of type T
Throws
Error if the HTTP response is not ok (status >= 400)
Example
interface UserData { id: number; name: string; }
const userData = await fetchFromPlugin<UserData>(
"/users/123",
{ pluginName: "my-plugin", pluginGroupId: "com.example" },
{ method: "GET" }
);getLoggerPrefix()
getLoggerPrefix: () =>
string
Get the logger suffix [LOADER-TOOLS-].
Returns
string
The logger suffix.
setLoggerSuffix()
setLoggerSuffix: (
suffix) =>void
Set the logger suffix (adds this as postfix to [LOADER-TOOLS]).
Parameters
suffix
string
The suffix to set (adds this as postfix to [LOADER-TOOLS]).
Returns
void
Functions
functions / getLoggerPrefix
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / getLoggerPrefix
Function: getLoggerPrefix()
getLoggerPrefix():
string
Defined in: utils/logger-prefix.ts:15
Get the logger suffix [LOADER-TOOLS-].
Returns
string
The logger suffix.
functions / initFrontendApp
@linked-planet/loader-tools v0.9.0
@linked-planet/loader-tools / initFrontendApp
Function: initFrontendApp()
initFrontendApp(
options):void
Defined in: loader/app-loader.ts:436
Initializes and loads a frontend application module with automatic mode detection and DOM readiness handling.
This is the main entry point for loading frontend applications. It:
- Automatically detects the appropriate loading mode based on hostname (localhost = development, others = production)
- Handles DOM readiness states (waits for DOMContentLoaded if needed)
- Sets up Shadow DOM if requested
- Manages fallback loading strategies (dev → dev-productionbundle → production)
Parameters
options
Configuration object for frontend app initialization
Returns
void
Throws
Error if appName is not provided
Throws
Error if pluginSettings are not provided
Throws
Error if no valid app root element can be found
Example
'''typescript
initFrontendApp({
appName: "my-react-app",
pluginSettings: { pluginName: "my-plugin", pluginGroupId: "com.example" },
developmentPort: 3000,
appRootElementSelector: "#app-container",
useShadowRoot: true,
starterFunc: (rootElement) => {
console.log("App loaded in:", rootElement);
}
});
'''