comparison client/src/components/Search.vue @ 1558:0ded4c56978e

refac: component filestructure. remove admin/map hierarchy
author Thomas Junk <thomas.junk@intevation.de>
date Wed, 12 Dec 2018 09:22:20 +0100
parents client/src/components/map/Search.vue@9b81ac91a43e
children f2d24dceecc7
comparison
equal deleted inserted replaced
1557:62171cd9a42b 1558:0ded4c56978e
1 <template>
2 <div :class="searchbarContainerStyle">
3 <div class="input-group-prepend m-0 d-print-none">
4 <span @click="toggleSearchbar" :class="searchButtonStyle" for="search">
5 <font-awesome-icon icon="search"></font-awesome-icon>
6 </span>
7 </div>
8 <div
9 :class="[
10 'searchgroup',
11 {
12 'searchgroup-collapsed': !showSearchbar,
13 big:
14 showContextBox &&
15 ['bottlenecks', 'staging'].indexOf(contextBoxContent) !== -1
16 }
17 ]"
18 >
19 <input
20 @keyup.enter="takeFirstSearchresult"
21 id="search"
22 v-model="searchQuery"
23 type="text"
24 :class="searchInputStyle"
25 />
26 </div>
27 <div
28 v-if="showSearchbar && searchResults !== null && !showContextBox"
29 class="searchresults border-top ui-element bg-white rounded-bottom d-print-none position-absolute"
30 >
31 <div
32 v-for="entry of searchResults"
33 :key="entry.name"
34 class="border-top text-left"
35 >
36 <a
37 href="#"
38 @click.prevent="moveToSearchResult(entry)"
39 class="p-2 d-block text-nowrap"
40 >
41 <font-awesome-icon
42 icon="ship"
43 v-if="entry.type === 'bottleneck'"
44 class="mr-1"
45 fixed-width
46 />
47 <font-awesome-icon
48 icon="water"
49 v-if="entry.type === 'rhm'"
50 class="mr-1"
51 fixed-width
52 />
53 <font-awesome-icon
54 icon="city"
55 v-if="entry.type === 'city'"
56 class="mr-1"
57 fixed-width
58 />
59 {{ entry.name }}
60 </a>
61 </div>
62 </div>
63 </div>
64 </template>
65
66 <style lang="scss" scoped>
67 .searchcontainer {
68 opacity: 0.96;
69 }
70
71 .searchcontainer .searchbar {
72 border-top-left-radius: 0 !important;
73 border-bottom-left-radius: 0 !important;
74 }
75
76 .searchgroup {
77 margin-left: -3px;
78 transition: width 0.3s;
79 width: 300px;
80 overflow: hidden;
81 }
82
83 .searchgroup.big {
84 width: 571px;
85 }
86
87 .searchgroup-collapsed {
88 width: 0;
89 }
90
91 .searchbar {
92 height: 2rem !important;
93 box-shadow: none !important;
94 }
95
96 .searchbar.rounded-top-right {
97 border-radius: 0 !important;
98 border-top-right-radius: 0.25rem !important;
99 }
100
101 .searchlabel.rounded-top-left {
102 border-radius: 0 !important;
103 border-top-left-radius: 0.25rem !important;
104 }
105
106 .input-group-text {
107 height: 2rem;
108 width: 2rem;
109 }
110
111 .input-group-prepend svg path {
112 fill: #666;
113 }
114
115 .searchresults {
116 box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2);
117 top: 2rem;
118 left: 0;
119 right: 0;
120 max-height: 24rem;
121 overflow: auto;
122 }
123
124 .searchresults > div:first-child {
125 border-top: 0 !important;
126 }
127
128 .searchresults a {
129 text-decoration: none;
130 }
131
132 .searchresults a:hover {
133 background: #f8f8f8;
134 }
135 </style>
136
137 <script>
138 /* This is Free Software under GNU Affero General Public License v >= 3.0
139 * without warranty, see README.md and license for details.
140 *
141 * SPDX-License-Identifier: AGPL-3.0-or-later
142 * License-Filename: LICENSES/AGPL-3.0.txt
143 *
144 * Copyright (C) 2018 by via donau
145 * – Österreichische Wasserstraßen-Gesellschaft mbH
146 * Software engineering by Intevation GmbH
147 *
148 * Author(s):
149 * Markus Kottländer <markus.kottlaender@intevation.de>
150 */
151 import debounce from "lodash.debounce";
152 import { mapState } from "vuex";
153
154 import { displayError } from "../lib/errors.js";
155 import { HTTP } from "../lib/http";
156
157 const setFocus = () => document.querySelector("#search").focus();
158
159 export default {
160 name: "search",
161 data() {
162 return {
163 searchQueryIsDirty: false,
164 searchResults: null,
165 isSearching: false
166 };
167 },
168 computed: {
169 ...mapState("application", [
170 "showSearchbar",
171 "showContextBox",
172 "contextBoxContent"
173 ]),
174 searchQuery: {
175 get() {
176 return this.$store.state.application.searchQuery;
177 },
178 set(value) {
179 this.$store.commit("application/searchQuery", value);
180 }
181 },
182 searchIndicator: function() {
183 if (this.isSearching) {
184 return "⟳";
185 } else if (this.searchQueryIsDirty) {
186 return "";
187 } else {
188 return "✓";
189 }
190 },
191 searchbarContainerStyle() {
192 return [
193 "input-group searchcontainer shadow-xs",
194 {
195 "d-flex": this.contextBoxContent !== "imports",
196 "d-none": this.contextBoxContent === "imports" && this.showContextBox
197 }
198 ];
199 },
200 searchInputStyle() {
201 return [
202 "form-control ui-element search searchbar d-print-none border-0",
203 { "rounded-top-right": this.showContextBox || this.searchResults }
204 ];
205 },
206 searchButtonStyle() {
207 return [
208 "ui-element input-group-text p-0 d-flex border-0 justify-content-center searchlabel bg-white d-print-none",
209 {
210 rounded: !this.showSearchbar,
211 "rounded-left": this.showSearchbar,
212 "rounded-top-left":
213 this.showSearchbar && (this.showContextBox || this.searchResults)
214 }
215 ];
216 }
217 },
218 watch: {
219 searchQuery: function() {
220 this.searchQueryIsDirty = true;
221 this.triggerSearch();
222 }
223 },
224 methods: {
225 takeFirstSearchresult() {
226 if (!this.searchResults || this.searchResults.length != 1) return;
227 this.moveToSearchResult(this.searchResults[0]);
228 },
229 triggerSearch: debounce(function() {
230 this.doSearch();
231 }, 500),
232 doSearch() {
233 this.isCalculating = true;
234 this.searchResults = null;
235
236 if (this.searchQuery == "") {
237 return;
238 }
239
240 HTTP.post(
241 "/search",
242 { string: this.searchQuery },
243 {
244 headers: {
245 "X-Gemma-Auth": localStorage.getItem("token"),
246 "Content-type": "text/xml; charset=UTF-8"
247 }
248 }
249 )
250 .then(response => {
251 // console.log("got:", response.data);
252 this.searchResults = response.data;
253 })
254 .catch(error => {
255 const { status, data } = error.response;
256 displayError({
257 title: this.$gettext("Backend Error"),
258 message: `${status}: ${data.message || data}`
259 });
260 });
261
262 this.isCalculating = false;
263 this.searchQueryIsDirty = false;
264 },
265 moveToSearchResult(resultEntry) {
266 // DEBUG console.log("Moving to", resultEntry);
267 if (resultEntry.geom.type == "Point") {
268 let zoom = 11;
269 if (resultEntry.type === "bottleneck") zoom = 17;
270 if (resultEntry.type === "rhm") zoom = 15;
271 if (resultEntry.type === "city") zoom = 13;
272
273 this.$store.commit("map/moveMap", {
274 coordinates: resultEntry.geom.coordinates,
275 zoom,
276 preventZoomOut: true
277 });
278 }
279 // this.searchQuery = ""; // clear search query again
280 this.toggleSearchbar();
281 },
282 toggleSearchbar() {
283 if (!this.showContextBox) {
284 if (!this.showSearchbar) {
285 setTimeout(setFocus, 300);
286 }
287 this.$store.commit("application/showSearchbar", !this.showSearchbar);
288 }
289 }
290 }
291 };
292 </script>