PageSourceSearch

https://www.ma-famille-bonheur.fr/modules/chronopost/views/js/chronorelais-17.js

js ma-famille-bonheur.fr collected 2026-10-08 01:59:41 UTC 26,083 bytes, 769 lines download raw bytes

1/**
2 * 2015-2024 Chronopost SAS
3 *
4 * NOTICE OF LICENSE
5 *
6 * This source file is subject to the Academic Free License 3.0 (AFL-3.0)
7 * that is bundled with this package in the file LICENSE.txt.
8 * It is also available through the world-wide-web at this URL:
9 * https://opensource.org/licenses/AFL-3.0
10 * If you did not receive a copy of the license and are unable to
11 * obtain it through the world-wide-web, please send an email
12 * to [email protected] so we can send you a copy immediately.
13 *
14 * @author    Chronopost SAS <[email protected]>
15 * @copyright 2015-2024 Chronopost SAS
16 * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
17 */
18
19/* Check if jquery exist */
20window.onload = function () {
21    if (!window.jQuery) {
22        (function () {
23            const script = document.createElement("script");
24            script.src = '/modules/chronopost/views/js/jquery-1.11.0.min.js';
25            script.type = 'text/javascript';
26            document.head.appendChild(script);
27        })();
28    }
29}
30
31var marker_group = null;
32var defaultPostCode = "75001";
33var initRelaisMapTimeout = null;
34var userJustSelectedRelayPoint = false;
35var postcodeInputTimeout = null;
36var userIsEditingPostcode = false;
37var userIsInteractingWithMap = false;
38var lastMapInteractionTime = 0;
39
40function isDeliveryStepActive() {
41    var $deliveryStep = $("#checkout-delivery-step");
42    if ($deliveryStep.length) {
43        // Support both PS 9.1/Hummingbird (step--current) and Classic theme (-current)
44        var hasCurrentClass = $deliveryStep.hasClass("-current") || $deliveryStep.hasClass("step--current");
45        var hasJsClass = $deliveryStep.hasClass("js-current-step");
46        return hasCurrentClass && hasJsClass;
47    }
48
49    // Fallback for one-page checkout or legacy flows without step markers
50    return $('body').hasClass('order-opc');
51}
52
53function capitalize(string) {
54    return string.charAt(0).toUpperCase() + string.slice(1);
55}
56
57/**
58 * Submit postCode change and rebuild markers
59 */
60function postcodeChangeEvent() {
61    initRelaisMap(null, $('#relais_postcode').val(), null, cust_country);
62}
63
64/**
65 * Toogle map when "Relais" carrier is selected
66 *
67 * @param address
68 * @param codePostal
69 * @param city
70 * @param e
71 * @returns {boolean}
72 */
73function toggleRelaisMap(address, codePostal, city, e) {
74    var valueChecked = $("#js-delivery .form-check > input[type=radio]:checked, #js-delivery .custom-radio > input[type=radio]:checked").val();
75    if (valueChecked !== undefined) {
76        valueChecked = valueChecked.replace(/,$/, '');
77    }
78
79    var carrierChecked = $("input[name^='delivery_option']:checked, input[name=id_carrier]:checked").val();
80    if (carrierChecked !== undefined) {
81        carrierChecked = carrierChecked.replace(/,$/, '');
82    }
83
84    var postCodeControls = $('#chrono_postcode_controls');
85    var chronorelaisContainer = $('#chronorelais_container');
86
87    if (valueChecked === CHRONORELAIS_AMBIENT_ID || carrierChecked === CHRONORELAIS_AMBIENT_ID_INT ||
88        valueChecked === CHRONORELAIS_ID || carrierChecked === CHRONORELAIS_ID_INT ||
89        valueChecked === RELAISEUROPE_ID || carrierChecked === RELAISEUROPE_ID_INT ||
90        valueChecked === RELAISDOM_ID || carrierChecked === RELAISDOM_ID_INT ||
91        valueChecked === TOSHOPDIRECT_ID || carrierChecked === TOSHOPDIRECT_ID_INT ||
92        valueChecked === TOSHOPDIRECT_EUROPE_ID || carrierChecked === TOSHOPDIRECT_EUROPE_ID_INT) {
93        if (typeof e !== "undefined") {
94            e.stopPropagation();
95        }
96
97        // Show Chronorelais controls
98        chronorelaisContainer.show();
99        postCodeControls.show();
100
101        initRelaisMap(address, codePostal, city, cust_country);
102
103        // Force Leaflet to recalculate map size after container becomes visible
104        if (relais_map !== null && typeof relais_map.invalidateSize === 'function') {
105            setTimeout(function() {
106                relais_map.invalidateSize();
107            }, 200);
108        }
109
110        return false;
111    }
112
113    // Hide Chronorelais controls
114    chronorelaisContainer.hide();
115    postCodeControls.hide();
116}
117
118/**
119 * Initialize layers and build markers
120 *
121 * @param address
122 * @param codePostal
123 * @param city
124 * @param country
125 */
126function initRelaisMap(address, codePostal, city, country) {
127    if (typeof codePostal === 'undefined') {
128        return;
129    }
130
131    // Skip if user just manually selected a relay point
132    if (userJustSelectedRelayPoint) {
133        console.log('[Chronopost] initRelaisMap - skipping, user just selected point');
134        return;
135    }
136
137    if (userIsEditingPostcode) {
138        return;
139    }
140
141    if (userIsInteractingWithMap) {
142        return;
143    }
144
145    var timeSinceLastInteraction = Date.now() - lastMapInteractionTime;
146    if (lastMapInteractionTime > 0 && timeSinceLastInteraction < 2000) {
147        return;
148    }
149
150    // Get Leaflet map.
151    if (relais_map === null) {
152        relais_map = mapInit();
153
154        if (relais_map === null) {
155            return;
156        }
157
158        // Initialize imagery
159        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
160            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
161        }).addTo(relais_map);
162
163        // Add markers layer
164        marker_group = L.featureGroup();
165        marker_group.addTo(relais_map);
166
167        relais_map.on('dragstart zoomstart click mousedown', function() {
168            userIsInteractingWithMap = true;
169            lastMapInteractionTime = Date.now();
170        });
171
172        relais_map.on('dragend zoomend moveend', function() {
173            lastMapInteractionTime = Date.now();
174            setTimeout(function() {
175                userIsInteractingWithMap = false;
176            }, 1000);
177        });
178
179        relais_map.on('click', function() {
180            lastMapInteractionTime = Date.now();
181            setTimeout(function() {
182                userIsInteractingWithMap = false;
183            }, 1000);
184        });
185
186        relais_map.on('popupopen', function() {
187            userIsInteractingWithMap = true;
188            lastMapInteractionTime = Date.now();
189        });
190
191        relais_map.on('popupclose', function() {
192            lastMapInteractionTime = Date.now();
193            setTimeout(function() {
194                userIsInteractingWithMap = false;
195            }, 1000);
196        });
197    } else {
198        // Clean old markers
199        marker_group.clearLayers();
200    }
201
202    // Build all markers
203    initRelaisMarkers(address, codePostal, city, country);
204}
205
206/**
207 * Initialize Leaflet map
208 *
209 * @returns {*}
210 */
211function mapInit() {
212    if ($('#chronorelais_map').length === 0) {
213        return null;
214    }
215
216    return L.map('chronorelais_map').setView([0, 0], 12);
217}
218
219/**
220 * Save customer "relais"
221 *
222 * @param relaisID
223 */
224function associateRelais(relaisID) {
225    if (isDeliveryStepActive()) {
226        chrAjaxRequest({
227            'action': 'storePointRelais',
228            'relaisID': relaisID,
229            'cartID': cartID,
230            'customerFirstname': encodeURIComponent(cust_firstname),
231            'customerLastname': encodeURIComponent(cust_lastname),
232        }, 'GET')
233            .catch((error) => {
234                console.log(error);
235            });
236    }
237}
238
239/**
240 * Create all "relais" list
241 *
242 * @param json
243 */
244function createAllPointRelais(json) {
245    var htmlForTxtSection = '';
246    var data;
247
248    try {
249        data = JSON.parse(json);
250    } catch (e) {
251        console.error('[Chronopost] Invalid JSON response:', json);
252        console.error('[Chronopost] Parse error:', e);
253        alert('Erreur: la réponse du serveur n\'est pas valide. Vérifiez la console pour plus de détails.');
254        return;
255    }
256
257    if (data.errorCode !== 0) {
258        alert('Code postal erroné, merci de modifier le code postal dans votre adresse de livraison.');
259
260        // init with original zipcode
261        cust_address = defaultPostCode + ", France";
262        $('#relais_postcode').val(defaultPostCode);
263        initRelaisMap(cust_address, defaultPostCode);
264
265        return;
266    }
267
268    if (data.hasOwnProperty("listePointRelais") && data.listePointRelais) {
269        var listePointsRelais = data.listePointRelais;
270
271        let associateRelaisId = listePointsRelais[0].identifiant;
272        if (listePointsRelais.length !== undefined) {
273            for (var i = 0; i < listePointsRelais.length; i++) {
274                // COMPAT WITH OLD RECHERCHEBT
275                var id = listePointsRelais[i].identifiant;
276                var nom = listePointsRelais[i].nom;
277                var adresse = listePointsRelais[i].adresse1;
278                var postalCode = listePointsRelais[i].codePostal;
279                var localite = listePointsRelais[i].localite;
280
281                // Create marker
282                createRelaisMarker(listePointsRelais[i]);
283                chronodata[id] = listePointsRelais[i];
284
285                // Build corresponding "relais" input
286                htmlForTxtSection += '<div class="checkbox"><label><input type="radio" name="chronorelaisSelect" id="bt' + id + '" value="' + id + '"';
287                if (i === 0 && data['lastPR'] === '' || data['lastPR'] === id) {
288                    htmlForTxtSection += " checked";
289                    associateRelaisId = id;
290                }
291                htmlForTxtSection += '/> ' + nom + ' - ' + adresse + ' - ' + postalCode + ' ' + localite + '</label></div>';
292            }
293            associateRelais(associateRelaisId);
294        } else {
295            // COMPAT WITH OLD RECHERCHEBT
296            var id = listePointsRelais.identifiant;
297            var nom = listePointsRelais.nom;
298            var adresse = listePointsRelais.adresse1;
299            var postalCode = listePointsRelais.codePostal;
300            var localite = listePointsRelais.localite;
301
302            // Create marker
303            createRelaisMarker(listePointsRelais);
304            chronodata[id] = listePointsRelais;
305
306            // Build corresponding "relais" input
307            htmlForTxtSection += '<div class="checkbox"><label><input type="radio" name="chronorelaisSelect" id="bt' + id + '" value="' + id + '"';
308            htmlForTxtSection += " checked";
309            htmlForTxtSection += '/> ' + nom + ' - ' + adresse + ' - ' + postalCode + ' ' + localite + '</label></div>';
310            associateRelais(listePointsRelais.identifiant);
311        }
312
313        // Add all relais to list
314        $('#relais_txt').html(htmlForTxtSection);
315
316        // Listener for BT select in radio list
317        $('body').on('change', 'input[name="chronorelaisSelect"]', function () {
318            var relaisId = $('input[name="chronorelaisSelect"]:checked').val();
319
320            // Flag that user just made a selection (prevent immediate reload)
321            userJustSelectedRelayPoint = true;
322            userIsInteractingWithMap = true;
323            lastMapInteractionTime = Date.now();
324
325            setTimeout(function() {
326                userJustSelectedRelayPoint = false;
327                userIsInteractingWithMap = false;
328            }, 1000);
329
330            associateRelais(relaisId);
331            openMarker(relaisId);
332        });
333
334        // Open closest marker
335        openMarker($('input[name="chronorelaisSelect"]:checked').val());
336
337        // Fit map to markers with tighter zoom
338        var bounds = marker_group.getBounds();
339        if (bounds.isValid()) {
340            relais_map.fitBounds(bounds, {
341                paddingTopLeft: [50, 50],
342                paddingBottomRight: [50, 50],
343                maxZoom: 14  // Limit zoom to avoid being too close
344            });
345        }
346    }
347}
348
349/**
350 * Open marker corresponding to given "relais" ID
351 *
352 * @param relaisId
353 */
354function openMarker(relaisId) {
355    if (typeof relaisId === 'undefined') {
356        return;
357    }
358
359    var marker = map_markers && map_markers[relaisId];
360    if (!marker || typeof marker.openPopup !== 'function') {
361        return;
362    }
363
364    // Ensure popup opens after any DOM/radio updates finish
365    setTimeout(function() {
366        marker.openPopup();
367        if (relais_map && typeof marker.getLatLng === 'function' && typeof relais_map.getBounds === 'function') {
368            var markerPosition = marker.getLatLng();
369            var mapBounds = relais_map.getBounds();
370
371            if (mapBounds && !mapBounds.contains(markerPosition)) {
372                relais_map.panTo(markerPosition);
373            }
374        }
375    }, 50);
376}
377
378/**
379 * Initialize all markers
380 *
381 * @param address
382 * @param cp
383 * @param city
384 * @param country
385 */
386function initRelaisMarkers(address, cp, city, country) {
387    let currentCarrier = $('.delivery-options input:checked').val();
388
389    function handleRelayPointsResponse(response) {
390        let data;
391        try {
392            data = JSON.parse(response);
393        } catch (e) {
394            console.error('[Chronopost] Invalid JSON response:', response);
395            createAllPointRelais(response);
396            return;
397        }
398
399        let hasNoResults = !data.listePointRelais ||
400            data.listePointRelais === null ||
401            (Array.isArray(data.listePointRelais) && data.listePointRelais.length === 0);
402
403        if (hasNoResults && city !== null && city !== 'unknown') {
404            chrAjaxRequest({
405                'action': 'getPointRelais2',
406                'city': 'unknown',
407                'codePostal': cp,
408                'country': country,
409                'carrier': currentCarrier,
410                'cartID': cartID,
411            }, 'GET')
412                .then(createAllPointRelais)
413                .catch((error) => {
414                    console.error('[Chronopost] Error loading relay points (retry):', error);
415                });
416        } else {
417            createAllPointRelais(response);
418        }
419    }
420
421    if (city === null) {
422        chrAjaxRequest({
423            'action': 'getPointRelais2',
424            'city': 'unknown',
425            'codePostal': cp,
426            'country': country,
427            'carrier': currentCarrier,
428            'cartID': cartID,
429        }, 'GET')
430            .then(createAllPointRelais)
431            .catch((error) => {
432                console.error('[Chronopost] Error loading relay points:', error);
433            });
434    } else {
435        chrAjaxRequest({
436            'action': 'getPointRelais2',
437            'codePostal': cp,
438            'address': encodeURIComponent(address),
439            'country': country,
440            'city': city,
441            'carrier': currentCarrier,
442            'cartID': cartID,
443        }, 'GET')
444            .then(handleRelayPointsResponse)
445            .catch((error) => {
446                console.error('[Chronopost] Error loading relay points:', error);
447            });
448    }
449}
450
451/**
452 * Create Leaflet Marker for given "relais"
453 *
454 * @param relais
455 */
456function createRelaisMarker(relais) {
457    var relaisIcon = L.icon({
458        iconUrl: path + '/views/img/postal.png',
459        iconSize: [45, 30]
460    });
461
462    var marker = L.marker([relais.coordGeolocalisationLatitude, relais.coordGeolocalisationLongitude], {icon: relaisIcon});
463
464    marker.on('click', function (e) {
465        userIsInteractingWithMap = true;
466        lastMapInteractionTime = Date.now();
467        setTimeout(function() {
468            userIsInteractingWithMap = false;
469        }, 1000);
470
471        $("#bt" + relais.identifiant).click();
472    });
473
474    map_markers[relais.identifiant] = marker;
475    marker.addTo(marker_group).bindPopup(buildMarkerPopup(relais));
476}
477
478/**
479 * Build HTML Marker popup for given "relais"
480 *
481 * @param relais
482 * @returns {string}
483 */
484function buildMarkerPopup(relais) {
485    var popup = '<div class="pointRelais"><h4>'
486        + relais.nom + '</h4><p>' + relais.adresse1 + '<br/ >' + relais.codePostal
487        + ' ' + relais.localite + '</p><h5>' + trad_horaire_ouverture + '</h5><table><tbody>';
488
489    if (typeof relais.listeHoraireOuverture !== 'undefined' && relais.listeHoraireOuverture !== null) {
490        for (var i = 0; i < relais.listeHoraireOuverture.length; i++) {
491            var day = relais.listeHoraireOuverture[i];
492            if (day.horairesAsString !== null) {
493                popup += '<tr class="first_item item"><td>' + capitalize(days[day.jour - 1]) + '</td><td>'
494                    + day.horairesAsString + '</td></tr>';
495            }
496        }
497    }
498
499    popup += '</tbody></table>'
500        + '<p class="text-right"><input type="hidden" name="btID" value="' + relais.identifiant
501        + '"/><a class="button_large btselect" href="javascript:;" class="pull-right">' + trad_selectionner + ' »</a></p>'
502        + '</div>';
503
504    return popup;
505}
506
507// Triggered on BT select from popup
508function btSelect() {
509    // Prevent map reload when selecting from popup
510    userIsInteractingWithMap = true;
511    lastMapInteractionTime = Date.now();
512    setTimeout(function() {
513        userIsInteractingWithMap = false;
514    }, 1000);
515
516    var btID = $(this).parent().children('input').val();
517    var mObj = $('#relais_txt input[value=' + btID + ']');
518    mObj.click();
519
520    associateRelais(btID);
521
522    $("html, body").animate({scrollTop: $('#relais_txt').offset().top}, 500);
523    $("#" + btID).prop('checked', true);
524}
525
526function cleanContainers() {
527    var dummyContainer = $('#chronorelais_dummy_container');
528
529    // move in DOM to prevent compatibility issues with Common Services' modules
530    if ($("#chronorelais_container").length > 0) {
531        dummyContainer.remove();
532    } else {
533        dummyContainer.attr('id', 'chronorelais_container');
534    }
535
536    let checkedDeliveryOption = $("input[name*='delivery_option[']:checked");
537    if (checkedDeliveryOption.length === 0 || typeof checkedDeliveryOption === 'undefined') {
538        return;
539    }
540
541    let carrierVal = checkedDeliveryOption.val();
542    let checkedCarrier = carrierVal.indexOf(',') !== -1 ? carrierVal.substring(0, carrierVal.indexOf(',')) : carrierVal;
543    if (checkedCarrier === CHRONORELAIS_AMBIENT_ID || checkedCarrier === CHRONORELAIS_ID || checkedCarrier === RELAISEUROPE_ID ||
544        checkedCarrier === RELAISDOM_ID || checkedCarrier === TOSHOPDIRECT_ID || checkedCarrier === TOSHOPDIRECT_EUROPE_ID) {
545        let deliveryOptionItem = checkedDeliveryOption.closest('.delivery-option__item.js-delivery-option');
546
547        if (deliveryOptionItem.length === 0) {
548            deliveryOptionItem = checkedDeliveryOption.closest('.delivery-option');
549        }
550
551        if (deliveryOptionItem.length > 0) {
552            $('#chronorelais_container').detach().appendTo(deliveryOptionItem);
553        } else {
554            // Fallback ultime: avant la section commentaire (.order-options)
555            let orderOptions = $('#js-delivery .order-options');
556            if (orderOptions.length > 0) {
557                $('#chronorelais_container').detach().insertBefore(orderOptions);
558            } else {
559                let container = $("[id^=delivery_option]:checked").parents('.delivery-option').children('label');
560                if (container.length === 0) {
561                    container = $('#extra_carrier');
562                }
563                $('#chronorelais_container').detach().insertAfter(container);
564            }
565        }
566    }
567}
568
569$(function () {
570    // Return if variable are undefined
571    if (typeof CHRONORELAIS_AMBIENT_ID === 'undefined' && typeof CHRONORELAIS_ID === 'undefined' &&
572        typeof RELAISEUROPE_ID === 'undefined' && typeof RELAISDOM_ID === 'undefined' &&
573        typeof TOSHOPDIRECT_ID === 'undefined' && typeof TOSHOPDIRECT_EUROPE_ID === 'undefined') {
574        return false;
575    }
576
577    var body = $("body");
578
579    body.on("click", ".gm-style img", function (event) {
580        event.preventDefault();
581
582        var deliveryStep = $("#checkout-delivery-step");
583        if (!deliveryStep.hasClass("-current")) {
584            deliveryStep.addClass("-current");
585        }
586
587        if (!deliveryStep.hasClass("js-current-step")) {
588            deliveryStep.addClass("js-current-step");
589        }
590    });
591
592    // Function to initialize relay map if on delivery step
593    let $deliveryStep = $("#checkout-delivery-step");
594    function initializeRelayMapIfOnDeliveryStep() {
595        if (userIsEditingPostcode) {
596            return;
597        }
598
599        if (userIsInteractingWithMap) {
600            return;
601        }
602
603        var timeSinceLastInteraction = Date.now() - lastMapInteractionTime;
604        if (lastMapInteractionTime > 0 && timeSinceLastInteraction < 2000) {
605            return;
606        }
607
608        if (isDeliveryStepActive()) {
609            cleanContainers();
610            toggleRelaisMap(cust_address_clean, cust_codePostal, cust_city);
611        }
612    }
613
614    // Debounced version to prevent multiple rapid calls
615    function debouncedInitializeRelayMap() {
616        // Clear any pending timeout
617        if (initRelaisMapTimeout) {
618            clearTimeout(initRelaisMapTimeout);
619        }
620        // Set new timeout - will only execute after 150ms of no new calls
621        initRelaisMapTimeout = setTimeout(function() {
622            initializeRelayMapIfOnDeliveryStep();
623            initRelaisMapTimeout = null;
624        }, 150);
625    }
626
627    // Initialize on page load
628    initializeRelayMapIfOnDeliveryStep();
629
630    // Listen to PrestaShop's checkout step change event (triggered by AJAX updates)
631    let currentCheckoutStep = $('.checkout-step.-current.js-current-step, .checkout-step.step--current.js-current-step');
632    prestashop.on('changedCheckoutStep', function(event) {
633        let checkoutStep = $('.checkout-step.-current.js-current-step, .checkout-step.step--current.js-current-step');
634
635        if (!checkoutStep.is(currentCheckoutStep) && isDeliveryStepActive()) {
636            debouncedInitializeRelayMap();
637        }
638
639        currentCheckoutStep = checkoutStep;
640    });
641
642    prestashop.on('updatedDeliveryForm', function(event) {
643        if (isDeliveryStepActive()) {
644            // Give PrestaShop time to finish rendering the form
645            setTimeout(function() {
646                debouncedInitializeRelayMap();
647            }, 100);
648        }
649    });
650
651    // Clean container on load
652    //cleanContainers();
653    //toggleRelaisMap(cust_address_clean, cust_codePostal, cust_city);
654
655    // Listener for selection of the ChronoRelais carrier radio button
656    body.on('click', '#js-delivery .form-check > input[type=radio], #js-delivery .custom-radio > input[type=radio], input[name^="delivery_option"], input[name=id_carrier]', function (e) {
657        cleanContainers();
658        toggleRelaisMap(cust_address_clean, cust_codePostal, cust_city, e);
659
660        var value = parseInt($(this).val());
661        if (value === parseInt(CHRONORELAIS_AMBIENT_ID) || value === parseInt(CHRONORELAIS_ID) ||
662            value === parseInt(RELAISEUROPE_ID) || value === parseInt(RELAISDOM_ID) ||
663            value === parseInt(TOSHOPDIRECT_ID) || value === parseInt(TOSHOPDIRECT_EUROPE_ID)) {
664            if ($('#chronorelais_container').length) {
665                $('html, body').animate({
666                    scrollTop: $('#chronorelais_container').offset().top
667                }, 500);
668            }
669        }
670    });
671
672    body.on('mousedown', '#relais_postcode', function (e) {
673        userIsEditingPostcode = true;
674    });
675
676    body.on('focus', '#relais_postcode', function (e) {
677        userIsEditingPostcode = true;
678    });
679
680    body.on('blur', '#relais_postcode', function (e) {
681        if (!postcodeInputTimeout) {
682            userIsEditingPostcode = false;
683        }
684    });
685
686    body.on('click', '#change_postcode', function(e) {
687        e.preventDefault();
688        e.stopPropagation();
689
690        if (postcodeInputTimeout) {
691            clearTimeout(postcodeInputTimeout);
692            postcodeInputTimeout = null;
693        }
694
695        userIsEditingPostcode = false;
696        postcodeChangeEvent();
697    });
698
699    body.on('input', '#relais_postcode', function (e) {
700        userIsEditingPostcode = true;
701
702        if (postcodeInputTimeout) {
703            clearTimeout(postcodeInputTimeout);
704        }
705
706        var inputValue = $(this).val();
707
708        if (inputValue.length >= 3) {
709            postcodeInputTimeout = setTimeout(function() {
710                userIsEditingPostcode = false;
711                postcodeChangeEvent();
712                postcodeInputTimeout = null;
713            }, 800);
714        }
715    });
716
717    body.on('keydown', '#relais_postcode', function (e) {
718        if (e.which === 13) {
719            e.preventDefault();
720            e.stopPropagation();
721
722            if (postcodeInputTimeout) {
723                clearTimeout(postcodeInputTimeout);
724                postcodeInputTimeout = null;
725            }
726
727            userIsEditingPostcode = false;
728            postcodeChangeEvent();
729            return false;
730        }
731    });
732
733    body.on('mousedown click', '.leaflet-popup, .leaflet-popup-content-wrapper, .leaflet-popup-content', function(e) {
734        userIsInteractingWithMap = true;
735        lastMapInteractionTime = Date.now();
736        console.log('[Chronopost] User clicked on popup element');
737        setTimeout(function() {
738            userIsInteractingWithMap = false;
739        }, 1000);
740    });
741
742    body.on('mousedown touchstart', '#chronorelais_map', function(e) {
743        userIsInteractingWithMap = true;
744        lastMapInteractionTime = Date.now();
745        console.log('[Chronopost] User interacting with map container');
746    });
747
748    body.on('mouseup touchend', '#chronorelais_map', function(e) {
749        lastMapInteractionTime = Date.now();
750        setTimeout(function() {
751            userIsInteractingWithMap = false;
752            console.log('[Chronopost] User finished interacting with map container');
753        }, 1000);
754    });
755
756    // Listener for BT select in popup
757    body.on("click", ".btselect", function (e) {
758        btSelect.call(e.target, e);
759    });
760
761    // Listener for cart navigation to next step
762    body.on('click', 'input[name=processCarrier]', function () {
763        if ($('input[name=id_carrier]:checked').val() == carrierID && !$("input[name=chronorelaisSelect]:checked").val()) {
764            alert($("#errormessage").val());
765            $("html, body").animate({scrollTop: $('#relais_txt_cont').offset().top}, 500);
766            return false;
767        }
768    });
769});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.