You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/ui-default/utils/pjax.js

148 lines
3.6 KiB
JavaScript

// Modified from jquery-pjax to support multiple fragments and jQuery 3.0
// https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js
import $ from 'jquery';
import { nanoid } from 'nanoid';
import NProgress from 'nprogress';
import Notification from 'vj/components/notification';
import { request } from './base';
const pjax = {};
let currentState = null;
let currentXHR = null;
let inProgress = 0;
function cancelLastXHR() {
if (currentXHR && currentXHR.readyState < 4) {
currentXHR.abort();
currentXHR = null;
}
}
function canonicalizeUrl(url) {
const a = document.createElement('a');
a.href = url;
return a.href;
}
function extractMetaData(requestOption, response) {
const metaData = {
title: response.title ? response.title : document.title,
url: response.url ? response.url : requestOption.url,
};
metaData.url = canonicalizeUrl(metaData.url);
return metaData;
}
function incProgress() {
inProgress++;
NProgress.done();
NProgress.start();
}
function decProgress() {
inProgress--;
if (inProgress === 0) {
NProgress.done();
}
}
pjax.request = async (opt) => {
if (typeof opt === 'string') opt = { url: opt };
const options = {
method: 'get',
push: true,
...opt,
};
if (!currentState) {
// create initial state
currentState = {
id: nanoid(),
options: {
url: window.location.href,
},
};
window.history.replaceState(currentState, null);
}
cancelLastXHR();
if (options.push) {
// in popstate, options.push === false
window.history.pushState(null, null, options.url);
}
incProgress();
try {
const params = {
beforeSend(xhr) {
currentXHR = xhr;
},
...options,
};
if (params.url && params.url.includes('?')) params.url += '&pjax=1';
else params.url = `${params.url || ''}?pjax=1`;
const data = await request.ajax(params);
const meta = extractMetaData(options, data);
currentState = {
id: nanoid(),
options: opt,
};
if (options.push) {
window.history.replaceState(currentState, null, meta.url);
}
if (meta.title) document.title = meta.title;
data.fragments.forEach((fragment) => {
if (process.env.NODE_ENV !== 'production') {
if (fragment.html === undefined) {
throw new Error("Fragement should contain 'html'");
}
}
const $el = $(fragment.html.trim());
if (process.env.NODE_ENV !== 'production') {
if ($el.length === 0) {
throw new Error("Unable to build elements from fragment 'html'");
}
}
const fragmentId = $el.attr('data-fragment-id');
if (process.env.NODE_ENV !== 'production') {
if (!fragmentId) {
throw new Error("Unable to extract fragment id from fragment 'html'");
}
}
const $target = $(`[data-fragment-id="${fragmentId}"]`);
if (process.env.NODE_ENV !== 'production') {
if ($target.length === 0) {
throw new Error('Unable to get target fragment from fragment id');
}
}
$target.trigger('vjContentRemove');
$target.replaceWith($el);
$el.trigger('vjContentNew');
});
} catch (err) {
if (!err.aborted) {
Notification.error(err.message);
console.error(err);
}
} finally {
decProgress();
}
};
function handlePopState(ev) {
if (!ev.originalEvent) return;
const { state } = ev.originalEvent;
if (!state) return;
if (!state.id || (currentState && state.id === currentState.id)) {
return;
}
pjax.request({
...state.options,
push: false,
});
}
$(window).on('popstate', handlePopState);
export default pjax;
window.Hydro.utils.pjax = pjax;