Reference

You are browsing documentation for an upcoming version of PageDriver. Documentation and features of this release are subject to change.

Can't find what you're looking for?

If you find yourself going to 3rd parties for answers (such as StackOverflow), please search our documentation issue list first, and submit your question if one doesn't already exist.

⭐ Frequently used functionality is highlighted.

namespace BIND

Binds allow the creation of extendable interface-like structures.

Any object may have bindings, but only the object itself should execute them. Currently we don't prevent 'external' bind execution, however prevention may be enforced in the future.

BindRegistry > BindInterface > Binding

  • BIND should have as few external dependencies as possible to allow ultimate flexibility.
  • Originally was a namespace.add('triggerName',()=>console.log('bar')) style mixin, but was refactored to allow: adding binds propr to interface instantiation and reduced memory consumption.

typedef BindRegistry Object.<string, BindInterface>

A bind registry is a collection of bind interfaces.

2025/08/26 At this time all project bindings are maintained in the defaultBindRegistry.

It cannot be stressed enough that directly accessing registries should only be done for debugging purposes via getBindRegistry.

bindInterface.mjs

typedef BindFilter function(triggers, args, iterator)β‡’ boolean

Custom function used by bindExec to limit execution of bound handlers.

Returns: boolean β‡’ True when at least one exact matching trigger is found.

ParameterTypeDescription
triggersstring

Used by the filter function receiving the current bind ID iteration.

argsobject

Arguments passed to bound function.

iteratornumber

Current bind ID (interface list array index). Utilize in the following way: this.list[iterator]

bindInterface.mjs

typedef BindInterface object

A single interface within a BindRegistry.

Extends: BIND.BindInterfaceInit

BindInterfaces go through an optional 'PLACEHOLDER' state when code attempts to getBindInterface or bind an uninitialized interface.

Properties

  • PLACEHOLDER boolean

    (INTERNAL) True when non-existant interface is loaded or bound to.

  • name string

    Self-reflection, primarily for debugging.

  • [mustBeNamed = true] boolean

    DEFAULT TRUE. When true, causes a TypeError to be thrown when attempting to bind anonymous functions.

  • [freezeHandler = true] boolean

    HARDCODED TRUE. Prevents modification of the handler function. Setting this to false would allow in-situ replacement of bound function handles. If you have a practical use case for this please contact the CORE development team.

  • [add] function

    add Additional processing when adding a binding. Can abort the process... by throwing an error :P

  • [disable] function

    disable Additional processing when disabling bindings. Bindings are never deleted, only disabled - usually by setting runsRemaining to -1.

  • [filter] BindFilter
  • [execOnAdd] object

    (EXPERIMENT) Simulated execution args used during initial binding when execOnAdd is true. See BIND/bind options.to add/remove binds. Does it make more sense to allow unique simulated args on a per-bindExec basis instead? Feedback welcome.

  • list Array.<Binding>
  • addedAt DOMHighResTimeStamp
  • [scopes] Object.<string, Array.<number>>

    (CONCEPT) for lookups? Could use code similar to EVENT add/disable bindings. Currently there is experimental scoping at the per-bind level.

typedef add function(triggers, fn, [position])

Extends: BIND.bind

ParameterTypeDescription
triggersArray.<string> | string

Array of strings, or a string which is split by spaces and converted into an array. Used by bind execution filter.

fnfunction

Executed when triggers pass the binds trigger filter

[position]number

(EXPERIMENT) Current execution index

bindInterface.mjs

typedef disable function(bindTarget, id)

Extends: BIND.disableBindByID

ParameterTypeDescription
bindTargetBindTarget
idnumber

bind ID #

Should we have a separate function for disabling by scope?

bindInterface.mjs
bindInterface.mjs

typedef BindInterfaceInit object

Properties

  • [mustBeNamed = true] boolean

    DEFAULT TRUE. When true, causes a TypeError to be thrown when attempting to bind anonymous functions.

  • [freezeHandler = true] boolean

    HARDCODED TRUE. Prevents modification of the handler function. Setting this to false would allow in-situ replacement of bound function handles. If you have a practical use case for this please contact the CORE development team.

  • [filter] BindFilter
  • [add] function

    add Additional processing when adding a binding. Can abort the process... by throwing an error :P

  • [disable] function

    disable Additional processing when disabling bindings. Bindings are never deleted, only disabled - usually by setting runsRemaining to -1.

bindInterface.mjs

typedef Binding object

Single binding within a BindInterface for potential execution by bindExec.

Properties

  • triggers Array.<string>

    Array of strings used by the bind execution filter BindFilter.

  • [scope] Array.<string> | string

    (EXPERIMENT) secondary filtering layer, primarily intended for DOM based filtering, but who knows...

  • handler function

    executed when triggers passes the bindExec filter. It is scoped to this object (this returns the current Binding).

  • id number

    array ID, pretty much only useful for self removal.

  • runsRemaining number

    Defaults to Infinity.

  • stats object

    Ideally moved to an accessible dedicated stats module allowing custom reporting, as adding binds to the binds module seems like a terrible idea.

  • stats.added DOMHighResTimeStamp

    Currently performance.now. Likely to change. See #165 Date.now() in STATS.

  • stats.runs Array.<DOMHighResTimeStamp>

    Times run (get total via runs.length)

  • [stats.disabled] DOMHighResTimeStamp

    Created when bind is deactivated successfully

was thinking of {Array<CSSStyleRule.selectorText>} scopes (EXPERIMENT) for lookups? Could use code similar to EVENT add/disable bindings.

typedef BindTarget object | function

Any non-primitive with a BindInterface property.

Properties: boundInterface BindInterface

constant defaultBindRegistry BindRegistry β„—

Project's default binding registry.

Categories: BIND, devTools

bindInterface.mjs

basicDOMFilter ⊨BindFilterβ‡’ boolean
Release candidate

Default bind filter used by bindExec unless a custom filter function is provided.

Implements: BindFilter

basicDOMFilter.mjs

bind(target, triggers, handler, [options])β‡’ number
Release candidate

Register a function that executes when the triggers parameter passes the target interface's bindExec filter.

Returns: number β‡’ Bind's position in the target interface's queue (execution order when executing)

ParameterTypeDescription
targetstring

Destination interface name.

triggersArray.<string> | string

Array of strings, or a string which is split by spaces and converted into an array. Used by bind execution filter.

handlerfunction

Executed when triggers pass the bindTarget trigger filter.

[options]object
[options.runsRemaining = Infinity]number

(EXPERIMENT) Deactivates the bind function after a set number of successful executions. Remaining runs decrease when the bound function returns true (bool). The bind is disabled (by ID) once runs reach 0.

[options.execOnAdd]boolean

(EXPERIMENT) Executes the function when added, against predefined trigger data.

[options.scope]Array.<string> | string

(EXPERIMENT) secondary execution filter, was originally intended for DOM based filtering.

Should we allow changing the 'execution index' (eg. location in the bind list) via an options parameter when adding bindings? If so, let us know of use cases for such a feature.

bind.mjs

bindAll(scope, target, trigger, binds, [options])β‡’ Array.<number>
Experimental

Most useful in modules with many similar bindings.

Returns: Array.<number> β‡’ Bind IDs

ParameterTypeDescription
scopestring
targetBindTarget
triggerstring | Array.<string>
bindsObject | Array.<function(), object>

a function, or an array containing the function and specific bind options

[options]object

Passed through to bind

this predates the split of trigger text (basic filter) & filter function params in the bind system and should be refactored to do so if we continue providing this helper.

bindAll.mjs

bindExec(target, triggers, args, [meta])β‡’ boolean
Active development

Execute bound functions which passed filtration.

Returns: boolean β‡’ True if any bind was executed. This may change in the future to 'successful' calls only, feedback welcome!

ParameterTypeDescription
targetstring

destination bind interface.

triggersstring | Array.<string>

Used by the filter function receiving the current bind ID iteration.

argsobject

Arguments passed to bound functions

[meta]object

(EXPERIMENT) Metadata passed to bound function, currently used by pd.OBSERVE

bindExec.mjs

getBindRegistry⇒ BindRegistry

Directly access a binding registry for live debugging, this fails outside of the DEBUG context.

Categories: BIND, devTools

bindInterface.mjs

initBindInterface(target, [options], [registry])β‡’ BindInterface

Initialize a BindInterface.

Prior to instantiation bindings may be registered to 'placeholder' interfaces; When a placeholder is initialized by this function, all previously added bindings are re-processed to ensure validity.

Best Practices

  • We recommend only a single interface is initialized per module to reduce complexity.
  • When naming a new interface, you must be very specific to avoid potential conflicts with future potential PageDriver CORE generic interfaces.
  • DON'T use the initalizer for accessing other interfaces, use getBindInterface instead.
  • Unless complete external access is warranted, exporting interfaces is discouraged.
ParameterTypeDescription
targetstring

Bind interface name

[options]BindInterfaceInit

Merge/replace default bind interface properties.

[registry = defaultBindRegistry]BindRegistry

(EXPERIMENT) Don't change this.

2025/11/09 PageDriver doesn't currently prevent 'interface hijacking'. We welcome feedback and potential solutions to prevent duplicate/conflicting interface names.

bindInterface.mjs

getBindInterface(target, [registry])β‡’ BindInterface

Load a bind interface, when it doesn't exist a special 'placeholder' is created with default values.

ParameterTypeDescription
targetstring

Bind interface name

[registry = defaultBindRegistry]BindRegistry

(EXPERIMENT) Don't change this.

bindInterface.mjs

bindStats(interfaceName, [consoleTable])β‡’ Array
Experimental

Returns: Array β‡’ Triggers, function name, total executions, time added, time disabled.

ParameterTypeDescription
interfaceNamestring
[consoleTable]boolean

additionally output via console.table

2024/07/01 Output may change significantly in future versions as part of stats/analytics module.

Categories: devTools

bindStats.mjs

disableBindByID(bindTarget, id)β‡’ boolean

Deactivate a specific bind.

Returns: boolean β‡’ Only returns true when a bind is actually disabled, all other situations such as no bindings, or already being disabled returns false.

Optional target specific disable logic can abort this process. Note disabling a binding doesn't delete it, it sets runsRemaining to -1 and records the timestamp to stats.disabled.

ParameterType
bindTargetBindTarget
idnumber
disableBindByID.mjs

namespace CONTEXT

Contexts are flat (non-hierarchical) negatable CSS classes used to toggle element visibility relevant to different personas, states, or permissions.

For example, elements meant for a regular user, moderator, or manager. See ContextName for naming rules.

Would be interesting if this somehow became scoped... πŸ€”

typedef ContextName string

Context class names:

  • CANNOT begin with a hyphen (-). This character is used for negated contexts.
  • MUST begin with an uppercase character (ex. 'A' not 'a'. 'Π›' not 'Π»'. 'Đ' not 'Δ‘') for differentiation from basic CSS classes. If your chosen programming locale doesn't have capitalization we recommend standardizing a basic latin script prefix (ex. CCログむンする, CC둜그인) across your project.
  • CANNOT be a reserved name.

Valid examples:

  • "Vendor" Basic Latin uppercase
  • "Π›ΠΈΠ΄Π΅Ρ€" Cyrillic uppercase
  • "Đặc_Quyền" Vietnamese uppercase with underscore
  • "CCログむンする" prefixed with uppercase latin script

Invalid examples:

  • "-Vendor" starts with hyphen (use for negation)
  • "vendor" starts lowercased
  • "123User" starts with number, note as of 2025/1/1 this is not enforced programmatically given classes are not normally permitted to begin with numbers.

See isValidContextName for the runtime validation.

typedef ContextState object

State of a single Context within the global context container.

Properties

  • state boolean
  • added EpochTimeStamp
  • [modified] EpochTimeStamp

typedef ContextClass Object.<ContextName, ContextState>

constant globalContextState Object.<ContextName, ContextState>

Global context state object.

To be refactored when the a standardized state manager interface is created.

context_globalState.mjs

constant globalContextStateReserved Array.<ContextName>

Current reserved context names:

  • Javascript - Always actived (activated during INIT). -Javascript is an alternative to <noscript> tags.
  • Debug - Active when DEBUG module debugger is loaded.
  • Kbd - Accessibility (a11y) helper, active when using keyboard-like controls.
context_reserved_defaults.mjs

contextInit(contexts) β„—

Generates Context Flag CSS styles.

ParameterType
contextsObject.<ContextName, boolean>
context_init.mjs

isValidContextName(contextName)β‡’ boolean β„—

Ensure potential context names are in proper form (non-negated and begin with a capital character).

Throws: TypeError β‡’ when contextName is not a string.

ParameterType
contextNamestring
context_isValidContextName.mjs

contextOff(contexts)β‡’ Object.<ContextName, ContextState>

Syntactic sugar for set( ... , false).

Returns: Object.<ContextName, ContextState> β‡’ Returns an empty object when no provided contexts are valid.

ParameterType
contextsArray.<ContextName> | ContextName
context_off.mjs

contextOn(contexts)β‡’ Object.<ContextName, ContextState>

Syntactic sugar for set( ... , true).

Returns: Object.<ContextName, ContextState> β‡’ Returns an empty object when no provided contexts are valid.

ParameterType
contextsArray.<ContextName> | ContextName
context_on.mjs

contextSet(contexts, [state])β‡’ Object.<ContextName, ContextState>

Context Flag base function.

Returns: Object.<ContextName, ContextState> β‡’ Returns an empty object when no provided contexts are valid.

Throws: Error β‡’ when no contexts are provided.

Used by on/off/toggle helpers.

ParameterTypeDescription
contextsArray.<ContextName> | ContextName
[state]undefined | boolean

undefined = toggle, true = on, false = off

context_set.mjs

contextToggle(contexts)β‡’ boolean | Object.<ContextName, ContextState>

Syntactic sugar for set( ... , undefined ), EXCEPT when a SINGLE (1) Context is provided it only returns the boolean state result, allowing for cleaner inlining.

Returns: boolean | Object.<ContextName, ContextState> β‡’ Boolean when only a single Context is provided.

Toggling an undefined Context creates it (in true state).

ParameterTypeDescription
contextsArray.<ContextName> | ContextName

One or more context flags

Example

contextToggle('Foo'); // Returns a boolean
contextToggle(['Foo','Bar']); // Returns { ContextState, ... }

Toggle prevents context function idempotentness. Additional details in contextSet.

context_toggle.mjs

module DEBUG
Active development

In-browser PageDriver debug tools.

The Debugger object and functionality is made available under the pd.DEBUG global property. It is activated under any of the following conditions:

  • 'debug' cookie exists during standard PageDriver initiation.
  • The 'debug.mjs' module is imported.

Due to active development some functions are likely undocumented on the reference page.

Properties

  • verboseActive number

    Total active verbose DEBUG flags.

  • initDOMSize number

    Number of initial document.body DOM elements.

  • verbose Proxy.<string, boolean>

    Verbosity flag proxy, used for customization of debug messages - these appear in the in-browser debugger 'Verbose' toggle list. Ex. if (pd.DEBUG?.verbose.myAwesomeFunction) console.log('Yippie!'). Be mindful as there are no 'reserved' verbose flags or groups (yet), so ensure:

    • your output is as specific as possible to a particular flag, or set of flags.
    • the verbose flag property name is specific enough.
  • once Proxy.<string, boolean>

    Proxy object that returns true only on first access, useful for one-off console messages.

I'm painfully aware how disgusting this code is! Refactoring PRs are welcome! πŸ’£

  • Debug module should maintain state internally and minimize PD imports when possible.
  • 2022/02/14 AFAIK Disabled DEBUG binds are the only remenants after disabling Debug mode.
  • 2022/07/07 [Consistency] use πŸ‘’ unicode arrow in debug output for uniformity

Maybe: VDEBUG.test.warn('test warning'); Replacing: if (pd.DEBUG && pd.DEBUG.verbose.test) console.warn('test');

Categories: devTools

remoteDebugging⇒ boolean

True when the client has remote debug permissions.

2025/03/09 Internally uses a remotely set 'debug-remote' cookie, if extending debug tooling DON'T rely on the cookie, only this function.

Currently relies on a remotely set cookie, NOT a globalThis.REMOTE.debug prop due to the client's ability to initiate debugging post PD init... and the REMOTE prop only being set on initial load. If this changes in the future due to enhancements to sync capabilities we may remove the cookie - but it's simple enough to maintain. Could refresh via HEAD NET request... unsure what the point would be tho'?

debug.mjs
debug.mjs

module OBSERVER
Release candidate

Project-wide bindable DOM mutation observer.

This module only supports a single active observer for no real reason, although performance and coder sanity are a good excuse as any.

Properties: execOnAdd

event childList

Properties: mutation MutationRecord

OBSERVER.mjs

event attributes

DOESN'T trigger on no-difference attribute mutations for potential performance reasons.

Properties

  • args MutationRecord
  • meta object

    {attributeValue, addAttribute, delAttribute, addAttributeValue, delAttributeValue}

OBSERVER.mjs

event raw⇒ mutationsList

Provides raw mutationLists instead of individual records.

OBSERVER.mjs

filter ⊨BindFilter

Implements: BindFilter

Returns true when unscoped, otherwise if there is a mutation target:

  • if a childList with added nodes, ensure scope is within target
  • if an attribute, ensure scope matches the attribute name
  • all other circumstances return true (ex. characterData, which as of 2024/12/15 isn't implemented)
OBSERVER.mjs
OBSERVER.mjs

namespace DOM

Document Object Model functionality.

DOM best practices

  • Instead of mutating HTML directly try using:
    • Predefined template elements.
    • Context classes (feature flagged areas), or toggleable followups.
    • Existing provided 'DOM' and 'UI' functionality.
  • Name and group CSS selectors by purpose, not properties. Ex. .sidenote.important, not .red.col-3.small.

constant DOMState WeakMap.<Element.childNodes>
Release candidate

When mutate mutates nodes, the node's original content is stored here.

I leave it to philosophers to decide if this means we use a VDOM πŸ™Š

DOMState.mjs

constant scratchPadSpace HTMLTextAreaElement β„—
Stable

Scratch space for escape/unescape functions.

elements.mjs

module tableOfContents(parentSelector, [generateAnchors], [maxDepth])β‡’ boolean
Early development

DOM Renderer - Table of contents.

Generates generates a table of contents from HTML heading tags, placed in the first located element with the .tableOfContents class.

ParameterTypeDescription
parentSelectorstring

Single selector, usually a page ID.

[generateAnchors = true]boolean

When no anchor is located, one is derived via the closest page ID and slugifying the heading text. pageID_slugifiedHeadingText, see slugify for more details.

[maxDepth = 6]number

Maximum heading depth for generated table, maximum of 6.

Although this function gracefully handles mismatched heading levels (ex. <h1> to <h3>), we recommend following WCAG guidelines for improved accessibility.

Categories: DOM, renderer

tableOfContents.mjs

classAdd(el, value)β‡’ boolean
Stable

Add a CSS class to an element (reduced DOM mutations).

ParameterTypeDescription
elHTMLElement
valuestring

Class name

classAdd.mjs

classRemove(el, value)β‡’ boolean
Stable

Remove a CSS class from an element (reduced DOM mutations).

ParameterTypeDescription
elHTMLElement
valuestring

Class name

classRemove.mjs

DOMcompareNodes(element, element2)β‡’ boolean
Release candidate

Compare two DOM nodes for effective equality.

Returns: boolean β‡’ True if element matches original state.

Used by DOM restorer to reduce mutations when restoring elements to their original state from saved fragments.

ParameterTypeDescription
elementElement | DocumentFragment

First element or fragment.

element2Element | DocumentFragment

Second element or fragment.

compareNodes.mjs

DOMSaveState(node, [overwriteExisting], [weakMap])β‡’ boolean
Release candidate

Save a DOM Node to a document fragment within a WeakMap.

Returns: boolean β‡’ True when saved.

ParameterTypeDescription
nodeNode
[overwriteExisting]boolean

Replace existing saved fragment, disabled by default.

[weakMap = DOMState]WeakMap.<Element.childNodes>
DOMState.mjs

DOMRestoreState(node, [force], [weakMap])β‡’ boolean
Release candidate

Restore a DOM node from previously saved state when the node don't match (reduces DOM mutation).

Returns: boolean β‡’ True when restored.

Replaces the target's children with a deep clone of the saved node.

ParameterTypeDescription
nodeNode
[force]boolean

When true disables node comparison, forcing replacement and a DOM mutation to occur. Please contact the PageDriver development team with your use case if you find this is necessary (node comparitor may have failed).

[weakMap = DOMState]WeakMap.<Element.childNodes>
DOMState.mjs

removeChildren(target)
Stable

A performant alternative to innerHTML=''.

ParameterType
targetNode

2022/03/15 Perf. between remove() and removeChild() is a wash.

elements.mjs

appendChildren(target, origin, [clone])
Stable

Append all origin node children to target node, cloned by default.

ParameterTypeDescription
targetNode
originNode
[clone = true]boolean

Uses .cloneNode(true) to attempt deep cloning πŸ‘

elements.mjs

escape(text)β‡’ string
Stable

Escape potential HTML using the browser's built-in innerHTML functionality.

If this fails to escape content it means the browser is vulnerable, which is both crazy AND not our problem!

ParameterType
textstring

Example

escape('<script type="module" src="evil.mjs"></script>');
// returns `&lt;script type="module" src="evil.mjs"&gt;&lt;/script&gt;`
escape('normal & usual');
// returns `normal &amp; usual`
elements.mjs

unescape(text)β‡’ string
Stable

Unescape escaped text using the browser's built-in textContent functionality.

ParameterType
textstring
elements.mjs

meta(name, [content])β‡’ HTMLMetaElement
Needs refactor or depreciation
Stable

Returns existing or creates new <meta> tag in <head>, optionally modifying content attribute.

ParameterType
namestring
[content]string
elements.mjs

link(rel, [href], [media])β‡’ HTMLLinkElement
Needs refactor or depreciation
Stable

Mutates or creates (if no match) the first found <link> element within <head> by rel attribute, UNLESS it's a stylesheet - which also matches href attribute.

ParameterTypeDescription
relstring

Relation type https://html.spec.whatwg.org/multipage/links.html#linkTypes

[href]string
[media]string
elements.mjs

loadScript(src, [options])β‡’ Promise
Feedback needed πŸ’¬
Stable

Append a script to the DOM and return loading status to an optional callback.

Asynchronous and removes own script element after loading by default. This was written before the days of wide browser module support and import / import(). Deprecation or refactor is being considered if the benefits it provides are insufficient.

ParameterTypeDescription
srcstring

relative or exact URL.

[options]object
[options.callback]function

Passed boolean based on loading success.

[options.async = true]boolean

Asynchronous loading.

[options.defer]boolean

Deferred - executed after the DOM has been parsed, but before firing DOMContentLoaded.

[options.remove = true]boolean

Remove script element after loading attempt 🧹

[options.crossorigin]'anonymous' | 'use-credentials'

When unspecified use-credentials is automatically applied when src is considered local (see NET/isLocalPath).

[options.integrity]string

SRI Hash

[options.module = true]boolean

Sets type="module" attribute.

[options.nomodule]boolean

Sets nomodule attribute, for scripts to run in pre-ES2015 compatible browers.

loadScript.mjs

markdown(el, [content], [src], [options])β‡’ boolean
Early development
Feedback needed πŸ’¬

DOM Renderer - render Markdown to HTML using 'Marked'.

ParameterTypeDescription
elstring | HTMLElement

uses querySelector if not an element.

[content]string

Markdown content

[src]string

Attempts an XHR request for content, overrides content if both are provided

[options]object
[options.headerIds = true]boolean
[options.headerPrefix = 'md-']string

2025/01/02 Marked DOESN'T sanitize output HTML!

Categories: DOM, renderer

renderMarkdown(mr)β‡’ boolean β„—
Experimental

Internal rendering function.

ParameterType
mrHTMLElement | Event
markdown.mjs
markdown.mjs

mutate(root, obj, [options])β‡’ stdReturn
Active development

Non-object keys are matched to DOM elements via the following rules:

  1. Elements with 'name' attribute:
    • Inexact key match via [name$="key"].
    • (When nested) Exact key match [name="parentKey[key]"] within nested arrays/objects.
  2. Elements with any class names starting with pd_.
  3. (EXPERIMENT) Class names directly matching the nested child object key's name (no 'pd_' required).

Default string, number, boolean type value mutations:

  • <input> and <select> form fields (radio, checkbox, select).
  • <time> add relative attribute to generate a relative locale time.
  • <textarea> updates vertical sizing (may be moved out of CORE).
  • All others element's content is replaced as PLAIN TEXT. When replacing a non-empty element, its INITIAL content (content not added by the populator) is retained. This content is restored on future calls with an empty string value, allowing for 'default content' in non-input elements. This MAY become a non-default boolean option in a future release - given feedback.
ParameterTypeDescription
rootstring | HTMLElement | Document

input name prefix eg. prefix_Item, or specific DOM element. For best performance specify an element to allow direct querying when populating a single element and its children, string is useful when targeting multiple root elements.

objObject.<string, any>

Any object containing state. As of 2022/07/20 we don't allow naked (unnested) arrays, because keys are necessary for DOM query matching. Place the array inside an object, or (not recommended) hijack private nested options parameter.

[options]object
[options.maxDepth = 10]number

Maximum allowable nested object recursion depth. If reached mutate() short circuits and returns, preventing additional processing.

[options.setDefaultInputs]Boolean

Permanently replace default input defaults (useful for form resets), this MAY become a special built-in similar to _disabled object key

[options.template = 'replace']'append' | 'replace'

'Replace' removes template generated elements when no keys are provided, 'append' retains them. Use 'append' when you want to add duplicate elements.

[options.cascade]Boolean

(EXPERIMENT) (DANGER) Allow simulated user interactions to cascade as if they were real. Test thoroughly to prevent infinite loops!

[options.event]Event

(EXPERIMENT) Likely to be removed. Allows mutate() to skip processing current event target element due to a user event.

[options.nested]Array.<string>

(PRIVATE) Used internally during nested object recursion, usually grouped form inputs.

[options.arrayIndex]Array.<number>

(PRIVATE) Used internally during array recursions.

2025/09/19 This function doesn't handle remote unloaded content correctly yet. We are thinking of holding a state stack that applies the mutations when roots contains such content.

This previously defaulted to document.body root, it was removed to improve code readability and increase DOM targeting specificity.


pd_* CSS classes were chosen over data attributes because:

  1. classes came out ahead of data attributes in query performance tests.
  2. classes have cleaner formatting and styling (#pages .pd_chainImg vs #pages [data-pd=chain_IMG]).
  3. multiple pd_* classes may be placed on a single element.

Categories: DOM hydrator, renderer, mutator

typedef MutateResult Object

stdReturn result object

Properties

  • touched Array.<Element>

    Elements virtually clicked.

  • mutated Array.<Element>

    Elements directly mutated.

  • added Array.<Element>

    Elements added (usually via template tags)

  • removed number

    Elements removed during mutation.

  • located number

    Elements located during queries.

mutate.mjs

constant result MutateResult β„—

mutate.mjs
mutate.mjs

stringToSelector(str, [extras])β‡’ string
Stable

Transforms string to generic CSS selector text, used by mutator() to turn object keys into a generic CSS selector.

Returns: string β‡’ CSS selector string, ordered by CSS specificity.

ParameterTypeDescription
strstring
[extras = '']string

Appended after each generated selector without any modification.

Example

stringToSelector('fizz') β†’ '#fizz,.pd_fizz,.fizz,[name$="fizz"],[data-fizz],[data-pd_template-key="fizz"],fizz'
stringToSelector('fizz','.buzz') β†’ '#fizz.buzz,.pd_fizz.buzz,.fizz.buzz,[name$="fizz"].buzz,[data-fizz].buzz,[data-pd_template-key="fizz"].buzz,fizz.buzz'

2025/01/02 Function DOESN'T guarantee validity, it has no CSS sanity checking. Consider escapeCSS() helper if necessary.

Return and extras param are actually CSSStyleRule['selectorText'] (still a string), but JSDoc chokes.

query.mjs

elementTitle(el)β‡’ string
Stable

Returns a human readable title from the custom data-name attribute OR derived from the id attribute by replacing - and _ with spaces, trimming whitespace, and capitalizing the first character.

Returns: string β‡’ Empty string when no useable attribute is found.

ParameterType
elHTMLElement

Example

<article id='reference'>...</article> // 'Reference'
<div id='user_profile-editor'>...</div> // 'User profile editor'
<div id="foo" data-name="returns as is!">...</div> // 'returns as is!'
query.mjs

queryFilter(selector, [root], [nestSelectors], [one])β‡’ Array.<HTMLElement>
Early development

Returns elements located within root(s) and recursion chain.

Returns: Array.<HTMLElement> β‡’ Couldn't this be any Element?

ParameterTypeDescription
selectorstring
[root = document.body]Element | string

Selector string allows for multiple roots!

[nestSelectors = []]Array.<string>
[one]boolean

Uses querySelector instead of querySelectorAll when true.

actually CSSStyleRule['selectorText'] not just basic strings

query.mjs

querySelectParent β„—

Moved from query.mjs as reference for refactor since it is only referenced in reset

Deprecated

reset.mjs

reset(parentSelector, [options]) β„—
Needs refactor or depreciation

Reset DOM/input state of a node's children.

ParameterTypeDescription
parentSelectorobject | string
[options]object

form, readonly, inputs, defaults, hidden, selectNone. If none are used, all options are true.

options.formboolean

reset all inputs in a form

options.toDefaultboolean

input fields set to default value

options.toInitboolean

restores non-form elements to initial state. See #166

options.listsboolean

sponsors / interests? Removes from DOM entirely.

options.readonlyboolean

remove readonly attribute and class EXCEPT hidden inputs

options.selectionboolean

revert checked/selected items to blank state

options.defaultsboolean

revert checked/selected items back to original state

options.hiddenboolean

hidden element values EXCEPT 'readonly'

options.selectNoneboolean

simulates click of inputs with "none" value (usually radio inputs)

options.inputBackgroundboolean

text input backgrounds

CODE FOR REFACTORING REFERENCE ONLY. DON'T USE.

reset.mjs

setStyle(styleTagID, options)β‡’ HTMLStyleElement
Release candidate

CSS Style loader.

Creates a new element if no matching style element exists.

ParameterTypeDescription
styleTagIDstring

Element.id

optionsobject
options.innerTextstring

HTMLStyleElement['innerText']

Should scoping be added to go with modularity?

Categories: DOM, CSS, loader, mutator

setStyle.mjs

temporary(target, attribute, [value], [options])β‡’ DOM.temporary.domTemporaryState
Stable

Temporarily mutate HTML element attributes.

Useful UX Helper that can be used to temporarily add animation classes, disabling buttons, etc.

ParameterTypeDescription
targetHTMLElement
attributestring

class, disabled etc...

[value = '']string

optional attribute value.

[options]object
[options.duration]number

in seconds, derived from computed animation duration (if any) if not supplied.

[options.delay]number

in seconds

2025/09/17 When duration is zero, DOM mutation events still trigger. This can cause unforseen consequences. Please don't rely on this behaviour.


2024/06/13 This function is limited to a single outstanding mutation per target, PRs welcome.

Categories: DOM, mutation, temporal

typedef domTemporaryState object

Properties

  • timerID number

    Timer ID provided by timeout API

  • attribute string
  • [value] string
  • delay number
  • duration number
  • status number

    0 pending, 1 complete, -1 aborted

temporary.mjs

constant weakMapDOMTemporary WeakMap.<HTMLElement, DOM.temporary.domTemporaryState> β„—

Internal state collection.

temporary.mjs
temporary.mjs

module EVENT
Release candidate

PageDriver browser Event delegator.

This interface is automatically initiated by pd.mjs unless PageDriver modules are being used piecemeal.

Functions bound to the EVENT interface MUST be named to pass the EVENT interface's custom filter rules listed below:

When an event is triggered...

  1. Sets lastEventTrusted helper boolean.
  2. Sets isKbd helper boolean.
  3. Monkey patches standard Event object into module:EVENT~PDEvent for additional properties.
  4. Executes binds passing the custom EVENT interface filter:
    1. The Event source is trusted OR allowed to cascade.
    2. AND if bind is scoped, it is the Event target OR within the target (closest).
    3. AND Event type matches one of the bind's triggers.
    4. AND ANY of the following:
      • Function name begins with _, which is considered an untargeted 'global' event.
      • OR function name exactly matches element's ID.
      • OR function name matches an HTML tag name within the parent Element chain (nodeNames are ALL CAPS). This check is skipped when the event directly targets an element without a parent node, such as the document root, since they don't have the 'closest' function. Ex. A or BUTTON.
      • OR function name matches the name attribute in the parent Element chain.
      • (EXPERIMENT) OR if target is an anchor link AND href attribute beginnings with #bind-.
  5. Adds PDEvent to priorEvents (10 max as of 2025/01/20)

Example

bind('EVENT','click', function test(){ console.log('test successful'); });

When bundling or minifying, bound function names MUST BE PRESERVED, otherwise the event filter breaks catastrophically!

Ideally EVENT becomes an interface of sorts that allows it be a delegator in other instances such as Node/native?

typedef PDEvent Event

Monkey patched Event.

Properties

  • shadowTarget Element

    Populated with true target when an Event targets an element within an open shadowRoot (via composedPath()[0]).

  • pd_cascade boolean

    Forces EVENT filter to treat a simulated event as real, which may trigger others.

EVENT.mjs

constant EVENT

Default module export

Properties

  • isKbd boolean

    Is this a keyboard-like event?

    • true for KeyboardEvent
    • false for MouseEvent and TouchEvent
    • null for all other events EXCEPT FocusEvent, where it retains its previous state.
  • priorEvents Array.<module:EVENT~PDEvent>

    Previously captured events (currently hardcoded as 10).

  • lastEventTrusted boolean

    Browser native event.isTrusted is untrustworthy, use this instead!

    For whatever reason event.isTrusted becomes true when:

    • a new MouseEvent is dispatched without an eventInitDict parameter. It becomes trusted after dispatch! This may be true for other artificially created events as well.
    • Follow-up events (such as input/change) on untrusted form input click events become trusted.

    Please see this CodePen for demonstration. Reach out to core team if this is a known "wontfix", or are working on an eventual solution allowing us to remove it.

  • activeListeners Array.<string>

    Array of active DOM event types with listeners added via bindAdded to document, which call pd.EVENT.handler.

  • notices

    State container for one-off developer warnings and notices.

EVENT.mjs

constant boundInterface BindInterface β„—

EVENT.mjs

eventHandler(event)

DOM/client Event handler.

See module for logic description.

ParameterType
eventmodule:EVENT~PDEvent

The primary reason for this handler's existence is to execute bound functions matching incoming events. I may have poorly abstracted the Event API and would love researching potential tightly integrated alternatives if sensible.

2022/11/16 Simulated events may cause infinite looping when an event triggers a handler or other code that then toggles or reverses the current state being set.

EVENT.mjs

add(eventName) ⊨add β„—

Adds document element event listeners (unique).

Implements: BIND.BindInterface.add

ParameterTypeDescription
eventNameArray.<string>

Actually <Event['type']> or Array or similar...

EVENT.mjs

disable(eventName) ⊨disable β„—

Removes document element event listeners when no EVENT binds utilizing them remain.

Implements: BIND.BindInterface.disable

ParameterTypeDescription
eventNameArray.<string>

Event['type']

EVENT.mjs

filter(_trigger, event, iterator) ⊨BindFilterβ‡’ boolean β„—

Event Filter

Implements: BindFilter

See module description for logic explanation.

ParameterType
_trigger*
eventmodule:EVENT~PDEvent
iteratorNumber
EVENT.mjs
EVENT.mjs

namespace HELPER

PageDriver CORE team helper functions.

module RegExps
Stable

Central repository of all non-trivial regular expressions.

  • RegExp commentary MUST provide a https://regex101.com link with matching/non-matching samples and MUST be maintained, along with comment parity.
  • RegExp constant names MUST start with 'RegExp' for code clarity.

constant RegExpOnlyFirstSentence RegExp

Extracts the first sentence, but only if it does not look like a list heading or item.

Match the first sentence of text from a paragraph including the optional period. May potentially contain generic tags < ... > and code within either curly braces or backticks. First sentence may also end on a new line instead of a period. Avoids lines starting with a spaced hypen (list item), or ending with a colon : as it usually precedes a list.

Tutorial: https://regex101.com/r/LiLwk1/4

regexs.mjs

constant RegExpJSDOCLink RegExp

Extract links and optional display text from JSDOC {@link} tags outside backticks.

Ideally RegExp is refactored to prevent returning backticked content entirely, until then you must check if either other groups are returned. Match groups:

  1. URL or path.
  2. (Optional) display text, trims optional | pipe separator.

Tutorial: https://regex101.com/r/Bvtw5o/3

DOESN'T support [foo]{@link bar} style links.

regexs.mjs

constant RegExpSafeStringLocator RegExp

Locates #{...} text, returning contained text as the 'inner' group.

Exact capture rules:

  • Must start with #{, not part of 'inner' capture group.
  • Must end with }, not part of 'inner' capture group.
  • Named capture group 'inner' within the brackets.
  • Must only contain periods, dashes, underscores, unicode letters, and 0-9 digits.
  • Must contain at least two unicode letters, or at least one unicode letter and digit.
  • Cannot begin or end with a period. Ex.#{.foo.}not matched.
  • Cannot start with a digit. Ex. #{42_digitAtStart} is not matched.
  • No periods before unicode chars, Ex. #{_.foo} is not matched.
  • Two periods cannot be next to one another. Ex. #{foo.bar} is OK, but #{foo..bar}, #{.foo}, and #{foo.} are not.
  • cannot contain any mix of more than 4 dashes or underscores next to one another. Eg. #{foo____} and #{-_-_foo-bar_-_-} are OK, but #{--___ foo} and `#{bar----} are not.

Tutorial: https://regex101.com/r/8Bp0Eo/1

2024/03/10 We capture the contents of the positive lookahead contents using a backreference as a workaround to Javascript RegExps not having atomics or posessive quantifiers. Consider refactoring if this changes in the future (following project CIU rules).

regexs.mjs
regexs.mjs

convertMS(ms)β‡’ object

Convert milliseconds to { days, hours, minutes, seconds } object.

ParameterTypeDescription
msnumber

milliseconds

2022/03/19 Only used by storage/clearOldLocalStorage, may remove/refactor in favour of native Date unless this finds use elsewhere.

convertMS.mjs

escapeCSS(inputStr, [options])β‡’ string
Stable

Native CSS.escape() wrapper function that outputs console warnings when inputted CSS string mutates when escaped.

Returns: string β‡’ escaped CSS

ParameterType
inputStrstring
[options]object
[options.throwIfEmpty]boolean

Categories: helper, devTools

escapeCSS.mjs

flagEmoji(str)β‡’ string
Stable

Transform basic ASCII ISO 3166-1 alpha-2 country code strings into 'regional indicator symbol' Unicode sets (aka 'flag Emoji').

Returns: string β‡’ Unicode regional indicator symbol

ParameterTypeDescription
strstring

ISO 3166-1 alpha-2 country code

Example

ca β†’ πŸ‡¨πŸ‡¦
de β†’ πŸ‡©πŸ‡ͺ
cn β†’ πŸ‡¨πŸ‡³

2025/11/8 For (most likely) political reasons the Windows operating system doesn't render regional flags in this Unicode range by default. A custom font must be loaded for this unicode range for these clients.

Categories: helper, emoji, string, transformer, unicode

flagEmoji.mjs

formatTimeAgo(date)β‡’ string | false

Returns a localized, relatively formatted date string in the client's inferred locale.

Returns: string | false β‡’ false when date is malformed

Modified version of Kyle Cook's formatter.

ParameterType
dateDate
formatTimeAgo.mjs

importLoader(src, [footgun])β‡’ Promise.<(Object|function())>
Stable

For developer exploring scripts via a live console such as DevTools, especially modules normally hidden from the global scope; how annoying! Scripts are imported into globalThis variables named derived from its file name.

Returns: Promise.<(Object|function())> β‡’ import() promise returns requested function or module (object) on success.

Throws: Error β‡’ Throws outside 'Development' environment.

This module has no exports, it creates the iL() function under globalThis.

  • Multiple exports: module is assigned.
  • Single export: single export assigned.
ParameterTypeDescription
srcstring

'/src/' prepended.

[footgun]string

Accepts a unique string located in this function's source code to bypass environment constraints for live demos and debugging.

Example

iL('HELPER/slugify.mjs'); // Loads single export module as `slugify()`.
iL('dom/elements.mjs'); // Loads multiple export module as `elements` module.

Categories: devTools, helper

importLoader.mjs

mergeDeep(objects)β‡’ object
Stable

Perform a deep merge of objects and returns a new object.

Returns: object β‡’ New object with merged key/values

Doesn't modify objects (immutable) and merges arrays via concatenation. Consider an alternative for advanced merges.

ParameterTypeDescription
objectsobject

Objects to merge

2022/09/06 Used during PD init for merging settings/REMOTE state, likely useful for later sync work :)

mergeDeep.mjs

simpleHash(str)β‡’ number
Stable

General-use hash generator.

Returns: number β‡’ Integer hash (currently CRC24)

Using this for cryptography will result in being shot from a cannon into low earth orbit ✨🌎 (don't use this for cryptographic purposes).

ParameterType
strstring

Categories: helper

simpleHash.mjs

slugify(str, [options])β‡’ string
Release candidate

'Slugify' a text string.

Normally used for URL paths and link anchors.

ParameterTypeDescription
strstring | null | undefined

The string to slugify.

[options]object

Configuration options.

[options.maxSaneLength = 28]boolean

Strings above this length are chopped and have a hash appended. If modified we recommend keeping the value below 120.

[options.separator = '-']string

Separator character.

[options.lowercase = true]boolean

Convert string to lowercase.

[options.strict]boolean

AKA paranoid mode. Removes all non-alphanumeric characters except separators.

[options.guaranteeUnique]boolean

When true this function throws an error instead of a console warning when a duplicate slug is generated (per script instance).

[options.minSaneLength = 4]boolean

Strings at or below this threshold generate a console warning.

Example

slugify('Hello World!') // 'hello-world'
slugify('CafΓ© & Restaurant') // 'cafe-restaurant'
slugify('Multiple   Spaces') // 'multiple-spaces'
slugify('--Leading-and-trailing--') // 'leading-and-trailing'
slugify('Hello World', { separator: '_' }) // 'hello_world'
slugify('Hello World', { lowercase: false }) // 'Hello-World'

This function doesn't transliterate non-ASCII characters, they are removed during processing. Consider pre-processing as necessary.

Categories: helper, string, transformer

slugify.mjs

namespace modal

activateModal(req)β‡’ boolean

Attempt activating a modal element by toggling the active class.

Returns: boolean β‡’ true on success, false when no #modals element is located in the document.

Throws: ReferenceError β‡’ When requested modal element doesn't exist.

Modals are contained within an element with the #modals ID.

ParameterTypeDescription
reqstring

HTML ID attribute of the requested modal element.

modal_activateModal.mjs

close([_event], [force])β‡’ boolean
Experimental

Attempt closing a modal.

Returns: boolean β‡’ false when no modals exists, no modal is active, or if the modal cannot be closed without force.

This function only closes modals whose first child is a closeBtn button unless the force parameter is true, otherwise the element temporarily gains the attention class. This allows non-critical modals to be closed by pressing on the backdrop.

ParameterTypeDescription
[_event]Event

Currently unused.

[force]boolean

forcibly close modal missing closeBtn first child.

modal_close.mjs

currentActiveModal⇒ HTMLElement | null
Stable

Active modal, or null if none.

Only one modal is active at any given time (assuming nothing broke).

modal_currentActive.mjs
modal_init.mjs

namespace NET

Network related functionality for handling XHR requests, tracking pending requests, and managing network state.

constant pendingREQ object β„—
Deprecated

Current number of outstanding network requests.

Deprecated

Properties

  • xhr number

    Number of active XHR requests

  • spinner number

    Number of active spinner indicators

net_pendingREQ.mjs

getResponseHeaderMap(xhr)β‡’ Object.<string, string> | Object
Stable

Maps XHR response headers into a KV object.

Returns: Object.<string, string> | Object β‡’ Empty object when no header values are returned.

ParameterType
xhrXMLHttpRequest
net_getResponseHeaderMap.mjs

isLocalPath(src)β‡’ boolean
Stable

Could src be considered a local path? Domain-like strings WITHOUT protocols (ex. 'example.com') return true as they may be valid local paths.

Returns: boolean β‡’ True if path is considered local, false otherwise.

Throws: TypeError β‡’ When src is not a string.

Returns false when src string begins with a protocol UNLESS it exactly matches window.location.origin.

ParameterTypeDescription
srcstring

Potential path

Example

// Assuming origin is https://example.com

// Returns true
isLocalPath('/foo');
isLocalPath('foo.com');
isLocalPath('bar.php');
isLocalPath('foo/bar?fizz#buzz');
isLocalPath('https://example.com');
isLocalPath('https://example.com/foo');
isLocalPath('//example.com');  // `//` matches current protocol, how useful!

// Returns false
isLocalPath('http://example.com');  // mismatched protocol
isLocalPath('https://example.com.evil.org/foo');  // mismatched origin
isLocalPath('javascript:alert("evil")');  // non-http protocol

constant lastIsLocalSrcResult Array.<string, boolean> β„—

Memoization short circuit for isLocalPath.

I never checked if this actually improves performance, but it probably helps? It was trivial to implement and just as trivial to remove if deemed unnecessary!

net_isLocalPath.mjs
net_isLocalPath.mjs

XHRActiveOPs⇒ number
Release candidate

Current active XHR network requests.

net_xhr.mjs

xhr(src, [callback], [options])β‡’ XMLHttpRequest
Feedback needed πŸ’¬
Active development

Perform an XHR, optionally returning status/object to callback

Returns: XMLHttpRequest β‡’ passes responseObj, src URL to provided callback

  • Automatically applies pd-csrf and pd-app-version headers to local requests.
  • Under Development ENV the noStore option is true on all local requests to ensure freshness. Create a cookie named pd-debug-breakLocalCache to disable this feature.

Why XHR and not Fetch?

  • Fetch doesn't track upload progress natively
  • Once timeouts, abort controls and other features are added, Fetch feels about the same.
  • It's what I knew best when writing PageDriver, feel free to contribute a PR, especially once work begins on ServiceWorkers!
ParameterTypeDescription
srcstring

Remote URL or local path. Specificity is important! Project's API path is prepended for relative paths NOT starting with a slash /. Ex. foo prepends API path, whilst /foo doesn't.

[callback]function
[options]object
[options.type = json]'json' | 'head' | 'html' | 'md'

Expected response type. NOT XMLHttpRequest built-in enum, may eventually be extendable via binds.

[options.spinner = true]boolean

Request contributes to pending operations UI element (spinner) activation.

[options.timeout = 10000]number

Milliseconds, default is 10 seconds

[options.formData]FormData

When included request is sent as 'POST', client's UTC offset and current active PD page are automatically appended (subject to change, provide feedback).

[options.noStore]boolean

Adds request headers to forcibly acquire a fresh response

[options.headers]object

Custom headers

2025/01/16 Doesn't handle HTTP error codes appropriately yet!

net_xhr.mjs
net_xhr.mjs

namespace PAGE

Dynamic page control interface.

The page container element should have nothing except pages as direct descendents. An active page is any direct descendant element of the project's page container with the active class. Theoretically, only one page should be active at any given time.

The first element within the container is considered the site's 'index' (aka home page), located at / and MUST have a valid ID attribute.

Pages may have data-description and data-keywords attributes that are copied to HTML head on activation - dramatically improving SEO. Defaults to SITE description/keywords when not present.

constant pageContainerEl HTMLElement | null
Early development

Document's page container element.

body>main, body>#main, body>#pages.

page_container.mjs

activatePage([pageID], [pushState], [passthrough])β‡’ stdReturn
Active development

Attempt changing the current active page.

  • When successful, the active class is added to the requested page element, the document title, meta description and meta keywords are updated.
  • entering helper class is temporarily added to the current page element, exiting on the previously active page, if any.
  • When not permitted (due to context class or inaddequate permissions), the index ID page is activated.
  • (EXPERIMENTAL) When not found, the error-404 ID page is activated. Mutates errorMsg.
  • (EXPERIMENTAL) When a critical error happens during APP initiation , the error-critical ID page is activated. Mutates errorMsg.
  • (EXPERIMENTAL) Pages with the unloaded class are remote. xhr attempts loading it from /pages/ router, which (if successful) calls this function again, replacing element's content and the unloaded class with remote. This requires server-side framework features.
ParameterTypeDescription
[pageID]string

When undefined we attempt re-activating the current active page; useful in situations when context classes or client permissions may have changed. Denied requests activate the project's index page instead.

[pushState = true]boolean

Default true. If true update window title & push a new state to window history stack

[passthrough]object

For unloaded remote pages: ?req=passthrough

page_activate.mjs

addPage(htmlString, [activate])
Early development

Add remote page contents to the document.

ParameterTypeDescription
htmlStringstring

HTML contents

[activate]boolean

Attempt activating page after mutation

page_addPage.mjs

currentActivePage⇒ HTMLElement | null
Stable

Active page, or null if none.

Only one page should be active at any given time, thus this only returns the first result. An active page is any direct descendant of the project's page container with the active class.

page_currentActive.mjs

namespace pd
Active development

PageDriver library entry point and global object.

Projects contents:

  • ../app/app.mjs Your project module. PageDriver functionality becomes available within app.INIT(), which is executed during initialization.
  • ../app/app_settings.mjs file should be cache friendly and not rely on any PageDriver components. There should be no dynamic elements.
  • The optional globalThis.REMOTE object provides initial dynamic state (such as user data). It is merged and overrides app settings.

Properties: DEBUG

Code reviewers

  • Too much to take in? Search for comment flags (see ### COMMENT FLAGS below) containing question marks instead and skip to those :p
  • Try not to focus on:
    • swappable/toggleFUQs/selected fns. Being refactored (see issue #16, #205)
    • closeables (along with a bunch of other INIT run-once features) will be refactored to use a bindable mutation observer
    • External settings (pd.SITE etc) are currently generated server side. Ideally they'll be compiled to a mostly static (JSON?) file. To be finalize this by next milestone... advice appreciated.

Contributor's guide

CODE COMMENT GUIDE

  • Negations MUST BE contractions for comprehension and specificity. They MUST be capitalized if critical. Examples: "don't" vs "do not", "won't" vs "will not", "can't" vs "can not" (although "cannot" is OK!)
  • Append relevant repo issue #s, if at all possible.
  • @warning must begin with a date (YYYY/MM/DD) to be checked for continued relevance

COMMENT FLAGS

  • Highlight flags in VSC via 'gruntfuggly.todo-tree' extension. Project settings are included in the repo.
  • WIP for code that is a work in progress and likely broken. Don't commit these.
  • i18n for marking strings requiring translation.
  • SETTINGS for variables eventually managed by project admin/super-admin.
  • BIND for marking areas with 3rd-party extendability (usually bindExec)
  • Once stable, code containing WIP, FIXME and TODO comment flags will be automatically rejected.

CORE DEVELOPER COMMENT FLAGS (don't commit these unless you're a CORE dev)

  • Add FIXMEs to critical, high priority code that you are unable to fix yourself. FIXME code CANNOT be submitted for stable releases!
  • TODO are low priority FIXMEs with an assigned issue (create one if none exists), and may exist in stable releases. These are a temporary measure from initial development efforts.
  • DOC for marking areas requiring external documentation from the documentation team. NOT an alternative to commenting your code.

CORE CODE GUIDE (oldest first)

  • 2020/04/25 [Consistency] 'use strict'; outside ES modules
  • 2020/04/25 [Compatibility] When using native features, they MUST pass the project's CIU threshold (currently ~95%)*. See https://gitlab.com/evilumb/pagedriver/dev/-/issues/66 for more details
  • 2020/04/25 [Consistency] Iterate HTMLCollections with bracket notation [i], not .item(i) method
  • 2020/04/25 FUTURE DEPRECIATION: Many PageDriver settings are set via project index's state initializer file, this will be improved. #162
  • 2020/04/25 [Mutation] use document.createDocumentFragment(); when the primary objective is reducing total DOM mutations, otherwise measure actual performance, prioritizing code readability.
  • 2020/04/25 [Performance] requestAnimationFrame() can force unnecessary re-rendering, most circumstances do not require it.
  • 2021/02/09 [Consistency] Use Object.assign() instead of spread operator when possible. Spread is 93.05% CIU, ~1% slower, and doesn't improve code readability that much.
  • 2021/03/10 [Documentation] Contributing RegEx? Register at https://regex101.com, and provide a link in code commentary along with some match/non-matching samples if possible. See the RegExp helper for examples.
  • 2021/04/03 [Mutation] When directly adding/removing CSS classes, they trigger class attribute mutation even if nothing actually changes (per spec). Use pd.DOM class helpers or check classlist.contains first if mutation is an issue (pd.OBSERVER may ignore these in the future).
  • 2021/04/06 [Consistency] options={} for parameter options, then Object.assign() defaults to params obj internally. Once spread operator passes CIU %, codebase may transition to it. Use deep merge helper for options with complicated nested defaults (although that might be a code smell). JSDOC options props, not params props.
  • 2021/05/03 [Consistency] Object literals > classes. Why? Lorin likes em'. Β―\_(ツ)_/Β―
  • 2022/03/24 [Consistency] Use provided function names instead of local destructuring to prevent CORE code bloat.
  • 2022/09/19 [Consistency] Modules exporting single default functions are preferred, however when exporting objects containing a set of functions, the file and module name MUST BE capitalized for easy identification and potential future splitting for improved tree-shaking.
  • Take care iterating HTMLCollections by index if elements are being removed within the loop, as HTML DOM is live!

HINTS

  • [Performance] Short circuit, break early when possible. "The cheapest function call is the one you never make" - Jason Miller, Preact dev.
  • 2022/08/30 [Performance] Converting NodeLists to Array is SLOW. Array.from is slower than spread.
  • 2021/05/03 [DevTools] alert() is for chumps. Try live expressions, breakpoints, debugger;, or console.log(). See additional helpful utilities here https://developer.chrome.com/docs/devtools/console/utilities/?utm_source=pagedriver&utm_medium=sourcecode
  • 2021/02/16 [Annoyance] HTMLElement.prototype.closest doesn't accept other HTMLElements, just a selector string :|
  • 2021/04/06 [Annoyance] Don't trust event.isTrusted, see notes in pd.EVENT.simulate.

Categories: INIT, client, global

namespace SITE object
Work in Progress πŸ”¨
Early development

Properties

  • env 'Development' | 'Production'

    Environment.

  • [perf] boolean

    Enables PageDriver performance monitoring outside 'Debug' mode.

  • contexts Array.<string>

    Context classes / flags

  • [domain = window.location.hostname] string

    Automatically created during project init.

  • [path = /] string

    This instance's root path.

  • api string

    API URI, prepended to local API requests.

  • title string

    Site title.

  • siteTitleFirst boolean

    Site title appears first, useful for RTL.

  • [titleDivider = Β·] string

    site and page title separator in window title.

  • header HTMLElement

    First found header or #header element.

  • primaryNav HTMLElement

    First found nav , #nav or #menu element.

  • mainRegion HTMLElement

    Primary content region (body>#main , body>main).

  • fixedScroll boolean

    Does the pages element have fixed scrolling? Is scrolling controlled by pd.SITE.pages element or window?

  • pages HTMLCollection

    #pages children.

  • modals HTMLCollection

    #modals children.

  • [remoteReporting] object

    Project's reporting/logging provider. Initialized under 'Production' environment. Create an empty 'pd-forceLogging' cookie to force remote logging outside 'Production' environment. Only Sentry.io is supported at this time.

  • remoteReporting.endpoint URI

    Provider's reporting endpoint

  • remoteReporting.src URI

    Provider's bundle location

  • [remoteReporting.integrity] string

    Subresource Integrity (SRI) hash, may be required by your CORS settings.

  • [remoteReporting.initExtras] object

    Optional properties added to the provider's init. Warning: This can override hardcoded PD options.

  • [description] string

    Default site-wide meta tag. Used when none available at page level.

  • [keywords] string

    Default site meta tag.

Most properties will permanently move once v1 lands.

module INIT
Active development

PageDriver initializer.

The following is out of date, eventually this list will be automatically rendered from inline @steps:

  1. Debug state check, when the debug cookie is present load and initialize debug tools.
  2. Begin performance measurement (when enabled).
    • Otherwise, execute optional app.notDebugging() function (to do things such as displaying a default console message/warning)
  3. Setup remote logging. If pd.SITE.ENV is Production, initialize logger based on pd.SITE.remoteReporting object. Logging can be forcibly enabled via forceLogging cookie in other environments.
  4. Set pd.SITE DOM references and booleans
  5. Context class setup
  6. i18n initialization
  7. pd.DOM elements (most removed for upcoming modularity)
    • closeable
  8. Attempt initializing app.js via app.INIT() (/w perf measurement)
  9. pd.SITE.location
  10. End performance measurement
  11. Destroy pd.INIT

addCloseButton(el)β‡’ boolean β„—

Create a close button ... refactor using future template system...?

Returns: boolean β‡’ True if closed

ParameterType
elHTMLElement
pd_init.mjs
pd_init.mjs
pd.mjs

module deprecating β„—

Deprecated

swappable(target) β„—

Toggle .swapped class on matching elements within local .swappable class node

Adds .swapped class to an element with an ID matching the combined target input's name and value (capitalized, if possible), after removing it from its siblings.

Example: Target is a selected radio input named 'eventVis' with the value 'public'. An element with the ID 'eventVisPublic' has .swapped class added, and the class is removed from its siblings.

ParameterTypeDescription
targetstring

Input element (Checkbox/Radio)

REPLACEME.mjs

toggleFUs

Toggle visibility of follow up components

Deprecated

REPLACEME.mjs

selected

Toggles .selected CSS class on parent element of checked input (checkbox/radio) and toggles related IDs within nearby .swappable element.

Deprecated

If input was added by the user (.userAdded), it is removed from the DOM.

Surround swappable items with a swappable class and give each an ID that is the checkbox/radio name + value with first character capitalized. Ex. a radio named fizz with a value of buzz would trigger an element with the fizzBuzz ID.

Only works if input elements are nested within... 2 elements of each other. Why 2? This functionality was added because I blindly trusted my own HTML, then realized some input elements were unnecessarily nested - now it sits as a reminder to ensure the content you are working with is correct to begin with. Maybe it will come in handy some day.

This trash heap is still necessary in 2021 because CSS doesn't have non-capturing selectors! Why?! WHY CAN'T :has() WORK OUTSIDE OF JS??

REPLACEME.mjs
REPLACEME.mjs

module ROUTE
Experimental

Location routing module.

ROUTE.mjs

module perfMark
Release candidate

PageDriver performance marking suite, we recommend using perfMark in components with potentially long run times.

Markings are only recorded when:

  • DEBUG mode is active AND/OR
  • SITE.perfMark setting boolean is true.

Console output only occurs when the 'DEBUG.verbose.perfMark' flag is enabled.

Example

const perfID = perfMark.start('example');
// ... measured code here
perfMark.end('example',perfID);

Should PerformanceObserver be utilized? To what end?

start(group, [id])β‡’ string | null

Initialize a performance mark with the name format ${group} INIT-${id}.

Returns: string | null β‡’ Mark ID or null.

Only functions if device has the performance API, pd.SITE.perfMark is true OR Debug mode is enabled.

ParameterTypeDescription
groupstring

Human readable grouping string, such as 'app'.

[id = Math.random]string

Used as part of the mark's name, randomized by default to prevent duplicates marks when being run multiple times. We recommend changing this to a more human string when only being run once.

Example

returns `0.8128820578073859` for 'FOO INIT-0.8128820578073859' performance mark.

Since Math.random has a non-zero chance of returning 0, we do (Math.random() || Math.random()) which reduces the odds. Alternatives welcome.

perfMark.mjs

end(group, id)β‡’ number | null

Generate a measurement named ${group} ${id} after creating a mark named ${group} END-${id}.

Returns: number | null β‡’ seconds, rounded to 2 decimal places. null on devices without performance API or with perfMark disabled.

Measurement only outputs console messages when:

  • the verbose perfMark debug flag is active
  • AND when the measurement is above 3 significant digits (to reduce console spam).
ParameterTypeDescription
groupstring
idstring | null

Can be null when perfMarking is disabled

We clear related performanceMarks once the performanceMeasure is created, please let us know if there is a use case for retaining the marks.

perfMark.mjs
perfMark.mjs

module STORAGE
Release candidate

2022/11/16 Cookie functions DON'T handle edge case where the client has duplicate cookies of the same name under both hostOnly and non-hostOnly domains (example.com vs .example.com). PRs welcome.

https://blog.openreplay.com/fixing-javascript-performance-problems-devtools/

  • Up to 20 4Kb cookies can be allocated per domain. Up to 80KB could be added to every HTTP request and response from that domain so reducing cookie data should improve performance.
  • Around 5MB of data can used in localStorage and sessionStorage. It is retained on the client but the API is synchronous and blocks other processes while data is written or read. This could cause a performance issue if your site makes repeated calls during normal operation.
  • Limits are higher for IndexDB, Cache, and File APIs so there can be a performance hit when attempting to locate a particular item from a large store.

setCookie(name, [value], [days], [options])

Create, modify, or destroy a cookie.

ParameterTypeDescription
namestring
[value]string
[days]null | number

0 destroys the cookie, null / undefined creates a 'Session' cookie.

[options]object
[options.samesite = 'strict']string
[options.hostOnly = true]string

If true cookie can only be read by the exact domain that set it, not subdomains. Note creating duplicate cookies using both option states breaks things as only one can be managed, PRs welcome.

[options.path = '/']string

Advanced isolation feature. Cookies may behave unexpectedly due to SPA routing. Path must always start with a / slash ...right?

Cookies are sent with client requests to the server, thus alternative storage methods should be used when this is not your intention. We've seen instances in non-PageDriver projects where the client is so laden with cookies that their request headers overwhelm the remote server, causing them to respond inappropriately or with 400 Bad Request errors.

STORAGE.mjs

getCookie(name, [existence])β‡’ string | boolean

Get a cookie's value or check for its existence.

Returns: string | boolean β‡’ Returns false boolean when non-existent. Returns boolean existence argument is true. Otherwise returns cookie contents string.

ParameterTypeDescription
namestring

Cookie name

[existence]boolean

(default false) If true, returns cookie existence boolean, not value - useful for valueless cookies.

STORAGE.mjs
STORAGE.mjs

namespace UI

UI Elements

module MENU
Experimental

User navigation DOM mutators.

activateMenuItem(pathName)

Toggle active class and aria-current on navigation links.

ParameterType
pathNamestring

Categories: DOM, mutator

menu.mjs

updateMenu([containerID])β‡’ boolean

Mutate primary project navigation from project pages.

  • Pages with the data-nomenu attribute are ignored.
  • Transforms the following optional attributes into menu item attributes:
    • data-description into title attribute.
    • data-accesskey into accessKey attribute.
    • data-icon via icon.
ParameterTypeDescription
[containerID = dynamic]string

Menu element ID

Categories: DOM, renderer

menu.mjs
menu.mjs

module mobileMenu
Experimental

Mobile menu navigation.

Prepend navigation button for collapsing primary navigation on small devices (hamburger πŸ”)

Categories: DOM, UI

mobileMenu.mjs

module relatedElements(target, [composed], [activeClass])β‡’ number
Release candidate

Toggles a CSS class on DOM elements related to a form field name/value attribute in the target's element chain, useful for toggling visiblity of elements outside an input element's CSS scope.

Returns: number β‡’ Total activated elements.

This function finds elements with data-re attributes corresponding to the target's name and value, then activates matching elements while deactivating others.

  1. Potential follow up elements are located via the data-re dataset attribute, with a value of the target's name attribute, followed by an underscore [data-re="nameAttr_"].
  2. Elements exactly matching [data-re="nameAttr_valueAttr"] and/or [data-re="nameAttr__ANY"] are given the active CSS class.
  3. The active CSS class is removed from non-matching elements.
  4. When no exact matches are activated, elements matching [data-re="nameAttr__NONE"] are given the active CSS class. The double underscore is intentional to reduce value collision chance.

Examples:

  • Target: <input name="eventVis" value="public"> - Elements with the attribute data-re="eventVis_public" add the active CSS class, whilst all other elements data attributes starting with eventVis_ (such as eventVis_private) have the class removed.
  • <option value="0"> - The 'name' attribute of a parent <select> is found with the value 'partySize'. No elements with the attribute data-re="partySize_0" were found, so elements with the data-re="partySize__NONE" add the active CSS class, whilst all other elements with re data classes starting with partySize_ have the active class removed.
  • (see warning below!) <input type="checkbox" name="noValue"> - Because this input has no value, it will activate elements with data-re="noValue_on"
  • (see warning below!) <option>foobar</option> - Because this input has no value, it will activate elements with data-re="noValue_foobar"
ParameterTypeDescription
targetElement

Any DOM element with name and value attributes in the parent element chain.

[composed]boolean

Indicates whether the shadow root should be used (false, default), or a root node beyond shadow root (true).

[activeClass = active]string

CSS class name toggled on related elements.

2025/01/02 Input elements missing the value attribute default to 'on' value. <select> and <option> element value attribute default to innerText, which is most likely incompatible and will cause an error to be thrown! To prevent this ensure all potential event targets have value attributes, which may be valueless (ensure form submission still works as expected).

This originally located related elements via partial CSS class matching, however since CSS class attribute value ordering is not guaranteed, the only option was to locate them via the * wildcard attribute selector [class*="re_foo"]. Sadly this could generate many false positives and was replaced by the data-re dataset attribute. If CSS class ordering can be guaranteed this may be revisited.

Categories: UI, CSS

constant _META _META

relatedElements.mjs

init pdINIT

relatedElements.mjs
relatedElements.mjs

module UI_toast stdModule
Experimental

Generate 'pop up' notifications UI elements (aka Toasts) from a cloneable template.

Categories: UI, notification, template

makeToast(msg)

Generate a single 'toast' UI element.

ParameterType
msgstring
UI_toasts.mjs
UI_toasts.mjs

textarea([height], [width], [minHeight])

Dynamically resize textarea elements to content.

// TODO externalized to module? // TODO Output as --pd-textarea-height to allow custom styling?

ParameterTypeDescription
[height = true]boolean

When false sets --scrollHeight CSS variable on element.

[width]boolean
[minHeight = 50]number

Minimum fallback height

textarea.mjs

addCloseButton(el)β‡’ boolean
Needs refactor or depreciation

Create a close button ... refactor using future template system...?

Returns: boolean β‡’ True when closed via local storage

ParameterType
elHTMLElement

Categories: UI

closeable.mjs

countdown⇒ number
Experimental

Countdown timer UI.

Returns: number β‡’ Interval ID when activated. Null when element invalid or expired datetime.

appear class temporarily applied to the element on activation. Mutation interval is exactly 1 second.

Properties

  • dateTime dateString

    Future date/time. Accepts anything new Date() would.

  • [el = '#countdown'] HTMLElement

    Defaults to 'countdown' ID element.

  • [completedClass = 'complete'] string

    Completed CSS class is added when timer expires OR an invalid date is provided.

Categories: UI, timer

UI_countdown.js

spinner(changeState, [el])β‡’ number
Release candidate
Feedback needed πŸ’¬

Pending operation UI (spinner) control.

Returns: number β‡’ Current pending operations. Cannot be less than 0.

Modifies the --counter variable on the spinner element which can be used to not only hide/reveal the element itself, but dynamically adjust UX - such as the spinner's animation speed.

ParameterTypeDescription
changeStatenumber

Must be exactly 1, 0, or -1. Zero returns current number of pending operations without modifying state.

[el = document.getElementById('spinner')]HTMLElement

(EXPERIMENT) Change default target spinner element (#spinner)

Categories: UI, DOM

UI_spinner.mjs

typedef _META object
Needs discussion πŸ’¬

PageDriver standardized metadata property.

Properties

  • name string

    Human readable project/module name. Used by error reporter.

  • [stable = 1] 0 | 1 | 1.1 | 1.2 | 2 | 3

    Copies the NodeJS stability index.

    • 0 - Deprecated. The feature may emit warnings. Backward compatibility is not guaranteed.
    • 1 - Experimental. The feature is not subject to semantic versioning rules. Non-backward compatible changes or removal may occur in any future release. Use of the feature is not recommended in production environments. Experimental features leave the experimental status typically either by graduating to stable, or are removed without a deprecation cycle. Experimental features are subdivided into stages:
      • 1.0 - Early development. Experimental features at this stage are unfinished and subject to substantial change.
      • 1.1 - Active development. Experimental features at this stage are nearing minimum viability.
      • 1.2 - Release candidate. Experimental features at this stage are hopefully ready to become stable. No further breaking changes are anticipated but may still occur in response to user feedback. We encourage user testing and feedback so that we can know that this feature is ready to be marked as stable.
    • 2 - Stable. Compatibility with the PageDriver ecosystem is a high priority.
    • 3 - Legacy. Although this feature is unlikely to be removed and is still covered by semantic versioning guarantees, it is no longer actively maintained, and other alternatives are available.
  • version semver

    Semantic versioning code, see https://semver.org/.

  • [changelog = '/changelog.md'] string

    URL or local path.

  • [compliance] Array.<string>

    ['i18n-AA','a11y-AA','GDPR']...

  • [license] string

    Software license shortname. ex. 'Apache-2.0'

  • [category] Array.<string>

    Taxonomy, comma delimited categorization for documentation and reference lookups. First one listed could be considered 'primary'.

  • [stage] '1-Discuss' | '1-Needs_champion' | '2-WIP' | '2-Feedback_needed' | '3-Test' | '4-Document' | '5-Shipped' | '6-Needs_Refactor_or_Deprecation'

    Current production stage. This doesn't really belong in _META, but some sort of JSDOC/Typedoc metadoc? Should match PageDriver's own 'flow' labels closely.

  • edition 'CORE' | 'PRO' | 'PLATFORM'

    Target edition.

  • [authors] Array.<UUIDv4>

    (CONCEPT)

  • [targets] Array.<'Client', 'Server', 'Platform'>

    Relevant domains.

    • C Client / Browser / Front-end
    • S Server-side / Edge
    • P PageDriver Platform.
  • [parallel] boolean

    (CONCEPT) 'Multi-thread' / parallelization safe. Workers? πŸ€”

  • [priority] number

    (CONCEPT) Loading prioritization? idk

typedef stdModule object
Experimental

PageDriver standard module.

Properties

  • [_META] _META
  • INIT function
  • [boundInterface] BindInterface

    Instantiate via initBindInterface.

typedef stdReturn object
Experimental

Pagedriver standardized return object.

Properties

  • status number

    Status code

    • 0 Pending
    • 1 Partial success (see errors)
    • 2 Success
    • -1 Aborted
    • -2 Failure (see errors)
  • [result] object

    Generic return object. This is where your custom return data goes.

  • [log] object

    Required when status is 1 (partial success) or -2 (failure).

  • [log.errors] Array.<PDLog>
  • [log.warnings] Array.<PDLog>
  • [stats] object

    (EXPERIMENT) Optional operational statistics. Ex. bind stats (added/runs/disabled). Likely to be moved to dedicated stats/analytics module. Feedback welcome.

2025/09/06 'status' property type will change to exact numbers when JSDOC renderer is fixed, as it currently doesn't accept negative numbers. See https://github.com/jsdoc/jsdoc/issues/2131

typedef PDLog object
Experimental

Standard error response

Properties

  • code string

    Stable error code for i18n (e.g., 'ERR_MAX_DEPTH')

  • [message] string

    Fallback English message

  • [context] object

    Data for i18n interpolation

  • [path] Array.<string>

    Location in data structure where error occurred

  • [details] *

    Additional error-specific information

Likely to change!

😯 Uh oh...

A critical error has occurred preventing the site from loading. An error report has been automatically sent for investigation.

If this error is preventing you from completing an important action, please try again later.

404 Not Found

Oops! We can't find the requested page. If this error is preventing you from completing an important task, consider contacting us to resolve it.

We're sorry...

Something unexpected happened. If this error is preventing you from completing an important action, please contact us to resolve it.

500 Internal Server Error

Unauthorized access

You do not have the necessary permission to access this data.

401 Unauthorized

Oops!

We can't seem to find the requested resource. If this error is preventing you from completing an important action, please contact us to resolve it.

404 Not Found

Developer Preview

Welcome to the PageDriver 'Library v0' milestone preview. You've been invited at this critical juncture to provide early input and feedback about conventions and functionality. Intuitiveness is a primary project goal.

Please use the feedback tool zealously, for good or bad! Looking forward to reviewing your thoughts and opinions - Lorin

Please note

Getting help

Only community based support options are available at this time. Once officially launched, additional support options may become available.

Please fully review the official reference and guides before reaching out for assistance.

DEV 2025 Sep 18, 07:55am UTC πŸͺ²