I have a table in my Vuejs project similar to the one that I shared its screenshot above.
My question is; How can I delete a <tr> element from the table when the Delete button of it is pressed for each row?
On the other hand, I want the Add button at the top right of the table to be functional as well. When I click the Add button, I want another <tr> element to be created at the bottom of the table. However, there should be input fields on this element where the user can enter information for each column. After the user has written the information in each column, that row should be added to the table. How can I do that?
if I am correct you are using v-for loop to render all <tr>s
then use the v-for with index like v-for="(obj, index) in objects" to obtain index
to add use Array.prototype.push() to add empty row e.g. objects.push({x: null, y: null})
to remove use Array.prototype.splice() e.g objects.splice(index, 1)
just assign those functionalities to respective buttons.
You could attempt this using a data property in your component and then implement two methods: deleteRow and addRow.
This could be an example code
data() {
return {
dataTable: [
{
id: 1,
code: code1,
description: description1,
},
{
id: 2,
code: code2,
description: description3,
},
...
]
}
}
methods: {
deleteRow(id) {
this.dataTable = this.dataTable.splice(
this.dataTable.findIndex(item => item.id === id)
)
}
addRow(newRow) {
// newRow = {id: 3, code: code3, description: description3}
this.dataTable = [...this.dataTable, newRow]
}
},
Your component will be updated by Vue.js reactivity system.
Probably addRow method should be called from a modal component where you put the input fields to set newRow.
Related
I am very new to the VUE framework. The b-editable-table model is not returning the current value but the old value.
Here's the bootstrap code.
<b-editable-table disableDefaultEdit
:rowUpdate="rowUpdate"
:editMode="'row'"
bordered
class="editable-table"
v-model="items"
:fields="fields"
striped>
<template #cell(edit)="data">
<div v-if="data.isEdit">
<BIconX class="edit-icon" #click="handleSubmit(data, false)"></BIconX>
<BIconCheck class="edit-icon"
#click="handleSubmit(data, true)"></BIconCheck>
</div>
<BIconPencil v-else
class="edit-icon"
#click="handleEdit(data, true)"></BIconPencil>
</template>
<template #cell(delete)="data">
<BIconTrash class="remove-icon"
#click="handleDelete(data)"></BIconTrash>
</template>
</b-editable-table>
Here's the Javascript Method handleSubmit:
The current value of the productPrice 11.63 but when I console data.item.productPrice, it returns me 11.25 which is an old value.
This is a result of console.log showing objects as 'live' data vs individual data properties that will log their value at the time of the actual console.log statement. This is further explained in the MDN documentation for console.log.
Don't use console.log(obj), use console.log(JSON.parse(JSON.stringify(obj))).
This way you are sure you are seeing the value of obj at the moment you log it. Otherwise, many browsers provide a live view that constantly updates as values change. This may not be what you want.
Technically your first console.log should be showing the old productPrice value for some (extremely) short amount of time, same as your other console logs, but then the log updates itself to show the new value almost instantaneously.
To get the updated value of a table cell
Because of the way b-editable-table is designed, we can't get the updated cell value from an event handler on a cell v-slot that isn't the cell being updated. One way to do what you want is to listen to the input-change event emitted from the table itself. This passes a data variable that contains the updated values of whatever row was just updated. From this you can assign the updated row data to a new data property on your component. This property will have the information you need when a user clicks the icon in your #cell(edit) slot
<b-editable-table
bordered
class="editable-table"
v-model="items"
:fields="fields"
#input-change="handleInput"
>
data() {
return {
fields: [ ... ],
items: [ ... ],
updatedRow: {} // <-- new property to hold updated row data
},
methods: {
handleInput(data) {
if (Object.keys(this.updatedRow).length === 0) {
this.updatedRow = {
...this.items[data.index],
[data.field.key]: data.value,
};
} else {
this.updatedRow = { ...this.updatedRow, [data.field.key]: data.value };
}
},
// method called by `#cell(edit)` slot
handleSubmit(data, update) {
this.rowUpdate = {
edit: false,
id: data.id,
action: update ? "update" : "cancel",
};
console.log("this.updatedRow.productPrice", this.updatedRow.productPrice); // <-- shows latest productPrice
this.updatedRow = {}
},
}
An explanation for:
this.updatedRow = {...this.items[data.index], [data.field.key]: data.value};
This line exists because the data object passed in by the event contains only pieces of the updated value that we still must manually construct into a row object. This is the format of the passed in data object (unimportant properties removed):
{
index: 0
field: {
key: "productPrice"
label: "Product Price"
...
}
value: "11.63"
...
}
So we have these properties:
index (the row updated)
field.key (the cell updated)
value (the new cell value)
At this point, we still have nothing giving us the full updated row object.
Our v-model this.items is still not updated! We have to construct this updated row object ourselves. The code that does this uses object destructing.
{ ...this.items[data.index] } gives us all the current (old) key-values of this.items[row]
adding [data.field.key]: data.value as another property will update the relevant key-value pair that was just updated by the user.
together, this.updatedRow = {...this.items[data.index], [data.field.key]: data.value}; gives us the row from this.items but with the absolute latest values from the row that was just updated by the user.
I have a list of objects that can be updated from the database.
So, when I load the list, objects have only id and name.
When I click on an object I load other fields that can be of any length - that's why I don't load them with the objects in the list.
I found that when I update an object it can be difficult to keep reactivity https://v2.vuejs.org/v2/guide/reactivity.html so I need to find some workaround.
this code works almost okay:
axios.get('/api/news', item.id).then(function(response){
if (response){
Object.assign(item, response.data.item);
}
});
But the problem is the fields that have not been presented from the beginning is not 100% reactive anymore. What is going on is a new field has been updated reactively only when I change another, previous one. So, if I show 2 text field with old and new properties, if I change the second property, the field will not be updated until I change the first one.
I got item object from the component:
data () {
return {
items: [],
}
},
and
<div v-for="item in items" #click="selectItem(item)" >
<span>{{item.name}}</span>
</div>
Then item's been passed to the function selectItem.
What is the proper pattern to load new fields and keep them reactive? (NB: it's not the case when I can assign field by field - I want to reuse the same code no matter which object it is, so I need so the solution for updating an object at a time without listing all new fields.)
Note. This code works inside the component.
Completely revised post: Ok, the example you give uses an array, which has its own caveats, in particular that you can't directly set values like vm.items[indexOfItem] = newValue and have it react.
So you have to use Vue.set with the array as the first argument and the index as the second. Here's an example that adds a name property to object items and then uses Vue.set to set the item to a new object created by Object.assign.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
other: 'data'
}, {
id: 2,
other: 'thingy'
}]
},
methods: {
selectItem(parent, key) {
const newObj = Object.assign({}, parent[key], {
name: 'some new name'
});
Vue.set(parent, key, newObj);
setTimeout(() => {parent[key].name = 'Look, reactive!';}, 1500);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item, index in items" #click="selectItem(items, index)">
<span>{{item.name || 'empty'}}</span>
</div>
<pre>
{{JSON.stringify(items, null, 2)}}
</pre>
</div>
Have a look at Change-Detection-Caveats Vue cannot detect property addition or deletion if you use "normal assign" methods.
You must use Vue.set(object, key, value)
Try something like the following:
axios.get('/api/news', item.id).then(function(response){
if (response){
let item = {}
Vue.set(item, 'data', response.data.item)
}
});
Than item.data would than be reactiv.
Can simply use Vue.set to update this.item reactively
axios.get('/api/news', item.id).then(function(response){
if (response){
this.$set(this, "item", response.data.item);
}
});
I have a vaadin-checkbox:
<vaadin-checkbox id=[[item.id]] disabled="true" checked="[[item.checked]]">[[item.description]]</vaadin-checkbox>
I defined my properties:
static get properties() {
return {
items: {
type: Array,
notify: true,
value: function() {
return [];
}
}
};
}
When I now change the data by pressing some button:
_selectItem(event) {
const item = event.model.item;
if (item.checked === true) {
this.$.grid.deselectItem(item);
} else {
this.$.grid.selectItem(item);
}
item.checked = !item.checked;
}
The state of the checkbox is still checked="true". Why isnt the checkbox getting updated? The same thing when I change the description of the item:
_selectItem(event) {
event.model.item.description = 'test';
}
The test description is never appearing. The checkbox is never getting updated.
The reason why the checkbox does not get updated by the button click handler is in the Polymer 2 data system. Polymer does not detect the change and does not update the template accordingly.
In order to make the change in a way that Polymer would detect it you have two options:
Use this.set(`items.${event.model.index}.checked`, !item.checked) if you can reliably assume that the index used by dom-repeat always matches that elements's index in the items array (it is not the case if you use sorting or filtering features of dom-repeat). See an example here https://jsfiddle.net/vlukashov/epd0dn2j/
If you do not know the index of the updated item in the items array, you can also use the Polymer.MutableData mixin and notify Polymer that something has changed inside the items array without specifying the index of the changed item. This is done by calling this.notifyPath('items') after making a change. However, this requires that your element extends the Polymer.MutableData mixin, and that dom-repeat has the mutable-data attribute set. See an example here: https://jsfiddle.net/vlukashov/epd0dn2j/24/
More information on this in the Polymer 2 docs.
I have a buefy table with details. Whenever I click on a chevron, the detailed view of the according row shows. It would be much better in my case, to have only one detailed view open. The desired outcome is: Whenever I click on a chevron, that detailed view opens and all other close.
In buefy, the opening of the detailed view is programmed like this:
<td v-if="detailed">
<a role="button" #click.stop="toggleDetails(row)">
<b-icon
icon="chevron-right"
both
:class="{'is-expanded': isVisibleDetailRow(row)}"/>
</a>
</td>
...
props: {
...
detailed: Boolean
...
}
...
methods: {
...
toggleDetails(obj) {
const found = this.isVisibleDetailRow(obj)
if (found) {
this.closeDetailRow(obj)
this.$emit('details-close', obj)
} else {
this.openDetailRow(obj)
this.$emit('details-open', obj)
}
// Syncs the detailed rows with the parent component
this.$emit('update:openedDetailed', this.visibleDetailRows)
},
openDetailRow(obj) {
const index = this.handleDetailKey(obj)
this.visibleDetailRows.push(index)
},
closeDetailRow(obj) {
const index = this.handleDetailKey(obj)
const i = this.visibleDetailRows.indexOf(index)
this.visibleDetailRows.splice(i, 1)
},
isVisibleDetailRow(obj) {
const index = this.handleDetailKey(obj)
const result = this.visibleDetailRows.indexOf(index) >= 0
return result
},
...
}
I see that there is an update_event sent to the parent. Do I have to save the
visibleDetailRows and tell the Child component to close it, when the button is pressed again? How would I do that?
The way I did it was to use the #details-open event to call a custom function :
<b-table
:data="data"
:opened-detailed="defaultOpenedDetails"
detailed
detail-key="id"
#details-open="(row, index) => closeAllOtherDetails(row, index)"
>
When you expand a row, the event is triggered.
And the called function closeAllOtherDetails() deletes all elements of the defaultOpenedDetails array, except the current row you just expanded :
closeAllOtherDetails(row, index) {
this.defaultOpenedDetails = [row.id]
}
That does the trick. Simple!
I am using knockout.js.
I am having a situation like,
Search page will produce search results .with edit icon and user clicked on edit icon and navigate to edit page.
On Edit page,I have drop down which should be populate with value from the previous screen.
Here is my Code:
var vm= {
Id: ko.observable(),
Name: ko.observable(),
description: ko.observable(),
Type: ko.observable(),
TypeList: ko.observableArray(),
};
var getData = function (Id) {
var ajaxOptions = {
url: 'Api/Group/Get?Id=' +Id,
type: 'GET',
dataType: 'json'
};
function gotData(data) {
vm.UserGroupId(data.Id);
vm.Name(data.name);
vm.description(data.description);
vm.Type(data.Type);
return data;
}
function getTypes(Data) {
//this will fetch me an array of (' ',TypeA,TypeB,TypeC)
dataService.getTypes(vm.TypeList);
//remove the empty one
vm.TypeList.shift();
//try to populate the value which i got from the search result at the top
vm.TypeList.unshift({ "name": 'TypeA', "value": '3' });
}
return $.ajax(ajaxOptions).then(gotUserGroup).then(getTypes);
};
now the issue I am facing is,I am able to get the dropdown values with value from search result.But apart from getting the value from search results,Iam also getting duplicate values.
my Html Code:
<div class="span2 control-label">Type: </div>
<div class="span4 controls form-inline">
<select name="select" data-bind="options: TypeList, optionsText: 'name', optionsValue: 'value', value: Type" />
</div>
For Example:
By default dataservice will give me ('',Type A,TypeB,Typec)
Once I selected the value in search result,lets suppose I select 'TypeA'
On the UI, I am able to see the values as (TypeA,TypeA,TypeB,Typec).
How can I eliminate duplicates?or How can I acheive this functinlity.
From the Knockout documentation: (http://knockoutjs.com/documentation/observableArrays.html)
myObservableArray.unshift('Some new value') inserts a new item at the beginning of the array
So you're adding a new value to the first of the array and leaving the value that was already there. So you need to also remove the value that was already there.
var newItem = { "name": 'TypeA', "value": '3' };
vm.TypeList.remove(newItem);
vm.TypeList.unshift(newItem);
I got the solution like
Initially get the value from db and then match the value with list already available and then remove the selected type and add the selected to the top by using knockoutjs unshift
var selectedType = ko.utils.arrayFirst(vm.TypeList(), function (item) {
if (data.Type === item.name)
return item;
return null;
});
vm.TypeList.shift();
vm.TypeList.remove(selectedType );
vm.TypeList.unshift(selectedType );