Is there a way to sort by the content inside an input box in vuejs? - vue.js

I am making a data table where I shall show diffrent users information. I want to be able to sort for an example all the users by their firstname or lastname. I have used inputboxes becuse I also want to be able to edit the users information.
I have tried diffrent scripts from here on stackoverflow and other sites. But to no sucess. tried template and even copied straight off a working code but with out inputboxes then. I am also very new to Vue js only worked with it for 4 days.
<div id="app">
<table class="table">
<thead>
<tr>
<!--Rubriker för tabellkolummer-->
<th>ID</th>
<th>Förnamn</th>
<th>Efternamn</th>
<th>Telefonnummer</th>
<th>E-postadress</th>
<th>Behörighet</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(user,index) in users">
<template v-if="user && user.editable">
<td><input type="text" v-model="user.uuid" class="input" disabled></td>
<td><input type="text" v-model="user.firstName" class="input"></td>
<td><input type="text" v-model="user.lastName" class="input"></td>
<td><input type="text" v-model="user.phone" class="input"></td>
<td><input type="text" v-model="user.email" class="input"></td>
<td><input type="text" v-model="user.jobTitle" class="input"></td>
<td><button class="button" #click="onUserClick(index)">Spara</button>
<button class="button" #click="tabortrad(index)">Ta bort rad</button></td>
</template>
<template v-else>
<td><input type="text" v-model="user.uuid" class="input" disabled></td>
<td><input type="text" v-model="user.firstName" class="input" disabled></td>
<td><input type="text" v-model="user.lastName" class="input" disabled></td>
<td><input type="text" v-model="user.phone" class="input" disabled></td>
<td><input type="text" v-model="user.email" class="input" disabled></td>
<td><input type="text" v-model="user.jobTitle" class="input" disabled></td>
<td><button class="button" #click="onUserClick(index)">Redigera</button>
<button class="button" #click="tabortrad(index)">Ta bort rad</button></td>
</template>
</tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th><button class="button is-pulled-right" #click='laggtillrad(users)'>Lägg till en rad</button></th>
</tbody>
</table>
</div>
<script>
faker.locale = "sv";
//Skapar slumpmässiga användardata
let randomusers = [];
for (i = 0; i < 4; i++) {
let uuid = faker.random.uuid();
let firstName = faker.name.firstName();
let lastName = faker.name.lastName();
let phone = faker.phone.phoneNumber();
let email = faker.internet.email();
let jobTitle = faker.name.jobTitle();
randomusers.push({ 'uuid': uuid, 'firstName': firstName, 'lastName': lastName, 'phone': phone, 'email': email, 'jobTitle': jobTitle });
}
new Vue({
el: '#app',
data: {
users: randomusers,
},
methods: {
//Lägger till en rad längst ner i tabellen
laggtillrad: function (event) {
var nyrad = {
uuid: faker.random.uuid(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
phone: faker.phone.phoneNumber(),
email: faker.internet.email(),
jobTitle: faker.name.jobTitle(),
};
this.users.push(nyrad)
},
//Tar bort raden där användaren trycker på "Ta bort" knappen
tabortrad: function (index) {
this.users.splice(index, 1);
},
//Redigerar raden där användaren trycker på "Redigera" knappen och ersätts med en spara knapp
onUserClick(index) {
const changeUser = this.users[index];
changeUser.editable = !changeUser.editable;
this.$set(this.users, index, changeUser);
}
},
})
</script>
I just want to be able to sort the users information. But think I must start all over again an use something called grid I think. But if someone can help me I will be very happy and maybe some off this code I have can come to use for someone else :).

Just add sortedUsers computed property that returns the sorted copy of users array and use it this way:
<tr v-for="(user,index) in sortedUsers"> and
computed: {
sortedUsers() {
// Using destructuring to prevent users array in-place mutation
return [...this.users].sort((userA, userB) => userA.firstName.localeCompare(userB.firstName))

Related

Trying to not show v-model change until I want to show it

Okay so I have a table and the table has a number display and an Input for the user to increase the number up and down.
<tr v-for="(obj, index) in OBJECTARRAY" :key="obj.key">
<td>{{OBJOTHERNUMBER- OBJNUMBER}}</td>
<td>
<input v-model="OBJNUMBER" type="number">
</td>
</tr>
I don't want this change to show dynamically with a v-model but with a button click that updates the change?
What you could do is use 2 variables.
DiscountPriceTemp
DiscountPrice
And when you click on the button, you update DiscountPrice with DiscountPriceTemp.
<tbody>
<tr v-for="(obj, index) in OBJECTARRAY" :key="obj.key">
<td>{{ obj.SalePrice - obj.DiscountPrice }}</td>
<td>
<input v-model="obj.DiscountPriceTemp" type="number">
</td>
</tr>
</tbody>
export default {
methods: {
onButtonClick (obj) {
obj.DiscountPrice = obj.DiscountPriceTemp
}
}
}
You can use a ref to access the input value and set the discountPrice when the update button is clicked. In Vue it's best practice to not access the document directly. Not sure if you want a button for each row or a bulk update so I've included both. Here's the codepen if you want to see it in action https://codepen.io/madison_at_vrume/pen/mdWNrMy
<div>
<tbody>
<tr v-for="(obj, index) in prices" :key="obj.key">
<td>{{ obj.salePrice - obj.discountPrice }}</td>
<td>
<input
:ref="`discountInput-${index}"
:value="obj.discountPrice"
type="number"
>
</td>
<td>
<button #click="updatePrice(index)">Update Price</button>
</td>
</tr>
</tbody>
<button #click="updateAllPrices()">Update Prices</button>
</div>
methods: {
updatePrice(index) {
const newDiscountPrice = this.$refs[`discountInput-${index}`].value;
this.prices[index].discountPrice = newDiscountPrice;
},
updateAllPrices() {
this.prices.forEach((obj, index) => {
this.updatePrice(index);
})
},
}

Vue js text field value becomes empty when new row is appended

I have an dynamic table where new row can be added as well as removed ,im populating text field values based on Onchange event of Select option ,For the first row i can populate the values but once i append a new row ,the old values of the past row gets removed.
export default {
data() {
return {
//editmode:true,
templatename: '',
type: 'margin',
games: {},
fields: {},
subs: {},
rows: []
// errors: {},
}
},
components: {
Datepicker,
MinusIcon,
PlusIcon,
XCircleIcon
},
methods: {
getexcercisesrepcount: function(event, dynamicrow) {
var currentrow = dynamicrow;
var excercise = event;
const URL = baseurl + `api/getexcercisesrepcount/`
axios({
method: 'post',
url: URL,
headers: {
'Content-Type': 'application/json',
},
data: excercise
})
.then(response => {
var rowsdata = response.data;
var i = 0;
for (i; i < rowsdata.length; i++) {
var exid = rowsdata[i].id;
if (dynamicrow != null) {
var crval = $("#set" + currentrow).val(exid); //This is where i set value for the table row
}
}
});
},
addRow: function() {
var elem = document.createElement('tr');
this.rows.push({
workoutname: "",
workoutcategory: "",
set: "",
rep: "",
resttime: "",
tempo: ""
})
},
removeElement: function(index) {
this.rows.splice(index, 1);
},
},
}
This is my table where i have added text fields as well as select option
<table class="border-collapse" style="width: 100%;" id="mytable">
<tr>
<th class="p-2 border border-solid d-theme-border-grey-light" width="25%">Workout</th>
<th class="p-2 border border-solid d-theme-border-grey-light text-center" width="25%">Excercise</th>
<th class="p-2 border border-solid d-theme-border-grey-light">Set</th>
<th class="p-2 border border-solid d-theme-border-grey-light">Rep</th>
<th class="p-2 border border-solid d-theme-border-grey-light">Rest time</th>
<th class="p-2 border border-solid d-theme-border-grey-light">Tempo</th>
</tr>
<tr :id="'tr'+index" v-for="(row, index) in rows" :key="index">
<td class="p-2 border border-solid d-theme-border-grey-light">
<vs-select v-model="row.workoutname" v-validate="'required'" #change="getworkoutexcercises($event)" name="workoutname[]" :id="'wrkout'+index" class="w-full select-large" autocomplete>
<vs-select-item :key="game.id" :value="game.id" :text="game.workoutname" v-for="game in games" class="w-full" />
</vs-select>
</td>
<td class="p-2 border border-solid d-theme-border-grey-light">
<vs-select v-model="row.workoutcategory" v-validate="'required'" #change="getexcercisesrepcount($event,index)" name="workoutcategory[]" :id="'wrkcat'+index" class="w-full select-large" autocomplete>
<vs-select-item :key="game.id" :value="game.id" :text="game.subexname" v-for="game in subs" class="w-full" />
</vs-select>
</td>
<td class="p-2 border border-solid d-theme-border-grey-light"><input type="text" class="form-control" v-validate="'required'" v-model="row.exsets" name="set[]" :id="'set'+index" value="" /></td>
<td class="p-2 border border-solid d-theme-border-grey-light"><input type="text" class="form-control" v-validate="'required'" v-model="row.rep" name="rep[]" :id="'rep'+index" /></td>
<td class="p-2 border border-solid d-theme-border-grey-light"><input type="text" class="form-control" v-validate="'required'" v-model="row.resttime" name="resttime[]" :id="'resttime'+index" /></td>
<td class="p-2 border border-solid d-theme-border-grey-light"><input type="text" class="form-control" v-validate="'required'" v-model="row.tempo" name="tempo[]" :id="'tempo'+index" /></td>
<a v-on:click="removeElement(index);" style="cursor: pointer">
<x-circle-icon size="1.5x" class="custom-class" style="margin-left: 5px;margin-top: 15px;color:red;"></x-circle-icon>
</a>
</tr>
</table>
<button style="margin-top:15px;margin-bottom:15px;" class="button btn-primary" #click="addRow">Add row</button>
You shouldn't create HTML in vue components, rather you should use the virtual DOM and things like v-for to create the HTML you want instead of doing it with createElement.
And in the case that you do use the v-dom and you run into a similar issue, you want to use key to uniquely tag each node so Vue knows what belongs with what.
But main point is that you shouldn't be manipulating the DOM directly from Vue functions ever. It's performance taxing and you lose the benefit of reactivity, as you have noticed.
Look into v-for and vue's documentation on rendering lists for what you are trying to achieve: https://v2.vuejs.org/v2/guide/list.html

Vuejs2- Avoid repetition of select field options using vee-validate

Iam using vee-validate plugin for validation. In my form, there is a select field in the table. Rows will be added dynamically in the table. I don't want to select the same select(Description column) option again and again Image. Hence I want to throw an error like "Selected description already exists in a table" this using vee-validate. Kindly help me to solve this.
Here is my code:
<template>
<div>
<b-card>
<div class="panel-body" id="app">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 20px;">No.</th>
<th style="width: 330px;">Description</th>
<th style="width: 130px;" class="text-right">Charges</th>
<th style="width: 130px;">Total</th>
<th style="width: 130px;"></th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in rows" :key="row.qty">
<td>
{{ index +1 }}
</td>
<td>
<select class="form-control" v-model="row.billChgDesc" v-validate="'required|check'" :name="'billChgDesc' + index" data-vv-as="Description" #change="checkRepetation">
<option v-for="option in billChgDescOpt" v-bind:value="option.value"
:key="option.value"> {{ option.text }}
</option>
</select>
<span v-show=" errors.has('billChgDesc' + index)" class="is-danger">{{ errors.first('billChgDesc' + index) }}</span>
</td>
<td>
<input class="form-control text-right" type="text" v-model="row.charges" data-type="currency" v-validate="'required'" :name="'charges' + index" data-vv-as="Charges" >
<span v-show=" errors.has('charges' + index)" class="is-danger">{{ errors.first('charges' + index) }}</span>
<td>
<input class="form-control text-right" :value="row.qty * row.charges" number readonly />
<input type="hidden" :value="row.qty * row.charges * row.tax / 100" number/>
</td>
<td>
<button class="btn btn-primary btn-sm" #click="addRow(index)"><i class="fa fa-plus"></i></button>
<button class="btn btn-danger btn-sm" #click="removeRow(index)"><i class="fa fa-minus"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right">TOTAL</td>
<td colspan="1" class="text-right"><input class="form-control text-right" v-model="delivery" number/></td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</b-card>
</div>
</template>
<script>
import Vue from 'vue'
import accounting from 'accounting'
export default {
data: function () {
return {
billChgDescOpt: [
{ value: '', text: 'Select' },
{ value: 'M', text: 'Maintenance Fee'},
{ value: 'W', text: 'Water Charges'},
{ value: 'P', text: 'Penalty Fee'},
],
rows: [
{qty: 5, billChgDesc: '', charges: 55.20, tax: 10},
{qty: 19, billChgDesc: '', charges: 1255.20, tax: 20},
],
grandtotal: 0,
delivery: 40,
selectArr:[]
}
},
methods: {
addRow: function (index) {
try {
this.rows.splice(index + 1, 0, {});
} catch(e)
{
console.log(e);
}
},
removeRow: function (index) {
this.rows.splice(index, 1);
},
checkRepetation:function(){
this.$validator.extend('check', {
getMessage: field => '* Slected ' + field + ' already exists',
validate: function(value){
selectArr.push(value);
}
})
}
}
}
</script>
<style lang="scss" scoped>
.is-danger{
color: RED;
}
</style>
Thanks in advance.
You're on the right track, but a couple changes need to be made. When you call this.$validator.extend, that only needs to be done once - when your component is created. It attaches the check method to the validator, so then every time you have the attribute v-validate="'required|check'" in your HTML, it will run that check method.
In your check validator, you need to answer the question "is this value already selected". The answer is to go through the this.rows and see if any of them have the same billChgDesc property. Because this is in Vue, by the time the validator gets run, the row in question already does have that value, so you want to check if MORE than one row have that value. So, something like this:
mounted() {
var self = this;
this.$validator.extend('check', {
getMessage: field => '* Selected ' + field + ' already exists',
validate: function(value){
return (self.rows.filter(function(v){
return v.billChgDesc == value;
}).length <= 1);
}
});
}
This validator returns true if only one item has the given value. I'm using the built-in filter method of Array (see docs).
You can see an example of this all working here: https://jsfiddle.net/ryleyb/f9q50wx4/1/

vue.js does not correctly rerender compared to the vue instance object

I have a small issue with my vue template. The code is the following :
<template>
<div class="panel panel-default"
v-bind:id="'panel_'+noeud.id">
<div class="panel-heading">{{noeud.name}}</div>
<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Noeud</th>
<th>Poid</th>
</tr>
</thead>
<tbody>
<tr
v-for="noeud_poids in weightSorted"
v-if="noeud_poids.macro_zonning_noeud_id_2 != noeud.id"
is="macrozonningproximitenoeudpoids"
:noeud_poids="noeud_poids"
:noeud="noeud"
:noeuds="noeuds"
:delete_callback="delete_final"
:change_callback="update_line">
</tr>
<tr>
<td>
<select v-model="new_noeud">
<option value=""></option>
<option v-for="one_noeud in noeuds "
v-bind:value="one_noeud.id">{{one_noeud.name}}</option>
</select>
</td>
<td>
<input type="text" v-model="new_weight">
</td>
<td>
<input type="button" class="btn btn-primary" #click="addNoeudProximite" value="Ajouter"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: ['pnoeud', 'pnoeuds'],
data: function(){
return {
points: 0,
points_restants: 100,
new_weight:0,
new_noeud:0,
noeud:this.pnoeud,
noeuds:this.pnoeuds,
weightSorted:this.pnoeud.weightSorted
}
},
mounted() {
},
methods:{
delete_final(macro_zonning_noeud_id_2){
axios.delete("/macrozonning/proximite/",{
params:{
macro_zonning_noeud_id_2:macro_zonning_noeud_id_2,
macro_zonning_noeud_id_1:this.noeud.id
}
}).then((res) => {
Vue.delete(this.weightSorted, String(macro_zonning_noeud_id_2));
})
},
update_line(nb_points){
this.points_restants = this.points_restants - nb_points;
this.points = this.points + nb_points;
},
addNoeudProximite(){
axios.put('/macrozonning/proximite/', {
'macro_zonning_noeud_id_1': this.noeud.id,
'macro_zonning_noeud_id_2': this.new_noeud,
'weight': this.new_weight
}).then((res) => {
Vue.set(this.weightSorted, String(this.new_noeud), res.data);
});
}
}
}
</script>
When the function delete_final is executed on the last item of my list, the view is correctly rerendered as the last item of my list is removed. But when I try to remove the first item of my list then the view rerenders but the the last item has been removed. When I check the Vue object in devtools, it does not reflect the new view, but it reflects the action taken (my first item has been removed).
If you have any idea where this problem comes from it would be awesome.
Thanks a lot community
Use a key attribute on the element you are rendering with v-for so that vue can exactly identify VNodes when diffing the new list of nodes against the old list. See key attribute
<tr> v-for="noeud_poids in weightSorted" :key="noeud_poids.id" </tr>

Add data to a dynamic row from database

So for example i have a form where the data is added dynamically. When I click on a "+" there comes one more row, where i can add more data. And so on...
Like in this example.
function deleteRow(row)
{
var i=row.parentNode.parentNode.rowIndex;
document.getElementById('POITable').deleteRow(i);
}
function insRow()
{
console.log( 'hi');
var x=document.getElementById('POITable');
var new_row = x.rows[1].cloneNode(true);
var len = x.rows.length;
new_row.cells[0].innerHTML = len;
var inp1 = new_row.cells[1].getElementsByTagName('input')[0];
inp1.id += len;
inp1.value = '';
var inp2 = new_row.cells[2].getElementsByTagName('input')[0];
inp2.id += len;
inp2.value = '';
x.appendChild( new_row );
}
<div id="POItablediv">
<input type="button" id="addPOIbutton" value="Add POIs"/><br/><br/>
<table id="POITable" border="1">
<tr>
<td>POI</td>
<td>Latitude</td>
<td>Longitude</td>
<td>Delete?</td>
<td>Add Rows?</td>
</tr>
<tr>
<td>1</td>
<td><input size=25 type="text" id="latbox"/></td>
<td><input size=25 type="text" id="lngbox" readonly=true/></td>
<td><input type="button" id="delPOIbutton" value="Delete" onclick="deleteRow(this)"/></td>
<td><input type="button" id="addmorePOIbutton" value="Add More POIs" onclick="insRow()"/></td>
</tr>
</table>
So how can i save this data on a database, and then read this. I also need to save the count of the row´s. Is this even possible with JS?