How to create a clickable anchor tag in VueJS with filtered/ transformed data - vue.js

I have a VueJS app that has the following style of (externally provided) data:
data: function() {
return {
posts: ['1:foo bar oof rab', '2:bar oof rab foo', '3:oof rab foo bar']
}
}
I want my template to loop through posts and make everything to the left of the semi-colon a clickable anchor tag:
<li><a id="1" href="#1">1</a>:foo bar oof rab</li>
<li><a id="2" href="#2">2</a>:bar oof rab foo</li>
<li><a id="3" href="#3">3</a>:oof rab foo bar</li>
Using a filter it's easy to split the text from the anchor position:
<template>
<div>
<v-for="post in posts">
<li>{{ post | trimAnchor }}:{{ post | trimPost }}</li>
</v-for>
</div>
</template>
filters: {
trimPost: function(value) {
value = value.toString();
return value.split(':')[1]
},
trimAnchor: function(value) {
value = value.toString();
return value.split(':')[0]
},
hashAnchor: function(value) {
value = value.toString();
return '#'+value.split(':')[0]
}
But filters don't work in v-bind or router-link:
<li>
<div :id="{{ post | hashAnchor }}">
<router-link="{{ post | hashAnchor }}">
{{ post | trimAnchor }}
</router-link>
</div>
:{{ post | trimPost }}
</li>
What is the correct approach to getting the output I'm after? Should I be using computed & if so, how?
Any help appreciated.

In these kind of cases I always recommend using a computed property. It keeps your template clean, and allows much freedom in preparing your data. Due to it being a computed property, it will automatically recalculate if your data were to change.
The first part is to create some object with all the necessary parts you require:
computed: {
anchors () {
if (!this.posts) {
return [];
}
return this.posts.map(
identifier => {
const [anchor, text] = identifier.split(':', 2);
return {
anchor,
text
}
}
)
}
}
Then you destructure it where you need it. I also added a key to your v-for, assuming that the first part is guaranteed to be unique. It would need to be if your anchors are going to work.
<template>
<div>
<v-for="{ anchor, text } in anchors" :key="anchor">
<li><a :href="`#${anchor}`">{{ anchor }}</a>:{{ text }}</li>
</v-for>
</div>
</template>
Of course you can use filters if needed on the link or li body if you need.

Related

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 search within nested objects

I have done my research trying to figure out how to achieve what I am describing below, however I had no luck.
In my Algolia index, some records have nested objects.
For example, title and subtitle attributes are of the following format:
title:
{
"en": "English title",
"gr": "Greek title"
}
I would like to execute queries only for a specific subset (in our example "en" or "gr") of these attributes, withoute "exposing" any facet in the UI — language selection would ideally be done “automatically” based on a variable (lang) passed to the Vue component with props. I am using Laravel Scout package with default Vue implementation, as described in documentation here.
My InstantSearch implementation is pretty simple, I am not defining anything specific regarding queries and searchable attributes, I am currently using all the default functionality of Algolia.
<template>
<ais-instant-search
:search-client="searchClient"
index-name="posts_index"
>
<div class="search-box">
<ais-search-box placeholder="Search posts..."></ais-search-box>
</div>
<ais-hits>
<template
slot="item"
slot-scope="{ item }"
>
<div class="list-image">
<img :src="'/images/' + item.image" />
</div>
<div class="list-text">
<h2">
{{ item.title }}
</h2>
<h3>
{{ item.subtitle }}
</h3>
</div>
</template>
</ais-hits>
</ais-instant-search>
</template>
<script>
import algoliasearch from 'algoliasearch/lite';
export default {
data() {
return {
searchClient: algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_SEARCH
),
route: route,
};
},
props: ['lang'],
computed: {
computedItem() {
// computed_item = this.item;
}
}
};
</script>
I would like to somehow pass an option to query “title.en” and “subtitle.en” when variable lang is set to “en”. All this, without the user having to select “title.en” or “subtitle.en” in the UI.
Update
Maybe computed properties is the path to go, however I cannot find how to reference search results/hits attributes (eg item.title) within computed property. It is the code I have commented out.
I think, you can use computed property. Just transform current item according to the current language variable.
new Vue({
template: "<div>{{ computedItem.title }}</div>",
data: {
langFromCookie: "en",
item: {
title: {
en: "Hello",
ru: "Привет"
}
}
},
computed: {
computedItem() {
const item = JSON.parse(JSON.stringify(this.item));
for (value in item) {
if (typeof item[value] === "object" && Object.keys(item[value]).includes(this.langFromCookie))
item[value] = item[value][this.langFromCookie];
}
return item;
}
}
}).$mount("#app")
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
If lang variable is available via props, you can check that inside list-text class and return {{title.en}} or {{title.gr}} accordingly by passing a dynamic lang value title[lang] like below
...
<div class="list-text">
<h2>
{{ item.title[lang] }}
</h2>
<h3>
{{ item.subtitle[lang] }}
</h3>
</div>
If you want to make a request according to lang prop when component mounts ,then you can make a request inside mounted() method then query like below
mounted() {
axios.get(`/getSomethingWithLang/:${this.item.title[this.lang]}`)
...
}

Conditional link behavior in VueJS

Couldn't find a proper name for the title, will be glad if someone figures out a better name.
I have a component which represents a product card. The whole component is wrapped in <router-link> which leads to product page.
However I have another case, when I do not need the component to lead to a product page, but instead I need to do some other action.
The only solution I found is to pass a callback function as a prop, and based on this, do something like:
<router-link v-if="!onClickCallback">
... here goes the whole component template ...
</router-link>
<div v-if="onClickCallback" #click="onClickCallback">
... here again goes the whole component template ...
</div>
How can I do this without copy-pasting the whole component? I tried to do this (real code sample):
<router-link class="clothing-item-card-preview"
:class="classes"
:style="previewStyle"
:to="{ name: 'clothingItem', params: { id: this.clothingItem.id }}"
v-on="{ click: onClick ? onClick : null }">
However I got this: Invalid handler for event "click": got null
Plus not sure if it's possible to pass prevent modificator for click and this just looks weird, there should be a better architectural solution
Commenting on the error, you could use an empty function instead of null, in the real code snippet
<router-link class="clothing-item-card-preview"
:class="classes"
:style="previewStyle"
:to="{ name: 'clothingItem', params: { id: this.clothingItem.id }}"
v-on="{ click: onClick ? onClick : null }">
This should works (replace a for "router-link" then insert right properties)
Further infos :
https://fr.vuejs.org/v2/guide/components-dynamic-async.html
v-bind is simply an Object where each keys is a props for your component, so here, I programmatically defined an object of properties depending on the wrapper (router link or a simple div). However we cannot do this for events (of course we could create our own event listener but it's a little bit tricky) so I simply but an handle method.
new Vue({
el: "#app",
data: {
products : [{onClickCallback : () => { alert("callback"); return true;}}, {}, {}]
},
methods : {
handleClick(product, event) {
if (!product.onClickCallback) return false
product.onClickCallback()
return true
},
getMyComponentName(product) {
if (product.onClickCallback) return "div"
return "a"
},
getMyComponentProperties(product) {
if (product.onClickCallback) return {is : "div"}
return {
is : "a",
href: "!#"
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<component
v-for="(product, index) in products"
:key="index"
v-bind="getMyComponentProperties(product)"
#click="handleClick(product, $event)"
>
<div class="product-card">
<div class="product-card-content">
<span v-show="product.onClickCallback">I'm a callback</span>
<span v-show="!product.onClickCallback">I'm a router link</span>
</div>
</div>
</component>
</div>
Do you have to use a <router-link>? If it can safely be a <div>, you could use something like
<div #click="handleClick" ...>
<!-- component template -->
</div>
and
methods: {
handleClick (event) {
if (this.onClickCallback) {
this.onClickCallback(event)
} else {
this.$router.push({ name: 'clothingItem', ... })
}
}
}
See https://router.vuejs.org/guide/essentials/navigation.html

How to handle when VueJS computed properties return HTML code?

I use VueJS 2 to render and calculate form items. Now I need to show a number if a propertie is under 10, and I need show a text message if the propertie is over or equal 10.
I use this code:
Vue.component('mycomponent', {
template: '#mytemp',
data: function() {
// ...
},
computed: {
mycomputedprop: function() {
if (this.model_a < 10) {
return '<span class="numbervalue">' + this.model_a + '€</span>';
} else {
return '<span class="textvalue">I\'ll contact you as soon as possible!</span>';
}
}
}
});
I use this code to show the value:
<div id="app">
{{ mycomputedprop }}
</div>
The problem is: if I show this value it shows the HTML code as text, not as HTML. How can I show the returned value as a HTML code?
You could use v-html
Document : Raw-HTML
<div id="app">
<div v-html="mycomputedprop"></div>
</div>
The contents of this div will be replaced with the value of the
rawHtml property, interpreted as plain HTML - data bindings are
ignored. Note that you cannot use v-html to compose template partials,
because Vue is not a string-based templating engine. Instead,
components are preferred as the fundamental unit for UI reuse and
composition.
Vue 3 Example:
const RenderHtmlApp = {
data() {
return {
rawHtml: '<span style="color: red">This should be red.</span>'
}
}
}
Vue.createApp(RenderHtmlApp).mount('#example1')
<script src="https://unpkg.com/vue#next"></script>
<div id="example1">
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
</div>
Assuming that modal_a is defined in the data of your component, why not handle this within the component template?
<div id="app">
<span v-if="model_a < 10" class="numbervalue">{{model_a}} €</span>
<span v-else class="textvalue">I\'ll contact you as soon as possible!</span>
</div>

Vuejs v-for set unique data

I have a component that I can add when I click on the button.
<button type="submit" #click="components ++">add select box</button>
<div v-for="component in components">
<select class="selectpicker form-control" v-model="customized_exercise.name" value={{exercise.id}}>
<option v-for="exercise in exercises">{{ exercise.name }}</option>
</select>
</div>
In this template when I add a couple of them and select a value,all the other components (select box) update with the same value.How can I make them have a unique value?
Vue
import ExerciseSelectbox from './ExerciseSelectbox.vue'
export default {
methods: {
fetchexercises: function(){
this.$http.get('/api/exerciseinstructions').then(function (data) {
this.$set('exercises',data['data'])
})
},
},
comoponents: { ExerciseSelectbox },
data() {
return{
components: 1,
newCustomizedExercise : {
id:'',
name:'',
reps:'',
sets_duration:'',
weight:'',
},
numbers:[100]
}
},
ready() {
this.fetchexercises()
}
}
This is old, but I was also looking for this answer. I found that the Vue documentation mentions this:
Inside v-for blocks we have full access to parent scope properties.
v-for also supports an optional second argument for the index of the
current item.
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
So you can use this index parameter to set the id of each element in your loop to keep each one separate.
Or you can set the set your model to a name in the array. Something like v-model="customized_exercise[index].name". But I haven't actually tried this option.
I found another SO answer about how to use v-bind if you want to string concatenate the index variable in a data attribute.
<a v-bind:href="'uniqueName' + index">12312</a>