Custom snackbar / notification with jquery not working as i want - javascript

I'm trying to make a custom notification / Snackbar / toast whatever you want to name it
And the problem I'm having is that when I click the button more than one time the snack bar just changes text and not creating a new one like stacking on top of the already existing one
Store.Toaster = (options) => {
const { text, type, time } = options;
if($(".text").html() != `` && $("#toaster").is(":visible")) {
return
}
$("#toaster").fadeIn("fast");
$("#toaster .toast-container .text").html(`${text}`);
setTimeout(() => {
$("#toaster").fadeOut("fast");
$("#toaster .toast-container .text").html(``);
}, 900);
switch (options.type) {
default:
$("#toaster .toast-container .type").hide()
break
case "success":
$("#toaster .toast-container .icontype").html(
`<i class="fas fa-shopping-cart"></i>`
);
$("#toaster .toast-container .icontype").css("color", "lightgreen");
$("#toaster .toast-container .type").css(
"border-bottom",
"1px solid lightgreen"
);
break;
case "info":
$("#toaster .toast-container .icontype").html(
`<i class="fas fa-shopping-basket"></i>`
);
$("#toaster .toast-container .icontype").css("color", "limegreen");
$("#toaster .toast-container .type").css(
"border-bottom",
"1px solid limegreen"
);
break;
case "error":
$("#toaster .toast-container .icontype").html(
`<i class="fa fa-times"></i>`
);
$("#toaster .toast-container .icontype").css("color", "lightcoral");
$("#toaster .toast-container .type").css(
"border-bottom",
"1px solid lightcoral"
);
}
};
& the CSS
#toaster {
display: none;
position: absolute;
top: 88%;
left: 50%;
transform: translate(-50%, -50%);
width: 320px;
max-width: 350px;
min-width: 320px;
color: white;
z-index: 1000;
background: #4b5962c2;
overflow: hidden;
border-radius: 4px;
.toast-container {
display: flex;
align-items: center;
.type {
color: lightgreen;
background: #272f35cb;
padding: 25px;
}
span[class="text"] {
font-size: smaller;
padding: 5px;
}
}
}
<div id="toaster">
<div class="toast-container">
<div class="type">
<span class="icontype"></span>
</div>
<span class="text"></span>
</div>
</div>
As I said, I want it to stack on top of each other so the text just doesn't change so you can see what you did before you trigged the new notification
Here's a preview on the problem https://gyazo.com/a151f4963af7a1d790a2ebd844429c02 RN it's no "big problem" but I don't understand how to make it work with the stack
Peace

To get them to stack up, you have to have multiple toast elements, not just one. And you have to manage adding and removing those individual elements. Here's an example that gives you the idea.
$(".make-toast").on('click', function() {
addToast({
text: 'hello world',
type: $(this).attr('data-type'),
time: 3000
})
})
function addToast(option) {
const id = Date.now();
option.id = id;
$("#toaster").css('display', 'block');
$("#toasts").prepend(slice(option));
setTimeout(() => {
removeToast(id);
}, option.time)
}
function removeToast(id) {
$(`#${id}`).slideUp('fast', remove);
function remove() {
$(`#${id}`).remove();
if (!$("toasts").children()) {
$("toaster").css('display', 'none');
}
}
}
function slice(option) {
let toast = $(`<div class="type ${option.type}" id="${option.id}"><div><span class="icontype"></span></div><span class="text"></span></div>`)
$(toast).find('.text').text(option.text);
return toast;
}
#toaster {
display: none;
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
max-width: 350px;
color: white;
z-index: 1000;
border-radius: 4px;
}
.toast-container {
display: flex;
align-items: center;
}
.type {
padding: 10px 25px;
margin-bottom: 3px;
animation: 300ms ease-in-out enter;
}
.type.info {
color: lightgreen;
background: #272f35cb;
}
.type.error {
color: white;
background: #dd1111dd;
}
span[class="text"] {
font-size: smaller;
padding: 5px;
}
#keyframes enter {
0% {
opacity: 0;
transform: translateY(100%);
}
100% {
opactiy: 1;
transform: translateY(0);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="make-toast" data-type="info">Info Toast</button>
<button class="make-toast" data-type="error">Error Toast</button>
<div id="toaster">
<div class="toast-container">
<div id="toasts"></div>
</div>
</div>

Related

v-calendar datepicker date range event after first date is picked

Is there a way of triggering an event for the date range picker of v-calendar after the first date is picked or stopping the inputs from adding the dates until both dates have been selected?
I have the following vue component:
new Vue({
el: "#app",
data() {
return {
range: {
start: null,
end: null
}
};
},
methods: {
handleBlur(event) {
if (event.currentTarget.value === '') {
event.currentTarget.parentNode.classList.remove("entered");
}
},
handleFocus(event) {
event.currentTarget.parentNode.classList.add("entered");
},
moveLabels() {
changeClass(this.$refs.filterDateForm);
changeClass(this.$refs.filterDateTo);
}
}
});
function changeClass(input) {
if (input.value === '') {
input.parentNode.classList.remove("entered");
} else {
input.parentNode.classList.add("entered");
}
}
#import url 'https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.min.css';
.filter__date-range-holder {
display: flex;
justify-content: space-between;
width: 95%;
}
.filter__date-range-column {
width: calc(50% - 15px);
}
.form__row {
position: relative;
margin: 1.5em 0;
background: white;
}
.form__control {
width: 100%;
border: 1px solid grey;
font-size: 1rem;
line-height: 1.5rem;
color: black;
padding: 0.75em;
background: transparent;
}
.invalid .form__control {
border-color: red;
outline-color: red;
}
.form__control:focus {
border-radius: 0;
}
.form__label {
display: inline-block;
position: absolute;
top: 50%;
left: calc(0.75em + 1px);
transform: translateY(-50%);
z-index: 1;
color: black;
background: white;
transition: all 0.25s ease-in-out;
pointer-events: none;
}
.entered .form__label {
top: 0;
left: 0.5rem;
font-size: 0.6875rem;
line-height: 0.6875rem;
padding: 0.2em;
}
.invalid .form__label {
color: red;
}
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.umd.min.js"></script>
<div id='app'>
<v-date-picker v-model="range" :popover="{ visibility: 'focus' }" is-range #input="moveLabels">
<template #default="{ inputValue, inputEvents }">
<div class="filter__date-range-holder">
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-from">From</label>
<input id="filter-date-from" ref="filterDateForm" type="text" name="from" class="form__control form__control--textbox" :value="inputValue.start" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-to">To</label>
<input id="filter-date-to" ref="filterDateTo" type="text" name="to" class="form__control form__control--textbox" :value="inputValue.end" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
</div>
</template>
</v-date-picker>
</div>
As you can see, the label starts off inside the textbox and animates to the top on focus or if there is a value in the input. However, with the date range picker, as soon as you select the first date, it updates both inputs with the selected date and so my label is over the value.
I have tried setting the #input event of the date picker and putting a watch on the range variable, but both only fire once both dates have been selected so I can only move my labels after the second date is selected.
I have also tried adding an #change event to the inputs, but as the value is only change via js, the change event is not picked up
In the end I solved this by doing the following:
Add an #input event to handle when date range is selected properly
Add a #dayclick event to add the entered class when a day is selected
Add a timeout to the handleBlur method (the #dayClick event seemed to take a bit of time to fire so the blur animation started before it kicked in)
Add a mutation observer to see if the calendar closes - as the calendar doesn't have a close event, I needed to see if the calendar was closed without valid date range selected - if this happened and the inputs were emptied, this observer removed the entered class
new Vue({
el: "#app",
data() {
return {
range: {
start: null,
end: null
}
};
},
mounted() {
const overlay = document.querySelector('.filter__overlay');
const config = {
attributes: false,
childList: true,
subtree: true
};
// watch to see if calendar is closed
const observer = new MutationObserver(mutationsList => {
mutationsList.forEach(mutation => {
if (mutation.type === 'childList' &&
mutation.removedNodes.length > 0 &&
mutation.removedNodes[0].classList &&
mutation.removedNodes[0].classList.contains('vc-popover-content')) {
removeClass(this.$refs.filterDateForm);
removeClass(this.$refs.filterDateTo);
}
});
});
observer.observe(overlay, config);
},
methods: {
handleBlur(event) {
const input = event.currentTarget;
setTimeout(() => {
removeClass(input);
}, 150);
},
handleFocus(event) {
event.currentTarget.parentNode.classList.add("entered");
},
handleCalendarBlur() {
changeClass(this.$refs.filterDateForm);
changeClass(this.$refs.filterDateTo);
},
handleCalendarClick() {
this.$refs.filterDateForm.parentNode.classList.add("entered");
this.$refs.filterDateTo.parentNode.classList.add("entered");
},
}
});
function removeClass(input) {
if (input.value === '') {
input.parentNode.classList.remove("entered");
}
}
function changeClass(input) {
if (input.value === '') {
input.parentNode.classList.remove("entered");
} else {
input.parentNode.classList.add("entered");
}
}
#import url 'https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.min.css';
.filter__date-range-holder {
display: flex;
justify-content: space-between;
width: 95%;
}
.filter__date-range-column {
width: calc(50% - 15px);
}
.form__row {
position: relative;
margin: 1.5em 0;
background: white;
}
.form__control {
width: 100%;
border: 1px solid grey;
font-size: 1rem;
line-height: 1.5rem;
color: black;
padding: 0.75em;
background: transparent;
}
.invalid .form__control {
border-color: red;
outline-color: red;
}
.form__control:focus {
border-radius: 0;
}
.form__label {
display: inline-block;
position: absolute;
top: 50%;
left: calc(0.75em + 1px);
transform: translateY(-50%);
z-index: 1;
color: black;
background: white;
transition: all 0.25s ease-in-out;
pointer-events: none;
}
.entered .form__label {
top: 0;
left: 0.5rem;
font-size: 0.6875rem;
line-height: 0.6875rem;
padding: 0.2em;
}
.invalid .form__label {
color: red;
}
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.umd.min.js"></script>
<div id='app'>
<div class="filter__overlay">
<v-date-picker v-model="range" :popover="{ visibility: 'focus' }" is-range #input="handleCalendarBlur" #dayclick="handleCalendarClick">
<template #default="{ inputValue, inputEvents }">
<div class="filter__date-range-holder">
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-from">From</label>
<input id="filter-date-from" ref="filterDateForm" type="text" name="from" class="form__control form__control--textbox" :value="inputValue.start" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-to">To</label>
<input id="filter-date-to" ref="filterDateTo" type="text" name="to" class="form__control form__control--textbox" :value="inputValue.end" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
</div>
</template>
</v-date-picker>
</div>
</div>
Please note this example works differently to the one in my site - for some reason the one in my site will remove the dates from the inputs if only one date is selected - this one seems to keep it

Loop through elements by class name, click any element within the array, but only affect the element clicked?

I am working on a WordPress site and I have a snippet of html that iterates with repeating classes.
I am attempting to create a click function but only affect the element that is clicked. All in JavaScript.
As of right now my function is affecting all elements with the class name. Test code can be found at my CodePen or below.
I can accomplish this without nested loops as seen here. So my assumption is the problem lies within the second forEach loop. I would appreciate any light on the matter.
Thank you in advance.
/**
*Constructors
**/
const carousel = document.getElementsByClassName("carousel");
const btns = document.getElementsByClassName("btns");
/**
*Execute
**/
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
Array.from(carousel).forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
/*
**Utilities
*/
/*containers*/
.feed-container {
position: absolute;
height: 200px;
width: 100%;
display: grid;
grid-template-columns: 1;
grid-template-rows: 1;
}
.carousel {
grid-row: 1;
grid-column: 1/5;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1;
grid-gap: 15px;
align-self: center;
border: 1px solid #ccc;
overflow-x: scroll;
overflow-y: hidden;
}
/*div-buttons*/
div[class*="slide-"] {
/*opacity: 0;*/
position: sticky;
grid-row: 1;
z-index: 5;
place-self: center;
transition: 0.5s;
padding: 15px;
}
.slide-left {
grid-column: 1;
}
.slide-right {
grid-column: 4;
}
/*items*/
div[class*="item-"] {
grid-row: 1;
width: 400px;
height: 200px;
}
.item-1 {
background: blue;
}
.item-2 {
background: red;
}
.item-3 {
background: grey;
}
.item-4 {
background: yellow;
}
/*scrollbar*/
::-webkit-scrollbar {
display: none;
}
/*chevrons*/
[class*="chevron-"] {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs, 1));
width: 22px;
height: 22px;
border: 2px solid transparent;
border-radius: 25px;
}
[class*="chevron-"]::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 40px;
height: 40px;
border-bottom: 8px solid;
border-left: 8px solid;
bottom: 0;
}
.chevron-left::after {
transform: rotate(45deg);
left: 15px;
}
.chevron-right::after {
transform: rotate(-135deg);
right: 15px;
}
/*
**Exceptions
*/
.btns:hover {
cursor: pointer;
}
.opaque {
opacity: 1 !important;
}
.show {
display: block;
}
<div id="wrapper" style="display:grid; grid-template-rows:repeat(2, auto); grid-gap: 100px;">
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
<br>
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
</div>
It's because you're getting all the elements with class name carousel and then looping through them with each click.
const carousel = document.getElementsByClassName("carousel");
Instead what you need to do is get the carousels only under the button's parent when you trigger the click event
eg something like this:
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
const targetElement = e?.target || e?.srcElement;
const parent = targetElement.parentElement();
const carousel = Array.from(parent.getElementsByClassName("carousel"));
carousel.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
I took a look at the following as recommend and it seems to do the trick.
I created a variable that calls the parentNode "forEach()" button clicked. Oppose to looping through each element.
Working example, codePen
const carousel = document.querySelectorAll(".carousel");
const btns = document.querySelectorAll(".btns");
btns.forEach((i) => {
i.addEventListener("click", () => {
var x = i.parentNode;
var y = Array.from(x.querySelectorAll(".carousel"));
y.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else {
n.scrollLeft += 20;
}
});
});
});

How to reset the progress bar after it ends

I've found this script for a progress bar. It runs smoothly from 100% to 0% after clicking the button.
But how can I reset the progress bar after it hits the 0? I'm planning to use this script in a slideshow and it should be 100% again after it reaches 0.
Hope you can help me in the right direction.
Thnx,
Leon
document.querySelector("button").addEventListener("click", () => {
document.querySelector(".progress .bar").style.transitionDuration = "10s";
document.querySelector(".progress").className += " complete";
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button>
Start
</button>
You can reset the progress bar by removing the complete class.
document.querySelector("button.start").addEventListener("click", () => {
document.querySelector(".progress .bar").style.transitionDuration = "10s";
document.querySelector(".progress").className += " complete";
});
document.querySelector("button.reset").addEventListener("click", () => {
let className = document.querySelector(".progress").className;
if (className.indexOf(' complete') > -1) {
className = className.substr(0, className.indexOf(' complete'));
document.querySelector(".progress .bar").style.transitionDuration = "0s";
document.querySelector(".progress").className = className;
}
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button class="start">
Start
</button>
<button class="reset">
Reset
</button>
If you want this to be dynamic without any user interaction, you can use setTimeout, set to the same duration used in your animationDuration, within your click event Handler to reset the transition and remove the complete class.
const start = document.querySelector("#start")
const progressBar = document.querySelector(".progress .bar")
const progress = document.querySelector(".progress")
function resetProgressBar(){
progressBar.style.transitionDuration = "10s"
progress.classList.add("complete")
setTimeout(() => {
progress.classList.remove("complete")// <-- remove the class with width:0
progressBar.style.transitionDuration = "0.1s" //<-- Add a very small transitionDuration or none if you prefer
}, 10000)// <-- Set this timeout duration to the same as your transitionDuration
}
start.addEventListener("click", resetProgressBar);
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button id="start">
Start
</button>
For selector .progress .bar, use a listener for event transitionend, because you are using transition rules in css:
The transitionend event is fired when a CSS transition has completed.
Inside this event listener, set transitionDuration to the default value. And in the next step, remove class complete from .progress, which will return the previous width of the progress bar.
document.querySelector("button").addEventListener("click", () => {
document.querySelector(".progress .bar").style.transitionDuration = "10s";
document.querySelector(".progress").className += " complete";
});
document.querySelector(".progress .bar").addEventListener("transitionend", () => {
document.querySelector(".progress.complete .bar").style.transitionDuration = "";
document.querySelector(".progress").classList.remove("complete");
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button>
Start
</button>
Thnx for all your suggestions. I've implemented the last suggestion into my slideshow script.
It is running and it is refreshing after slide change. But somewhere on the ride it stops, I think it's confused when to start the progress bar.
Anyone an idea to make this more solid?
jQuery('.owl-carousel').owlCarousel({
items: 1,
margin: 0,
nav: false,
dots: false,
slideBy: 1,
rewind: false,
autoplay: true,
autoplayTimeout: 5000,
autoplaySpeed: 10000,
loop: true,
animateOut: 'fadeOut',
responsive: false,
mouseDrag: false,
touchDrag: false,
lazyLoadEager: 2
});
jQuery('.owl-carousel').on('changed.owl.carousel', function(event) {
document.querySelector(".progress .bar").style.transitionDuration = "5s";
document.querySelector(".progress").className += " complete";
})
document.querySelector(".progress .bar").addEventListener("transitionend", () => {
document.querySelector(".progress.complete .bar").style.transitionDuration = "5";
document.querySelector(".progress").classList.remove("complete");
});
.progress {
width: 50%;
height: 2em;
border: 1px solid black;
}
.bar {
width: 100%;
background-color: deepskyblue;
color: white;
text-align: center;
line-height: 2em;
transition-property: width;
transition-timing-function: linear;
}
.progress.complete .bar {
width: 0%;
}
button {
margin-top: 1em;
}
<div class="progress">
<div class="bar">Loading...</div>
</div>
<button>
Start
</button>

Custom elements in iteration require 'v-bind:key' directives in Vue.js

I have question about Vue.js. How can i fix this? I didn't find anything in documentation. I've got this error: "[vue/require-v-for-key]
Elements in iteration expect to have 'v-bind:key' directives."
And this: "Elements in iteration expect to have 'v-bind:key' directives."
I have this i my Roulette.vue
<template>
<div class="roulette">
<h1>Roulette</h1>
<div class="radio" v-for="genre in genres"> **here**
<input
#change="onGenrePick"
type="radio"
name="genre"
v-bind:id="genre.id"
v-bind:value="genre.id">
<label v-bind:for="genre.id">{{genre.name}}</label>
</div>
<Button class="btn" :onClick="roll">Roll</Button>
<MovieCard
v-if="!!movie"
v-bind:image="serviceGetImagePath(movie.poster_path)"
v-bind:title="movie.title"
v-bind:releaseDate="serviceFormatYear(movie.release_date)"
v-bind:id="movie.id"
v-bind:voteAverage="movie.vote_average"/>
</div>
</template>
<script>
import MovieCard from '../components/MovieCard'
import Button from '../components/Button'
import {movieService} from '../mixins/movieService'
export default {
name: 'Roulette',
components: {Button, MovieCard},
mixins: [movieService],
mounted: async function () {
this.genres = await this.serviceGetGenres()
},
data: () => ({
genres: [],
pickedGenres: new Set(),
movie: null
}),
methods: {
async roll() {
const genreIds = Array.from(this.pickedGenres)
const movies = await this.serviceGetMoviesByGenre(genreIds)
this.movie = movies[this.getRandom(movies.length)]
},
onGenrePick(e) {
this.pickedGenres.add(e.target.value)
},
getRandom(max) {
return Math.floor(Math.random() * max - 1)
}
}
}
</script>
<style scoped lang="scss">
.roulette {
margin: 40px;
}
.btn {
display: block;
min-width: 220px;
}
.radio {
display: inline-block;
margin: 20px 10px;
> label {
margin-left: 5px;
}
}
</style>
And in my UpcomingMovies.vue also
<template>
<div class="wrapper" v-if="movies.length">
<MovieCard
v-for="movie in movies" **here**
v-bind:image="serviceGetImagePath(movie.poster_path)"**here**
v-bind:title="movie.title"**here**
v-bind:releaseDate="serviceFormatYear(movie.release_date)"**here**
v-bind:id="movie.id"**here**
v-bind:voteAverage="movie.vote_average"/>**here**
<div class="loader">
<Button class="loader__btn" :onClick="loadNextPage">Load</Button>
</div>
<router-link :to="routes.roulette.path">
<div class="roulette">
<img src="../assets/roulette.png" alt="Roulette">
</div>
</router-link>
</div>
<Loader v-else/>
</template>
<script>
import Button from '../components/Button'
import MovieCard from '../components/MovieCard'
import Loader from '../components/Loader'
import { movieService } from '../mixins/movieService'
import routes from '../router/routes'
export default {
name: 'UpcomingMovies',
mixins: [movieService],
components: { Button, MovieCard, Loader },
data: () => ({
movies: [],
page: 1,
routes
}),
mounted() {
this.getMovies(this.page)
},
methods: {
getMovies: async function (page) {
const movies = await this.serviceGetMovies(page)
this.movies.push(...movies)
},
loadNextPage() {
this.getMovies(++this.page)
}
}
}
</script>
<style scoped lang="scss">
.wrapper {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
margin-top: 40px;
margin-bottom: 40px;
}
.loader {
width: 100%;
text-align: center;
margin-top: 40px;
margin-bottom: 40px;
&__btn {
border: 5px dashed white;
background-color: transparent;
border-radius: 50%;
width: 80px;
height: 80px;
font-weight: bold;
text-transform: uppercase;
transition: border-radius 100ms ease-in-out, width 120ms ease-in-out 120ms;
&:hover {
border-radius: 0;
background-color: rgba(white, 0.1);
width: 200px;
}
}
}
.roulette {
cursor: pointer;
position: fixed;
right: 25px;
bottom: 25px;
> img {
opacity: .8;
animation: rotate 5s infinite;
width: 70px;
height: auto;
transition: opacity 220ms linear;
&:hover {
opacity: 1;
}
}
}
#keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>
Vue internally uses unique keys for each loop to determine which element to update at which point during an update process. Therefore every v-for needs a v-bind:key attribute to work properly. In your special case this would be as the following:
<div class="radio" v-for="genre in genres" v-bind:key="someUniqueId"> **here**
You can use the current loop index as ID or anything else.

Converting to the ajax async false alternative because of depreciation

I've done a research on this question and found some solutions. However, not every one of them worked. As I understand, async false makes a UI block, which shouldn't be. I could use an overlay until ajax request is completed and on the request success, hide the overlay.
That was my try using a callback argument in the getNewQuote() function (only a small snippet of it):
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
callback();
return quote;
}, 4000);
};
getNewQuote(function() {
console.log("DONE");
var getRandomColor = function() {
var colors = [
"#ff9966",
"#7f00ff",
"#396afc",
"#0cebeb",
"#06beb6",
"#642b73",
"#36d1dc",
"#cb356b",
"#3a1c71",
"#ef3b36",
"#159957",
"#000046",
"#007991",
"#56ccf2",
"#f2994a",
"#e44d26",
"#4ac29a",
"#f7971e",
"#34e89e",
"#6190e8",
"#3494e6",
"#ee0979"
],
randomNumber = Math.floor(Math.random() * colors.length);
return colors[randomNumber];
};
var updateText = function($t, qt) {
var twitter = "https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=";
twitter += '"' + qt.text + '" ';
twitter += qt.author;
var tumblr = "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes,freecodecamp&caption=";
tumblr += qt.author;
tumblr += "&content=";
tumblr += qt.text;
tumblr += "&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fbuttons&shareSource=tumblr_share_button";
var $icon = $("<i class='fa fa-quote-left'>")
.prop("aria-hidden", true);
$t.find(".quote-text").html("").append($icon, qt.text);
$t.find(".quote-author").html("- " + qt.author);
$("#tweet-quote").attr("href", twitter);
$("#tumblr-quote").attr("href", tumblr);
};
var calcNewHeight = function(q) {
var $temp = $("<div>", {
class: "quote-container temp",
}).appendTo($("body"));
$temp.append($("<div>", {
class: "quote-text"
}), $("<div>", {
class: "quote-author"
}));
updateText($temp, q);
var h = $temp.height() + 40;
$temp.remove();
return h;
};
var changeColor = function(newColor) {
$("body, .button:not(#new-quote)").animate({
backgroundColor: newColor
});
$("#new-quote").animate({
color: newColor
});
$(".quote-text, .quote-author").css("color", newColor);
if ($("#modStyle").length === 0) {
$("head").append(
"<style id='modStyle'>#new-quote:before {background:" + newColor + ";}</style>"
);
} else {
$("head style#modStyle").html("#new-quote:before {background:" + newColor + ";}");
}
};
var getQuote = function() {
var nq, nc, nh = 0;
nq = getNewQuote();
nc = getRandomColor();
nh = calcNewHeight(nq);
$(".quote-container").children().css("opacity", 0);
changeColor(nc);
$(".quote-container, #new-quote").animate({
height: nh,
}, {
duration: 1000,
queue: false
});
$(".quote-container").animate({
padding: "2.5em"
}, {
duration: 1000,
queue: false
});
$("#new-quote").animate({
padding: "2.5em .75em"
}, {
duration: 1000,
queue: false
});
updateText($(".quote-container"), nq);
$(".quote-container").children().fadeTo(750, 1);
};
$("#new-quote").on("click", getQuote);
$(".quote-container, #new-quote").css({
visibility: "visible",
height: 0
});
$("#new-quote").css("padding", "0 .75em");
getQuote();
});
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
background: #333;
color: #333;
font-family: sans-serif;
}
.v-wrap {
height: 100%;
text-align: center;
}
.v-wrap:before {
content: "";
display: inline-block;
vertical-align: middle;
width: 0;
height: 100%;
}
.quote-container {
width: 31.25rem;
background: #fff;
margin: 0;
display: inline-block;
vertical-align: middle;
border-radius: 0.1875rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
visibility: hidden;
padding: 0 2.5rem;
}
.quote-text {
font-size: 1.625rem;
}
.quote-text i {
margin-right: 0.6rem;
}
.quote-text p {
display: inline;
}
.quote-author {
font-size: 1rem;
margin: 0 0.4rem 2rem 0;
text-align: right;
}
.button {
padding: 0.75rem;
text-align: center;
font-size: 1rem;
color: #fff;
border-radius: .1875rem;
display: inline-block;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.button:not(#new-quote):hover {
opacity: .8 !important;
}
.button:not(#new-quote) {
min-width: 1rem;
min-height: 1rem;
}
.button i {
vertical-align: middle;
}
#new-quote {
white-space: nowrap;
writing-mode: vertical-lr;
height: 50%;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
vertical-align: middle;
background: #fff !important;
margin: 0;
position: relative;
right: 0.25625rem;
color: #333;
visibility: hidden;
}
#new-quote:before {
content: "";
position: absolute;
height: 100%;
width: 0.0625rem;
bottom: 0;
left: 0;
visibility: hidden;
-webkit-transform: scaleY(0);
transform: scaleY(0);
-webkit-transition: all .3s ease-in-out;
transition: all .3s ease-in-out;
}
#new-quote:hover:before {
visibility: visible;
-webkit-transform: scaleY(1);
transform: scaleY(1);
}
footer {
font-size: 0.85rem;
margin-bottom: 1rem;
}
footer a {
text-decoration: none;
color: #fff;
position: relative;
}
footer a:before {
content: "";
position: absolute;
width: 100%;
height: .0625rem;
bottom: 0;
left: 0;
background: #fff;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all .3s ease-in-out 0s;
transition: all .3s ease-in-out 0s;
}
footer a:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="v-wrap">
<div class="quote-container" style="">
<div class="quote-text">
</div>
<div class="quote-author"></div>
<a id="tweet-quote" class="button"><i class="fa fa-twitter"></i></a>
<a id="tumblr-quote" class="button"><i class="fa fa-tumblr"></i></a>
</div>
<div id="new-quote" class="button">New quote</div>
<footer>
Created by LukasLSC
</footer>
</div>
Code output:
As you can see, ajax wasn't success
Uncaught TypeError: callback is not a function and Uncaught TypeError: Cannot read property 'text' of undefined (only in the stack snippet).
I found out this error disappears if I remove the getQuote(); function call. However, I need to call it, overwise, my project won't work. I also tried to use return $.ajax but there was a return quote line so I couldn't use it. The full code can be found here on codepen: https://codepen.io/Kestis500/pen/ZvyxKB?editors=0110.
Then I switched to another method using jQuery promises and used information in this thread: https://stackoverflow.com/a/40658281/8889739. Full code: https://codepen.io/Kestis500/pen/qpjxoq?editors=0110.
var MyFirstFunction = function() {
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
return quote;
}, 4000);
};
}
var MySecondFunction = function() {
console.log("DONE");
var getRandomColor = function() {
var colors = [
"#ff9966",
"#7f00ff",
"#396afc",
"#0cebeb",
"#06beb6",
"#642b73",
"#36d1dc",
"#cb356b",
"#3a1c71",
"#ef3b36",
"#159957",
"#000046",
"#007991",
"#56ccf2",
"#f2994a",
"#e44d26",
"#4ac29a",
"#f7971e",
"#34e89e",
"#6190e8",
"#3494e6",
"#ee0979"
],
randomNumber = Math.floor(Math.random() * colors.length);
return colors[randomNumber];
};
var updateText = function($t, qt) {
var twitter = "https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=";
twitter += '"' + qt.text + '" ';
twitter += qt.author;
var tumblr = "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes,freecodecamp&caption=";
tumblr += qt.author;
tumblr += "&content=";
tumblr += qt.text;
tumblr += "&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fbuttons&shareSource=tumblr_share_button";
var $icon = $("<i class='fa fa-quote-left'>")
.prop("aria-hidden", true);
$t.find(".quote-text").html("").append($icon, qt.text);
$t.find(".quote-author").html("- " + qt.author);
$("#tweet-quote").attr("href", twitter);
$("#tumblr-quote").attr("href", tumblr);
};
var calcNewHeight = function(q) {
var $temp = $("<div>", {
class: "quote-container temp",
}).appendTo($("body"));
$temp.append($("<div>", {
class: "quote-text"
}), $("<div>", {
class: "quote-author"
}));
updateText($temp, q);
var h = $temp.height() + 40;
$temp.remove();
return h;
};
var changeColor = function(newColor) {
$("body, .button:not(#new-quote)").animate({
backgroundColor: newColor
});
$("#new-quote").animate({
color: newColor
});
$(".quote-text, .quote-author").css("color", newColor);
if ($("#modStyle").length === 0) {
$("head").append(
"<style id='modStyle'>#new-quote:before {background:" + newColor + ";}</style>"
);
} else {
$("head style#modStyle").html("#new-quote:before {background:" + newColor + ";}");
}
};
var getQuote = function() {
var nq, nc, nh = 0;
nq = getNewQuote();
nc = getRandomColor();
nh = calcNewHeight(nq);
$(".quote-container").children().css("opacity", 0);
changeColor(nc);
$(".quote-container, #new-quote").animate({
height: nh,
}, {
duration: 1000,
queue: false
});
$(".quote-container").animate({
padding: "2.5em"
}, {
duration: 1000,
queue: false
});
$("#new-quote").animate({
padding: "2.5em .75em"
}, {
duration: 1000,
queue: false
});
updateText($(".quote-container"), nq);
$(".quote-container").children().fadeTo(750, 1);
};
$("#new-quote").on("click", getQuote);
$(".quote-container, #new-quote").css({
visibility: "visible",
height: 0
});
$("#new-quote").css("padding", "0 .75em");
getQuote();
}
MyFirstFunction().done(MySecondFunction);
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
background: #333;
color: #333;
font-family: sans-serif;
}
.v-wrap {
height: 100%;
text-align: center;
}
.v-wrap:before {
content: "";
display: inline-block;
vertical-align: middle;
width: 0;
height: 100%;
}
.quote-container {
width: 31.25rem;
background: #fff;
margin: 0;
display: inline-block;
vertical-align: middle;
border-radius: 0.1875rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
visibility: hidden;
padding: 0 2.5rem;
}
.quote-text {
font-size: 1.625rem;
}
.quote-text i {
margin-right: 0.6rem;
}
.quote-text p {
display: inline;
}
.quote-author {
font-size: 1rem;
margin: 0 0.4rem 2rem 0;
text-align: right;
}
.button {
padding: 0.75rem;
text-align: center;
font-size: 1rem;
color: #fff;
border-radius: .1875rem;
display: inline-block;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.button:not(#new-quote):hover {
opacity: .8 !important;
}
.button:not(#new-quote) {
min-width: 1rem;
min-height: 1rem;
}
.button i {
vertical-align: middle;
}
#new-quote {
white-space: nowrap;
writing-mode: vertical-lr;
height: 50%;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
vertical-align: middle;
background: #fff !important;
margin: 0;
position: relative;
right: 0.25625rem;
color: #333;
visibility: hidden;
}
#new-quote:before {
content: "";
position: absolute;
height: 100%;
width: 0.0625rem;
bottom: 0;
left: 0;
visibility: hidden;
-webkit-transform: scaleY(0);
transform: scaleY(0);
-webkit-transition: all .3s ease-in-out;
transition: all .3s ease-in-out;
}
#new-quote:hover:before {
visibility: visible;
-webkit-transform: scaleY(1);
transform: scaleY(1);
}
footer {
font-size: 0.85rem;
margin-bottom: 1rem;
}
footer a {
text-decoration: none;
color: #fff;
position: relative;
}
footer a:before {
content: "";
position: absolute;
width: 100%;
height: .0625rem;
bottom: 0;
left: 0;
background: #fff;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all .3s ease-in-out 0s;
transition: all .3s ease-in-out 0s;
}
footer a:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="v-wrap">
<div class="quote-container" style="">
<div class="quote-text">
</div>
<div class="quote-author"></div>
<a id="tweet-quote" class="button"><i class="fa fa-twitter"></i></a>
<a id="tumblr-quote" class="button"><i class="fa fa-tumblr"></i></a>
</div>
<div id="new-quote" class="button">New quote</div>
<footer>
Created by LukasLSC
</footer>
</div>
Code output:
It broke everything, the gray screen is because of the default codepen background
Uncaught TypeError: Cannot read property 'done' of undefined
You're using return from the asynchronous operation's callback. That just sets the return value of that callback (which is ignored in the case of setTimeout's or XHR's callback), it doesn't set the return value of your function.
You can't return the value from your function, which is why you're adding a callback. Instead:
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
callback(quote); // <====
}, 4000);
};
...and use the parameter of the callback, e.g.:
getNewQuote(function(quote) {
// Use quote here...
});
Live Example:
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
callback(quote); // <====
}, 1000);
};
getNewQuote(function(quote) {
console.log("quote:", quote);
});

Categories