1/* ===================================================================== 2 Jandaia Hotel - montador de deep link do motor Omnibees 3 --------------------------------------------------------------------- 4 Mantem o href do botao "Ver disponibilidade" sempre sincronizado com 5 os campos do widget. Sem postback e sem parsing no servidor: era isso 6 que devolvia HTTP 500 quando o campo de data era digitado a mao. 7 8 Parametros conforme o manual publico da Omnibees 9 (widgets.omnibees.com/manual/parametros.php). ATENCAO: sao 10 case-sensitive - "NRooms" funciona, "nrooms" nao. 11 12 Sem dependencia de jQuery. Carregado com defer. 13 ===================================================================== */ 14(function () { 15 'use strict'; 16 17 var MAX_AGE_FIELDS = 4; 18 var widgetSeq = 0; 19 20 /* ---------- utilidades ---------- */ 21 22 // "2026-09-01" -> "01092026" (formato que o motor espera) 23 function toOmnibeesDate(iso) { 24 if (!iso || iso.length !== 10) { return ''; } 25 return iso.slice(8, 10) + iso.slice(5, 7) + iso.slice(0, 4); 26 } 27 28 function parseISO(iso) { 29 if (!iso || iso.length !== 10) { return null; } 30 // Meio-dia evita que fuso horario empurre a data para o dia anterior. 31 var d = new Date(iso + 'T12:00:00'); 32 return isNaN(d.getTime()) ? null : d; 33 } 34 35 function toISO(date) { 36 var m = String(date.getMonth() + 1); 37 var d = String(date.getDate()); 38 if (m.length < 2) { m = '0' + m; } 39 if (d.length < 2) { d = '0' + d; } 40 return date.getFullYear() + '-' + m + '-' + d; 41 } 42 43 function addDays(date, n) { 44 var d = new Date(date.getTime()); 45 d.setDate(d.getDate() + n); 46 return d; 47 } 48 49 function clamp(value, min, max) { 50 var n = parseInt(value, 10); 51 if (isNaN(n)) { n = min; } 52 if (n < min) { n = min; } 53 if (n > max) { n = max; } 54 return n; 55 } 56 57 /* ---------- um widget ---------- */ 58 59 function initWidget(root) { 60 var elIn = root.querySelector('[data-jbook-in]'); 61 var elOut = root.querySelector('[data-jbook-out]'); 62 var elAd = root.querySelector('[data-jbook-ad]'); 63 var elCh = root.querySelector('[data-jbook-ch]'); 64 var elCode = root.querySelector('[data-jbook-code]'); 65 var elGo = root.querySelector('[data-jbook-go]'); 66 var elAges = root.querySelector('[data-jbook-ages]'); 67 var elAgeList = root.querySelector('[data-jbook-ages-list]'); 68 var elError = root.querySelector('[data-jbook-error]'); 69 70 if (!elIn || !elOut || !elGo) { return; } 71 72 // Prefixo unico: a mesma pagina pode ter mais de um widget 73 // (hero e barra fixa do mobile), e os ids dos campos de idade 74 // sao gerados em runtime. 75 widgetSeq += 1; 76 var uid = root.id || ('jbook' + widgetSeq); 77 78 var chain = root.getAttribute('data-chain') || '2974'; 79 var hotel = root.getAttribute('data-hotel') || '5444'; 80 var room = root.getAttribute('data-room') || ''; 81 82 function showError(msg) { 83 if (!elError) { return; } 84 if (msg) { 85 elError.textContent = msg; 86 elError.hidden = false; 87 } else { 88 elError.textContent = ''; 89 elError.hidden = true; 90 } 91 } 92 93 /* Campos de idade: um por crianca declarada. Preserva o que ja 94 foi digitado quando o contador muda. */ 95 function syncAgeFields() { 96 if (!elCh || !elAges || !elAgeList) { return; } 97 98 var qty = clamp(elCh.value, 0, MAX_AGE_FIELDS); 99 var existing = elAgeList.querySelectorAll('[data-jbook-age]'); 100 var i; 101 102 var previous = []; 103 for (i = 0; i < existing.length; i++) { 104 previous.push(existing[i].value); 105 } 106 107 if (qty === 0) { 108 elAges.hidden = true; 109 elAgeList.innerHTML = ''; 110 return; 111 } 112 113 elAges.hidden = false; 114 elAgeList.innerHTML = ''; 115 116 for (i = 0; i < qty; i++) { 117 var wrap = document.createElement('div'); 118 wrap.className = 'jbook__age'; 119 120 var id = uid + '_age_' + i; 121 122 var label = document.createElement('label'); 123 label.className = 'jbook__age-label'; 124 label.setAttribute('for', id); 125 label.textContent = 'Crianca ' + (i + 1); 126 127 var input = document.createElement('input'); 128 input.type = 'number'; 129 input.id = id; 130 input.className = 'jbook__age-input'; 131 input.min = '0'; 132 input.max = '17'; 133 input.step = '1'; 134 input.inputMode = 'numeric'; 135 input.setAttribute('data-jbook-age', ''); 136 // Default 8: e' a faixa de cortesia mais comum e evita que 137 // o motor receba "0" silenciosamente, o que restringe a 138 // disponibilidade sem o hospede perceber. 139 input.value = (previous[i] !== undefined && previous[i] !== '') ? previous[i] : '8'; 140 input.addEventListener('input', update); 141 input.addEventListener('change', update); 142 143 wrap.appendChild(label); 144 wrap.appendChild(input);
145 elAgeList.appendChild(wrap); 146 } 147 } 148 149 /* Coerencia das datas: check-in nunca no passado, check-out 150 sempre pelo menos um dia depois do check-in. */ 151 function normaliseDates() { 152 var today = parseISO(toISO(new Date())); 153 var din = parseISO(elIn.value); 154 var dout = parseISO(elOut.value); 155 156 if (!din || din < today) { 157 din = addDays(today, 1); 158 elIn.value = toISO(din); 159 } 160 161 // O minimo do check-out acompanha o check-in escolhido. 162 elOut.min = toISO(addDays(din, 1)); 163 164 if (!dout || dout <= din) { 165 dout = addDays(din, 1); 166 elOut.value = toISO(dout); 167 } 168 169 return { checkIn: din, checkOut: dout }; 170 } 171 172 function buildUrl() { 173 var dates = normaliseDates(); 174 175 var adults = elAd ? clamp(elAd.value, 1, 6) : 2; 176 var children = elCh ? clamp(elCh.value, 0, MAX_AGE_FIELDS) : 0; 177 178 if (elAd) { elAd.value = adults; } 179 if (elCh) { elCh.value = children; } 180 181 var url; 182 if (room) { 183 // Abre a categoria ja selecionada dentro do motor. 184 url = 'https://book.omnibees.com/hotel/' + encodeURIComponent(hotel) + 185 '/room/' + encodeURIComponent(room) + '?c=' + encodeURIComponent(chain); 186 } else { 187 url = 'https://book.omnibees.com/hotelresults?c=' + encodeURIComponent(chain) + 188 '&q=' + encodeURIComponent(hotel); 189 } 190 191 url += '&NRooms=1'; 192 url += '&CheckIn=' + toOmnibeesDate(toISO(dates.checkIn)); 193 url += '&CheckOut=' + toOmnibeesDate(toISO(dates.checkOut)); 194 url += '&ad=' + adults; 195 url += '&ch=' + children; 196 197 if (children > 0) { 198 var nodes = elAgeList ? elAgeList.querySelectorAll('[data-jbook-age]') : []; 199 var ages = []; 200 for (var i = 0; i < nodes.length; i++) { 201 ages.push(clamp(nodes[i].value, 0, 17)); 202 } 203 // O motor separa idades por ponto-e-virgula. 204 // encodeURIComponent cuida de virar %3B. 205 var joined = encodeURIComponent(ages.join(';')); 206 url += '&ag=' + joined + '&child_age_input=' + joined; 207 } 208 209 var code = elCode ? elCode.value.trim() : ''; 210 if (code) { 211 // "Code" e' case-sensitive no motor: nao normalizar. 212 url += '&Code=' + encodeURIComponent(code); 213 } 214 215 url += '&lang=pt-BR¤cyId=16'; 216 217 return { url: url, adults: adults, children: children, dates: dates }; 218 } 219 220 function update() { 221 var built = buildUrl(); 222 elGo.setAttribute('href', built.url); 223 showError(''); 224 return built; 225 } 226 227 /* Steppers */ 228 function bindStepper(action, attr, min, max) { 229 var buttons = root.querySelectorAll('[data-jbook-' + action + '="' + attr + '"]'); 230 var target = attr === 'ad' ? elAd : elCh; 231 if (!target) { return; } 232 233 for (var i = 0; i < buttons.length; i++) { 234 buttons[i].addEventListener('click', function () { 235 var current = clamp(target.value, min, max); 236 target.value = clamp(current + (action === 'inc' ? 1 : -1), min, max); 237 if (target === elCh) { syncAgeFields(); } 238 update(); 239 }); 240 } 241 } 242 243 bindStepper('inc', 'ad', 1, 6); 244 bindStepper('dec', 'ad', 1, 6); 245 bindStepper('inc', 'ch', 0, MAX_AGE_FIELDS); 246 bindStepper('dec', 'ch', 0, MAX_AGE_FIELDS); 247 248 [elIn, elOut, elAd, elCode].forEach(function (el) { 249 if (!el) { return; } 250 el.addEventListener('change', update); 251 el.addEventListener('input', update); 252 }); 253 254 if (elCh) { 255 elCh.addEventListener('change', function () { syncAgeFields(); update(); }); 256 elCh.addEventListener('input', function () { syncAgeFields(); update(); }); 257 } 258 259 /* Medicao: o site nao media nada do funil de reserva. 260 O evento vai antes da navegacao; o linker cross-domain do GA4 261 decora o clique de ancora sozinho. */ 262 elGo.addEventListener('click', function () {
263 var built = update(); 264 if (typeof window.gtag === 'function') { 265 window.gtag('event', 'inicio_reserva', { 266 motor: 'omnibees', 267 categoria: room || 'busca_geral', 268 adultos: built.adults, 269 criancas: built.children, 270 noites: Math.round( 271 (built.dates.checkOut - built.dates.checkIn) / 86400000 272 ) 273 }); 274 } 275 }); 276 277 syncAgeFields(); 278 update(); 279 } 280 281 function init() { 282 var widgets = document.querySelectorAll('[data-jbook]'); 283 for (var i = 0; i < widgets.length; i++) { 284 initWidget(widgets[i]); 285 } 286 } 287 288 if (document.readyState === 'loading') { 289 document.addEventListener('DOMContentLoaded', init); 290 } else { 291 init(); 292 } 293})();
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.