Step 0: Create Boilerplate with Vue Cli

We can either add ReactiveSearch to an existing app or create a boilerplate app with Vue Cli. For this quickstart guide, we will use the Vue Cli.

vue create my-awesome-search && cd my-awesome-search

Step 1: Install ReactiveSearch

We will fetch and install reactivesearch-vue module using yarn or npm.

yarn add @appbaseio/reactivesearch-vue

or

npm install @appbaseio/reactivesearch-vue

Step 2: ReactiveSearch as UMD

It is also possible to run ReactiveSearch without relying on a Node.JS environment tooling for the build setup. Here, I am using v1.0.0-beta.19, this can be replaced with the version you are using.

<script src="https://cdn.jsdelivr.net/npm/@appbaseio/reactivesearch-vue@1.0.0-beta.19/dist/@appbaseio/reactivesearch-vue.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-slider-component@2.8.2/dist/index.js"></script>

Note:

You can remove vue-slider-component script if not using any slider component from ReactiveSearch.

You can also use the minified version.

<script src="https://cdn.jsdelivr.net/npm/@appbaseio/reactivesearch-vue@1.0.0-beta.19/dist/@appbaseio/reactivesearch-vue.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-slider-component@2.8.2/dist/index.js"></script>

Use ReactiveSearch's Components

Fully Import

import Vue from 'vue';
import ReactiveSearch from '@appbaseio/reactivesearch-vue';
import App from './App';
Vue.config.productionTip = false;

Vue.use(ReactiveSearch);

/* eslint-disable no-new */
new Vue({
	el: '#app',
	components: { App },
	template: '<App/>',
});

The above imports ReactiveSearch entirely.

Only import the components you need

import Vue from 'vue';
import { ReactiveBase } from '@appbaseio/reactivesearch-vue';
import App from './App';

Vue.config.productionTip = false;

Vue.use(ReactiveBase);

/* eslint-disable no-new */
new Vue({
	el: '#app',
	components: { App },
	template: '<App/>',
});

Step 2: Adding the first component

Lets add our first ReactiveSearch component: ReactiveBase, it is a backend connector where we can configure the Elasticsearch index / authorization setup.

We will be using kebab-case here. You can read more about component naming convention here.

We will demonstrate creating an index using appbase.io service, although you can use any Elasticsearch backend within ReactiveBase.

create an appbase.io app

Caption: For the example that we will build, the app is called good-books-ds and the associated read-only credentials are nY6NNTZZ6:27b76b9f-18ea-456c-bc5e-3a5263ebc63d. You can browse and clone the dataset into your own app from here.

We will update our src/App.vue file to add ReactiveBase component.

<template>
  <div id="app">
    <reactive-base
      app="good-books-ds"
      credentials="nY6NNTZZ6:27b76b9f-18ea-456c-bc5e-3a5263ebc63d"
    >
      <h1>Hello from ReactiveBase 👋</h1>
    </reactive-base>
  </div>
</template>

<script>
  export default {
    name: "App",
  };
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

This is how the app should look after running the yarn run serve command.

Image


Step 3: Adding Filters and Result Components

For this app, we will be using multi-list and single-range components for filtering the dataset. And reactive-list component for showing the search results.

Lets add them within the ReactiveBase component. But before we do that, we will look at the important props for each.

<multi-list
	componentId="Authors"
	dataField="authors.raw"
	class="filter"
	title="Select Authors"
	selectAllLabel="All Authors"
/>

The multi-list creates a multiple selection based list UI component that is connected to a database field and shows items using the author.raw field in the dataset. Here is how it will look visually.

Next, we will look at the single-range component for creating a ratings based filter.

<single-range
  componentId="Ratings"
  dataField="average_rating"
  :data="[
    { 'start': 0, 'end': 3, 'label': 'Rating < 3' },
    { 'start': 3, 'end': 4, 'label': 'Rating 3 to 4' },
    { 'start': 4, 'end': 5, 'label': 'Rating > 4' }
  ]"
  title="Book Ratings"
  class="filter"
/>

single-range filters the DB by rating field based on the UI choice the user makes. We also set the Rating > 4 option to be default selected when the UI loads up first.

Finally, we need a component to show the matching results. reactive-list does exactly this.

<reactive-list
  componentId="SearchResult"
  dataField="original_title.raw"
  className="result-list-container"
  :class="{ full: showBooks }"
  :pagination="true"
  :from="0"
  :size="5"
  :react="{and: ['Ratings','Authors']}"
>
  <div slot="renderData" slot-scope="{ item }">
    <div class="flex book-content" key="item._id">
      <img :src="item.image" alt="Book Cover" class="book-image" />
      <div class="flex column justify-center ml20">
        <div class="book-header">{{ item.original_title }}</div>
          <div class="flex column justify-space-between">
              <div>
                <div>
                  by <span class="authors-list">{{ item.authors }}</span>
                </div>
                <div class="ratings-list flex align-center">
                  <span class="stars">
                <i v-for="(item, index) in Array(item.average_rating_rounded).fill('x')" class="fas fa-star" :key="index" />
              </span>
              <span class="avg-rating">({{item.average_rating}} avg)</span>
            </div>
          </div>
          <span class="pub-year">Pub {{item.original_publication_year}}</span>
        </div>
      </div>
    </div>
  </div>
</reactive-list>

The :react prop here specifies that it should construct a query based on the current selected values of searchbox and ratingsfilter components. Every time the user changes the input value, a new query is fired -- you don't need to write a manual query for any of the UI components here, although you can override it via :customQuery prop.

This is how the app looks after adding ReactiveList component:

Now, we will put all three components together to create the UI view.

<template>
  <div id="app">
    <reactive-base app="good-books-yj" credentials="gBgUqs2tV:3456f3bf-ea9e-4ebc-9c93-08eb13e5c87c" >
      <div class="filters-container">
        <multi-list
          componentId="Authors"
          dataField="authors.raw"
          class="filter"
          title="Select Authors"
          selectAllLabel="All Authors"
        />
        <single-range
          componentId="Ratings"
          dataField="average_rating"
          :data="[
            { 'start': 0, 'end': 3, 'label': 'Rating < 3' },
            { 'start': 3, 'end': 4, 'label': 'Rating 3 to 4' },
            { 'start': 4, 'end': 5, 'label': 'Rating > 4' }
          ]"
          title="Book Ratings"
          class="filter"
        />
      </div>
      <reactive-list
        componentId="SearchResult"
        dataField="original_title.raw"
        className="result-list-container"
        :pagination="true"
        :from="0"
        :size="5"
        :react="{and: ['Ratings','Authors']}"
      >
        <div slot="renderData" slot-scope="{ item }">
          <div class="flex book-content" key="item._id">
            <img :src="item.image" alt="Book Cover" class="book-image" />
            <div class="flex column justify-center ml20">
              <div class="book-header">{{ item.original_title }}</div>
                <div class="flex column justify-space-between">
                    <div>
                      <div>
                        by <span class="authors-list">{{ item.authors }}</span>
                      </div>
                      <div class="ratings-list flex align-center">
                        <span class="stars">
                      <i v-for="(item, index) in Array(item.average_rating_rounded).fill('x')" class="fas fa-star" :key="index" />
                    </span>
                    <span class="avg-rating">({{item.average_rating}} avg)</span>
                  </div>
                </div>
                <span class="pub-year">Pub {{item.original_publication_year}}</span>
              </div>
            </div>
          </div>
        </div>
      </reactive-list>
    </reactive-base>
  </div>
</template>

<script>
  export default {
    name: "app",
  };
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
</style>

If you have followed along so far, you should a screen similar to:

Image

We have built our entire search UI in just 80 lines!

The only thing missing at this point is the styling, ReactiveSearch doesn't use a layout system internally. For example, if you are using a grid from Bootstrap or Materialize, you can use that to style the layout. Or if you prefer to use Flex, you can use that. We can now import css to make the app look more clean.


Step 4: Adding CSS

By importing styles.css file, here is our final app layout and responsive methods to switch between container.

<script>
import "./styles.css";

export default {
  name: "app",
  data: function() {
    return {
      showBooks: window.innerWidth <= 768 ? true : false
    };
  },
  methods: {
    switchContainer: function() {
      return (this.showBooks = !this.showBooks);
    }
  }
};
</script>

If you have followed along, this is how our app should look now.

For convenience, you can checkout the final code from the ReactiveSearch demos - https://github.com/appbaseio/vue-reactivesearch/tree/master/packages/demos/good-books.