Subversion Repositories eFlore/Applications.cel

Rev

Rev 3869 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3845 idir 1
import {debounce} from "../lib/debounce.js";
2
 
3
export const NOMINATIM_OSM_URL = 'https://nominatim.openstreetmap.org/';
4
const NOMINATIM_OSM_DEFAULT_PARAMS = {
5
    'format': 'json',
6
    'addressdetails': 1,
7
    'limit': 10
8
};
9
const ESC_KEY_STRING = /^Esc(ape)?/;
10
 
11
export function TbPlaces(clientCallback) {
12
 
13
    /**
14
     * used in this.onSuggestionSelected()
15
     *
16
     * @callback clientCallback
17
     * @param {String} locality
18
     * @param {{lat: number, lng: number }} coordinates
19
     */
20
    this.clientCallback = clientCallback;
21
    this.searchResults = [];
22
}
23
 
24
TbPlaces.prototype.init = function() {
25
    this.initForm();
26
    this.initEvts();
27
};
28
 
29
TbPlaces.prototype.initForm = function() {
30
    this.places = $('#tb-places');
31
    this.placeLabel = this.places.siblings('label');
32
    this.placesResults = $('.tb-places-results');
33
    this.placesResultsContainer = $('.tb-places-results-container');
34
    this.placesCloseButton = $('.tb-places-close');
35
};
36
 
37
TbPlaces.prototype.initEvts = function() {
38
    if (0 < this.places.length) {
39
 
40
        this.toggleCloseButton(false);
41
        this.places.off('input').on('input', debounce(this.launchSearch.bind(this), 500));
3849 idir 42
        this.places.off('keydown').on('keydown', debounce(this.handlePlacesKeydown.bind(this), 500));
43
    }
44
};
3845 idir 45
 
3849 idir 46
TbPlaces.prototype.handlePlacesKeydown = function(evt) {
47
    const suggestionEl = $('.tb-places-suggestion'),
48
        isEscape = 27 === evt.keyCode || ESC_KEY_STRING.test(evt.key),
49
        isArrowDown = 40 === evt.keyCode || 'ArrowDown' === evt.key,
50
        isEnter = 13 === evt.keyCode || 'Enter' === evt.key;
3845 idir 51
 
3849 idir 52
    if (isEscape || isArrowDown || isEnter) {
53
        evt.preventDefault();
3845 idir 54
 
3849 idir 55
        if (isEscape) {
56
            this.placesCloseButton.trigger('click');
57
            this.places.focus();
58
        } else if(isArrowDown || isEnter) {
59
            if ( 0 <  suggestionEl.length) {
3845 idir 60
                suggestionEl.first().focus();
3849 idir 61
            } else {
62
                this.launchSearch();
3845 idir 63
            }
3849 idir 64
        }
3845 idir 65
    }
66
};
67
 
3849 idir 68
TbPlaces.prototype.launchSearch = function () {
3845 idir 69
    if (!!this.places.val()) {
70
        const url = NOMINATIM_OSM_URL+'search',
3869 delphine 71
            params = {
72
                'q': this.places.val(),
73
                'format': 'json',
74
                'polygon_geojson': 1,
75
                'zoom': 17
76
            };
3845 idir 77
 
78
        this.placeLabel.addClass('loading');
79
        $.ajax({
80
            method: "GET",
81
            url: url,
82
            data: {...NOMINATIM_OSM_DEFAULT_PARAMS, ...params},
83
            success: this.nominatimOsmResponseCallback.bind(this),
84
            error: () => {
85
                this.placeLabel.removeClass('loading');
3849 idir 86
                this.handleSearchError();
3845 idir 87
            }
88
        });
89
    }
90
};
91
 
92
TbPlaces.prototype.nominatimOsmResponseCallback = function(data) {
93
    this.places.siblings('label').removeClass('loading');
94
    if (0 < data.length) {
95
        this.searchResults = data;
96
        this.setSuggestions();
97
        this.toggleCloseButton();
98
        this.resetOnClick();
99
        this.onSuggestionSelected();
3849 idir 100
    } else {
101
        this.handleSearchError();
3845 idir 102
    }
103
};
104
 
105
TbPlaces.prototype.setSuggestions = function() {
106
    const lthis = this,
107
        acceptedSuggestions = [];
108
 
109
    this.placesResults.empty();
110
    this.searchResults.forEach(suggestion => {
111
        if(lthis.validateSuggestionData(suggestion)) {
112
            const locality = suggestion['display_name'];
113
 
114
            if (locality && !acceptedSuggestions.includes(locality)) {
115
                acceptedSuggestions.push(locality);
116
                lthis.placesResults.append(
117
                    '<li class="tb-places-suggestion" data-place-id="'+suggestion['place_id']+'" tabindex="-1">' +
118
                        locality +
119
                    '</li>'
120
                );
121
            }
122
        }
123
    });
3869 delphine 124
    if(0 < acceptedSuggestions.length) {
125
        this.placesResultsContainer.removeClass('hidden');
126
    } else {
127
        this.resetPlacesSearch();
128
    }
3845 idir 129
};
130
 
131
TbPlaces.prototype.validateSuggestionData = function(suggestion) {
132
    const validGeometry = undefined !== suggestion.lat && undefined !== suggestion.lon,
3869 delphine 133
        typeLocalisation = this.places.data('typeLocalisation') || '',
134
        validGeometryType = 'rue' === typeLocalisation ? 'LineString' === suggestion?.geojson.type : true,
3845 idir 135
        validAddressData = undefined !== suggestion.address,
136
        validDisplayName = undefined !== suggestion['display_name'];
137
 
3869 delphine 138
    return (validGeometry && validGeometryType && validAddressData && validDisplayName);
3845 idir 139
};
140
 
141
TbPlaces.prototype.onSuggestionSelected = function() {
142
    const lthis = this;
143
 
144
    $('.tb-places-suggestion').off('click').on('click', function (evt) {
145
        const $thisSuggestion = $(this),
146
            suggestion = lthis.searchResults.find(suggestion => suggestion['place_id'] === $thisSuggestion.data('placeId'));
147
 
148
        evt.preventDefault();
149
 
150
        lthis.places.val($thisSuggestion.text());
151
        lthis.clientCallback(suggestion);
152
        lthis.placesCloseButton.trigger('click');
153
 
154
    }).off('keydown').on('keydown', function (evt) {
155
        evt.preventDefault();
156
 
157
        const $thisSuggestion = $(this);
158
 
159
        if (13 === evt.keyCode || 'Enter' === evt.key) {
160
            $thisSuggestion.trigger('click');
161
        } else if (38 === evt.keyCode || 'ArrowUp'=== evt.key) {
162
            if(0 < $thisSuggestion.prev().length) {
163
                $thisSuggestion.prev().focus();
164
            } else {
165
                lthis.places.focus();
166
            }
167
        } else if((40 === evt.keyCode || 'ArrowDown' === evt.key) && 0 < $thisSuggestion.next().length) {
168
            $thisSuggestion.next().focus();
169
        } else if (27 === evt.keyCode || ESC_KEY_STRING.test(evt.key)) {
170
            lthis.placesCloseButton.trigger('click');
171
            lthis.places.focus();
172
        }
173
    });
174
};
175
 
176
TbPlaces.prototype.resetOnClick = function () {
177
    const lthis = this;
178
 
179
    this.placesCloseButton.off('click').on('click', function (event) {
180
        event.preventDefault();
181
        lthis.resetPlacesSearch();
182
    });
183
};
184
 
185
TbPlaces.prototype.toggleCloseButton = function(isShow = true) {
186
    this.placesCloseButton.toggleClass('hidden', !isShow);
187
    $('.tb-places-search-icon').toggleClass('hidden', isShow);
188
};
189
 
3849 idir 190
TbPlaces.prototype.handleSearchError = function() {
191
    this.resetPlacesSearch();
192
    if (0 === $('#tb-places-error').length) {
193
        this.places.closest('#tb-places-zone').after(
194
            `<span id="tb-places-error" class="error mb-3 mt-3">
3869 delphine 195
                Votre recherche n’a pas donné de résultat pour le moment.<br>Vous pouvez soit poursuivre ou modifier votre recherche,<br>soit rechercher votre station directement sur la carte.
3849 idir 196
            </span>`
197
        );
198
        setTimeout(function() {
199
            $('#tb-places-error').remove();
3869 delphine 200
        }, 10000);
3849 idir 201
    }
202
};
203
 
3845 idir 204
TbPlaces.prototype.resetPlacesSearch = function() {
205
    this.toggleCloseButton(false);
206
    this.placesResultsContainer.addClass('hidden');
207
    this.placesResults.empty();
208
};
209