Do you like this project? Please support my Mecha CMS project too. Thank you!

Fetch 1.2.15

You can read F3H as how you would read A11Y and I18N.

Your job is to make a website that works traditionally: clicking on a link will direct you to a new page, sending and uploading data through a form will store it to the web storage. After you finish making it, this application will serve as a feature enhancer, which is, to enable AJAX features to your traditional web pages automatically, so that your web pages can load faster because you can specify which parts of the destination page you want to load into the current page.


Progressive enhancement is a design principle that is focused in ensuring the availability of web page content in the most limited conditions, and if possible, to provide the most advanced features when the conditions are supportive. By enhancing features while maintaining the basic functions of HTML pages, this concept will ensure that if your advanced JavaScript features cannot work optimally under certain conditions, then at least the web page content that you want to provide is still accessible to most users.

Demo

Features

Usage

Browser

With the basic knowledge of accessing the DOM and manipulating it using JavaScript, you can use this application like a pro.

<!DOCTYPE html>
<html dir="ltr">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <main id="content">
      <p>Lorem <a href="./path-to-page">ipsum</a> dolor sit amet.</p>
    </main>
    <script src="./index.min.js"></script>
    <script>
      const f3h = new F3H;
      const content = document.querySelector('#content');
      f3h.on('success', response => {
          document.title = response.title;
          content.innerHTML = response.querySelector('#content').innerHTML;
      });
    </script>
  </body>
</html>

Node.js

Functions and methods in this application are mostly native JavaScript and are intended for use by the browser. Node.js doesn’t know about the DOM, so this kind of practice will probably be used more often to build new browser packages than to be used directly in the Node.js server.

CommonJS

const F3H = require('@taufik-nurrohman/f3h').default;

const f3h = new F3H;

f3h.on('success', response => {
    document.title = response.title;
    let from = document.querySelector('main'),
        to = response.querySelector('main');
    from.innerHTML = to.innerHTML;
});

ECMAScript

import F3H from '@taufik-nurrohman/f3h';

const f3h = new F3H;

f3h.on('success', response => {
    document.title = response.title;
    let from = document.querySelector('main'),
        to = response.querySelector('main');
    from.innerHTML = to.innerHTML;
});

Examples

Settings

let f3h = new F3H(cache = false);
let f3h = new F3H(state = {
        cache: false,
        history: true,
        is: (source, ref) => { … },
        lot: {
            'x-requested-with': 'F3H'
        },
        ref: (source, ref) => { … },
        sources: 'a[href], form',
        turbo: false,
        type: 'document',
        types: { … }
    });
Name Description
cache See state.cache.
state The configuration data.
state.cache Set to true to enable local cache feature. Only responses from the GET requests that will be cached by the application. Setting the value to false on a website that already has a long built-in cache expiration time might not give any meaningful difference as caches will be stored in the browser, natively.
state.history Set to false to disable the history feature (loading different pages won’t change the URL in the address bar).
state.is Default filter function to exclude HTML elements selected by state.sources with certain criteria. For example, to disable AJAX events on HTML elements that has target attribute with value of _blank.
state.lot List of HTTP headers to be added to the request headers.
state.ref Default URL filter to replace the reference URL to fetch from the source element, without replacing the original URL value in the source element.
state.sources A CSS selector that selects the HTML elements you want to bind the AJAX events to.
state.turbo Pre-fetch URL on hover for faster web page browsing experience. Enabling this feature will automatically enable the local cache feature.
state.type Default response type to be applied to the destination URL with a file extension that’s not yet listed in the state.types object.
state.types List of file extensions with the expected response type.

Methods and Properties

F3H.instances

Return the application instances. This application normally will be instantiated once because there will be only one page opened in a browser window.

for (let key in F3H.instances) {
    console.log(key);
    console.log(F3H.instances[key]);
}

F3H.state

This property stores the initial values of f3h.state.

const f3h = new F3H({
    foo: ['bar', 'baz', 'qux']
});

console.log([F3H.state, f3h.state]);

F3H.version

Return the application version.

let version = F3H.version,
    major = version.split('.')[0];

if (+major < 3) { … }

f3h.abort(request)

Abort requests.

f3h.abort(); // Abort all requests
f3h.abort('https://example.com/asdf'); // Abort request to `https://example.com/asdf`

f3h.caches

This object stores your AJAX response status, body and headers as static cache. Refreshing the page will remove all caches.

console.log(f3h.caches);

f3h.fetch(ref, type, from)

Internal method used to help doing the generic fetch request using XMLHttpRequest.

let xhr = f3h.fetch('https://127.0.0.1/test.html', 'GET', window);
xhr.addEventListener('load', () => {
    console.log(xhr.status);
});

f3h.kick(ref)

Trigger AJAX redirection to the ref. Useful for doing manual redirection of a page after an update action is done.

// Refresh current page view
button.addEventListener('click', () => {
    f3h.kick(window.location.href);
});

f3h.links

Returns all of the links to prefetch available in the response.

f3h.lot

Return the HTTP response headers as object. Every property in it are case-insensitive.

f3h.on('success', response => {
    console.log(f3h.lot);
});

f3h.pop()

Remove all AJAX events from the page.

f3h.ref

This variable stores current URL that is being fetched by the application, including the hash part.

f3h.scripts

Returns all of the JavaScript code available in the response.

f3h.state

Return the modified application states.

f3h.status

Return the HTTP response status.

f3h.on('success', response => {
    if (404 === f3h.status) {
        // Handle 404 page
    } else {
        // Handle normal page
    }
});

f3h.styles

Returns all of the CSS code available in the response.

Hooks

Name Description
200 Will be triggered on AJAX loading success event, with the page status of 200.
404 Will be triggered on AJAX loading success event, with the page status of 404.
* Will be triggered on AJAX loading success event, with the page status of * where * is a HTTP status code as described in the HTTP status code reference.
abort Will be triggered on AJAX aborting event.
enter Will be triggered on AJAX sending event.
error Will be triggered on AJAX loading error event.
exit Will be triggered on the next AJAX loading event. e.g. just after user clicks on a link.
focus Adding a focus hook will disable the default focus behavior, that is, to focus to the first element that has an autofocus attribute.
pop Will be triggered after f3h.pop().
pull Will be triggered on AJAX downloading progress event (GET requests only).
push Will be triggered on AJAX uploading progress event (POST requests only).
scroll Adding a scroll hook will disable the default scroll behavior, that is, to scroll to the first element that has an id or name attribute with the same value as the current location hash value.
success Will be triggered on AJAX loading success event, regardless of the page status. Might result with a null response on URL redirection after form submission. Consider to use the 200 hook instead.

f3h.fire(event, data)

Trigger a hook.

f3h.fire('success', [document, window]);

f3h.hooks

Return the added hooks.

console.log(f3h.hooks);

f3h.off(event, then)

Remove a hook.

f3h.off('success');
f3h.off('success', onSuccess); // With context

f3h.on(event, then)

Add a new hook.

f3h.on('success', response => {
    console.log(response);
});
function onSuccess(response) {
    console.log(response);
}

f3h.on('success', onSuccess); // With context

Frequently Asked Questions

Can I mimic POST request via standard links?

My purpose in making this application is to improve the standard navigation system on a HTML document that already able to work without AJAX features. To mimic POST request on standard links isn’t natural and can cause unwanted responses when the AJAX feature does not work due to certain conditions.

However, you can still send POST requests via standard links as long as you have a server-side language that capable of doing that. For example, with PHP server-side language, you can use the curl extension to send POST request even without submitting a form:

<?php // Example page in `/post.php`

$curl = curl_init();

curl_setopt($curl, CURLOPT_URL, '.');
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $_SERVER['QUERY_STRING']);

// Receive server response…
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

$out = curl_exec($curl);

curl_close($curl);

// Further processing…
if ($out) { /* … */ } else { /* … */ }

Use a standard link with form data as query string on the link element:

<a href="/post.php?data[foo]=1&amp;data[bar]=2">Send data!</a>

I have a REST API functionality that returns data as JSON, but my URL doesn’t end with extension .json. How do I set the response type as JSON under these circumstances?

The F3H.state.types object contains some data in the form of file extension as object key and the desired response type as object value. By default, the value of the object is set as string, but you can also specify it as a function that returns the type of response based on a specific pattern in the given URL.

There is a "" key to express the default response type for URL or file name that does not have extension. A typical clean URL that is commonly used for search engine optimization. You can use this property to specify the response type as JSON under certain conditions in the given link according to the standard URL naming pattern in your application:

F3H.state.types[""] = link => {
    // Assume your REST API link format is `/foo/bar?output=json`
    if (link.match(/[?&]output=json([&#]|$)/)) {
        return 'json';
    }
    // Return the default response for extension-less URL
    return 'document';
};

And here’s the way to process JSON or HTML data simultaneously on success:

const f3h = new F3H;

f3h.on('success', response => {
    if ('application/json' === f3h.lot['content-type']) {
        // Handle JSON response
    } else {
        // Handle HTML response
    }
});

Can I integrate it with jQuery?

Every application built using native JavaScript will be able to integrate with any third party JavaScript library. If you know how to select DOM elements using jQuery, then you are ready to go:

const f3h = new F3H;

let $document = $(document),
    $main = $('main'),
    $nav = $('nav');

f3h.on('success', response => {
    let $response = $(response);
    $document.prop('title', $response.prop('title'));
    $main.html($response.find('main').html());
    $nav.html($response.find('nav').html());
});

I get an error TypeError: response is null from the web console every time I submit a form. Why?

Your form data processor may have redirected you to a certain page via 302 status (and the like) when the data you provided was sent successfully. Consider to use 200 hook instead of success, or be sure to check that the response status is 200 before doing anything to the response given:

f3h.on(200, response => {
    console.log(response);
});
f3h.on('success', response => {
    if (200 === f3h.status) {
        // Handle normal page
    } else if (404 === f3h.status) {
        // Handle error page
    } else {
        // Send successful AJAX loading event to your web statistic
    }
});

Why doesn’t my Google AdSense code refresh when I switch pages?

Google AdSense code will be loaded automatically when you switch pages, but if the next page you visit has the same Google AdSense code as the previous page code, then the code will not be reloaded. To overcome that, you have to clear the cache item on every successful event. First of all, add special ID for the scripts that you want to force to always reload:

<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<ins class="adsbygoogle" data-ad-client="ca-pub-•••••••" data-ad-slot="•••••••"></ins>
<script id="google-script-1">(adsbygoogle = window.adsbygoogle || []).push({})</script>

Then make sure to clear the cache every time a successful event occurs:

f3h.on('success', () => {
    delete f3h.scripts['google-script-1'];
});

License

Use it for free, pay if you get paid. So, you’ve just benefited financially after using this project? It’s a good idea to share a little financial support with this open source project too. Your support will motivate me to do any further development, as well as to provide voluntary support to overcome problems related to this project.

Thank you! ❤️