Lazy load image in tippy.js Tooltip? - javascript

I'm using tippy.js to generate tooltips. Everything works fine however I have allot of tool tips each with different image content loaded. To reduce load time I would like to lazy load the image to only apear when the tooltip is visible.
I know tippy.js has a built in function to call AJAX content (https://atomiks.github.io/tippyjs/ajax/) - however the content I want to load is in the HTML and I do not want to have to write separate javascript for each tooltip. Ideally it would be great to somehow fetch the img scr of the particular tip I am hovering and than call it to load with the onShow(instance) ~ i.e. fetch('$(this).find("img").data("src");'). I tried a few variations of this but nothing is working.
Here is a codepen (without) lazy loading images: https://codepen.io/jinch/pen/aboNOvP?editors=1010
I have tried to manipulate the AJAX example on their website to work but had not luck (see below).
tippy('body .tippy', {
theme: 'google',
touchHold: true,
hideOnClick: false,
interactive: true,
placement: 'left',
distance: 20,
arrow: true,
animateFill: false,
animation: 'shift-away',
onShow(instance) {
if (instance.state.ajax === undefined) {
instance.state.ajax = {
isFetching: false,
canFetch: true,
}
}
if (instance.state.ajax.isFetching || !instance.state.ajax.canFetch) {
return
}
fetch('$(this).find("img").data("src")')
.then(response => response.blob())
.then(blob => {
})
.catch(error => {
// ...
})
.finally(() => {
instance.state.ajax.isFetching = false
})
},
onHidden(instance) {
instance.setContent('Loading...')
instance.state.ajax.canFetch = true
},
})
The idea result would having the images load only when called to reduce the initial load time of the page.

Thanks to #atomiks you can find the answer here: https://github.com/atomiks/tippyjs/issues/562#issuecomment-521650755

Related

Wordpress custom block not showing in block inserter

I'm trying to create a custom block for a site but the block is not appearing in the editor dialogue. I've gone through multiple tutorials and changed my code a lot but it simply won't work.
What I've checked:
The block is added through a plugin but it also doesn't work when
moved to the theme.
I know the plugin is working correctly as I can use other wp
hooks/actions with no issues within the plugin.
I have tried using both 'init' & 'enqueue_block_assets' but neither
work.
I have verified all the file locations and paths are correct as I
have echoed them out to check.
I have changed to the default theme and it still does not appear.
Any help would be appreciated.
Here is the js block src (which is compiled):
import { registerBlockType } from '#wordpress/blocks'
registerBlockType('ghs/landing-page-block', {
title: 'Landing Page',
apiVersion: 2,
category: 'design',
icon: 'smiley',
description: 'Layout for the GHS landing page',
keywords: ['GHS', 'landing', 'page', 'front'],
edit: () => {
return (<div>hello</div>)
},
save: () => {
return (<div>hello</div>)
}
});
and the php registering it:
add_action('init', function() {
$asset_file = include( WP_PLUGIN_DIR . '/ghs-custom-blocks/assets/js/landing-page-block.asset.php');
wp_register_script('ghs-landing-page',
WP_PLUGIN_DIR . '/ghs-custom-blocks/assets/js/landing-page-block.js',
$asset_file['dependencies'],
$asset_file['version']);
register_block_type('ghs/landing-page-block', [
'api_version' => 2,
'editor_script' => 'ghs-landing-page',
]);
});
Solved it, it was because I was using WP_PLUGIN_DIR instead of plugin_dir_url(__FILE__). It meant the js request url was from the root instead of the wp installation.

Unable to get data from created() to data() in VueJS 2

I am fetching data from API inside the created method and i want to use these data in the page.
Here is my code.
created(){
let id = this.$route.params.id
let videos;
this.$axios.get(this.$axios.defaults.apiURL + 'v1.0.0/tips/' +id,).then((response) => {
this.videos = response.data.data;
}, (error) => {
toast.$toast.error('Something went wrong! Please try again', {
position: 'top'
})
});
},
data(){
let videos = this.videos;
return {
video: {
sources: [{
src: videos.video_url,
type: 'video/mp4'
}],
options: {
autoplay: true,
volume: 0.6,
poster: videos.thumbnail
}
}
}
}
I am getting error that thumbnail and video_url is not defined. This 2 values are coming from API response. How can i solve this? Thanks
I can see two obvious issues with your code (without seeing it in action):
created is a synchronous hook, but your axios request is returning a promise. Instead of waiting for the promise, you are immediately trying to show the result, hence the issue you are encountering - the data just hasn't arrived yet.
Your use of this seems a bit chaotic (i.e. let videos = this.videos - where would this.videos come from? The only other 'videos' is declared inside of a different function with let)
There are multiple ways to solve this, depending on what you want to show while you are fetching the data and what type of component this is - if you want to show a spinner while you are waiting for the request to be answered, or if you just want to show some progress bar on the previous page and only enter this one once it's loaded.
In-component loading
In the first case, I would suggest setting a variable or using a loader management solution like vue-wait. Your code could look like this then:
data() {
return {
loading: true,
videos: null,
}
},
computed: {
video() {
return this.videos ? {
sources: [{
src: this.videos.video_url,
type: 'video/mp4'
}],
options: {
autoplay: true,
volume: 0.6,
poster: this.videos.thumbnail
}
} : null
}
},
methods: {
fetch() {
let id = this.$route.params.id
this.$axios.get(this.$axios.defaults.apiURL + 'v1.0.0/tips/' + id, ).then((response) => {
this.videos = response.data.data;
}, (error) => {
toast.$toast.error('Something went wrong! Please try again', {
position: 'top'
})
}).finally(() => (this.loading = false));
},
},
created() {
this.fetch()
},
In your template, you would add somewhere v-if=!loading to make sure that the request has finished before you try to show something
Data-fetching before entering page
If this is a page though, you could request the data in beforeRouteEnter - there's a whole article that explains the principle on the vue site

Wait for dynamic content to populate dom in react

I am trying to detect when a list has been fully populated with items so I can scroll to a specific one in React.
I've tried using setTimeout and requestAnimationFrame to delay the execution but both seemed hacky...
Use effect hook also doesn't work because it runs prior to the dom actually being repainted.
Heres what I have so far:
const observer = new MutationObserver(mutationList => {
console.log(mutationList);
if (listRef.current) {
if (listRef.current?.contains(searchedItemRef.current)) {
console.log('Found Node');
console.log(searchedItemRef.current);
searchedItemRef.current!.scrollIntoView({ block: 'center' });
}
}
});
useEffect(() => {
if (listRef.current)
observer.observe(listRef.current, {
childList: true,
subtree: true,
});
}, [props.activeChatChannel]); //some global state that represents chat data

How to correctly call js modules with imports from third party scripts

I have a very strange problem I can't put my finger on.
I am using Barba.js which makes awesome page transitions and Swiper.js which is a lovely carousel. The problem is that it makes your page a SPA so you have to reinit scripts. Quite a pain to tell you the truth.
In my barba.js file I call a swiper carousel at the top
import barba from "#barba/core";
import { homeCarousel } from './swiper.js'
function initCarousel() {
if (document.querySelector('.swiper-container') != null) {
homeCarousel();
}
}
barba.init({
sync: true,
preventRunning: true,
views: [{
namespace: 'main',
afterEnter() {
setTimeout(() => {
initCarousel();
}, 550);
}
}],
transitions: [{
async leave() {
done();
},
async enter() {
newPage();
},
}]
})
Then in my swiper.js file
import Swiper from 'swiper';
import 'swiper/css/swiper.css';
const homeCarousel = () => {
var hs = new Swiper('.home__carousel', {
init: false,
});
hs.init();
}
homeCarousel();
export { homeCarousel }
This all works o.k. but I have another page that doesn't have a carousel on it so .swiper-container will return null. That's good too, but because am importing at the top of barba.js
import { homeCarousel } from './swiper.js'
It's called when the page is refreshed thus causing a nasty error
Cannot read property 'push' of undefined
When the route is changed, there's no horrible console error.
I guess in an ideal world I would place this import statement inside of the if (document.querySelector('.swiper-container') != null) but imports have to be top level.
Any suggestions, please? I'm about to through the PC out of the window. Probably a good thing.
As you use swiper.js as a library it should contain only API without side effects.
In this case remove homeCarousel(); from swiper.js.

Using modal 'showOpenDialog" and then opening a modal window only works once

Electron v8.1.1 – macOS 10.14.5
I recently updated an older project to use Electron 8.1.1 and I am running into an issue with using a modal showOpenDialog and then opening a modal window with the filepicker result.
It works the first time through but if, after closing the modal window, I try to do it again, it fails –  I get a system 'beep' when trying to open the filepicker (calling the showPDFPicker function below.
I've tried to debug it but it gets deep into the Electron code and just exits without error – just the system "beep". It's as if the modal dialog never closed.
If I call the filepicker with null instead of a parent window, the routine works as expected:
dialog.showOpenDialog(null, options)
Has anyone encountered this with Electron v8+? It used to work in prior versions (afaik). I tried putting in a setTimeOut call to delay opening the modal window for a 1000 ms, thinking that maybe there was some clean up time needed – but that did not help.
I built a test project and stripped out all other code but am still getting the failure.
I'd like the showOpenDialog filepicker to be modal so I'm hoping it's a problem with my code and not a bug in Electron.
main.js
function showPDFPicker() {
var options = {
title: 'Select PDF',
properties: ['openFile'],
filters: [{ name: 'PDF', extensions: ['pdf'] }],
defaultPath: app.getPath("desktop")
}
dialog.showOpenDialog(mainWindow, options)
.then(result => {
if (result.canceled == false) {
filePickerCallback(result.filePaths)
}
}).catch(err => {
console.log('ERROR', err);
});
function filePickerCallback(filenames) {
if (filenames && filenames.length > 0) {
createPDFConvertWindow(filenames[0]);
}
}
}
function createPDFConvertWindow(pdfFile) {
pdfConvertWindow = new BrowserWindow({
parent: mainWindow,
modal: true,
show: false,
width: 800,
height: 600,
backgroundColor: '#cccccc',
center: true,
webPreferences: {
nodeIntegration: true
}
});
var theUrl = path.join(__dirname, 'app', 'import-dialog.html');
pdfConvertWindow.loadFile(theUrl);
pdfConvertWindow.on('ready-to-show', () => {
pdfConvertWindow.show();
})
}

Categories