Maniraj Murugan
Maniraj Murugan

Reputation: 9084

Dynamic price calculation using Vue.js

I am building a hotel booking application and currently I am working on the booking part.

Here I am giving the user the option to edit the preferred room type and the number of adults and the number of children using a select box.

In clear, say if the user selected the room type "standard room" with one adult and no children, then we can get the default price value that was already stored for that particular room.

I have attached the fiddle up to the this step:

https://jsfiddle.net/c1ud4mkv/.

I am in the need of help after this step. If the user needs to stay with 2 adults and one or two children, then the price needs to changed accordingly.

For example; the price as of now for a standard room type with 1 adult was Rs.1000.

If the user wants to add 1 extra adult it means the price needs to change to Rs.2000. And if the user needs to add 1 child along with 2 adults it means the price needs to be calculated as Rs.3000 (Rs.2000 + Rs.1000) (Rs.1000 is price for extra 1 child).

The HTML:

<div id="app">
 <table>
    <thead>
     <td>Room Type</td>
     <td>Adult</td>
     <td>Children</td>
     <td>Total Price</td>
    </thead>
        <tbody>
          <tr v-for="(row, index) in rows">
            <td>
              <select data-value="Room Type">
                <option v-for="room in rooms">{{room.title}}</option>
              </select>
            </td>
            <td>
              <select data-value="1 Adult">
              <option value="1">1 Adult</option>
              <option value="2">2 Adults</option>
            </select>
          </td>
          <td>
            <select data-value="No Child">
            <option value="1">No Child</option>
            <option value="2">1 Child</option>
            <option value="3">2 Children</option>
          </select>
        </td>
        <td v-for="item in items">
          {{item.price}}
        </td>
      </tr>
    </tbody>
  </table>
    <div class="add-remove">
    <div class="col-md-6 text-center add">
      <a @click="addRoom" class="add-room"> Add Room </a>
    </div>
    <div class="col-md-6 text-center remove">
      <a @click="removeRoom(index)" class="remove-room"> Remove Room </a>
    </div>
  </div>
</div>

And Script was:

new Vue({
  el: '#app',

  data: {
    rows: [],
    rooms: [
    {
        value:0,
        title: 'Standard Room'
    },
    {
        value:0,
        title: 'Deluxe Room'
    },
    ],
    items: [{
        price: 1000,
    }]
  },

  methods: {
    addRoom: function() {
      var elem = document.createElement('tr');
      this.rows.push({
      });
    },

    removeRoom: function(index) {
      this.rows.splice(index, 1);
    },

    }

  })

And jsfiddle link is https://jsfiddle.net/c1ud4mkv/

Upvotes: 2

Views: 2992

Answers (2)

bbsimonbb
bbsimonbb

Reputation: 29002

Total price should be a computed property, calculated from your data.

update

Since you want x rows, with each row repeating the same behaviour, you will need a component representing a row, that I have called reservation. You'll quickly spot that the component is a copy-paste of the previous Vue. Almost nothing changes.

Now, the top level vue template just becomes a for-loop for creating components. Note that a component can directly reference the store for shared data (rooms), but has to get its uniqueness via its properties.

Notice that the whole state of the page is represented by a properly structured store, separate from the Vue stuff, with no dependancies between items of state. So the calculated value, totalPrice, is not in the store, but derived from values that are. To structure a problem like this, I would start by writing the store, then think about how to display it.

AddRow is a method, because it runs at a chosen time, returns nothing and updates the store. Methods often push data upstream. TotalPrice is a computed property because we want it reactive, it's used in a template, it's on the downstream side.

see it running here.

so here is the whole thing...

markup

<div id="vueRoot">
  <reservation v-for="myReservation in store.reservations" :reservation="myReservation"></reservation>
  <a class="myPlus" v-on:click="addRow">+</a>
</div>

code

var vueStore = {
  rooms : [
    {label : 'palatial suite', price : 1000.73},
    {label : 'presidential suite', price : 2000.36}
  ],
  reservations : [{
      selectedRoom : null,
      numAdults : 1,
      numChildren : 0
  }]
};
Vue.component("reservation",{
  template : `<div style="padding:12px">
  Room :
  <select v-model="reservation.selectedRoom">
    <option v-for="room in rooms" v-bind:value="room.price">
      {{room.label}}
    </option>
  </select>
  Number of adults :
  <select v-model="reservation.numAdults">
    <option>1</option>
    <option>2</option>
  </select>
  Number of children :
  <select v-model="reservation.numChildren">
    <option>0</option>
    <option>1</option>
    <option>2</option>
  </select>
  Total Price : {{totalPrice}}
</div>`,
  data : function(){
    return {
      rooms : vueStore.rooms
    }
  },
  props : [ 'reservation' ],
  computed : {
    totalPrice : function(){
      if(this.reservation.selectedRoom){
        return this.reservation.selectedRoom 
          + this.reservation.numAdults * 500 
          + this.reservation.numChildren * 200
      }
      else return '';
    }
  }
});
vm = new Vue({
  el : "#vueRoot",
  data : {store : vueStore},
  methods : {
  addRow : function(){
  this.store.reservations.push({
      selectedRoom : null,
      numAdults : 1,
      numChildren : 0
  })
  }
  }
});

Upvotes: 3

Jason Smith
Jason Smith

Reputation: 1209

This might be best solved with a component. However, using the configuration you have here, I have modified as follows to get something that works:

First, the HTML. Note how I used v-model to properly bind the select elements to your Vue instance.

<div id="app">
<table>
<thead>
<td>Room Type</td>
<td>Adult</td>
<td>Children</td>
<td>Total Price</td>
</thead>
        <tbody>
          <tr v-for="(row, index) in rows">
            <td>
              <select v-model="row.roomType">
                <option v-for="room in rooms" v-bind:value="room.title">{{room.title}}</option>
              </select>
            </td>
            <td>
              <select v-model="row.adultCount">
              <option value="1">1 Adult</option>
              <option value="2">2 Adults</option>
            </select>
          </td>
          <td>
            <select v-model="row.childCount">
            <option value="0">No Child</option>
            <option value="1">1 Child</option>
            <option value="2">2 Children</option>
          </select>
        </td>
        <td>
          {{calcRoomTotal(row)}}
        </td>
      </tr>
    </tbody>
  </table>
    <div class="add-remove">
    <div class="col-md-6 text-center add">
      <a @click="addRoom" class="add-room"> Add Room </a>
    </div>
    <div class="col-md-6 text-center remove">
      <a @click="removeRoom(index)" class="remove-room"> Remove Room </a>
    </div>
  </div>

Next, the JavaScript. Notice how I changed the "Add row" function to include the properties of the room.

new Vue({
  el: '#app',

  data: {
    rows: [{
      roomType : "Standard Room",
      adultCount : 1,
      childCount : 0
      }],
    rooms: [
    {
        value:0,
        title: 'Standard Room'
    },
    {
        value:0,
        title: 'Deluxe Room'
    },
    ],
    items: [{
        price: 1000,
    }]
  },

  methods: {
    addRoom: function() {
      this.rows.push({
      roomType : "Standard Room",
      adultCount : 1,
      childCount : 0
      });
    },

    removeRoom: function(index) {
      this.rows.splice(index, 1);
    },

    calcRoomTotal: function(row) {
        return (parseInt(row.adultCount) + parseInt(row.childCount)) * 1000;
    }   
}

Finally, note that the new calcRoomTotal method is used to display the room total for each line.

Here's a JSFiddle with the new setup.

Upvotes: 1

Related Questions