1(function ($, Drupal) { 2 Drupal.behaviors.transferfixAll = { 3 attach: function (context, settings) { 4 5 $('.initial-field', context) 6 .once('transferfix-initialField') 7 .on('click', function () { 8 if (!$('.widget-toggle').hasClass('expanded')) { 9 $('.additional-field').show(); 10 $('.widget-toggle').addClass('expanded'); 11 } 12 }); 13 14 $('.widget-toggle', context) 15 .once('transferfix-widgetToggle') 16 .on('click', function () { 17 if (!$(this).hasClass('expanded')) { 18 $('.additional-field').show(); 19 $(this).addClass('expanded'); 20 } 21 else { 22 $('.additional-field').hide(); 23 $(this).removeClass('expanded'); 24 } 25 }); 26 27 $('.search-toggle', context) 28 .once('transferfix-searchToggle') 29 .on('click', function () { 30 var $toggle = $(this); 31 var $wrapper = $toggle.next('.widget-wrapper'); 32 var isOpening = $toggle.hasClass('collapsed'); 33 34 // The outer search toggle controls the whole wrapper. Keep 35 // the date/time fields in sync with the wrapper state. 36 if (isOpening) { 37 $wrapper.find('.additional-field').show(); 38 $wrapper.stop(true, true).slideDown(); 39 } 40 else { 41 $wrapper.stop(true, true).slideUp(function () { 42 $wrapper.find('.additional-field').hide(); 43 }); 44 } 45 46 $toggle.toggleClass('collapsed'); 47 }); 48 49// $('.form-item-from', context) 50// .once('insert-flex-break') 51// .before('<div class="tfix-flex-break"></div>'); 52 53// $('.form-item-passengers', context) 54// .once('insert-flex-filler') 55// //.before('<div class="tfix-flex-filler before-passengers"></div>') 56// .after('<div class="tfix-flex-filler after-passengers"></div>'); 57 58 $('.from-to-switch', context) 59 .once('transferfix-fromToSwitch') 60 .on('click touchend', function (e) { 61 e.preventDefault(); 62 e.stopPropagation(); 63 64 console.log('SWITCH'); 65 66 var $from = $('#edit-from', context); 67 var $to = $('#edit-to', context); 68 69 console.log('switch: from(select) = ' + $from.val()); 70 console.log('switch: to(select) = ' + $to.val()); 71 72// var fromVal = $from.val(); 73// var toVal = $to.val(); 74 if (!$from.length || !$to.length) { 75 return; 76 } 77 78 var tsFrom = $from[0] && $from[0].tomselect; 79 var tsTo = $to[0] && $to[0].tomselect; 80 81// if (tsFrom && tsTo) { 82// // false => not silent; will fire change events inside Tom Select 83// tsFrom.setValue(toVal, false); 84// tsTo.setValue(fromVal, false); 85// 86// // Also emit native change on the underlying <select> for any jQuery listeners 87// tsFrom.input.dispatchEvent(new Event('change', {bubbles: true})); 88// tsTo.input.dispatchEvent(new Event('change', {bubbles: true})); 89// 90// // Only meaningful for 'return' (two datetime labels). If one-way, we do nothing. 91// var journey = ($('input[name="journey_type"]:checked', context).val() || '').toLowerCase(); 92// if (journey !== 'return') { 93// return; 94// } 95// 96// // Grab labels by their 'for' attributes, as rendered by php 97// var $startLbl = $('.tfix-container-label[for="start-date"]', context); 98// var $endLbl = $('.tfix-container-label[for="end-date"]', context); 99// 100// if ($startLbl.length && $endLbl.length && $endLbl.is(':visible')) { 101// // Swap the text content between the two labels 102// var tmp = $startLbl.text(); 103// $startLbl.text($endLbl.text()); 104// $endLbl.text(tmp); 105// } 106// } 107// else { 108// // Fallback if Tom Select not ready yet 109// $from.val(toVal).trigger('change'); 110// $to.val(fromVal).trigger('change'); 111// } 112 113 // Fallback: if TomSelect not ready, just swap values (old behavior) 114 if (!tsFrom || !tsTo) { 115 var f = $from.val(), t = $to.val(); 116 $from.val(t).trigger('change'); 117 $to.val(f).trigger('change'); 118 return; 119 } 120 121 // Datasets precomputed in PHP and exposed via drupalSettings 122 var sets = (settings.transferfix && settings.transferfix.locationSets) || null; 123 124 if (!sets || !sets.hubs || !sets.dest) { 125 // degrade gracefully to value swap 126 var fromVal0 = tsFrom.getValue(); 127 var toVal0 = tsTo.getValue(); 128 tsFrom.setValue(toVal0, false); 129 tsTo.setValue(fromVal0, false); 130 return; 131 } 132 133 console.log('switch: sets.hubs = ' + sets.hubs); 134 135 // Ensure roles exist; default to PHP initial split 136 var fromRole = $from.data('locationType') || 'hubs'; 137 var toRole = $to.data('locationType') || 'dest'; 138 139 // Flip roles on click 140 var newFromRole = (fromRole === 'hubs') ? 'dest' : 'hubs'; 141 var newToRole = (toRole === 'hubs') ? 'dest' : 'hubs'; 142 143 // Keep current selections before we rebuild datasets 144 var prevFromVal = tsFrom.getValue(); 145 var prevToVal = tsTo.getValue(); 146 147 // Helper: apply dataset with optgroups preserved 148 function applySet(ts, list) { 149 // clear options + optgroups completely 150 ts.clearOptions(); 151 ts.optgroups = {}; // reset stored groups 152 153 if (typeof ts.clearCache === 'function') { 154 ts.clearCache(); // clear render cache if available 155 } 156 157 // make sure grouping is enabled 158 ts.settings.optgroupField = 'optgroup'; 159 ts.settings.lockOptgroupOrder = true; 160 161 // Build a whitelist of allowed values 162 var allowed = new Set((list || []).map(function (o) { 163 return o.value; 164 })); 165 166 var seen = new Set();
167 (list || []).forEach(function (o) { 168 var g = o.group || ''; 169 if (g && !seen.has(g)) { 170 ts.addOptionGroup(g, {label: g}); 171 seen.add(g); 172 } 173 //ts.addOption({value: o.value, text: o.label, optgroup: g}); 174 // keep disabled headers coming from PHP (e.g., resort-region-*) 175 var opt = {value: o.value, text: o.label, optgroup: g}; 176 if (o.disabled === true) { 177 opt.disabled = true; 178 } // mark as disabled 179 ts.addOption(opt); 180 181 // defensive: ensure internal option cache is also flagged as disabled 182 if (o.disabled === true && ts.options[o.value]) { 183 ts.options[o.value].disabled = true; 184 } 185 }); 186 187 // Extra safety: purge anything not whitelisted (covers edge TomSelect states) 188 Object.keys(ts.options).forEach(function (val) { 189 if (!allowed.has(val)) { 190 ts.removeOption(val, true); // true = silent 191 } 192 }); 193 194 ts.refreshOptions(false); 195 } 196 197 // Rebuild datasets according to NEW roles 198 var newFromList = sets[newFromRole]; 199 var newToList = sets[newToRole]; 200 applySet(tsFrom, newFromList); 201 applySet(tsTo, newToList); 202 203 // Build membership sets to decide where old selections belong 204 var newFromVals = new Set(newFromList.map(function (o) { 205 return o.value; 206 })); 207 var newToVals = new Set(newToList.map(function (o) { 208 return o.value; 209 })); 210 211 // Decide where to place previous selections 212 var nextFrom = ''; 213 var nextTo = ''; 214 215 if (prevFromVal) { 216 if (newFromVals.has(prevFromVal)) 217 nextFrom = prevFromVal; 218 else if (newToVals.has(prevFromVal)) 219 nextTo = prevFromVal; 220 } 221 if (prevToVal) { 222 if (newToVals.has(prevToVal)) { 223 if (!nextTo) 224 nextTo = prevToVal; 225 226 else 227 nextFrom = prevToVal; 228 } 229 else if (newFromVals.has(prevToVal)) { 230 if (!nextFrom) 231 nextFrom = prevToVal; 232 233 else 234 nextTo = prevToVal; 235 } 236 } 237 238 // Apply new selections (not silent -> triggers change handlers such as updateDateTimeLabels) 239 tsFrom.setValue(nextFrom, false); 240 tsTo.setValue(nextTo, false); 241 242 // Persist NEW roles on elements (no body class; reliable across clicks) 243 $from.data('locationType', newFromRole); 244 $to.data('locationType', newToRole); 245 }); 246 247 // Journey type toggler: add/remove a class on submit wrapper when "return" is selected 248 $('.tfix-search-form', context).once('tfix-journey-scope').each(function () { 249 var $scope = $(this); 250 251 // Bind once per radio group inside THIS container 252 $scope.find('input[type="radio"][name="journey_type"]') 253 .once('tfix-journey-bind') 254 .on('change', function () { 255 // Always resolve wrapper locally; no reliance on <form> ID 256 var $wrap = $(this).closest('.tfix-search-form'); 257 if (!$wrap.length) { 258 // Fallback if markup changes 259 $wrap = $(this).closest('form'); 260 if (!$wrap.length) 261 $wrap = $scope; 262 } 263 264 // Determine if "return" is selected (by value/id/label text) 265 var $checked = $wrap.find('input[type="radio"][name="journey_type"]:checked'); 266 var val = ($checked.val() || '').toLowerCase(); 267 var id = ($checked.attr('id') || '').toLowerCase(); 268 var labelText = ''; 269 if (id) { 270 var $label = $wrap.find('label[for="' + $checked.attr('id') + '"]'); 271 labelText = ($label.text() || '').toLowerCase().trim(); 272 } 273 var reReturn = /(return|retur|round\s*trip|roundtrip)/; 274 var isReturn = reReturn.test(val) || reReturn.test(id) || reReturn.test(labelText); 275 276 // Toggle class ONLY on this container 277 $wrap.toggleClass('has-return', !!isReturn); 278 }); 279 280 // Initialize this container 281 initJourneyGroup($scope); 282 }); 283 284 } 285 }; 286 287 function initJourneyGroup($scope) { 288 // Find radios ONLY inside the given scope 289 var $radios = $scope.find('input[type="radio"][name="journey_type"]'); 290 if (!$radios.length) 291 return; 292 293 var $checked = $radios.filter(':checked'); 294 if ($checked.length) { 295 // Fire change once to sync UI without duplicates 296 $checked.once('tfix-journey-init').trigger('change'); 297 return; 298 } 299 300 // Prefer "return" if available; otherwise first radio 301 var $return = $radios.filter('[value="return"]'); 302 if ($return.length) { 303 $return.prop('checked', true).trigger('change'); 304 } 305 else { 306 $radios.first().trigger('change'); 307 } 308 } 309 310})(jQuery, Drupal);
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.