{"id":15679737,"url":"https://github.com/getdave/fetch-pjax","last_synced_at":"2025-05-07T10:42:19.962Z","repository":{"id":57234650,"uuid":"126793997","full_name":"getdave/fetch-pjax","owner":"getdave","description":"[BETA] Enables PJAX (PushState + Ajax) style navigation with the native Fetch API","archived":false,"fork":false,"pushed_at":"2022-03-17T11:38:23.000Z","size":198,"stargazers_count":12,"open_issues_count":2,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-29T11:42:42.558Z","etag":null,"topics":["ajax","fetch","history-api","javscript","pjax","popstate","pushstate","replacestate"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/getdave.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-26T07:58:16.000Z","updated_at":"2025-03-02T14:21:39.000Z","dependencies_parsed_at":"2022-09-15T04:41:32.750Z","dependency_job_id":null,"html_url":"https://github.com/getdave/fetch-pjax","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getdave%2Ffetch-pjax","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getdave%2Ffetch-pjax/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getdave%2Ffetch-pjax/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getdave%2Ffetch-pjax/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getdave","download_url":"https://codeload.github.com/getdave/fetch-pjax/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252862040,"owners_count":21815784,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","fetch","history-api","javscript","pjax","popstate","pushstate","replacestate"],"created_at":"2024-10-03T16:35:11.480Z","updated_at":"2025-05-07T10:42:19.927Z","avatar_url":"https://github.com/getdave.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fetch Pjax\n\u003e PJAX (PushState + Ajax) navigation functionality using the native Fetch API\n\nFetch 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__.\n\nFetch Pjax __provides full url, back button and history support__ via liberal usage of `history.pushState` and the `window.onpopstate` event. \n\n## Features\n\n* URL updating and management\n* Back button support\n* Internal \"hash\" link support (inc. scroll position restoration)\n* Choose multiple DOM update targets\n* Form handling (inc. `POST`/`GET` and `enctypes`)\n* Highly configurable - customise behaviour via [options](#options)\n* Extensible by design - use [callbacks](#callbacks) to achieve your unique requirements\n* Full ability to customise Fetch request details \n* Test coverage for all key features\n\n## Installation\n\n#### via npm/yarn\n```sh\n// npm users\nnpm install fetch-pjax --save\n\n// yarn users\nyarn add fetch-pjax\n```\n\nthen... \n\n```javascript\nimport FetchPjax from 'fetch-pjax' \n\nconst FetchPjax = require('fetch-pjax');\n```\n\n\n#### via good old script tag\n\n```\n\u003cscript src=\"dist/fetch-pjax.umd.js\" /\u003e\n\n// FetchPjax is now available on global namespace\n```\n\n\n\n### Requirements / Assumptions\n\n`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.\n\n\n## Usage\n\nBy 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.\n\n### Signature\n\nThe basic usage signature of a `FetchPjax` is:\n\n`new FetchPjax(options); // options is an plain JS object {}`\n\n### Basic Usage\n\nFor many users, creating an instance of `FetchPjax` will be enough to get PJAX working on your site.\n\nThe example below assumes you have [imported/included the `fetch-pjax` library](#installation).\n\n```javascript\nnew FetchPjax(); // sensible defaults applied\n```\n\nFor the above example, all clicks on `\u003ca\u003e` 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.\n\nBy default the sections of the DOM matching the following selectors will be updated on each PJAX request:\n\n* `main` - the `\u003cmain\u003e` element of the page. Usually assumed to contain the page's main content\n* `title` - the page's `\u003ctitle\u003e` element from the `\u003chead\u003e`\n\nAs everyone's site is different, you can easily customise this by providing the `targets` option. For example:\n\n```javascript\nnew FetchPjax({\n\ttargets: { // define which portions of the current page should be replaced\n\t\ttitle: 'title' \n\t\tcontent: '#main-content',  \n\t\tsidebar: 'aside.sidebar',\n\t\tspecialArea: '.my-special-area-selector'\n\t}\n});\n```\n\nNote that when you provide a `targets` option, the defaults are overidden, so you will need to include them manually. \n\n### Customising Fetch Request Options\n\nAs 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:\n\n```javascript\nconst token = btoa('someuser:somepassword');\n\nnew FetchPjax({\n\tfetchOptions: {\n\t\theaders: {\n\t\t\t'X-PJAX': true, // we recommend retaining this identify PJAX requests\n\t\t\t'Authorization: `Bearer ${token}`' // send HTTP auth headers with every request\n\t\t}\n\t}\n});\n```\n\nThese 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.\n\n### Lifecycle Callbacks\n\n`FetchPjax` provides a number of callback functions which you can use to hook into key events in the plugin execution lifecycle. Events include:\n\n* `onBeforePjax`\n* `onSuccessPjax`\n* `onErrorPjax`\n* `onCompletePjax`\n* `onBeforeRender`\n* `onAfterRender`\n* `onBeforeTargetRender`\n* `onAfterTargetRender`\n\nMaking use of a callback is easy. Simply provide an object as the `callbacks` option. For example: \n\n```javascript\nnew FetchPjax({\n\tcallbacks: {\n\t\tonBeforePjax: () =\u003e {\n\t\t\t// do something here before PJAX is dispatched\n\t\t},\n\t\tonErrorPjax: () =\u003e {\n\t\t\t// do something here when PJAX encounters an error\n\t\t}\n\t}\n});\n```\n\nDifferent callbacks receive different arguments. For more on callbacks, see the callback documentation.\n\n## Options\n\nThe following options can be provided to customise the functionality of `FetchPjax`.\n\n\nOption | Type | Default | Description\n------ | ---- | ------- | -----------\n`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.\n`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.\n`selector` | `string` | `a` | a valid DOM selector for the elements on which you wish to listen for clicks (or other valid `eventType`)\n`ignoreSelector` | `string` | `[data-fetch-pjax-ignore]` | a valid DOM selector for the elements which you wish to _exclude_ from being handled by PJAX.  \n`handleForms` | `boolean` | `true` | determines whether or not FetchPjax should handle Form `submit` events\n`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)\n\n`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\n`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` \n`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](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax). To overide on a per-request basis, see [`modifyFetchOptions`](#callbacks)\n`popStateUseContentCache` | `boolean` | `true` | determines whether to cache visted urls request HTML in the browser `history.state` object for faster \n`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.\n`callbacks` | `object` | - | see [Callbacks](#callbacks)\n\n### Callbacks\n\nFetchPjax 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](#example-callback-usage))\n\n__Note:__ all callbacks (with the exception of `modifyFetchOptions`) are passed the current `instance` of `FetchPjax` as their first argument. \n\nCallback | Args    | Description\n---------| ------- | -------------\n`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](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax) documentation for details)\n`onBeforePjax` | `instance`[, `fetchOptions (object)`] | called before the PJAX request is fired. `fetchOptions` object may be undefined if  called as part of popState cache handling\n`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`\n`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\n`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`.\n`onBeforeRender` | `instance` | called before the start of a render cycle (where the `targets` are updated in the DOM)\n`onAfterRender` | `instance` | called after the end of a render cycle (where the `targets` are updated in the DOM)\n`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.\n`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.\n\n### Example Callback usage\n\n```javascript\nnew FetchPjax({\n\t\n\tcallbacks: {\n        onBeforePjax: (instance, fetchOptions) =\u003e {\n        \t// do something here\n    \t},\n    \tonCompletePjax: (instance) =\u003e {\n        \t// do something here always\n    \t}\n    }\n});\n```\n\n### Recipes\n\n#### Customising Fetch options on a per request basis\n\n__Problem:__ you need to modify the options passed to `fetch()` on a per request basis.\n\n__Solution:__ utilise the `modifyFetchOptions` option to conditionally modify the `fetchOptions` based on the request.\n\n__Example:__\t\n```javascript\nnew FetchPjax({\t\n\tmodifyFetchOptions: (fetchOptions) =\u003e {\n\n\t\t// Set different fetchOptions for different urls\n\t\tif (fetchOptions.url.includes('foo')) {\n\t\t    return { // notice we must return the object\n\t\t        headers: {\n\t\t            'X-SPECIAL-HEADER': true // note this will be *merged* into the default headers object\n\t\t        }\n\t\t    };\n\t\t}\n\n\t\tif (fetchOptions.url.includes('bar')) {\n\t\t    return { // notice we must return the object\n\t\t        headers: {\n\t\t            'X-SOME-DIFFERENT-HEADER': true // note this will be merged into the default headers object\n\t\t        }\n\t\t    };\n\t\t}\n\n\t}\n});\n```\n\n__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`](https://github.com/jonschlinkert/assign-deep) to ensure nested objects are retained.\n\n\n## Development setup\n\nTo contribute to the development of FetchPjax first ensure you have `node` installed on your system. Also ensure you have either `npm` or `yarn` available.\n\nA integration test suite is available via [Cypress.io](https://www.cypress.io/). This should be run continuous during development to ensure your modifications do not adversely effect the existing functionality.\n\nTo 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!\n\nAll new functionality must be tested, following the existing conventions for guidance.\n\n\n## Release History\n\nSee `CHANGELOG.md`.\n\n## Meta\n\nFetchPjax is the brain child of David Smith \n* Twitter - [@get_dave](https://twitter.com/get_dave)\n* Website - [aheadcreative.co.uk](https://aheadcreative.co.uk)\n* Github - [https://github.com/getdave/](https://github.com/getdave/)\n\nDistributed under the MIT license. See ``LICENSE`` for more information.\n\n## Roadmap\n\n- [x] Ability to designate certain origins as being allowed for cross original requests - currently all cross origins blocked\n- [x] Reduce bundle size (currently too big)\n- [x] Focus management\n- [x] Accessibility testing and improvements (`aria-live`?)\n- [x] Centralise PJAX logic and avoid multiple conditionals and optional handling across various methods\n\n\n## Contributing\n\nThis project uses Gitflow. All pull requests should be branched from the `develop` branch only.\n\n1. Fork it \n2. Checkout the `develop` branch\n3. Create your feature branch (`git checkout -b feature/myfeature`)\n4. Commit your changes (`git commit -am 'Add some myfeature'`)\n5. Push to the branch (`git push origin feature/myfeature`)\n6. Create a new Pull Request\n\n\u003c!-- Markdown link \u0026 img dfn's --\u003e\n[npm-image]: https://img.shields.io/npm/v/fetch-pjax.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/fetch-pjax\n[npm-downloads]: https://img.shields.io/npm/dm/fetch-pjax.svg?style=flat-square\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetdave%2Ffetch-pjax","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetdave%2Ffetch-pjax","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetdave%2Ffetch-pjax/lists"}