-
Content based, anticore is a utility used to easily interact with any DOM server-side rendered (SSR) element, even received in AJAX or SSE. Of course, it also supports the
ServiceWorker
rendered elements to work offline. -
Contracts oriented, just like an event listener, you can write some contracts to define some process on any element matching a selector when anticore receives it, regardless of how many (various) views needs them.
-
Automated on demand, by a single function call, during the script initialization, it avoids the need to write any AJAX request, they are automatically deduced by the anchor/form attributes, on the user interaction related event (
click
/submit
). -
Very low front-end, weighing only 4Ko minified without dependency, it provides a strategy to incite you to keep your front code lightest as possible, letting your rendering strategy, and the control to the server side.
The front code is only used as an unobstrusive overlay to improve the user experience and let the client resources available for any other operations.
-
Reusable, through projects, you have a lot of as generic contracts to cover most of your needs at once... you can also write any project-specific contracts if needed, just based on the selector precision.
-
Server-side/framework agnostic, no specific server configuration needed, just to receive some DOM contents (HTML, SVG, ...). It can be used in conjunction to your favorite libraries & frameworks.
-
Easy to maintain, it doesn't chain all the process, each contract, ideally following the single responsibility principle, is simply replaceable/removable without the need to check the entire project.
-
TrustedTypes support for your own trusted contents.
anticore-starter (also available into ./demo)
Using NPM:
npm i anticore
Or using a CDN:
import anticore, { defaults, on, trigger } from 'https://unpkg.com/anticore@latest/index.js'
A contract is just a query selector associated function, a few like a routing route, where the selector is tested each time anticore receives a new content.
By default, once triggered, anticore triggers the contracts on any element matching a provided selector, event if that element is already in the document or received after.
import { on } from 'anticore'
// a contract example
on('a.query.selector', (matchingElement, serverResponseURL) => {
// processes to apply on the element
})
anticore loads the contents in a <body class="anticore">
(created internally), then you can use that class in your selector to target that
elements, specifically.
For example, we don't want to replace an already embedded element by itself.
import { on } from 'anticore'
// selects an element which is only received after the page load
on('.anticore a.query.selector', (matchingElement, serverResponseURL) => {
// processes to apply on the element
})
A useful selector prefix can also be body:not(.anticore)
to only target the elements initially loaded (non AJAX/SSE).
import { on } from 'anticore'
on('body:not(.anticore) a.query.selector', (matchingElement, serverResponseURL) => {
// processes to apply on a loaded element
})
Initialization (aka main.js
)
import { defaults, trigger } from 'anticore'
// an import to load all on your contracts
import './contracts.js'
defaults() // register the default contracts, to handle your anchors/forms without target attribute
trigger() // applies the contracts on the current document elements
A very first contract (aka view-switcher.js
)
When you create an AJAX navigation, the most common operation is to replace the current contents by the user requested contents.
To do that, commonly...
- you need to create a lot of functions to build some AJAX (
XMLHttpRequest
/fetch
) requests - you need to check if the response is the expected response (e.g. 403, 404, 500, ...)
- you need to write some functions to treat the received contents
- you need to update the user's history
... requiring an abstraction to avoid code repetitions
With anticore, it's really shorter/simpler!
Since the <main>
& <title>
are unique in a page, you can easily write that process with a unique contract.
import { on } from 'anticore'
// matching any received <main> / <title>
on('.anticore > main, .anticore title', (element, url) => {
// creating a selector based on the element node name
const selector = element.nodeName.toLowerCase()
// retrieving the same embedded element in the document
const current = document.querySelector(selector)
// replacing the embedded element by the new one
current.replaceWith(element)
if (selector === 'title') {
// updating the history
history.pushState({}, element.innerHTML, url)
} else {
window.scrollTo(0, 0)
}
})
Voilà, import it in your main.js
and your AJAX navigation is resolved for all your pages, at once!.
Caution: to improve the user experience and the performances, it's recommended to import the "switchers" as very last
contracts, in your main.js
.
import { defaults, trigger } from 'anticore'
// non-switching contracts here
import './contracts.js'
// then the view-switcher
import './view-switcher.js'
defaults()
trigger()
Example:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Initial title</title>
<script src="/assets/main.js" type="module"></script>
</head>
<body>
<main>
<h1>Initial title</h1>
<a href="/response">Load the response</a>
</main>
</body>
</html>
response
<title>Response title</title>
<main>
<h1>Response title</h1>
</main>
Resolves to
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Response title</title>
<script src="/assets/main.js" type="module"></script>
</head>
<body>
<main>
<h1>Response title</h1>
</main>
</body>
</html>
Useful to declare a contract to be applied for any element matching the selector, where:
selector
: a query selectorlistener
: a function to be applied on the matching elementselement
: the matching elementurl
: the url providing the node (can be empty, e.g. when the nodes are already in the current page)
import { on } from 'anticore'
// An listener can be async
on('body', async (element, url) => {
element.append(`Hello world from ${url}`)
})
A function that wraps 2 contracts
A contract that tells to anticore to fetch the href
of any clicked anchor
It uses the following selector
a[href^="http"]:not([download]):not([target]),
a[href^="."]:not([download]):not([target]),
a[href^="/"]:not([download]):not([target])
A contract that tells to anticore to fetch the action
of any submitted form
It uses the following selector
form:not([target])
Additionally, before the fetching, it removes the form descendants that have a error
class, useful to remove the error messages from a previous submission.
Useful to apply the declared contracts on the provided node
, where:
- optional
node
: the targeted node (element or current document)
import { trigger } from 'anticore'
trigger(document)
Useful to create your own DOM content fetchers, where:
request
: the Request instanceevent
: the event invoking thefetch
target
: the element invoking thefetch
(gets a.fetching
, until resolved)
** If no event
is provided, anticore just fetches without DOM parsing, no contracts triggering**
import { fetch } from 'anticore'
fetch(request, event, target) // fetches the contents & triggers the contracts on them
fetch(request) // just fetches
Once called, anticore attempts to fetch the response until its resolution.
If the user isn't connected, anticore retries after a delay (1 second by default)
If provided, anticore manages the event
like this:
- It checks if the event behavior is prevented by
Event.preventDefault()
(e.g. from a form validation contract) or if the bubbling is canceledevent.cancelBubble
. In that case, anticore just avoids doing anything - It prevents the event behavior
- It adds a
fetching
class on the element that triggers the event - It enqueues that request
- It really fetches the contents
- It triggers the contracts on them
- It removes the
fetching
class on the element that triggers the event
Useful to listen Server-Sent Events, where
url
: the URL to listenoptions
: theEventSource
optionsreviver
: a function to parse the DOM nodes from each message
import { sse } from 'anticore'
const eventSource = sse(url, options, reviver)
Useful to listen events, on any support (touch and/or not)
event
: the event typetarget
: anEventTarget
listener
: a function called each time the event triggersoptions
: theaddEventListener
options
It returns a function to remove the listener, without any arguments needed
import { listen } from 'anticore'
const forget = listen(event, target, listener, options)
The V4 is now promise-based, the V3 next()
is removed, if you need to await some async operations, just use an async
listener.
If your project is defining a strict Content-Security-Policy (CSP) you can need to add the following header/meta, to trust your DOM contents:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types anticore