Vuejs Conditional class using compute - vue.js

I'm learning Vuejs and I can't see any documentation or examples on how to have conditional class, based on the record.
I have a conditional class with a variable 'taskClassComputed':
<div
v-bind:key="task.id"
v-for="task in currenttasks"
v-bind:class="taskClassComputed"
>
I then have a computed function to determine the class to use:
taskClassComputed: function () {
var classType = "task-openTile";
if (this.state === "CLOSED") {
classType = "task-closedTile";
} else if (this.state === "OPEN") {
if (this.fasttrack != "undefined") {
classType = "task-tile";
} else if (this.score >= 2000) {
classType = "task-fastTrackedTiles";
}
classType = "task-tile";
}
return classType;
},
But the function always returns the default value. Can someone point me to documentation on how I learn how to do this - or tell me where I'm going wrong.
Many thanks.
I've stipped down the page (still working) to show the code being used.
<template #updateParentTasks="updateTasks">
<div id="todo-app" class="relative overflow-hidden">
<div id="data-list-list-view">
<vs-table
#updateParentTasks="updateTasks"
ref="tasktable"
v-model="selected"
:data="currenttasks"
>
<div
v-bind:key="task.id"
v-for="task in currenttasks"
v-bind:class="taskClassComputed"
>
<div>
<div
id="task-card"
v-on="$listeners"
#updateParentTasks="updateTasks"
>
<div #updateParentTasks="updateTasks">
<vs-row vs-w="12">
<vs-col
class="shape"
vs-type="flex"
vs-lg="1"
vs-sm="1"
vs-xs="1"
>Testing</vs-col
>
</vs-row>
</div>
</div>
</div>
</div>
</vs-table>
</div>
</div>
</template>
<script>
import consts from "#/const.js";
function update_itemsbystate(src, itemstate) {
const dest = [];
let i = 0,
j = 0;
for (i = 0; i < src.length; i++) {
if (src[i].state === itemstate) {
/*eslint-disable-next-line*/
console.log(
"item " + itemstate + ":" + i + " " + j + JSON.stringify(src[i])
);
dest[j] = JSON.parse(JSON.stringify(src[i]));
j++;
}
}
return dest;
}
function parseByState(tasks, page) {
let result = "OPEN";
if (page.showDeleted === true) {
result = "DELETED";
} else if (page.showClosed === true) {
result = "CLOSED";
}
return update_itemsbystate(tasks, result);
}
import tasksidebar from "./TaskSidebar";
import QuickTaskPopup from "./QuickTaskPopup";
import { mapGetters } from "vuex";
import taskutils from "../../utils/taskutils";
export default {
name: "TaskList",
components: {
tasksidebar,
QuickTaskPopup,
// NewTaskPopup,
},
data() {
return {
selected: [],
// tasks: [],
currenttasks: [],
isMounted: false,
showOpen: true,
showClosed: false,
showDeleted: false,
// Filter Sidebar
isFilterSidebarActive: true,
clickNotClose: true,
// Data Sidebar
addNewDataSidebar: false,
sidebarData: {},
isPopupActive: false,
isNewPopupActive: false,
};
},
computed: {
...mapGetters(["alltasks"]),
currentPage() {
if (this.isMounted) {
return this.$refs.tasktable.currentx;
}
return 0;
},
queriedItems() {
return this.$refs.tasktable
? this.$refs.tasktable.queriedResults.length
: this.currenttasks.length;
},
windowWidth() {
return this.$store.state.windowWidth;
},
taskClassComputed: function () {
var classType = "task-openTile";
if (this.state === "CLOSED") {
classType = "task-closedTile";
} else if (this.state === "OPEN") {
if (this.fasttrack != "undefined") {
classType = "task-tile";
} else if (this.score >= 2000) {
classType = "task-fastTrackedTiles";
}
classType = "task-tile";
}
return classType;
},
},
mounted() {
this.isMounted = true;
},
methods: {
addNewData() {
this.sidebarData = {};
this.toggleDataSidebar(true);
},
updateTasks() {
console.log("updating tasks in task list");
this.currenttasks = parseByState(this.alltasks, this);
this.$forceUpdate();
},
openTasks() {
if (this.showOpen === false) {
this.showOpen = true;
this.showDeleted = false;
this.showClosed = false;
this.updateTasks();
}
},
closedTasks() {
if (this.showClosed === false) {
this.showOpen = false;
this.showDeleted = false;
this.showClosed = true;
this.updateTasks();
}
},
deletedTasks() {
if (this.showDeleted === false) {
this.showOpen = false;
this.showDeleted = true;
this.showClosed = false;
this.updateTasks();
}
},
editData(data) {
this.sidebarData = data;
this.toggleDataSidebar(true);
},
},
beforeMount() {
this.currenttasks = parseByState(this.alltasks, this);
console.log("pre-mount");
},
created() {
this.setSidebarWidth();
},
};
</script>
<style lang="scss">
.task-fastTrackedTile {
padding: 40 !important;
overflow: hidden;
border: 1px solid;
border-color: red;
background: rgba(184, 96, 96, 0.07);
border-left-width: 7px;
border-left-color: red;
margin-bottom: 5px;
border-radius: 0.5rem;
}
.task-closedTile {
padding: 40 !important;
// overflow: hidden;
border: 1px solid;
border-color: gray;
background: rgba(239, 239, 255, 0.07);
border-left-width: 7px;
border-left-color: gray;
margin-bottom: 5px;
border-radius: 0.5rem;
}
.task-openTile {
padding: 40 !important;
// overflow: hidden;
border: 1px solid;
border-color: rgba(203, 203, 218, 0.4);
border-left-width: 7px;
border-left-color: rgba(25, 25, 170, 0.4);
margin-bottom: 5px;
border-radius: 0.5rem;
}
.taskFasttrackProp {
// overflow: hidden;
// text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.7rem;
color: red;
margin-right: 15px;
}
.taskProp {
// overflow: hidden;
// text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.7rem;
color: blue;
margin-right: 15px;
}
</style>

you can set multiple classes to vue element by using:
<my-componenet :class="['class1', condition1 ? 'class2' : 'class3']" ..
// condition1 can be anything data, computed, prop, ...
also you can check doc for more details

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>

how to set a trey of letters`style differently in a v-for loop?

It is similar to the question of whose URL I`ll put below but with one difference, I want not one letter or word style conditionally presented but a trey of letters, I wish all the letters to be in
a different color, is there any solution to it?
the tricky thing is the v-for loop based on an array whose length is not constant.
Change the style of a substring in a v-for loop
example:
I want this input tag letters full of different random colored letters
here is the full code:
<template>
<div
class="nice-input"
:class="{'nice-input--shaked': animated, 'nice-input--caret': !caret}"
>
<input
:id="id !== undefined ? id : 'input-'+name"
v-model="value"
type="text"
:name="name"
autocomplete="off"
>
<label :for="id !== undefined ? id : 'input-'+name">
<span v-for="word in arr" :key="word.sa" class="nice-input__animate"
:style="randomColor">{{ word }}</span>
</label>
</div>
</template>
<script>
export default {
name: 'weirdInput',
props: ['name', 'id'],
data () {
return {
animated: false,
caret: true,
value: '',
randomColor: '#000000',
}
},
computed: {
arr: function () {
return this.value.split('')
},
},
watch: {
value: {
handler: function (after, before) {
const self = this
this.caret = false
if (after.length > before.length) {
if (this.value.slice(-1) === ' ') return false
setTimeout(function () {
self.animated = true
setTimeout(function () {
self.animated = false
self.caret = true
}, 300)
}, 750)
}
this.randomColor = 'color: #' + Math.floor(Math.random() * 16777215).toString(16)
},
},
},
}
</script>
<style scoped>
.nice-input {
position: relative;
}
.nice-input input {
border:none;
border-radius:4px;
padding:7px 10px;
font-family: 'Lato', sans-serif;
font-size:14px;
box-shadow: rgba(0,0,0,.05) 0 5px 20px;
letter-spacing:0;
width:1000px;
color: transparent;
font-weight:900;
caret-color: #555;
}
.nice-input input:focus {
outline:none;
box-shadow: rgba(0,0,0,.1) 0 5px 20px;
}
.nice-input label {
position: absolute;
top: 6px;
left: 10px;
letter-spacing:0;
font-size:0;
}
.nice-input span {
font-family: 'Lato', sans-serif;
font-size:14px;
font-weight:900;
}
.nice-input__animate {
animation: print .75s 1 ease-in-out;
}
.nice-input--shaked {
animation: shake .2s 1 ease-in-out;
}
.nice-input--caret {
caret-color: transparent;
}
#keyframes print {
from{
position:absolute;
transform: scale(100);
}
99% {
position:absolute;
}
to {
position:relative;
}
}
#keyframes shake {
from,
to {
}
50% {
transform:scale(0.97);
}
}
</style>

Vue 3 Composition API component doesn't work reactivate class

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)
}

Using svg-gauge in vue component

I'd like to use svg-gauge (https://github.com/naikus/svg-gauge) in a vue component (vue-svg-gauge does not work properly). I am struggling where to start. I have imported the library but am not sure about using it within vue. This is what I have done so far:
<script>
module.exports = {
props: {
'value': {type: Number, default: 20},
},
data: function () {return {
chart: null
}},
mounted: function () {
this.chart = new Gauge(
document.getElementById("gauge1"), {
min: 0,
max: 50,
dialStartAngle: 180,
dialEndAngle: 0,
value: this.value,
color: function(value) {
if (value < 0) {
return "#5ee432";
} else if(value < 25) {
return "#fffa50";
} else if(value < 50) {
return "#f7aa38";
} else {
return "#ef4655";
}
}
}
);
},
}
}
}
</script>
It renders a gauge but I'm not sure how to update it or if this is the right way of going aboout things?
Thanks
Martyn
Update:
I am now using High charts gauge as it see easier to configure.
The solution I came up with for the above question is as follows (from an old commit)
<template>
<div class="wrapper bg-dark text-light border-0">
<div id="gauge1" class="gauge-container two"></div>
</div>
</template>
<script>
module.exports = {
props: {
'value': {type: Number, default: 0},
},
data: function () {return {
chart: null
}},
mounted: function () {
this.chart = new Gauge(
document.getElementById("gauge1"), {
min: 0,
max: 50,
dialStartAngle: 180,
dialEndAngle: 0,
value: this.value,
color: function(value) {
if (value < 0) {
return "#5ee432";
} else if(value < 25) {
return "#fffa50";
} else if(value < 50) {
return "#f7aa38";
} else {
return "#ef4655";
}
}
}
);
},
beforeDestroy: function () {
// this.chart.destroy()
},
watch: {
value: function (val) {
this.chart.value = val
},
options: function (opt) {
// this.chart.destroy()
// this.chart = new RadialGauge(Object.assign({}, this.options, {renderTo: 'canvas-gauge', value: this.value})).draw()
}
}
}
</script>
<style scoped>
/* ------ Default Style ---------- */
.gauge-container {
width: 250px;
height: 250px;
display: block;
float: left;
padding: 10px;
background-color: #222;
margin: 7px;
border-radius: 3px;
position: relative;
}
.gauge-container > .label {
position: absolute;
right: 0;
top: 0;
display: inline-block;
background: rgba(0,0,0,0.5);
font-family: monospace;
font-size: 0.8em;
padding: 5px 10px;
}
.gauge-container > .gauge .dial {
stroke: #334455;
stroke-width: 2;
fill: rgba(0,0,0,0);
}
.gauge-container > .gauge .value {
stroke: rgb(47, 227, 255);
stroke-width: 2;
fill: rgba(0,0,0,0);
}
.gauge-container > .gauge .value-text {
fill: rgb(47, 227, 255);
font-family: sans-serif;
font-weight: bold;
font-size: 0.8em;
}
/* ------- Alternate Style ------- */
.wrapper {
height: 150px;
float: left;
margin: 7px;
overflow: hidden;
}
.wrapper > .gauge-container {
margin: 0;
}
.gauge-container.two {
}
.gauge-container.two > .gauge .dial {
stroke: #334455;
stroke-width: 10;
}
.gauge-container.two > .gauge .value {
stroke: orange;
stroke-dasharray: none;
stroke-width: 13;
}
.gauge-container.two > .gauge .value-text {
fill: #ccc;
font-weight: 100;
font-size: 1em;
}
</style>

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>