Skip to content

Commit

Permalink
Merge pull request #231 from lnu-norge/user-friendly-location-input
Browse files Browse the repository at this point in the history
User friendly location input
  • Loading branch information
DanielJackson-Oslo committed Feb 29, 2024
2 parents 8621882 + 4047a0d commit 25638ef
Show file tree
Hide file tree
Showing 12 changed files with 1,686 additions and 88 deletions.
14 changes: 10 additions & 4 deletions app/assets/stylesheets/tom_select.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
@apply text-sm;
}

.ts-control .item {
@apply text-sm bg-gray-200 px-2 py-0.5;
}

.dropdown {
.ts-control {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
Expand Down Expand Up @@ -62,15 +66,16 @@
.plugin-clear_button .clear-button {
opacity: 0;
position: absolute;
top: 8px;
top: 0;
right: calc(8px - 6px);
margin-right: 0 !important;
background: transparent !important;
transition: opacity 0.5s;
cursor: pointer;
@apply p-2 text-sm;
}
.plugin-clear_button.single .clear-button {
right: calc(8px - 6px + 2rem);
right: 8px;
}
.plugin-clear_button.focus.has-items .clear-button, .plugin-clear_button:hover.has-items .clear-button {
opacity: 1;
Expand Down Expand Up @@ -336,8 +341,9 @@
border-top: 0 none;
}
.ts-dropdown .optgroup-header {
color: #303030;
background: #fff;
color: theme('colors.gray.500');
@apply text-sm;
@apply pt-4;
cursor: default;
}

Expand Down
54 changes: 37 additions & 17 deletions app/javascript/controllers/duplicate_checker_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,51 @@ export default class extends Controller {
connect() {
const form = this.element
this.hideRestOfForm()

form.addEventListener('change', () => {
this.checkDuplicates()
});

const titleField = form.querySelector("#space_title")
titleField.addEventListener('input', (e) => {
if (e.target.value.length < 5) {
return
}

this.checkDuplicates()
})
}

async checkDuplicates() {
const data = new FormData(this.element);
const title = data.get("space[title]");
const address = data.get("space[address]");
const post_number = data.get("space[post_number]");
const {
title,
address,
post_number
} = this.extractDataFromForm()

const enoughDataToFindByTitle = title.length > 5
const enoughDataToFindByAddress = (!post_number || (!address && !post_number))
if (this.checkIfDataIsStale(title, address, post_number)) {
return // No need to check anything if we have the same data
}

if (!enoughDataToFindByTitle || !enoughDataToFindByAddress) return
const enoughDataToFindByTitle = title.length > 5
const enoughDataToFindByAddress = (!!address && !!post_number)

if (this.checkIfDataIsStale(title, address, post_number)) {
return
if (!enoughDataToFindByTitle && !enoughDataToFindByAddress) {
return this.renderNoDuplicates()
}

return await this.renderFetchedDuplicates(title, address, post_number)
}

extractDataFromForm() {
const data = new FormData(this.element);
const title = data.get("space[title]");
const address = data.get("space[address]");
const post_number = data.get("space[post_number]");
return { title, address, post_number }
}

async renderFetchedDuplicates(title, address, post_number) {
let url = "/check_duplicates?"
url += `title=${title}&`
url += `address=${address}&`
url += `post_number=${post_number}&`
url += `title=${title}&`
url += `address=${address}&`
url += `post_number=${post_number}&`

const result = await (await fetch(url)).json()
if (result && result.html) {
Expand All @@ -68,8 +80,16 @@ export default class extends Controller {
return this.showRestOfForm()
}

renderNoDuplicates() {
return this.duplicatesRenderHereTarget.innerHTML = ""
}


checkIfDataIsStale(title, address, post_number) {
if (title !== this.state.title || address !== this.state.address || post_number !== this.state.post_number) {
if (
title !== this.state.title ||
address !== this.state.address ||
post_number !== this.state.post_number) {
this.state.title = title
this.state.address = address
this.state.post_number = post_number
Expand Down
52 changes: 35 additions & 17 deletions app/javascript/controllers/locationsearch_controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Controller } from '@hotwired/stimulus'
import TomSelect from 'tom-select'
import fylkerAndKommuner from "./search_and_filter/fylker_and_kommuner";

export default class extends Controller {
static values = {
Expand All @@ -8,29 +9,46 @@ export default class extends Controller {

connect() {
this.tomselect = new TomSelect('#locationInput', {
valueField: 'text',
labelField: 'text',
searchField: 'text',
allowEmptyOption: true,
create: true,
load: (search, callback) => {
this.searchGeoNorge(search, callback)
}}
maxOptions: null,
valueField: 'value',
labelField: 'kommune',
searchField: ['kommune', 'fylke'],
optgroupField: 'fylke',
sortField: ['fylke', 'kommune'],
persist: false, // but do not store user inputted options as dropdown items
plugins: ['clear_button'], // For multiple select
optgroups: this.optGroups,
options: this.options
}
);
}

async searchGeoNorge(search, callback) {
if(search === "")
callback([]);
get optGroups() {
return fylkerAndKommuner.map(fylke => ({ value: fylke.name, label: fylke.name }))
}

const url =`https://ws.geonorge.no/stedsnavn/v1/sted?sok=${search}&fuzzy=true`
get options() {
return fylkerAndKommuner.map(fylke => {
const options = []

const result = await (await fetch(url)).json();
options.push({
kommune: `Hele ${fylke.name}`,
value: fylke.id,
fylke: fylke.name
})

const data = result.navn.map((name) => {
return { text: name.stedsnavn[0].skrivemåte }
});
if (fylke.kommuner) {
fylke.kommuner.forEach(kommune => {
options.push({
kommune: kommune.name,
value: kommune.id,
fylke: fylke.name
})
})
}

callback(data);
return options
}).flat()
}
}

97 changes: 55 additions & 42 deletions app/javascript/controllers/mapbox_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,14 +264,14 @@ export default class extends Controller {
});

this.locationTarget.onchange = (event) => {
this.submitSearch(event)
this.getSearchCoordinatesFromGeoNorge(event)
};

this.formTarget.onsubmit = (event) => {
// To stop the form from submitting, as that currently does nothing but refresh the page.
event.preventDefault()
this.clearDebounce("titleSearch")
this.submitSearch(event)
this.getSearchCoordinatesFromGeoNorge(event)
this.hideSearchBox()
};
}
Expand Down Expand Up @@ -311,15 +311,62 @@ export default class extends Controller {
}
}

async submitSearch(event) {
const url =`https://ws.geonorge.no/stedsnavn/v1/sted?sok=${event.target.value}&fuzzy=true`
async getSearchCoordinatesFromGeoNorge(event) {

const url = this.getUrlForGeoNorgeLocation(event);

if (!url) {
return this.moveMapToFitNorway();
}

const result = await (await fetch(url)).json();
this.moveMapToGeoNorgeLocation(result);
}

getUrlForGeoNorgeLocation(event) {
const fylkesnummer = event.target.value.split('fylke-')[1];
const kommunenummer = event.target.value.split('kommune-')[1];

if (kommunenummer) {
return `https://ws.geonorge.no/kommuneinfo/v1/kommuner/${kommunenummer}`;
}

if (fylkesnummer) {
return `https://ws.geonorge.no/kommuneinfo/v1/fylker/${fylkesnummer}`;
}

// Else return the whole country
return null
}

if(!result || !result.navn || result.navn.length === 0)
return;

const place = result.navn[0]
this.moveMapToGeoNorgeLocation(place)
moveMapToGeoNorgeLocation(result) {

const avgrensningsboks = result?.avgrensningsboks?.coordinates?.[0]
if (!avgrensningsboks) {
return this.moveMapToFitNorway();
}

return this.moveMapToFitBounds(this.geoNorgeAvgrensingsBoksToBoundingBox(avgrensningsboks));
}

geoNorgeAvgrensingsBoksToBoundingBox = (avgrensningsboks) => {
const [lng1, lat1] = avgrensningsboks[0];
const [lng2, lat2] = avgrensningsboks[2];
return [lng1, lat1, lng2, lat2];
}

moveMapToFitNorway() {
return this.moveMapToFitBounds([4.032154,57.628953,31.497974,71.269784])
}
moveMapToFitBounds(bounds) {
console.log(bounds)
this.map.fitBounds(bounds, {
padding: 10,
animate: false
}, {
wasZoom: true,
});
}

removeMarker(key) {
Expand Down Expand Up @@ -374,40 +421,6 @@ export default class extends Controller {
});
}

moveMapToGeoNorgeLocation(place) {
const location = place.geojson.geometry;

if (location.type === "Point") {
this.moveMapTo(location.coordinates)
}

if (location.type === "MultiPoint") {
// MultiPoints can have arbitrary amounts of points, and so needs to be fit
// as described here: https://docs.mapbox.com/mapbox-gl-js/example/zoomto-linestring/
const bounds = new mapboxgl.LngLatBounds(
location.coordinates[0],
location.coordinates[0]
);
for (const coord of location.coordinates) {
bounds.extend(coord);
}
return this.map.fitBounds(bounds, {
padding: 100,
animate: false,
maxZoom: 13
}, {
wasZoom: true,
});
}

// Unsure about location type given, use the center given by representasjonspunkt instead
const point = [
place.representasjonspunkt.øst,
place.representasjonspunkt.nord
];
return this.moveMapTo(point);
}

moveMapTo(point) {
this.map.jumpTo({ center: point, zoom: 12 }, { wasZoom: true });
}
Expand Down
Loading

0 comments on commit 25638ef

Please sign in to comment.