Skip to content
An unified system to easily handle all your HTML/SVG contents, using simple event listeners.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
controllers
utils
.gitignore
.npmignore
content-handler.js
content-supervisor.js
license.md
package-lock.json
package.json
readme.md

readme.md

content-handler

Based on EventTarget, content handlers are an unified system to automate and simply await some DOM (HTML/SVG) contents from the server, ignoring the content origin, avoiding complex logic in your front code, even from AJAX requests or received as Server-Sent Events

Install

npm i content-handler

Quick start

Create your own content-handler based project to build your app/site in seconds!

Browsers support

This tool doesn't have any dependencies, but can need some polyfills to support the standard constructors on all browsers

How a content handler gets the contents?

  1. Full page loading
  2. Register your listeners, based on selector events (including your controllers)
  3. The ContentHandler triggers the listeners on the matching elements
  4. Your controllers are awaiting some user events (click on an anchor, submit on a form)
  5. The ContentSupervisor triggers the related controllers
  6. Fetching
  7. The ContentHandler parses the response in a DocumentFragment and returns to step 3.

Get the content handler for a document

Each handler handles a single document!

import ContentHandler from 'content-handler'

// If the document isn't provided, it uses the current window.document
const handler = ContentHandler.getByDocument(document)

Await elements from a handler

Loaded or not

import ContentHandler from 'content-handler'

// Await any main tag, already present in the document or loaded by the content
// handler, to apply the listener on it
ContentHandler
  .getByDocument()
  .addEventListener('main', ({element}) => {
    // Rewrites the content (just to illustrate, don't do that in real projects)
    element.innerHTML = `<h1>You're currently testing content-handler</h1>
    <p>This main is rewritten by the ContentHandler's listener, even if loaded
    or not, but in this case, this node <em>is already contained</em> by the
    document, at the page loading.</p>`
  })

Loaded only

The fetched elements are not in the current page, they need to be added on it.

import ContentHandler from 'content-handler'

// Await any main tag, but only loaded by the content handler, to apply the
// listener on it
ContentHandler
  .getByDocument()
  .addEventListener('body:first-child main', ({element}) => {
    // Select the current main
    const main = document.querySelector('main')
    
    // Replace the current main by the new one
    main.parentNode.replaceChild(element, main)
  })

Present only

The fetched elements are already in the current page, they don't need to be added on it.

import ContentHandler from 'content-handler'

// Await any main tag, already present in the document, to apply the listener on it
ContentHandler
  .getByDocument()
  .addEventListener('body:not(:first-child) main', ({element}) => {
    // Rewrites the content (just to illustrate, don't do that in real projects)
    element.innerHTML = `<h1>You're currently testing content-handler</h1>
    <p>This main is rewritten by the ContentHandler's listener, but only if this
    node <em>is already contained</em> by the document, at the page loading.</p>`
  })

Declare the controllers

A content handler doesn't make anything alone, to automate the fetching ou SSE listening, you need to tell him how to do it.

It also uses the handler.addEventListener() and should need some controllers (provided or custom) to define the request behavior, make some pre-validation, some client optimizations before to send it to the server, etc.

The provided controllers are generic as possible, not required but recommended, to avoid uncommon behaviors between browsers.

It shipped with 3 simple initial controllers with, for each, a default .selector to target the related elements (you can define yours), and a .listen() method to control the request.

anchor

Listen all the anchors at once!
import ContentHandler from 'content-handler/content-handler.js'
import anchor from 'content-handler/controllers/fetcher/anchor.js'
import cache from 'content-handler/controllers/fetcher/init/cache.js'
import headers from 'content-handler/controllers/fetcher/init/headers.js'
import credentials from 'content-handler/controllers/fetcher/init/credentials.js'
import mode from 'content-handler/controllers/fetcher/init/mode.js'
import redirect from 'content-handler/controllers/fetcher/init/redirect.js'
import referrer from 'content-handler/controllers/fetcher/init/referrer.js'

ContentHandler
  .getByDocument()
  .addEventListener(anchor.selector, anchor.listen([
    cache.default, // follow the default cache rule
    headers.xhr, // add the common AJAX header
    credentials.sameOrigin, // allow credentials only for the current origin
    mode.sameOrigin, // allow requests handling only for the current origin
    redirect.follow, // follows the redirects
    referrer.client // set request.referrer to "about:client" by default
], {/* optional env object */}))

form

Listen all the forms at once!
import ContentHandler from 'content-handler/content-handler.js'
import form from 'content-handler/controllers/fetcher/form.js'
import cache from 'content-handler/controllers/fetcher/init/cache.js'
import headers from 'content-handler/controllers/fetcher/init/headers.js'
import credentials from 'content-handler/controllers/fetcher/init/credentials.js'
import mode from 'content-handler/controllers/fetcher/init/mode.js'
import redirect from 'content-handler/controllers/fetcher/init/redirect.js'
import referrer from 'content-handler/controllers/fetcher/init/referrer.js'

ContentHandler
  .getByDocument()
  .addEventListener(form.selector, form.listen([
    headers.contentType, // detect the content type, if needed
    cache.default, // follow the default cache rule
    headers.xhr, // add the common AJAX header
    credentials.sameOrigin, // allow credentials only for the current origin
    mode.sameOrigin, // allow requests handling only for the current origin
    redirect.follow, // follows the redirects
    referrer.client // set request.referrer to "about:client" by default
], {/* optional env object */}))

sse

Listen all the event sources at once!
import ContentHandler from 'content-handler/content-handler.js'
import sse from 'content-handler/controllers/sse/sse.js'
import withCredentials from 'content-handler/controllers/sse/configuration/with-credentials.js'
import input from 'content-handler/controllers/sse/input.js'

ContentHandler
  .getByDocument()
  .addEventListener(sse.selector, sse.listen([
    input.dataset, // get the input url from "data-sse" attribute
    withCredentials.sameOrigin // allow credentials only for the current origin
  ], {/* optional env object */}))

Make your own controller

Making your own controller is really easy, it just a function, receiving a request config object and returning a new one based on it.

As a best practice, you never should modify the received object!

For asynchronous operations, in place of returning the new config object, you can simply return a Promise, resolving the config object.

The unique difference between AJAX/SSE config objects is the init/configuration property

A simple fetching controller
function customFetcherController (config) {
  const {input, supervisor} = config // {element, init, input, supervisor, env}
  
  // if the request doesn't targets the current origin, abort
  if (input.origin !== document.location.origin) {
    supervisor.abort()
  }
  
  return {...config}
}
A simple SSE controller
function customSSEController (config) {
  const {input, supervisor} = config // {configuration, element, input, supervisor, env}
  
  // if the request doesn't targets the current origin, abort
  if (input.origin !== document.location.origin) {
    supervisor.abort()
  }
  
  return {...config}
}

License

MIT

You can’t perform that action at this time.