Vue 3 Composition API component doesn't work reactivate class - vue.js

I have a button and I just want it animate on a click - make as it been pressed. It works if I make optional component as below:
<template>
<button class="button" :class="{'shadow__click': classButton}" #click="buttonClass">
Tell me already!
</button>
</template>
<script>
export default {
data(){
return {
classButton : false
}
},
methods: {
buttonClass(){
this.classButton = true;
setTimeout(() => {
this.classButton = false
}, 1000)
}
}
}
</script>
<style lang="less">
.button{
cursor: pointer;
padding: 12px 24px;
position: relative;
border-radius: 5px;
background-color: #38b2ac;
border: none;
color: #fff8e1;
}
.shadow__click{
animation: click 1s
}
#keyframes click {
0% {
top: 0;
left: 0;
}
25% {
top: 5px;
left: 1px;
}
50% {
top: 10px;
left: 3px;
}
75% {
top: 5px;
left: 1px;
}
100% {
top: 0;
left: 0;
}
}
</style>
but it doesn't want to work when I do Composition way and I don't see a problem but it simply doesn't work (( I console.loged function and it goes to function changes the value of a variable, but class is not applying. Is it a Vue 3 bug?
<script>
import { ref } from "vue"
setup(){
let classButton = ref(false);
function buttonClass(){
classButton = true;
setTimeout(() => {
classButton = false
}, 1000)
}
return { classButton, buttonClass}
}
</script>

You should mutate the ref using the value field :
let classButton = ref(false);
function buttonClass(){
classButton.value = true;
setTimeout(() => {
classButton.value = false
}, 1000)
}

Related

Using wheelnav.js on Vue 3 project

So I have seen this pie menu generator which gives you an HTML, CSS and JS code and I wanted to use it in my Vue 3 project. I am new to vue and this was how I imported it.
here is the link to the pie menu generator: http://pmg.softwaretailoring.net/
I did this in my wrapper component. Somehow this bought many errors in which I think is because of how I imported the JS library.
<template>
<div class="context-menu" v-show="show" :style="style" ref="context" tabindex="0" #blur="close">
<div id='piemenu' data-wheelnav data-wheelnav-slicepath='DonutSlice' data-wheelnav-marker
data-wheelnav-markerpath='PieLineMarker' data-wheelnav-rotateoff data-wheelnav-navangle='270'
data-wheelnav-cssmode data-wheelnav-init>
<div data-wheelnav-navitemtext='0' onmouseup='alert("Place your logic here.");'></div>
<div data-wheelnav-navitemtext='1' onmouseup='alert("Place your logic here.");'></div>
</div>
</div>
</template>
<script>
import '../assets/raphael.min.js'
import '../assets/raphael.icons.min.js'
import '../assets/wheelnav.min.js'
var piemenu = new wheelnav('piemenu');
piemenu.wheelRadius = piemenu.wheelRadius * 0.83;
piemenu.createWheel();
export default {
name: 'CmpContextMenu',
props: {
display: Boolean, // prop detect if we should show context menu
},
data() {
return {
left: 0, // left position
top: 0, // top position
show: false, // affect display of context menu
};
},
computed: {
// get position of context menu
style() {
return {
top: this.top + 'px',
left: this.left + 'px',
};
},
},
methods: {
// closes context menu
close() {
this.show = false;
this.left = 0;
this.top = 0;
this.myTrigger = false;
console.log('trigger false');
},
open(evt) {
this.show = true;
// updates position of context menu
this.left = evt.pageX || evt.clientX;
this.top = evt.pageY || evt.clientY;
},
},
};
</script>
<style>
.context-menu {
position: fixed;
z-index: 999;
cursor: pointer;
}
#piemenu>svg {
width: 100%;
height: 100%;
}
#piemenu {
height: 400px;
width: 400px;
margin: auto;
}
#media (max-width: 400px) {
#piemenu {
height: 300px;
width: 300px;
}
}
[class|=wheelnav-piemenu-slice-basic] {
fill: #497F4C;
stroke: none;
}
[class|=wheelnav-piemenu-slice-selected] {
fill: #497F4C;
stroke: none;
}
[class|=wheelnav-piemenu-slice-hover] {
fill: #497F4C;
stroke: none;
fill-opacity: 0.77;
cursor: pointer;
}
[class|=wheelnav-piemenu-title-basic] {
fill: #333;
stroke: none;
}
[class|=wheelnav-piemenu-title-selected] {
fill: #fff;
stroke: none;
}
[class|=wheelnav-piemenu-title-hover] {
fill: #222;
stroke: none;
cursor: pointer;
}
[class|=wheelnav-piemenu-title]>tspan {
font-family: Impact, Charcoal, sans-serif;
font-size: 24px;
}
.wheelnav-piemenu-marker {
stroke: #444;
stroke-width: 2;
}
</style>

Reinitialize matterjs after page changes in nuxtjs

I have a matterjs instance in my nuxt app that drops items on the floor. Everything works when I visit the page for the first time or do a page refresh. But when I change the pages (routes) inside my app, so I come back to the page with the matterjs instance, the instance is gone. I always have to do a page refresh...
How can I reinitialize matterjs?
Fallbox
<section class="fallbox">
<div class="fallbox-content">
<nuxt-link to="/"><h1>Index</h1></nuxt-link>
</div>
<div class="fallbox-scene">
<div v-for="item in items" :key="item.className">
<span :class="item.className" class="item"></span>
</div>
</div>
</section>
export default {
data() {
return {
items: [
{
className: "-i1",
},
{
className: "-i2",
},
{
className: "-i3",
},
{
className: "-i4",
},
{
className: "-i5",
},
],
};
},
mounted() {
window.addEventListener("DOMContentLoaded", () => {
this.startFallbox();
});
},
methods: {
startFallbox() {
const Engine = Matter.Engine;
const Render = Matter.Render;
const Runner = Matter.Runner;
const Bodies = Matter.Bodies;
const Body = Matter.Body;
const Composite = Matter.Composite;
const MouseConstraint = Matter.MouseConstraint;
const engine = Engine.create();
const world = engine.world;
engine.gravity.y = 1;
const fallbox = document.querySelector(".fallbox-scene");
const render = Render.create({
element: fallbox,
engine,
options: {
width: fallbox.offsetWidth,
height: fallbox.offsetHeight,
},
});
// Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);
const itemArray = this.items;
itemArray.forEach((i) => {
const get = document.getElementsByClassName(i.className)[0];
get.style.opacity = 1;
const item = {
w: get.clientWidth,
h: get.clientHeight,
body: Bodies.rectangle(
Math.random() * window.innerWidth,
Math.random() * -1000,
get.clientWidth,
get.clientHeight,
{
restitution: 0.5,
angle: Math.random() * 360,
}
),
elem: get,
render() {
const { x, y } = this.body.position;
this.elem.style.top = `${y - this.h / 2}px`;
this.elem.style.left = `${x - this.w / 2}px`;
this.elem.style.transform = `rotate(${this.body.angle}rad)`;
},
};
Body.rotate(item.body, Math.random() * 360);
Composite.add(world, [item.body]);
(function rerender() {
item.render();
requestAnimationFrame(rerender);
})();
});
const ground = Bodies.rectangle(
fallbox.offsetWidth / 2,
fallbox.offsetHeight,
2000,
1,
{
isStatic: true,
}
);
const left = Bodies.rectangle(
0,
fallbox.offsetHeight / 2,
1,
fallbox.offsetHeight,
{
isStatic: true,
}
);
const right = Bodies.rectangle(
fallbox.offsetWidth,
fallbox.offsetHeight / 2,
1,
fallbox.offsetHeight,
{
isStatic: true,
}
);
Composite.add(world, [ground, left, right]);
const mouseConstraint = MouseConstraint.create(engine, {
element: fallbox,
constraint: {
stiffness: 0.2,
},
});
mouseConstraint.mouse.element.removeEventListener(
"mousewheel",
mouseConstraint.mouse.mousewheel
);
mouseConstraint.mouse.element.removeEventListener(
"DOMMouseScroll",
mouseConstraint.mouse.mousewheel
);
Composite.add(world, mouseConstraint);
Render.lookAt(render, {
min: { x: 0, y: 0 },
max: { x: fallbox.offsetWidth, y: fallbox.offsetHeight },
});
},
},
};
.fallbox {
position: relative;
height: 100vh;
width: 100%;
margin: auto;
background: black;
overflow: hidden;
.fallbox-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 95%;
max-width: 1050px;
z-index: 2;
// -khtml-user-select: none;
// -moz-user-select: none;
// -ms-user-select: none;
// user-select: none;
// pointer-events: none;
h1 {
font-size: 160px;
font-weight: 500;
line-height: 140px;
margin-bottom: 150px;
text-align: center;
color: white;
}
}
.fallbox-scene {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
contain: strict;
.item {
height: 120px;
width: 120px;
background: red;
position: absolute;
opacity: 0;
user-select: none;
will-change: transform;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}
}
}
activated() {
this.startFallbox()
}
Use the activated hook to restart the animation. The code when mounted() is still needed.
I assume the problem is that when you navigate away and you some back the initial animation is finished, but as Nuxt caches some pages they are served from cache and therefore the mounted() hook is not called. This is where the activated() hooks comes in. It's called when a page/component was keept alive and is reactivated.

How to display a component when page is loading in nuxt

I am quite new to nuxt, and I need help here.
async asyncData({ params, route }) {
const { data } = await axios.get(
`${process.env.baseUrl}/homes/?search=${
params.search
}&home_status=${1}`
)
return {
homes: data.results,
}
}
I am trying to populate my component with data(using asyncData), but I want my skeleton loader to show if my page is loading. How do I do that in nuxt?
Here is the code for my skeleton loader;
<template>
<div class="placeholder-container">
<div class="placeholder wave">
<div class="square"></div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
</div>
</div>
</template>
<style scoped>
.placeholder-container {
width: 35rem;
margin: 15px auto 15px auto;
}
.placeholder {
padding: 10px;
width: 100%;
// border: 1px solid lightgrey;
display: flex;
flex-direction: column;
}
.placeholder div {
background: #e8e8e8;
}
.placeholder .square {
width: 100%;
height: 22rem;
border-radius: 1rem;
margin: 0 0 10px;
}
.placeholder .line {
height: 12px;
margin: 0 0 10px 0;
}
.placeholder .line:nth-child(2) {
width: 120px;
}
.placeholder .line:nth-child(3) {
width: 180px;
}
.placeholder .line:nth-child(4) {
width: 150px;
}
.placeholder.wave div {
animation: wave 1s infinite linear forwards;
-webkit-animation: wave 1s infinite linear forwards;
background: #f6f7f8;
background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);
background-size: 800px 104px;
}
#keyframes wave {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
#-webkit-keyframes wave {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
</style>
What I normally do without using nuxt, is to create a data variable(loading=true), and change it to false after I finish making the api call, but since asyncData runs in the server, how do I make that work? I will also appreciate it if there is a better way of doing something like this
Placeholder
To display a placeholder component on a particular page
during loading, switch from asyncData to the fetch hook, which exposes the $fetchState.pending flag that is set to true when complete:
<template>
<div>
<MyLoading v-if="$fetchState.pending" />
<MyContent v-else :posts="posts" />
</div>
</template>
<script>
export default {
data() {
return {
posts: []
}
},
async fetch() {
const { data } = await this.$axios.get(...)
this.posts = data
}
}
</script>
Customizing loading progress bar
Nuxt provides a default loading progress bar that appears at the top of the app while a page is loading. You could customize the progress bar's appearance:
// nuxt.config.js
export default {
loading: {
color: 'blue',
height: '5px'
}
}
Or you could specify your own custom loading component instead:
// nuxt.config.js
export default {
loading: '~/components/MyLoading.vue'
}
demo

Vue-multiselect - How to insert html code in placeholder?

Im trying insert span in placeholder, for color change. But placeholder returns only string, ow to fix that?
computed: {
customPlaceholder () {
let numLength = this.options.length;
return this.placeholder + "<span>"+numLength+"</span>"
}
}
I think you're trying to add a custom placeholder inside an input field.
to do this you need some mix of css and html.
new Vue({
el: '#editor',
data: {
input: '',
input2: 'some text'
},
computed: {
placeholderText: function () {
return `${this.input2} <span>*</span>`
}
},
methods: {
update: _.debounce(function (e) {
this.input = e.target.value
}, 300)
}
})
#editor div {
display: inline-block;
}
.input-placeholder {
position: relative;
}
.input-placeholder input {
padding: 10px;
font-size: 25px;
}
.input-placeholder input:valid + .placeholder {
display: none;
}
.placeholder {
position: absolute;
pointer-events: none;
top: 0;
bottom: 0;
height: 25px;
font-size: 25px;
left: 10px;
margin: auto;
color: #ccc;
}
.placeholder span {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/lodash#4.16.0"></script>
<div id="editor">
<div class="input-placeholder">
<input type="text" #input="update">
<div class="placeholder" v-if="!input" v-html="placeholderText">
Email <span>*</span>
</div>
</div>
</div>
I have created this jsfiddle for my solution.
you can use css placeholder selector
input::-webkit-input-placeholder { /* Edge */
color: green!important;
}
input:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: green!important;
}
input::placeholder {
color: green!important;
}
Try it, then try to remove the !important.
But information is missing. You want to change the color dynamically or not? or you want to have different colours into the same placeholder?

How to add preloader and success message on form submit using vue-resource

How to accomplish below task using vue-resource:
Include preloader text Loading... or gif image when fetching the data
from the server.
Show success message on form submit.
One way of doing this is :
<template>
<div>
<div class="loader" v-if="loader"></div>
<div>
//display fetchedData using logic you wish like v-for.....
</div>
<form>
//your form inputs
<button #click.prevent="submit">Submit</button>
</form>
</div>
</template>
<script>
export default{
data(){
return{
loader: false,
fetchedData: null
}
},
mounted(){
this.loader = true;
this.$httpget('your_url')
.then(response => {
this.fetchedData = response;
this.loader = false;
},err => {
});
},
methods:{
submit(){
this.loader = true;
this.$http.post('your_url', {your_body})
.then(response => {
this.loader = false;
},err => {
alert('form not submitted');
});
}
},
}
</script>
<style scoped>
loader {
position: absolute;
left:50%;
top:50%;
transform: translate(-50%, -50%);
border: 10px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 75px;
height: 75px;
animation: spin 2s linear infinite;
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
Here is the working fiddle
This was questioned by me, with the help that I got from #Vamsi, here is my solution:
Component
<loading-indicator v-if="loadingGroup" :bgAlpha="'.6'"></loading-indicator>
<script>
import LoadingIndicator from '../partials/LoadingIndicator'
export default {
data () {
return {
loadingGroup: true,
}
},
components: {LoadingIndicator},
methods: {
fetchGroup() {
let _this = this;
this.loadingGroup = true;
api._get({url: 'api/group/' + _this.$route.params.id})
.then(function (response) {
_this.groupData = response.data;
_this.loadingGroup = false;
});
}
},
mounted() {
this.fetchGroup();
}
}
</script>
My Template that's in: ../partials/LoadingIndicator.vue
<template>
<div class="pin pin-xy d-flex"
:style="{ backgroundColor: 'rgba(255, 255 ,255,' + bgAlpha + ')'}">
<div class="loading-indicator">
<div class="loading-indicator-circle"></div>
</div>
</div>
</template>
<script>
export default {
props: {
bgAlpha: String
}
}
</script>
<style lang="scss">
.pin {
position: absolute;
&-xy {
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
.d-flex {
display: flex;
}
.loading-indicator {
width: 32px;
height: 32px;
margin: auto;
overflow: hidden;
animation: animation-fadeIn 1s ease-in;
}
.loading-indicator-circle {
animation: loading-indicator-rotation 0.67s linear infinite;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PGxpbmVhckdyYWRpZW50IGlkPSdGYXN0TG9hZGluZ0luZGljYXRvci1saW5lYXJHcmFkaWVudCcgZ3JhZGllbnRVbml0cz0ndXNlclNwYWNlT25Vc2UnIHgxPScxLjc4MDQnIHkxPScxNi4wMzc5JyB4Mj0nMzAuMTQzOScgeTI9JzE2LjAzNzknPjxzdG9wIG9mZnNldD0nMC40MTY5JyBzdG9wLWNvbG9yPScjQ0RDRkQyJy8+PHN0b3Agb2Zmc2V0PScwLjkzNzYnIHN0b3AtY29sb3I9J3JnYmEoMjQ4LDI0OCwyNDksMCknLz48L2xpbmVhckdyYWRpZW50PjxjaXJjbGUgY3g9JzE2JyBjeT0nMTYnIHI9JzEyLjcnIHN0eWxlPSdmaWxsOiBub25lOyBzdHJva2U6IHVybCgjRmFzdExvYWRpbmdJbmRpY2F0b3ItbGluZWFyR3JhZGllbnQpOyBzdHJva2Utd2lkdGg6IDI7Jz48L2NpcmNsZT48L3N2Zz4=");
height: 100%;
width: 100%
}
#keyframes loading-indicator-rotation {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
</style>