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

F3H v1.0.11

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

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-internal-page">ipsum</a> dolor sit amet.</p>
    </main>
    <script src="f3h.min.js"></script>
    <script>
    let f3h = new F3H,
        content = document.querySelector('#content');
    f3h.on('success', function(response) {
        document.title = response.title;
        content.innerHTML = response.querySelector('#content').innerHTML;
    });
    </script>
  </body>
</html>

Examples

Settings

let f3h = new F3H(cache);
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: { … }
    });

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.

let 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._

Alias of F3H.prototype used to formalize the prototype creation.

// Create `cancelAllRequests()` method
F3H._.cancelAllRequests = function() {
    return this.abort();
};

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.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', function(response) {
    console.log(this.lot);
});

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', function(response) {
    if (404 === this.status) {
        // Handle 404 page
    } else {
        // Handle normal page
    }
});

f3h.styles

Returns all of the CSS code available in the response.

f3h.abort(id)

Abort requests.

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

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', function() {
    console.log(this.status);
});

f3h.pop()

Remove all AJAX events from the page.

Hooks

Name Description
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.
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.

f3h.hooks

Return the added hooks.

console.log(f3h.hooks);

f3h.on(name, fn)

Add a new hook.

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

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

f3h.off(name, fn)

Remove a hook.

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

f3h.fire(name, lot)

Trigger hooks.

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

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:

let f3h = new F3H;

f3h.on('success', function(response) {
    if ('application/json' === this.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:

let f3h = new F3H,

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

f3h.on('success', function(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, function(response) {
    console.log(response);
});
f3h.on('success', function(response) {
    if (200 === this.status) {
        // Handle normal page
    } else if (404 === this.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', function() {
    delete this.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! ❤️