How to build a pricing slider - Vue
Tags: vue, javascript, tutorial, showdev
Published: 2020-03-23 14:14:27

In the last article of this series, I am going to show you how to create the same exact **pricing component in VueJS**.

That's going to be a piece of cake since it's just a matter of following the same [approach I used for React](https://dev.to/cruip/how-to-build-a-pricing-slider-in-react-59cc).

Let's create a **Pricing.vue** template file, and add the HTML 👇

```vue
<template>
  <div class="pricing">
    <div class="pricing-slider center-content">
      <label class="form-slider">
        <span>How many users do you have?</span>
        <input type="range">
      </label>
      <div class="pricing-slider-value">
        <!-- {{ Current slider value }} -->
      </div>
    </div>

    <div class="pricing-items">
      <div class="pricing-item">
        <div class="pricing-item-inner">
          <div class="pricing-item-content">
            <div class="pricing-item-header center-content">
              <div class="pricing-item-title">Basic</div>
              <div class="pricing-item-price">
                <span class="pricing-item-price-currency"></span>
                <span class="pricing-item-price-amount">Free</span>
              </div>
            </div>
            <div class="pricing-item-features">
              <ul class="pricing-item-features-list">
                <li class="is-checked">Excepteur sint occaecat</li>
                <li class="is-checked">Excepteur sint occaecat</li>
                <li class="is-checked">Excepteur sint occaecat</li>
                <li>Excepteur sint occaecat</li>
                <li>Excepteur sint occaecat</li>
              </ul>
            </div>
          </div>
          <div class="pricing-item-cta">
            <a class="button" href="http://cruip.com/">Buy Now</a>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Pricing"
};
</script>
```
##Shaping input and output data
We already have a **data scheme** with *input* and *output* values (check out the [first article](https://dev.to/cruip/how-to-build-a-pricing-slider-html-vanilla-js-53d0#shaping-input-and-output-data) of this series to know more).

| Key | Slider value | Price, currency | Price, amount | Price, after |
|-----|:------------:|:---------------:|:-------------:|-------------:|
| 0   | 1,000        |                 |Free           |              |
| 1   | 1,250        | $               |13             |/m            |
| 2   | 1,500        | $               |17             |/m            |
| 3   | 2,000        | $               |21             |/m            |
| 4   | 2,500        | $               |25             |/m            |
| 5   | 3,500        | $               |42             |/m            |
| 6   | 6,000        | $               |58             |/m            |
| 7   | 15,000       | $               |117            |/m            |
| 8   | 50,000       | $               |208            |/m            |
| 9   | 50,000+      |                 |Contact us     |              | 
We will use the `data` object to contain those values.

##Defining the component data
React has a built-in data function that returns an object. Here is where we will store *input range* and *data scheme* values.
```javascript
data() {
  return {
    priceInputValue: "0",   // initial input value
    priceInput: {           // slider values
      0: "1,000",
      1: "1,250",
      2: "1,500",
      3: "2,000",
      4: "2,500",
      5: "3,500",
      6: "6,000",
      7: "15,000",
      8: "50,000",
      9: "50,000+"
    },
    priceOutput: {          // output values
      plan1: {
        0: ["", "Free", ""],
        1: ["$", "13", "/m"],
        2: ["$", "17", "/m"],
        3: ["$", "21", "/m"],
        4: ["$", "25", "/m"],
        5: ["$", "42", "/m"],
        6: ["$", "58", "/m"],
        7: ["$", "117", "/m"],
        8: ["$", "208", "/m"],
        9: ["", "Contact Us", ""]
      }
    }
  };
}
```
Now bind the `priceInputValue` to the input range with the `v-model` directive 
```jsx
<input type="range" v-model="priceInputValue" />
```
##Setting range slider attributes
Create a `ref` on the **input range element**
```jsx
<input type="range" ref="slider" v-model="priceInputValue">
```  
Let's set the range slider `min`, `max` attributes, right after the component output has been rendered to the DOM 👇
```jsx
mounted() {
  this.$refs.slider.setAttribute("min", 0);
  this.$refs.slider.setAttribute(
    "max",
    Object.keys(this.priceInput).length - 1
  );
}
```
We have now a range slider whose values go from 0 to 9! 🙌

Here is a recap of what we have built so far:
{% codesandbox t18zr %}

##Binding input and output data
We have a working range slider, but it is still disconnected from the visualized price.

We need another method that **retrieves the data to be outputted**, accordingly with the current input value
```javascript
getPricingData(obj, pos) {
  return pos !== undefined
    ? obj[this.priceInputValue][pos]
    : obj[this.priceInputValue];
}
```
This method has two parameters:
* `obj` - The input or output object we want to retrieve data from
* `pos` (optional) - The position of a required element in the array, if there is any. It is required for the *plan* objects, since to each key corresponds an array of values (e.g. `0: ["", "Free", ""], ...`).

So, to output the **current slider value**, we will call the method like this 👇
```vue
<div class="pricing-slider-value">{{ getPricingData(priceInput) }}</div>
```
And here is how to output the **price data** instead 👇
```vue
<div class="pricing-item-price">
  <span class="pricing-item-price-currency">
    {{ getPricingData(this.priceOutput.plan1, 0) }}
  </span>
  <span class="pricing-item-price-amount">
    {{ getPricingData(this.priceOutput.plan1, 1) }}
  </span>
  {{ getPricingData(this.priceOutput.plan1, 2) }}
</div>
```
{% codesandbox hk798 %}
##Adjusting the slider value element position
Almost there. 🏁 We want the slider value to be following the slider thumb.

We need a new method for that but, before proceeding, there is another DOM element that needs to be referenced.

Create a `ref` for the **current slider value**
```vue
<div ref="sliderValue" class="pricing-slider-value">{{ getPricingData(priceInput) }}</div>
```
Get the slider thumb size from a CSS property 👍
```javascript
this.thumbSize = parseInt(
  window
    .getComputedStyle(this.$refs.sliderValue)
    .getPropertyValue("--thumb-size"),
  10
);
```
Now that we have referenced the `.pricing-slider-value` element, let's create the method we need.
```javascript
handleSliderValuePosition(input) {
  const multiplier = input.value / input.max;
  const thumbOffset = this.thumbSize * multiplier;
  const priceInputOffset =
    (this.thumbSize - this.$refs.sliderValue.clientWidth) / 2;
  this.$refs.sliderValue.style.left =
    input.clientWidth * multiplier - thumbOffset + priceInputOffset + "px";
},
```
Here is a visual representation of what the method does 👇
![Slider positioning adjustment](https://dev-to-uploads.s3.amazonaws.com/i/7e0wbwqbf7rexl0xg09g.png)
Call the method as soon as the component has rendered.
```javascript
mounted() {
  this.handleSliderValuePosition(this.$refs.slider);
}
```
We also need to call that method every time the input range value changes, so let's use the `v-on` directive to listen to element input event:
```vue
<input
  type="range"
  ref="slider"
  v-model="priceInputValue"
  @input="handleSliderValuePosition($event.target)"
>
```
##Conclusion
Here is the final result. Click on *Open Sandbox* to see the full code.
{% codesandbox 0gngs %}

I hope you enjoyed this tutorial. If you want to see this in action, here is a landing page template where it’s implemented 👉 **[Evelyn](https://cruip.com/demos/evelyn/)**

[![Pricing component from Evelyn template](https://dev-to-uploads.s3.amazonaws.com/i/6gz1tnwviv67u466t6xq.png)](https://cruip.com/demos/evelyn/)