cant change type(danger,success) and add animated on vue strap - vue.js

i Want to make a progress bar using vue strap . i install vue strap on this link
this link
now i add a progress bar, this progress bar is showing , this bar is only showing color primary and cant showing animated .
<template>
<div class="progress">
<progressbar now="99" type="danger" striped animated ></progressbar>
</div>
</template>
<script>
import { progressbar } from 'vue-strap'
export default {
components: {
progressbar
},
mounted() {
console.log('Component mounted.')
}
}
</script>
with this code , this type is primary and this animated didnt work .
i change browser from chrome to mozila , but its still didnt work . my browser is newest .
whats wrong about this ? i dont know why animated didnt work

There is a bug in VueStrap library when it comes to progress bar animations. The template for progress bar in VueStrap uses class active to animate, whereas, in Bootstrap 4 we have to use class progress-bar-animated. A work around of this problem is to created your own Progress Bar component which makes use of the Bootstrap 4.
Custom Progress Bar component could be written as:
Vue.component('c-progressbar', {
template: `
<div class="progress">
<div class="progress-bar" :class="progressClasses"
role="progressbar"
:style="progressStyle"></div>
</div>`,
props: {
striped: Boolean,
animated: Boolean,
now: {
type: Number,
required: true
},
contextType: {
type: String,
default: 'primary'
}
},
data: function() {
let context = 'bg-' + this.contextType
return {
progressClasses: {
'progress-bar-striped': this.striped,
'progress-bar-animated': this.animated,
[context]: true
},
progressStyle: {
width: this.now + '%'
}
}
}
})
new Vue({ el: '#app' })
You can use this pen for testing: https://codepen.io/abdullah-shabaz/pen/YzXdYgd

Related

How could I change an image in a child page when pressing a button in its parent page?

I have a DefaultLayout component with a dark mode toggle button which is its own component. One if its children (DefaultLayout's) is About.vue where I want a specific image to change its src depending on a localStorage value that can be set to either 'dark' or 'light'.
I've managed to read the localStorage value but the image does not change unless I refresh the page.
I'm new to Vue so I'm lost on how I can create a method to do this in DefaultLayout and change a variable in its child. I've tried to use an emit with no luck.
Could anyone point me in the right direction?
Yes, the local storage is for keeping data not propagate events.
The simplest way for you is to make a prop in child component and pass the value by this prop. But if you want to implement it as global variable the suggested way is by Pinia.
Below is a simple example
Vue.component('About', {
name: 'About',
template: `<div>
<div v-if="mode==='dark'">Dark</div>
<div v-else>Light</div>
</div>
`,
data() {
return {
mode: 'light',
};
},
mounted() {
this.setMode('white'); // In realtime use `this.getMode()` instead of 'white'
},
methods: {
setMode(val) {
this.mode = val;
},
getMode() {
return JSON.parse(localStorage.getItem('mode'));
}
}
});
var app = new Vue({
el: "#app",
template: `<div>
<input type="checkbox" v-model="toggler" #input="setVal" />
<About ref="about" />
</div>`,
data() {
return {
toggler: false,
};
},
methods: {
setVal() {
const mode = this.toggler === false ? 'dark' : 'light';
// localStorage.setItem('mode', mode); // In realtime uncomment this line
this.$refs.about.setMode(mode);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>

How can I can show a div if an object doesn't have a value outside of the object scope

I have a small Nuxt issue that I can't work out how to get around.
Essentially, I have an object (used for a carousel slider).
<template>
<div
:class="[$style.swiperSlide, 'swiper-slide']"
v-for="slide in slides"
:key="slide.id">
<nuxt-img
:class="[$style.img]"
:alt="slide.alt"
:src="imgSources(slide)"
sizes="sm:100vw"
/>
<div :class="[$style.info, 'info-b']" v-if="slide.info">
{{ slide.info }}
</div>
</div>
<button :class="[$style.infoOpen]"
#click="showTab"
v-if="slideInfoAvailable"
>
Close
</button>
</template>
<script>
export default {
props: {
slides: {
type: Array,
required: true,
default: () => []
}
},
computed: {
slideInfoAvailable() {
return this.slide?.info
}
},
mounted() {
const swiper = new Swiper(".swiper-container", {
. . .
});
},
methods: {
imgSources(slide) {
return `/img${slide.imgPath}.jpg`;
},
};
</script>
All works o.k, the problem is that I have a button outside of this v-for that I need to only be visible if there's slide.info but because this div is outside of the v-for it can't tell if it's available.
Cannot read property 'info' of undefined
The easiest way out of this is to add the button inside of the slider - but I can't for Z-index CSS issues. It has to be outside of the 'slider' div.
Any ideas how I can only show the button if there's slide.info? For some of my slides, there won't be.
<slider
:slides="[
{
imgPath: '/demo',
info: 'Demo info for this slide',
alt: 'Homepage'
},
{
imgPath: '/demo2',
alt: 'Homepage'
},
]"
/>
One way I could do it would be to see if .slide-active .style.info exists. If it doesn't exist then I can hide the button as slide-active is added to the active div by the slider API.
The issue is coming from the fact that you probably have some async fetching and that slides are not available upon initial render. To prevent this, you can use a computed with some optional chaining like this
export default {
computed: {
slideInfoAvailable() {
return this.slide?.info
}
}
}
Then, call it like this
<button :class="[$style.infoOpen]" #click="showTab" v-if="slideInfoAvailable">
You cannot use ?. directly in the template.
You could also do the classic way of
<button :class="[$style.infoOpen]" #click="showTab" v-if="slide && slide.info">
but it does not look as sexy IMO (but you do not need any computed).
And yeah, for this kind of thing, better to handle it with Vue than relying on some hacky dirty CSS tricks!

Delay the rendering of a component in vue

I've created a component in vue which wraps a vue-apexchart donut graph. As soon as the page loads and this component is loaded, the vue-apexchart animates and displays a small graph.
Now I would like to instantiate multiple of these components from a dataset side by side. Instead of the components to all load an animate at the same time, I would like a small rendering delay to give it an overall nice effect. Something like this would be nice:
<donut :items="series1"></donut>
<donut :items="series2" delay=1500></donut>
The vue-apexchart doesent support initialization delays, and as far as I can see there isn't any vue-specific official solution to delay the rendering of components.
I've tried to put a setTimeout in any of the component hooks to stall the initialization,
I´ve also tried to inject the all the graph DOM in the template element on a v-html tag in a setTimeout, but apexchart doesent notice this new dom content, and vue doesent notice the html bindings either.
I´ve created this fiddle which loads two instances of a graph:
https://jsfiddle.net/4f2zkq5c/7/
Any creative suggestions?
There are several ways you can do this, and it depends on whether you can actually modify the <animated-component> logic yourself:
1. Use VueJS's built-in <transition-group> to handle list rendering
VueJS comes with a very handy support for transitions that you can use to sequentially show your <animated-component>. You will need to use a custom animation library (like VelocityJS) and simply store the delay in the element's dataset, e.g. v-bind:data-delay="500". VueJS docs has a very good example on how to introduce staggered transitions for <transition-group>, and the example below is largely adapted from it.
You then use the beforeAppear and appear hooks to set the opacity of the individual children of the <transition-group>.
Vue.component('animated-component', {
template: '#animatedComponentTemplate',
props: {
data: {
required: true
}
}
});
new Vue({
el: '#app',
data: {
dataset: {
first: 'Hello world',
second: 'Foo bar',
third: 'Lorem ipsum'
}
},
methods: {
beforeAppear: function(el) {
el.style.opacity = 0;
},
appear: function(el, done) {
var delay = +el.dataset.delay;
setTimeout(function() {
Velocity(
el, {
opacity: 1
}, {
complete: done
}
)
}, delay)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<transition-group name="fade" v-on:before-appear="beforeAppear" v-on:appear="appear">
<animated-component v-bind:data="dataset.first" v-bind:key="0"> </animated-component>
<animated-component v-bind:data="dataset.second" v-bind:key="1" v-bind:data-delay="500"> </animated-component>
<animated-component v-bind:data="dataset.third" v-bind:key="2" v-bind:data-delay="1000"> </animated-component>
</transition-group>
</div>
<script type="text/x-template" id="animatedComponentTemplate">
<div>
<h1>Animated Component</h1>
{{ data }}
</div>
</script>
2. Let <animated-component> handle its own rendering
In this example, you simply pass the a number to the delay property (remember to use v-bind:delay="<number>" so that you pass a number and not a string). Then, in the <animated-component>'s mounted lifecycle hook, you use a timer to toggle the visibility of the component itself.
The technique on how you want to show the initially hidden component is up to you, but here I simply apply an initial opacity of 0 and then transition it after a setTimeout.
Vue.component('animated-component', {
template: '#animatedComponentTemplate',
props: {
data: {
required: true
},
delay: {
type: Number,
default: 0
}
},
data: function() {
return {
isVisible: false
};
},
computed: {
styleObject: function() {
return {
opacity: this.isVisible ? 1 : 0
};
}
},
mounted: function() {
var that = this;
window.setTimeout(function() {
that.isVisible = true;
}, that.delay);
}
});
new Vue({
el: '#app',
data: {
dataset: {
first: 'Hello world',
second: 'Foo bar',
third: 'Lorem ipsum'
}
}
});
.animated-component {
transition: opacity 0.25s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<animated-component v-bind:data="dataset.first"> </animated-component>
<animated-component v-bind:data="dataset.second" v-bind:delay="500"> </animated-component>
<animated-component v-bind:data="dataset.third" v-bind:delay="1000"> </animated-component>
</div>
<script type="text/x-template" id="animatedComponentTemplate">
<div class="animated-component" v-bind:style="styleObject">
<h1>Animated Component, delay: {{ delay }}</h1>
{{ data }}
</div>
</script>
If you have the possibility to reformat your data, you can build an array of series objects, add a show: true/false property and iterate it:
//template
<div v-for="serie in series">
<donut :items="serie.data" v-if="serie.show"></donut>
</div>
//script
data: function() {
return {
series: [
{ data: [44, 55, 41, 17, 15], show: false },
{ data: [10, 20, 30], show: false },
]
}
}
Now you can create a setTimeout function which will change the serie.show to true by incrementing the delay based on the serie index.
Then add the function on the mounted hook:
methods: {
delayedShow (serie, idx) {
let delay = 1500 * idx
setTimeout(() => {
serie.show = true
}, delay)
}
},
mounted () {
this.series.forEach((serie, idx) => {
this.delayedShow(serie, idx)
})
}
Live example
Faced the same problem with ApexCharts Pie Charts being redrawn rapidly in sequence due to data being pulled from a pinia store mutating too quickly for the chart to keep up, leading to ugly errors in the console.
I resolved the issue by using a boolean ref in a v-if="showChart" on the component and then using a setTimeout to trigger a delayed drawing of the chart:
import { ref } from "vue";
import useStore from "#/store/myChartStore";
const store = useStore();
const showChart = ref(false);
store.$subscribe((mutation, state) =>{
showChart.value = false;
setTimeout(()=> {
showChart.value = true;
}
, 100);
});
If you're not using a store, you may find another way to watch the initial availability of the chart data and then delay the rendering using that same approach.

Show string in component

I am trying to learn Vue.js, and am playing around with the modal component example. I'm trying to change it up a bit so that the button clicked can provide data to the component (I'm very new to this so my terminology may be off).
I've updated the app to be:
// start app
var app = new Vue({
el: '#app',
data: {
showModal: false,
title: 'Default Title'
},
methods: {
modalInit: function(title) {
//this.title = title;
this.showModal = true;
}
}
})
The updates were mainly so that I can change the title within the modal based on the button clicked, here is the update to the button:
<button id="show-modal"#click="modalInit('A title')">Show Modal</button>
The relevant portion of the x-template:
<div class="modal-header">
<h2>{{ title }}</h2>
</div>
Not sure if it matters, but the component is:
Vue.component('modal', {
template: '#modal-template',
})
In this state, the modal will open fine but the title won't be there and I get the console error: [Vue warn]: Property or method "title" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I can't figure out to to properly "declare reactive data properties in the data option".
Thanks!
You've specified a title property for the root component. But, the modal component does not have a title property. Add it like this:
Vue.component('modal', {
template: '#modal-template',
data() {
return {
title: "Default Title"
}
}
})
If you want to pass in a dynamic value for title, make it a property instead:
Vue.component('modal', {
template: '#modal-template',
props: ['title']
})
Then, you can pass the value for the title in the component tag:
<modal :title="dynamicTitle"></modal>

VueJS - trigger Modal from materializecss

I am trying to trigger a modal from the materializecss framework within a VueJS-instance.
Both, VueJS and Materializecss, are implemented correct. On their own both frameworks work fine.
Clicking the open-button results in an error:
Uncaught TypeError: data[option] is not a function
at HTMLDivElement. (adminarea.js:24562)
at Function.each (adminarea.js:10567)
at jQuery.fn.init.each (adminarea.js:10356)
at jQuery.fn.init.Plugin [as modal] (adminarea.js:24556)
at Vue$3.showLoader (adminarea.js:21396)
at boundFn (adminarea.js:54956)
at HTMLButtonElement.invoker (adminarea.js:56467)
This is my Vue-Instance:
const app = new Vue({
el: '#app',
data: {
activeUser: {
username: '',
email: ''
},
},
methods: {
showLoader(){
$('#loaderModal').modal('open');
},
closeLoader(){
$('#loaderModal').modal('close');
}
},
mounted() {
// Get current User
axios.get('/api/currentUser')
.then(response => {
this.activeUser.username = response.data.username;
this.activeUser.email = response.data.email;
});
},
components: {
Admindashboard
}
});
And here is the part of my html-file with the modal structure:
<!-- Modal Structure -->
<div id="loaderModal" class="modal">
<div class="modal-content">
<h4>Fetching data..</h4>
<div class="progress">
<div class="indeterminate"></div>
</div>
</div>
</div>
<button class="btn cyan waves-effect waves-cyan" v-on:click="showLoader">Open</button>
Any ideas? Thanks!
It seems I found an solution:
Nice to know for Laravel-users: for my current project I use Laravel 5.5 with Materializecss, VueJS and VueRouter but I think the solution is universal. Materializecss was installed via npm and has to be included into your application. I've required the css-framework within my ressources/assets/js/bootstrap.js:
...// more code
try {
window.$ = window.jQuery = require('jquery');
window.materialize = require('materialize-css');
} catch (e) {
console.log(e);
}
...// more code
Now you have to initialize the Modal-function on the mounted-event of your wrapping Vue-instance:
const app = new Vue({
router,
data: {
...
},
methods: {
testClick: function(){
console.log('Testklick-Call');
$('#modal1').modal('open');
}
},
mounted: function(){
console.log('Instance mounted');
$('.modal').modal();
}
}).$mount('#app');
The code above is placed within my ressources/assets/js/app.js and is packed by default by Laravel Mix but I think this is universal and also usable without Laravel Mix/Webpack etc.
Now you can call every modal programmatically from where ever you want. I've tested it in my main instance on a click-event. Function is placed in my Vue-instance (see above). HTML-Code see below:
<button v-on:click="testClick">Open Modal</button>
But you can also make use of the modal within a mounted-function or any other function of any component:
<template>
<div>
<p>I am an component!</p>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted!');
$('#modal1').modal('open');
}
}
</script>
This also works, if the component becomes only visible after clicked on a link (using VueRouter).
Hopefully this helps someone except me :)
As suggested here, you need to add following code in the mounted block:
mounted() {
$('#loaderModal').modal(); //New line to be added
// Get current User
axios.get('/api/currentUser')
.then(response => {
this.activeUser.username = response.data.username;
this.activeUser.email = response.data.email;
});
},