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.