In this article of this series we will implement some helper custom components we will be using throughout our App.
Below i have created some custom components which we will be using in our real-estate app, and because we will be using them in multiple areas i thought that we have to implement them first before moving forward.
resources/js/components/partials/PropertyTemplate.vue
<template> <div class="box-two proerty-item"> <div class="item-thumb"> <router-link :to="getPropertyDetailUrl()"><img :src="property.pictures[0].image_url" :alt="property.title"></router-link> </div> <div class="item-entry overflow "> <div v-if="this.show_fav_icon" class="favorite-and-print" style="margin-top: -9px"> <a class="add-to-fav" href="#" title="add to favorites"> <i class="fa fa-star-o"></i> </a> </div> <h5><router-link :to="getPropertyDetailUrl()"> {{property.title}} </router-link></h5> <div class="dot-hr"></div> <span class="pull-left"><b> Area :</b> {{property.area}}m </span> <span class="proerty-price pull-right"> $ {{formatMoney(property.price)}}</span> <p style="display: none;" v-if="property.description">{{property.description}}</p> <div class="property-icon"> <span v-for="(feature, index) in getFeatures()" :key="index"> <img :src="'/template/assets/img/icon/' + feature.icon"> ({{feature.val}}){{index === getFeatures().length - 1 ? '' : '|'}} </span> <div class="dealer-action pull-right" v-if="show_actions"> <router-link :to="`/user/edit-property/${property.id}`" class="button">Edit </router-link> <a href="#" @click.prevent="deleteProperty" class="button delete_user_car">Delete</a> <router-link :to="getPropertyDetailUrl()" class="button">View</router-link> </div> </div> </div> </div> </template> <script> import {useToast} from "vue-toastification"; import {formatMoney, slugify} from "../../api/helpers"; export default { name: "PropertyTemplate", props: ["property", "show_actions", "show_fav_icon"], setup(props, context) { const toast = useToast(); const getPropertyDetailUrl= function () { return `/property/${props.property.id}/${slugify(props.property.title)}`; } const getFeatures = function () { let features = []; if(props.property.bedrooms) { features = [...features, {icon: 'bed.png', val: props.property.bedrooms}]; } if(props.property.bathrooms) { features = [...features, {icon: 'shawer.png', val: props.property.bathrooms}]; } if(props.property.garages) { features = [...features, {icon: 'cars.png', val: props.property.garages}]; } return features; } const deleteProperty = function () { } function addToFav() { } function removeFromFav() { } return { formatMoney, getPropertyDetailUrl, getFeatures, deleteProperty } } </script> <style scoped> .add-to-fav { background: #a6a3a3; } .fav-selected { color: #FDC600; border-color: #FDC600; } </style>
This component represents the property template that will be rendered when rendering list of properties which will be in the homepage, search page, user properties, etc. The component accepts the some props like the property object and show_actions, show_fav_icon.
We will update this component in future parts when manipulation properties removal and favorites properties.
resources/js/components/partials/ICheckInput.vue
<template> <div class="checkbox"> <label> <input :type="type" ref="checkboxref" :name="name" :value="value" :checked="defaultChecked" /> <strong>{{text}}</strong> </label> </div> </template> <script> export default { name: "ICheckInput", props: ["value", "text", "name", "type", "defaultChecked"], mounted() { const self = this; $(document).ready(function () { $(`input[name=${self.$refs.checkboxref.name}]`).iCheck({ checkboxClass: "icheckbox_square-yellow", radioClass: "iradio_square-yellow", increaseArea: '20%' }); $(document).on("ifChecked ifUnchecked", `input[name=${self.$refs.checkboxref.name}]`, function (e) { if(e.type === 'ifChecked') { self.$emit('onChecked', e.target.value); } else if (e.type === 'ifUnchecked') { self.$emit('onUnchecked', e.target.value); } }); }); }, watch: { defaultChecked(newVal, oldVal) { if(newVal != oldVal) { if(newVal) { $(`input[name=${this.$refs.checkboxref.name}]`).iCheck('check'); } else { $(`input[name=${this.$refs.checkboxref.name}]`).iCheck('uncheck'); } } } } } </script>
This component represent a nice jquery check box. If you return in the template we downloaded previously you will see this kind of checkbox, and because it’s rendered using jquery i preferred that we make it as a custom component that will receive some props like value, text, defaultChecked, etc.
resources/js/components/partials/SelectPicker.vue
<template> <select :name="name" class="selectpicker" ref="selectpickerref" data-live-search="true" data-live-search-style="begins" :title="title"> <option v-for="opt in options" :key="opt.id" :value="opt.id" :selected="opt.id === content">{{opt.name}}</option> </select> </template> <script> export default { name: "SelectPicker", props: ["name", "title", "options", "selected"], emits: ["onChange"], data() { return { content: this.selected } }, methods: { }, mounted() { this.content = this.selected; $(document).ready(() => { $(`select[name=${this.$refs.selectpickerref.name}]`).selectpicker(); $(`select[name=${this.$refs.selectpickerref.name}]`).on('changed.bs.select', (e, clickedIndex, isSelected, previousValue) => { this.content = parseInt(e.target.value); this.$emit('onChange', this.content); }); }); }, watch: { options(newVal, oldVal) { if(this.$refs.selectpickerref) { setTimeout(() => $(`select[name=${this.$refs.selectpickerref.name}]`).selectpicker('refresh'), 1000); } }, selected(newVal, oldVal) { if(newVal != oldVal) { this.content = parseInt(newVal); if(this.$refs.selectpickerref) { $(`select[name=${this.$refs.selectpickerref.name}]`).val(this.content); $(`select[name=${this.$refs.selectpickerref.name}]`).selectpicker("refresh"); } } } } } </script>
This component also renders a nice select input with support with autocomplete and because it’s rendered using jquery i make it as a separate component which receive props like name, title, options, selected.
resources/js/components/partials/NumberRange.vue
<template> <div ref="myRange"> <label :for="id">{{label}}:</label> <div class="range-wrapper"> <input type="number" placeholder="from" :min="min" :max="max" :step="step" :value="updatedSliderValue[0]" @change="emitValue($event, 'from')" /> <input type="number" placeholder="to" :min="min" :max="max" :step="step" :value="updatedSliderValue[1]" @change="emitValue($event, 'to')" /> </div> </div> </template> <script> export default { name: "NumberRange", props: ["label", "id", "sliderValue", "min", "max", "step"], emits: ["onChange"], data() { return { updatedSliderValue: this.sliderValue } }, methods: { emitValue(event, key) { const updated = !isNaN(parseInt(event.target.value)) ? parseInt(event.target.value) : 0; if(key === 'from') { this.updatedSliderValue = [updated, this.updatedSliderValue[1]]; } else { this.updatedSliderValue = [this.updatedSliderValue[0], updated]; } this.$emit('onChange', this.updatedSliderValue); } }, watch: { sliderValue(newVal, oldVal) { if(newVal[0] != oldVal[0] || newVal[1] != oldVal[1]) { this.updatedSliderValue = [...newVal]; } } } } </script> <style scoped> input[type=number] { width: 40%; border: 1px solid #ccc; } .range-wrapper input:last-child { margin-left:5px; } </style>
This component renders a number range with two fields (min, max). We will be using this component later in the search page when filtering properties by i.e min price and max price.
resources/js/components/partials/Pagination.vue
<template> <div class="pagination" v-if="item && item.total > item.data.length"> <ul> <li :class="{disabled: item.current_page === 1}"><a href="#" :class="{disabled: item.current_page === 1}" @click.prevent="handlePage(item.current_page - 1)">Prev</a></li> <li v-for="(n,index) in item.last_page" :key="index" :class="{active: item.current_page === n}"><a :class="{disabled: item.current_page === n}" href="#" @click.prevent="handlePage(n)">{{n}}</a></li> <li><a href="#" :class="{disabled: item.current_page === item.last_page}" @click.prevent="handlePage(item.current_page + 1)">Next</a></li> </ul> </div> </template> <script> export default { name: "Pagination", props: ["item"], emits: ["onPaginate"], methods: { handlePage(page) { this.$emit('onPaginate', page); } } } </script> <style scoped> a.disabled { color: gray; pointer-events: none; } </style>
This simple component displays pagination links by looping over laravel eloquent results. Simply we pass it an item as a prop. This item can be any laravel collection. When clicking on any page number it emits a custom event “onPaginate” with the current page.
Finally create this helper js file:
resources/js/api/helpers.js
export const formatMoney = (amount) => { return parseFloat(amount).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'); } export const slugify = text => text .toString() .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .toLowerCase() .trim() .replace(/\s+/g, '-') .replace(/[^\w-]+/g, '') .replace(/--+/g, '-') export const serializeObject = function(obj) { var str = []; for(var p in obj){ if (obj.hasOwnProperty(p)) { str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); } } return str.join("&"); }
Continue to part 6: Preparing Property Form>>>