I have a paragraph in my app, in which the user can translate it at the click of a button. I interpolate the text seen via my props (props.row.text) and my data (this.data.translatedText).
The data property is empty/null until the button is cliked, which therefore causes me to receive the error "data is null" in my Console. I attempt to fix this with the code below, however, this throws me error but does not display the interpolated text after the button click.
Does anyone have a suggestion for how best to structure this, so that the {{ props.row.text }} is replaced by {{ this.data.translatedText }} after the button is clicked?
Component.vue
<span id="translationButton">
<translation-button #click="calltranslatedText()" #changeTitle="ChangeT" />
</span>
<div class="ticket-text-container">{{ props.row.text }}</div>
<div class="ticket-text-container" v-if="calltranslatedText()">{{ this.data.translatedText }}</div>
methods: {
ChangeT(title)
{
this.translatedText = title
},
calltranslatedText() {
return true
},
Firstly don't use this and data in the template just indicate a prop name you defined in data function.
Secondly, you can just use translatedText in v-if to show div with the translated text.
<div class="ticket-text-container" v-if="translatedText">{{ translatedText }}</div>
Don't forget to set translatedText to an empty string or null to hide this block again if needed.
Related
What or where is individual id or any other element that I can call to run a function on component.
Example: using add button I'm dynamically creating colored squares. Each square has close button.
Then I want to delete one of squares, let's say the blue one (third child of template).
v-bind:id='var' doesn't works because then all squares have the same id.
You can use ref, you can define ref in every added component. The ref is array. you can refrence component through index of the ref.
for example;<div v-for="(item) in arr" :key="item.id"> <span ref="testRef"> item.name </span> </div>, you can use 'this.$refs.testRef[index]' find component which you want.
There are multiple options. For instance, in your v-for loop you can get the index this way: (official docs)
<div v-for="(item, index) in myList" :key="index">
{{ item.name }}
</div>
You could then for example use the index in a ref to access the element for the script.
Another option is to pass the event instance to the onclick listener with the special keyword $event, and use this to get the element:
<div ... #click="myFunction($event)">
{{ item.name }}
</div>
And then in your script
myFunction(ev) {
// do something with the element that was clicked on
console.log(ev.target)
}
However, a more 'vue' way would be to not mess with the elements, but only manipulate the underlying data and let vue worry about representing the data with elements on your page. For instance, you could make your squares be represented by a list of objects, containing their own data own the state (opened or closed). So in your script you have:
data() {
return {
squares: [
{ color: "ff0000", opened: true },
...
]
}
},
And in your template something like
<div
v-for="square in squares"
v-show="suare.opened"
#click="square.opened = false"
> ...
This is the first time I am using modal component. Inside a for loop of an array of objects, I also added a modal component, "Add Item". The v:onClick="showSectionID" function in the SHOW button within the modal should just consolelog the id of the object who's associated modal was opened and click it's respective SHOW button. But instead it is giving the id of the last object wherever I click the SHOW button from any of associated modals.
Just to test, I removed the whole modal and only kept the SHOW button and in this case it gives me the correct id. I really cannot figure out what s is wrong in the modal and searched several sources in the internet to see a similar solution but couldn't find. See code:
<div v-for="(section) in allDataObject['Section']" :key="section['uuid']">
<h4>Section Name: {{ section["sectionName"] }}</h4>
<h4>Section Description: {{ section["sectionDescription"] }}</h4>
<template>
<div>
<b-button #click="modalShow = !modalShow">Add Item</b-button>
<b-modal v-model="modalShow">Fill form to add an item !
<button v-on:click="showSectionID (section['uuid'])">SHOW</button>
</b-modal>
</div>
</template>
</div>
In your code, you are creating a modal component for each section within the for loop.
I wouldn't be surprised if actually all your modals show up on the screen, but you see the last one because it's on top of all the other ones. But it also depends on how the modal is implemented.
I suggest you to move the modal template outside your for loop and change what you store in your component data so that you know which section to show in the modal.
Let's say your data() will look like this:
data() {
return {
modalVisible: false,
modalSectionUUID: null
}
}
Then you can create two methods to show and hide the modal:
methods: {
showModal(sectionUUID) {
this.modalVisible = true;
this.modalSectionUUID = sectionUUID;
},
hideModal() {
this.modalVisible = false;
this.modalSectionUUID = null;
}
}
Now, your template will finally look something like this:
<b-modal v-model="modalVisible">Fill form to add an item !
<button v-on:click="showSectionID(modalSectionUUID)">SHOW</button>
</b-modal>
<div v-for="(section) in allDataObject['Section']" :key="section['uuid']">
<h4>Section Name: {{ section["sectionName"] }}</h4>
<h4>Section Description: {{ section["sectionDescription"] }}</h4>
<template>
<div>
<b-button #click="showModal(section['uuid'])">Add Item</b-button>
</div>
</template>
</div>
I'm trying to customize an out-of-the-box form in Vue.js where inputs are shown/hidden depending on the selection of 2 radio buttons:
<b-form-group label-class="ssrv-form-control">
<div class="ssrv-5">
<b-form-radio v-model="isOperator" name="operatorRad" value="false">Consultant</b-form-radio>
</div>
<div class="ssrv-0">
OR
</div>
<div class="ssrv-1 rad">
<b-form-radio v-model="isOperator" name="operatorRad" value="true">{{ userDetails.operator.description }}</b-form-radio>
</div>
</b-form-group>
I have defined isOperator in the data (am I defining data correctly? I'm trying to modify the out-of-the-box code, not sure what this means):
export default {
name: 'User-Details',
components: {...},
props: {...},
data () {
let data = {
...
isOperator: true,
...
};
and I'm trying to make this show/hide a button and input fields. I'm starting with the button as it seems simpler:
<b-button v-show="isOperator === true" #click="save" :block="true" size="lg" variant="primary" active-class="ssrv-form-button" class="ssrv-form-button">
{{$t("common.form.signUp")}}
</b-button>
My current problem, is the button isn't showing/hiding based on the two radio buttons. If I make isOperator: true in the data, the page loads with the 2nd radio button selected and the button showing. When I click the second radio button, it disappears. But then when I click the original radio button again, the button doesn't show back up. I get the same result when I try to show/hide an input field, I can get it to show initially by setting isOperator to true, but then when I select the other radio button to make it disappear I can't make it appear again. If isOperator is set to false, it just never shows.
I put a isOperator is {{ isOperator }} p element and I can see the value is change true/false as expected, but the buttons/inputs aren't showing back up.
From my very limited understanding of Vue.js, I set the v-model to a variable I want an element to modify, and the value what that variable will be set to when the radio button is selected. Then on a separate element I want to show/hide, I can use v-if/v-show with "myvalue === true/false" to show/hide. Is this an oversimplification and I'm missing steps?
That's because of a mismatch in the type of isOperator property. When you first mount the component the value of isOperator is a boolean (true), and then later on when you click on the radio buttons it becomes a string. You need to adjust the value property in your template as below:
<b-form-group label-class="ssrv-form-control">
<div class="ssrv-5">
<b-form-radio v-model="isOperator" name="operatorRad" :value="false">Consultant</b-form-radio>
</div>
<div class="ssrv-0">
OR
</div>
<div class="ssrv-1 rad">
<b-form-radio v-model="isOperator" name="operatorRad" :value="true">{{ userDetails.operator.description }}</b-form-radio>
</div>
</b-form-group>
I'm making a link/button component which either can have a button or an anchor wrapper, a text and an optional icon. My template code below is currently rendering either an anchor or a button (with the exact same content) based on an if statement on the wrapper element, resulting in duplicate code.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</button>
</template>
Is there a more clean way for wrapping my buttonText and icon inside either an anchor or button?
I've solved my issue by intensive Google-ing! Found this issue regarding Vue on Github which pointed me in the right direction.
Small piece of backstory
I'm using Vue in combination with Storybook to build a component library in which a button can either be a button or an anchor. All buttons look alike (apart from color) and can be used for submitting or linking. To keep my folder structure ordered, I would like a solution that generates a multiple buttons types (with or without link) from one single file.
Solution
Using computed properties I'm able to "calculate" the necessary tag, based on the url property of my component. When a url is passed, I know that my button has to link to another page. If there is no url property it should submit something or preform a custom click handler (not in the sample code below).
I've created the returnComponentTag computed property to avoid placing any complex or bulky logic (like my original solution) in my template. This returns either an a or a button tag based on the existence of the url property.
Next, as suggested by ajobi, using the :is attribute I'm able to define the component tag based on the result of my computed property. Below a stripped sample of my final (and working) solution:
<template>
<component :is="returnComponentTag" v-bind:href="url ? url : ''" class="btn" :class="modifier" :id="id">
{{buttonText}}
</component>
</template>
<script>
export default {
name: "Button",
props: {
id: {
type: Number
},
buttonText: {
type: String,
required: true,
default: "Button"
},
modifier: {
type: String,
default: "btn-cta-01"
},
url: {
type: String,
default: ""
}
},
computed: {
returnComponentTag() {
return this.url ? "a" : "button"
}
}
};
</script>
You could extract the wrapping element into a dedicated component.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
<slot></slot>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
<slot></slot>
</button>
</template>
// You would use it like this
<SomeComponent /* your props here */ >
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</SomeComponent>
There are multiple ways of doing this. Two examples would be the following based on the point of view:
You are defining two different components (Button or Anchor) and want to use a wrapper to render either one of them.
You could seperate the Wrapper Content into two components so that the wrapper only decides on which of the components to render (either the Button or the Anchor).
The problem with this approach could be you will have doubled code for methods and styling for the button and anchor component.
You are defining the content as a component and use the wrapper to define what to wrap the content in.
See Answer of https://stackoverflow.com/a/60052780/11930769
It would be great to know, why you would want to achive this. Maybe there are better solutions for your usecase. Cheers!
I have prepared tag input control in Vue with tag grouping. Templates includes:
<script type="text/x-template" id="tem_vtags">
<div class="v-tags">
<ul>
<li v-for="(item, index) in model.items" :key="index" :data-group="getGroupName(item)"><div :data-value="item"><span v-if="typeof model.tagRenderer != 'function'">{{ item }}</span><span v-if="typeof model.tagRenderer == 'function'" v-html="tagRender(item)"></span></div><div data-remove="" #click="remove(index)">x</div></li>
</ul>
<textarea v-model="input" placeholder="type value and hit enter" #keydown="inputKeydown($event,input)"></textarea>
<button v-on:click="add(input)">Apply</button>
</div>
</script>
I have defined component method called .getGroupName() which relays on other function called .qualifier() that can be set over props.
My problem: once I add any tags to collection (.items) when i type anything into textarea for each keydown .getGroupName() seems to be called. It looks like entering anything to textarea results all component rerender?
Do you know how to avoid this behavior? I expect .getGroupName to be called only when new tag is added.
Heres the full code:
https://codepen.io/anon/pen/bKOJjo?editors=1011 (i have placed debugger; to catch when runtime enters .qualifier().
Any help appriciated.
It Man
TL;DR;
You can't, what you can do is optimize to reduce function calls.
the redraws are dynamic, triggered by data change. because you have functions (v-model and #keydown) you will update the data. The issue is that when you call a function: :data-group="getGroupName(item)" it will execute every time, because it makes no assumptions on what data may have changed.
One way of dealing with is is setting groupName as a computed key-val object that you can look up without calling the function. Then you can use :data-group="getGroupName[item]" without calling the function on redraw. The same should be done for v-html="tagRender(item)"
Instead of trying to fight how the framework handles data input events and rendering, instead use it to your advantage:
new Vue({
el: '#app',
template: '#example',
data() {
return {
someInput: '',
someInputStore: []
};
},
methods: {
add() {
if (this.someInputStore.indexOf(this.someInput) === -1) {
this.someInputStore.push(this.someInput);
this.someInput = '';
}
},
}
});
<html>
<body>
<div id="app"></div>
<template id="example">
<div>
<textarea v-model="someInput" #keyup.enter.exact="add" #keyup.shift.enter=""></textarea>
<button #click="add">click to add new input</button>
<div>
{{ someInputStore }}
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</body>
</html>
Event modifiers modifying
In the example, you can see that I am using 4 different event modifiers in order to achieve the desired outcome, but I will focus on the combination of them here:
#keyup.enter.exact - allows control of the exact combination of system modifiers needed to trigger an event. In this case, we are looking for the enter button.
#keyup.shift.enter - this is the interesting bit. Instead of trying to hackily prevent the framework from firing on both events, we can use this (and a blank value) to prevent the event we added into #keyup.enter.exact from firing. I must note that ="" is critical to the whole setup working properly. Without it, you aren't giving vue an alternative to firing the add method, as shown by my example.
I hope this helps!