uix-kit
Version:
A free web kits for fast web design and development, compatible with Bootstrap v5.
849 lines (564 loc) • 24.2 kB
JavaScript
/*
*************************************
* <!-- Ajax Page Loader (Loading A Page via Ajax Into Div) -->
*************************************
*/
import {
UixBrowser,
UixModuleInstance,
UixGUID,
UixDebounce,
} from '@uixkit/core/_global/js';
import UixApplyAsyncScripts from '@uixkit/core/_global/js/fn/UixApplyAsyncScripts';
import '../scss/_style.scss';
export const AJAX_PAGE_LOADER = ( ( module, $, window, document ) => {
if ( window.AJAX_PAGE_LOADER === null ) return false;
module.AJAX_PAGE_LOADER = module.AJAX_PAGE_LOADER || {};
module.AJAX_PAGE_LOADER.version = '0.2.1';
module.AJAX_PAGE_LOADER.documentReady = function( $ ) {
let windowWidth = window.innerWidth,
windowHeight = window.innerHeight;
//all images from pages
let sources = [];
//Added timer to prevent page loading errors for a long time
let timeClockInit;
//Determine the direction of a jQuery scroll event
//Fix an issue for mousewheel event is too fast.
const quietPeriod = 500, //Do not change it
animationTime = 1000,//According to page transition animation changes
loaderRemoveDelay = 500,
AJAXPageLinks = '[data-ajax-page]',
$navs = $( AJAXPageLinks ).parent().parent().find( 'li' ),
total = $navs.length,
$sectionsContainer = $( '.uix-ajax-load__fullpage-container' ),
ajaxContainer = '.ajax-container',
curAjaxPageID = $( ajaxContainer ).data( 'ajax-page-id' );
let lastAnimation = 0;
// The progress of each page load, using global variables to accurately determine
let loadedProgress = 0;
//loading animation
const loadingAnim = function( per ) {
$( '#app-loading' ).text( $( '#app-loading' ).data( 'txt' ).replace(/\{progress\}/g, per ) );
};
//Prevent this module from loading in other pages
if ( $sectionsContainer.length == 0 ) return false;
/*
====================================================
* Navigation Interaction
====================================================
*/
//Activate the first item
if ( $( '.js-uix-ajax-load__container' ).length == 0 ) {
moveTo( $( ajaxContainer ), false, 'down', 0, false );
} else {
//Activate navigation from AJAX request
if ( typeof curAjaxPageID != typeof undefined ) $navs.eq( curAjaxPageID ).addClass( 'is-active' );
}
/*
====================================================
* AJAX Interaction
====================================================
*/
/*
* Initialize the clickable ajax links
*
* @return {Void}
*/
function ajaxInit() {
if ( windowWidth <= 768 ) {
$( AJAXPageLinks ).data( 'mobile-running', true );
} else {
$( AJAXPageLinks ).data( 'mobile-running', false );
}
}
ajaxInit();
function windowUpdate() {
// Check window width has actually changed and it's not just iOS triggering a resize event on scroll
if ( window.innerWidth != windowWidth ) {
// Update the window width for next time
windowWidth = window.innerWidth;
// Do stuff here
ajaxInit();
}
}
// Add function to the window that should be resized
const debounceFuncWindow = UixDebounce(windowUpdate, 50);
window.removeEventListener('resize', debounceFuncWindow);
window.addEventListener('resize', debounceFuncWindow);
/*
* Call AJAX on click event for "single pages links"
*
*/
$( document ).off( 'click.AJAX_PAGE_LOADER' ).on( 'click.AJAX_PAGE_LOADER', AJAXPageLinks, function( e ) {
//Prevents third-party plug-ins from triggering
if ( $( this ).data( 'mobile-running' ) ) {
return;
}
e.preventDefault();
// The progress of each page load
loadedProgress = 0;
//
const $this = $( this );
const curIndex = $this.attr( 'data-index' );
let curURL = $this.attr( 'href' );
//The currently URL of link
if ( typeof curURL === typeof undefined ) {
curURL = $this.closest( 'a' ).attr( 'href' );
}
//Prevent multiple request on click
if ( $( AJAXPageLinks ).data( 'request-running' ) ) {
return;
}
$( AJAXPageLinks ).data( 'request-running', true );
// Modify the URL without reloading the page
if( history.pushState ) {
history.pushState( null, null, curURL );
} else {
location.hash = curURL;
}
//Click on this link element using an AJAX request
const dir = ( $navs.filter( '.is-active' ).find( '> a' ).attr( 'data-index' ) > curIndex ) ? 'up' : 'down';
moveTo( $( ajaxContainer ), curURL, dir, curIndex, false );
return false;
});
//Detect URL change & Fire click event
window.addEventListener( 'popstate', function( e ) {
let eleTarget = null,
goURL = location.href;
$( AJAXPageLinks ).each( function() {
//don't use $( this ).attr( 'href' )
if ( this.href === location.href ) {
eleTarget = this;
goURL = this.href;
}
});
//Empty content that does not exist
if ( eleTarget == null ) {
moveTo( $( ajaxContainer ), false, 'down', 0, false );
}
//Push new content to target container
const pageIndex = $( eleTarget ).data( 'index' );
//Push new content to target container
if ( typeof pageIndex != typeof undefined ) {
moveTo( $( ajaxContainer ), goURL, 'down', pageIndex, false );
}
// Output history button
//console.log( $( eleTarget ).data( 'index' ) );
});
/*
* Scroll initialize
*
* @param {Event} event - The wheel event is fired when a wheel button of a pointing device (usually a mouse) is rotated.
* @param {String} dir - Gets a value that indicates the amount that the mouse wheel has changed.
* @return {Void}
*/
function scrollMoveInit( event, dir ) {
const timeNow = new Date().getTime();
// Cancel scroll if currently animating or within quiet period
if( timeNow - lastAnimation < quietPeriod + animationTime) {
return;
}
if ( dir == 'down' ) {
//scroll down
moveTo( $( ajaxContainer ), false, 'down', false, true );
} else {
//scroll up
moveTo( $( ajaxContainer ), false, 'up', false, true );
}
lastAnimation = timeNow;
}
/*
* Move Animation
*
* @param {Element} container - The instance returned from the request succeeds
* @param {String} url - The target URL via AJAX.
* @param {String} dir - Rolling direction indicator.
* @param {Number} customIndex - User-specified index value, located on the corresponding AJAX hyperlink.
* @param {Boolean} wheel - Whether to enable mouse wheel control.
* @return {Void}
*/
function moveTo( container, url, dir, customIndex, wheel ) {
const index = parseFloat( $navs.filter( '.is-active' ).find( '> a' ).attr( 'data-index' ) );
const isNumeric = /^[-+]?(\d+|\d+\.\d*|\d*\.\d+)$/;
let nextIndex = null;
//If there is a custom index, it is enabled first
if ( isNumeric.test( customIndex ) ) {
nextIndex = customIndex;
} else {
if ( dir == 'down' || dir === false ) {
nextIndex = index + 1;
} else {
nextIndex = index - 1;
}
}
if ( nextIndex <= parseFloat( total - 1 ) && nextIndex >= 0 ) {
if ( nextIndex > parseFloat( total - 1 ) ) nextIndex = parseFloat( total - 1 );
if ( nextIndex < 0 ) nextIndex = 0;
//Prevents third-party plug-ins from triggering
if ( $navs.eq( nextIndex ).find( '> a' ).data( 'mobile-running' ) ) {
return;
}
//Activate navigation from AJAX request
$navs.removeClass( 'is-active' );
$navs.eq( nextIndex ).addClass( 'is-active' );
//Use automatic indexing when no URLs come in.
if ( !url || typeof url === typeof undefined ) {
url = $navs.eq( nextIndex ).find( '> a' ).attr( 'href' );
}
// Modify the URL without reloading the page when mouse wheel
if ( wheel ) {
const turl = $navs.eq( nextIndex ).find( '> a' ).attr( 'href' );
if( history.pushState ) {
history.pushState( null, null, url );
} else {
location.hash = turl;
}
}
//Click on this link element using an AJAX request
// Add a request or response interceptor
const axiosInterceptor = axios.interceptors.request.use(function(config) {
// Do something before request is sent
//Display loader
showLoader();
//
return config;
},
function(error) {
return Promise.reject(error);
});
// To send data in the application/x-www-form-urlencoded format instead
const formData = new FormData();
const defaultPostData = {
action : 'load_singlepages_ajax_content'
};
for(let k in defaultPostData) {
formData.append(k, defaultPostData[k]);
}
/*
// For multiple form fields data acquisition
const formData = new FormData();
const oldFormData = $this.serializeArray();
oldFormData.forEach(function(item){
formData.append(item.name, item.value);
});
formData.append('action', 'load_singlepages_ajax_content');
*/
// Create a request event
axios({
timeout: 15000,
method: ( typeof container.data( 'ajax-method' ) === typeof undefined ) ? 'POST' : container.data( 'ajax-method' ),
url: url,
data: formData,
responseType: 'text',
})
.then(function (response) {
const htmlCode = response.data;
//A function to be called if the request succeeds
//Display loading image when AJAX call is in progress
//Remove existing images
sources = [];
//Push all images from page
$( htmlCode ).find( 'img' ).each(function() {
sources.push(
{
"url": this.src,
"id": 'img-' + UixGUID.create(),
"type": 'img'
}
);
});
//Push all videos from page
$( htmlCode ).find( '.uix-video__slider > video' ).each(function() {
let _src = $( this ).find( 'source:first' ).attr( 'src' );
if ( typeof _src === typeof undefined ) _src = $( this ).attr( 'src' );
sources.push(
{
"url": _src,
"id": 'video-' + UixGUID.create(),
"type": 'video'
}
);
});
//Execute after all images have loaded
let per;
let perInit = 1;
if ( sources.length == 0 ) {
per = 100;
//loading animation
loadingAnim( per );
//Remove loader
const oldContent = container.html();
hideLoader(container, $( htmlCode ).filter( 'title' ).text(), dir, oldContent, htmlCode);
}
const loadImages = function() {
let promises = [];
for (let i = 0; i < sources.length; i++) {
if ( sources[i].type == 'img' ) {
///////////
// IMAGE //
///////////
promises.push(
new Promise(function(resolve, reject) {
const img = document.createElement("img");
img.src = sources[i].url;
img.onload = function(image) {
//Compatible with safari and firefox
if ( typeof image.path === typeof undefined ) {
return resolve(image.target.currentSrc);
} else {
return resolve(image.path[0].currentSrc);
}
};
}).then( textureLoaded )
);
} else {
///////////
// VIDEO //
///////////
promises.push(
new Promise( function(resolve, reject) {
$( '#' + sources[i].id ).one( 'loadedmetadata', resolve );
return resolve( sources[i].url);
}).then( textureLoaded )
);
}
}
return Promise.all(promises);
};
const textureLoaded = function( url ) {
//loading
per = parseInt( 100 * ( perInit / sources.length ) );
console.log( 'progress: ' + per + '%' );
if ( isNaN( per ) ) per = 100;
// The progress of each page load
loadedProgress = per;
//loading animation
loadingAnim( per );
let texture = null;
perInit++;
return per;
};
const func = function() {
ajaxSucceeds( dir, container, $( htmlCode ).find( '.js-uix-ajax-load__container' ).html(), $( htmlCode ).filter( 'title' ).text() );
};
//images loaded
//Must be placed behind the loadImages()
loadImages().then( function( images ) {
clearInterval( timeClockInit );
func();
});
//Calculating page load time
const timeLimit = 10,
timeStart = new Date().getTime();
//Prevent duplicate runs when returning to this page
if ( timeClockInit ) {
clearInterval( timeClockInit );
}
timeClockInit = setInterval( function() {
//Converting milliseconds to minutes and seconds
let _time = (new Date().getTime() - timeStart) / 1000;
if ( _time >= timeLimit ) {
console.log( 'Page load timeout!' );
//Remove loader
if ( htmlCode.indexOf( '<body' ) >= 0 ) {
window.location.href = location.href;
} else {
const oldContent = container.html();
hideLoader(container, $( htmlCode ).filter( 'title' ).text(), dir, oldContent, htmlCode);
}
// clear loader event
clearInterval( timeClockInit );
func();
}
}, 500 );
})
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
const status = error.response.status;
console.log(status);
if ( status == 404 || status == 405 ) window.location.href = url;
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
//
window.location.href = url;
} else {
// If there was a problem, we need to
// dispatch the error condition
console.log(error.message);
}
});
// Remove an interceptor later
axios.interceptors.request.eject(axiosInterceptor);
}
}
/*
* A function to be called if the request succeeds
*
* @param {String} dir - Gets a value that indicates the amount that the mouse wheel has changed.
* @param {Element} container - The instance returned from the request succeeds
* @param {String} content - The data returned from the server
* @param {String} title - The title of a requested page.
* @return {Void}
*/
function ajaxSucceeds( dir, container, content, title ) {
//If the page resource is not loaded, then the following code is not executed
if ( loadedProgress < 100 ) return false;
//Remove loader
const oldContent = container.html();
hideLoader(container, title, dir, oldContent, content);
}
/*
* Remove loader
*
* @param {Element} container - The instance returned from the request succeeds
* @param {String} title - The title of a requested page.
* @param {String} dir - Gets a value that indicates the amount that the mouse wheel has changed.
* @param {String} oldContent - The old data returned from the server
* @param {String} content - The data returned from the server
* @return {Void}
*/
function hideLoader( container, title, dir, oldContent, content ) {
TweenMax.to( '.uix-ajax-load__loader', 0.5, {
alpha : 0,
onComplete : function() {
TweenMax.set( this.target, {
css : {
'display' : 'none'
}
});
//The data returned from the server
container.html( content ).promise().done( function() {
//Transition effect between two elements.
eleTransitionEff( dir, oldContent, content );
//Change the page title
if ( title ) {
document.title = title;
}
//Prevent multiple request on click
$( AJAXPageLinks ).data( 'request-running', false );
});
},
delay : loaderRemoveDelay/1000
});
}
/*
* Display loader
*
* @return {Void}
*/
function showLoader() {
//loading animation
loadingAnim( 0 );
//loader effect from AJAX request
TweenMax.set( '.uix-ajax-load__loader', {
css : {
'display' : 'block'
},
onComplete : function() {
TweenMax.to( this.target, 0.5, {
alpha : 1
});
}
});
}
/*
* Transition effect between two elements.
*
* @param {String} dir - Gets a value that indicates the amount that the mouse wheel has changed.
* @param {String} oldContent - A string of HTML to set as the content of matched old element.
* @param {String} newContent - A string of HTML to set as the content of matched new element.
* @return {Void}
*/
function eleTransitionEff( dir, oldContent, newContent ) {
const $originalItem = $sectionsContainer.find( '> section' ),
$cloneItem = $originalItem.clone();
//Reset the original element
$originalItem.css( {
'z-index': 1
} );
//Clone the last element to the first position
$cloneItem
.prependTo( $sectionsContainer )
.css( {
'z-index': 2,
'transform': 'translateY('+( ( dir == 'down' || dir === false ) ? windowHeight : -windowHeight )+'px)'
} )
//Add the latest content to the new container
.find( ajaxContainer )
.html( newContent );
$originalItem.first().find( ajaxContainer ).html( oldContent ).promise().done( function() {
TweenMax.to( $originalItem.first(), animationTime/1000, {
y : ( dir == 'down' || dir === false ) ? -windowHeight/2 : windowHeight/2,
ease : Power2.easeOut
});
TweenMax.to( $cloneItem, animationTime/1000, {
y : 0,
ease : Power2.easeOut,
onComplete : function() {
//Remove duplicate elements
$originalItem
.first()
.remove();
// Apply some asynchronism scripts
$( document ).UixApplyAsyncScripts();
}
});
});
}
/*
====================================================
* Mouse Wheel Method
====================================================
*/
let startY = 0;
const onTouchStart = function ( e ) {
const touches = e.touches;
if ( touches && touches.length ) {
startY = touches[0].pageY;
}
};
const onDeviceWheel = function ( e ) {
//Gets a value that indicates the amount that the mouse wheel has changed.
let dir, delta, mobileDeltaY = null;
const touches = e.touches;
if ( touches && touches.length ) {
mobileDeltaY = startY - touches[0].pageY;
} else {
delta = Math.max(-1, Math.min(1, (-e.deltaY)));
}
if ( mobileDeltaY != null ) {
if ( mobileDeltaY >= 50 ) {
//--- swipe up
dir = 'up';
}
if ( mobileDeltaY <= -50 ) {
//--- swipe down
dir = 'down';
}
} else {
if( delta < 0 ) {
//scroll down
dir = 'down';
} else {
//scroll up
dir = 'up';
}
}
scrollMoveInit( e, dir );
};
window.addEventListener( 'wheel', onDeviceWheel, UixBrowser.supportsPassive ? { passive: true } : false );
window.addEventListener( 'touchstart', onTouchStart, UixBrowser.supportsPassive ? { passive: true } : false );
window.addEventListener( 'touchmove', onDeviceWheel, UixBrowser.supportsPassive ? { passive: true } : false );
};
module.components.documentReady.push( module.AJAX_PAGE_LOADER.documentReady );
return class AJAX_PAGE_LOADER {
constructor() {
this.module = module;
}
};
})( UixModuleInstance, jQuery, window, document );