diff client/src/components/map/Search.vue @ 1272:bc55ffaeb639

cleaned up client/src directory better organization of files and directories, better naming, separation of admin and map context
author Markus Kottlaender <markus@intevation.de>
date Thu, 22 Nov 2018 07:07:12 +0100
parents
children a7dd8a3356fc
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/Search.vue	Thu Nov 22 07:07:12 2018 +0100
@@ -0,0 +1,223 @@
+<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"
+            >
+            <div v-if="showSearchbar && searchResults !== null && !showInContextBox" class="searchresults border-top ui-element bg-white rounded-bottom d-print-none">
+                <div v-for="entry of searchResults" :key="entry.name" class="border-top py-2">
+                    <a href="#" @click.prevent="moveToSearchResult(entry)">{{ entry.name }}</a>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style lang="sass" scoped>
+  .searchcontainer
+    height: $icon-height
+    opacity: $slight-transparent
+
+  .searchbar-expanded
+    min-width: 600px
+    .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: $border-radius !important
+
+  .searchlabel
+    &.rounded-top-left
+      border-radius: 0 !important
+      border-top-left-radius: $border-radius !important
+
+  .input-group-text
+    height: $icon-height
+    width: $icon-width
+
+  .input-group-prepend
+    .fa-search
+      color: #666
+
+  .searchresults
+    margin-left: -31px
+    max-height: 20rem
+    overflow: auto
+    > div:first-child
+      border-top: 0 !important
+</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 "../../lib/errors.js";
+import { HTTP } from "../../lib/http";
+
+const setFocus = () => document.querySelector("#search").focus();
+
+export default {
+  name: "search",
+  data() {
+    return {
+      searchQueryIsDirty: false,
+      searchResults: null,
+      isSearching: false
+    };
+  },
+  computed: {
+    ...mapState("application", ["showSearchbar", "showInContextBox"]),
+    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 ml-3 shadow-xs",
+        {
+          "searchbar-collapsed": !this.showSearchbar,
+          "searchbar-expanded": this.showSearchbar,
+          "d-flex": this.showInContextBox !== "imports",
+          "d-none": this.showInContextBox === "imports"
+        }
+      ];
+    },
+    searchInputStyle() {
+      return [
+        "form-control ui-element search searchbar d-print-none border-0",
+        { "rounded-top-right": this.showInContextBox || this.searchResults }
+      ];
+    },
+    searchButtonStyle() {
+      return [
+        "ui-element input-group-text p-0 d-flex border-0 justify-content-center searchlabel bg-white d-print-none",
+        {
+          rounded: !this.showSearchbar,
+          "rounded-left": this.showSearchbar,
+          "rounded-top-left":
+            this.showSearchbar && (this.showInContextBox || this.searchResults)
+        }
+      ];
+    }
+  },
+  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.showInContextBox) {
+        if (!this.showSearchbar) {
+          setTimeout(setFocus, 300);
+        }
+        this.$store.commit("application/showSearchbar", !this.showSearchbar);
+      }
+    }
+  }
+};
+</script>