@sliphua/pjax

    0.13.0 • Public • Published

    ES6+ Pjax

    MoOx/pjax ES6+ Edition.

    Build Status

    Easily enable fast AJAX navigation (Fetch + pushState)

    A maintained, modern, and smaller (~3 KB gzipped minified) version of Pjax.

    Pjax aims to deliver app-like browsing experiences. It doesn't rely on other libraries like jQuery.

    🐿️ Jump to Usage, Options, Status, Q&A, or Contributing Guide.

    Installation

    Choose a source

    jsDelivr

    Visit https://www.jsdelivr.com/package/npm/@sliphua/pjax.

    npm

    Install package @sliphua/pjax:

    npm install @sliphua/pjax

    Git

    Clone this repo and install:

    git clone https://github.com/PaperStrike/Pjax.git
    cd Pjax
    npm install

    Pick a script in dist folder

    pjax.js or pjax.min.js

    To declare Pjax as a global variable, link one of them in a separate <script> tag as:

    <script src="./dist/pjax.js"></script>

    pjax.esm.js or pjax.esm.min.js

    To use Pjax as an ECMAScript module, import the default value from one of them as:

    import Pjax from './dist/pjax.esm';

    Each script file has a related .map file, known as the source map, for debugging. Browsers won't fetch them without DevTools opened, so it won't affect your users' experiences. For more information, click the link to find out.


    What Pjax Does

    In short, ONE fetch with a pushState call.

    Pjax fetches the new content, switches parts of your page, updates the URL, executes newly added scripts and scroll to the right position without refreshing the whole thing.

    How Pjax Works

    1. Listen to every trigger of anchor elements.
    2. Fetch the target page via fetch.
    3. Render the DOM tree using DOMParser.
    4. Check if every defined selector selects the same amount of elements in current DOM and the new DOM.
      • If no, Pjax uses standard navigation.
      • If yes, Pjax switches the elements in index order.
    5. Update current URL using pushState.
    6. Execute all newly-added or targeted scripts in document order.
    7. Scroll to the defined position.

    Overview

    Just designate the elements that differs between navigations. Pjax handles all the rest things.

    Consider the following page:

    <!DOCTYPE html>
    <html lang="">
    <head>
      <title>My Cool Blog</title>
      <meta name="description" content="Welcome to My Cool Blog">
      <link href="/styles.css" rel="stylesheet">
    </head>
    
    <body>
      <header class="the-header">
        <nav>
          <a href="/" class="is-active">Home</a>
          <a href="/about">About</a>
          <a href="/contact">Contact</a>
        </nav>
      </header>
    
      <section class="the-content">
        <h1>My Cool Blog</h1>
        <p>
          Thanks for stopping by!
    
          <a href="/about">Click Here to find out more about me.</a>
        </p>
      </section>
    
      <aside class="the-sidebar">
        <h3>Recent Posts</h3>
        <!-- sidebar content -->
      </aside>
    
      <footer class="the-footer">
        &copy; My Cool Blog
      </footer>
    
      <script src="onDomReady.js"></script>
    </body>
    </html>

    We want Pjax to intercept the URL /about, and replace .the-content with the resulting content of the request.

    Besides, we would like to replace the <nav> to show the active /about link, as well as update our page meta and the <aside> sidebar.

    So all in all we want to update the page title and meta, header, content area, and sidebar, without reloading styles or scripts.

    We can easily achieve this by telling Pjax to use such CSS selectors:

    const pjax = new Pjax({
      selectors: [
        'title',
        'meta[name=description]',
        '.the-header',
        '.the-content',
        '.the-sidebar',
      ],
    });

    Now, when someone in a Pjax-compatible browser clicks an internal link on the page, the content of each of the selectors will transform to the specific content pieces found in the next page.

    Magic! For real! Nothing server-side!

    Compatibility

    Browser Supported versions Release date
    Chrome 66+ Apr 17, 2018
    Edge 79+ Jan 15, 2020
    Firefox 60+ May 9, 2018
    Opera 53+ May 10, 2018
    Safari 12.2+ Jul 22, 2019

    Usage

    new Pjax([options])

    The most basic way to get started.

    When instantiating Pjax, you can pass options into the constructor as an object:

    const pjax = new Pjax({
      // default value, listens on all <a>
      defaultTrigger: true,
      selectors: [
        'title',
        '.the-header',
        '.the-content',
        '.the-sidebar',
      ],
    });

    This will enable Pjax on all links, and designate the part to replace using CSS selectors 'title', '.the-header', '.the-content', '.the-sidebar'.

    In some cases, you might want to only target some specific elements or specific moments to apply Pjax behavior. In that case:

    1. Set defaultTrigger to false. This will make Pjax not listen to link (<a> or <area> element) click and keyup events.
    2. Use loadURL method of the Pjax instance to let Pjax handle the navigation on your demand.
    // Set `defaultTrigger` to `false`.
    const pjax = new Pjax({ defaultTrigger: false });
    
    // Use `loadURL` on your demand.
    document.addEventListener((event) => {
      const { target } = event;
      if (someCondition) {
        event.preventDefault();
        pjax.loadURL(target.href);
      }
    });

    loadURL(url, [overrideOptions])

    Calling this method aborts the last call (if unfinished) and navigates to the given URL in Pjax way.

    Any error other than AbortError leads to a normal navigation (via window.location.assign). Note that AbortError happens on fetch timeout, too.

    const pjax = new Pjax();
    
    // use case 1
    pjax.loadURL('/your-url').catch(() => {});
    
    // use case 2 (with options override)
    pjax.loadURL('/your-url', { timeout: 200 }).catch(() => {});
    
    // use case 3 (with further action)
    pjax.loadURL('/your-url')
      .then(() => {
        onSuccess();
      })
      .catch(() => {
        onAbort();
      });

    weakLoadURL(url, [overrideOptions])

    This method behaves almost the same as loadURL, except that it won't use normal navigation on errors — it throws regardless of the error's type.

    Useful when you need to handle all the errors on your own.

    const pjax = new Pjax();
    
    // use case
    pjax.weakLoadURL('/your-url')
      .then(() => {
        onSuccess();
      })
      .catch((e) => {
        onError(e);
      });

    switchDOM(url, [overrideOptions])

    This method accepts the URL string of the target document, set up the fetch timeout, and sends the request with Pjax headers. It also takes the responsibility of firing Pjax related events.

    It returns a promise that resolves when all the following steps have done:

    • Switch elements selected by selectors option.
    • Call pushState to update the URL.
    • Focus the first autofocus if previous focus has gone.
    • Execute all newly-added or targeted scripts in document order, and wait for the blocking ones (e.g., inline scripts).
    • Scroll to position given by scrollTo option.

    reload()

    A helper shortcut for window.location.reload, a static member of Pjax.

    Pjax.reload();

    Options

    selectors (Array, default: ['title', '.pjax'])

    CSS selector list used to target contents to replace.

    const pjax = new Pjax({
      selectors: [
        'title',
        '.the-content',
      ],
    });

    If a query returns multiples items, it will just keep the index.

    Every selector, in the current page and new page, must select the same amount of DOM elements. Otherwise, Pjax will fall back into normal page load.

    switches (Object, default: {})

    This contains callbacks used to switch old elements with new elements.

    The object keys should match one of the defined selectors (from the selectors option).

    Examples:

    const pjax = new Pjax({
      selectors: ['title', '.Navbar', '.pjax'],
      switches: {
        // default behavior
        'title': Pjax.switches.default,
        '.the-content': async (oldEl, newEl) => {
          // How you deal with the two nodes.
        },
        '.pjax': Pjax.switches.innerText,
      },
    });

    Callbacks may return a promise to make Pjax recognize when the switch has done. Newly added scripts execute and labeled scripts re-execute after all switches finishes.

    Existing Switch Callbacks

    • Pjax.switches.innerHTML — Replace HTML contents by using Element.innerHTML.
    • Pjax.switches.textContent — Replace all text by using Node.textContent.
    • Pjax.switches.innerText — Replace readable text by using HTMLElement.innerText.
    • Pjax.switches.attributes — Rewrite all attributes, leaving inner HTML untouched.
    • Pjax.switches.replaceWith — The default behavior, replace elements by using ChildNode.replaceWith.

    Creating a Switch Callback

    Your callback function can do whatever you want, as long as you keep the amount of the elements selected by the selectors option remain the same.

    In this example, the current class represents the only switching element, so that the switch elements' amount won't change. Before the returned promise resolves, Pjax will neither update the URL, execute the script elements nor scroll the page.

    const pjax = new Pjax({
      selectors: ['.sidebar.current'],
    });
    
    const customSwitch = (oldEle, newEle) => {
      oldEle.classList.remove('current');
      newEle.classList.add('current');
      oldEle.after(newEle);
    
      return new Promise((resolve) => {
        // Assume there's animations start right after inserting to DOM.
        newEle.addEventListener('animationend', () => {
          oldEle.remove();
          resolve();
        }, { once: true });
      });
    };

    NOTE: Pjax waits for the switches in a navigation, but may abort the whole navigation when the next one happens (e.g., user triggering the “Back” button).

    scripts (String, default: 'script[data-pjax]')

    CSS selector used to target scripts to re-execute at page switches. If needing multiple specific selectors, separate them by a comma. Like:

    // Single selector
    const pjax = new Pjax({
      scripts: 'script.pjax',
    });
    // Multiple selectors
    const pjax = new Pjax({
      scripts: 'script.pjax, script.analytics',
    });

    NOTE: Pjax always executes scripts in newly loaded contents. You don't have to mark them here.

    scrollTo (Number | [Number, Number] | Boolean, default: true)

    When set to a number, this represents the vertical value (in px from the top of the page) to scroll to after a page switch.

    When set to an array of 2 numbers ([x, y]), this represents the value to scroll both horizontally and vertically.

    Set this to true to make Pjax decide the scroll position. Pjax will try to act as the browsers' default behavior. For example, scroll the element into view when hash changing to its id, scroll to page left top when navigating to a new page without a valid hash.

    Set this to false to disable all scrolling by Pjax.

    NOTE: This option does not affect the scroll restoration defined below.

    scrollRestoration (Boolean, default: true)

    When set to true, Pjax will attempt to restore the scroll position when navigating backward or forward.

    cacheMode (RequestCache, default: 'default')

    This contains the cache mode of Pjax requests, which shares the same available values with Request.cache.

    timeout (Integer, default: 0)

    The time in milliseconds to abort the fetch requests. Set to 0 to disable.

    Status

    Accessible by calling on the Pjax instance.

    location (URL, default: new URL(window.location.href))

    The last location recognized by Pjax.

    abortController (AbortController | null, default: null)

    The abort controller that can abort the page navigation handling by Pjax.

    For example, to abort Pjax on certain events:

    const pjax = new Pjax();
    
    document.addEventListener('yourCustomEventType', () => {
      pjax.abortController?.abort();
    });

    Events

    When calling Pjax to load a URL that within the same origin while with different path or search string, Pjax fires a number of events.

    All events fire from the document, not the clicked anchor nor the caller function. You can get more detail of the event via event.detail.

    An ordered list showing the types of these events, and the moment they happen:

    1. pjax:send event when Pjax sends the request.
    2. Pjax switches the DOM. See switchDOM method for details.
    3. pjax:error event if any error happens to step 2.
    4. pjax:complete event when step 2 finishes.
    5. pjax:success event if step 2 finishes without any error.

    If you use a loading indicator (e.g. topbar), a pair of send and complete events may suit you well.

    document.addEventListener('pjax:send', topbar.show);
    document.addEventListener('pjax:complete', topbar.hide);

    HTTP Headers

    Pjax uses several custom headers when it sends HTTP requests. If the requests goes to your server, you can use those headers for some meta information about the response.

    • X-Requested-With: Fetch
    • X-PJAX: true
    • X-PJAX-Selectors — A serialized JSON array of selectors, taken from options.selectors. You can use this to send back only the elements that Pjax will use to switch, instead of sending the whole page. Note that you may need to deserialize this on the server (Such as by using JSON.parse)

    DOM Ready State

    Most of the time, you will have code related to the current DOM that you only execute when the DOM became ready.

    Since Pjax doesn't trigger the standard DOM ready events, you'll need to add code to re-trigger the DOM ready code. Here's a simple example:

    function whenDOMReady() {
      // do your stuff
    }
    
    document.addEventListener('DOMContentLoaded', whenDOMReady);
    
    const pjax = new Pjax();
    
    document.addEventListener('pjax:success', whenDOMReady);

    NOTE: Don't instantiate Pjax in the whenDOMReady function.

    Q&A

    CONTRIBUTING

    CHANGELOG

    LICENSE

    Install

    npm i @sliphua/pjax

    DownloadsWeekly Downloads

    29

    Version

    0.13.0

    License

    MIT

    Unpacked Size

    331 kB

    Total Files

    32

    Last publish

    Collaborators

    • avatar