PageSourceSearch

https://jandaia.com.br/js/booking.js?v=202607231124

js jandaia.com.br collected 2026-10-08 03:04:39 UTC 10,845 bytes, 293 lines download raw bytes

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&currencyId=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.