diff client/src/application/Search.vue @ 1196:a397b52981b8

html cleanup got rid of Topbar.vue to avoid unnecessary component nesting
author Markus Kottlaender <markus@intevation.de>
date Mon, 19 Nov 2018 08:24:47 +0100
parents
children 37889ae85133
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/application/Search.vue	Mon Nov 19 08:24:47 2018 +0100
@@ -0,0 +1,197 @@
+<template>
+    <div :class="searchbarContainerStyle">
+        <div class="input-group-prepend shadow">
+            <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="form-control ui-element search searchbar d-print-none"
+            >
+            <ul v-if="showSearchbar && searchResults !== null " 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
+    height: $icon-height
+    border-radius: 0.25rem
+
+  .searchbar-expanded
+    margin-left: auto
+    margin-right: auto
+    padding-left: $offset
+    width: $searchbar-width !important
+    .searchbar
+      border-top-left-radius: 0 !important
+      border-bottom-left-radius: 0 !important
+
+
+  .searchbar-collapsed
+    margin-left: auto
+    width: $icon-width !important
+    transition: $transition-fast
+
+  .searchbar
+    margin-left: auto
+    margin-right: auto
+    height: $icon-height !important
+
+  .searchlabel
+    background-color: white !important
+
+  .input-group-text
+    height: $icon-height
+    width: $icon-width
+
+  .list-group
+    pointer-events: 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 {
+      searchQuery: "",
+      searchQueryIsDirty: false,
+      searchResults: null,
+      isSearching: false
+    };
+  },
+  computed: {
+    ...mapState("application", ["showSearchbar"]),
+    searchIndicator: function() {
+      if (this.isSearching) {
+        return "⟳";
+      } else if (this.searchQueryIsDirty) {
+        return "";
+      } else {
+        return "✓";
+      }
+    },
+    searchbarContainerStyle() {
+      return [
+        "d-flex input-group searchcontainer",
+        {
+          "searchbar-collapsed": !this.showSearchbar,
+          "searchbar-expanded": this.showSearchbar
+        }
+      ];
+    },
+    searchButtonStyle() {
+      return [
+        "ui-element input-group-text p-0 d-flex justify-content-center searchlabel d-print-none",
+        {
+          rounded: !this.showSearchbar,
+          "rounded-left": this.showSearchbar
+        }
+      ];
+    }
+  },
+  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.showSearchbar) {
+        setTimeout(setFocus, 300);
+      }
+      this.$store.commit("application/showSearchbar", !this.showSearchbar);
+    }
+  }
+};
+</script>