Subversion Repositories eFlore/Applications.cel

Rev

Rev 3869 | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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