Flickity & Swup - destroying flickity - javascript

I am trying to destroy and re-load my Flickity slideshow while using Swup for page transitions, and I am not having much luck. This is my js file:
import Swup from 'swup';
var Flickity = require('flickity');
function init() {
if (document.querySelector('.testimonials-slideshow')) {
var flkty = new Flickity('.testimonials-slideshow', {
wrapAround: true,
pageDots: false,
autoPlay: true,
arrowShape: 'M68.374,83.866L31.902,50L68.374,16.134L64.814,12.3L24.214,50L64.814,87.7L68.374,83.866Z'
});
}
}
function unload() {
flkty.destroy();
}
init();
const swup = new Swup();
swup.on('contentReplaced', init);
swup.on('willReplaceContent', unload);
But when I try this I get the error flkty is not defined. Can anyone give me any pointers on this?

Variable scoping
As mentioned by CBroe, your var is undefined because of where you define it. It is defined in a function, but should be defined at the "top level".
import Swup from 'swup';
var Flickity = require('flickity');
// Added a "global" definition here:
var flkty;
function init() {
if (document.querySelector('.testimonials-slideshow')) {
// Removed var:
flkty = new Flickity('.testimonials-slideshow', {
wrapAround: true,
pageDots: false,
autoPlay: true,
arrowShape: 'M68.374,83.866L31.902,50L68.374,16.134L64.814,12.3L24.214,50L64.814,87.7L68.374,83.866Z'
});
}
}
function unload() {
flkty.destroy();
}
init();
const swup = new Swup();
swup.on('contentReplaced', init);
swup.on('willReplaceContent', unload);
Furthermore, if you are using any kind of module bundler, sometimes it can still get lost, so you could consider doing something like:
window.flkty = new Flickity('.testimonials-slideshow', ...
And always reference it in that way, i.e.
window.flkty.destroy();
Only destroying instances that exist
That's it for your variable definition. The next potential error is that you only init flkty when the query selector matches:
if (document.querySelector('.testimonials-slideshow')) {
But you destroy it every willReplaceContent, so really you could do with a check on "is it inited, this page load?". In this instance, you can do a check like so:
// Init the var as false:
var flkty = false
function init() {
if (document.querySelector('.testimonials-slideshow')) {
flkty = new Flickity('.testimonials-slideshow', ...);
}
}
function unload() {
if(flkty){
flkty.destroy();
// Make sure the flkty var is set to false at the end:
flkty = false;
}
}
Neatening up your code
This can all get a bit out of hand, so what we started doing was creating modules. Here is a skeleton of a carousel module we use:
// modules/Carousel.js
import Swiper from "swiper";
export default {
carouselEl: null,
carouselSwiper: null,
setup() {
this.carouselEl = document.getElementById("header-carousel");
if (!this.carouselEl) {
// Just stop if there is no carousel on this page
return;
}
this.carouselSwiper = new Swiper(this.carouselEl, { ... });
this.carouselSwiper.on("slideChange", () => { ... });
},
destroy() {
// If we already have one:
if (this.carouselSwiper) {
this.carouselSwiper.destroy();
}
// Make sure we are reset, ready for next time:
this.carouselSwiper = null;
},
};
Then, in our main.js we do something like you have:
import Carousel from "./modules/Carousel.js";
function init(){
Carousel.setup();
// Add more here as the project grows...
}
function unload(){
Carousel.unload();
}
swup = new Swup();
swup.on("contentReplaced", init);
swup.on("willReplaceContent", unload);
init();
All of the modules have setup and unload functions that won't break if the elements don't exist, so we can call all of them on each page load and unload.
I love swup but also have personal experience in the nightmare of initing and destroying things so let me know if you need any further help.

Related

Removing listeners / scoping issues

In a project with a soft page transition I have a hard time killing all redundant listeners. On a page-change the removeEventListeners function is called. However I'm not able to remove the listeners on the window.
I know the variable scoping in the first code example is wrong. And I've been trying to fix it in the second example. Unsuccessfully. Any ideas?
export default class Slider extends Core {
init() {
const swiper = new Swiper('.mySlider', {
slidesOffsetBefore: this.sliderOffset(),
});
window.addEventListener('resize', () => {
swiper.params.slidesOffsetBefore = this.sliderOffset();
swiper.update();
});
}
removeEventListeners() {
// this doesn't work since there is no function reference
window.removeEventListener('resize');
}
sliderOffset() {
// does stuff and...
return value
}
}
This doesn't seem to work either...
export default class Slider extends Core {
init() {
this.swiper = new Swiper('.mySlider', {
slidesOffsetBefore: this.sliderOffset(),
});
window.addEventListener('resize', this.updateSwiper);
}
updateSwiper() {
this.swiper.params.slidesOffsetBefore = this.sliderOffset();
this.swiper.update();
}
removeEventListeners() {
window.removeEventListener('resize', this.updateSwiper);
}
sliderOffset() {
// does stuff and...
return value
}
}

Displaying a loading animation during a vue js function

A simple show and hide on the loading element is failing during the Vue js function execution.
I have a vue js function which will do some things and update the data when it's done. I'm trying to show a loading animation during this process. I've dummied the process down to a simple loop for the purposes of this discussion to eliminate all other issues (i.e ajax, async, etc.).
My HTMl for the button looks like this:
<button type="button" v-on:click="DoStuff()">Do Stuff</button>
My vue js code looks like this:
var client = new Vue({
el: '#client',
data: {
someData: []
},
methods: {
DoStuff: function() {
//Show Loader
$(".loader").show();
//Waste 5 seconds
for (var i = 0; i < 100000000; i++) {
var x = i;
}
//Hide loader
$(".loader").hide();
// let me know it's done.
alert('cool');
}
The loader never shows. If I comment out the hide command, the loader show up AFTER the alert. Makes me think some sort of async operation is going on under the hood but I'm not doing async stuff.
You don't need to use jQuery to set and hide elements on the page based on a condition, in fact that kind of defeats the purpose of what VueJS and other front end javascript frameworks are used for.
First you should add a property called loading to your data object
data: {
someData: [],
loading: false
}
Then, in your doStuff function, remove the jquery lines and set the loading property accordingly
methods: {
doStuff: function() {
//Show Loader
this.loading = true;
//Waste 5 seconds
setTimeout(() => {
this.loading = false;
}, 5000)
}
}
Finally, in your view, add this line
<div v-if="loading">Loading some data</div>
Ill end this by saying that I also think your snippet is a little messed up. the methods property should be inside the Vue instance definition.
var client = new Vue({
el: '#client',
data: {
someData: [],
loading: false
},
methods: {
doStuff: function() {
//Show Loader
this.loading = true;
//Waste 5 seconds
setTimeout(() => {
this.loading = false;
}, 5000)
}
}
}
Don't use jquery. You can so this with vuejs.
var client = new Vue({
el: '#client',
data: {
someData: [],
loading: false,
},
methods: {
DoStuff() {
//Show Loader
this.loading = true;
//Waste 5 seconds
//Hide loader
this.loading = true;
// let me know it's done.
alert('cool');
}
And your HTML.
<div class="loading" v-if="loading">
Loading....
</div>
<div v-else>
The rest of your website
</div>
It's better to not mix with JQuery, just use Vue conditional displaying instead (v-show).
In your loader:
<div class="loader" v-show="loading"></div>
In your Vue code:
var client = new Vue({
el: '#client',
data: {
someData: [],
loading: false
},
methods: {
DoStuff: function() {
this.loading = true;
//Waste 5 seconds
for (var i = 0; i < 100000000; i++) {
var x = i;
}
this.loading = false;
// let me know it's done.
alert('cool');
}
Thanks Everyone. I have to focus on a more important feature that came up. But I just wanted to share what I've gleamed. This is not a (or the) final answer but it explains the problem and the correct approach.
The problem was correctly identified by Andrew Lohr as is further explained here:
jQuery hide() and show() not immediately run when reversed later in the function
The best (but not only) solution would be use an event bus as mentioned by perellorodrigo here: https://flaviocopes.com/vue-components-communication/
I will post my final solution when I get around to it.

Excalibur project not showing anything

My code is supposedly made to show two sprites of buttons (nothing terribly complex (or is it?), but nothing is appearing, not even the blue screen that is supposed to show with only creating the game variable and initiating it. All my code has been made from following the official Excalibur documentation, so what is happening?
The code:
var game = new ex.Engine({
width: 1024,
height: 768
});
function loadAssets()
{
var loader = new ex.Loader();
var resources = {
txGameTitle: new.ex.Texture("icons/GUI/final/"),
txStartButton: new.ex.Texture("icons/GUI/final/MenuPlayButton.png"),
txLoadButton: new.ex.Texture("icons/GUI/final/MenuLoadButton.png"),
txOptionsButton: new.ex.Texture("icons/GUI/final/"),
txExitButton: new.ex.Texture("icons/GUI/final/"),
txMenuBackground: new.ex.Texture("icons/GUI/final/"),
txMenuMusic: new.ex.Sound("icons/GUI/final/")
};
for (var loadable in resources)
{
if (resources.hasOwnProperty(loadable))
{
loader.addResource(resources[loadable]);
}
}
}
function startUp()
{
var StartButton = new ex.Actor.extend({
onInitialize: function (engine)
{
this.addDrawing(txStartButton.asSprite());
}
});
var LoadButton = new ex.Actor.extend({
onInitialize: function (engine)
{
this.addDrawing(txLoadButton.asSprite());
}
});
}
function init()
{
loadAssets();
startUp();
}
init();
game.start(loader).then(function () {
console.log("Game started!");
});
sorry for the bad formatting.
I think that could be because of a code error. I noticed that right at the end of the file
game.start(loader)
the loader variable is referenced but seems like it is not defined. There is the same variable which created inside loadAssets function, but it is a local one. Probably in order to use it, you need to define it above.
var loader;
function loadAssets() {
loader = ...
}
...other code
game.start(loader).then(...
Another variant is to define loader outside the loadAssets function.
var loader = new ex.Loader();
function loadAssets() {
var resources = {...
}
...other code
game.start(loader).then(...

Passing functions between RequireJS files

I've got a file which needs to run on page load (randomise_colors.js), but also needs to be called by another file as part of a callback function (in infinite_scroll.js). The randomise_colors script just loops through a list of posts on the page and assigns each one a color from an array which is used on the front-end.
Infinite Scroll loads new posts in to the DOM on a button click, but because the randomise_colors.js file has already ran on page load, new content loaded is not affected by this so I need it to run again. I'm open to other suggestions if it sounds like I could be tackling the problem in a different way, I'm no JS expert.
Currently I'm getting Uncaught ReferenceError: randomise_colours is not defined referring this line of infinite_scroll.js:
randomise_colours.init();
I'm calling all files that need be loaded on document.ready in app.js
require(['base/randomise-colours', 'base/infinite-scroll'],
function(randomise_colours, infinite_scroll) {
var $ = jQuery;
$(document).ready(function() {
infinite_scroll.init();
randomise_colours.init();
});
}
);
This is infinite_scroll.js which initialises Infinite Scroll and features the callback. The callback function runs whenever new items are loaded in via AJAX using the Infinite Scroll jQuery plugin. I've put asterix around the area where I need to run the randomise_colors.init() function from randomise_colors.js.
define(['infinitescroll'], function() {
var $ = jQuery,
$loadMore = $('.load-more-posts a');
function addClasses() {
**randomise_colours.init();**
};
return {
init: function() {
if($loadMore.length >= 1) {
this.setUp();
} else {
return false;
}
},
setUp: function() {
this.initInfiniteScroll();
},
initInfiniteScroll: function() {
$('.article-listing').infinitescroll({
navSelector : '.load-more-posts',
nextSelector : '.load-more-posts a',
itemSelector : '.standard-post'
}, function(newItems) {
addClasses();
});
//Unbind the standard scroll-load function
$(window).unbind('.infscr');
//Click handler to retrieve new posts
$loadMore.on('click', function() {
$('.article-listing').infinitescroll('retrieve');
return false;
});
}
};
});
And this is my randomise_colors.js file which runs fine on load, but needs to be re-called again after new content has loaded in.
define([], function() {
var $ = jQuery,
$colouredSlide = $('.image-overlay'),
colours = ['#e4cba3', '#867d75', '#e1ecb9', '#f5f08a'],
used = [];
function pickRandomColour() {
if(colours.length == 0) {
colours.push.apply(colours, used);
used = [];
}
var selected = colours[Math.floor(Math.random() * colours.length)];
var getSelectedIndex = colours.indexOf(selected);
colours.splice(getSelectedIndex, 1);
used.push(selected);
return selected;
};
return {
init: function() {
if($colouredSlide.length >= 1) {
this.setUp();
} else {
return false;
}
},
setUp: function() {
this.randomiseColours();
},
randomiseColours: function() {
console.log('randomise');
$colouredSlide.each(function() {
var newColour = pickRandomColour();
$(this).css('background', newColour);
});
}
};
});
You would have to reference randomiseColours inside the infiniteScroll file. So you need to change your define function to the following:
define(['infinitescroll', 'randomise-colours'], function(infiniteScroll, randomise_colours)
Remember that when using require you need to reference all variables through the define function, otherwise they will not be recognised.

Why can I not access global variables in my js function?

I am trying to set up a set of global variables in my js app script that allows me to access them throughout my functions in the page and the site. For some reason I keep getting undefined in my console even though I know the objects are there.
This is my js snippet the js is quite long so I thought I would show you the important bit thats wrong(i think)
(function ($) {
"use strict";
var global = function() {
this.init();
};
global.prototype = {
// ------------------------------------
// Global variables
mainContainer : 'div#container',
tmbContainer : '.rsThumbsContainer',
tmbContainerT : '.rsThumbsContainer',
slider : '.collection #gallery-t-group',
body : 'body',
close : '<div class="close-button" id="close"><p class="icon-close"></p></div>',
socials : '.socialbar-vertical',
loader : '<div class="loader"></div>',
gallery : '.collection #gallery-t-group',
// ------------------------------------
// Initialise
init: function() {
var app = this;
this.testGlobals();
this.loadSlide();
this.fakingIt();
this.unloadSlide();
this.mobileNav();
this.loadThumbs();
this.royalSlider();
this.thumbsSwitch();
this.functionResize();
this.theSocialActivated();
this.theSliderActivated();
this.theSliderDeactivated();
this.slideEventChange();
console.log('======> new.global.js');
},
// ------------------------------------
// Functions
testGlobals: function () {
console.log(body);
}
}
$(document).ready(function () {
new global();
});
})(jQuery);
In my console I get
Uncaught ReferenceError: body is not defined
Is there a simple thing I am missing here.
Try replacing
testGlobals: function () {
console.log(body);
}
with
testGlobals: function () {
console.log(this.body);
}
or
testGlobals: function () {
console.log(global.body);
}

Categories