I'm making the numbers in red or blue in table cells according to the value using :cell-class-name. It works fine in the table, body but I also want to have the same effect in the summary row. Is there any way to do that?
<template>
<el-table
:data="products"
style="width: 90%"
show-summary
:header-cell-style="{
'background-color': '#f7f7f7',
color: '#6e6e6e',
'font-weight': '400',
'text-align': 'end',
}"
:cell-class-name="classChecker" >
<el-table-column width="50" prop="profit" align="center"> </el-table-column>
<el-table-column width="50" prop="cost" align="center"> </el-table-column>
<el-table-column width="50" prop="price" align="center"> </el-table-column>
</template>
methods: {
classChecker({ row, column }) {
const val = row[column.property];
if (val > 0) {
return "blueClass";
} else {
return "redClass";
}
}
}
<style>
.blueClass {
color: #0090ff;
}
.redClass {
color: red;
}
</style>
I had the same requirement a while back, my understanding is that they don't have this feature, so you'll have to implement it yourself.
Here's how I did it:
codepen: https://codepen.io/rugia/pen/ZEajGqg
basically what it does is to add color class on target columns' sum cell every time tabledata updates.
methods: {
async applyFooterColor () {
await this.$nextTick()
const table = this.$refs.table
const cols = ['amount1', 'amount2']
table.columns.forEach(col => {
const cells = document.getElementsByClassName(col.id)
const cell = cells[cells.length - 1].children[0]
if (cols.includes(col.property)) {
const val = +cell.textContent
if (+val !== 0) {
const color = val > 0 ? 'blue' : 'red'
cell.classList.add(color)
return
}
}
cell.classList.remove('red', 'blue')
})
}
},
watch: {
tableData: {
immediate: true,
handler: function() {
this.applyFooterColor()
}
}
}
Related
I have 4 range inputs. Each of them has min number 0, max number 10.
In Total they can't sum to more than 22.
One way to approach this would be to disable all inputs once they hit 22 and add a reset button. I would find it to be more user-friendly to allow the ranges to be decremented after the max is reached instead of a whole reset.
I tried disabling if it's less or equal 0, but the scroller was still under control.
Check the comments on the
sandbox here if it easier , but the parent class is as below:
<template>
<div class="vote">
<div class="vote__title">Left: <span>{{ hmLeft }}</span> votes</div>
<div class="vote__body">
<div v-for="user in activeInnerPoll" :key="user._id">
<userVoteFor :hmLeft="hmLeft" #cntCount="cntCount" :id="user._id"/>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex"
import userVoteFor from "#/components/userVoteFor";
export default {
name: "Vote.vue",
components: {
userVoteFor
},
data(){
return {
votes: 22,
objRes: {} // that's where we write what id of a user and how many counts
}
},
computed: {
...mapGetters("polls", ["activeInnerPoll"]), // array of objects {_id : "some_id", cnt: 0}
hmLeft(){ // how much left, counter which tells how many votes left
let sum = 0;
for(let key in this.objRes){
sum += this.objRes[key];
}
return this.votes - sum;
}
},
methods: {
cntCount(id, cnt){ // emit for children, gets id and cnt of input-range and sets to result obj
this.objRes[id] = parseInt(cnt);
}
}
}
</script>
<style scoped lang="scss">
#import "#/assets/vars.scss";
#import "#/assets/base.scss";
.vote{
&__title{
#include center;
margin-top: 15px;
span{
font-size: 20px;
margin: 0 5px;
color: $pink;
}
}
}
</style>
Child class here:
<template>
<div class="vote__component">
<label class="vote__component__label" :for="id">{{ playerNameById( id )}}</label>
<input #input="check($event)" // thought maybe something to do with event ?
:disabled="disable"
class="vote__component__input"
:id="id"
type="range"
min="0"
max="10"
step="1"
v-model="cnt">
<div class="vote__component__res">{{ cnt }}</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "userVoteFor.vue",
props: {
id: {
type: String,
required: true
},
hmLeft: {
type: Number,
required: true
}
},
emits: ["cntCount"],
data() {
return {
cnt: 0,
disable: false,
lastVal: 0
}
},
computed: {
...mapGetters("user", ["playerNameById"]) // gets map object which stores names for user by id
},
methods: {
check(e){
console.log(e);
if(this.hmLeft <= 0) { //HERE IS APART WHERE I THINK SHOULD BE WRITTEN LOGIC if hmLeft <= 0 then ... , else write cnt in resObj and computed var will calc how many votes left
this.lastVal = this.cnt;
this.cnt = this.lastVal;
}
else this.$emit("cntCount", this.id, this.cnt);
}
}
}
</script>
<style scoped lang="scss">
.vote__component{
width: 80%;
margin: 10px auto;
position: relative;
display: flex;
justify-content: right;
padding: 10px 0;
font-size: 15px;
&__input{
margin-left: auto;
width: 60%;
margin-right: 20px;
}
&__res{
position: absolute;
top: 20%;
right: 0;
}
&__label{
}
}
</style>
The way I'd implement this is by using a watch and the get and set method of computed.
The array of values would be updated via a computed. This makes it easy to hook into a v-model and allows us to maintain reactivity with the original array.
The watch is then used to compute the total that is available. Then, for bonus points, we can use the total to adjust the width of the input so the step size remains consistent.
Even though this is using the composition Api, you can implement that using data, watch and computed the classical way
const makeRange = (max, vals, index) => {
const defaultMax = 10;
const num = Vue.computed({
get: () => vals[index],
set: value => vals[index] = Number(value)
});
const total = Vue.computed(() => vals.reduce((a, b) => a + b, 0), vals);
const style = Vue.computed(() => {
return `width: ${(numMax.value * 12 + 20)}px`
})
const numMax = Vue.computed(() => {
return Math.min(defaultMax, (num.value + max - total.value))
}, total);
return {num, numMax, style};
};
const app = Vue.createApp({
setup() {
const vals = Vue.reactive([5, 5, 5])
const max = 22;
const ranges = vals.map((v,i)=>makeRange(max, vals, i));
// helpers for visualising
const total = Vue.computed(() => vals.reduce((a, b) => a + b, 0), vals);
const totalLeft = Vue.computed(() => max - total.value , total.value);
return {ranges, vals, totalLeft, total, max};
}
}).mount('#app');
<script src="https://unpkg.com/vue#3.0.2/dist/vue.global.prod.js"></script>
<div id="app">
<li v-for="range in ranges">
<input
:style="range.style.value"
type="range" min="0"
:max="range.numMax.value"
v-model="range.num.value"
>
value: {{range.num.value}}
max: {{range.numMax.value}}
</li>
<li>{{ vals.join(' + ') }} = {{ total }}</li>
<li>max is {{ max }} , minus total {{total }} is {{ totalLeft }}</li>
</div>
I have v-text-field and I can't able to display the error message out of v-text-field dom position. in vuetify documentation there is not reference to this issue.
Is it possible to have the error message near the input?
Actual:
Expected:
<template>
<v-app>
<v-content>
<playground></playground>
<v-text-field
style="width:120px;"
class="numer"
:rules="[rules.required, rules.min, rules.max]"
v-model="numValue"
type="number"
append-outer-icon="add"
#click:append-outer="increment"
prepend-icon="remove"
#click:prepend="decrement"
></v-text-field>
{{numValue}}
</v-content>
</v-app>
</template>
<script>
import Playground from "./components/Playground";
export default {
name: "App",
components: {
Playground
},
data: function() {
return {
numValue: 0,
form: {
min: 2,
max: 10
},
rules: {
required: value => !!value || "Required.",
min: v => v >= this.form.min || `The Min is ${this.form.min}`,
max: v => v <= this.form.max || `The Max is ${this.form.max}`
}
};
},
methods: {
increment() {
if (this.numValue < this.form.max) {
this.numValue = parseInt(this.numValue, 10) + 1;
}
},
decrement() {
if (this.numValue > this.form.min) {
this.numValue = parseInt(this.numValue, 10) - 1;
}
}
}
};
</script>
<style>
.numer {
flex: 0 0 auto;
margin: 0;
padding: 0;
}
.numer input {
text-align: center;
}
</style>
The code in codesandbox
I know I'm late but if someone needs help:
Put this field inside a form.
<v-form ref="form">
<v-text-field
style="width:120px;"
class="numer"
:rules="[rules.required, rules.min, rules.max]"
v-model="numValue"
type="number"
append-outer-icon="add"
#click:append-outer="increment"
prepend-icon="remove"
#click:prepend="decrement"
></v-text-field>
</v-form>
Then call anywhere on template:
<div #click="$refs.form.validate()">Validate it!</div>
Or on the functions:
methods: {
foo() {
this.$refs.form.validate()
}
}
I am trying to pass an array of objects as a prop to a component. The Array is being passed without an array. I am neither receiving any compilation error.
I tried actually looking on to the object tried some stuff. But it did not work
Here is the code:
CardRenderer.vue:
<template lang="html">
<div>
<b-container class="bv-example-row">
<b-row v-for="(row, i) of rows" v-bind:key="i">
<b-col v-for="(item, j) of row" v-bind:key="j" >
<!-- you card -->
<b-card
:title="item.title"
img-src="item.icon"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
<h1>{{item.name}}</h1>
<pre>{{item.description}}</pre>
</b-card-text>
<b-button :href="'/dashboard/'+item.name" variant="primary">More</b-button>
</b-card>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script lang="js">
export default {
name: 'CardRenderer',
props: {
renderData: {
type: Array,
required: true,
default: () => ([]),
}
},
data() {
return {
rows: null
}
},
mounted() {
const itemsPerRow = 3
let rowss = []
// eslint-disable-next-line
console.log(this.renderData)
let arr = this.renderData
for (let i = 0; i < arr.length; i += itemsPerRow) {
let row = []
for (let z = 0; z < itemsPerRow; z++) {
row.push(arr[z])
}
rowss.push(row)
}
this.rows = rowss
// eslint-disable-next-line
// console.log(this.rows)
},
methods: {
},
computed: {
// rows() {
// }
}
}
</script>
<style scoped>
</style>
Something.vue
<template lang="html">
<!-- <h1>Something</h1> -->
<CardRenderer :renderData=valObj />
</template>
<script lang="js">
import CardRenderer from './CardRenderer'
export default {
name: 'something',
components: {
CardRenderer
},
props: [],
data() {
return {
valObj: []
}
},
mounted() {
let key = this.findUrl()
let value = this.$store.getters.responseAPI.apps.filter((elem) => {
if(elem.name == key) return elem.apps
})
if (value && value.length > 0)
this.valObj = value[0].apps
//eslint-disable-next-line
console.log(this.valObj)
},
methods: {
findUrl() {
let url = window.location.pathname.split("/").slice(-1)[0];
return url
}
},
computed: {
}
}
</script>
<style scoped >
.something {
}
</style>
This is what i am sending as a prop.
This is what i receive on the component
There's a couple of issues here.
First, you should be using kebab-cased attribute names and quotes around the value...
<CardRenderer :render-data="valObj" />
The second issue is timing related. In your component, you calculate rows based on the initial renderData in the mounted hook but this will not update when the parent component alters valObj.
What you should do instead is use a computed property which will react to valObj / renderData changes.
For example
data () { return {} }, // removed rows from data
computed: {
rows () {
let itemsPerRow = 3
let rows = []
for (let i = 0; i < this.renderData.length; i += itemsPerRow) {
rows.push(this.renderData.slice(i, i + itemsPerRow))
}
return rows
}
}
I can move and transfer the rectangles with the code below.
I used konva library at vue.js
This works well.
But I want to get the x,y position to save into local-storage after moving it
Could you teach how to get that?
And I am sorry for the long code
You can attach this code at '.vue' which works well without problem.
It moves and transfer well, but I can 't see the value of position moving it
<template>
<div>
<v-stage ref="stage" :config="stageSize" #mousedown="handleStageMouseDown">
<v-layer ref="layer">
<v-rect v-for="item in rectangles" :key="item.id" :config="item"/>
<v-transformer ref="transformer"/>
</v-layer>
</v-stage>
<div>
<p>{{ rectangles[0].x }}</p>
<button #click="addCounter">+</button>
<button #click="subCounter">-</button>
<button #click="position">SAVE</button>
</div>
</div>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height
},
rectangles: [
{
x: 150,
y: 100,
width: 100,
height: 100,
fill: "red",
name: "rect1",
draggable: true
},
{
x: 150,
y: 150,
width: 100,
height: 100,
fill: "green",
name: "rect2",
draggable: true
}
],
selectedShapeName: ""
};
},
methods: {
position() {
localStorage.setItem(this.rectangles[0].x, true);
},
addCounter() {
this.rectangles[0].x++;
},
subCounter() {
this.rectangles[0].x--;
},
handleStageMouseDown(e) {
// clicked on stage - cler selection
if (e.target === e.target.getStage()) {
this.selectedShapeName = "";
this.updateTransformer();
return;
}
// clicked on transformer - do nothing
const clickedOnTransformer =
e.target.getParent().className === "Transformer";
if (clickedOnTransformer) {
return;
}
// find clicked rect by its name
const name = e.target.name();
const rect = this.rectangles.find(r => r.name === name);
if (rect) {
this.selectedShapeName = name;
} else {
this.selectedShapeName = "";
}
this.updateTransformer();
},
updateTransformer() {
// here we need to manually attach or detach Transformer node
const transformerNode = this.$refs.transformer.getStage();
const stage = transformerNode.getStage();
const { selectedShapeName } = this;
const selectedNode = stage.findOne("." + selectedShapeName);
// do nothing if selected node is already attached
if (selectedNode === transformerNode.node()) {
return;
}
if (selectedNode) {
// attach to another node
transformerNode.attachTo(selectedNode);
} else {
// remove transformer
transformerNode.detach();
}
transformerNode.getLayer().batchDraw();
}
}
};
</script>
You can use dragmove and transform events.
<v-rect
v-for="item in rectangles"
:key="item.id"
:config="item"
#dragmove="handleRectChange"
#transform="handleRectChange"
/>
handleRectChange(e) {
console.log(e.target.x(), e.target.y()); // will log current position
},
Demo: https://codesandbox.io/s/lp53194w59
I tried setting an active state whenever I select an item and add an active class to it. Now, I wonder how can we get the unselected item and add a specific class to it like not-active.
Sample:
<img src="/images/icons/thumbup.png"
#click="setActive('thumbUp')"
class="thumb-active"
:class="{ active: isActive('thumbUp') }">
<img src="/images/icons/thumbdown.png"
#click="setActive('thumbDown')"
class="thumb-active"
:class="{ active: isActive('thumbDown') }">
Vue script:
export default {
data() {
return {
activeItem: '',
}
},
methods: {
isActive: function (button) {
return this.activeItem === button
},
setActive: function (button) {
this.activeItem = button
}
},
}
Very, very simply
:class="{ active: isActive('state A'), 'not-active': isActive('state B') }"
Here's a demo...
var app = new Vue({
el: '#app',
data: {
activeItem: null
},
methods: {
isActive: function (button) {
return this.activeItem === button
},
setActive: function (button) {
this.activeItem = button
}
}
})
.active {
background-color: green;
color: white;
font-weight: bold;
}
.not-active {
background-color: red;
color: white;
}
button {
font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<button src="/images/icons/thumbup.png"
#click="setActive('thumbUp')"
class="thumb-active"
:class="{ active: isActive('thumbUp'), 'not-active': isActive('thumbDown') }">
Thumbs Up!
</button>
<button src="/images/icons/thumbdown.png"
#click="setActive('thumbDown')"
class="thumb-active"
:class="{ active: isActive('thumbDown'), 'not-active': isActive('thumbUp') }">
Thumbs Down¡
</button>
</div>