Commit 5c3199e6 by Mac Stephens

Add user location and filtering functionality, Improve map marker handling fix…

Add user location and filtering functionality, Improve map marker handling fix list scrolling and details navigation; default detail summaries expanded; add dynamic main navbar title.
parent dc203ba1
[Settings]
LogFileNum=579
LogFileNum=583
webClientVersion=0.1.0
......@@ -2,31 +2,6 @@
<!-- Header / controls (non-scrolling) -->
<div class="flex-shrink-0">
<!-- Local navbar (Complaints) -->
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<span id="complaints_title" class="navbar-brand mb-0 h5 text-white">Complaints</span>
</div>
<div class="col">
<button id="complaints_btnrefresh" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
</button>
</div>
<div class="col">
<button id="complaints_btngroup" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
</button>
</div>
<div class="col">
<button id="complaints_btnfilter" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span>
</button>
</div>
</div>
</div>
</nav>
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2">
......
......@@ -113,46 +113,25 @@ begin
if FLoading then Exit;
FLoading := True;
console.log('GetComplaints: Invoking API...');
try
try
xdcResponse := await(xdwcComplaints.RawInvokeAsync('IApiService.GetComplaintList', []));
console.log('RawInvoke returned:', xdcResponse.Result);
respObj := TJSObject(xdcResponse.Result);
xdwdsComplaints.Close;
console.log('Dataset closed');
xdwdsComplaints.SetJsonData(respObj['data']);
console.log('JsonData set on dataset:', respObj['data']);
xdwdsComplaints.Open;
console.log('PriorityColor field name = ' +
xdwdsComplaintsPriorityColor.FieldName +
' sample value = ' +
xdwdsComplaintsPriorityColor.AsString);
if xdwdsComplaints.RecordCount > 0 then
begin
console.log('First record - Complaint:' + xdwdsComplaints.FieldByName('Complaint').AsString);
end;
complaintsCount := Integer(respObj['count']);
lblEntries.Caption := Format('%d active complaints', [complaintsCount]);
console.log('Label updated:' + lblEntries.Caption);
except
on E: EXDataClientRequestException do
begin
console.log('XData exception:' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
end;
finally
console.log('GetComplaints complete');
end;
HideSpinner('spinner');
end;
......@@ -161,7 +140,6 @@ end;
procedure TFViewComplaints.tmrRefreshTimer(Sender: TObject);
begin
GetComplaints;
console.log('tmrRefreshTimer fired');
end;
end.
......
......@@ -77,6 +77,17 @@ object FViewMain: TFViewMain
OnClick = lblUsersClick
Caption = 'Users'
end
object lblMainTitle: TWebLabel
Left = 131
Top = 31
Width = 61
Height = 15
ElementID = 'lbl_main_title'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebPanel1: TWebPanel
Left = 136
Top = 110
......
......@@ -3,17 +3,17 @@
<!-- Top Nav -->
<nav class="navbar navbar-light bg-primary border-light text-light py-2 flex-shrink-0">
<div class="container-fluid">
<!-- Left: Font button -->
<button id="view.main.btnfont" type="button" class="btn btn-outline-primary text-light border-light btn-sm">
font
</button>
<!-- Center: App title -->
<a id="view.main.apptitle" class="navbar-brand text-light ms-3" href="index.html">emiMobile</a>
<!-- App title + current view title -->
<div class="d-flex align-items-center ms-3">
<a id="view.main.apptitle" class="navbar-brand fw-bold text-light mb-0 me-1" href="index.html">emiMobile</a>
<span class="navbar-brand text-light mb-0 mx-0">-</span>
<span id="lbl_main_title" class="navbar-brand text-light mb-0 ms-1"></span>
</div>
<!-- Right: Connection label -->
<span id="view.main.lblconnection" class="navbar-text text-light ms-auto"></span>
</div>
</div>
</nav>
<!-- Main content: fills space between navbars -->
......
......@@ -24,6 +24,7 @@ type
btnComplaints: TWebButton;
btnUnits: TWebButton;
tmrBadgeCounts: TWebTimer;
lblMainTitle: TWebLabel;
procedure WebFormCreate(Sender: TObject);
procedure mnuLogoutClick(Sender: TObject);
procedure wllblUserProfileClick(Sender: TObject);
......@@ -90,16 +91,18 @@ begin
lblUsers.Visible := false;
Utils.HideSpinner('spinner');
ShowForm(TFViewMap);
SetHeaderTitle('Map');
RefreshBadgesAsync;
end;
procedure TFViewMain.SetHeaderTitle(const title: string);
var
el: TJSElement;
var el: TJSElement;
begin
el := Document.getElementById('view.main.lbltitle');
el := Document.getElementById('lbl_main_title');
if el <> nil then
TJSHtmlElement(el).innerText := title;
el.innerHTML := title
else
console.log('SetHeaderTitle: lbl_main_title not found');
end;
......
......@@ -2,72 +2,6 @@ object FViewMap: TFViewMap
Width = 475
Height = 802
ElementFont = efCSS
object btnMenu: TWebButton
Left = 62
Top = 66
Width = 41
Height = 25
Caption = 'Menu'
ChildOrder = 1
ElementID = 'map.btnmenu'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnAlerts: TWebButton
Left = 148
Top = 66
Width = 35
Height = 25
Caption = 'Alerts'
ChildOrder = 1
ElementID = 'map.btnalerts'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnGroups: TWebButton
Left = 194
Top = 66
Width = 41
Height = 25
Caption = 'Groups'
ChildOrder = 1
ElementID = 'map.btngroups'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnLocate: TWebButton
Left = 246
Top = 66
Width = 39
Height = 25
Caption = 'Locate'
ChildOrder = 1
ElementID = 'map.btnlocate'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnFilters: TWebButton
Left = 297
Top = 66
Width = 35
Height = 25
Caption = 'Filters'
ChildOrder = 1
ElementID = 'map.btnfilters'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnDisplay: TWebButton
Left = 351
Top = 66
Width = 40
Height = 25
Caption = 'Display'
ChildOrder = 1
ElementID = 'map.btndisplay'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object pnlMap: TWebPanel
Left = 62
Top = 120
......@@ -76,7 +10,7 @@ object FViewMap: TFViewMap
ElementID = 'map_pnlmap'
ChildOrder = 7
ElementPosition = epIgnore
TabOrder = 6
TabOrder = 0
object lfMap: TTMSFNCLeaflet
Left = 0
Top = 0
......
<div id="map.root" class="d-flex flex-column h-100 w-100">
<nav class="d-flex gap-2 bg-primary p-2 overflow-x-auto border-bottom border-primary shadow-sm flex-shrink-0">
<!-- New: Offcanvas -->
<div class="offcanvas offcanvas-top"
tabindex="-1"
id="map_filters_offcanvas"
aria-labelledby="map_filters_offcanvas_label"
style="--bs-offcanvas-width: 280px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="map_filters_offcanvas_label">Map Filters</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<button id="map.btnmenu" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<i class="fa fa-bars me-2"></i><span class="d-none d-sm-inline">Menu</span>
</button>
<div class="offcanvas-body">
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="map_filter_units" checked>
<label class="form-check-label" for="map_filter_units">Show Units</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="map_filter_complaints" checked>
<label class="form-check-label" for="map_filter_complaints">Show Complaints</label>
</div>
</div>
<button id="map.btnalerts" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<i class="fa fa-exclamation-circle me-2"></i><span class="d-none d-sm-inline">Alerts</span>
<div class="d-grid gap-2 mt-4">
<button type="button" class="btn btn-primary" id="map_filters_apply" data-bs-dismiss="offcanvas">
Apply
</button>
<button id="map.btngroups" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<i class="fa fa-users me-2"></i><span class="d-none d-sm-inline">Groups</span>
<button type="button" class="btn btn-outline-secondary" id="map_filters_reset">
Reset
</button>
</div>
<button id="map.btnlocate" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<i class="fa fa-location-arrow me-2"></i><span class="d-none d-sm-inline">Locate</span>
</button>
<div class="mt-3 small text-muted">
<span class="fw-semibold">Active:</span>
<span id="map_filters_summary"></span>
</div>
</div>
</div>
<button id="map.btnfilters" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<i class="fa fa-sliders-h me-2"></i><span class="d-none d-sm-inline">Filter</span>
</button>
<div class="flex-grow-1 position-relative" style="min-height:0;">
<div id="map_pnlmap" class="position-absolute w-100 h-100 top-0 start-0"></div>
<button id="map.btndisplay" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<i class="fa fa-sun me-2"></i><span class="d-none d-sm-inline">Display</span>
<button id="btn_map_filters"
type="button"
class="btn btn-primary position-absolute top-0 end-0 m-2 shadow"
style="z-index:1000;"
data-bs-toggle="offcanvas"
data-bs-target="#map_filters_offcanvas"
aria-controls="map_filters_offcanvas">
<i class="fa fa-sliders-h me-1"></i>Filters
</button>
</nav>
<div class="flex-grow-1 position-relative" style="min-height: 0;">
<div id="map_pnlmap" class="position-absolute w-100 h-100 top-0 start-0"></div>
</div>
</div>
......@@ -8,16 +8,10 @@ uses
WEBLib.ExtCtrls, DB, WEBLib.WebCtrls, WEBLib.REST, VCL.TMSFNCTypes, VCL.TMSFNCUtils,
VCL.TMSFNCGraphics, VCL.TMSFNCGraphicsTypes, VCL.TMSFNCCustomControl, VCL.TMSFNCWebBrowser,
VCL.TMSFNCMaps, VCL.TMSFNCLeaflet, VCL.TMSFNCMapsCommonTypes, System.StrUtils, XData.Web.Client,
XData.Web.Connection, ConnectionModule, Utils;
XData.Web.Connection, ConnectionModule, Utils, uMapFilters;
type
TFViewMap = class(TWebForm)
btnMenu: TWebButton;
btnAlerts: TWebButton;
btnGroups: TWebButton;
btnLocate: TWebButton;
btnFilters: TWebButton;
btnDisplay: TWebButton;
pnlMap: TWebPanel;
lfMap: TTMSFNCLeaflet;
httpReqGeoJson: TWebHttpRequest;
......@@ -31,12 +25,18 @@ type
procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
procedure tmrRefreshTimer(Sender: TObject);
private
userLocationMarker: TTMSFNCMapsMarker;
geoWatchId: Integer;
FUnitsLoaded: Boolean;
FComplaintsLoaded: Boolean;
FZoomPending: Boolean;
FLoadingPoints: Boolean;
mapFilters: TMapFilters;
[async] procedure LoadPointsAsync;
function CarIconForDistrict(const DistrictCode: string): string;
procedure UpdateDeviceLocation(lat, lng: Double);
procedure StartDeviceLocation;
procedure StopDeviceLocation;
public
end;
......@@ -77,9 +77,82 @@ begin
};
end;
{$ENDIF}
StartDeviceLocation;
end;
procedure TFViewMap.UpdateDeviceLocation(lat, lng: Double);
begin
if userLocationMarker = nil then
begin
userLocationMarker := lfMap.Markers.Add;
userLocationMarker.DataString := 'device';
userLocationMarker.IconURL := 'assets/markers/location_dot.png';
userLocationMarker.Title := ''; // keeps popup code from binding anything for this marker
end;
userLocationMarker.Latitude := lat;
userLocationMarker.Longitude := lng;
end;
procedure TFViewMap.StartDeviceLocation;
begin
asm
const self = this;
if (!navigator.geolocation) {
console.log('Geolocation not supported');
return;
}
const onPos = function (pos) {
const lat = pos.coords.latitude;
const lng = pos.coords.longitude;
try {
self.UpdateDeviceLocation(lat, lng);
} catch (e) {
console.log('UpdateDeviceLocation failed', e);
}
};
const onErr = function (err) {
console.log('Geolocation error:', err && err.message ? err.message : err);
};
if (self.geoWatchId != null && self.geoWatchId !== 0) {
try { navigator.geolocation.clearWatch(self.geoWatchId); } catch(e) {}
self.geoWatchId = 0;
}
self.geoWatchId = navigator.geolocation.watchPosition(onPos, onErr, {
enableHighAccuracy: true,
maximumAge: 5000,
timeout: 15000
});
end;
end;
procedure TFViewMap.StopDeviceLocation;
begin
asm
const self = this;
if (self.geoWatchId != null && self.geoWatchId !== 0 && navigator.geolocation) {
try { navigator.geolocation.clearWatch(self.geoWatchId); } catch(e) {}
self.geoWatchId = 0;
}
end;
end;
[async] procedure TFViewMap.httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
var
i: Integer;
......@@ -136,6 +209,12 @@ begin
end;
await(LoadPointsAsync);
if mapFilters = nil then
begin
mapFilters := TMapFilters.Create(lfMap);
mapFilters.Init;
end;
end;
function TFViewMap.CarIconForDistrict(const DistrictCode: string): string;
......@@ -162,7 +241,7 @@ end;
var
resp: TXDataClientResponse;
root, item, uo: TJSObject;
data, units: TJSArray;
units: TJSArray;
i, ui: Integer;
m: TTMSFNCMapsMarker;
lat, lng: Double;
......@@ -177,23 +256,61 @@ var
canShowDetails: Boolean;
canShowDetailsText: string;
detailsBtnHtml: string;
unitsData: TJSArray;
complaintsData: TJSArray;
begin
if FLoadingPoints then
Exit;
FLoadingPoints := True;
ShowSpinner('spinner');
try
FUnitsLoaded := False;
FComplaintsLoaded := False;
// --- Units ---------------------------------------------------------------
unitsData := nil;
complaintsData := nil;
// --- Fetch Units ---------------------------------------------------------
try
resp := await(xdwcMap.RawInvokeAsync('IApiService.GetUnitMap', []));
root := TJSObject(resp.Result);
data := TJSArray(root['data']);
unitsData := TJSArray(root['data']);
FUnitsLoaded := True;
except
on E: EXDataClientRequestException do
Console.Log('Units XData error: ' + E.ErrorResult.ErrorMessage);
end;
if data <> nil then
begin
// --- Fetch Complaints ----------------------------------------------------
try
resp := await(xdwcMap.RawInvokeAsync('IApiService.GetComplaintMap', []));
root := TJSObject(resp.Result);
complaintsData := TJSArray(root['data']);
FComplaintsLoaded := True;
except
on E: EXDataClientRequestException do
Console.Log('Complaints XData error: ' + E.ErrorResult.ErrorMessage);
end;
// --- Swap Markers (no blank map while loading) ---------------------------
lfMap.BeginUpdate;
try
for i := 0 to data.Length - 1 do
// Delete old unit/complaint markers right before adding new ones
for i := lfMap.Markers.Count - 1 downto 0 do
begin
m := lfMap.Markers[i];
if SameText(m.DataString, 'unit') or StartsText('complaint|', m.DataString) then
lfMap.Markers.Delete(i);
end;
// Add unit markers
if unitsData <> nil then
begin
item := TJSObject(data[i]);
for i := 0 to unitsData.Length - 1 do
begin
item := TJSObject(unitsData[i]);
lat := Double(item['Lat']);
lng := Double(item['Lng']);
......@@ -203,7 +320,6 @@ begin
callType := string(item['CallType']);
priorityText := string(item['Priority']);
statusText := string(item['Status']);
updateTimeText := string(item['UpdateTime']);
officer1Lname := string(item['Officer1Lname']);
......@@ -228,9 +344,7 @@ begin
'</button>';
end
else
begin
detailsBtnHtml := '';
end;
officer1Display := '';
if Trim(officer1Lname + officer1Fname + officer1Empnum) <> '' then
......@@ -298,30 +412,14 @@ begin
m.DataString := 'unit';
m.IconURL := CarIconForDistrict(dist);
end;
finally
lfMap.EndUpdate;
end;
end;
FUnitsLoaded := True;
except
on E: EXDataClientRequestException do
Console.Log('Units XData error: ' + E.ErrorResult.ErrorMessage);
end;
// --- Complaints ----------------------------------------------------------
try
resp := await(xdwcMap.RawInvokeAsync('IApiService.GetComplaintMap', []));
root := TJSObject(resp.Result);
data := TJSArray(root['data']);
if data <> nil then
// Add complaint markers
if complaintsData <> nil then
begin
lfMap.BeginUpdate;
try
for i := 0 to data.Length - 1 do
for i := 0 to complaintsData.Length - 1 do
begin
item := TJSObject(data[i]);
item := TJSObject(complaintsData[i]);
complaintId := string(item['ComplaintId']);
codeDesc := string(item['DispatchCodeDesc']);
......@@ -360,11 +458,13 @@ begin
else
rowsHtml := '<tr><td colspan="3" class="text-muted">No units</td></tr>';
// Complaint Markers
m := lfMap.Markers.Add;
m.Latitude := lat;
m.Longitude := lng;
m.DataString := 'complaint|' + complaintId;
m.IconURL := iconUrl;
m.Title :=
'<div class="d-flex flex-column gap-1 px-1 py-1" style="width:260px;">' +
'<div class="fw-semibold small">' +
......@@ -404,30 +504,33 @@ begin
'</button>' +
'</div>' +
'</div>';
m.IconURL := iconUrl;
end;
end;
finally
lfMap.EndUpdate;
end;
end;
FComplaintsLoaded := True;
except
on E: EXDataClientRequestException do
Console.Log('Complaints XData error: ' + E.ErrorResult.ErrorMessage);
end;
if mapFilters <> nil then
mapFilters.Apply;
finally
HideSpinner('spinner');
FLoadingPoints := False;
end;
end;
procedure TFViewMap.lfMapCustomizeMarker(Sender: TObject; var ACustomizeMarker: string);
begin
ACustomizeMarker :=
'var m=' + MARKERVAR + ', o=m.options||{};' + #13#10 +
'var rawTitle = (o && o.title) ? o.title : "";' + #13#10 +
'var ds = (o && o.datastring) ? o.datastring : "";' + #13#10 +
'if (ds === "device") {' + #13#10 +
' try { if (m.unbindTooltip) m.unbindTooltip(); } catch(e) {}' + #13#10 +
' try { if (m.unbindPopup) m.unbindPopup(); } catch(e) {}' + #13#10 +
' return;' + #13#10 +
'}' + #13#10 +
'o.tooltipHtml = rawTitle;' + #13#10 +
'o.title = "";' + #13#10 +
'var t = o.tooltipHtml || "";' + #13#10 +
......@@ -521,7 +624,7 @@ begin
// --- Marker Badge ------------------------------------------------------------
'.emi-marker-wrap{position:relative;display:inline-block;}'+#13#10+
'.emi-marker-img{display:block;}'+#13#10+
'.emi-marker-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--bs-danger);color:#fff;font:700 11px/16px system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;box-shadow:0 0 0 2px #fff;}';
'.emi-marker-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--bs-danger);color:#fff;font:700 11px/16px system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;box-shadow:0 0 0 2px #fff;}' + #13#10;
end;
procedure TFViewMap.tmrRefreshTimer(Sender: TObject);
......
......@@ -61,10 +61,6 @@
</tr>
</tbody>
</table>
</div>
</div>
</div>
......
......@@ -2,31 +2,6 @@
<!-- Header / controls (non-scrolling) -->
<div class="flex-shrink-0">
<!-- Local navbar (Units) -->
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<span id="units_title" class="navbar-brand mb-0 h5 text-white">Units</span>
</div>
<div class="col">
<button id="units_btnrefresh" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
</button>
</div>
<div class="col">
<button id="units_btngroup" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
</button>
</div>
<div class="col">
<button id="units_btnfilter" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span>
</button>
</div>
</div>
</div>
</nav>
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2">
......
unit uMapFilters;
interface
uses
System.SysUtils, System.StrUtils,
JS, Web,
VCL.TMSFNCLeaflet, VCL.TMSFNCMaps, VCL.TMSFNCMapsCommonTypes;
type
TMapFilters = class
private
FMap: TTMSFNCLeaflet;
FShowUnits: Boolean;
FShowComplaints: Boolean;
function GetCheckBoxChecked(const elementId: string; defaultValue: Boolean): Boolean;
procedure SetCheckBoxChecked(const elementId: string; checked: Boolean);
procedure SetText(const elementId: string; const value: string);
procedure ReadUi;
procedure ResetUi;
procedure UpdateSummary;
procedure ApplyToMap;
procedure HandleDocClick(e: TJSMouseEvent);
procedure HandleDocChange(e: TJSEvent);
public
constructor Create(AMap: TTMSFNCLeaflet);
procedure Init;
procedure Apply;
procedure Reset;
end;
implementation
{ TMapFilters }
constructor TMapFilters.Create(AMap: TTMSFNCLeaflet);
begin
inherited Create;
FMap := AMap;
FShowUnits := True;
FShowComplaints := True;
end;
procedure TMapFilters.Init;
begin
Document.addEventListener('click', @HandleDocClick);
Document.addEventListener('change', @HandleDocChange);
ResetUi;
ReadUi;
UpdateSummary;
ApplyToMap;
end;
procedure TMapFilters.Apply;
begin
ReadUi;
UpdateSummary;
ApplyToMap;
end;
procedure TMapFilters.Reset;
begin
ResetUi;
ReadUi;
UpdateSummary;
ApplyToMap;
end;
procedure TMapFilters.HandleDocClick(e: TJSMouseEvent);
var
el: TJSElement;
id: string;
begin
el := TJSElement(e.target);
if el = nil then
Exit;
// Note: This supports clicking inner icons/spans by walking up to a parent with an id
asm
while (el && !el.id) { el = el.parentElement; }
end;
if (el <> nil) and (el is TJSHtmlElement) then
begin
id := string(TJSHtmlElement(el).id);
if id = 'map_filters_apply' then
begin
e.preventDefault;
e.stopPropagation;
Apply;
Exit;
end;
if id = 'map_filters_reset' then
begin
e.preventDefault;
e.stopPropagation;
Reset;
Exit;
end;
end;
end;
procedure TMapFilters.HandleDocChange(e: TJSEvent);
begin
//Note: Summary text changes when toggles change
ReadUi;
UpdateSummary;
end;
procedure TMapFilters.ReadUi;
begin
FShowUnits := GetCheckBoxChecked('map_filter_units', True);
FShowComplaints := GetCheckBoxChecked('map_filter_complaints', True);
end;
procedure TMapFilters.ResetUi;
begin
SetCheckBoxChecked('map_filter_units', True);
SetCheckBoxChecked('map_filter_complaints', True);
end;
procedure TMapFilters.UpdateSummary;
var
summaryText: string;
begin
summaryText := '';
if FShowUnits then
summaryText := 'Units';
if FShowComplaints then
begin
if summaryText <> '' then
summaryText := summaryText + ', ';
summaryText := summaryText + 'Complaints';
end;
if summaryText = '' then
summaryText := 'None';
SetText('map_filters_summary', summaryText);
end;
procedure TMapFilters.ApplyToMap;
var
i: Integer;
m: TTMSFNCMapsMarker;
ds: string;
showMarker: Boolean;
begin
if FMap = nil then
Exit;
FMap.BeginUpdate;
try
for i := 0 to FMap.Markers.Count - 1 do
begin
m := FMap.Markers[i];
ds := Trim(string(m.DataString));
// Note: Map form sets:
// - units: m.DataString := 'unit'
// - complaints: (currently none) but you can set m.DataString := 'complaint|<id>'
showMarker := True;
if SameText(ds, 'unit') then
showMarker := FShowUnits
else if StartsText('complaint', LowerCase(ds)) then
showMarker := FShowComplaints;
// Note: TTMSFNCMapsMarker supports visibility toggling
m.Visible := showMarker;
end;
finally
FMap.EndUpdate;
end;
end;
function TMapFilters.GetCheckBoxChecked(const elementId: string; defaultValue: Boolean): Boolean;
var
el: TJSElement;
begin
Result := defaultValue;
el := Document.getElementById(elementId);
if (el <> nil) and (el is TJSHtmlInputElement) then
Result := TJSHtmlInputElement(el).checked;
end;
procedure TMapFilters.SetCheckBoxChecked(const elementId: string; checked: Boolean);
var
el: TJSElement;
begin
el := Document.getElementById(elementId);
if (el <> nil) and (el is TJSHtmlInputElement) then
TJSHtmlInputElement(el).checked := checked;
end;
procedure TMapFilters.SetText(const elementId: string; const value: string);
var
el: TJSElement;
begin
el := Document.getElementById(elementId);
if (el <> nil) and (el is TJSHtmlElement) then
TJSHtmlElement(el).innerText := value;
end;
end.
......@@ -22,7 +22,8 @@ uses
Utils in 'Utils.pas',
View.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html},
View.ComplaintDetails in 'View.ComplaintDetails.pas' {FViewComplaintDetails: TWebForm} {*.html},
View.UnitDetails in 'View.UnitDetails.pas' {FViewUnitDetails: TWebForm} {*.html};
View.UnitDetails in 'View.UnitDetails.pas' {FViewUnitDetails: TWebForm} {*.html},
uMapFilters in 'uMapFilters.pas';
{$R *.res}
......
......@@ -186,6 +186,7 @@
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="uMapFilters.pas"/>
<None Include="index.html"/>
<None Include="css\app.css"/>
<None Include="css\spinner.css"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment