VueJS - Auto create a A-Z letters list from the data - vue.js

Is it possible to create a A-Z letters list (like this) from the data from a API and Vue to be able to determine if a property in a data contains a name that starts with what letter. If the data doesn't contain a specific letter name then remove/disable the href attribute from the letter anchor tag.
In the linked example, letters K, X and Z are missing coz they don't have the data
JSON
[
{
"id": 77,
"link": "http://my-site/cosmoquotes/authors/anonymous/",
"name": "Anonymous",
"slug": "anonymous"
},
{
"id": 72,
"link": "http://my-site/authors/ferdinand-marcos/",
"name": "Ferdinand Marcos",
"slug": "ferdinand-marcos"
},
{
"id": 75,
"link": "http://my-site/authors/john-f-kennedy/",
"name": "John F. Kennedy",
"slug": "john-f-kennedy"
},
{
"id": 67,
"link": "http://my-site/authors/john-maxwell/",
"name": "John Maxwell",
"slug": "john-maxwell"
}
]
Component
export default {
data() {
return {
authorsRequest: {
type: 'authors',
params: {
per_page: 100
}
},
}
},
computed: {
authors () {
return this.$store.getters.requestedItems(this.authorsRequest)
},
},
methods: {
getAuthors() {
return this.$store.dispatch('getItems', this.authorsRequest)
},
},
created() {
this.getAuthors()
}
}
So as per the returned data, only the letters 'A', 'F' and 'J' should be clickable/displayed.

I managed to do it like this,
unfortunatly it needs the authors array and the conditionnal function to be outside of the Vue component because I couldn't find how to pass argument to computed values
But since I'm new to vue (didn't even finish reading the introduction) I'm sure there has to be a better solution
EDIT: found the way to have the function in the component with methods, I could then move the data in the component too
let a = new Vue({
el: "#selector",
data: {
authors: [{"id": 77,"link": "http://my-site/cosmoquotes/authors/anonymous/","name": "Anonymous","slug": "anonymous"},{"id": 72,"link": "http://my-site/authors/ferdinand-marcos/","name": "Ferdinand Marcos","slug": "ferdinand-marcos"},{"id": 75,"link": "http://my-site/authors/john-f-kennedy/","name": "John F. Kennedy","slug": "john-f-kennedy"},{"id": 67,"link": "http://my-site/authors/john-maxwell/","name": "John Maxwell","slug": "john-maxwell"}]
},
computed: {
// there have to be a way to get this array without doing it like this but I don't know it ^^
letters() {
let letters = []
for(let i = "A".charCodeAt(0); i <= "Z".charCodeAt(0); i++) {letters.push(String.fromCharCode([i]))}
return letters
}
},
methods: {
// you may add a toUpperCase()/toLowerCase() if you're not sure of the capitalisation of you datas
isALink(letter) {
return this.authors.some(aut => aut.name.startsWith(letter))
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="selector">
<template v-for="letter in letters">
<a v-if="isALink(letter)" :href="letter">{{ letter }}</a>
<a v-else>{{ letter }}</a>
</template>
</div>

you can set the unique name as the id of dom. when you click to letter X, just get the first name start with X , and use getElementById to match the dom and scroll to the dom.

Related

Adding and reading json data stored in vuex

I have a vuex store and i am adding some josn data and this is the format.
[
{
"id":1,
"firstname": "toto",
"lastname": "titi"
},
{ "id":2,
"firstname": "one",
"lastname": "two"
}
]
I am adding the data on an on click action and this is the action method
addLink: function() {
var dt = '[{"id":1,"firstname":"xx","lastname": "yy"},{"id":2,"firstname": "one","lastname": "two"}]';
this.ADD_LINK(dt)
this.newLink = '';
},
The data is getting added to the store and i can access it like this
computed: {
users(){
return this.countLinks;
}
}
I can display the data this way {{users}} and this is getting displayed. This is because i clicked twice and added the json twice.
[ "[{\"id\":1,\"firstname\":\"xx\",\"lastname\": \"yy\"},{\"id\":2,\"firstname\": \"one\",\"lastname\": \"two\"}]", "[{\"id\":1,\"firstname\":\"xx\",\"lastname\": \"yy\"},{\"id\":2,\"firstname\": \"one\",\"lastname\": \"two\"}]" ]
However, when i try to use v-for
<ul id="users">
<li v-for="user in users" :key="user.id">
{{ users.firstname}}
</li>
</ul>
i cannot display any data and i have no error. How can i display the data saved in vuex?.
You can create a computed property that returns the objects in one list parsed as JSON:
new Vue({
el:"#app",
data: () => ({
users: [ "[{\"id\":1,\"firstname\":\"xx\",\"lastname\": \"yy\"},{\"id\":2,\"firstname\": \"one\",\"lastname\": \"two\"}]", "[{\"id\":1,\"firstname\":\"xx\",\"lastname\": \"yy\"},{\"id\":2,\"firstname\": \"one\",\"lastname\": \"two\"}]" ]
}),
computed: {
usersList: function() {
return this.users.flatMap(userList => JSON.parse(userList));
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul id="users">
<li v-for="(user, index) in usersList" :key="index">
{{ user.firstname}}
</li>
</ul>
</div>
Note: Since ids are not unique in your example, you can use an index in v-for as the key. Also, to show the first name, you need to use the user object.
Another solution: Parse dt in the store and use Array#concat to add the elements as objects to the initial list:
let countLinks = [
{ "id":1, "firstname": "toto", "lastname": "titi" },
{ "id":2, "firstname": "one", "lastname": "two" }
];
function ADD_LINK(dt) {
countLinks = countLinks.concat(JSON.parse(dt));
}
const dt = '[{"id":1,"firstname":"xx","lastname": "yy"},{"id":2,"firstname": "one","lastname": "two"}]';
ADD_LINK(dt);
console.log(countLinks);
you have to store the data as is, rather than converting into string
addLink: function() {
var dt = [
{
"id":1,
"firstname": "xx",
"lastname": "yy"
},
{
"id":2,
"firstname": "one",
"lastname": "two"
}
];
// remove the single quote from the above array
this.ADD_LINK(dt)
this.newLink = '';
},
In case you are getting the var dt from external source then you should consider converting into valid js json format using this:
addLink: function() {
var dt = '[{"id":1,"firstname":"xx","lastname": "yy"},{"id":2,"firstname": "one","lastname": "two"}]';
// parse it to json format
var parsedDt = JSON.parse(dt);
// add the `parsedDt`
this.ADD_LINK(parsedDt)
this.newLink = '';
},

Hierarchical dynamic loaded components with children distributed along dynamic named slots

We're creating an app where the interface is dynamically mounted based on a JSON returned by an API. It looks like this:
{
"path": "Container",
"children": [
{
"slot": "default",
"path": "Banner",
"props": {
"items": [ "image1.jpg", "image2.jpg", "image3.jpg" ]
}
},
{
"slot": "header",
"path": "Flex",
"props": {
"flow": "row"
},
"children": [
{
"slot": "default",
"path": "Icon",
"props": {
"name": "mdi-forum"
}
},
{
"slot": "default",
"text": "Example of title"
}
]
}
]
}
So I created a dynamic ComponentLoader with a computed doing a dynamic import, then I also inject more dynamic component loaders recursively as needed, using a v-for through the children list:
<template>
<component v-if="component" :is="component" v-bind="$attrs">
<template v-for="(child, i) of children">
<ComponentLoader
v-if="child.path"
v-bind="child.props"
:key="`${child.path}-${i}`"
:path="child.path"
:children="child.children"
/>
<template v-else>{{ child.text || '' }}</template>
</template>
</component>
</template>
<script>
import Error from '~/components/ComponentLoaderError.vue'
export default {
name: 'ComponentLoader',
components: { Error },
props: {
path: { type: String, required: true },
children: { type: Array, default: () => [] },
},
computed: {
component() {
if (!this.path) return null
return () => import(`~/components/${this.path}`).then((m) => m || m.default).catch(() => Error)
},
},
}
</script>
It's almost working, but all children gone injected to the default slot of each loaded component, which makes all sense since I'm not informing the desired slot during the loop through the children.
Inspired in this answer, I added a v-slot bind on the <template> with the v-for, using Dynamic Slot Names to inject on the right slot based in the child.slot property already received from the JSON:
<template v-for="(child, i) of children" #[child.slot]>
For nodes with only one child to be distributed on each slot, it's working as expected. But when I have more children to be distributed in the same slot (like the last children array in that JSON), only the last child is injected, overriding others before.
So, how to inject many children to dynamic named slots inside a loop?

return unique values from array in vuejs

I am using Vuejs and Vuex to return an array of item objects. The items can be submitted to the database with the same name multiple times. I need to create a list of unique item names in an array in Vuejs.
from map getters below all Items returns an array of objects that looks like
[
{"name": "item one", "number": "001", "size": "4000kb"}
{"name": "item two", "number": "002", "size": "5000kb"}
{"name": "item three", "number": "003", "size": "6000kb"}
]
methods: {
...mapActions(["fetchItems"])
},
computed: {
...mapGetters(["allItems"]),
itemNames: function() {
return [...new Set(this.allItems.name)]
}
},
created() {
this.fetchItems(),
this.itemNames()
},
In computed properties itemNames if I take off the .name [..new Set(this.allItems)]
the array returns the complete objects - how can I just pull the name out to a list?
The v-for does not return the array
<v-list-item v-for="(itemName, index ) in itemNames" :key="index">
<v-list-item-content> {{ itemName }}</v-list-item-content>
</v-list-item>
Thanks for any help.
I solved this one in computed properties by mapping the items to a new array like so
itemNames: function() {
return [...new Set(this.allItems.map(x => x.item.Name))]
}

How get the data from an array like this with axiosjs

I connected to a URL using Axios using this:
getUsers: function() {
axios.get(urlUsers).then(response => {
this.lists = response.data
});
and get this data:
"lists": [
{
"name": "Destacados",
"tags": [
"Aguila"
],
"isRoot": true,
"products": [
{
"name": "Coors",
"code": "139017",
And tryng to list products.
How?
You can use v-for to render lists. In your case you have a nested array so you would need to do it twice. However I think you should change your data element 'lists' and remove all the excess quotes ("") so it looks like this:
lists: [
{
name: "Destacados",
tags: [
"Aguila",
],
isRoot: true,
products: [
{
name: "Coors",
code: 139017,
},
{
name: "Bud",
code: 139019,
}
],
}
]
and then run your v-for loops:
<template>
<div v-for="list in lists" :key="list.name">
<ul v-for="product in list.products :key="product.name">
<li>{{product.name}}</li>
<li>{{product.code}}</li>
</ul>
</div>
</template>

Instantiating a deep object and referencing different levels in other instances

I would like to know how to render two html elements that reference different levels in the same object.
Let's say I have a global deep object:
var sports = [{
sportName: 'soccer',
teams: [{
teamName: 'TeamA'
},
{
teamName: 'TeamB'
}
]
},
{
sportName: 'basketball',
teams: [{
teamName: 'TeamC'
},
{
teamName: 'TeamD'
}
]
}
]
Now, I want to two unordered lists that represent the different levels in the hierarchy of that object.
<ul id="sports">
<li v-for:"sport in sports">
<span>{{ sport.sportName }}</span>
</li>
</ul>
<script>
var sportsList = new Vue({
el:'#sports',
data: {
sports: sports
}
})
</script>
Here is the other list, in a completely different part of the app:
<ul id="teams">
<li v-for:"team in teams">
<span>{{ team.teamName }}</span>
</li>
</ul>
<script>
var sportsList = new Vue({
el:'#teams',
data: {
teams: sports[sportName].teams
}
})
</script>
My questions are these:
1) Will rendering two seperate instances of the different levels in the sports data object still result in data reactivity in each of those instances?
2) I've noticed that as soon as I instantiate the first list (sports), the get and set for the nested items (teams) are stored in the prototype... which leads me on to the next question. Does it make sense to instantiate a second Vue instance for teams, when it has already been instantiated in the first? I'm finding it difficult to navigate deeper objects within Vue :(
I think the best approach is to use a computed, rather than trying to copy parts of an array to data, which can cause you to end up in a bit of a mess. So in your case I can see you're trying to get teams for a sport, so I would set up a computed that returns teams for a sport stored in data:
computed: {
teams() {
return this.sports.filter(sport => {
return this.sportName === sport.sportName
})[0].teams;
}
},
Now all you need to do is set a data property called sportName which that filter can react to, so the full view model looks like this:
var app = new Vue({
el: '#app',
computed: {
teams() {
return this.sports.filter(sport => {
return this.sportName === sport.sportName
})[0].teams;
}
},
methods: {
setSport(name) {
this.sportName = name;
}
},
data: {
sportName: 'basketball',
sports: [{
sportName: 'soccer',
teams: [{
teamName: 'TeamA'
}, {
teamName: 'TeamB'
}]
}, {
sportName: 'basketball',
teams: [{
teamName: 'TeamC'
}, {
teamName: 'TeamD'
}]
}]
}
});
And here's the JSFiddle: https://jsfiddle.net/hgw2upzf/