Skip to content

Commit 2fa49a8

Browse files
committed
Update items page to match collections page
- Add new pagination layout - Add empty state when collection has no items - Remove jQuery dependency - Extend from base.html
1 parent 777ae2c commit 2fa49a8

File tree

2 files changed

+109
-100
lines changed

2 files changed

+109
-100
lines changed

runtimes/eoapi/stac/eoapi/stac/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,17 @@ async def item_collection(
512512
)
513513

514514
if output_type == MimeTypes.html:
515+
offset = int(request.query_params.get("offset") or 0)
516+
limit = int(request.query_params.get("limit") or 10)
517+
515518
item_collection["id"] = collection_id
516519
return create_html_response(
517520
request,
518521
item_collection,
519522
template_name="items",
520523
title=f"{collection_id} items",
524+
limit=limit,
525+
offset=offset,
521526
)
522527

523528
elif output_type == MimeTypes.csv:
Lines changed: 104 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% include "header.html" %}
1+
{% extends "base.html" %}
22

33
{% set show_prev_link = false %}
44
{% set show_next_link = false %}
@@ -8,125 +8,129 @@
88
{% set urlq = url + '?' %}
99
{% endif %}
1010

11-
11+
{% block content %}
1212
<nav aria-label="breadcrumb">
1313
<ol class="breadcrumb bg-light">
1414
{% for crumb in crumbs %}
1515
{% if not loop.last %}
16-
<li class="breadcrumb-item"><a href="{{ crumb.url }}/">{{ crumb.part }}</a></li>
17-
{% else %}<li class="breadcrumb-item active" aria-current="page">{{ crumb.part }}</li>
16+
<li class="breadcrumb-item"><a href="{{ crumb.url }}/">{{ crumb.part }}</a></li>
17+
{% else %}
18+
<li class="breadcrumb-item active" aria-current="page">{{ crumb.part }}</li>
1819
{% endif %}
1920
{% endfor %}
2021

2122
<li class="ml-auto json-link"><a target="_blank" href="{{ urlq }}f=geojson">GeoJSON</a></li>
2223
</ol>
2324
</nav>
2425

25-
<h1>Collection Items: {{ response.title or response.id }}</h1>
26-
27-
<div id="map" class="rounded" style="width:100%; height:400px;">Loading...</div>
28-
29-
<p>
30-
<b>Number of matching items:</b> {{ response.numberMatched }}<br/>
31-
<b>Number of returned items:</b> {{ response.numberReturned }}<br/>
32-
</p>
33-
34-
<div class="form-row" style="margin-bottom:10px;" id="controls">
35-
{% for link in response.links %}
36-
{% if link.rel == 'previous' %}
37-
<div class="col-auto"><a class="btn btn-secondary" title="previous page" href="{{ link.href }}">prev</a></div>
26+
<h1 class="my-4">
27+
<span class="d-block text-uppercase text-muted h6 mb-0">Collection Items:</span>
28+
{{ response.title or response.id }}
29+
</h1>
30+
{% if response.features|length > 0 %}
31+
<div class="d-flex flex-row align-items-center mb-4">
32+
<div class="form-inline ml-auto" style="gap: 10px">
33+
<div class="d-flex">
34+
<label for="limit">Page size: </label>
35+
<select class="form-control form-control-sm ml-1" id="limit" aria-label="Select page size"> <!-- TODO: dynamically populate the values based on oga_max_limit -->
36+
<option value="10" {% if limit == 10 %}selected{% endif %}>10</option>
37+
<option value="25" {% if limit == 25 %}selected{% endif %}>25</option>
38+
<option value="50" {% if limit == 50 %}selected{% endif %}>50</option>
39+
<option value="100" {% if limit == 100 %}selected{% endif %}>100</option>
40+
</select>
41+
</div>
42+
{% if response.links|length > 0 %}
43+
<div class="btn-group btn-group-sm" role="group" aria-label="Paginate">
44+
{% for link in response.links %}
45+
{% if link.rel == 'prev' or link.rel == 'previous' %}
46+
<a class="btn btn-secondary" title="previous page" href="{{ link.href }}">« prev</a>
47+
{% endif %}
48+
{% endfor %}
49+
{% for link in response.links %}
50+
{% if link.rel == 'next' %}
51+
<a class="btn btn-secondary" title="next page" href="{{ link.href }}">next »</a>
52+
{% endif %}
53+
{% endfor %}
3854
{% endif %}
39-
{% endfor %}
40-
<div class="col-auto">
41-
<select class="form-control" id="limit"> <!-- TODO: dynamically populate the values based on oga_max_limit -->
42-
<option value="10">page size</option>
43-
<option>10</option>
44-
<option>100</option>
45-
<option>1000</option>
46-
<option>10000</option>
47-
</select>
55+
</div>
4856
</div>
49-
{% for link in response.links %}
50-
{% if link.rel == 'next' %}
51-
<div class="col-auto"><a class="btn btn-secondary" title="next page" href="{{ link.href }}">next</a></div>
52-
{% endif %}
53-
{% endfor %}
5457
</div>
58+
59+
<div id="map" class="rounded mb-2" style="width:100%; height:400px;">Loading...</div>
60+
5561
<div class="table-responsive">
56-
{% if response.features is defined and response.features|length > 0 %}
57-
<table class="table">
58-
<thead class="thead-light">
59-
<th>ID</th>
60-
{% for key, value in response.features.0.properties.items() %}
61-
<th style="font-size: 13px">{{ key }}</th>
62-
{% endfor %}
63-
</thead>
64-
<tbody>
65-
{% for feature in response.features %}
66-
<tr style="font-size: 11px">
67-
<td><a target="_blank" href="{{ template.api_root }}/collections/{{ feature.collection }}/items/{{ feature.id }}">{{ feature.id }}</a></td>
68-
{% for key, value in feature.properties.items() %}
69-
<td style="overflow: hidden; text-overflow: ellipsis; max-width: 200px; white-space: nowrap;">{{ value }}</td>
70-
{% endfor %}
71-
</tr>
72-
{% endfor %}
73-
</tbody>
74-
</table>
75-
{% endif %}
62+
{% if response.features and response.features|length > 0 %}
63+
<table class="table">
64+
<thead class="thead-light">
65+
<th>ID</th>
66+
{% for key, value in response.features.0.properties.items() %}
67+
<th style="font-size: 13px">{{ key }}</th>
68+
{% endfor %}
69+
</thead>
70+
<tbody>
71+
{% for feature in response.features %}
72+
<tr style="font-size: 11px">
73+
<td><a target="_blank" href="{{ template.api_root }}/collections/{{ feature.collection }}/items/{{ feature.id }}">{{ feature.id }}</a></td>
74+
{% for key, value in feature.properties.items() %}
75+
<td style="overflow: hidden; text-overflow: ellipsis; max-width: 200px; white-space: nowrap;">{{ value }}</td>
76+
{% endfor %}
77+
</tr>
78+
{% endfor %}
79+
</tbody>
80+
</table>
81+
{% endif %}
82+
</div>
83+
{% else %}
84+
<div class="text-center mx-auto py-5 w-50">
85+
<p class="h4 mb-3">No items found</p>
86+
<p>You need to add STAC Collections and Items; for example by following the <a href="https://github.com/vincentsarago/MAXAR_opendata_to_pgstac">MAXAR open data demo</a> or <a href="https://github.com/developmentseed/eoAPI/tree/main/demo">other demos.</a></p>
7687
</div>
88+
{% endif %}
7789

7890
<script>
79-
80-
81-
82-
function changePageSize() {
83-
var url = "{{ template.api_root }}/collections/{{ response.features[0].collection }}/items?";
84-
const searchParams = new URLSearchParams(window.location.search);
85-
searchParams.set('limit', $("#limit").val());
86-
url += searchParams.toString();
87-
window.location.href = url;
88-
}
89-
$(function() {
90-
//
91-
// mapping
92-
//
93-
var geojson = {{ response|tojson }};
94-
95-
var features = (geojson.features) ? geojson.features : [];
96-
var hasGeom = features.some(feat => feat.geometry);
97-
if (hasGeom) {
98-
var map = L.map('map').setView([0, 0], 1);
99-
map.addLayer(new L.TileLayer(
100-
'https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
101-
maxZoom: 18,
102-
attribution: 'Map data &copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap contributors</a>'
91+
window.addEventListener("load", function () {
92+
// Pagination
93+
document.getElementById("limit").addEventListener("change", (event) => {
94+
// Set new page size
95+
const limit = event.target.value;
96+
let url = "{{ template.api_root }}/collections/{{ response.id }}/items?";
97+
const searchParams = new URLSearchParams(window.location.search);
98+
searchParams.set('limit', limit);
99+
searchParams.set('offset', 0);
100+
url += searchParams.toString();
101+
window.location.href = url;
102+
});
103+
104+
// Mapping
105+
const geojson = {{ response|tojson }};
106+
107+
const hasGeom = geojson.features && geojson.features.some(feat => feat.geometry);
108+
if (hasGeom) {
109+
const map = L.map('map').setView([0, 0], 1);
110+
map.addLayer(new L.TileLayer(
111+
'https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
112+
maxZoom: 18,
113+
attribution: 'Map data &copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap contributors</a>'
114+
}
115+
));
116+
117+
function addPopup(feature, layer) {
118+
const aElm = document.createElement('a');
119+
aElm.setAttribute('href', `{{ template.api_root }}/collections/${feature.collection}/items/${feature.id}`);
120+
aElm.setAttribute('target', '_blank');
121+
aElm.innerText = feature.id;
122+
layer.bindPopup(aElm);
103123
}
104-
));
105124

106-
function addPopup(feature, layer) {
107-
var aElm = document.createElement('a');
108-
aElm.setAttribute('href', `{{ template.api_root }}/collections/${feature.collection}/items/${feature.id}`);
109-
aElm.setAttribute('target', '_blank');
110-
aElm.innerText = feature.id;
111-
layer.bindPopup(aElm);
112-
}
113-
114-
var features = L.geoJSON(geojson, {
115-
onEachFeature: addPopup
116-
}).addTo(map);
117-
118-
map.fitBounds(features.getBounds());
119-
} else {
120-
document.getElementById("map").style.display = "none";
121-
}
125+
const features = L.geoJSON(geojson, {
126+
onEachFeature: addPopup,
127+
weight: 2
128+
}).addTo(map);
122129

123-
//
124-
// event handling
125-
//
126-
$("#limit").on("change", function() {
127-
changePageSize();
130+
map.fitBounds(features.getBounds());
131+
} else {
132+
document.getElementById("map").style.display = "none";
133+
}
128134
});
129-
});
130135
</script>
131-
132-
{% include "footer.html" %}
136+
{% endblock %}

0 commit comments

Comments
 (0)