Summer Sale! All accounts are 50% off this week.

Luka's avatar
Level 1

How to create an Editable Table with Vue.js

I have recently started to learn Laravel and try to now get into Vue.js at the same time. My project got a page,which displays a list of Users which get read out from the database. I now thought that it would be nice to be able to add a new User directly on that page so I looked around how I could achieve this with Vue and found a nice script. I now decided that it would be nice to have the option to "Edit" as well, so I tried to look around how to achieve this and got totally lost. I thought that a click on a column should maybe add a input field with the cell value in it, but how is this possible to achieve? How could I let my clickList function know which table cell I clicked and how would I add the input field back? And would I not need to send a form tag back as well to be able to save it ? Should I add a Form tag to around the and add a submit button next to the "trash" so that you have to click on that after you are finish to save the changes? Or is there a better solution? Does anyone know of a Tutorial I should read?

my view index.blade.php

 <div id="vue-wrapper">
<div class="content">
    <p class="text-center alert alert-danger" v-if="hasError">Please enter some value!</p>
    {{ csrf_field() }}
    <p class="text-center alert alert-success" v-if="hasDeleted">Deleted Successfully!</p>
    <div class="table table-borderless" id="table">
        <table class="table table-borderless" id="table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Postcode</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tr v-for="item in items">
                    <td v-on:click="clickList(item)">@{{ item.id }}</td>
                    <td v-on:click="clickList(item)">@{{ item.name }}</td>
                    <td>@{{ item.postcode }}</td>
                    <td @click.prevent="deleteItem(item)" class="btn btn-danger">
                        <span class="glyphicon glyphicon-trash"></span></td>
            </tr>
            <tr> 
            <div class="form-group row">
                <th></th>
                <th><input type="text" class="form-control" id="name" name="name" required v-model="newItem.name" placeholder=" Enter some name"></th>
                <th><input type="text" class="form-control" id="name" name="postcode" required v-model="newItem.postcode" placeholder=" Postcode"></th>
                <th><button class="btn btn-primary" @click.prevent="createItem()" id="name" name="name">
                <span class="glyphicon glyphicon-plus"></span> ADD </button>
                </th>
            </div>          
            </tr>
        </table>
    </div>
</div>
</div> 

resources\assets\js\app.js

  const app = new Vue({
 el: '#vue-wrapper',

data: {
  items: [],
  hasError: false,
  hasDeleted: false,
  but: '',
  newItem : {'name':''}
 },
 mounted : function(){
    this.getVueItems();
 },
methods : {
    getVueItems: function(){
        axios.get('/vueitems').then(response => {
        this.items = response.data;
        
      });
    },
    createItem: function(){
            this.hasDeleted = false;
        var input = this.newItem;
        if(input['name'] == ''){
            this.hasError = true;
            this.hasDeleted = false;
        }
        else{
            this.hasError = false;
        axios.post('/vueitems',input)
          .then(response => {
              this.newItem = {'name':''};
              this.getVueItems();
          });
        this.hasError = false;
            }
},
    
    clickList: function (item) {
              var $row = $(item).parents('tr');  //accede a la fila
            var $cols = $row.find('td');  //lee campos
            console.log("clickList fired with " + $row +item.id);
    },
    
    
    
    editItem: function(but){
            var $row = $(but).parents('tr');  //accede a la fila
            var $cols = $row.find('td');  //lee campos
            if (ModoEdicion($row)) return;  //Ya está en edición
            //Pone en modo de edición
            IterarCamposEdit($cols, function($td) {  //itera por la columnas
    var cont = $td.html(); //lee contenido
                var div = '<div style="display: none;">' + cont + '</div>';  //guarda contenido
                var input = '<input class="form-control input-sm"  value="' + cont + '">';
                $td.html(div + input);  //fija contenido
            });
            FijModoEdit(but);
    },
    
    saveItem: function(item){
    
    },
deleteItem: function(item){
    axios.post('/vueitems/'+item.id).then((response) => {
        this.getVueItems();
        this.hasError = false,
        this.hasDeleted = true
    });
  },
  }
  });
0 likes
8 replies
MaverickChan's avatar

it is really not that hard .

1 . make the td conetenteditable

2 . on td , add 2 event listener , @blur @focus

3 . when focus , read data and edit content , when blur , save back to database.

Luka's avatar
Level 1

Thank you to both of you. I have somehow worked out below. This now turns my td cell into an input field, but how would I now identify which field I clicked on? I need to know the recordId I have to update and which field because in the end I will have multiple cells which I might update and I suppose I should update my db as soon I click enter so I need the recordID and which column to update or is there a better way? should I give the a class which contains the id and the name of the column?

        <tr v-for="item in items">
                    <td id="item.id">@{{ item.id }}</td>
                    <td>
                        <div v-show = "item.edit == false">
                            <label @dblclick = "item.edit = true"> @{{item.name}} </label>
                        </div>
                        <input v-show = "item.edit == true" 
                               v-model = "item.name" 
                               v-on:blur= "item.edit=false;" 
                               @keyup.enter = "item.edit=false; update(item.name)">
                    </td>
                    <td>@{{ item.postcode }}</td>
                    <td @click.prevent="deleteItem(item)" class="btn btn-danger">
                        <span class="glyphicon glyphicon-trash"></span>
                    </td>   
            </tr>


   methods : {
        editTodo: function(todo) {
            this.editedTodo = todo;
        },
  
        getVueItems: function(){
           axios.get('/vueitems').then(response => {

        $.each(response.data, function(i, v) {
             response.data[i].edit =  false;
        });
        this.items = response.data;
        
        });
      },
        update: function(item){
         var $row = $(item).prev('td');  
         console.log($row.id);
      },
1 like
topvillas's avatar

I wouldn't send an update straight away.

Give the items a "dirty" property as the same time as you're giving them an "edit" property. When you've edited one of the items set it's dirty property to true.

Then have a save button that filters the items by their dirty property and send a json array to the sever to deal with.

Luka's avatar
Level 1

Not sure that I understand you right, but I think you suggest to have a "Save" Button at the end? I am not sure about it since people might forget that they have to click this button otherwise their changes are lost. There shouldn't be too many changes anyway, just maybe the Phone Number or when the Admin sees he made a mistake... so if it updates straight after clicking enter would be fine.

topvillas's avatar

Yeah, it was just a suggestion. If the use case it that the admin will make occasional changes then I think what you're doing is fine. But make sure the user knows that something's happening.

Luka's avatar
Level 1

Thanks, I can see that your solution would be good if it is a full grid with lots of changes. But mine will be a list of maybe 20-30 rows and maybe 10 columns. On the bottom of the list will be the option to enter a new User to the list and then each row got a delete button so you can remove the User again from that list and then it might be the odd change specially when you discover that you made a mistake while you added the person. The change can only be done when you double-click on the cell so it should not happen my accident as well. I just wonder how to achieve that.

Luka's avatar
Level 1

Everytime I think I am nearly there I run into a new problem. I have now figured out how to make the input field visible to change the entry, but then I realised that I will face a problem, when I have multiple columns. Right now it is obvious that when I doubleclick on name, postcode also turns into an input field. That makes perfect sense since I check for item.edit and I use the same for both. As you can see, I inject this in my getVueItems method with the default value false. I don't want to make up multiple edit now and inject them, is there any nicer way to achieve this?

  <td>
            <div v-show = "item.edit == false">
                <label @dblclick = "item.edit = true"> @{{item.name}} </label>
            </div>
            <input name="name" 
                               v-show = "item.edit == true" 
                               v-model = "item.name" 
                               v-on:blur= "item.edit=false;" 
                               @keyup.enter = "item.edit=false; update(item.name,$event)">
  </td>
                    
  <td>   
            <div v-show = "item.edit == false">
                <label @dblclick = "item.edit = true"> @{{item.postcode}} </label>
            </div>
            <input name="postcode" 
                               v-show = "item.edit == true" 
                               v-model = "item.postcode" 
                               v-on:blur= "item.edit=false;" 
                               @keyup.enter = "item.edit=false; update(item.postcode,$event)">
  </td>

Please or to participate in this conversation.