0

I have a table that was generated dynamically using Vuejs. It has input elements in cells that render as readOnly. Each element has an 'edit' button and when it is click, it changes to 'save' and enables the input elements of that row for editing. When save is clicked, I would like to capture the new value entered and the previous value. I formatted my date field as mm/dd/yy from the original which is in this format 2019-10-10T07:00:00Z I am able to successfully format the date using momentjs but it doesn't seem to stick. The value entered is different from when I alert it. Any ideas of what I'm doing wrong? Do I need to refactor my code to allow me to do this because this has to be done for each field, which is to get access to new value and previous value.

<div id="app">
<table border=1 width=100%>
  <tr>
    <td>EDIT</td>
    <td v-for="editableKey in editableKeys" :key="editableKey" class="label">{{ editableKey }}</td>
  </tr>
  <tr v-for="(program, index) in programs">
    <td><button v-if="program.isReadOnly" v-on:click="editItem(program)">edit</button>&nbsp;<button @click="saveItem(program)" v-else>save</button></td>    
    <td><input type="text" v-bind:data-id="program.id" :readonly="program.isReadOnly" v-model="formatDate(program)"></td>  
    <td><input type="text" v-bind:data-id="program.id" :readonly="program.isReadOnly" v-model="program.company"></td>
    <td><input type="text" v-bind:data-id="program.id" :readonly="program.isReadOnly" v-model="program.funding"></td>
    <td><input type="text" v-bind:data-id="program.id" :readonly="program.isReadOnly" v-model="program.funded"></td>
    <td><select :class="bgColor(program)" type="text" v-bind:data-id="program.id" :disabled="program.isReadOnly" v-model="program.Recruit">
            <option>Internal</option>
            <option>Recruiting</option>
        </select>
      <!--<input  :class="bgColor(program)" type="text" v-bind:data-id="program.id" :readonly="program.isReadOnly" v-model="program.Recruit">--></td>
    <td><input type="text" v-on:change="" v-bind:data-id="program.id" :readonly="program.isReadOnly" v-model="program.program"></td>    
  </tr>
</table>

</div>



new Vue({
  el:"#app",
  data: () => ({
    programs: [],
    editableKeys: ['date', 'company', 'funding', 'funded', 'recruit', 'program'],
  }),
  created () {
    this.getPrograms();
  },
  methods: {
    getPrograms() {
     axios.get("https://my-json-server.typicode.com/isogunro/jsondb/Programs").then(response => {
       this.programs = response.data.map(program => ({
           ...program,
           isReadOnly: true
       }));
      }).catch(error => {
       console.log(error);
      });
    },
    editItem (program) {
      program.isReadOnly = false
    },
    saveItem (program) {
      program.isReadOnly = true
      console.log(program)
      alert("New Value: "+program.Date)
      alert("Previous Value: "+program.Date)
    },
    bgColor (program) {
      return program.funded === program.funding ? 'yellow' : 'white'
    },
    formatDate(program){
      var formatL = moment.localeData().longDateFormat('L');
      var format2digitYear = formatL.replace(/YYYY/g,'YY');
      return moment(program.Date).format(format2digitYear);
    },
    updateField(program){
      console.log(program)
      alert(program)
    }
  }
})

Here's a pen for some clarity. Thanks for any help that can be provided.

1 Answer 1

1

If you want it to behave in a way that the data in the input is different than what is being shown you'll have to decouple the input from the data.

v-model needs to havea a variable assigned so that the input can update its value. Right now you're using v-model="formatDate(program)" which cannot work, because passing a static result of the function cannot be reactive.

There are several ways of accomplishing this. Here is an example that just gets it done. There's certainly room for improvement and alternative implementations...

replace v-model with value and event listeners

This defines where the value comes from; a function generating a formated date. (note that using a function in template for rendering is not good practice. it's better to do this once and use a cached value, so that the value doesn't have to be recalculated)

<input
  type="text"
  v-bind:data-id="program.id"
  :readonly="program.isReadOnly"
  :value="formatDate(program)"
  @input="updateDate($event, program)"
>

update methods

    editItem (program) {
      program.isReadOnly = false
      program.tempDate = null; // added tempDate to be set to null initially
    },
    saveItem (program) {
      program.isReadOnly = true
      if(program.tempDate !== null) {
        // if tempDate is set, update Date
        program.Date = program.tempDate;
        // and clear tempDate
        program.tempDate = null;
      }
      console.log({...program})
    },
    // new method to convert value of the input from string to Date (if date passed is valid)
    updateDate(e, program ) {
      if(new Date(e.target.value).toString() !== 'Invalid Date'){
        program.tempDate = new Date(e.target.value)
      } else {
        program.tempDate = null
      }
    },

update

As mentioned, using functions to format values in template is not a good idea. Instead, reformat the data before you use it (see map function) once it is in the right format, you can use v-model just note that this example does not check for correctness of date format.

function toDDMMYY(date) {
  const [y, m, d] = (new Date(date)).toISOString().slice(0, 10).split('-')
  return `${d}/${m}/${y%100}`
}
new Vue({
  el: "#app",
  data() {
    return {
      test: "hello",
      programs: "",
      hide: true,
      editable: [],
    }
  },
  created: function() {
    this.getPrograms();
  },
  methods: {
    getPrograms: function() {
      axios.get("https://my-json-server.typicode.com/isogunro/jsondb/Programs").then((response) => {
          this.programs = response.data.map(row => ({
            ...row,
            dateFormatted: toDDMMYY(row.Date),
            editable: false
          }));
        })
        .catch(function(error) {
          console.log(error);
        });
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>

<div id="app">
  <table border=1 width=100%>
    <tr>
      <td width=10px>EDIT</td>
      <td>Date</td>
      <td>Program</td>
      <td>Company</td>
      <td>Funding</td>
      <td>Funded</td>
      <td>Recruit</td>
    </tr>
    <tr v-for="program in programs">
      <td>
        <button v-if="program.editable == false" @click="program.editable = true">edit</button>
        <button v-else @click="program.editable = false">save</button>
      </td>
      <td><input type="text" v-bind:data-id="program.id" :readonly="!program.editable" v-model="program.dateFormatted"></td>
      <td><input type="text" v-bind:data-id="program.id" :readonly="!program.editable" v-model="program.program"></td>
      <td><input type="text" v-bind:data-id="program.id" :readonly="!program.editable" v-model="program.company"></td>
      <td><input type="text" v-bind:data-id="program.id" :readonly="!program.editable" v-model="program.funding"></td>
      <td><input type="text" v-bind:data-id="program.id" :readonly="!program.editable" v-model="program.funded"></td>
      <td><input type="text" v-bind:data-id="program.id" :readonly="!program.editable" v-model="program.Recruit"></td>
    </tr>
  </table>

</div>

Sign up to request clarification or add additional context in comments.

12 Comments

Thanks for responding. I definitely don't want to do anything that isn't good practice so I think I will refactor it not to use objects, which is how I started out. I just couldn't figure out how to makes the input elements readOnly until edit was clicked. Here was my original pen Any thoughts?
wow. Thanks a lot! Makes sense. The spread operator is new to me, so I didn't even realize you can reformat the date before use. That is good to know.
you don't have to use a spread operator. you could also do Object.assign to make a copy and add additional props.
Object.assign({}, row, {dateFormatted: toDDMMYY(row.Date), editable: false}), the first empty object ({}) is there for immutability, it can be skipped
it's because the arrow function was setup to expect an object ()=>({}) take out the ({ before and the }) after
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.