Vue: how to set props to an array range? - vue.js

I have a grid created for videos. It makes a call to an api to return back videos. I have a state isFetching that is set to true when the page loads and tries to retrieve the list from the API. After the videos are retrieved, isFetching is set to false and the data is stored as videoList.
I am using a grid with a placeholder component for each item if the API takes a while to fetch. The VideoGrid expects a items because inside of it, it iterates through it. I want to have 16 grid items but I don't want to have to do :items=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]. I tried :items="[new Array(16).keys()] but that seems to create a nested array. Is there a way to accomplish this without having to write it out like that?
Main.vue
<VideoGrid v-if="isFetching && !videoList" :items="[1,2,3,4,5,6]">
<template>
<ItemPlaceholder />
</template>
</VideoGrid>
<VideoGrid v-else :items="videoList">
<template v-slot="slotProps>
<VideoComponent :title="slotProps.title" />
</template>
</VideoGrid>

You should just be able to remove the brackets from your items declaration like so:
<VideoGrid v-if="isFetching && !videoList" :items="Array(16).fill(1)">
<template>
<ItemPlaceholder />
</template>
</VideoGrid>
<VideoGrid v-else :items="videoList">
<template v-slot="slotProps>
<VideoComponent :title="slotProps.title" />
</template>
</VideoGrid>
Array(16).fill(1) will create an array with 16 number 1's. If you want to have incrementing values in your array you can use Array.from({length: 16}, (v, i) => i) or see How to initialize an array's length in JavaScript?.

Related

Multiple VueSelect

I am obtaining from an API the data to fill a v-select type component, the component is filled correctly and I also obtain the options that the user already had assigned (multiple)
And the v-select component are filled correctly
But when i try to add another option occurs the next
Here is the Source Code
<validation-provider
#default="validationContext"
name="skill"
>
<b-form-group
label="Skills"
label-for="skill_id"
:state="getValidationState(validationContext)"
>
<v-select
v-model="itemData.skills"
:dir="$store.state.appConfig.isRTL ? 'rtl' : 'ltr'"
multiple
:options="skillOptions"
:clearable="false"
:reduce="(val) => val.value"
input-id="skill_id"
/>
<b-form-invalid-feedback
:state="getValidationState(validationContext)"
>
{{ validationContext.errors[0] }}
</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
Here i have the array of objects, with multiple elements
itemEdit.value.skills
Then map to get a reduced array with only the value and label
I have probed using only the label without value and the result is the same
const openEdit = item => {
isSidebarActive.value = true
itemEdit.value = item
itemEdit.value.skills = item.skills.map(skill => ({
value: skill.id,
label: skill.skill,
}))
isAdd.value = false
}
Everything apparently goes right, the v-model match correctly with the available options, the problem comes when the user interact with the vSelect to add another item, the already selected items disappear
Thanks in advance
Based on this page o the Vue Select Documentation, the reduce prop is usually meant for use with a label prop, and is only needed if you provide an object to v-select.
I suspect you are providing an array of primitives in your v-model (ie itemData.skills is an array of strings) instead of an array of objects. If it is an array of objects instead, then I suspect you either don't have a [label] key on the object (the default that label is set to), or you don't have a [value] key (what you are trying to return with your reduce prop).
Ideally, you would give us an example of your data coming in for us to be able to help you better.
thanks for listen, the problem was solved removing:
:reduce="(val) => val.value"
From
<v-select
v-model="itemData.skills"
:dir="$store.state.appConfig.isRTL ? 'rtl' : 'ltr'"
multiple
:options="skillOptions"
:clearable="false"
:reduce="(val) => val.value"
input-id="skill_id"
/>

Expand all Bootstrap-Vue b-table rows

I have a b-table which looks something like this:
<b-table :fields="fields" :items="tableRows">
<template #head(expand_all+)="data">
//would like to make this a button to expand all row-details
<div class="text-right">{{data.label}}</div>
</template>
...
<template #cell(expand_all+)="data">
<div class="expand-arrow-container":class="{ 'expanded': data.detailsShowing }" #click="data.toggleDetails">
<font-awesome-icon icon="chevron-right" />
</div>
</template>
...
<template #row-details="row">
//display data here
</template>
</b-table>
Now, all of this works fine and and my row details expand/contract as I would expect (see b-table row-details for more information).
The problem arises in that I want to be able to click one button and expand/contract all the row-details. My plan is to change template #head(expand_all+)="data" into a button which can be clicked to accomplish this. However, I do not know how to do this. I have searched and read the b-table documentation and have not found any way of accomplishing what I want. I've even taken a look at the table object to see if it had any references to its rows and I didn't see anything.
Do any of you know a way to accomplish this? Getting a reference to the rows would make this a simple problem, but as I mentioned I didn't see anything. I am also not looking to swap out the b-table with a bunch of b-collapse elements since I have put a lot of work into the table.
As described on the docs, you can set a _showDetails property on your items passed to the table. Which if set to true will expand that row's details. This means you could loop through all your items and set that property to true to expand all rows. And do the same with false to collapse them.
If the record has its _showDetails property set to true, and a row-details scoped slot exists, a new row will be shown just below the item, with the rendered contents of the row-details scoped slot.
Here's two example methods you could use.
We're using $set because we're adding a new property to an already existing object. You can read more about why here
expandAll() {
for(const item of this.tableRows) {
this.$set(item, '_showDetails', true)
}
},
collapseAll() {
for(const item of this.tableRows) {
this.$set(item, '_showDetails', false)
}
}

Vue doesn't rerender list even when using set

I made a list that each item could be removable and my code looked like this:
Template
<template v-for="(timeFrame, index) in form.timeFrames">
<el-form-item >
<el-button #click="removeTimeFrame(index)">
<i class="el-icon-remove"></i>
</el-button>
</el-form-item>
</template>
Js:
removeTimeFrame(index = 0) {
this.$set(this.form, 'timeFrames', this.form.timeFrames.filter((_, i) => index !== i));
}
Somehow the list doesn't rerender until I add an new item to the list. Does anybody know what's wrong with my code?
templates in Vue need a top level element wrapping their content, so you shouldn't assign the v-for directly to the template tag, but instead create a div inside the template tag and either add v-for to the el-form-item component or wrap it in another div-tag.
Additionally, every element in a v-for loop should contain a key. If you do not plan to reorder or delete elements from the loop, the index-value of each element is fine for this. Seeing your example I suspect that a unique identifier, such as a randomly generated unique id, might work better for your use case.

Assign index of v-for to id of created component

I am creating components in a loop in my vue app. However, I need those components to have an id value like "board-1" etc. using the index of the loop. (Just like I did it with v-bind:key="component-${block._uid}".)
How can I achieve this?
<div
class = "col-4"
v-for="(block, index) in layouts"
v-bind:key="`component-${block._uid}`"
>
<Board
id="`board-${block._uid}`"
class="droparea"
#dropped-component="$emit('dropped-component', $event, index)"
:acceptsDrop=true
draggable="true"
>
Layout {{index + 1}}
</Board>
</div>
You need to bind the value for JS code to get executed, otherwise you are attributing a string, not JS code.
:id="`board-${block._uid}`"
You can also use v-bind, actually : is just a shorthand
v-bind:id="`board-${block._uid}`"

Combining v-for with v-show on same element in template

I want to display a list of entries, and I have it working up through retrieving JSON from a server, parsing it, storing it in a Vuex.Store and iterating through it with v-for-"entry in this.$store.state.entries".
When a user first visits the page all entries will be visible. The next step is to filter the entries so that only matching entries remain visible. Since this filtering will be changing a lot, I want to use v-show. I have a separate component that lets users enter search terms, the server is queried, and an array of numbers—matching IDs—is returned. I want to only show entries with IDs that match the numbers in the array, queriedEntries. My template is below:
<template>
<div id="entries">
<div v-for="entry in this.$store.state.entries"
v-html="entry.content"
v-show="this.$store.state.queriedEntries.includes(entry.id)">
</div>
</div>
</template>
I get an error that I don't understand, and searching for answers hasn't yielded anything because it doesn't match the problem others have had.
[Vue warn]: Error in render: "TypeError: this is undefined"
It's the this in the v-show, but every other this works. What's up?
Your problem is occurring because you are referencing this inside your template. This is not necessary.
The first thing I recommend you do is have a read into Vuex' Getters. Further down on the same page, you'll find information about mapGetters. This will help to prevent you from directly targeting/modifying data within your state. Modification of data should be left only to Mutations and Actions.
For example, your code may look like the below:
// in your component script
...
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
allEntries: 'entries', // map state.entries to the name 'allEntries'
queriedEntries, // your other state value. You may want to convert this to a getter
// other state values if necessary
})
}
}
...
// in your component template
<template>
<div id="entries">
<div v-for="entry in allEntries"
v-html="entry.content"
v-show="queriedEntries.includes(entry.id)">
</div>
</div>
</template>
...
Here you can see that we have used mapState which helpfully generates computed getter functions from our data in the store. We can then use the property name we have assigned it to within our template.
I ended up removing this from everything but the v-for, as suggested, and the code worked. Why this causes an error in v-show and v-html is still a mystery.
Final, working code:
<div v-for="(entry, entryindex) in this.$store.state.entries"
v-bind="{id:entryindex}"
v-bind:key="entryindex"
v-show="$store.state.queryMatchedEntries[0] == -1 || $store.state.queryMatchedEntries.indexOf(parseInt(entryindex)) != -1">