3

I'm learning about vue.js, and I'm trying out a filter feature using range date. The scenario is: first filter by type, then filter by date range (there are start and end dates). The results will appear when the end date is selected. I have created the first filter, which is type, and it works. I have searched various ways for the date range, but still can't find the right one. How do I filter date ranges using computed or methods? If anyone knows, can you please tell me step by step?

new Vue({
  el: '#app',
  data: {
    selectedType: '',
    startDate:null,
    endDate:null,
    items: [
      {
        name: 'Nolan',
        type: 'mercedes',
        year: '2020',
        country: 'england',
        date: '08/01/2020'
      },
      {
        name: 'Edgar',
        type: 'bmw',
        year: '2020',
        country:'belgium',
        date: '08/11/2020'
      },
      {
        name: 'John',
        type: 'bmw',
        year: '2019',
        country: 'england',
        date: '08/21/2020'
      },
      {
        name: 'Axel',
        type: 'mercedes',
        year: '2020',
        country: 'england',
        date: '08/01/2020'
      }
    ]
  },
  computed: {
    filterItem: function () {
      
      let filterType = this.selectedType
      if (!filterType) return this.items;  // when filterType not selected

      let startDate = this.startDate && new Date(this.startDate);
      let endDate = this.endDate && new Date(this.endDate);
      
      return this.items.filter(item => {
        return item.type == filterType;
      }).filter(item => {
        const itemDate = new Date(item.date)
        if (startDate && endDate) {
          return startDate <= itemDate && itemDate <= endDate;
        }
        if (startDate && !endDate) {
          return startDate <= itemDate;
        }
        if (!startDate && endDate) {
          return itemDate <= endDate;
        }
        return true;  // when neither startDate nor endDate selected
      })
      
    }
  }
})
.list-item {
  margin-top: 50px;
}

#app {
  position: relative;
  padding-bottom: 200px;
}

span {
  margin: 0 15px;
  cursor: pointer;
}

.filter-box {
  margin-top: 15px;
}

.card {
  box-shadow: 0px 10px 16px rgba(0, 0, 0, 0.16);
  width: 400px;
  padding: 20px 30px;
  margin-bottom: 30px;
}

button {
  background-color: #1cf478;
  border: none;
  padding: 10px 25px;
  font-weight: bold;
  border-radius: 15px;
}

select,input {
  border: none;
  padding: 10px 15px;
  background-color: #c1c1c1;
  border-radius: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="app">
  <label for="">Type</label>
  <select v-model="selectedType">
    <option value="" disabled selected hidden>Type</option>
    <option value="mercedes">Mercedes</option>
    <option value="bmw">BMW</option>
  </select>
  
  <label for="">From</label>
  <input type="date" v-model="startDate">
  
  <label for="">To</label>
  <input type="date" v-model="endDate">
  
  <div class="list-item" v-for="item in filterItem">
    <div class="card">
      <p>Name: {{ item.name }}</p>
      <p>Car: {{ item.type }}</p>
      <p>Date: {{ item.date }}</p>
      <p>Country: {{ item.country }}</p>
    </div>
  </div>
</div>

1
  • How you tried to implement the data filter? Commented Feb 14, 2021 at 7:45

2 Answers 2

4

Here's how you could fix it:

  1. Create startDate and endDate as Dates. However, note that the date picker uses ISO string format (yyyy-mm-dd), which is UTC. The data contains locale specific dates (mm/dd/yyyy), so Date parses the string with local time zone. We need to normalize the dates so we're comparing dates with the same time zone offset (local preferred):

    methods: {
      localizeDate(date) {
        if (!date || !date.includes('-')) return date
        const [yyyy, mm, dd] = date.split('-')
        return new Date(`${mm}/${dd}/${yyyy}`)
      }
    }
    
    let startDate = this.localizeDate(this.startDate);
    let endDate = this.localizeDate(this.endDate);
    
  2. The date comparisons in your if-else is explicitly checking for null, but their values wouldn't be null since you're instantiating Date objects. With the update in step 1, the date values are either Date or false, so you could update your comparisons to check for a falsy value instead of null:

    if (startDate && endDate) {/*...*/}
    if (startDate && !endDate) {/*...*/}
    if (!startDate && endDate) {/*...*/}
    
  3. selectedType is never null, but you can check if it's falsy to determine if the type is unspecified. In that case, you could just return the original array to avoid the unnecessary call to Array.prototype.filter:

    const itemsByType = filterType ? this.items.filter(item => item.type === filterType) : this.items
    
  4. Since you want to display the date as MMM dd, yyyy, you could use the "long" date style of Intl.DateTimeFormat in a component method, and invoke it from the template:

    methods: {
      formatDate(date) {
        return new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }).format(new Date(date))
      }
    }
    
    <p>Date: {{ formatDate(item.date) }}</p>
    

new Vue({
  el: '#app',
  data: {
    selectedType: '',
    startDate:null,
    endDate:null,
    items: [
      {
        name: 'Nolan',
        type: 'mercedes',
        year: '2020',
        country: 'england',
        date: '08/01/2020'
      },
      {
        name: 'Edgar',
        type: 'bmw',
        year: '2020',
        country:'belgium',
        date: '08/11/2020'
      },
      {
        name: 'John',
        type: 'bmw',
        year: '2019',
        country: 'england',
        date: '08/21/2020'
      },
      {
        name: 'Axel',
        type: 'mercedes',
        year: '2020',
        country: 'england',
        date: '08/01/2020'
      }
    ]
  },
  computed: {
    filterItem() {
      let filterType = this.selectedType;
      let startDate = this.localizeDate(this.startDate);
      let endDate = this.localizeDate(this.endDate);
      
      const itemsByType = filterType ? this.items.filter(item => item.type === filterType) : this.items
      return itemsByType
        .filter(item => {
          const itemDate = new Date(item.date)
          if (startDate && endDate) {
            return startDate <= itemDate && itemDate <= endDate;
          }
          if (startDate && !endDate) {
            return startDate <= itemDate;
          }
          if (!startDate && endDate) {
            return itemDate <= endDate;
          }
          return true;
        })
    }
  },
  methods: {
    localizeDate(date) {
      // Date picker uses ISO format (yyyy-mm-dd), which is UTC. The data
      // contains locale specific date strings (mm/dd/yyyy), which `Date`
      // parses with local time-zone offset instead of UTC. Normalize the
      // ISO date so we're comparing local times.
      if (!date || !date.includes('-')) return date
      const [yyyy, mm, dd] = date.split('-')
      return new Date(`${mm}/${dd}/${yyyy}`)
    },
    formatDate(date) {
      return new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }).format(new Date(date))
    }
  }
})
.list-item {
  margin-top: 50px;
}

.card {
  box-shadow: 0px 10px 16px rgba(0, 0, 0, 0.16);
  width: 400px;
  padding: 20px 30px;
  margin-bottom: 30px;
}

select,input {
  border: none;
  padding: 10px 15px;
  background-color: #c1c1c1;
  border-radius: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <label for="">Type</label>
  <select v-model="selectedType">
    <option value="" disabled selected hidden>Type</option>
    <option value="mercedes">Mercedes</option>
    <option value="bmw">BMW</option>
  </select>
  
  <label for="">From</label>
  <input type="date" v-model="startDate">
  
  <label for="">To</label>
  <input type="date" v-model="endDate">
  
  <div class="list-item" v-for="item in filterItem">
    <div class="card">
      <p>Name: {{ item.name }}</p>
      <p>Car: {{ item.type }}</p>
      <p>Date: {{ formatDate(item.date) }}</p>
      <p>Country: {{ item.country }}</p>
    </div>
  </div>
</div>

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

Comments

2

This question is more about js, not vuejs. Use Date type, not string type for date data. It is comparable, for example, new Date('2020-01-01') < new Date('2020-01-02') is true

  computed: {
    filterItem: function () {
      const filterType = this.selectedType  // string or null
      const startDate = this.startDate;    // Date or null
      const endDate = this.endDate;  // Date or null
      
      return this.items.filter(item => {
        if (filterType === null) return true;  // when filterType not selected
        return item.type == filterType;
      }).filter(item => {
        const itemDate = new Date(item.date)
        if (startDate !== null && endDate !== null)) {
          return startDate <= itemDate && itemDate <= endDate;
        }
        if (startDate !== null && endDate === null) {
          return startDate <= itemDate;
        }
        if (startDate === null && endDate !== null) {
          return                          itemDate <= endDate;
        }
        return true;  // when neither startDate nor endDate selected
      })
    }

2 Comments

i've edited my post, and adding your code there. but it doesn't work
Did you get error on console? I guess startDate and endDate willl be string, not Date. You might need to cast.

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.