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.
BIND
The backbone of unobnoxious extendability.
Binds / Bindable v1
. Should we permit multiple bind registries at all? Assume bind functions could switch via an optional registry
parameter. Please provide use cases for more than one registry.
- BIND should have as few external dependencies as possible to allow ultimate flexibility.
- Originally was a 'mixin' with
foo.add('action',()=>console.log('bar'))
style code, but it was refactored with the rationale of reduced duplication/memory consumption, and the ability to arrange bindings for dynamically added bind interfaces.
Any object may have a bind queue, but only the object itself should execute them. Currently we don't prevent remote bind execution, however prevention may be enforced in the future.
Binds allow the creation of interface-like structures.
Typedef
BindFilter function
Used to filter bound handlers for execution by bindExec.
function
Returns: boolean
β True when at least one exact matching trigger is found.
MUST return a boolean for success or failure.
Parameter | Type | Description |
---|---|---|
triggers | string | Used by the filter function receiving the current bind ID iteration. |
args | object | Arguments passed to bound function. |
iterator | number | Current location in the binds array index. Utilize in the following way: |
bindInterface object
A single interface within the bind registry.
object
Property | Type | Description |
---|---|---|
[mustBeNamed] | boolean | DEFAULT TRUE. When true, causes a type error to be thrown when an anonymous bind function is added. |
[add] | function | Additional processing when adding a binding. Can abort the process... by throwing an error :P |
[disable] | function | Additional processing when disabling bindings. Bindings are never deleted, only disabled - usually by setting |
filter | BindFilter | |
[execOnAdd] | object | (EXPERIMENT) Simulated execution |
list | Array.<Binding> | |
name | string | Self-reflection, primarily for debugging. |
addedAt | DOMHighResTimeStamp | |
[boundTo] | bindTarget | (CONCEPT) Would permanently bind the interface to whatever calls bindExec() first and deny execution anywhere else. This would be be non-trivial as it would likely rely on some sort of container object reflection. |
[boundAt] | DOMHighResTimeStamp | (CONCEPT) See above. |
[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. |
Example
// Sample containing a single binding
SAMPLE_INTERFACE:{
name:'SAMPLE_INTERFACE',
boundTo:null,
mustBeNamed:true,
boundAt:null,
createdAt:79.29999,
scopes:{
'#sampleID':[0],
'.sampleClass':[0],
},
list:[{
id:0,
runsRemaining:Infinity,
scope:['#sample','.sample'],
triggers:['click'],
handler:()=>{ console.log('hello world'); },
_STATS:{
runs:[123],
added:performance.now(),
disabled:null,
},
}],
};
bindRegistry Object.<string, bindInterface>
Registry contains all potential project bindings, grouped by interface name.
Object.<string, bindInterface>
Binding object
A single binding within a bindInterface potentially executed by BIND.bindExec.
object
was thinking of {Array.<CSSStyleRule.selectorText>} scopes (EXPERIMENTAL) for lookups? Could use code similar to EVENT add/disable bindings
Property | Type | Description |
---|---|---|
triggers | Array.<string> | Array of strings. Used by bind execution filter. |
[scope] | string | Array.<string> | (EXPERIMENTAL) secondary filtering layer, primarily intended for DOM based filtering, but who knows... |
handler | function | executed when |
id | number | array ID, useful for self removal. |
runsRemaining | number | Optional, default is |
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 |
stats.runs | Array.<DOMHighResTimeStamp> | Times run (get total via |
[stats.disabled] | DOMHighResTimeStamp | Created when bind is deactivated successfully |
bindTarget object
| function
Any non-primitive with binds
property.
object
| function
binds
property.
Commonly objects/functions.
Property | Type |
---|---|
boundInterface | binds |
[name] | string |
Typedef
boundInterface object
object
Property | Type | Description |
---|---|---|
[list] | Array.<Binding> | If this array doesn't exist, it is created on the first successful call to BIND/bind targeting the object. |
[mustBeNamed] | boolean | When true, causes a |
[add] | function | |
[disable] | function | |
[execOnAdd] | object | (EXPERIMENTAL) Simulated default details used during initial execution when |
Typedef
add function
Optional destination specific processing.
function
Mixes: BIND.bind
Able to abort bind 'addition' process.
Parameter | Type | Description |
---|---|---|
triggers | Array.<string> | string | Array of strings, or a string which is split by spaces and converted into an array. Used by bind execution filter. |
fn | function | Executed when triggers pass the |
[position] | number | (EXPERIMENTAL) Current execution index |
Class
Function
basicFilter() β¨BindFilter
β boolean
Default bind filter used by BIND.bindExec unless a custom filter function is provided.
BindFilter
β boolean
Implements: BindFilter
bind(target, triggers, handler, [options])β number
Register a function that executes when the triggers
parameter passes the target interface's BIND.bindExec filter.
number
triggers
parameter passes the target interface's BIND.bindExec filter.
Returns: number
β Bind ID (execution order)
Parameter | Type | Default | Description |
---|---|---|---|
target | string | Destination interface name. |
|
triggers | Array.<string> | string | Array of strings, or a string which is split by spaces and converted into an array. Used by bind execution filter. |
|
handler | function | Executed when triggers pass the |
|
[options] | object | ||
[options.runsRemaining] | number | Infinity | Runs decrease when bound function returns (bool) |
[options.execOnAdd] | boolean | (EXPERIMENT) Executes the function when added, against predefined trigger data. Was added to force mustatio of pre-exiting content. |
|
[options.scope] | Array.<string> | string | (EXPERIMENT) secondary execution filter, was originally intended for DOM based filtering. |
|
[options.position] | number | list.length | (EXPERIMENT) Execution index, negative integers are added from the end of the list. If you use this PLEASE let us know your use case! This was added very early on in the development of PageDriver and did not see any use. |
bindAll(scope, target, trigger, binds)β Array
Most useful in modules with many similar binds.
Array
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.
Returns: Array
β Bind IDs
Parameter | Type | Description |
---|---|---|
scope | string | |
target | bindTarget | |
trigger | string | function | |
binds | object.<function()> | Array.<function(), object> | a function, or an array containing the function and specific bind options |
bindExec(target, triggers, args, [meta])β boolean
Execute bound functions which passed filtration.
boolean
Returns: boolean
β True
if any bind was executed. This may change in the future to 'successful' calls only, feedback welcome!
Parameter | Type | Description |
---|---|---|
target | string | destination bind interface. |
triggers | string | Array.<string> | Used by the filter function receiving the current bind ID iteration. |
args | object | arguments passed to bound function |
[meta] | object | (EXPERIMENT) metadata passed to bound function, currently used by pd.OBSERVE |
initBindInterface(target, [options], [registry])β bindInterface
β
Load a bind interface, if the target interface doesn't exist it is created.
bindInterface
β
Parameter | Type | Default | Description |
---|---|---|---|
target | string | Bind interface name |
|
[options] | bindInterface | Merge/replace default bind interface properties. |
|
[registry] | bindRegistry | defaultBindRegistry | [EXPERIMENT] Don't change this. |
bindStats(interfaceName, [consoleTable])β Array
Array
Returns: Array
β Triggers, function name, total executions, time added, time disabled.
Parameter | Type | Description |
---|---|---|
interfaceName | string | |
[consoleTable] | boolean | additionally output via |
disableBindByID(target, id)β boolean
Deactivate a bind.
boolean
Do we replace this with an easier to use public facing scope based function? Does disabling by ID actually feel clunky...? Also, is the boolean return sufficient?
Returns: boolean
β Only returns true
when a bind is actually disabled, all other sitautions (such as no bindings, or already being disable return false
, see @fyi note.
Optional bindTarget
specific bindTarget.boundInterface.disable logic can abort the process.
Parameter | Type |
---|---|
target | bindTarget |
id | number |
CONTEXT
Contexts are flat (non-hierarchical) negatable CSS classes - although they may represent sub-states or roles.
Should this feature be renamed to CSS Flags or something similar?
Would be interesting if this somehow became scoped... π€
They are used to toggle common project elements relevant to different personas. For example, elements meant for a regular user, moderator, or manager. See ContextName for naming rules.
Property | Type | Description |
---|---|---|
globalContextStateReserved | Array.<ContextName> | Current reserved context names:
|
Typedef
ContextName string
string
Context class names:
- CANNOT begin with a hyphen (
-
). They are reserved for negated contexts. - MUST begin with an uppercase character (ex. 'A' not 'a'. 'Π' not 'Π»'. 'Δ' not 'Δ') to differentiate them from basic CSS classes.
- CANNOT exist on the {@link CONTEXT.globalContextStateReserved reserved Context list}.
If your programming locale doesn't have capitalization we recommend standardizing a basic latin script prefix (ex. CCγγ°γ€γ³γγ
, CCλ‘κ·ΈμΈ
) across your project.
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 CONTEXT.isValidContextName for the runtime validation.
Function
contextInit(contexts) β
Context Flag Class initializer, produces necessary CSS.
Parameter | Type |
---|---|
contexts | Object.<ContextName, boolean> |
isValidContextName(contextName)β boolean
β
Ensure potential context names are in proper form (non-negated and begin with a capital character).
boolean
β
Parameter | Type |
---|---|
contextName | string |
contextOff(contexts)β Object.<ContextName, ContextState>
Syntactic sugar for set( ... , false)
.
Object.<ContextName, ContextState>
set( ... , false)
.
Returns: Object.<ContextName, ContextState>
β Returns an empty object when no provided contexts are valid.
Parameter | Type |
---|---|
contexts | Array.<ContextName> | ContextName |
contextOn(contexts)β Object.<ContextName, ContextState>
Syntactic sugar for set( ... , true)
.
Object.<ContextName, ContextState>
set( ... , true)
.
Returns: Object.<ContextName, ContextState>
β Returns an empty object when no provided contexts are valid.
Parameter | Type |
---|---|
contexts | Array.<ContextName> | ContextName |
contextSet(contexts, [state])β Object.<ContextName, ContextState>
Context Flag base function.
Object.<ContextName, ContextState>
Returns: Object.<ContextName, ContextState>
β Returns an empty object when no provided contexts are valid.
Used by on/off/toggle helpers.
Parameter | Type | Description |
---|---|---|
contexts | Array.<ContextName> | ContextName | |
[state] | undefined | boolean |
|
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.
boolean
| Object.<ContextName, ContextState>
set( ... , undefined )
, EXCEPT when a SINGLE (1) Context is provided it only returns the boolean state result, allowing for cleaner inlining.
Toggle prevents context function idempotentness. Additional details in CONTEXT.contextSet.
Returns: boolean
| Object.<ContextName, ContextState>
β Note this function returns a boolean when only a single Context is provided.
Toggling an undefined Context creates it (in true
state).
Parameter | Type | Description |
---|---|---|
contexts | Array.<ContextName> | ContextName | One or more context flags |
Example
contextToggle('Foo'); // Returns a boolean
contextToggle(['Foo','Bar']); // Returns { ContextState, ... }
CLIENTS β
Reserved for PageDriver fullstack module
Property | Type | Description |
---|---|---|
sessionStatusInterval | number | |
csrf | string | Cross-Site Request Forgery token. |
IPCountry | string | Client's country code in ISO 3166 Alpha-2 format based on IP, if provided by CDN or host. |
_META | _META |
pd/USER object
β
object
β
Property | Type | Description |
---|---|---|
otpLength | number | |
avatar | Array.<string, string> | URL prefix and suffix |
private | object | Private details |
public | object | Public shareable details |
DEBUG
In-browser PageDriver debugger tooling.
I'm painfully aware how disgusting this code is! Refactoring PRs welcome π£
- IIFE?
- Debug module should retain state internally.
- 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.foo.warn('test warning');
Replacing:
if (pd.DEBUG && pd.DEBUG.verbose.foo) console.warn('test warning');
HOWEVER:
ACTUALLY using .bind would solve this issue... sort of mem leaky tho'...
in doing so you lose closure for additional funct, only echoing simpler messages to console... hmm
Adding your own verbose flags is as simple as if (pd.DEBUG && pd.DEBUG.verbose.myCodeFlag) console.log('foo!')
, no additional code required. Please be mindful as there are no 'reserved' flags or groups (yet), so ensure both:
- your output is as specific as possible to a particular flag, or set of flags.
- the verbose flag property name is specific enough.
Property | Type | Description |
---|---|---|
verboseActive | number | Total active verbose flags |
Function
debugCookie
codeSampleFromDOM β
Create code samples from the DOM itself π
- Ensure source code has an ID attribute
- Add
<pre data-source="ID_HERE"></pre>
, wheredata-source
value matches the source ID. - (optional) add
language-
class for syntax highlighting
Parameter | Type | Description |
---|---|---|
sourceID | string | idk about these tag names... |
markdown bindTarget
Markdown DOM Renderer
bindTarget
https://gitlab.com/evilumb/pagedriver/dev/-/issues/202. Eventually PDMD
tokenizer/renderer may extend or replace Marked.
Parameter | Type | Default | Description |
---|---|---|---|
el | string | HTMLElement | uses querySelector if not an element. |
|
[content] | string | Markdown content |
|
[src] | string | Attempts an XHR request for content, overrides |
|
[options] | object | ||
[options.headerIds] | boolean | true | |
[options.headerPrefix] | boolean | md- |
OBSERVER
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.
Property |
---|
execOnAdd |
Function
filter()
Returns false when bind trigger doesn't match an execution trigger.
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)
DOM
Property | Type | Description |
---|---|---|
DOMState | WeakMap.<Element.childNodes> | When DOM.populate mutates an element containing initial DOM content (non-empty) its held here. I leave it to the philosophers to decide if this means we use a VDOM π // TODO To be integrated/moved into core state controller once implemented? |
Function
compareWithOriginalState(element, originalState)β boolean
Compare a DOM element with its stored original state
boolean
Returns: boolean
β True if element matches original state
Parameter | Type | Description |
---|---|---|
element | Element | Current element |
originalState | DocumentFragment | Original state fragment |
classAdd(el, value)
Add CSS class (minimize mutation)
Should this return a boolean value?
Parameter | Type | Description |
---|---|---|
el | HTMLElement | |
value | string | Class name |
classRemove(el, value)
Remove CSS class (minimize mutation)
Should this return a boolean value?
Parameter | Type | Description |
---|---|---|
el | HTMLElement | |
value | string | Class name |
removeChildren(target)
The performant alternative to innerHTML=''
.
innerHTML=''
.
2022/03/15 Perf. between remove()
and removeChild()
is a wash.
Parameter | Type |
---|---|
target | Node |
appendChildren(target, origin, [clone])
Appends all target children from the origin node, cloned by default.
Parameter | Type | Default | Description |
---|---|---|---|
target | Node | ||
origin | Node | ||
[clone] | boolean | true | Uses |
escape(text)
Escape text using the browser's built-in functionality.
If this fails to escape content it means the browser is vulnerable, which is both crazy AND not our problem, horray!
Parameter | Type |
---|---|
text | string |
meta(name, [content])β HTMLMetaElement
Returns existing or creates new <meta>
tag in <head>
, optionally modifying content attribute.
HTMLMetaElement
<meta>
tag in <head>
, optionally modifying content attribute.
Could replace with a generic populator extension, bulk input array/obj via metaTag or some other key name...?
Parameter | Type |
---|---|
name | string |
[content] | string |
link(rel, [href], [media])β HTMLLinkElement
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.
HTMLLinkElement
<link>
element within <head>
by rel
attribute, UNLESS it's a stylesheet - which also matches href
attribute.
Initially created to modify the 'canonical' relationship attribute - maybe removed and refactored into the SPA location router. Tell us about other practical uses to prevent this from happening! Maybe useful for setting preload/preconnects as part of build tooling?... hmmm
Parameter | Type | Description |
---|---|---|
rel | string | Relation type https://html.spec.whatwg.org/multipage/links.html#linkTypes |
[href] | string | |
[media] | string |
escapeCSS(inputStr, [options])
Wraps native CSS.escape()
function to warn developers if input is modified when escaped.
CSS.escape()
function to warn developers if input is modified when escaped.
Parameter | Type |
---|---|
inputStr | string |
[options] | object |
[options.throwIfEmpty] | boolean |
loadScript(src, [options])β Promise
Append a script to the DOM and return loading status to an optional callback.
Promise
Should we allow separate onload/error/abort callbacks? callback||onload etc... Perhaps BINDable as an alternative (either/or, but never both)?
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 likely unless a specific use case is found.
Parameter | Type | Default | Description |
---|---|---|---|
src | string | relative or exact URL. |
|
[options] | object | ||
[options.callback] | function | Passed |
|
[options.async] | boolean | true | Asynchronous loading |
[options.defer] | boolean | Deferred - executed after the DOM has been parsed, but before firing DOMContentLoaded |
|
[options.remove] | boolean | true | Remove script element after loading attempt π§Ή |
[options.crossorigin] | string | If unspecified |
|
[options.integrity] | string | ||
[options.module] | boolean | true | Sets |
[options.nomodule] | boolean | Sets |
loadStyle(styleTagID, options)β HTMLStyleElement
CSS Style loader.
HTMLStyleElement
Should scoping be added to go with modularity?
Creates a new element if no matching style element exists.
Parameter | Type |
---|---|
styleTagID | Element.id |
options | Object |
options.innerText | HTMLStyleElement.innerText |
populate(root, obj, [options])
Populate DOM nodes from JS objects, recursively.
Non-object keys are matched to DOM elements via the following rules:
- Elements with 'name' attribute:
- Inexact key match via
[name$="key"]
- (When nested) Exact key match
[name="parentKey[key]"]
within nested arrays/objects
- (When nested) Exact key match
- Elements with any class names starting with
pd_
- (WIP) Class directly matching the nested child object key's name (no 'pd_')
pd_*
classes are preferred over data attributes because:
- CSS selectors for formatting are cleaner (
#pages .pd_chainImg
vs#pages [data-pd=chain_IMG]
) - Classes come out ahead of data attributes in query performance tests
Default string
, number
, boolean
type value mutations:
<input>
and<select>
form fields (radio, checkbox, select)<time>
addrelative
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 (or via
pd.DOM.reset
), allowing for 'default content' in non-input elements. This MAY become a non-default boolean option in a future release - given feedback.
Built-in object
mutation keys:
_readOnly[]
applies readonly attribute toINPUT
,TEXTAREA
elements, andreadonly
CSS helper class to parentLABEL
element if one exists. Appliesreadonly
CSS class directly to any other located elements._disabled[]
Applies disabled attribute toINPUT
,TEXTAREA
,BUTTON
elements, as well asdisabled
helper CSS class to parentLABEL
if it exists. Appliesdisabled
CSS class to any other located elements.
Parameter | Type | Default | Description |
---|---|---|---|
root | string | Element | input name prefix eg. prefix_Item, or specific DOM element. For better performance specify a Node to allow direct querying when populating a single element and its children, a string is useful when targeting multiple root elements. // DOC unsure if Node is sufficient, Object? |
|
obj | Object.<string, any> | Object containing new state. As of 2022/07/20 DOM.populate doesn't allow naked (unnested) arrays, because keys are necessary for DOM query matching. Place the array inside an Object, or (not recommended) hijack private |
|
[obj._readOnly] | Array.<string> | (EXPERIMENT) Special built-in key. |
|
[obj._disabled] | Array.<string> | (EXPERIMENT) Special built-in key. |
|
[options] | object | ||
[options.maxDepth] | number | 10 | Maximum allowable nested object recursion depth. If reached populator 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 |
|
[options.template] | 'append' | 'replace' | 'replace' | 'replace' removes existing elements generated from templates, 'append' retains them. |
[options.cascade] | Boolean | (EXPERIMENT) (DANGER) Allow simulated interactions to cascade naturally, test to prevent infinite loops! |
|
[options.event] | Event | (EXPERIMENT) Likely to be removed. Skips processing of elements matching original event target. Improve UX by allowing populator to skip current target element when populating due to a user event. |
|
[options.nested] | string | (PRIVATE) Used internally during nested object recursion, usually grouped form inputs. |
stringToSelector(str, [extras])β string
β
Simple string to CSS selector text transformer.
string
β
actually CSSStyleRule['selectorText']
for various params, but JSDoc rendering chokes.
Returns: string
β ordered by CSS specificity
Primarily for transforming object keys into useful generic CSS selectors.
Parameter | Type | Default | Description |
---|---|---|---|
str | string | ||
[extras] | string | '' | Appended after to each selector |
elementTitle(el)β string
| boolean
Human readable title from data-name
attribute or derived via id
attribute.
string
| boolean
data-name
attribute or derived via id
attribute.
Returns: string
| boolean
β invalid element returns false
boolean. Empty string when no useful attribute is found.
When no data-name
attribute is found a string is created from the id
attribute by replacing -
and _
with spaces, trimming whitespace, and capitalizing the first character.
Parameter | Type |
---|---|
el | HTMLElement |
queryFilter(selector, [root], [nestSelectors])β Array.<Element>
Provides elements located within root(s) and recursion chain.
Array.<Element>
actually CSSStyleRule['selectorText'] not just basic strings
Returns: Array.<Element>
β Actually could be any sorta Element, no?
Parameter | Type | Default | Description |
---|---|---|---|
selector | string | ||
[root] | Element | string | document.body | Selector string allows for multiple roots! |
[nestSelectors] | Array.<string> | [] |
querySelectParent() β
Moved from query.mjs as reference for refactor since it references
Deprecated
reset(parentSelector, [options])
Reset DOM/input state of a node's children
Deprecated
Parameter | Type | Description |
---|---|---|
parentSelector | Object | string | |
[options] | object | form, readonly, inputs, defaults, hidden, selectNone. If none are used, all options are true. |
options.form | boolean | reset all inputs in a form |
options.toDefault | boolean | input fields set to default value |
options.toInit | boolean | restores non-form elements to initial state. See #166 |
options.lists | boolean | sponsors / interests? Removes from DOM entirely. |
options.readonly | boolean | remove |
options.selection | boolean | revert checked/selected items to blank state |
options.defaults | boolean | revert checked/selected items back to original state |
options.hidden | boolean | hidden element values EXCEPT 'readonly' |
options.selectNone | boolean | simulates click of inputs with "none" value (usually radio inputs) |
options.inputBackground | boolean | text input backgrounds |
temporary(target, attribute, [value], [options])β DOM.temporary.domTemporaryState
Temporarily mutate HTML element attributes.
DOM.temporary.domTemporaryState
Useful UX Helper that can be used to temporarily add animation classes, disabling buttons, etc.
Parameter | Type | Default | Description |
---|---|---|---|
target | HTMLElement | ||
attribute | string |
|
|
[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 |
Typedef
textarea([height], [width], [minHeight])
Dynamically resize textarea
elements to content.
textarea
elements to content.
// TODO externalized to module? // TODO Output as --pd-textarea-height to allow custom styling?
Parameter | Type | Default | Description |
---|---|---|---|
[height] | boolean | true | When false sets |
[width] | boolean | ||
[minHeight] | number | 50 | Minimum fallback height |
EVENT
ideally EVENT becomes an interface of sorts that allows it be a delegator in other instances such as Node/native?
Property | Type | Description |
---|---|---|
isKbd | boolean | Is this a keyboard event?
|
priorEvents | Array.<(Event|undefined)> | Previously captured events (currently hardcoded as 10). |
lastEventTrusted | boolean | null | Browser native For whatever reason
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 |
Function
add(eventName) β¨add
β
Adds document
element event listeners as necessary
add
β
document
element event listeners as necessary
Extends: BIND.bind
Implements: add
Parameter | Type | Description |
---|---|---|
eventName | Array.<string> | Actually <Event['type']> or similar |
disable(eventName) β¨disable
β
Removes document
element event listeners when no more EVENT binds utilizing them remain.
disable
β
document
element event listeners when no more EVENT binds utilizing them remain.
Extends: BIND.disableBindByID
Implements: disable
Parameter | Type | Description |
---|---|---|
eventName | Array.<string> | Event['type'] |
filter(_trigger, event, iterator) β¨{}
β boolean
β
{}
β boolean
β
Implements: {}
Parameter | Type |
---|---|
_trigger | * |
event | Event |
iterator | Number |
eventHandler(event)
DOM/client Event handler.
I have a suspicion I poorly abstracted the Event API here and would love to refactor more tightly into the existing Event API if reasonable sensible. The core reason of this handler's existence is to execute bound functions matching incoming events.
EVENT binds must be named functions.
- Set EVENT.lastEventTrusted helper
- Set EVENT.isKbd helper
- Event binding filter returns a boolean based on the following conditions:
- The Event source is trusted OR allowed to cascade.
- AND if bind is scoped, it is the Event target OR within the target (closest).
- AND Event type matches one of the bind's triggers.
- AND ANY of the following:
- (KLUDGE) Function name begins with
_
, which is considered untargeted 'global'. - 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.
- OR function name matches the
name
attribute in the parent Element chain. - (EXPERIMENT/HACK) OR if target is an anchor link, href attribute beginning with
#bind-
.
- (KLUDGE) Function name begins with
Parameter | Type |
---|---|
event | Event |
event/simulateEvent
Simulate user events via synthetic event
Should simulation functions provide return values?
HELPER β
PageDriver CORE team internal helper functions.
They may be removed or have breaking changes between minor versions.
Function
convertMS(ms)β object
Convert milliseconds to { days, hours, minutes, seconds } object.
object
Parameter | Type | Description |
---|---|---|
ms | number | milliseconds |
formatTimeAgo(date)β string
Returns a localized, relatively formatted date string
string
Modified version of formatter by Kyle Cook
Parameter | Type |
---|---|
date | Date |
importLoader(src)β Promise
Imports a script into a globalThis
variable named via its file name.
Promise
globalThis
variable named via its file name.
Returns: Promise
β import()
promise.
Primarily intended for developer exploration of scripts via a live console such as DevTools, especially modules normally hidden from the global scope. How annoying!
- Multiple exports: module is assigned.
- Single export: single export assigned.
Parameter | Type | Description |
---|---|---|
src | string | '/src/' prepended. |
mergeDeep(objects)β object
Performs a deep merge of objects and returns new object.
object
Returns: object
β New object with merged key/values
Doesn't modify objects (immutable) and merges arrays via concatenation.
2022/09/06 Being used for merging settings/remote at initiation, likely useful for later sync work :)
Consider https://github.com/jhildenbiddle/mergician for advanced merges
Parameter | Type | Description |
---|---|---|
objects | object | Objects to merge |
simpleHash(str)β number
General-use hasher.
number
Returns: number
β Integer hash (currently CRC24)
Using this for cryptography will result in being shot from a cannon into low earth orbit β¨π
Parameter | Type |
---|---|
str | string |
truthy(value)β boolean
Coerce human readable truthy values to boolean (true/false)
boolean
Parameter | Type |
---|---|
value | boolean | string | number |
INIT
Self-deleting project initializer.
The following is likely out of date, eventually this list will be automatically rendered based on inline @step
and @stepOpt
s:
- Start performance measurement
- Debug state check, when
debug
cookie is present load and initialize debug tools.- Otherwise, execute optional
app.notDebugging()
function (to do things such as displaying a default console message/warning)
- Otherwise, execute optional
- Setup remote logging. If
pd.SITE.ENV
isProduction
, initialize logger based onpd.SITE.remoteReporting
object. Can be forcibly enabled viaforceLogging
cookie in other environments - Set pd.SITE DOM references and booleans
- Context class setup
- i18n initialization
- pd.DOM elements (most removed for upcoming modularity)
- closeable
- Attempt initializing app.js via
app.INIT()
(/w perf measurement) - pd.SITE.location
- End performance measurement
- Destroy
pd.INIT
modal
Function
activateModal(req)β boolean
Activate a modal
boolean
Parameter | Type | Description |
---|---|---|
req | string | HTML ID attribute |
close([_event], [force])β boolean
Attempt closing modal.
boolean
Returns: boolean
β false
when no modals exists, no modal is active, or if the modal cannot be closed without force.
If the modal's first child isn't a closeBtn
button, it will fail and temporarily add the attention
class to the modal element. force
is true.
Parameter | Type | Description |
---|---|---|
[_event] | Event | Currently unused. |
[force] | boolean | forcibly close modal that does not have |
lastIsLocalSrcResult Array.<string, boolean>
β
Memoization short circuit for pd.NET.isLocal
.
Array.<string, boolean>
β
pd.NET.isLocal
.
I never checked if this actually improves performance, but it probably helps? Trivial to implement and just as trivial to remove if deemed unnecessary! [src URL, isLocal result]
submitTimeout delayState
| null
β
Internal placeholder for outstanding form submit button mutations, likely to be refactored to allow multiple concurrent form submissions.
delayState
| null
β
NET
Network related functions and helpers.
Function
getAllResponseHeaders(xhr)β Object.<string, string>
| Object
Maps XHR response headers into a KV object.
Object.<string, string>
| Object
Returns: Object.<string, string>
| Object
β Empty object when no header values are returned.
Parameter | Type |
---|---|
xhr | XMLHttpRequest |
isLocal(src)β boolean
boolean
Could src
be considered local? Note domain-like strings, such as hostnames WITHOUT a protocol such as 'example.com' return true
as it could be a valid local path. Returns false
when src
string begins with 'https://', 'http://' or '//', UNLESS it matches the current window.location.origin
.
Parameter | Type | Description |
---|---|---|
src | string | URL, path, etc... |
Example
islocal('/example'); // true
islocal('example.com'); // true
islocal('https://example.com'); // false
islocal('//example.com'); // false
xhr(src, [callback], [options])β XMLHttpRequest
Perform an XHR, optionally returning status/object to callback
XMLHttpRequest
Returns: XMLHttpRequest
β passes responseObj, src URL to provided callback
- Automatically applies
pd-csrf
andpd-app-version
headers to local requests - Under
Development
ENV thenoStore
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!
Parameter | Type | Default | Description |
---|---|---|---|
src | string | Remote URL or local path. Specificity is important! Project's API path is prepended for relative paths NOT starting with a slash |
|
[callback] | function | ||
[options] | object | ||
[options.type] | 'json' | 'head' | 'html' | 'md' | json | Expected response type. NOT XMLHttpRequest built-in enum, may eventually be extendable via binds. |
[options.spinner] | boolean | true | Display spinner |
[options.timeout] | number | 10000 | 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 |
prior Array.<Array>
β
Current/prior page IDs & X/Y scroll positions
Array.<Array>
β
// HACK all code relating to 'prior' is a god damn mess and needs to be refactored for readability/more use cases
pd
PageDriver core library entry point.
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.
PageDriver was created to solve the following pain points
- Too much damn boilerplate, keep code coherent and clean.
- Bring back some separation of concerns (HTML/CSS/JS). They can be in the same file as part of simple single-file components, but not so intermingled.
- Callback hell, solved by 'bind' buses (bus maybe the wrong term?)
- Minimize and clean call stack to assist in debugging.
- DOM hydration concerns, I want it to follow state on demand!
- Lack of accessibility. A11y is normally out-of-scope for small to medium sized projects, I want to change that.
- Requiring a virtual DOM. It does have a use case for repeating elements scrolling outside the viewport (such as tables). I've done testing and it's unnecessary for the majority of projects. Let's keep things close to vanilla!
Contributor's guide
- Please review https://gitlab.com/evilumb/pagedriver/dev/-/issues/125
- If you're reading this, you should join our #development Discord channel!
- Useful daily references: MDN Web Docs, Regex101, Can I Use, HTML symbols
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 (usuallybindExec
)- Once stable, code containing
WIP
,FIXME
andTODO
comment flags will be automatically rejected.
CORE DEVELOPER COMMENT FLAGS (don't commit these unless you're a CORE dev)
- Add
FIXME
s to critical, high priority code that you are unable to fix yourself.FIXME
code CANNOT be submitted for stable releases! TODO
are low priorityFIXME
s 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 ESM 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 where possible. Spread is 93.05% CIU, 1% slower, and doesn't improve code readability that much. - 2021/03/10 [Documentation] Using Regex? Register at https://regex101.com, and provide a link in code commentary along with some match/non-matching samples if possible.
- 2021/04/03 [Mutation] When directly adding/removing CSS classes, they trigger
class
attribute mutation even if nothing actually changes (per spec). Usepd.DOM
class helpers or checkclasslist.contains
first if mutation is an issue (pd.OBSERVER may ignore these in the future). - 2021/04/06 [Consistency]
options={}
for parameter options, thenObject.assign()
defaults toparams
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). JSDOCoptions
props, notparams
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;
, orconsole.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 inpd.EVENT.simulate
.
Property |
---|
DEBUG |
Namespace
SITE object
Most properties will permanently move to other CORE objects once v1 lands
object
Property | Type | Description |
---|---|---|
ENV | 'Production' | 'Development' | Should probably have some overview of the differences eh? :p |
contexts | Array.<string> | Context classes / flags |
[domain] | string | |
[path] | string | Root path of PageDriver instance |
api | string | Local API path/URI, prepended to local API requests |
title | string | Site title |
siteTitleFirst | number | Site title appears first, useful for RTL |
[titleDivider] | string | site and page title separator in window title |
header | HTMLElement | First found |
primaryNav | HTMLElement | First found |
mainRegion | HTMLElement | Primary content region ( |
fixedScroll | boolean | Does the |
pages | HTMLCollection |
|
modals | HTMLCollection |
|
remoteReporting | object | Reporting provider (only sentry.io supported at this time) |
remoteReporting.endPoint | string | URL |
[remoteReporting.integrity] | string | integrity hash |
remoteReporting.src | string | reporter bundle location |
[description] | string | Default site-wide meta tag. Used when none available at page level. |
[keywords] | string | Default site meta tag. |
deprecating β
Deprecated
Function
swappable(target) β
Toggle .swapped
class on matching elements within local .swappable
class node
.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.
Parameter | Type | Description |
---|---|---|
target | string | Input element (Checkbox/Radio) |
selected()
Toggles .selected
CSS class on parent element of checked
input (checkbox/radio) and toggles related IDs within nearby .swappable
element.
.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??
app/settings
STORAGE
Function
setCookie(name, [value], [days], [options])
Modify a cookie, yum! πͺ
Parameter | Type | Default | Description |
---|---|---|---|
name | string | ||
[value] | string | ||
[days] | null | number |
|
|
[options] | object | ||
[options.samesite] | string | strict | |
[options.path] | string | '/' | Advanced isolation feature. Cookies may behave unexpectedly due to SPA routing. Path must always start with a / slash ...right? |
getCookie(name, [existence])β string
| boolean
Get cookie value or check existence
string
| boolean
Returns: string
| boolean
β Returns false boolean when non-existent. Returns boolean existence
argument is true. Otherwise returns cookie contents string.
Parameter | Type | Description |
---|---|---|
name | string | Cookie name |
[existence] | boolean | (default false) If true, returns cookie existence boolean, not value - useful for valueless cookies. |
_META object
PageDriver metadata standard
object
See https://gitlab.com/evilumb/pagedriver/dev/-/issues/126
Unsure about the following:
- cats:['login','security'],
- requires:['foo','bar'],
- suggests:['group'],
Property | Type | Description |
---|---|---|
name | string | Human readable project/module name |
version | semver | Semantic versioning code, see https://semver.org/ |
changelog | string | |
compliance | array.<string> | 'i18n-AA','a11y-AA','GDPR'... |
license | string | shortname ex. 'Apache-2.0' |
stable | boolean | Considered unstable when undefined or false. |
[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? |
edition | 'CORE' | 'PRO' | 'PLATFORM' | Intended target edition |
authors | array.<UUIDv4> | |
domains | array.<'Library', 'Framework', 'Platform'> | Relevant domains. Workers? π€ |
[parallel] | boolean | |
[priority] | number | (?) Loading prioritization? idk |
addButton(el)β boolean
Create a close button ... refactor using future template system...?
boolean
Returns: boolean
β True when closed via local storage
Parameter | Type |
---|---|
el | HTMLElement |
π― 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.
Oops!
We can't seem to find the requested page. If this error is preventing you from completing an important task, please contact us to resolve it.