Coder Social home page Coder Social logo

getdave / fetch-pjax Goto Github PK

View Code? Open in Web Editor NEW
11.0 0.0 1.0 198 KB

[BETA] Enables PJAX (PushState + Ajax) style navigation with the native Fetch API

License: Other

JavaScript 96.28% HTML 3.72%
ajax pjax fetch javscript pushstate popstate replacestate history-api

fetch-pjax's Introduction

Fetch Pjax

PJAX (PushState + Ajax) navigation functionality using the native Fetch API

Fetch Pjax uses AJAX (via the fetch API) to deliver a super fast browsing experience by loading HTML from your server and replacing only the relevant portions of the page with the Ajax'd HTML. This means the browser doesn't have to reload the page CSS/JavaScript on each request (as it does on a normal page request) which therefore delivers lighting fast page loads.

Fetch Pjax provides full url, back button and history support via liberal usage of history.pushState and the window.onpopstate event.

Features

  • URL updating and management
  • Back button support
  • Internal "hash" link support (inc. scroll position restoration)
  • Choose multiple DOM update targets
  • Form handling (inc. POST/GET and enctypes)
  • Highly configurable - customise behaviour via options
  • Extensible by design - use callbacks to achieve your unique requirements
  • Full ability to customise Fetch request details
  • Test coverage for all key features

Installation

via npm/yarn

// npm users
npm install fetch-pjax --save

// yarn users
yarn add fetch-pjax

then...

import FetchPjax from 'fetch-pjax' 

const FetchPjax = require('fetch-pjax');

via good old script tag

<script src="dist/fetch-pjax.umd.js" />

// FetchPjax is now available on global namespace

Requirements / Assumptions

FetchPjax expects that you are using an environment that supports the fetch API. If your environment does not support fetch natively, you will need to include a suitable polyfill before usage.

Usage

By default FetchPjax will run "config-less" - that is, it has a set of sensible defaults designed to account for the majority of use cases. However, it is designed to be highly configurable, allowing you to extend and modify it's behaviour to suit your needs.

Signature

The basic usage signature of a FetchPjax is:

new FetchPjax(options); // options is an plain JS object {}

Basic Usage

For many users, creating an instance of FetchPjax will be enough to get PJAX working on your site.

The example below assumes you have imported/included the fetch-pjax library.

new FetchPjax(); // sensible defaults applied

For the above example, all clicks on <a> elements will now be intercepted and replaced by an AJAX request issued to the url defined by the anchor's href attribute. Assuming that the response is valid HTML, it will be parsed and the relevant sections of the page will be updated with the new HTML retrived from the PJAX request. In addition the browser's address bar will reflect the new page url. In short, the page will appear and behave in a "normal" manner.

By default the sections of the DOM matching the following selectors will be updated on each PJAX request:

  • main - the <main> element of the page. Usually assumed to contain the page's main content
  • title - the page's <title> element from the <head>

As everyone's site is different, you can easily customise this by providing the targets option. For example:

new FetchPjax({
	targets: { // define which portions of the current page should be replaced
		title: 'title' 
		content: '#main-content',  
		sidebar: 'aside.sidebar',
		specialArea: '.my-special-area-selector'
	}
});

Note that when you provide a targets option, the defaults are overidden, so you will need to include them manually.

Customising Fetch Request Options

As suggested by the name, FetchPjax utilises fetch under the hood. In some cases you may wish to customise the options provided to the underlying fetch() call for each PJAX request. To do this simply provide the fetchOptions option. For example:

const token = btoa('someuser:somepassword');

new FetchPjax({
	fetchOptions: {
		headers: {
			'X-PJAX': true, // we recommend retaining this identify PJAX requests
			'Authorization: `Bearer ${token}`' // send HTTP auth headers with every request
		}
	}
});

These options will be set on every PJAX request. If you need to update the options on a per request basis consider utilising the modifyFetchOptions option.

Lifecycle Callbacks

FetchPjax provides a number of callback functions which you can use to hook into key events in the plugin execution lifecycle. Events include:

  • onBeforePjax
  • onSuccessPjax
  • onErrorPjax
  • onCompletePjax
  • onBeforeRender
  • onAfterRender
  • onBeforeTargetRender
  • onAfterTargetRender

Making use of a callback is easy. Simply provide an object as the callbacks option. For example:

new FetchPjax({
	callbacks: {
		onBeforePjax: () => {
			// do something here before PJAX is dispatched
		},
		onErrorPjax: () => {
			// do something here when PJAX encounters an error
		}
	}
});

Different callbacks receive different arguments. For more on callbacks, see the callback documentation.

Options

The following options can be provided to customise the functionality of FetchPjax.

Option Type Default Description
autoInit boolean true determines whether FetchPjax should initialise itself by default when a new instance is created. Useful for situations where you might need to defer execution.
eventType string click defines the event type on which to listen to trigger a PJAX cycle. For example, specifying touchstart would then only trigger PJAX for touchstart events.
selector string a a valid DOM selector for the elements on which you wish to listen for clicks (or other valid eventType)
ignoreSelector string [data-fetch-pjax-ignore] a valid DOM selector for the elements which you wish to exclude from being handled by PJAX.
handleForms boolean true determines whether or not FetchPjax should handle Form submit events
formSelector string form a valid DOM selector for any Forms within the document which you wish to be handeled by PJAX (only applied if handleForms is truthy)

targets | object | { content: 'main', title: 'title' } | defines which portions of the page should be replaced by the Ajax'd content on each PJAX request. The object keys must be unique, but are entirely arbitary and can be anything that helps you to identify them. The object values must be a valid DOM selector for an element which must exist within the page DOM at the point of creating a FetchPjax instance popStateFauxLoadTime | int | 300 | a period (in milliseconds) to wait before cached content retrieved from the history.state is used to update the DOM. This is intended to improve the user experience by simulating a network request delay. Without this content is replaced too quickly to be perceivable. Only utilised when the popStateUseContentCache option is set to true fetchOptions | object | { headers: { 'X-PJAX': true } } | sets the options argument sent to the underlying fetch(url, options={}) call for each PJAX request. Must be a valid options object as provided to the second init argument of the Fetch API. To overide on a per-request basis, see modifyFetchOptions popStateUseContentCache | boolean | true | determines whether to cache visted urls request HTML in the browser history.state object for faster trackInitialState | boolean | true | determines whether the intial page is added to the window.history state using history.replaceState(). You probably don't want to disable this. callbacks | object | - | see Callbacks

Callbacks

FetchPjax provides callback functions which run at Useful points in it's execution cycle. Callback functions should be passed as part of the callbacks option object (see Example Callback Usage)

Note: all callbacks (with the exception of modifyFetchOptions) are passed the current instance of FetchPjax as their first argument.

Callback Args Description
modifyFetchOptions fetchOptions (object) provides the ability to conditionally modify the fetchOptions object on a per request basis. You can conditionally choose to return an objects object from your supplied callback function. When you do, this object will be used as the [second] init options argument of the fetch() call for the PJAX request (refer to the Fetch API documentation for details)
onBeforePjax instance[, fetchOptions (object)] called before the PJAX request is fired. fetchOptions object may be undefined if called as part of popState cache handling
onSuccessPjax instance, url (string), html (string) called upon the completion of the PJAX request when the response was successful. Receives the request url and the response html
onErrorPjax instance, error (object) called upon the completion of the PJAX request when the request resulted in an error. Called with error object containing status and statusText properties
onCompletePjax instance always called upon the completion of a PJAX request regardless of whether result is success or error. Useful for logic that must always run post-PJAX cycle. Called after onSuccessPjax and onErrorPjax.
onBeforeRender instance called before the start of a render cycle (where the targets are updated in the DOM)
onAfterRender instance called after the end of a render cycle (where the targets are updated in the DOM)
onBeforeTargetRender instance, targetKey (string), targetEl (DOM Node), contentEl (DOM Node)[, renderer (function OR undefined)] called before an individual target is updated in the DOM. Called with the targetKey being updated, the targetEl in the page DOM, the contentEl in the Ajax'd content and [if it was provided] the custom renderer function.
onAfterTargetRender instance, targetKey (string), targetEl (DOM Node), contentEl (DOM Node)[, renderer (function OR undefined)] called after an individual target is updated in the DOM. Called with the targetKey being updated, the targetEl in the page DOM, the contentEl in the Ajax'd content and [if it was provided] the custom renderer function.

Example Callback usage

new FetchPjax({
	
	callbacks: {
        onBeforePjax: (instance, fetchOptions) => {
        	// do something here
    	},
    	onCompletePjax: (instance) => {
        	// do something here always
    	}
    }
});

Recipes

Customising Fetch options on a per request basis

Problem: you need to modify the options passed to fetch() on a per request basis.

Solution: utilise the modifyFetchOptions option to conditionally modify the fetchOptions based on the request.

Example:

new FetchPjax({	
	modifyFetchOptions: (fetchOptions) => {

		// Set different fetchOptions for different urls
		if (fetchOptions.url.includes('foo')) {
		    return { // notice we must return the object
		        headers: {
		            'X-SPECIAL-HEADER': true // note this will be *merged* into the default headers object
		        }
		    };
		}

		if (fetchOptions.url.includes('bar')) {
		    return { // notice we must return the object
		        headers: {
		            'X-SOME-DIFFERENT-HEADER': true // note this will be merged into the default headers object
		        }
		    };
		}

	}
});

Notes: the object returned from modifyFetchOptions is merged into the existing fetchOptions object. It will not overwrite the original fetchOptions. Rather it is deep-merged into it using assign-deep to ensure nested objects are retained.

Development setup

To contribute to the development of FetchPjax first ensure you have node installed on your system. Also ensure you have either npm or yarn available.

A integration test suite is available via Cypress.io. This should be run continuous during development to ensure your modifications do not adversely effect the existing functionality.

To start the tests run npm run test:dev - a server will start and Cypress will load shortly after. In another terminal window start rollup in watch mode using npm run dev. Start developing!

All new functionality must be tested, following the existing conventions for guidance.

Release History

See CHANGELOG.md.

Meta

FetchPjax is the brain child of David Smith

Distributed under the MIT license. See LICENSE for more information.

Roadmap

  • Ability to designate certain origins as being allowed for cross original requests - currently all cross origins blocked
  • Reduce bundle size (currently too big)
  • Focus management
  • Accessibility testing and improvements (aria-live?)
  • Centralise PJAX logic and avoid multiple conditionals and optional handling across various methods

Contributing

This project uses Gitflow. All pull requests should be branched from the develop branch only.

  1. Fork it
  2. Checkout the develop branch
  3. Create your feature branch (git checkout -b feature/myfeature)
  4. Commit your changes (git commit -am 'Add some myfeature')
  5. Push to the branch (git push origin feature/myfeature)
  6. Create a new Pull Request

fetch-pjax's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fetch-pjax's Issues

The forward browser button doesn't update the page.

If you press the back button everything works find but when you then press the forward button nothing happens but a title and url change. Also not if you use defer on the script tag or if put the fetch.js before the closing body tag. Browsers: Chrome/Safari/Firefox

How to achieve redirections?

Thank you for the great library. I have started to give a try to this. It's working well and really fasts. But the problem which I found is I couldn't make a redirection anyway if server-side returns a redirection URL. Can you please give me an idea of how I can make a redirection or if I miss something please give your advice.

Wait for condition to be true before replacing the main content

Hi Dave!

Thanks for your very nice library, it works like a charm! ๐Ÿ˜ƒ
I don't know if you're still maintaining the library, nevertheless I'll try to ask.

Is there an option or callback to wait for some condition to be true, before applying the main content replace?

Although "skeleton screens" are the hottest **** right now, I'm trying to implement a loading animation. It should be visible until all of the "Pjaxed" images are loaded.
I'm using the following code to check if all the images are loaded:

[].forEach.call(images, function(img) {
  img.onload = function() {
    amount--;
    if (amount === 0) {
      // Replace the main content here
      console.log("all images loaded, ready to replace the main content")
    }
  }
});

However, of course the library can't wait for the images to be loaded and then replace the content.

Is there maybe something like
onBeforeRenderButWaitForSomethingToBeTrue = > X?

Could I store the main content temporarily in a DocumentFragment before and then wait for some condition to be true and then replace the main content with the DocumentFragment?

Or maybe I'm missing something?

Thanks again for the great library, it helped a lot!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.