VUE: How to use prop with CSS modules? - javascript

row.vue
<script>
export default {
name: "Row",
data() {
return {
app: [
"row",
"row-test",
"flex-center"
]
}
},
template: `
<div :class="$module[app]"><slot></slot></div>
`
}
</script>
row.scss
.row {
color: red;
}
.row-test {
color: green;
}
.flex-center {
align-items: center;
justify-content: center;
}
App.vue
<template>
<div id="app">
<row app="row-test flex-center">my row</row>
<router-view />
</div>
</template>
vue.config.js
css: {
requireModuleExtension: true,
loaderOptions: {
css: {
modules: {
localIdentName: 'hex-[HASH:HEX:3]'
}
}
},
modules: true
}
I want to declare my attributes using the app attribute, where they will be written similarly to an ordinary class. Then I want there to be an Array in row.vue, which will add only the values that I define in and convert them to CSS using CSS modules (class is randomly generated)
I'm getting this
<div app="row-test flex-center">my row</div>
Expected result
<div class="hex-g3D hex-f41">my row</div>

If you're passing app as prop to Row component, define a computed property to return the modules with the different classes:
<script>
export default {
name: "Row",
props:['app'],
computed:{
modules(){
return this.app.length? this.app.map(_class=>{
return this.$module[_class];
}):[];
}
},
template: `
<div :class="modules"><slot></slot></div>
`
}
</script>
and pass it like :
<template>
<div id="app">
<row :app="['row-test', 'flex-center']">my row</row>
<router-view />
</div>
</template>

Related

Update elements background color in VueJs

In my app, I have 4 components:
App.vue
ProfileCardSlider
ProfileCard
ColorPicker
The idea is users would be able to update the background color of their ProfileCard using the element.
I can't work out how I can get the color value from the input and then emit that value up to the parent (App.vue in this case). In App.vue I believe I also need a color data property which this emitted value would then update and pass down as props to the ProfileCard.
These are my two components:
App.vue
import ProfileCardSlider from "./components/ProfileCardSlider.vue";
import ColorPicker from "./components/ColorPicker.vue";
export default {
components: {
ProfileCardSlider,
ColorPicker,
},
data() {
return {
color: "red",
};
},
methods: {
updateColor() {
this.color = color;
},
},
};
</script>
<template>
<ColorPicker :color="color" #select-color="updateColor" />
<ProfileCardSlider :color="color" />
</template>
ColorPicker.vue
<script>
export default {
props: ["color"],
methods: {
selectColor(color) {
this.$emit('update-color', color)
},
},
};
</script>
<template>
<div class="color__picker">
<h2 class="color__picker--title">Card background colour:</h2>
<input type="color" #change="selectColor" />
</div>
</template>
Using v-model and props in the correct way can help achieve this.
In your App.vue, use the color data property as hex (best practice to use in props), i.e #000000, and bind the color property to v-model to ColorPicker component.
Then in ColorPicker component, use the value prop which is the v-model binding from App.vue, and emit an input event on color input updating.
At last, listen to the color-changing event in App.vue to pass the updated color to ProfileCardSlider.vue.
So, the final code should be-
App.vue
<template>
<div>
<ColorPicker v-model="color" #input="updateColor" />
<ProfileCardSlider :color="color" />
</div>
</template>
<script>
import ProfileCardSlider from "./components/ProfileCardSlider.vue";
import ColorPicker from "./components/ColorPicker.vue";
export default {
name: "App",
data() {
return {
color: "#ffffff",
};
},
components: {
ProfileCardSlider,
ColorPicker,
},
methods: {
updateColor(color) {
this.color = color;
},
},
};
</script>
ColorPicker.vue
<template>
<div class="color__picker">
<h2 class="color__picker--title">Card background colour:</h2>
<input
type="color"
:value="value"
#input="$emit('input', $event.target.value)"
/>
</div>
</template>
<script>
export default {
name: "ColorPicker",
props: ["value"]
};
</script>
<style>
</style>
ProfileSlider.vue
(I take some dummy code for this component as you didn't mention in the question but the logic would be the same.)
<template>
<div class="card">
<div class="container" :style="{ background: color }">
<h4><b>John Doe</b></h4>
<p>Architect & Engineer</p>
</div>
</div>
</template>
<script>
export default {
name: "ProfileCardSlider",
props: {
color: {
required: true,
},
},
};
</script>
<style>
.card {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
width: 40%;
}
.container {
padding: 2px 16px;
}
</style>
You should see that when you will change the color from the color input which is inside ColorPicker.vue, the card inside ProfileSlider.vue will have an updated color.

How can I duplicate my component in vue.js?

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

How to pass a style property to a child component as a computed property in Vue.js?

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>

How does :class="`level ${file.invalidMessage && 'has-text-danger'}`" break down?

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.

Access the $refs of a parent from a child - Vue.js

Hopefully this has been answered before - essentially I'm trying to append a block ("CodeBlock.vue") to an element inside App.vue from an onClick event triggered inside a sibling of CodeBlock, and a child of App.vue, ("ButtonSidebar.vue"). I'm a little confused by emitting events and/or using an eventBus Vue instance, so any pointers would be greatly appreciated:
So far I have the following. CodeBlock.vue which will be used as an instance and appended to a div inside App.vue.
CodeBlock.vue:
<template>
<div :class="type">
THIS IS A CODE BLOCK!!
</div>
</template>
<script>
export default {
name: 'CodeBlock',
props: [ 'type' ]
}
</script>
App.vue:
<template>
<div id="app" class="container">
<ButtonSidebar/>
<div id="pageBlocks" ref="container"></div>
</div>
</template>
<script>
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
// import { eventBus } from './main'
import AddTitle from './components/modules/AddTitle'
import AddSubTitle from './components/modules/AddSubTitle'
import ButtonSidebar from './components/modules/ButtonSidebar'
import CodeBlock from './components/modules/CodeBlock'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
export default {
name: 'App',
components: {
AddTitle,
AddSubTitle,
ButtonSidebar,
CodeBlock
}
}
</script>
<style>
#app {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #1f1f1f;
margin-top: 60px;
}
.no-border {
border: unset !important;
border: 0px !important;
}
</style>
ButtonSidebar.vue:
<template>
<div>
<b-button class="btn-circle absolute-float-tight text-dark" v-on:click="reveal=!reveal">
<font-awesome-icon v-if="!reveal" :icon="faPlusIcon" />
<font-awesome-icon v-if="reveal" :icon="faMinusIcon" />
</b-button>
<transition name="custom-classes-transition" enter-active-class="animated bounceInDown" leave-active-class="animated bounceOutRight">
<div v-if="reveal" class="absolute-float-reveal">
<b-button class="btn-circle text-dark" v-on:click="addCodeBlock"><font-awesome-icon :icon="faCodeIcon" /></b-button>
</div>
</transition>
</div>
</template>
<script>
import Vue from 'vue'
import FontAwesomeIcon from '#fortawesome/vue-fontawesome'
import faPlus from '#fortawesome/fontawesome-pro-regular/faPlus'
import faMinus from '#fortawesome/fontawesome-pro-regular/faMinus'
import faCode from '#fortawesome/fontawesome-pro-regular/faCode'
import CodeBlock from './CodeBlock'
export default {
name: 'ButtonSidebar',
computed: {
faPlusIcon () {
return faPlus
},
faMinusIcon () {
return faMinus
},
faCodeIcon () {
return faCode
}
},
components: {
FontAwesomeIcon,
CodeBlock
},
data () {
return {
reveal: false
}
},
props: ['codeBlocks'],
methods: {
addCodeBlock () {
var ComponentClass = Vue.extend(CodeBlock)
var instance = new ComponentClass({
propsData: { type: 'primary' }
})
instance.$mount()
this.$el.querySelector('#pageBlocks').appendChild(instance.$el)
}
}
}
</script>
<style scoped>
.absolute-float-tight {
left: 20px;
position: absolute;
}
.absolute-float-reveal {
left: 60px;
position: absolute;
}
.btn-circle {
background-color: transparent;
border-radius: 50%;
height: 34px;
padding: 0;
width: 34px;
}
</style>
It's around the this.$el.querySelector('#pageBlocks').appendChild(instance.$el) part that I start to loose the plot a bit...I'm worried that I have to strip everything down and start again perhaps?
You should avoid reaching to the DOM as much as possible. The source of truth for the data should be in your components.
refs are very useful to integrate other js library that needs a DOM element.
So in your case, assuming codeBlocks are available in your App.vue components, the SidebarButton needs to emit an event when it's clicked so that the parent App.vue can add a new Codeblock:
(I have removed some code not needed for the example. CodeBlock.vue stays the same)
App.vue
<template>
<div id="app" class="container">
<ButtonSidebar #add-block="addCodeBlock" />
<CodeBlock v-for="block in codeBlocks" :type="block.type" />
</div>
</template>
<script>
import ButtonSidebar from '../ButtonSidebar'
import CodeBlock from '../CodeBlock'
export default {
name: 'App',
components: {ButtonSidebar, CodeBlock},
data() {
return {
codeBlocks: []
}
},
methods: {
addCodeBlock() {
const newBlock = {type: 'whatever'}
this.codeBlocks.push(newBlock)
}
}
}
</script>
ButtonSideBar.vue
<template>
<div>
<b-button class="btn-circle text-dark" v-on:click="addCodeBlock</b-button>
</div>
</template>
<script>
export default {
name: 'ButtonSidebar',
data () {
return {
reveal: false
}
},
methods: {
addCodeBlock () {
this.$emit('add-block')
}
}
}
</script>
A good pattern to follow in Vue is to lift the state to the parents and passing it down as props whenever you feel like you want to share state between parents and children.
I think you could achieve it in this simple way:
App.vue (template section)
<ButtonSidebar #add="addCodeItem"/>
<div id="pageBlocks">
<codeBlock v-for="code in arrCodes" :type="code.type"/>
</div>
App.vue (script)
export default {
data() {
return {
arrCodes: []
}
},
methods: {
addCodeItem(codeType) {
this.arrCodes.push( { type: codeType } )
}
}
}
ButtonSidebar.vue (script section)
addCodeBlock () {
this.$emit('add', 'yourtype');
}

Categories