How to dynamically add attributes to elements in vue.js? - vue.js

Given an items array of objects:
items: [{
label: 'My Link',
attrs: {
href: '/route',
target: '_blank',
title: 'Some title',
},
},{
label: 'My Link 2',
attrs: {
href: '/route2',
target: '_self',
title: 'Some title 2',
},
}];
My vue has a v-for that loops through a list of items:
<ul>
<li v-for="item in items">
<a ADD_ITEM_ATTRIBUTES_HERE>{{ item.label }}</a>
</li>
</ul>
How can I loop through each item's attribute and dynamically add it to the anchor element where the Attribute name is the object key and the attribute value is its matching value?
I'm coming from jQuery world where this kind of manipulation is pretty straight forward, but I'm not sure how to modify the DOM in this case.
Most questions I find here have to do with checking if an element has a prop or meets a condition to set its value. I'm not trying to do that. In my case I don't know what the prop will be nor its value.

Since you already have attributes in an object, you can directly pass it to v-bind; Also see bind an object of attributes example:
<a v-bind="item.attrs">{{ item.label }}</a>
This will result in keys in attrs object as attributes and values in attrs as corresponding values.

Related

Vue v-model on custom radio buttons

I've created a custom radio button component in Vue. The component is set up to take these props: label (string), value (string), checked (boolean), and a vModelValue which serves as the v-model value that will be used on the component. I set the internal reference to the v-model value to be prop: vModelValue and event: 'change'.
I have a computed value called local_vModelValue which gets the vModelValue prop sent down in the v-model on the component and sets it to the v-model internally.
This works correctly as is, except for one problem. Accessibility isn't working correctly. When I use voice over controls, and I have two distinct radio groups made up of three buttons each, it will identify the selected button as 1 of 6 even though it should be 1 of 3. It sees all 6 buttons on the page and acts as if there is one group.
To fix this, I want to put a name attribute in my component in the underlying logic, and I set up a computed property to check if there is a vModelValue. If there is, it sets the name to that vModelValue (or it SHOULD do so). I don't want to have to send a name down as a prop at this point. I want it to just use the vModelValue as the name. (Later I will check if there is a name attribute prop on the component and then it will use that as the name but for now I'm just trying to get it work with the vModelValue as the name.)
The problem is it just won't set the name to that vModelValue coming in.
Here is the component:
CustomRadioButtons.vue
<template>
<div>
<label
tabindex="0"
>
<input
type="radio"
:checked="checked"
v-model="local_vModelValue"
:value="value"
:name="getNameValue"
#change="onChange"
>
<span>
{{ label }}
</span>
</label>
</div>
</template>
<script>
export default {
name: 'CustomRadioButtons',
model: {
prop: 'vModelValue',
event: 'change'
},
methods: {
onChange(event) {
this.$emit('change', event.target.value, this.label, this.name, this.vModelValue)
},
},
props: {
vModelValue: {
type: String,
default: ''
},
label: String,
value: String,
name: {
type: String,
default: ''
},
checked: {
type: Boolean,
default: false
}
},
computed: {
local_vModelValue: {
get(){
return this.vModelValue;
},
set(value) {
this.$emit('change', value)
}
},
getNameValue() {
return this.vModelValue.length > 0 ? this.vModelValue : this.name
}
},
watch: {
vModelValue:{
immediate: true,
handler(){
console.log(this.vModelValue, this.checked, this.name)
}
}
},
}
</script>
App.vue
<template>
<div id="app">
<h3>Custom Radio Buttons 1</h3>
<div v-for="(button, i) in buttons" :key="'buttons'+i">
<CustomRadioButtons :label="button.label" :value="button.value" :checked="true" v-model="cat"></CustomRadioButtons>
</div>
<h3>Custom Radio Buttons 2</h3>
<div v-for="(button, i) in otherButtons" :key="'otherbuttons'+i">
<CustomRadioButtons :label="button.label" :value="button.value" :checked="true" v-model="dog"></CustomRadioButtons>
</div>
</div>
</template>
<script>
import CustomRadioButtons from "#/components/CustomRadioButtons"
export default {
name: 'App',
components: {
CustomRadioButtons
},
data(){
return {
cat: 'cat',
dog: 'dog',
buttons: [{label: 'label 1', value: 'value 1', name: 'name1'}, {label: 'label 2', value: 'value 2', name: 'name1'}, {label: 'label 3', value: 'value 3', name: 'name1'}],
otherButtons: [{label: 'test 1', value: 'value 1', name: 'name2'}, {label: 'test 2', value: 'value 2', name: 'name2'}, {label: 'test 3', value: 'value 3', name: 'name2'}],
}
},
props: ['value'],
}
</script>
Using the computed value of getNameValue causes the whole thing to work very strangely and I never see the name get updated to the vModelValue.
I have an answer now to the name attribute issue. The name attribute WAS getting updated with the vModelValue. However, since I was trying to log this.name in the console to see that value change, I couldn't actually see that anything was being changed. That's because this.name refers to the 'name' prop I set up in my component. But I wasn't passing a name prop down, so the name prop continued to default to an empty string.
If I take the name prop definition out, the name shows up as undefined in the console. If I move 'name' to the data object and make it a data property, it still shows up as an empty string in the console. I need to figure out why that is the case. But at least the name attribute in the DOM updates as it should.
One issue that comes up is that the buttons don't work right if the two radio groups have identical values for any of the corresponding buttons. Since I'm setting the name to the vModelValue, and the vModelValue is the value of which ever button is currently selected, if the two separate radio groups have a matching value for their selected buttons, the name becomes identical and then the groups are seen as one group. This is a problem!

How can I add an href to a q-table (Quasar Data Table)?

I have put a q-table in my page to show my data. this is work correctly. I want to put an href in first column and another in last column. You should can click on them to go to other pages for showing details row and edit row Respectively.
Title: go to page /#/offer/uuid
Edit: go to page /#/account/offers/edit/uuid
<template>
<q-page>
<div class="row justify-center">
<q-table
:data="data"
:columns="columns"
row-key="id"
selection="single"
:selected.sync="selected"
:filter="filter"
:loading="loading"
>
</q-table>
</div>
</div>
</q-page>
</template>
<script>
.....
columns: [
{
name: 'title',
required: true,
label: 'Title',
align: 'left',
field: row => row.title,
sortable: true,
format: val => '' + val + '' //this is not work
},
{
name: 'category',
label: 'Category',
field: 'category',
sortable: true
},
{
name: 'payment',
label: 'Payment',
field: 'payment'
},
{
name: 'action',
label: 'Edit',
field: 'key',
format: val => '' + val + '' //this is not work
}
]
.....
</script>
This answer assumes Quasar 1.0.0-rc4. For earlier versions it may look a bit different but the crux of it is that you need to use scoped slots.
There are scoped slots called body-cell-[name] that can be used to render content that isn't plain text. Here the [name] portion should match the name for the corresponding column in your columns definition.
<q-table ...>
<template v-slot:body-cell-title="cellProperties">
<q-td :props="cellProperties">
{{ cellProperties.value }}
</q-td>
</template>
<template v-slot:body-cell-action="cellProperties">
<q-td :props="cellProperties">
{{ cellProperties.value }}
</q-td>
</template>
</q-table>
My use of v-slot assumes Vue 2.6.0+, for earlier versions you would use slot and slot-scope instead.
You haven't explained where the UUID part of your URL comes from so I haven't included that in the code above. I would imagine it is included somewhere in the row data, so in practice you would need something like :href="'#/offer/' + encodeURIComponent(cellProperties.row.uuid)".
If you're using a routing library such as Vue Router then there are alternatives to building URLs directly within <a> tags. You may wish to investigate further before sprinkling hand-crafted URLs throughout your application code. However, the use of scoped slots is likely to remain no matter how you implement your links.
Use :href. For example:
<a :href="'/app/product/'+props.row.productId">{{ props.row.name }}</a>

Using Slots or slot-scopes in v-for loops to access properties?

I'm having a difficult time understanding slots for some reason and why they should even be used. The only reason I can think of that would be nice for usuage is if we can reference specific properties within a v-for loop of an element and output different templates quicker perhaps...
So, am thinking, and possibly I could be wrong in thinking this, but if I have a variable like so:
<script>
const items: [
{
label: 'My Label',
url: '#',
headerTitle: 'My Header Title'
},
{
label: 'My Label 2',
url: 'https://www.myurl.com',
headerTitle: 'My Header Title 2'
},
{
label: 'My Label 3',
url: 'https://www.myurl3.com'
}
]
export default {
data () {
return {
items: items
}
}
}
</script>
And than in the template, possibly this:
<template>
<div v-for="(item, index) in items" :key="item.id">
<template slot-scope="headerTitle">
<h1>{{ item.headerTitle }}</h1>
</template>
<template slot-scope="label">
<div class="mylabel">
{{ item.label }}
</div>
</template>
<template slot-scope="url">
<a :href="item.url">{{ item.label }}</a>
</template>
</div>
</template>
I don't know if this makes sense or not, but basically using the property as a slot-scope and than for everytime that property is defined, it will output something. But this doesn't work properly. Is this not what slot-scopes are for within component v-for loops? Is this not how to use these properties of an array of objects?
This kinda makes sense to me. Anyways to do it like this perhaps?

Vue js how render curly brackets variables with in v-for loop

in my data object
items: [
{ name: "Breakfast", comp: "breakfastItems" },
{ name: "Lunch", comp: "lunchItems" },
{ name: "Dinner", comp: "dinnerItems" },
{ name: "Dessert", comp: "desertItems" }
]
where comp is a computed property.
in my component template I want to achive something like this using a for loop.
<span v-for="n in items">
{{n.comp}}
</span>
this doesn't work because I need to add {{}} when it's rendering. How do I do that?
To bind computed properties inside your template via dynamic interpolation, you can use the $root variable.
Assuming that the comp properties you've listed are collections underneath, the template might look like:
<span v-for="n in items">
<span v-for="m in $root[n.comp]">{{ m }}</span>
</span>
Here's a demonstration of the suggestion.

Drop down/Sub-menus in KeystoneJS

I'm currently in the process of developing a website. As part of the requirements, I need to include dropdown menu/sub menu from some of the main menu items. I'm able to create main menu items with KeystoneJS but I can't seem to find a tutorial on how to implement sub-menu items. How should I go about it?
Your question is a little unclear, but I'm assuming you're talking about updating the navigation bar that you get given after running the generator, and not about the admin UI itself?
If so, it will depend on what templating engine you use. I've done this myself with the handlebars template engine. I just added a locals.subsection, similar to locals.section.
I then updated routes/middleware to look like this:
locals.navLinks = [
{ label: 'Home', key: 'home', href: '/' },
{ label: 'About Us', key: 'about', pages: [
{ label: 'What We Do', subkey: 'whatwedo', href: "/whatwedo" },
{ label: 'Our Journey', subkey: 'journey', href: "/journey" }
] },
{ label: 'Blog', key: 'blog', href: '/blog' }
];
where in the above example, the "About Us" menu item will be a drop down, and the other two will not be. Then on the routes for your individual pages, you'll need to specify the section, and also the subsection if you want it to be a dropdown. In the above example, the whatwedo route has locals.section: about and locals.subsection: whatwedo.
You'll then need to update your default layout. For me, it's written in handlebars, and so looked like this:
{{# each navLinks}}
{{#if href}}
<li {{#ifeq ../../section key}}class="active"{{/ifeq}}>
{{ label }}
</li>
{{else}}
<li class="dropdown{{#ifeq ../../section key}} active{{/ifeq}}">
{{ label }} <span class="caret"></span>
<ul class="dropdown-menu">
{{#each pages}}
<li {{#ifeq ../../../subsection subkey}}class="active"{{/ifeq}}>
{{ label }}
</li>
{{/each}}
</ul>
</li>
{{/if}}
{{/each}}
I realise you are probably using jade rather than handlebars, but hopefully you'll be able to 'translate' this code.
Apologies if I've misinterpreted your question. Hope this helps.