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.
Features
- Light-weight, no dependencies. It uses vanilla JavaScript.
- Simple API. Easy to master.
- Accessible AJAX response as HTML elements (by default).
- Instant click with native HTML5 Prefetch, improved by the power of JavaScript.
- Progressively enhanced. If at any time the JavaScript fails to load, or if users are using an old web browser, or if users decide to disable the JavaScript features, or if some third-party JavaScript makes this application fail to work, your website will still be able to work as when it didn’t have any AJAX 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
- No Idea?
- Apply Events to Specific Elements
- Progress Animation Example (Requires
Content-Length
response header) - CSS and JavaScript Evaluation Across Pages
- Basic Form Example (Requires PHP)
- Upload Form Example (Requires PHP)
- Native Hash Change Events
- Load More Example
- Infinite Scroll Example
- Smooth Scrolling Example
- Server-Side Redirection Example (Requires PHP, experimental)
- Form Data Value (Requires PHP, test)
- JavaScript and CSS Placement Test
- Search Form Example
- jQuery Integration Example
- Soft Redirection Example
- Custom Event Example
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&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! ❤️