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.
Related
<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 am trying to style a p tag that is slotted into a child component using the tag.
Parent Code
<template>
<BasicButton content="Test 1234" #click="SendMessage('test')" height="10" width="50" />
<TransparentTopbar />
<BasicContainer width="90">
<p class="p-blueish-gray">{{ LorumIpsum() }}</p>
</BasicContainer>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import Homepage from "./components/home/Homepage.vue";
import TransparentTopbar from "./components/tools/topbar/TransparentTopbar.vue";
import BasicButton from "./components/tools/button/BasicButton.vue";
import BasicContainer from "./components/tools/container/basic_container/BasicContainer.vue"
//import func from "vue-temp/vue-editor-bridge";
export default defineComponent({
name: "App",
components: {
Homepage,
TransparentTopbar,
BasicButton,
BasicContainer
},
methods: {
SendMessage: sendMessage,
LorumIpsum: lorumIpsum
},
});
Child Code
<template>
<div class="light-rounded-container card-margins" :style="container_style">
<slot></slot>
</div>
</template>
<script lang="ts" src="./BasicContainer.ts" />
<style scoped src="./BasicContainer.css" />
import { defineComponent } from 'vue';
export default defineComponent({
name: 'BasicContainer',
props: {
width: {
type: Number,
default: 90,
required: false
}
},
data() {
return {
container_style: {
width: this.width.toString() + '%'
}
}
},
methods: {}
});
.light-rounded-container {
background-color: #242629;
border-radius: 15px;
align-self: center;
}
::v-slotted(p) {
color:red !important;
width: 1% !important;
padding-left: 5% !important;
padding-right: 5% !important;
padding-top: 25px !important;
padding-bottom: 15px !important;
}
The BasicContainer component is the one I am trying to style slotted content on. I would like to style the p tag that I pass to this component in the parent, but I would like to style it from within this child component.
#StevenB. you are corrent. I did not have my style in a separately defined NON-SCOPED .css file. After moving my styling into their and re structuring my HTML to avoid bleeding the css style into other global scope, it worked. Much appreciated.
<template>
<div class="container p-2">
<form id="Lookup">
<div class="row p-2">
<div class="col-12 input-group ">
<input type="text" name="postcode" :placeholder="initial" v-model="location" class="form-control p-3"><div class="input-group-append"><i class="material-icons input-group-text" #click="$emit('findlocation', location)">location_searching</i></div>
</div>
</div>
</form>
</div>
</template>
<script>
export default{
props: ['initial'],
data: function () {
return {
location : this.initial
}
}
}
</script>
The above is my Vue Component which is passed a string value called initial
This value is passed from the template below
<practicesearch-component #findlocation="getlocation" :initial=postalTown" />
where postalTown is a data property of the main vue
but instead of getting the string value of postalTown in location I get
function String() { [native code] }
the prop initial in the Vue Component shows the correct string value but location has been assigned a function
What am I doing wrong?
When Vue initializes your component, the data function does not have access to the View Model this. You can use the mounted hook to assign the value
export default {
props: ['initial'],
data: () => ({
location: undefined
}),
mounted() {
this.location = this.initial;
}
}
Note that this way, whenever initial changes in the parent, location will not be updated.
Here is a quick sample:
Vue.productionTip = false;
Vue.component('child', {
template: `
<div class="child">
Child component:
<br/>
location: {{ location }}
<br/>
initial: {{ initial }}
</div>
`,
props: { initial: String },
data: () => ({ location: undefined }),
mounted () {
this.location = this.initial;
}
});
new Vue({
el: '#app',
template: `
<div class="parent">
Parent Component
<br/>
location: {{ location }}
<child :initial="location" />
</div>
`,
data: () => ({ location: 'US' })
});
.parent {
background-color: darkgray;
padding: 1em;
border: solid 1px black;
color: white;
}
.child {
background-color: gray;
padding: 1em;
border: solid 1px black;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
After much analysis I found the problem that was causing this behaviour.
<practicesearch-component #findlocation="getlocation" :initial=postalTown" />
postalTown in the above was not set initially in the Vue instance but within mounted therefore the component was being passed an uninitialised string object and not the initialised value of postalTown
I added the :key attribute on postalTown as below which causes the component to re-render with the initialised value of postalTown
Perhaps this is not the best option but it works
<practicesearch-component #findlocation="getlocation" :key=postalTown :initial=postalTown ></practicesearch-component>
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');
}