Vuejs/Nuxtjs : How to create dynamic V-model names without using the v-for? - vue.js

I am encountering a tricky issue in my Vuejs/Nuxtjs application. In the application, I am creating multiple Nodes dynamically. These Nodes have the Radio button for which I have assigned a v-model. However, when I change the value of one Vuejs v-model is affecting all other Node Values.
I am aware that this issue is happening because of the same v-model being used for all Nodes. I would like to assign a different V-model to my Radio button but I want to do it without using the v-for.
I have created the sample code in the CodeSandbox
Steps to reproduce:
Drag and drop the Identifiers into the canvas. Now the URN will be selected.
Now Drag and drop another Identifiers into the canvas. Now the first Identifiers Node: URN will disappear. I am unable to handle each Node value independently.
The problem is arising in the file #components/IdentifiersNode.vue and in the radio button.
Code sample based on the Kissu response :
<input
id="identifierTypeURN"
:data="identifierSyntax"
value="URN"
type="radio"
name="instanceIdentifierURN"
#input="instanceIdentifiersSyntaxChange('URN')"
>
<label for="identifierTypeURN">URN</label>
<input
id="identifierTypeWebURI"
:data="identifierSyntax"
value="WebURI"
type="radio"
name="instanceIdentifierWebURI"
#input="instanceIdentifiersSyntaxChange('WebURI')"
>
<label for="identifierTypeWebURI">WebURI</label>
Can someone please check and let me know what am I doing wrong here: https://codesandbox.io/s/cocky-matan-kvqnu?file=/nuxt.config.js

After some effort able to get it working. I was using the Radio button functionalities wrongly. I changed it to something like this and it worked fine:
<template>
<div ref="el">
<div class="header">Identifiers Node: {{ ID }}</div>
<div id="app" class="nodeContainer">
{{ "Value : " + identifierSyntax }}
Syntax:
<input
:id="`identifierTypeURN-${ID}`"
:data="identifierSyntax"
value="URN"
type="radio"
:name="`instanceIdentifier-${ID}`"
:checked="identifierSyntax === 'URN'"
#input="instanceIdentifiersSyntaxChange($event, 'URN')"
/>
<label :for="`identifierTypeURN-${ID}`">URN</label>
<input
:id="`identifierTypeWebURI-${ID}`"
:data="identifierSyntax"
value="WebURI"
type="radio"
:name="`instanceIdentifier-${ID}`"
:checked="identifierSyntax === 'WebURI'"
#input="instanceIdentifiersSyntaxChange($event, 'WebURI')"
/>
<label :for="`identifierTypeWebURI-${ID}`">WebURI</label>
</div>
</div>
</template>
<script>
export default {
data() {
return {
ID: "",
nodeId: "",
bizStep: "",
allNodeInfo: [],
identifierSyntax: "URN",
};
},
mounted() {
console.log("MOUNTED");
this.$nextTick(() => {
const id = this.$el.parentElement.parentElement.id;
const data = this.$df.getNodeFromId(id.slice(5));
this.ID = data.data.ID;
this.nodeId = data.data.nodeId;
this.allNodeInfo = JSON.parse(
JSON.stringify(
this.$store.state.modules.ConfigureIdentifiersInfoStore
.identifiersArray,
null,
4
)
);
this.identifierSyntax = this.allNodeInfo.find(
(node) => node.identifiersId === this.nodeId
).identifierSyntax;
});
},
methods: {
// On change of the IdentifierSyntax change, change the value in the respective node info
instanceIdentifiersSyntaxChange(event, syntaxValue) {
console.log("CHANGED : " + syntaxValue);
console.log(event.target.defaultValue);
this.identifierSyntax = syntaxValue;
// Change the value of the respective syntax within the Node information in IdentifiersNode array
this.$store.commit(
"modules/ConfigureIdentifiersInfoStore/identifiersSyntaxChange",
{ nodeId: this.ID, syntaxValue }
);
},
},
};
</script>
<style>
.header {
background: #494949;
margin-top: -15px;
margin-left: -15px;
margin-right: -15px;
padding: 10px 15px;
margin-bottom: 15px;
}
</style>

Related

How can I use a computed property to change the color of a text pill depending on the word using vue composition api?

I have a pill shape that say's either Production or Development. I want the background color to be different depending on if it is production or development. I've done this before with option api, but I'm having trouble in composition api.
Computed Method:
const reportStatus = computed(function() {
if (dashboard.status === 'Production') {return 'status--production'}
return 'status--qa'
});
Styling:
.status--production {
background-color: blue;
color: white;
}
.status--qa {
background-color: green;
color: white;
}
Template code:
<div>
<span class="float-left text-center w-24 inline-block flex-shrink-0 rounded-full px-2 py-0.5 text-xs font-medium text-white inset-x-0 top-0" :class="reportStatus">
{{ dashboards.status }}</span>
</div>
Script Dashboard code:
const dashboard = [
{
report: "Ad Hoc",
description: "",
status: "Production"
},
Use computed properties for things like changing reactive variable output. For example, some different date format.
The best way to your issue is:
<template>
<span class="fload ..." :class="[ dashboard.status === 'Production' ? 'status-production' : 'status-qa']">{{ dashboard.status }}</span>
</template>
Make sure dashboard is a reactive object/value like ref() or reactive() second one fit best for objects. Objects are tricky to use every time you have to assign a full new object instead of just change a value in it.
Computed property:
<template>
<div>
<span class="float ..." :class="addClass">{{ dashboard.status }}</span>
<button #click="dashboard = { ...dashboard, status: 'lol' }">Change</button>
<button #click="dashboard = { ...dashboard, status: 'Production' }">Production</button>
</div>
</template>
<script setup>
const dashboard = ref({ status: 'Production' })
const addClass = computed(() => dashboard.value.status === 'Production' ? 'status-production' : 'status-qa')
</script>
If you use ref() and change dashboard like "dashboard.value.status = 'some value'" reactivity won't work same as "dashboard.status = 'some value'" in template. You will always need to assign a new object to trigger reactivity.
reactive() don't have that problem because all items inside of it are reactive.
When you have two class attributes, the second one gets ignored. Combine your two class attributes into one;
<div>
<span :class="`float-left text-center w-24 inline-block flex-shrink-0 rounded-full px-2 py-0.5 text-xs font-medium text-white inset-x-0 top-0 ${reportStatus}`">
{{ dashboards.status }}
</span>
</div>
Your code should work fine, Just for a demo purpose I am using just an object for dashbaords variable but you can change it to an array as per your requirement.
Here is the live demo (Please have a look and try to find the root cause of the issue you are facing by referring this) :
const { ref, computed, onMounted } = Vue;
let options = {
setup: function () {
let dashboards = ref({});
const reportStatus = computed(function() {
return (dashboards.value.status === 'Production') ? 'status--production' : 'status--qa'
});
onMounted(function() {
dashboards.value = {
report: "Ad Hoc",
description: "",
status: "Production"
}
});
return {
dashboards,
reportStatus
};
}
};
let appVue = Vue
.createApp(options)
.mount('#app');
.status--production {
background-color: blue;
color: white;
border: 1px solid black;
}
.status--qa {
background-color: green;
color: white;
border: 1px solid black;
}
<script src="https://unpkg.com/vue#3.0.0-beta.14/dist/vue.global.js"></script>
<div id="app">
<span :class="reportStatus">
{{ dashboards.status }}
</span>
</div>

Changing one Vuejs v-model value is changing all other values

I have a Nuxtjs/Vuejs application within which I am creating multiple Nodes. These Nodes have the Radio button for which I have assigned v-model. However, when I change the value of one Vuejs v-model is affecting all other Node Values. Following is the code sample that I have created for the Node. The ID value is unique for each Node.
<template>
<div ref="el">
<div class="header">
Node: {{ ID }}
</div>
<div>
Syntax:
<input
id="identifierTypeURN"
v-model="identifierSyntax"
type="radio"
value="URN"
name="instanceIdentifierURN"
#change="instanceIdentifiersSyntaxChange('URN')"
>
<label for="identifierTypeURN">URN</label>
<input
id="identifierTypeWebURI"
v-model="identifierSyntax"
type="radio"
value="WebURI"
name="instanceIdentifierWebURI"
#change="instanceIdentifiersSyntaxChange('WebURI')"
>
<label for="identifierTypeWebURI">WebURI</label>
</div>
</div>
</template>
I am aware that this is happening because I am using the same v-model name for all the Nodes so I changed to something like this. But still the issue persists:
<template>
<div ref="el">
<div class="header">
Identifiers
Node: {{ ID }}
</div>
<div>
Syntax:
<div v-for="node in allNodeInfo" :key="node.identifiersId">
<div v-if="node.identifiersId === ID">
<input
id="identifierTypeURN"
v-model="node.identifierSyntax"
type="radio"
value="URN"
name="instanceIdentifierURN"
#change="instanceIdentifiersSyntaxChange('URN')"
>
<label for="identifierTypeURN">URN</label>
<input
id="identifierTypeWebURI"
v-model="node.identifierSyntax"
type="radio"
value="WebURI"
name="instanceIdentifierWebURI"
#change="instanceIdentifiersSyntaxChange('WebURI')"
>
<label for="identifierTypeWebURI">WebURI</label>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
ID: '',
nodeId: '',
eventCount: '',
bizStep: '',
allNodeInfo: [],
instanceIdentifierSyntax: ''
}
},
mounted () {
this.$nextTick(() => {
const id = this.$el.parentElement.parentElement.id
const data = this.$df.getNodeFromId(id.slice(5))
this.ID = data.data.ID
this.nodeId = data.data.nodeId
this.allNodeInfo = JSON.parse(JSON.stringify(this.$store.state.modules.ConfigureIdentifiersInfoStore.identifiersArray, null, 4))
const identifiersNode = this.allNodeInfo.find(node => node.identifiersId === this.nodeId)
this.instanceIdentifierSyntax = identifiersNode.identifierSyntax
console.log(JSON.stringify(this.allNodeInfo, null, 4))
})
},
methods: {
// On change of the IdentifierSyntax change, change the value in the respective node info
instanceIdentifiersSyntaxChange (syntaxValue) {
// Change the value of the respective syntax within the Node information in IdentifiersNode array
console.log(this.ID + " --- " + syntaxValue)
}
}
}
</script>
<style>
</style>
I know I am making some small mistake where I need to differentiate each Nodes V-model but nothing is clicking me. Can someone please help me.
You have to pass your node and your v-model also to your methods within this event.. like this, because for every loop in your v-for there will be a "new created node" which includes your v-model and than you can refer to this:
#change="instanceIdentifiersSyntaxChange('URN', node, identifierSyntax)"
In your methods you just do everything based on your node.identifierSyntax = HERE WHAT YOU WANT..
Hopefully I understood the question correct and helped you out!
EDIT: (Standard procedure)
Normally it looks like this when you add a single "input" to your v-for.
Template:
<div v-for="node in inputs" :key="node.id">
<input v-model="node.someDefinition" :value="node.someDefinition"/>
<div #change="another_Function(node, someDefinition)></div>
</div>
<button #click="add_new_Input>ADD</button>
Script:
data() {
return {
id: 0,
inputs: [{ //this is representing the first input when side will be loaded
id: this.id,
//your stuff in here as well
}]
}
}
methods: {
add_new_Input() {
this.inputs.push({
id: this.id += 1
})
}
}
This should be enough.. So in your template you have a v-for where your are looping over an array (or something else but in my case it's an array) with all inputs - be aware every input of me gets an unique ID when it will be created or added.
Also you have - in my case here - an input-tag where you can get the v-model or set the :value. It's binded to my unique node which I have created in my methods.
If you pass your #change you also have to pass the current node and the v-model / value that the correct one will be changed.
Hopefully it now helps you out!

How to give a dynamically rendered element it's own data value & target it in it's parent component?

I have a BaseMenuItem component that has normal button elements as well as special news elements.
I have added a ticker effect to the news type els and want to stop the ticker on that element when it's clicked. Currently the click event stops the ticker effect on the whole group.
How can I target a single element from that group?
There are two methods, openNews one showing the specific news article that the element is linked to.
And clearItemType that clears the itemType upon recieving the emitted event from the BaseMenuItem component.
I'm just not sure which element to target to change it's itemType.
Does Vuejs have a way to make an unique data value for dynamically generated elements?
If you need anymore information please let me know!
Cheers!
BaseMenuItem
<template>
<q-btn align="left" dense flat class="main-menu-item" v-on="$listeners">
<div class="flex no-wrap items-center full-width">
<iconz v-if="iconz" :name="iconz" type="pop" color="black" class="mr-md" />
<q-icon v-if="menuIcon" :name="menuIcon" class="text-black mr-md" />
<div #click="$emit('stop-ticker')" v-if="itemType === 'news'" class="ellipsis _ticker">
<div class="ellipsis _ticker-item">{{ title }}</div>
</div>
<div v-else>
<div class="ellipsis">{{ title }}</div>
</div>
<slot>
<div class="ml-auto"></div>
<div class="_subtitle mr-md" v-if="subtitle">{{ subtitle }}</div>
<q-icon name="keyboard_arrow_right" class="_right-side" />
<ComingSoon v-if="comingSoonShow" />
</slot>
</div>
</q-btn>
</template>
<style lang="sass" scoped>
// $
.main-menu-item
display: block
font-size: 15px
position: relative
width: 100%
border-bottom: 1px solid #F5F5F5
+py(10px)
._left-side
color: #000000
._subtitle
margin-left: auto
opacity: 0.7
._ticker
position: absolute
font-weight: bold
margin-left: 2em
width: 82%
&-item
display: inline-block
padding-left: 100%
animation: ticker 8s linear infinite
#keyframes ticker
to
transform: translateX(-100%)
</style>
<script>
import { iconz } from 'vue-iconz'
export default {
name: 'MainMenuItem',
components: { iconz },
props: {
comingSoonShow: { type: Boolean, default: false },
title: { type: String, default: 'menu' },
subtitle: { type: String, default: '' },
menuIcon: { type: String, default: '' },
iconz: { type: String, default: '' },
itemType: { type: String, default: '' },
}
}
</script>
MainMenuPage
<template>
<div class="eachMenuGroup" v-if="newsList.length">
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
:itemType="itemType"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
#stop-ticker="clearItemType"
></MainMenuItem>
</div>
</template>
<style lang="sass" scoped>
.readLink
font-weight: 500
</style>
<script>
methods: {
openNews(postInfo) {
dbAuthUser().merge({ seenNewsPosts: { [postInfo.id]: true } })
Browser.open({ url: postInfo.url, presentationStyle: 'popover' })
},
clearItemType() {
this.itemType = ''
return
},
</script>
EDIT: I edited since your latest comment. See below
To target the exact element within your v-for that fired the event, you can use $refs by using an index :
<MainMenuItem v-for="(item, index) in items" :ref="`menuItem--${index}`" #stop-ticker="clearItemType(index)" />
In your method:
clearItemType(index){
console.log(this.$refs[`menuItem--${index}`])
// this is the DOM el that fired the event
}
Edited version after your comments:
If you pass the exact same prop to each el of your v-for you wont be able to modify its value just for one child in the parent context.
Instead, keep an array of distinctive values in your parent. Each child will receive one specific prop that you can change accordingly in the parent either by listening to an child emitted event or by passing index as click event as I've done below for simplicity.
See snippet below
Vue.config.productionTip = false;
const Item = {
name: 'Item',
props: ['name', 'isActive'],
template: `<div>I am clicked {{ isActive }}!</div>`,
};
const App = new Vue({
el: '#root',
components: {
Item
},
data: {
items: ["item1", "item2"],
activeItemTypes: []
},
methods: {
toggle(index) {
this.activeItemTypes = this.activeItemTypes.includes(index) ? this.activeItemTypes.filter(i => i !== index) : [...this.activeItemTypes, index]
}
},
template: `
<div>
<Item v-for="(item, i) in items" :name="item" #click.native="toggle(i)" :isActive="activeItemTypes.includes(i)"/>
</div>
`,
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="root"></div>

Property or method "firstName" is not defined on the instance but referenced during render

I keep getting this warning for my inputs on my Signup.vue page. I have warning for all the four inputs in my code:
firstName
lastName
email
password
I keep getting this warning for each instance respectively.
[Vue warn]: Property or method "firstName" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
---> found in
---> <Register> at src/components/Register.vue
<App> at src/App.vue
<Root>
This is my Signup component
<template>
<div id="register">
<form>
<label for="firstName">First Name</label>
<input type="text" id="firstName" v-model="firstName"/>
<label for="lastName">Last Name</label>
<input type="text" id="lastName" #v-model="lastName"/>
<label for="email">Email</label>
<input type="text" id="email" v-model="email"/>
<label for="password">Password</label>
<input type="text" id="password" v-model="password"/>
<button type="submit">Register!</button>
</form>
</div>
</template>
<script>
export default {
data(){
return {
form: {
firstName: '',
lastName: '',
email: '',
password: ''
}
}
},
methods: {
//methid take two parameters input or local parameter and the value of it 'firstname'-john
updateForm (input, value){
this.form[input] = value
let storedForm = this.openStorage() //extract openstorage and place in storedform
if(!storedForm) storedForm = {} //if no data, then make empty object
storedForm[input] = value //store new value
this.saveStorage(storedForm) //save changes in saveStorage function which is connected to local storage
},
mounted() {
if (localStorage.name){
this.name = localStorage.name;
}
},
//opening local storage form
openStorage(){
return JSON.pasrse(localStorage.getItem('form'))
},
//saving local storage data
saveStorage(){
localStorage.setItem('form', JSON.stringify(form))
}
},
watch: {
name(newName){
localStorage.name = newName;
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
While rendering the view, Vue is unable to find the properties for each of your input v-models, since the fields are in the form object. You can either refer to them as form.firstName, form.lastName etc or move them out of the object in the data() section.

How to make reference just to one element from many? vuejs

Im learning vue and Im having this problem. I have about 25 buttons, The only thing that changes is label and ref. Each one looks like this:
<Button
icon="pi pi-check"
type="button"
label="Min"
class="p-button-text"
ref="Min"
#click="showActivatedButton"
v-bind:class="{ buttonActivated: buttonState }"
/>
In data I have this:
buttonState: false;
In methods I have this:
showActivatedButton() {
this.buttonState = !this.buttonState;
}
In style I have this:
.buttonActivated {
background: rgba(62, 70, 76, 0.16);
color: #3e464c;
border: 1px solid;
}
So the idea is once I click one of the buttons, this class .buttonActivated will be added just to THAT button, and if I click it the same button again, the class is removed. As you see it now, it adds and removes the class to every single button, no matter which one I clicked. Please Help :)
You need a separate reactive buttonState property for each button. Currently you only have one buttonState, which is shared between all the buttons. I would recommend creating a child component which you can reuse for each button, and declare buttonState inside the data() of the child component.
You could simplify like this:
<template>
<div>
<div
v-for="(button, index) in buttons"
:key="index"
>
<Button
icon="pi pi-check"
type="button"
:label="button.label"
class="p-button-text"
ref="button.ref"
#click="button.active = !button.active "
v-bind:class="{ buttonActivated: button.active }"
/>
</div>
</div>
</template>
<script>
export default {
data () {
return {
buttons: [
{ href: 'whatever', label: 'whatever', active: false },
{ href: 'whatever1', label: 'whatever1', active: false }
...add buttons
]
}
}
}
</script>
<style scoped>
.buttonActivated {
background: rgba(62, 70, 76, 0.16);
color: #3e464c;
border: 1px solid;
}
</style>