hirolau
hirolau

Reputation: 13901

Model and Computed Property interaction in Vue.js

Using vue.js I am trying to build a simple task manager.

When a user clicks the "complete" checkbox I want two things to happen:

  1. If the "Show all tasks" is unchecked, hide the task.
  2. Send an ajax request to the server to mark the task as complete/open.

The impotent parts are shown below:

<div id="tasks-app">
<input type="checkbox" id="checkbox" v-model="show_all">
<label for="checkbox">Show all tasks</label><br>

<table class="table">
  <tr><th v-for="column in table_columns" v-text="column"></th><tr>
  <tr v-for="row in visibleTasks" :class="{danger: !row.daily_task.complete && row.daily_task.delayed, success: row.daily_task.complete}">
    <td v-text="row.task.name"></td>
    <td v-text="row.task.deadline"></td>
    <td v-text="row.daily_task.status"></td>
    <td v-text="row.daily_task.task_user"></td>
    <td>
      <input type="checkbox" v-on:change="updateStatus(row)" v-model="row.daily_task.complete" >Complete</input>
    </td>
    <td><input v-model="row.daily_task.delay_reason"></input></td>
</table>
</div>

And the VUE.js code:

app = new Vue({
  el: '#tasks-app',

  data: {
    table_columns: ['Task','Deadline','Status','User','Actions','Reason'],
    tasks: [],
    filter_string: '',
    show_all: false
  },

  computed: {
    visibleTasks() {

      show_all = this.show_all

      if(show_all){
         search_filter = this.tasks         
      }else{
         search_filter = _.filter(this.tasks,function(task){
            return !task.daily_task.complete;
         })
      }
      return search_filter
    }
  },

  methods: {
    updateStatus(row){
      var id = row.daily_task.id
      var complete = row.daily_task.complete
      if(complete){
        axios.get('set_task_complete/' + id)
      }else{
        axios.get('set_task_open/' + id)
      }
    }

  }

})

If the show all checkbox is checked, this works as expected. The data changes and then the updateStatus function is called.

If however the show all checkbox is unchecked, the visibleTasks will trigger and the logic for the updateStatus will fail, as the row will be hidden and the ID that is send to the server will be off by one. If the row hides before updateStatusis called the wrong row is passed to the updateStatus function.

I could solve this by adding a filter update at the end of updateStatus function but that does not seems to utilize the Vue.js library. Could someone help me what components of Vue you would use to solve this problem?

Upvotes: 0

Views: 212

Answers (3)

Fran&#231;ois Romain
Fran&#231;ois Romain

Reputation: 14393

You can simplify a lot if you separate the logic:

  1. Loop over every tasks (completed or not) to display them (without considering show_all)
  2. Update tasks with v-on:click="updateStatus(row)"
  3. On each tr, add v-show="show_all || !row.status.complete"

Upvotes: 1

kevguy
kevguy

Reputation: 4438

I've refactored your code a bit and it seems to work fine:

  1. you shouldn't use v-on:change, instead, use <input type="checkbox" v-on:click="updateStatus(row)" v-bind:checked="row.daily_task.complete">

  2. Don't update row.daily_task.complete straight away, update it only when the asynchronous axios is complete.

const fakeUpdateComplete = (id) => {
  return new Promise((resolve, reject) => { resolve(true); });
};

const fakeUpdateIncomplete = (id) => {
  return new Promise((resolve, reject) => { resolve(true); });
};

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

  data: {
    history: [],
    table_columns: ['Task','Deadline','Status','User','Actions','Reason'],
    tasks: [
      {
        task: {
          name: 'A',
          deadline: '2017-01-01',
        },
        daily_task: {
          id: 1,
          status: '',
          task_user: '',
          complete: true,
          delayed: false,
          delay_reason: ''
        }
      },
      {
        task: {
          name: 'B',
          deadline: '2017-01-02',
        },
        daily_task: {
          id: 2,
          status: '',
          task_user: '',
          complete: false,
          delayed: false,
          delay_reason: ''
        }
      },
      {
        task: {
          name: 'C',
          deadline: '2017-01-03',
        },
        daily_task: {
          id: 3,
          status: '',
          task_user: '',
          complete: false,
          delayed: false,
          delay_reason: ''
        }
      },
      {
        task: {
          name: 'D',
          deadline: '2017-01-03',
        },
        daily_task: {
          id: 4,
          status: '',
          task_user: '',
          complete: true,
          delayed: false,
          delay_reason: ''
        }
      },
      {
        task: {
          name: 'E',
          deadline: '2017-01-03',
        },
        daily_task: {
          id: 5,
          status: '',
          task_user: '',
          complete: false,
          delayed: false,
          delay_reason: ''
        }
      }
    ],
    filter_string: '',
    show_all: true
  },

  computed: {
    visibleTasks() {
      const show_all = this.show_all
      let search_filter;
      if(show_all){
         search_filter = this.tasks         
      }else{
        console.log(this.tasks);
        search_filter = this.tasks.filter(task => {
          return !task.daily_task.complete
        });
      }
      return search_filter
    }
  },

  methods: {
    updateStatus(row){
      const id = row.daily_task.id;
      const complete = !row.daily_task.complete;
       
      if (complete) {
        
        fakeUpdateComplete(id).then(() => {
           this.history.push(`Task ${row.task.name} with id ${row.daily_task.id} is marked as complete`);
           row.daily_task.complete = !row.daily_task.complete;
        });
      } else {
        fakeUpdateIncomplete(id).then(() => {
           this.history.push(`Task ${row.task.name} with id ${row.daily_task.id} is marked as incomplete`);
           row.daily_task.complete = !row.daily_task.complete;
        });
      }
      /*
      if(complete){
        axios.get('set_task_complete/' + id)
      }else{
        axios.get('set_task_open/' + id)
      }
      */
    }

  }

})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="tasks-app">
<input type="checkbox" id="checkbox" v-model="show_all">
<label for="checkbox">Show all tasks</label><br>

<table class="table">
  <tr><th v-for="column in table_columns" v-text="column"></th><tr>
  <tr 
    v-for="row in visibleTasks" 
    :class="{danger: !row.daily_task.complete && row.daily_task.delayed, success: row.daily_task.complete}"
  >
    <td>{{row.task.name}}</td>
    <td>{{row.task.deadline}}</td>
    <td>{{row.daily_task.status}}</td>
    <td>{{row.daily_task.task_user}}</td>
    <td>
      <input type="checkbox" v-on:click="updateStatus(row)" v-bind:checked="row.daily_task.complete">
      <label for="complete">Complete</label>
    </td>
    <td><input v-model="row.daily_task.delay_reason" /></td>
  </tr>
</table>
  <div>
    <div><b>Data:</b></div>
    <div>{{this.tasks}}</div>
  </div>
   <div>
    <div><b>History:</b></div>
    <div v-for="item in history">
      {{item}}
    </div>
  </div>
</div>

Upvotes: 1

Giap Nguyen
Giap Nguyen

Reputation: 628

Your problem is use both change event handle and model. Both actually, trigger at same time when you click on the checkbox.

<input type="checkbox" v-on:change="updateStatus(row)" v-model="row.daily_task.complete" >Complete</input>

You should edit your code use only v-on:change="updateStatus(row)". After updateStatus complete ajax calls toggle row.daily_task.complete to trigger visibleTasks to update your view.

updateStatus(row){
      var id = row.daily_task.id
      var complete = !row.daily_task.complete
      var p;
      if(complete){
        p = axios.get('set_task_complete/' + id)
      }else{
        p = axios.get('set_task_open/' + id)
      }
      p.then(() => row.daily_task.complete = complete)
    }

Upvotes: 1

Related Questions