Dynamically add styles from array Vue - vue.js

I have some progress bar the width of which are getting from data from the array. The color of this bar depends on the same data. There are 3 colors if progress is 0 the color is grey, more than 0 its blue, and if it 100 its should be green.
<div class="card-item" v-for="item in status" :key="item.id"> // loop
<div class="progress-item">
<span>{{item.progress}}%</span> // data from arr
<div class="progress-bar">
<div class="progress-bar-inner" :style="{width: item.progress}"></div> // width in style should in %
</div>
</div>
</div>
Maybe i should to write the function?

You can use a method or inline the logic in a style object.
If you only have 3 cases, I would use a conditional class instead of styles, though.
Here's a codepen that shows many possibilities: https://codepen.io/Flamenco/pen/gjNxXp?editors=1111
methods: {
theColor(item){
return item.length===0 ? 'grey' : item.length < 100 ? 'blue' : 'green'}
}
}
<div>using inline style</div>
<div :style="{color: item.length===0 ? 'grey' : item.length < 100 ? 'blue' : 'green'}">...</div>
<div>using method</div>
<div :style="{color: theColor(item)}">...</div>
Using conditional class. Much easier to read, debug, maintain, and extend.
methods: {
theClass(item) {
if (item.length === 0) return 'none'
else if (item.length < 100) return 'under'
else return 'over'
}
}
.none {
color: grey;
}
.under {
color: blue;
}
.over {
color: green;
}
<div>A few ways to do this...</div>
<div :class='theClass(item)'>...</div>
<div :class='[theClass(item), "anotherClass"]'>...</div>
<div :class='{[theClass(item)]:true, anotherClass:true]}'>...</div>

Related

Button with v-on:click that switches between the classes btn-primary and btn-primary-outline not working

Im working on a button that opens a modal. By default it should have the btn-primary-outline class (Blue text, transparent background, blue border), when clicked it should have the btn-primary class (White text, blue background, blue border). But its not working, the button stays trasparent, the text blue, and all its doing is toggling the blue button border on and off.
HTML:
<button v-on:click="settingsButtonIsActive = !settingsButtonIsActive"
class="btn margin-top-half center-block col-xs-12"
:class="[settingsButtonIsActive ? 'btn-primary' : '', 'btn-primary-outline']">
<strong>{{labels.lblButtonConfiguration}}</strong>
</button>
Controller:
data = {
settingsButtonIsActive: false
}
I feel like the button doesnt like not having any of those 2 clases defined, but i cant think of any other way to do it.
Your "mistake" you use an array syntax but the logic of "else" is empty '' (If settingsButtonIsActive is false render => '') + always render btn-primary-outline (The (ternary) operator inside 0 index - btn-primary-outline on 1 index).
For example this:
:class="[settingsButtonIsActive ? 'btn-primary' : '', 'btn-primary-outline', 'hello', 'world']">
render:
<button class="btn-primary btn-primary-outline hello world">
Not in "vue" this is your logic:
var element = document.getElementById("myDIV");
if(settingsButtonIsActive){
element.classList.add("btn-primary");
}
else{
element.classList.add("");
}
element.classList.add("btn-primary-outline");
This is the correct markup (For -or- a -or- b) - shortcut for "if else":
class="settingsButtonIsActive ? 'btn-primary' : 'btn-primary-outline'">
No need her for array syntax: https://v2.vuejs.org/v2/guide/class-and-style.html#Array-Syntax
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
var app = new Vue({
el: '#app',
data: {
msg: "settingsButtonIsActive",
settingsButtonIsActive: true,
isActive: "btn-primary",
hasError: "btn-primary-outline"
}
})
button{
padding: 5px;
cursor: pointer;
}
.btn-primary{
background: red;
border: 1px solid red;
}
.btn-primary-outline{
background: transparent;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div id="app">
<button v-on:click="settingsButtonIsActive = !settingsButtonIsActive"
:class="settingsButtonIsActive ? 'btn-primary' : 'btn-primary-outline'">
<strong>{{msg}}</strong>: {{settingsButtonIsActive}}
</button>
</div>
I would recommend rewriting the code a bit.
First of all, you can write the class name switch as a computed property:
// ... beginning of your .js code
computed: {
isButtonActive () {
return this.settingsButtonIsActive ? 'btn-primary' : 'btn-outline-primary'
}
}
// ... rest of your .js code
then you can merge both class attributes into one and bind it like this:
<button v-on:click="settingsButtonIsActive = !settingsButtonIsActive"
:class="['btn', 'margin-top-half', 'center-block', 'col-xs-12', isButtonActiveClass">
<strong>{{labels.lblButtonConfiguration}}</strong>
</button>
There is also another option, but because you change between two classes, I think a computed property is a much cleaner solution. However, you can achieve same result also with this code:
<button v-on:click="settingsButtonIsActive = !settingsButtonIsActive"
:class="['btn', 'margin-top-half', 'center-block', 'col-xs-12', {'btn-primary': isButtonActiveClass}, {'btn-outline-primary': !isButtonActiveClass}">
<strong>{{labels.lblButtonConfiguration}}</strong>
</button>
Note that when I want to change classes dynamically I'm passing an object into the array.
More can be found on https://v2.vuejs.org/v2/guide/class-and-style.html.
I fixed it by using the object syntax instead of the array syntax.
<button v-on:click="settingsButtonIsActive = !settingsButtonIsActive"
class="btn margin-top-half center-block col-xs-12"
:class="{'btn-primary' : settingsButtonIsActive === true, 'btn-primary-outline' : settingsButtonIsActive === false}">
<strong>{{labels.lblButtonConfiguration}}</strong>
</button>

How to pass function to html

I'm new to vue js, so I have simple function to hide the progress bar created in methods, but doesn't seem to work, I'm wondering if I need to add event or bind it, I think it's something simple, but I can't figure it out.
methods: {
hideProgressBar: function() {
const hideProgress = document.querySelector(".progress-bar");
if (hideProgress) {
hideProgress.classList.add(hide);
} else {
hideProgress.classList.remove(hide);
}
}
}
.progress-bar {
height: 1rem;
color: #fff;
background-color: #f5a623;
margin-top: 5px;
}
.hide.progress-bar {
display: none;
}
<div class="progress-bar" role="progressbar"></div>
If you want to invoke the method when the page is loaded, add the following created option after your methods option
created: function(){
this.hideProgressBar()
}
otherwise if you want to invoke the method based on an event then you would need to add your event.
If you're using vue.js you'd want to use the inbuilt directives as much as you can.
This means you can avoid the whole hideProgressBar method.
<button #click="hideProgressBar = !hideProgressBar">Try it</button>
<div class="progress-bar" v-if="!hideProgressBar">
Progress bar div
</div>
And you script would have a data prop that would help you toggle the hide/show of the progress bar
data () {
return {
hideProgressBar: false
}
}
just try like this:
methods: {
hideProgressBar: function() {
var element = document.getElementsByClassName("progress-bar")[0];
if (element.classList.contains('hide')) {
element.classList.remove('hide');
} else {
element.classList.add('hide');
}
}
}
<div class="progress-bar">
Progress bar 1 div
</div>
<div class="progress-bar hide">
Progress bar 2 div
</div>
I have used two progress bar for demonstration. Initially,
The first progress bar doesn't contain the hide class so hide class will be added.
Then the second progress already has hide class so it will be removed
DEMO:
//hides first progress bar by adding hide class.
var element = document.getElementsByClassName("progress-bar")[0];
if (element.classList.contains('hide')) {
element.classList.remove('hide');
} else {
element.classList.add('hide');
}
//display second progress bar by remove hide class
var element = document.getElementsByClassName("progress-bar")[1];
if (element.classList.contains('hide')) {
element.classList.remove('hide');
} else {
element.classList.add('hide');
}
.progress-bar {
height: 1rem;
color: #fff;
background-color: #f5a623;
margin-top: 5px;
}
.hide.progress-bar {
display: none;
}
<div class="progress-bar">
Progress bar 1 div
</div>
<div class="progress-bar hide">
Progress bar 2 div
</div>

Condition on value for style on Vue.js

I'd like to check the value of my template
<template slot="projected_end_date" slot-scope="{ line }">
<datetime-display :value="line.projected_end_date"
type="date"
style= "color: red"></datetime-display>
</template>
and only set the style for red color when my value is less than current date. Any suggestion? I'm assuming it should be something like
value > DateHelper.now()? style = ...
Take a look at Class and Styles Bindings
You can use it like:
<div :class="{'my-class': myCondition}">
Simple example
new Vue({
el: '#app',
data: {
selectedColor: 'red'
}
})
.red { background: red}
.blue { background: blue}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<button #click="selectedColor='red'">Red</button>
<button #click="selectedColor='blue'">Blue</button>
<p :class="{red: selectedColor === 'red', blue: selectedColor === 'blue'}">the background color will change !</p>
</div>
You can use class and style binding like this:
:style="value < DateHelper.now() ? { 'color': 'red'} : ''"
Or,
:style="{color: value < DateHelper.now() ? 'red' : 'inherit'}"
Or some default color:
:style="{color: value < DateHelper.now() ? 'red' : 'black'}"
But I suggest you to use class binding whenever possible:
:class="{warning: value < DateHelper.now()}"
And in your css styles:
.warning {
color: red;
}
Also, you can simply use Date.now() instead of a helper function.

Vue Accordion with transition

I'm trying to integrate the Accordion component with a body transition, but without success :( . All is working as well except the animation.
template:
<div class="accordion">
<div class="accordion-title" #click="isOpen = !isOpen" :class="{'is-open': isOpen}">
<span>{{title}}</span>
<i class="ic ic-next"></i>
</div>
<div class="accordion-body" :class="{'is-open': isOpen}">
<div class="card">
<slot name="body"></slot>
</div>
</div>
</div>
component:
props: {
title: {
type: String,
default: 'Title'
}
},
data() {
return {
isOpen: false
}
}
And styles:
.accordion-body {
font-size: 1.3rem;
padding: 0 16px;
transition: .3s cubic-bezier(.25,.8,.5,1);
&:not(.is-open) {
display: none;
height: 0;
overflow: hidden;
}
&.is-open {
height: auto;
// display: block;
padding: 16px;
}
}
.card {
height: auto;
}
I tried to use <transition> but it doesn't work with height or display properties.
Help please!
display:none will remove your content and avoid the animation, you should trick with opacity, overflow:hidden and height, but you ll be forced to do a method for that.
For example (not tested, but inspiring):
in template:
<div class="accordion" #click="switchAccordion" :class="{'is-open': isOpen}">
<div class="accordion-title">
<span>{{title}}</span>
<i class="ic ic-next"></i>
</div>
<div class="accordion-body">
<p></p>
</div>
</div>
in component (add a method):
methods: {
switchAccordion: function (event) {
let el = event.target
this.isOpen = !this.isOpen // switch data isOpen
if(this.isOpen) {
let childEl1 = el.childNodes[1]
el.style.height = childEl1.style.height
} else {
let childEl2 = el.childNodes[2]
el.style.height = childE2.style.height // or .clientHeight + "px"
}
}
}
in style:
.accordion {
transition: all .3s cubic-bezier(.25,.8,.5,1);
}
.accordion-body {
font-size: 1.3rem;
padding: 0 16px;
opacity:0
}
.is-open .accordion-body {
opacity:0
}
In this case, your transition should work as you want.
The javascript will change the height value and transition transition: all .3s cubic-bezier(.25,.8,.5,1); will do the animation

Tab switching causes a little deformation during animation

I made a simple vertical content slider based on tabs and found optical issue when animating . If you click on different tab, current content slides up and in the same time new one slides down. All contents have same height. The problem is that the height is changing a little bit during the animation (focus on bottom border). I don't really know how to fix it. Is there any way to prevent it?
Here is the complete code:
http://jsfiddle.net/YGY26/8/
JS:
var active = 1;
function item(id) {
if (id !== active) {
$("#description" + active).slideUp("slow");
if (active !== id) {
$("#description" + id).slideDown("slow");
active = id;
}
}
}
HTML:
<div class='slider_box' onclick='item(1);'>
<h3>Content1</h3>
<div id='description1' class='slider_content' style='display: block'>
some content to show
</div>
</div>
<div class='slider_box' onclick='item(2);'>
<h3>Content2</h3>
<div id='description2' class='slider_content'>
some content to show
</div>
</div>
<div class='slider_box' onclick='item(3);'>
<h3>Content3</h3>
<div id='description3' class='slider_content'>
some content to show
</div>
</div>
Basic CSS:
.slider_box {
position: relative;
width: 330px;
}
.slider_box h3 {
color: white;
background: black;
}
.slider_content {
height: 330px;
display: none;
}