I've inherited someone else's vue code that uses vue-tables-2. The table looks like this (very, very simplified)...
<v-client-table :data="myRows" :columns="columns" :options="options">
<div slot="columnA" slot-scope="{row}">
<input type="text" v-model.trim="row.someKey" class="form-control">
</div>
<div slot="etc"> ...
Notice how it says slot-scope="{row}", instead of slot-scope="row". As I inspect rows, I find each row looks like this...
{ row: { keyA: value, keyB: value, etc }, index: 1 },
{ row: { keyA: value, keyB: value, etc }, index: 2 }, etc
So I guess the original author "destructured" the row to avoid wasting keystrokes, as in v-model.trim="row.row.someKey".
Now I'm adding a new column which uses the row and the array index of the row data, like this...
<div slot="columnZ" slot-scope="row">
<p>col value is {{row.row.someOtherKey}} and index is {{row.index-1}}<p>
</div>
But I don't like row.row any better than the original author, and the row.index is given in counting numbers, not the zero-based array index.
I assume vue-tables-2 is the source of both of these issues. Is there a way I can use the package but just get the row data without having it wrapped in another "row", and is there a way I can get a zero-based array index per row?
Is there a way I can use the package but just get the row data without having it wrapped in another "row"
Yes and no. Slot props (anything the component shares with it's slot) is always shared as a singe object. You are the one who gives the variable the name.
So in case of slot="columnZ" slot-scope="row" (which is by the way a deprecated syntax - current syntax is v-slot:columnZ="row")
You can chose any other name - instead of row it can be props or anything else
what you get is whole object - { row: { keyA: value, keyB: value, etc }, index: 1 } (assuming)
But you can destructure the object into components - v-slot:columnZ="{ row, index }"
This is same operation as in plain JS:
const obj = { row: { keyA: 1, keyB: "asdasd", }, index: 1 }
// here the names `row` and `index` are given by the names of 1st level properties
const { row, index } = obj
console.log(row)
console.log(index)
// it is possible to "rename" te variable
const { row: user } = obj
console.log(user)
As for the missing zero-based index - if this is what the component shares with the slot, there is very little you can do about it. The component is in control. If you really need it, just add one more key into every row containing zero-based index
Related
I'm trying to delete a specific item in the array based on the index. Currently, I have an issue deleting the first and last element of the array. When I try delete the last element, the first element gets deleted and vice versa.
Here's an excerpt from my code
HTML
<div *ngFor="let item of itemsList.slice().reverse(); index as i">
<ion-item>{{item.name}} <button (click)="deleteItem(i)">Delete</button></ion-item>
</div>
TS
itemsList = [{
name: 'Item 0'
}];
count = 0;
constructor() {}
addItem() {
this.count += 1
this.itemsList.unshift({
name: `Item ${this.count}`
})
}
deleteItem(index) {
console.log('Delete ', this.itemsList[index].name)
this.itemsList.splice(index, 1)
}
I created a working example using StackBlitz. Could anyone please help?
Use length - index - 1 in your splice to get the correct index of the normal array.
this.itemsList.splice(this.itemsList.length - index -1, 1)
I can't think of any reason why you wouldn't code it like this:
(click)="deleteItem(item)"
It's really a much better way to write code.
actually no biggie but how would a computed property filter function look like that always returns the current array + 5 more elements?
more in detail:
Template:
<span class="box-content" v-for="item in activeItems" :key="item.id">
<img class="item" :src="item.filename" />
</span>
Script
data: function() {
return {
items: [],
limit: 1,
};
},
computed: {
activeItems: function() {
return this.items.filter( function(s) {
if(s.length > this.limit) {
return s;
}
});
// return this.limit ? this.items : this.items;
}
},
on page load , an axios post request gets an object of items, whose response is pushed into the items array which is empty upon component declaration.
so axios -> get object with items -> push into empty array.
now i want to display ,like, 5 items and make a show more button.
The problem now is, my activeItems function is invalid, it does not know "this.limit" and i doubt anyway that it returns the correct result as i just made it return itself and not a set of objects / arrays.
What I would do next is trying around with splice and slice, array copies and pushing elements into it until a certain condition is met but.. is there a better way ?
Thanks in advance
The filter function should be used to filter based on the internal values of an array. Say you have an array of objects with persons, and each Person as an age, then you could use the Array.prototype.filter function to filter based on that age of each entry.
The filter function therefore goes through every entry in your array and determines whether an item should be included or excluded.
If you, on the other hand, want to limit the amount of entries based on a maximum number of entries, I would suggest you use Array.prototype.slice, as you mentioned already.
Your computed function could be rewritten to:
activeItems: function() {
return this.items.slice(0, this.limit)
}
First, in your code, this.limit is undefined because this is referencing the anonymous function. If you want to access the component, you will better use arrow functions syntax.
Also, s references an element of your array, so s.length will be undefined too I guess...
Now, filter does not seem to be the best choice for your need. I'll go with slice instead. Somthing like:
computed: {
activeItems() {
return this.items.splice(0, this.limit)
}
}
Where limit is increased by 5 when you click the show more button.
Of course you could do it. You just missed some code on it. Here how you fix it
activeItems: function() {
let limit = this.limit
return this.items.filter( function(item, s) {
return s <= limit
});
}
If you don't mind using filter, here are some way to do it.
First : put condition in your for loop, this one
<span class="box-content" v-for="(item, index) in items" :key="item.id" v-if="index <= limit">
<img class="item" :src="item.filename" />
</span>
Second is to slice your array on you desired length, this one
<span class="box-content" v-for="(item, index) in items.slice(0, limit)" :key="item.id">
<img class="item" :src="item.filename" />
</span>
I made a function on my form where I can add multiple file inputs for multiple images by clicking a button, this is working as expected. Now when I try to delete an input field with .splice it keeps deleting the last item of the array that my input fields are in, not the input field with the matching index. I have been trying to fix this issue for hours now, I just can't seem to find a solution. I hope someone can tell me what I'm doing wrong.
This is the method for adding a new input field:
addInputField() {
i++
this.values.links.push({
id: i,
url: ''
});
}
this is the code for deleting an input field:
deleteInputField(index) {
this.values.links.splice(index, 1);
const items = this.values.links.filter(item => {
return item.id > index
});
items.forEach(function (item) {
item.id = item.id -1;
});
}
This is the button that deletes the input field:
<v-icon
medium
v-if="link.id > 0"
color="#FF0000"
class="ma-4"
#click="deleteInputField(link.id)"
>
If you want to pass the id (link.id), just use this function.
function deleteInputField(itemId){
this.values.links = this.values.links.filter(item => {
return item.id !== itemId;
});
}
This will loop through the array and return all but the one that has the same id as the itemId passed.
otherwise, just pass in the index from the v-for loop in the #click handler
deleteInputField(index) {
this.values.links.splice(index, 1);
}
links is an array of objects. You are not passing an index in that array, but an id inside the nested object.
<v-icon
medium
v-if="link.id > 0"
color="#FF0000"
class="ma-4"
#click="deleteInputField(link.id)" // Change to index (mostly taken from v-for loop)
>
From the definition of the splice method, the first parameter is start:
The index at which to start changing the array. If greater than
the length of the array, start will be set to the length of the array.
If negative, it will begin that many elements from the end of the
array (with origin -1, meaning -n is the index of the nth last element
and is therefore equivalent to the index of array.length - n). If
array.length + start is less than 0, it will begin from index 0.
If you step through the code you will realise that the id you are passing is not an actual index of the link in the links array but the id of the link which is greater than the length of the array
The ID is not the index if you need to pass the index to the deleteInputField function you can delete the desired input field by this.values.links.splice(index, 1);
I am trying to dynamically create/remove a Vue component. I have figured out how to dynamically add the component, but I am having some troubles with allowing the users to remove the specific component.
Consider below two Vue files:
TableControls.vue
<a v-on:click="addColumn">Add Column</a>
<script>
export default {
methods: {
addColumn: function () {
Event.$emit('column-was-added')
}
}
};
</script>
DocumentViewer.vue:
<div v-for="count in columns">
<VueDragResize :id="count">
<a #click="removeColumn(count)">Remove Column</a>
</VueDragResize>
</div>
<script>
import VueDragResize from 'vue-drag-resize';
export default {
components: {
VueDragResize
},
data() {
return {
columns: [1],
}
},
created() {
Event.$on("column-was-added", () => this.addColumn())
},
methods: {
addColumn: function () {
this.columns.push(this.columns.length + 1)
},
removeColumn: function (id) {
this.columns.splice(id, 1)
}
}
};
</script>
As you can see, whenever a user clicks on <a v-on:click="addColumn">Add Column</a>, it will submit an event, and the DocumentViewer.vue file will pick up it, firing the addColumn method. This will ultimately create a new <VueDragResize></VueDragResize> component.
This works great.
The problem is when I want to remove the component again. My removeColumn method simply removes an id from the columns array:
removeColumn: function (id) {
this.columns.splice(id, 1)
}
This results in that a column is in fact removed. However, consider below example. When user clicks on the remove icon for the first column, it will remove the 2nd column instead. (And when there is only one column present, it cannot be removed).
I believe this is due to the fact that I splice() the array, but I cannot see how else I can remove the component dynamically?
I see, Array on Vue does not re render when you modify them.
You need to use the
Vue.set(items, indexOfItem, newValue)
if you want to modify
and use
Vue.delete(target, indexOfObjectToDelete);
If you want to delete an item from an array
You may read the additional info here
https://v2.vuejs.org/v2/api/#Vue-delete
If you want to delete an item from array. Using this will cause the component to rerender.
In this case it will be intuitive to do this
removeColumn: function (id) {
Vue.delete(this.columns, id)
}
Note that id should be the index. Vue.delete ensures the re-render of the component.
EDIT, you must use the index, instead of the count here.
<div v-for="(count, index) in columns">
<VueDragResize :id="index">
<a #click="removeColumn(index)">Remove Column</a>
</VueDragResize>
</div>
I would recommend reshaping your data, each element should be an object with an id and whatever other properties you want. Not simply an id then you would need something like this:
removeColumn(id) {
const elToRemove = this.columns.findIndex(el => el.id === id)
let newArr = [elToRemove, ...this.columns]
this.columns = newArr
}
Also make another computed property for columns like this to make sure they change dynamically (when you add/remove):
computed: {
dynColumns(){ return this.columns}
}
I have same problem, and I found the solution of this problem. It is need to set #key with v-for. This is Built-in Special Attributes.
By default, if you do not set "#key", array index is set to#key. So if array length is 3, #key is 0,1,2. Vue identify eash v-for elements by key. If you remove second value of array, then array index is 0 and 1, because array length is 2. Then Vue understand that #key==2 element removed, So Vue remove 3rd component. So if you remove second value of array, if no #key, third component will be removed.
To avoid this, need to set #key to identify component like this:
let arr = [
{ id: 'a', ...},
{ id: 'b', ...},
{ id: 'c', ...}
];
<div v-for="obj in arr" :key="obj.id">
<someYourComponent>
...
</someYourComponent>
</div>
I have an HTML table with rows created using an ng-repeat and data using properties of objects in an object array. They are being sorted by a name column alphabetically. I have setup some simple editing using ng-show, ng-focus, and ng-blur, so that there is no need for a save button or something similar.
However, as I am editing the name column, if I type a first letter that would cause that row to be lower in the table, it sorts the table as I am still typing. I tried creating a "temp" object array, copying the original array into it, modifying the "temp" array when editing, then copying the temp array into the original array when editing is done. That didn't work; the same thing happened anyway.
After a little research, I learned that both the temp array and the original array were probably pointing to the same data, so I tried multiple ways of cloning the array. I finally got it to work...with one exception: when I edited the object to be sorted lower, it moved. When I tried to edit it again, it did all sorts of random, unexpected stuff.
After some diagnostics, I discovered that the indexes of the objects in the table (gotten from $index) were being changed when sorted. For example:
Table
-------------------------------------
|Name |Age |Phone |Index |
-------------------------------------
|George |25 |928-7495|0 |
|John |34 |342-0673|1 |
|Megan |28 |834-1943|2 |
|Susan |19 |274-8104|3 |
-------------------------------------
If I changed George to Tim, the index of that object becomes 3 also. All the other indexes stay the same. Can anyone tell me why this is happening and/or give me suggestions on how to fix this?
So apparently I just didn't realize that the indexes changed every time I changed the data or the sorting of the data. After adding the index as a property of my objects, everything worked as expected.
This is how I use the orderBy Angularjs filter with column sorts that reverse on click. The "a" tag in the "th" is what controls the behavior. I have this in a directive, which is recommended, but the code can be in a controller.
I use the Angularjs orderBy filter as they state.
$filter('orderBy')(array, expression, reverse)
// This is the order of properties in the code below that looks like this
// $scope.rows = angularSortBy( $scope.rows, columnName, toggleOrderDirection( columnName ));
Each column has a font-awesome multi-arrow toggle called
<i class="fa fa-sort"></i>
Where columns would be something like this...
$scope.columns = { columnOne: { label: 'Column One', orderDirection: true, selected: false },
columnTwo: { label: 'Column Two', orderDirection: true, selected: false },
columnThree: { label: 'Column Three', orderDirection: true, selected: true }};
and rows could be anything you wish...
<table>
<tr>
<th>Sort By</th>
<th ng-repeat="(key, value) in columns">
{{ value.label }}
<span ng-if="value.selected">
(<i class="fa fa-sort"></i>) // font-awesome arrow font.
</th>
</tr>
<tr ng-repeat="row in rows">// stuff here</tr>
var angularSortBy = $filter( 'orderBy' ); // use Angular's built in filter to sort table.
$scope.orderBy = function( columnName ){
resetAllSelectedStates(); // sets column.selected prop to false.
setAsSelected( columnName, true );
$scope.rows = angularSortBy( $scope.rows, columnName, toggleOrderDirection( columnName ));
}
function resetAllSelectedStates(){
Object.keys( $scope.columns ).forEach( resetColumnPropertyToDefault );
}
function resetColumnPropertyToDefault( name ){
$scope.columns[ name ].selected = false;
}
function setAsSelected( name ){
$scope.columns[ name ].selected = true;
}
function toggleOrderDirection( name ){
return $scope.columns[ name ].orderDirection = !$scope.columns[ name ].orderDirection;
}