view client/src/application/Search.vue @ 1205:5ba14f7f5bdc

merge
author Markus Kottlaender <markus@intevation.de>
date Mon, 19 Nov 2018 12:40:56 +0100
parents ddfdf440da24 68fb4af05b73
children 0900b341cef0
line wrap: on
line source

<template>
    <div :class="searchbarContainerStyle">
        <div class="input-group-prepend">
            <span @click="toggleSearchbar" :class="searchButtonStyle" for="search">
                <i class="fa fa-search d-print-none"></i>
            </span>
        </div>
        <div class="searchgroup flex-fill">
            <input
                @keyup.enter="takeFirstSearchresult"
                v-if="showSearchbar"
                id="search"
                v-model="searchQuery"
                type="text"
                :class="searchInputStyle"
            >
            <ul v-if="showSearchbar && searchResults !== null && !showBottlenecks" class="list-group d-print-none">
                <li v-for="entry of searchResults" :key="entry.name" class="list-group-item">
                    <a href="#" @click.prevent="moveToSearchResult(entry)">{{entry.name}}</a>
                </li>
            </ul>
        </div>
    </div>
</template>

<style lang="sass" scoped>
  .searchcontainer
    margin-left: $offset
    height: $icon-height

  .searchbar-expanded
    .searchbar
      border-top-left-radius: 0 !important
      border-bottom-left-radius: 0 !important

  .searchbar-collapsed
    width: $icon-width !important
    transition: $transition-fast

  .searchbar
    height: $icon-height !important
    box-shadow: none !important
    &.rounded-top-right
      border-radius: 0 !important
      border-top-right-radius: 0.25rem !important

  .searchlabel
    background-color: white !important
    &.rounded-top-left
      border-radius: 0 !important
      border-top-left-radius: 0.25rem !important

  .input-group-text
    height: $icon-height
    width: $icon-width

  .list-group
    pointer-events: auto
    max-height: 40vh
    overflow: auto
</style>

<script>
/*
 * This is Free Software under GNU Affero General Public License v >= 3.0
 * without warranty, see README.md and license for details.
 * 
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * License-Filename: LICENSES/AGPL-3.0.txt
 * 
 * Copyright (C) 2018 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 * 
 * Author(s):
 * Markus Kottländer <markus.kottlaender@intevation.de>
 */
import debounce from "lodash.debounce";
import { mapState } from "vuex";

import { displayError } from "../application/lib/errors.js";
import { HTTP } from "../application/lib/http";

const setFocus = () => document.querySelector("#search").focus();

export default {
  name: "search",
  data() {
    return {
      searchQueryIsDirty: false,
      searchResults: null,
      isSearching: false
    };
  },
  computed: {
    ...mapState("application", [
      "showSearchbar",
      "showBottlenecks",
      "showImportSoundingResults"
    ]),
    searchQuery: {
      get() {
        return this.$store.state.application.searchQuery;
      },
      set(value) {
        this.$store.commit("application/searchQuery", value);
      }
    },
    searchIndicator: function() {
      if (this.isSearching) {
        return "⟳";
      } else if (this.searchQueryIsDirty) {
        return "";
      } else {
        return "✓";
      }
    },
    searchbarContainerStyle() {
      return [
        "input-group searchcontainer shadow",
        {
          "searchbar-collapsed": !this.showSearchbar,
          "searchbar-expanded": this.showSearchbar,
          "d-flex": !this.showImportSoundingResults,
          "d-none": this.showImportSoundingResults
        }
      ];
    },
    searchInputStyle() {
      return [
        "form-control ui-element search searchbar d-print-none border-0",
        { "rounded-top-right": this.showSearchbar && this.showBottlenecks }
      ];
    },
    searchButtonStyle() {
      return [
        "ui-element input-group-text p-0 d-flex border-0 justify-content-center searchlabel d-print-none",
        {
          rounded: !this.showSearchbar,
          "rounded-left": this.showSearchbar,
          "rounded-top-left": this.showSearchbar && this.showBottlenecks
        }
      ];
    }
  },
  watch: {
    searchQuery: function() {
      this.searchQueryIsDirty = true;
      this.triggerSearch();
    }
  },
  methods: {
    takeFirstSearchresult() {
      if (!this.searchResults || this.searchResults.length != 1) return;
      this.moveToSearchResult(this.searchResults[0]);
    },
    triggerSearch: debounce(function() {
      this.doSearch();
    }, 500),
    doSearch() {
      this.isCalculating = true;
      this.searchResults = null;

      if (this.searchQuery == "") {
        return;
      }

      HTTP.post(
        "/search",
        { string: this.searchQuery },
        {
          headers: {
            "X-Gemma-Auth": localStorage.getItem("token"),
            "Content-type": "text/xml; charset=UTF-8"
          }
        }
      )
        .then(response => {
          // console.log("got:", response.data);
          this.searchResults = response.data;
        })
        .catch(error => {
          const { status, data } = error.response;
          displayError({
            title: "Backend Error",
            message: `${status}: ${data.message || data}`
          });
        });

      this.isCalculating = false;
      this.searchQueryIsDirty = false;
    },
    moveToSearchResult(resultEntry) {
      // DEBUG console.log("Moving to", resultEntry);
      if (resultEntry.geom.type == "Point") {
        let zoom = 11;
        if (resultEntry.type === "bottleneck") zoom = 17;
        if (resultEntry.type === "rhm") zoom = 15;
        if (resultEntry.type === "city") zoom = 13;

        this.$store.commit("map/moveMap", {
          coordinates: resultEntry.geom.coordinates,
          zoom,
          preventZoomOut: true
        });
      }
      // this.searchQuery = ""; // clear search query again
      this.toggleSearchbar();
    },
    toggleSearchbar() {
      if (!this.showBottlenecks) {
        if (!this.showSearchbar) {
          setTimeout(setFocus, 300);
        }
        this.$store.commit("application/showSearchbar", !this.showSearchbar);
      }
    }
  }
};
</script>