0

I'm currently struggling with a small VueJS application for filtering among a API response, from multiple values based of drop-downs.

I could manage two filters, if one was declared outside the if (filtered) and the second condition declared inside.

However I'm wondering how's the case for e.g more than just two options. Code attached below.

// Trunkera beskrivning
Vue.filter("truncate", function (value, limit) {
  if (value.length > limit) {
value = value.substring(0, limit - 3) + "...";
  }
  return value;
});

new Vue({
  el: "#app",
  data: function () {
return {
  visible: false,
  boats: null,
  filter: {
    options: {
      brands: [],
      model: [],
      engineBrands: [],
      engineModels: [],
      type: [],
      maxPrice: null,
      minPrice: null,
      years: [],
    }
  },
  selected: {
    options: {
      brand: null,
      length: 1000,
      condition: null,
      engineBrand: null,
      engineModel: null,
      engineType: null,
      fridge: null,
      kitchen: null,
      type: null,
      shower: null,
      year: null,
      water: null,
      wc: null
    }
  }
};
  },
  created() {
const vm = this;
fetch(
    'https://www.sokbat.se/api/Ad?json=%7b"CompanyId":"5055","AdCategoryId":"10","SortOrder":0,"StartAd":0,"NumberOfAds":0%7d'
  )
  .then((response) => {
    return response.json();
  })
  .then((data) => {
    let arr = [];
    // fyll på båtar
    vm.boats = data;
    // forEach...hämta detaljer
    /*data.forEach((boat) => {
      fetch(`https://www.sokbat.se/api/ad/${boat.AdId}`)
        .then((response) => {
          arr.push(response.json())
      })
    })*/
    this.calcAttributes(data);
    //vm.boats = arr;
  });
  },
  computed: {
computed_items: function () {
  const vm = this;
  let filterType = this.selected.options.type,
    filterEngineModel = this.selected.options.engineModel,
    filterBrand = this.selected.options.brand,
    // Ranges
    filterPrice = this.selected.options.price,
    filterYear = this.selected.options.year,
    filterLength = this.selected.options.length,
    filterWidth = this.selected.options.width;

  return this.boats.filter(function (item) {
    let filtered = true;

    if (filtered) {
      // Båttyp
      if (filterType && filterType.length > 0) {
        filtered = item.MotoBoatTypeSelectionCaption == filterType;
      }
      // Märke
      if (filterBrand && filterBrand.length > 0) {
        filtered = item.Brand == filterBrand;
      }
      // Båtmotor
      if (filterEngineModel && filterEngineModel.length > 0) {
        filtered = item.EngineModel == filterEngineModel;
      }
      // Motorår
      if (filterYear && filterYear != "") {
        filtered = item.BoatYear == filterYear;
      }
      /* Pris
      if (filterPrice && filterPrice[0] > vm.filter.options.minPrice) {
        filtered = item.Price >= filterPrice[0]
      }
      // Längd
      if (filterWidth && filterWidth[0] >= vm.filter.options.minWidth) {
        filtered = item.Width >= filterWidth[0]
      }
      if (filterLength && filterLength[0] >= vm.filter.options.minLength) {
        filtered = item.Length >= filterLength[0]
      }*/
    }
    return filtered;
  });
}
  },
  mounted: function () {
const vm = this;
let i = 0;
this.$watch('selected', function () {
  console.log(vm.computed_items);
}, {deep:true})
  },
  methods: {
calcAttributes(data) {
  const vm = this;
  let tmp_brands = [],
    tmp_models = [],
    tmp_yrs = [],
    tmp_width = [],
    tmp_types = [];

  data.forEach((item) => {
    tmp_brands.push(item.Brand);
    tmp_models.push(item.EngineModel);
    tmp_yrs.push(item.BoatYear);
    tmp_types.push(item.MotoBoatTypeSelectionCaption)
  });

  let maxPrice = 0,
    minPrice = 0,
    minWidth = 0,
    maxWidth = 0,
    minLength = 0,
    maxLength = 0;
  data.forEach(item => {
    if (item.Price >= maxPrice) {
      maxPrice = item.Price
    }
    if (item.Price <= maxPrice) {
      minPrice = item.Price
    }
    //
    if (item.Width >= maxWidth) {
      maxWidth = item.Width
    }
    if (item.Width <= minWidth) {
      minWidth = item.Width
    }
    //
    if (item.Length >= maxLength) {
      maxLength = item.Length
    }
    if (item.Length <= minLength) {
      minLength = item.Length
    }
  });
  // min/max bredd
  // unique's
  vm.filter.options.maxPrice = maxPrice;
  vm.filter.options.minPrice = minPrice;
  //
  vm.filter.options.maxWidth = maxWidth;
  vm.filter.options.minWidth = minWidth;
  vm.filter.options.type = Array.from(new Set(tmp_types));
  //
  vm.filter.options.maxLength = maxLength;
  vm.filter.options.minLength = minLength;
  vm.filter.options.brands = Array.from(new Set(tmp_brands))
  vm.filter.options.engineModels = Array.from(new Set(tmp_models))
  vm.filter.options.years = Array.from(new Set(tmp_yrs));
}
  }
});
[v-cloak]{display:none}body{font-family:Ubuntu}.filter-box{background:#333;border-radius:5px;padding:1rem;margin:2rem}.results{padding:1rem;margin:2rem}.el-row{margin-bottom:20px}.el-row:last-child{margin-bottom:0}.el-col{border-radius:4px}.bg-purple-dark{background:#99a9bf}.bg-purple{background:#d3dce6}.bg-purple-light{background:#e5e9f2}.grid-content{border-radius:4px;min-height:36px}.row-bg{padding:10px 0;background-color:#f9fafc}.el-select{width:100%}.el-input__inner{color:#333!important}.inline>p{color:#fff;margin:0;padding:0}.boat-card{margin:10px}.time{font-size:16px;line-height:20px;color:#999}.time p{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.time p br{display:none}.bottom{margin-top:13px;line-height:12px}.button{padding:0;float:right}.image{width:100%;display:block}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}input::placeholder{color:#333!important}.el-col.el-col-8.el-col-xs-24.el-col-sm-12.el-col-md-8.el-col-lg-6{min-height:350px}.fade-enter-active,.fade-leave-active{transition:opacity .5s}.fade-enter,.fade-leave-to{opacity:0}a{text-decoration:none}
<div id="app" v-cloak>

  <div class="filter-box">

    <el-row :gutter="20">
      <el-col :span="6">
        <el-select v-model="selected.options.brand" placeholder="Märke">
          <el-option default label="Alla" value=""></el-option>
          <el-option v-for="item in filter.options.brands" :key="item" :label="item" :value="item">
          </el-option>
        </el-select>
      </el-col>

      <el-col :span="12">
        <div class="inline">
          <p>Pris</p>
          <el-slider v-model="selected.options.price" range :min="filter.options.minPrice" :max="filter.options.maxPrice">
          </el-slider>
        </div>
      </el-col>

      <el-col :span="6">
        <el-select v-model="selected.options.engineModel" placeholder="Motormodell">
          <el-option default label="Alla" value=""></el-option>
          <el-option v-for="item in filter.options.engineModels" :key="item" :label="item" :value="item">
          </el-option>
        </el-select>
      </el-col>

    </el-row>


    <el-row :gutter="20">
      <el-col :span="6">
        <el-select v-model="selected.options.type" placeholder="Båttyp">
          <el-option default label="Alla" value=""></el-option>
          <el-option v-for="item in filter.options.type" :key="item" :label="item" :value="item">
          </el-option>
        </el-select>
      </el-col>

      <el-col :span="6">
        <div class="inline">
          <p>Bredd</p>
          <el-slider v-model="selected.options.width" range :min="filter.options.minWidth" :max="filter.options.maxWidth">
          </el-slider>
        </div>
      </el-col>

      <el-col :span="6">
        <div class="inline">
          <p>Längd</p>
          <el-slider v-model="selected.options.length" range show-stops :min="filter.options.minLength" :max="filter.options.maxLength">
          </el-slider>
        </div>
      </el-col>

      <el-col :span="6">
        <el-select v-model="selected.options.year" placeholder="År">
          <el-option default label="Alla" value=""></el-option>
          <el-option v-for="item in filter.options.years" :key="item" :label="item" :value="item">
          </el-option>
        </el-select>
      </el-col>

    </el-row>

  </div>

  <div v-if="boats.length > 1" class="results">
    <el-row>

      <el-col v-for="(boat, i) in computed_items" :span="8" :key="i" :xs="8" :sm="8" :md="8" :lg="6">
        <transition name="fade">
          <a :href="`https://marine.local/bat/?id=${boat.AdId}`" target="_blank">
            <el-card class="boat-card" :body-style="{ padding: '8px' }" shadow="hover">
              <img :src=`${boat.AdResourceURI}` class="image">
              <div style="padding: 14px;">
                <span>{{boat.AdTitle}}</span>
                <div class="bottom clearfix">
                  <time class="time" :inner-html.prop="boat.AdIntroduction | truncate(60)"></time>
                  <el-button type="text" class="button">
                    {{boat.Price.toLocaleString('sv-SE', { style: 'currency', currency: 'SEK' })}}
                  </el-button>
                </div>
              </div>
            </el-card>
          </a>
        </transition>

      </el-col>

    </el-row>
  </div>

</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" rel="stylesheet" />
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

2
  • 1
    please show the output array. Commented Apr 28, 2020 at 16:33
  • @EugenSunic Added a watcher with console.log statement! :-) Commented Apr 28, 2020 at 16:47

1 Answer 1

1

You already have the filters in data.filter.options. Use them. You might need to add minLength, maxLength, and some more.

You also have to make sure that the name of these variables in this.filter.options match the variables in your boats, e.g. brand instead of brands.

And I would make the year a range value (min-max) instead of an array. Up to you.

computed: {
  filtered_boats: function () {
    let filtered = this.boats;

    // the minimums
    let mins = {
      "minPrice": 'price',
      "minLength": 'length',
      ...
    };
    filtered = Object.keys(mins).forEach(k => if (this.filter.options[k] !== null) filtered = filtered.filter(boat => boat[mins[k]] >= this.filter.options[k]));

    // the maximums
    let maxs = {
      "maxPrice": 'price',
      "maxLength": 'length',
      ...
    };
    filtered = Object.keys(maxs).forEach(k => if (this.filter.options[k] !== null) filtered = filtered.filter(boat => boat[maxs[k]] <= this.filter.options[k]));

    // the multi-value filters
    let fields = ['brand', 'model', 'type', ...];
    filtered = fields.forEach(f => if (this.filter.options[f].length > 0) filtered = filtered.filter(boat => this.filter.options[f].indexOf(boat[f]) != -1));

    return filtered;
  }
}

Sorry, I couldn't test the code. But I hope you get the idea!

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

3 Comments

Thanks for the answer, I tried it out but can't get any order out of it. I've implemented a shorter solution for multi-value filters, and renamed filter/selected options into same Key names as each item too. codepen.io/jackbillstrom/pen/WNQOmeR?editors=1011
Hi! I had a look at your code. I couldn't get the min-max filters to work because they do not send their values back to Vue. The brand, engine type, etc work as expected. I would suggest using basic HTML inputs (e.g. two selects for price, one min and one max) and once it works, enhance it with the slider. codepen.io/jlsalinas/pen/PoPjgEa?editors=1011
Hello once again, wow I appreciate that alot. I think I was overthinking it while trying to map my own custom keys against the search results, also debugged alot just now and say that I could've gotten results If I didn't send in empty key values. But in overall, thanks once again! :-) Stay safe

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.