Browse Source

Add autocomplete with Horsey using a custom backend service

develop
Bèr Kessels 4 months ago
parent
commit
1efe9bcd5f

+ 26
- 0
app/assets/javascripts/application.js View File

@@ -1 +1,27 @@
//= require "leaflet.js"
//= require "horsey.js"

var bragi = function(q, callback, base_url) {
r = new XMLHttpRequest();
r.open("GET", `${base_url}/autocomplete?q=${q}`, true);
r.onload = function() {
if (r.readyState != 4 || r.status != 200) return;
const data = JSON.parse(r.responseText);
const items = data.features.map(sourceEntry);

callback(null, [{ list: items }]);
}
r.send(q);
};

var sourceEntry = function(feature) {
var attrs = (feature.properties.geocoding || {});

return {
id: attrs.id,
poi_types: (attrs.poi_types || []).map(function(tp) { return tp.name }),
type: attrs.type,
city: ((attrs.address || {}).city || ''),
label: attrs.label
};
};

+ 3211
- 0
app/assets/javascripts/horsey.js
File diff suppressed because it is too large
View File


+ 2
- 0
app/assets/javascripts/horsey.min.js
File diff suppressed because it is too large
View File


+ 9
- 0
app/assets/stylesheets/application.scss View File

@@ -2,6 +2,7 @@

@import "bootstrap";
@import "leaflet.css";
@import "horsey.css";

#map {
height: 400px;
@@ -9,3 +10,11 @@
#map.full {
height: 800px;
}

.inline-icon {
height: 1em;
width: 1em;
}
.sey-selected .inline-icon {
filter: invert(100%);
}

+ 52
- 0
app/assets/stylesheets/horsey.css View File

@@ -0,0 +1,52 @@
.sey-container {
display: none;
position: absolute;
box-shadow: 1px 2px 6px;
background-color: #fff;
color: #333;
transition: left 0.1s ease-in-out;
z-index: 1;
}
.sey-list {
padding: 0;
margin: 0;
list-style-type: none;
}
.sey-show {
display: block;
}
.sey-hide {
display: none;
}
.sey-empty {
cursor: default;
padding: 7px;
}
.sey-item {
cursor: pointer;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 7px;
}
.sey-item:hover {
background-color: #444;
color: #fff;
}
.sey-selected {
background-color: #333;
color: #fff;
}
.sey-char-highlight {
font-weight: bold;
}
.sey-category-id {
background-color: #eee;
color: #aaa;
text-align: right;
text-transform: capitalize;
font-style: italic;
font-size: 12px;
box-shadow: 1px 0px 1px;
padding: 7px;
}

+ 1
- 0
app/assets/stylesheets/horsey.min.css View File

@@ -0,0 +1 @@
.sey-container{display:none;position:absolute;box-shadow:1px 2px 6px;background-color:#fff;color:#333;transition:left .1s ease-in-out;z-index:1}.sey-list{padding:0;margin:0;list-style-type:none}.sey-show{display:block}.sey-hide{display:none}.sey-empty{cursor:default;padding:7px}.sey-item{cursor:pointer;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:7px}.sey-item:hover{background-color:#444;color:#fff}.sey-selected{background-color:#333;color:#fff}.sey-char-highlight{font-weight:700}.sey-category-id{background-color:#eee;color:#aaa;text-align:right;text-transform:capitalize;font-style:italic;font-size:12px;box-shadow:1px 0 1px;padding:7px}

+ 52
- 0
app/views/home.erb View File

@@ -0,0 +1,52 @@
<%
js = "horsey(document.getElementById('search-input'), {
source (data, done) { bragi(data.input, done, '#{bragi_url}'); },
getText: 'label',
getValue: 'id',
renderItem: function(li, suggestion) {
var image = `<img class=\"inline-icon\" src=\"/images/${suggestion.type}.svg\" alt=\"${suggestion.type} icon\">`;
var type = `<small>${suggestion.poi_types.join(',')}</small>`;
var label = `${suggestion.label}`;
var city = `<small>${suggestion.city}</small>`;
li.innerHTML = `${image} ${label} ${city} ${type}`;
}
});"
set_content_for(:closing_js, js)
%>

<section class="container">
<div class="col justify-content-center">
<form action="/search" method="get">
<input autocomplete="off"
id="search-input"
name="q"
tabindex="1"
class="form-control form-control-lg"
type="search"
placeholder="bijv. Fietsenstalling, Nijmegen"
aria-label="Search for Places"/>
</form>
</div>
</section>
<hr>
<section class="row">
<div class="col">
<h2 class="title">Populaire plaatsen</h2>
<ul class="popular">
<li><a href="">Amsterdam</a></li>
<li><a href="">Rotterdam</a></li>
<li><a href="">Utrecht</a></li>
<li><a href="">Groningen</a></li>
<li><a href="/in/nijmegen">Nijmegen</a></li>
</ul>
</div>
<div class="col">
<h2 class="title">Recent gezocht</h2>
<ul class="popular">
<li><a href="">HEMA</a></li>
<li><a href="">Albert Heijn</a></li>
<li><a href="/in/nijmegen">Nijmegen</a></li>
<li><a href="">Tankstation Groningen</a></li>
</ul>
</div>
</section>

+ 11
- 0
lib/app.rb View File

@@ -63,6 +63,13 @@ module Hours
# Autoescape HTML. Requires erubis
set :erb, escape_html: true

# Base URL where to find autocomplete server
helpers do
def bragi_url
ENV['BRAGI_URL']
end
end

error Hours::DuplicateError do |error|
unprocessable_entity(error)
end
@@ -112,6 +119,10 @@ module Hours
headers 'Location' => places_url(command.aggregate_id)
end

get '/', provides: 'html' do
erb :home
end

get '/in/:slug', provides: 'html' do
@region = Hours::Projections::RegionQuery.build.handle(
params[:slug],

+ 3
- 0
public/images/house.svg View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M4 0l-4 3h1v4h2v-2h2v2h2v-4.03l1 .03-4-3z" />
</svg>

+ 1
- 0
public/images/poi.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 14 20"><path d="M7 0C3.13 0 0 3.13 0 7c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5C5.62 9.5 4.5 8.38 4.5 7S5.62 4.5 7 4.5 9.5 5.62 9.5 7 8.38 9.5 7 9.5z"/></svg>

+ 3
- 0
public/images/street.svg View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M3 0v1h-2l-1 1 1 1h2v5h1v-4h2l1-1-1-1h-2v-2h-1z" />
</svg>

+ 1
- 0
public/images/zone.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 18 19"><path d="M12 9V3L9 0 6 3v2H0v14h18V9h-6zm-8 8H2v-2h2v2zm0-4H2v-2h2v2zm0-4H2V7h2v2zm6 8H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V7h2v2zm0-4H8V3h2v2zm6 12h-2v-2h2v2zm0-4h-2v-2h2v2z"/></svg>

Loading…
Cancel
Save