🏠 Root
/
home
/
artorgp
/
www
/
wp-content
/
plugins
/
visual-portfolio
/
assets
/
js
/
Editing: main.js
import $ from 'jquery'; import rafSchd from 'raf-schd'; import { throttle } from 'throttle-debounce'; const { VPData } = window; const { __ } = VPData; const $wnd = $( window ); /** * Emit Resize Event. */ function windowResizeEmit() { if ( typeof window.Event === 'function' ) { // modern browsers window.dispatchEvent( new window.Event( 'resize' ) ); } else { // for IE and other old browsers // causes deprecation warning on modern browsers const evt = window.document.createEvent( 'UIEvents' ); evt.initUIEvent( 'resize', true, false, window, 0 ); window.dispatchEvent( evt ); } } const visibilityData = {}; let shouldCheckVisibility = false; let checkVisibilityTimeout = false; let isFocusVisible = false; // fix portfolio inside Tabs and Accordions // check visibility by timer https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom/33456469 // // https://github.com/nk-crew/visual-portfolio/issues/11 // https://github.com/nk-crew/visual-portfolio/issues/113 function checkVisibility() { clearTimeout( checkVisibilityTimeout ); if ( ! shouldCheckVisibility ) { return; } const $items = $( '.vp-portfolio__ready' ); if ( $items.length ) { let isVisibilityChanged = false; $items.each( function () { const { vpf } = this; if ( ! vpf ) { return; } const currentState = visibilityData[ vpf.uid ] || 'none'; visibilityData[ vpf.uid ] = this.offsetParent === null ? 'hidden' : 'visible'; // changed from hidden to visible. if ( currentState === 'hidden' && visibilityData[ vpf.uid ] === 'visible' ) { isVisibilityChanged = true; } } ); // resize, if visibility changed. if ( isVisibilityChanged ) { windowResizeEmit(); } } else { shouldCheckVisibility = false; } // run again. checkVisibilityTimeout = setTimeout( checkVisibility, 500 ); } // run check function only after portfolio inited. $( document ).on( 'inited.vpf', ( event ) => { if ( event.namespace !== 'vpf' ) { return; } shouldCheckVisibility = true; checkVisibility(); } ); /** * If the most recent user interaction was via the keyboard; * and the key press did not include a meta, alt/option, or control key; * then the modality is keyboard. Otherwise, the modality is not keyboard. */ document.addEventListener( 'keydown', function ( e ) { if ( e.metaKey || e.altKey || e.ctrlKey ) { return; } isFocusVisible = true; }, true ); /** * If at any point a user clicks with a pointing device, ensure that we change * the modality away from keyboard. * This avoids the situation where a user presses a key on an already focused * element, and then clicks on a different element, focusing it with a * pointing device, while we still think we're in keyboard modality. */ document.addEventListener( 'mousedown', () => { isFocusVisible = false; }, true ); document.addEventListener( 'pointerdown', () => { isFocusVisible = false; }, true ); document.addEventListener( 'touchstart', () => { isFocusVisible = false; }, true ); /** * Main VP class */ class VP { constructor( $item, userOptions ) { const self = this; self.$item = $item; // get id from class const classes = $item[ 0 ].className.split( /\s+/ ); for ( let k = 0; k < classes.length; k += 1 ) { if ( classes[ k ] && /^vp-uid-/.test( classes[ k ] ) ) { self.uid = classes[ k ].replace( /^vp-uid-/, '' ); } if ( classes[ k ] && /^vp-id-/.test( classes[ k ] ) ) { self.id = classes[ k ].replace( /^vp-id-/, '' ); } } if ( ! self.uid ) { // eslint-disable-next-line no-console console.error( __.couldnt_retrieve_vp ); return; } self.href = window.location.href; self.$items_wrap = $item.find( '.vp-portfolio__items' ); self.$slider_thumbnails_wrap = $item.find( '.vp-portfolio__thumbnails' ); self.$pagination = $item.find( '.vp-portfolio__pagination-wrap' ); self.$filter = $item.find( '.vp-portfolio__filter-wrap' ); self.$sort = $item.find( '.vp-portfolio__sort-wrap' ); // find single filter block. if ( self.id ) { self.$filter = self.$filter.add( `.vp-single-filter.vp-id-${ self.id } .vp-portfolio__filter-wrap` ); } // find single sort block. if ( self.id ) { self.$sort = self.$sort.add( `.vp-single-sort.vp-id-${ self.id } .vp-portfolio__sort-wrap` ); } // user options self.userOptions = userOptions; self.firstRun = true; self.init(); } // emit event // Example: // $(document).on('init.vpf', function (event, infiniteObject) { // console.log(infiniteObject); // }); emitEvent( event, data ) { data = data ? [ this ].concat( data ) : [ this ]; this.$item.trigger( `${ event }.vpf`, data ); this.$item.trigger( `${ event }.vpf-uid-${ this.uid }`, data ); } /** * Init */ init() { const self = this; // destroy if already inited if ( ! self.firstRun ) { self.destroy(); } self.destroyed = false; self.$item.addClass( 'vp-portfolio__ready' ); // init options self.initOptions(); // init events self.initEvents(); // init layout self.initLayout(); // init custom colors self.initCustomColors(); self.emitEvent( 'init' ); if ( self.id ) { $( `.vp-single-filter.vp-id-${ self.id }` ) .addClass( 'vp-single-filter__ready' ) .parent( '.vp-portfolio__layout-elements' ) .addClass( 'vp-portfolio__layout-elements__ready' ); $( `.vp-single-sort.vp-id-${ self.id }` ) .addClass( 'vp-single-sort__ready' ) .parent( '.vp-portfolio__layout-elements' ) .addClass( 'vp-portfolio__layout-elements__ready' ); } // resized self.resized(); // images loaded self.imagesLoaded(); self.emitEvent( 'inited' ); self.firstRun = false; } /** * Check if script loaded in preview. * * @return {boolean} is in preview. */ isPreview() { const self = this; return !! self.$item.closest( '#vp_preview' ).length; } /** * Called after resized container. */ resized() { windowResizeEmit(); this.emitEvent( 'resized' ); } /** * Images loaded. */ imagesLoaded() { const self = this; if ( ! self.$items_wrap.imagesLoaded ) { return; } self.$items_wrap.imagesLoaded().progress( () => { this.emitEvent( 'imagesLoaded' ); } ); } /** * Destroy */ destroy() { const self = this; // remove loaded class self.$item.removeClass( 'vp-portfolio__ready' ); if ( self.id ) { $( `.vp-single-filter.vp-id-${ self.id }` ) .removeClass( 'vp-single-filter__ready' ) .parent( '.vp-portfolio__layout-elements' ) .removeClass( 'vp-portfolio__layout-elements__ready' ); $( `.vp-single-sort.vp-id-${ self.id }` ) .removeClass( 'vp-single-sort__ready' ) .parent( '.vp-portfolio__layout-elements' ) .removeClass( 'vp-portfolio__layout-elements__ready' ); } // destroy events self.destroyEvents(); // remove all generated styles self.removeStyle(); self.renderStyle(); self.emitEvent( 'destroy' ); self.destroyed = true; } /** * Add style to the current portfolio list * * @param {string} selector css selector * @param {string} styles object with styles * @param {string} media string with media query */ addStyle( selector, styles, media ) { media = media || ''; const self = this; const { uid } = self; if ( ! self.stylesList ) { self.stylesList = {}; } if ( typeof self.stylesList[ uid ] === 'undefined' ) { self.stylesList[ uid ] = {}; } if ( typeof self.stylesList[ uid ][ media ] === 'undefined' ) { self.stylesList[ uid ][ media ] = {}; } if ( typeof self.stylesList[ uid ][ media ][ selector ] === 'undefined' ) { self.stylesList[ uid ][ media ][ selector ] = {}; } self.stylesList[ uid ][ media ][ selector ] = $.extend( self.stylesList[ uid ][ media ][ selector ], styles ); self.emitEvent( 'addStyle', [ selector, styles, media, self.stylesList, ] ); } /** * Remove style from the current portfolio list * * @param {string} selector css selector (if not set - removed all styles) * @param {string} styles object with styles * @param {string} media string with media query */ removeStyle( selector, styles, media ) { media = media || ''; const self = this; const { uid } = self; if ( ! self.stylesList ) { self.stylesList = {}; } if ( typeof self.stylesList[ uid ] !== 'undefined' && ! selector ) { self.stylesList[ uid ] = {}; } if ( typeof self.stylesList[ uid ] !== 'undefined' && typeof self.stylesList[ uid ][ media ] !== 'undefined' && typeof self.stylesList[ uid ][ media ][ selector ] !== 'undefined' && selector ) { delete self.stylesList[ uid ][ media ][ selector ]; } self.emitEvent( 'removeStyle', [ selector, styles, self.stylesList ] ); } /** * Render style for the current portfolio list */ renderStyle() { const self = this; // timeout for the case, when styles added one by one const { uid } = self; let stylesString = ''; if ( ! self.stylesList ) { self.stylesList = {}; } // create string with styles if ( typeof self.stylesList[ uid ] !== 'undefined' ) { Object.keys( self.stylesList[ uid ] ).forEach( ( m ) => { // media if ( m ) { stylesString += `@media ${ m } {`; } Object.keys( self.stylesList[ uid ][ m ] ).forEach( ( s ) => { // selector const selectorParent = `.vp-uid-${ uid }`; let selector = `${ selectorParent } ${ s }`; // add parent selector after `,`. selector = selector.replace( /, |,/g, `, ${ selectorParent } ` ); stylesString += `${ selector } {`; Object.keys( self.stylesList[ uid ][ m ][ s ] ).forEach( ( p ) => { // property and value stylesString += `${ p }:${ self.stylesList[ uid ][ m ][ s ][ p ] };`; } ); stylesString += '}'; } ); // media if ( m ) { stylesString += '}'; } } ); } // add in style tag let $style = $( `#vp-style-${ uid }` ); if ( ! $style.length ) { $style = $( '<style>' ) .attr( 'id', `vp-style-${ uid }` ) .appendTo( 'head' ); } $style.html( stylesString ); self.emitEvent( 'renderStyle', [ stylesString, self.stylesList, $style, ] ); } /** * First char to lower case * * @param {string} str string to transform * @return {string} result string */ firstToLowerCase( str ) { return str.substr( 0, 1 ).toLowerCase() + str.substr( 1 ); } /** * Init options * * @param {Object} userOptions user options */ initOptions( userOptions ) { const self = this; // default options self.defaults = { layout: 'tile', itemsGap: 0, pagination: 'load-more', }; // new user options if ( userOptions ) { self.userOptions = userOptions; } // prepare data options const dataOptions = self.$item[ 0 ].dataset; const pureDataOptions = {}; Object.keys( dataOptions ).forEach( ( k ) => { if ( k && k.substring( 0, 2 ) === 'vp' ) { pureDataOptions[ self.firstToLowerCase( k.substring( 2 ) ) ] = dataOptions[ k ]; } } ); self.options = $.extend( {}, self.defaults, pureDataOptions, self.userOptions ); self.emitEvent( 'initOptions' ); } /** * Init events */ initEvents() { const self = this; const evp = `.vpf-uid-${ self.uid }`; // Stretch function stretch() { const rect = self.$item[ 0 ].getBoundingClientRect(); const { left } = rect; const right = window.innerWidth - rect.right; const ml = parseFloat( self.$item.css( 'margin-left' ) || 0 ); const mr = parseFloat( self.$item.css( 'margin-right' ) || 0 ); self.$item.css( { marginLeft: ml - left, marginRight: mr - right, maxWidth: 'none', width: 'auto', } ); } if ( self.$item.hasClass( 'vp-portfolio__stretch' ) && ! self.isPreview() ) { $wnd.on( `load${ evp } resize${ evp } orientationchange${ evp }`, () => { stretch(); } ); stretch(); } // add helper focus class // TODO: change to CSS :has() when will be widely available // @link https://caniuse.com/?search=%3Ahas self.$item.on( `focus${ evp }`, '.vp-portfolio__item a', function () { const $item = $( this ).closest( '.vp-portfolio__item' ); $item.addClass( 'vp-portfolio__item-focus' ); if ( isFocusVisible ) { $item.addClass( 'vp-portfolio__item-focus-visible' ); } } ); self.$item.on( `blur${ evp }`, '.vp-portfolio__item a', function () { $( this ) .closest( '.vp-portfolio__item' ) .removeClass( 'vp-portfolio__item-focus vp-portfolio__item-focus-visible' ); } ); self.$filter.on( `click${ evp }`, '.vp-filter .vp-filter__item a', function ( e ) { e.preventDefault(); const $this = $( this ); if ( ! self.loading ) { $this .closest( '.vp-filter__item' ) .addClass( 'vp-filter__item-active' ) .siblings() .removeClass( 'vp-filter__item-active' ); } self.loadNewItems( $this.attr( 'href' ), true ); } ); // on sort click self.$sort.on( `click${ evp }`, '.vp-sort .vp-sort__item a', function ( e ) { e.preventDefault(); const $this = $( this ); if ( ! self.loading ) { $this .closest( '.vp-sort__item' ) .addClass( 'vp-sort__item-active' ) .siblings() .removeClass( 'vp-sort__item-active' ); } self.loadNewItems( $this.attr( 'href' ), true ); } ); // on filter/sort select change self.$filter .add( self.$sort ) .on( `change${ evp }`, '.vp-filter select, .vp-sort select', function () { const $this = $( this ); const value = $this.val(); const $option = $this.find( `[value="${ value }"][data-vp-url]` ); if ( $option.length ) { self.loadNewItems( $option.attr( 'data-vp-url' ), true ); } } ); // Function to handle scrolling to top function scrollToTop( $pagination ) { if ( self.options.pagination === 'paged' && $pagination.hasClass( 'vp-pagination__scroll-top' ) ) { const $adminBar = $( '#wpadminbar' ); const currentTop = window.pageYOffset || document.documentElement.scrollTop; let { top } = self.$item.offset(); // Custom user offset if ( $pagination.attr( 'data-vp-pagination-scroll-top' ) ) { top -= parseInt( $pagination.attr( 'data-vp-pagination-scroll-top' ), 10 ) || 0; } // Admin bar offset if ( $adminBar.length && $adminBar.css( 'position' ) === 'fixed' ) { top -= $adminBar.outerHeight(); } // Limit max offset top = Math.max( 0, top ); if ( currentTop > top ) { window.scrollTo( { top, behavior: 'smooth', } ); } } } // Handle pagination click self.$item.on( `click${ evp }`, '.vp-pagination .vp-pagination__item a', function ( e ) { e.preventDefault(); const $this = $( this ); const $pagination = $this.closest( '.vp-pagination' ); if ( $pagination.hasClass( 'vp-pagination__no-more' ) && self.options.pagination !== 'paged' ) { return; } self.loadNewItems( $this.attr( 'href' ), self.options.pagination === 'paged' ); scrollToTop( $pagination ); } ); // on categories of item click self.$item.on( `click${ evp }`, '.vp-portfolio__items .vp-portfolio__item-meta-category a', function ( e ) { e.preventDefault(); e.stopPropagation(); self.loadNewItems( $( this ).attr( 'href' ), true ); } ); // resized container self.$item.on( `transitionend${ evp }`, '.vp-portfolio__items', ( e ) => { if ( e.currentTarget === e.target ) { self.resized(); } } ); self.emitEvent( 'initEvents' ); } /** * Destroy events */ destroyEvents() { const self = this; const evp = `.vpf-uid-${ self.uid }`; // destroy click events self.$item.off( evp ); self.$filter.off( evp ); self.$sort.off( evp ); // destroy window events $wnd.off( evp ); self.emitEvent( 'destroyEvents' ); } /** * Init layout */ initLayout() { const self = this; self.emitEvent( 'initLayout' ); self.renderStyle(); } /** * Init custom color by data attributes: * data-vp-bg-color * data-vp-text-color */ initCustomColors() { const self = this; self.$item.find( '[data-vp-bg-color]' ).each( function () { const val = $( this ).attr( 'data-vp-bg-color' ); self.addStyle( `[data-vp-bg-color="${ val }"]`, { 'background-color': `${ val } !important`, } ); } ); self.$item.find( '[data-vp-text-color]' ).each( function () { const val = $( this ).attr( 'data-vp-text-color' ); self.addStyle( `[data-vp-text-color="${ val }"]`, { color: `${ val } !important`, } ); } ); self.renderStyle(); self.emitEvent( 'initCustomColors' ); } /** * Add New Items * * @param {object|dom|jQuery} $items - elements. * @param {bool} removeExisting - remove existing elements. * @param {Object} $newVP - new visual portfolio jQuery. */ addItems( $items, removeExisting, $newVP ) { const self = this; self.emitEvent( 'addItems', [ $items, removeExisting, $newVP ] ); } /** * Remove Items * * @param {object|dom|jQuery} $items - elements. */ removeItems( $items ) { const self = this; self.emitEvent( 'removeItems', [ $items ] ); } /** * AJAX Load New Items * * @param {string} url - url to request. * @param {bool} removeExisting - remove existing elements. * @param {Function} cb - callback. */ loadNewItems( url, removeExisting, cb ) { const self = this; const { randomSeed } = self.options; if ( ( self.loading && typeof self.loading.readyState === 'undefined' ) || ! url || self.href === url ) { return; } // Abort previous AJAX loader to prevent conflict. // We need it mostly for Search feature, because users can type also when already in loading state. if ( self.loading && self.loading.readyState && self.loading.abort ) { self.loading.abort(); } const ajaxData = { method: 'POST', url, data: { vpf_ajax_call: true, vpf_random_seed: typeof randomSeed !== 'undefined' ? randomSeed : false, }, complete( { responseText } ) { self.href = url; self.replaceItems( responseText, removeExisting, cb ); }, }; self.loading = true; self.$item.addClass( 'vp-portfolio__loading' ); self.emitEvent( 'startLoadingNewItems', [ url, ajaxData ] ); self.loading = $.ajax( ajaxData ); } /** * Replace items to the new loaded using AJAX * * @param {string} content - new page content. * @param {bool} removeExisting - remove existing elements. * @param {Function} cb - callback. */ replaceItems( content, removeExisting, cb ) { const self = this; if ( ! content ) { return; } // load to invisible container, then append to posts container content = content .replace( '<body', '<body><div id="vp-ajax-load-body"' ) .replace( '</body>', '</div></body>' ); const $body = $( content ).filter( '#vp-ajax-load-body' ); // find current block on new page const $newVP = $body.find( `.vp-portfolio.vp-uid-${ self.uid }` ); // insert new items if ( $newVP.length ) { const newItems = $newVP.find( '.vp-portfolio__items' ).html(); const nothingFound = $newVP.hasClass( 'vp-portfolio-not-found' ); // We should clean up notices here, as they may be cloned over and over. self.$item.find( '.vp-notice' ).remove(); if ( nothingFound ) { self.$item .find( '.vp-portfolio__items-wrap' ) .before( $newVP.find( '.vp-notice' ).clone() ); self.$item.addClass( 'vp-portfolio-not-found' ); } else { self.$item.removeClass( 'vp-portfolio-not-found' ); } // update filter if ( self.$filter.length ) { self.$filter.each( function () { const $filter = $( this ); let newFilterContent = ''; if ( $filter.parent().hasClass( 'vp-single-filter' ) ) { newFilterContent = $body .find( `[class="${ $filter .parent() .attr( 'class' ) .replace( ' vp-single-filter__ready', '' ) }"] .vp-portfolio__filter-wrap` ) .html(); } else { newFilterContent = $newVP .find( '.vp-portfolio__filter-wrap' ) .html(); } $filter.html( newFilterContent ); } ); } // update sort if ( self.$sort.length ) { self.$sort.each( function () { const $sort = $( this ); let newFilterContent = ''; if ( $sort.parent().hasClass( 'vp-single-sort' ) ) { newFilterContent = $body .find( `[class="${ $sort .parent() .attr( 'class' ) .replace( ' vp-single-sort__ready', '' ) }"] .vp-portfolio__sort-wrap` ) .html(); } else { newFilterContent = $newVP .find( '.vp-portfolio__sort-wrap' ) .html(); } $sort.html( newFilterContent ); } ); } // update pagination if ( self.$pagination.length ) { self.$pagination.html( $newVP.find( '.vp-portfolio__pagination-wrap' ).html() ); } self.addItems( $( newItems ), removeExisting, $newVP ); self.emitEvent( 'loadedNewItems', [ $newVP, removeExisting, content, ] ); if ( cb ) { cb(); } } // update next page data const nextPageUrl = $newVP.attr( 'data-vp-next-page-url' ); self.options.nextPageUrl = nextPageUrl; self.$item.attr( 'data-vp-next-page-url', nextPageUrl ); self.$item.removeClass( 'vp-portfolio__loading' ); self.loading = false; self.emitEvent( 'endLoadingNewItems' ); // images loaded self.imagesLoaded(); // init custom colors self.initCustomColors(); } } // extend VP object. $( document ).trigger( 'extendClass.vpf', [ VP ] ); // global definition const plugin = function ( options, ...args ) { let ret; this.each( function () { if ( typeof ret !== 'undefined' ) { return; } if ( typeof options === 'object' || typeof options === 'undefined' ) { if ( ! this.vpf ) { this.vpf = new VP( $( this ), options ); } } else if ( this.vpf ) { ret = this.vpf[ options ]( ...args ); } } ); return typeof ret !== 'undefined' ? ret : this; }; plugin.constructor = VP; // no conflict const oldPlugin = $.fn.vpf; $.fn.vpf = plugin; $.fn.vpf.noConflict = function () { $.fn.vpf = oldPlugin; return this; }; // initialization $( () => { $( '.vp-portfolio' ).vpf(); } ); const throttledInit = throttle( 200, rafSchd( () => { $( '.vp-portfolio:not(.vp-portfolio__ready)' ).vpf(); } ) ); if ( window.MutationObserver ) { new window.MutationObserver( throttledInit ).observe( document.documentElement, { childList: true, subtree: true, } ); } else { $( document ).on( 'DOMContentLoaded DOMNodeInserted load', () => { throttledInit(); } ); }
Save
Cancel