PageSourceSearch

https://www.traversiers.com/_assets/26dcebd1710e5fe9e47e5c1f9979e512/js/modules/schedule.js?1789147798

js traversiers.com collected 2026-09-24 20:00:27 UTC 23,985 bytes, 588 lines download raw bytes

1if(typeof STQ === 'undefined') var STQ = {};
2
3/**
4 * Affichage des horaire pour la traverse en cours.
5 */
6STQ.schedule = (function(){
7    var DEPARTURE_CODE_REGULAR  = 'regular';
8
9    var $box;
10    var $aria_container;
11    var $grid_container;
12    var $header_container;
13
14    var date_picker = document.querySelector('.js--datepicker');
15    var $button_previous;
16    var $button_next;
17    var $button_today;
18
19    var nextDepartureInitiated = false;
20    var $nextDepartureTemplate;
21    var $nextDepartureBox;
22    var $scheduleBox;
23    var directionSelected = 0;
24    var times = [];
25    var coordinates = [];
26    var special_departure_types= {};
27    var directionSelectedByLocalStorage = false;
28
29    var schedules = [];
30    var legendNeeded = [];
31
32    var route_code;
33
34    var expected_date;
35    var last_click_delay;
36
37    var night_start_date;
38    var night_end_date;
39
40    function run(){
41        $box = $(".js--schedule");
42        $aria_container = $(".js--schedule-aria");
43
44        $nextDepartureTemplate = $('#next-departure-template');
45        $nextDepartureBox = $($nextDepartureTemplate.html());
46        $scheduleBox = $('.js--schedule');
47
48        if($box.length > 0){
49            //Current page is a schedule page.
50            route_code = $box.data('appid');
51
52            $button_previous = $box.find('.js--date-prev');
53            $button_next = $box.find('.js--date-next');
54            $button_today = $box.find('.js--schedule-today');
55
56            initEvents();
57            showToday();
58        }
59    }
60
61    function showToday() {
62        var date = formatDateYearMonthDay(new Date(), "/");
63        useDate(date);
64        refreshDepartures();
65    }
66
67    function useDate(date) {
68        date = date.replace("/", "-");
69        date = date.replace("/", "-");
70        date_picker.value = date;
71    }
72
73    function initEvents(){
74        $button_next.attr('disabled', true);
75        $button_previous.attr('disabled', true);
76
77        $button_today.click(function(e){
78            showToday();
79            e.preventDefault();
80        });
81
82        // Fait un increment de 24h avec la date
83        $button_next.click(function(e){
84            e.preventDefault();
85            var next_date;
86            // If nothing selected, don't go directly to tomorrow, select today first.
87            if(getDate() !== false){
88                next_date = incrementDate(1);
89                useDate(next_date);
90                refreshDepartures();
91            }else{
92                showToday();
93            }
94        });
95
96        // Réduit la date  de 24h
97        $button_previous.click(function(e){
98            e.preventDefault();
99            if(getDate() !== false){
100                if(new Date(getDate()).getTime() > new Date().getTime()){
101                    useDate(incrementDate(-1));
102                    refreshDepartures();
103                }
104            }
105        });
106
107        date_picker.value = formatDateYearMonthDay(new Date(), '-');
108        //trigger le call ajax si l'itineraire est choisi
109        date_picker.addEventListener('duetChange', function() {
110            if(isStringDateValid(date_picker.value)){
111                refreshDepartures();
112            }else{
113                showToday();
114            }
115        });
116
117        // set the datepicker to limit the minimal date to today
118        date_picker.min = formatDateYearMonthDay(new Date(), '-');
119    }
120
121    /**
122     * Validate une date pour verifier sont format (YYYY/MM/DD YYYY-MM-DD)
123     * (valide seulement les mois 01-12 et jours 01-31)
124     *
125     * @param {string} string_date la date en chaîne de caractères
126     *
127     * @returns {boolean} True si la date est valide
128     */
129    function isStringDateValid(string_date){
130
131        var date_valid = false;
132        // verify id YYYY/MM/DD or YYYY-MM-DD  is a realistic date
133        var date_regex = /^\d{4}[-\/]((0\d)|(1[012]))[-\/](([012]\d)|3[01])$/;
134        if(date_regex.test(string_date)){
135            date_valid = true;
136        }
137
138        return date_valid
139    }
140
141    function refreshDepartures(){
142        $box.find('.js--departure-list-wrapper').empty();
143        $box.find('.departure-list-wrapper').addClass('is-loading');
144
145        var date = new Date(getDate());
146        // API required dashes.
147        expected_date = formatDateYearMonthDay(date, "-");
148
149        // Delay fetch to avoid spamming server if user fast-clicks on arrows buttons.
150        clearTimeout(last_click_delay);
151        last_click_delay = setTimeout(function(){
152            STQ.eaProxy.getSchedule(route_code, expected_date, STQ.language, function(data){
153                // Make sure latecomer aren't used if they are not the final request.
154                var answer_date = data.jour_demande;
155                if(answer_date == expected_date || STQ.Mock){
156
157                    // Build departures.
158                    var itineraries = data.trajet;
159                    var departure_types = {};
160                    var departure_types_raw = data.type;
161
162                    var server_local_time = data.heure_locale;
163                    var server_local_date = data.journee_locale;
164
165                    var type_unique_number = 1;
166                    for(var code in departure_types_raw){
167
168                        var current_type_number;
169                        if(code !== DEPARTURE_CODE_REGULAR){
170                            current_type_number = type_unique_number;
171                            type_unique_number++;
172                        }else{
173                            current_type_number = 0;
174                        }
175                        departure_types[code] = {
176                            type_unique_number: current_type_number,
177                            text: departure_types_raw[code]
178                        };
179                    }
180
181                    //type de départ utiliée
182                    special_departure_types = {};
183
184                    //day has departures
185                    var day_has_departures = false;
186
187                    if(!nextDepartureInitiated){
188                        nextDepartureInitiated = true;
189                        var datenow = new Date(data.journee_locale.replace(/-/g, "/") +' ' +data.heure_locale);
190                        setupNextDepartures(itineraries, datenow);
191                    }
192
193                    $.each(itineraries, function(keyItineraries, itinerary){
194                        var from = itinerary.rive_depart;
195                        var to = itinerary.rive_arrivee;
196                        var divider = $box.find('.traverse-schedule-direction').data('divider');
197                        var direction  = from + ' ' + divider + ' ' + to;
198
199                        schedules[keyItineraries] = {
200                            from: from,
201                            to: to,
202                            direction : direction,
203                            schedule : [],
204                            nextDaySchedule : [],
205                            legends : []
206                        };
207
208                        // Only handling a single day
209                        var day = itinerary.jour.pop();
210                        var next_departure_found = false;
211                        if(day !== undefined && day.length !== 0){
212                            $.each(day.depart, function(key, departure){
213                                // Départ de la « nuit » : appartient au lendemain de la journée demandée.
214                                var is_next_day = departure.is_next_day === true;
215
216                                var departure_time = departure.heure;
217                                var $departure_markup = $($("#schedule-single-row-template").html());
218
219                                var time_parts = departure_time.split(":");
220                                $departure_markup.find('.js--departure-time').html(STQ.dateFormatter.getFormattedTime(time_parts[0], time_parts[1]));
221
222                                if (!is_next_day && data.jour_demande === data.journee_locale && !next_departure_found) {
223                                    if (departure.is_past) {
224                                        $departure_markup.find('.departure-wrapper').addClass('is-passed');
225                                        $departure_markup.find('.is-passed-msg').remove();
226                                    } else {
227                                        next_departure_found = true;
228                                    }
229                                }
230
231                                var type_code = departure.type;
232
233                                $departure_markup.find('.js--departure-type-note').html(departure_types[type_code].text);
234
235                                // Set departure type. Ignore Regular.
236                                if(type_code != DEPARTURE_CODE_REGULAR){
237                                    var departure_unique_number = departure_types[type_code].type_unique_number;
238                                    $departure_markup.find('.departure-wrapper').addClass('legend-' + departure_unique_number);
239
240                                    special_departure_types[type_code] = departure_types[type_code];
241                                    schedules[keyItineraries].legends.push(departure.type);
242                                }
243
244                                if(is_next_day){
245                                    schedules[keyItineraries].nextDaySchedule.push($departure_markup);
246                                }else{
247                                    day_has_departures = true;
248                                    schedules[keyItineraries].schedule.push($departure_markup);
249                                }
250                            });
251                        }
252                        schedules[keyItineraries].legends = schedules[keyItineraries].legends.filter(function (value, index, self) {
253                            return self.indexOf(value) === index;
254                        });
255                    });
256
257                    // Build heading.
258                    var date_parts = answer_date.split("-");
259                    // Month is "0" based
260                    var day = new Date(date_parts[0], (date_parts[1] - 1), date_parts[2]);
261
262                    // Dates utilisées pour l'étiquette des départs de nuit (journée demandée → lendemain).
263                    night_start_date = day;
264                    night_end_date = new Date(day.getFullYear(), day.getMonth(), day.getDate() + 1);
265
266                    $box.find('.js--header-date').text(STQ.dateFormatter.getFormattedTextualDate(day, true));
267                    $aria_container.find('.js--header-date').text(STQ.dateFormatter.getFormattedTextualDate(day, true));
268
269                    updateScheduleDisplay();
270                    $box.find('.departure-list-wrapper').removeClass('is-loading');
271                    $box.show();
272
273                    var isNotToday = new Date(getDate()).getTime() > new Date().getTime();
274                    $button_next.attr('disabled', false);
275                    $button_previous.attr('disabled', !isNotToday);
276
277                    // Add accordion button for long schedule
278                    var scheduleAccs = $('.js--schedule-acc-ctn');
279                    scheduleAccs.each(function(index) {
280                        var accContainer = $(this);
281                        var accContent = accContainer.find('.js--schedule-times');
282                        var accButton = accContainer.find('.js--schedule-acc-btn');
283                        if (accContent.children().length > 6) {
284                            var accMessage = accContainer.find('.js--schedule-acc-msg');
285                            accButton.on('click', function() {
286                                if (accContainer.hasClass('active')) {
287                                    accContainer.removeClass('active');
288                                    accContent.slideUp(300);
289                                    accMessage.html($(this).attr('data-openlabel'));
290                                    $(this).attr('aria-expanded', false);
291                                } else {
292                                    accContainer.addClass('active');
293                                    accContent.slideDown(300);
294                                    accMessage.html($(this).attr('data-closelabel'));
295                                    $(this).attr('aria-expanded', false);
296                                }
297                            });
298
299                            if (index !== 0 && $(document).width() < 585) {
300                                accButton.click();
301                            }
302
303                            $(window).on('resize', function() {
304                                if (!accContainer.hasClass('active') && $(document).width() >= 585) {
305                                    accButton.click();
306                                }
307                            });
308                        } else {
309                            accButton.hide();
310                        }
311                    });
312                }
313            });
314        }, 300);
315    }
316
317    function updateScheduleDisplay() {
318        var $wrapper = $box.find('.js--departure-list-wrapper');
319        var $directionMarkup = $($('#schedule-direction-template').html());
320        schedules.forEach(function (s) {
321            var $markup = $directionMarkup.clone();
322            var $noDeparture = $markup.find('.js--no-departures');
323            var $scheduleTimes = $markup.find('.js--schedule-times');
324            $markup.find('.js--direction-start').html(s.from);
325            $markup.find('.js--direction-end').html(s.to);
326            $markup.appendTo($wrapper);
327            if (s.schedule.length){
328                $noDeparture.removeClass('active');
329                $scheduleTimes.html(s.schedule).show();
330                $box.find('.legend-traverse-list').empty();
331                $.each(special_departure_types, function(code, data){
332                    if(s.legends.includes(code)){
333                        var $row = $($("#schedule-header-legend-item-template").html());
334                        $row.filter(".js--legend-row").addClass('legend-' + data.type_unique_number);
335                        $row.find(".js--legend-text").html(data.text);
336                        $row.appendTo($box.find('.legend-traverse-list'));
337                    }
338                });
339                $box.find('.legend-traverse').toggle(s.legends.length > 0);
340            } else {
341                $scheduleTimes.empty();
342                $noDeparture.addClass('active');
343            }
344
345            // Départs de la nuit (lendemain), affichés à la suite de la liste principale.
346            if (s.nextDaySchedule && s.nextDaySchedule.length) {
347                var $nextDayTemplate = $('#schedule-nextday-template');
348                var $nextDayBlock = $($nextDayTemplate.html());
349
350                $nextDayBlock.find('.js--nextday-label').html(
351                    STQ.dateFormatter.getNightLabel(
352                        night_start_date,
353                        night_end_date,
354                        $nextDayTemplate.data('prefix'),
355                        $nextDayTemplate.data('sep')
356                    )
357                );
358                $nextDayBlock.find('.js--nextday-times').html(s.nextDaySchedule);
359
360                var seeOtherLabel = String($nextDayTemplate.data('seeother')).replace(
361                    '%date%',
362                    STQ.dateFormatter.getFormattedDayMonth(night_end_date)
363                );
364                $nextDayBlock.find('.js--see-other-times-label').html(seeOtherLabel);
365                $nextDayBlock.find('.js--see-other-times')
366                    .on('click', function(e){
367                        e.preventDefault();
368                        // Déclenche la même action que le bouton « Date suivante ».
369                        $button_next.click();
370                        // Remonte directement vers l'en-tête de l'horaire.
371                        var $header = $box.find('.js--schedule-box');
372                        if($header.length){
373                            $('html, body').scrollTop($header.offset().top - 50);
374                        }
375                    });
376
377                $nextDayBlock.appendTo($markup);
378            }
379        });
380        $wrapper.show();
381    }
382
383    function setupNextDepartures(itineraries, date) {
384        if($nextDepartureTemplate.length === 0){
385            return;
386        }
387
388        // Date
389        var dateString = STQ.dateFormatter.getFormattedTextualDate(date, true);
390        dateString = dateString.charAt(0).toUpperCase() + dateString.slice(1);
391        $nextDepartureBox.filter('.traverse-today').html(dateString);
392
393        var nextDepartureData = getNextDepartureData(itineraries);
394
395        // Génère HTML avec les données
396        const groupTemplate = $nextDepartureBox.find('.traverse-infos').clone();
397        const coastTemplate = $nextDepartureBox.find('.next-departure-direction').clone()
398        groupTemplate.find('.next-departure-direction').remove()
399        $nextDepartureBox.filter('.traverse-infos-groups').empty();
400
401        var nextDepartures = $('<div></div>');
402        nextDepartureData.forEach(function(departureGroup){
403            var group = groupTemplate.clone();
404            group.find('.traverse-duration span').show().html(' ' + departureGroup[0].time);
405            if(departureGroup[0].time  === null){
406                group.find('.traverse-duration').hide();
407
408            }
409
410            departureGroup.forEach(function(direction){
411                var directionHtml = coastTemplate.clone();
412                directionHtml.find('.js--direction-start').html(direction.start);
413                directionHtml.find('.js--direction-end').html(direction.end);
414
415                var nextDeparturesTimes = direction.departures.map(function(time){
416                    return '<span class="next-departures-' + time.type + '">' + time.text + '</span>';
417                });
418                directionHtml.find('.next-departures').html(nextDeparturesTimes);
419
420                directionHtml.find('.traverse-link-geo').css('display', false).attr('href', direction.coordinates);
421                if(direction.coordinates === null){
422                    directionHtml.find('.traverse-link-geo').hide();
423                    directionHtml.find('.traverse-link-coordinate').css('display', false);
424                }else{
425                    directionHtml.find('.traverse-link-coordinate').hide();
426                }
427
428                group.find('.traverse-next-departures-wrapper').append(directionHtml);
429            });
430
431            nextDepartures.append(group);
432        })
433        nextDepartures = nextDepartures.children();
434        $nextDepartureBox.filter('.traverse-infos-groups').append(nextDepartures);
435
436        var ticketLink = $nextDepartureBox.find('.js-ticket-link');
437        var ticketLinkExt = $nextDepartureBox.find('.js-ticket-link-ext');
438        var reservationLink = $nextDepartureBox.find('.js-reservation-link');
439
440        setDepartureBoxLink(ticketLink, route_code, 'billeterie_ticket', 'store_'+route_code);
441        setDepartureBoxLink(ticketLinkExt, route_code, 'billeterie_ticket_ext', 'store_'+route_code+'_ext_'+STQ.language);
442        setDepartureBoxLink(reservationLink, route_code, 'reservation', 'reservation_'+route_code);
443
444        $nextDepartureBox.insertBefore($nextDepartureTemplate);
445    }
446
447    function setDepartureBoxLink($el, route_code, service_code, url_key)
448    {
449        $el.hide()
450        STQ.eaProxy.routeHasService(route_code, service_code, function(has){
451            if(has){
452                STQ.eaProxy.getUrl(url_key, function(url){
453                    if(url){
454                        $el.attr('href', url);
455                        $el.show();
456                    }
457                })
458            }
459        });
460    }
461
462    function getNextDepartureData(itineraries) {
463        var nextDeparturesData = [];
464        $.each(itineraries, function(key, itinerary) {
465            var time = findTimeFor(itinerary.rive_depart, itinerary.rive_arrivee);
466            var coordinates = findCoordinateFor(itinerary.rive_depart);
467            var departures = getNextDepartures(itinerary).slice(0,2);
468
469            nextDeparturesData[key] = {
470                time: time,
471                coordinates: coordinates,
472                departures: departures,
473                start: itinerary.rive_depart,
474                end: itinerary.rive_arrivee,
475            };
476        });
477
478        const array_chunks = (array, chunk_size) => Array(Math.ceil(array.length / chunk_size)).fill().map((_, index) => index * chunk_size).map(begin => array.slice(begin, begin + chunk_size));
479        return array_chunks(nextDeparturesData, 2);
480    }
481
482    function findTimeFor(start, end){
483        var coastInfos = $nextDepartureTemplate.data('times');
484        var time = null;
485        if(coastInfos){
486            if(coastInfos.length === 1){
487                time = coastInfos[0].time;
488            }else{
489                coastInfos.forEach(function(coast){
490                    if(coast.name.search(start) !== -1 && coast.name.search(end) !== -1){
491                        time = coast.time;
492                    }
493                })
494            }
495        }
496        return time;
497    }
498
499    function findCoordinateFor(point){
500        var coordinatesData = $nextDepartureTemplate.data('geo');
501        var coordinate = null;
502        if(coordinatesData){
503            coordinatesData.forEach(function(coordinateDato){
504                if(coordinateDato.name !== '' && point.search(coordinateDato.name) !== -1 && coordinateDato.address !== ''){
505                    coordinate = "https://www.google.com/maps/dir/?api=1&destination=" + coordinateDato.address;
506                    return true;
507                }
508            })
509        }
510        return coordinate;
511    }
512
513    function getNextDepartures(itinerary) {
514        var day = itinerary.jour[0];
515        var itineraryDepartures = [];
516        if (day !== undefined && day.length !== 0) {
517            $.each(day.depart, function(keyDepart, departure) {
518                var splitTime = departure.heure.split(':');
519                if(!departure.is_past){
520                    itineraryDepartures.push({
521                        text: STQ.dateFormatter.getFormattedTime(splitTime[0], splitTime[1]),
522                        type: 'time'
523                    })
524                }
525            });
526            if(itineraryDepartures.length === 0 && day.depart.length > 0){
527                itineraryDepartures.push({
528                    text: $nextDepartureBox.find('.traverse-next-departures').data('labels').nomore,
529                    type: 'nomore'
530                });
531            }
532        }
533        if(itineraryDepartures.length === 0){
534            itineraryDepartures.push({
535                text: $nextDepartureBox.find('.traverse-next-departures').data('labels').none,
536                type: 'none'
537            });
538        }
539        return itineraryDepartures;
540    }
541
542    function getDate(){
543        var date = date_picker.value;
544        if(date.length == 0){
545            date = false;
546        }else{
547            // Accept HTML5 formats.
548            date = date.replace("-", "/");
549            date = date.replace("-", "/");
550        }
551        return date;
552    }
553
554    function incrementDate(number_of_day){
555        var date = new Date(getDate());
556        date.setDate(date.getDate() + number_of_day);
557        return formatDateYearMonthDay(date, "/");
558    }
559
560    function formatDateYearMonthDay(date, separator){
561        if(!separator){
562            separator = "-";
563        }
564        var month = date.getMonth()+1;
565        var year = date.getFullYear();
566        var day = date.getDate();
567
568        if(month < 10){
569            month = '0' + month;
570        }
571
572        if(day < 10){
573            day = '0' + day;
574        }
575
576        return year + separator + month + separator + day;
577    }
578
579    return {
580        run: run
581    };
582
583})();
584
585//Auto run
586customElements.whenDefined('duet-date-picker').then(() => {
587    STQ.schedule.run();
588});

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.