I am following this tutorial to upload files using VueJS and Express server and I have encountered a line of code that I do not understand.
At 8:19 we are introduced to :class="`level ${file.invalidMessage && 'has-text-danger'}`". I couldn't find anything similar in the Vue documentation.
Video Example:
<div v-for="(file, index) in files" :key="index"
:class="`level ${file.invalidMessage && 'has-text-danger'}`"
>
</div>
Vue Docs:
<div v-bind:class="{ active: isActive }"></div>
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
<div v-bind:class="classObject"></div>
<div v-bind:class="[activeClass, errorClass]"></div>
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
There is nothing similar to that weird syntax in the Vue Documentation.
I understand what ${} inside back quotes marks is used for(template literals).
I understand what the code purpose is, I have tested it and works, but I do not understand how it works and what shortcuts were used.
Can somebody "translate" that line of code ?
The :class binding in Vue accepts strings:
new Vue({
el: "#app",
computed: {
classlist() {
return this.classes.join(' ')
}
},
data() {
return {
classes: [
'first-class',
'second-class'
],
domClassList: null
}
},
mounted() {
// getting and setting the classlist of the DOM element
this.domClassList = document.getElementById('classList').getAttribute('class')
}
})
.level {
background: green;
padding: 15px;
}
.first-class {
color: white;
}
.second-class {
font-weight: 700;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="classList" :class="`level ${ classlist }`">
CLASSES ADDED AS STRINGS
</div>
<div>Classes: {{ domClassList }}</div>
</div>
As you can see in the snippet above, the classes are made from one level and the join of a Vue data variable (the classes array) that is returned in a computed property (classlist).
The other part of the question is the conditional format:
new Vue({
el: "#app",
computed: {
classlist() {
return this.classes.join(' ')
}
},
data() {
return {
firstOperand: true,
classes: [
'first-class',
'second-class'
],
domClassList: null
}
},
methods: {
setDomClassList() {
// getting and setting the classlist of the DOM element
this.domClassList = document.getElementById('classList').getAttribute('class')
},
toggleOperand() {
this.firstOperand = !this.firstOperand
// this.$nextTick is required so Vue can set the data
// values before we read it from the DOM
const self = this
this.$nextTick(function() {
self.setDomClassList()
})
}
},
mounted() {
this.setDomClassList()
}
})
.level {
background: green;
padding: 15px;
}
.first-class {
color: white;
}
.second-class {
font-weight: 700;
}
/* this class is set so you can see the difference on
toggling the firstOperand variable
*/
.false {
font-style: italic
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="classList" :class="`level ${ firstOperand && classlist }`">
CLASSES ADDED AS STRINGS
</div>
<div>Classes: {{ domClassList }}</div>
<hr />
<div>
FIRSTOPERAND IS {{ firstOperand }}<br />
<button #click="toggleOperand">TOGGLE FIRSTOPERAND TO {{ !firstOperand }}</button>
</div>
</div>
The second snippet is almost the same as the first, except the && is introduced. I also created a .false CSS value so you can see the returned false.
Related
If I have an element like this that is in a for loop:
<p class="description" id="description" #click="onClickDescription">{{ review.text }}</p>
meaning that there is more than one of these elements, how can I change the style of the element that was clicked.
I have this function:
onClickDescription(e) {
console.log(e)
let d = document.getElementById('description');
d.style.webkitLineClamp = 99;
}
But this will change the style of all my <p> elements instead of just the one I clicked. How can I get around this?
You can use index or id if you have it in review object:
new Vue({
el: '#demo',
data() {
return {
reviews: [{text: 'aaa'}, {text: 'bb'}, {text: 'ccc'}],
selected: null
}
},
methods: {
onClickDescription(i) {
this.selected = i
}
}
})
.description {
color: green;
}
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div v-for="(review, i) in reviews" :key="i">
<p class="description" :class="i === selected && 'active'" #click="onClickDescription(i)">{{ review.text }}</p>
</div>
</div>
<template>
<!--This is my main file -->
<div id="inputs">
<h1>언어 관리</h1>
<v-btn color="primary" elevation="10" large #click="duplicateEl"
>Add row</v-btn
>
<Contents />
</div>
</template>
<script>
import Contents from "./Contents.vue";
export default {
name: "LanguageMainMenu",
components: { Contents },
methods: {
duplicateEl() {
alert("You can duplicate buttons");
},
},
};
</script>
<style>
h1 {
text-align: center;
font-size: 38px;
padding-top: 20px;
margin: auto;
}
</style>
The best apprach is to use the component inside v-for.
Increment the index when the button is clicked.
Dont forget to use key inside the v-for
Working Fiddle
var example1 = new Vue({
el: '#app',
name: "LanguageMainMenu",
components: {
Contents: {
template: `<div>Contents Component</div>`,
}
},
data() {
return {
totalCount: 1,
}
},
methods: {
duplicateEl() {
this.totalCount++;
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.4/vue.js"></script>
<div id="app">
<!--This is my main file -->
<div id="inputs">
<h1>언어 관리</h1>
<button #click="duplicateEl">Add row</button>
<Contents v-for="count in totalCount" :key="`component-${count}`" />
</div>
</div>
You can add a property in data object and use v-for for render buttons.
Let method duplicateEl to change the property value.
<v-btn v-for="item in btnNumber" ....>
duplicateEl(){
this.btnNumber++
}
I have the following problem:
I have too much logic in my inline style and would to place it inside a computed property. I know, that this is the way, that I should go, but do not know, how to achieve it.
Below I a simple example that I made for better understanding. In it, on button press, the child's component background-color is changing.
My code can be found here: Codesandbox
My parent component:
<template> <div id="app">
<MyChild :colorChange="active ? 'blue' : 'grey'" />
<p>Parent:</p>
<button #click="active = !active">Click Me!</button> </div> </template>
<script> import MyChild from "./components/MyChild";
export default { name: "App", components: {
MyChild, }, data() {
return {
active: false,
}; }, }; </script>
and my child component:
<template> <div class="hello">
<p>Hello from the child component</p>
<div class="myDiv" :style="{ background: colorChange }">
here is my color, that I change
</div> </div> </template>
<script> export default { name: "HelloWorld", props: {
colorChange: {
type: String,
default: "green",
}, }, }; </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .myDiv { color: white; padding: 1rem 0; } </style>
I also have a second question. Let's say, that I have more than one child component and also would like to change to colors on button click, but those colors differ. How can I achieve it without repeating myself (within the computed properties?)
Code example for my parent component:
<MyChild :colorChange="active ? 'blue' : 'grey'" />
<MyChild :colorChange="active ? 'grey' : 'blue'" />
<MyChild :colorChange="active ? 'blue' : 'red'" />
<MyChild :colorChange="active ? 'yellow' : 'blue'" />
Thanks in advance!
Maybe You can bind class and use different css classes:
Vue.component('MyChild',{
template: `
<div class="hello">
<p>Hello from the child component</p>
<div class="myDiv" :class="collorCurrent">
here is my color, that I change
</div>
</div>
`,
props: {
colorChange: {
type: String,
default: "green",
},
colorDef: {
type: String,
default: "green",
},
isActive: {
type: Boolean,
default: false,
},
},
computed: {
collorCurrent() {
return this.isActive ? this.colorChange : this.colorDef
}
}
})
new Vue({
el: "#demo",
data() {
return {
active: false,
}
},
})
.myDiv { color: white; padding: 1rem; }
.blue {
background: blue;
font-size: 22px;
}
.red {
background: red;
font-variant: small-caps;
}
.yellow {
background: yellow;
color: black;
}
.grey {
background: grey;
text-decoration: underline;
}
.green {
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<p>Parent:</p>
<button #click="active = !active">Click Me!</button>
<my-child :is-active="active" :color-def="'grey'" :color-change="'blue'"></my-child>
<my-child :is-active="active" :color-def="'blue'" :color-change="'grey'"></my-child>
<my-child :is-active="active" :color-def="'red'" :color-change="'blue'"></my-child>
<my-child :is-active="active" :color-def="'blue'" :color-change="'yellow'"></my-child>
</div>
I'm trying to capture an event on the component root node, but the following does not work. I don't want to just listen on a node in the component. I want to be able to click on any element and then hit backspace to remove it. The code below is a basic example of how I setup my code.
<template>
<div v-on:keydown.delete="delete()">
<img id="image" src="..." v-on:click="set_active()">
</div>
</template>
<script>
export default {
return {
data() {
active: ''
},
methods: {
delete(){
delete this.$refs[this.active][0];
},
set_active() {
this.active = event.target.getAttribute('id');
}
}
}
}
</script>
After doing some tests, here is what I discovered:
Having a method called delete won't work. I don't know why, the question remains unanswered here. Rename it to remove, for example.
When trying to catch keyboard events on a div, you may need to add a tabindex attribute for it to work. (See here)
Interactive demo
Vue.component('my-component', {
template: '#my-component',
data() {
return {
images: [
"https://media.giphy.com/media/3ohs7KtxtOEsDwO3GU/giphy.gif",
"https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif",
"https://media.giphy.com/media/8L0xFP1XEEgwfzByQk/giphy.gif"
],
active: null
};
},
methods: {
set_active(i) {
this.active = i;
},
remove() {
if (this.active !== null) {
this.images = this.images.filter((_, i) => i !== this.active);
this.active = null;
}
}
}
});
var vm = new Vue({
el: '#app'
});
div {
outline: none; /* Avoid the outline caused by tabindex */
border: 1px solid #eee;
}
img {
height: 80px;
border: 4px solid #eee;
margin: .5em;
}
img:hover {
border: 4px solid #ffcda9;
}
img.active {
border: 4px solid #ff7c1f;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<my-component></my-component>
</div>
<template id="my-component">
<div #keydown.delete="remove" tabindex="0">
<img
v-for="(img, i) in images"
:key="i"
:src="img"
:class="{ active: active === i }"
#click="set_active(i)"
/>
</div>
</template>
I am following a tutorial and I want to add a new feature where the surname of each candidate is added as a class. I got this to work inline but then I wanted to clean it up and rather call it as a function.
Working inline
mayor.name.replace(/ /g,'').replace('Mr.','').toLowerCase()
The function textClass removes spaces and "Mr." from the string. I've tried adding this as a computed property but I don't know how to call it on mayor.name
CSS
.black{ color: black;}
.brown{ color: brown;}
.pink{ color: pink;}
.red{ color: red;}
HTML
<div class="container">
<div id="mayor-vote">
<h2>Mayor Vote</h2>
<ul class="list-group" style="width: 400px;">
<li v-for="candidate in candidates" class="list-group-item clearfix">
<div class="pull-left">
<strong style="display: inline-block; width: 100px;">{{ candidate.name }}:</strong> {{ candidate.votes }}
</div>
<button class="btn btn-sm btn-primary pull-right" #click="candidate.votes++">Vote</button>
</li>
</ul>
<h2>Our Mayor is <span class="the-winner" :class="mayor.name.textClass">{{ mayor.name }}</span></h2>
<button #click="clear" class="btn btn-default">Reset Votes</button>
</div>
</div>
</body>
JS
new Vue({
el: '#mayor-vote',
data: {
candidates: [
{ name: "Mr. Black", votes: 140 },
{ name: "Mr. Red", votes: 135 },
{ name: "Mr. Pink", votes: 145 },
{ name: "Mr. Brown", votes: 140 }
]
},
computed: {
mayor: function(){
var candidateSorted = this.candidates.sort(function(a,b){
return b.votes - a.votes;
});
return candidateSorted[0];
},
textClass: function() {
return this.replace(/ /g,'').replace('Mr.','').toLowerCase();
}
},
methods: {
clear: function() {
this.candidates = this.candidates.map( function(candidate){
candidate.votes = 0;
return candidate;
})
}
}
});
There are few mistakes in your code, one is dynamic class binding in vue takes a hash object, not an string. So you have to return an hash like this : { active: true } from the computed property.
Second thing is computed property in vue always modify another vue propery or values returned from an mehtod, to correct these you need to make following changes:
You have to use this.mayor.name in computed property to calculate dynamic class like this:
computed: {
mayor: function(){
var candidateSorted = this.candidates.sort(function(a,b){
return b.votes - a.votes;
});
return candidateSorted[0];
},
textClass: function() {
var tmp = {}
tmp[this.mayor.name.replace(/ /g,'').replace('Mr.','').toLowerCase()] = true
return tmp
}
},
and apply like this in HTML:
<h2>Our Mayor is <span class="the-winner" :class="textClass">{{ mayor.name }}</span></h2>