class Search { constructor() { this.searchDebounced = _.debounce((query) => { if (query.length < 3) return; fetch(`/hints.ajax?q=${encodeURIComponent(query)}`) .then(response => { if (!response.ok) throw new Error(`statusText is ${response.statusText}`); return response.json(); }) .then(({response, error}) => { this.unlockButton(); if (error) throw new Error(error); this.autoComplete.setData(response.map(item => { return {label: item, value: ''}; })); this.autoComplete.renderIfNeeded(); }) }, 150); this._filterDebounced = _.debounce((e) => { let filter = e.target; globalMaps.setFilter(filter.value); }, 500) let field = document.getElementById('queryInput'); let btn = document.getElementById('querySubmit'); let filterField = document.getElementById('filterInput'); this.autoComplete = new Autocomplete(field, { data: [], maximumItems: 10, onInput: (value) => { this.searchDebounced(value); }, onSelectItem: ({label}) => { // console.log('selected:', label) }, highlightClass: 'text-danger' }); btn.addEventListener('click', this.onSubmit); field.addEventListener('keydown', this.onInputKeyDown); filterField.addEventListener('input', this._filterDebounced); this.btn = btn; this.field = field; } onInputKeyDown = (e) => { if (e.keyCode === 10 || e.keyCode === 13) this.onSubmit(); } getOffers(query, page) { fetch(`/offers.ajax?q=${encodeURIComponent(this.field.value)}&page=${page}`) .then(response => { if (!response.ok) throw new Error(`statusText is ${response.statusText}`); return response.json(); }) .then(({error, tradeNames, end, offers, pages}) => { if (error) throw new Error(error); if (tradeNames) { this.autoComplete.setData(tradeNames.map(item => { return {label: item, value: ''}; })); this.autoComplete.renderIfNeeded(); return this.unlockButton(); } for (let offer of offers) globalMaps.addOffer(offer); if (page >= pages) { return this.unlockButton(); } else { this.lockButton(pages > 1 ? `${page} из ${pages}` : null); setTimeout(() => { this.getOffers(query, page + 1); }, 1000) } }) .catch((error) => { console.error(error); alert(error); this.unlockButton(); }) } onSubmit = (e) => { if (this.isLocked()) return; this.lockButton('Загрузка...'); globalMaps.removeAllPoints(); this.getOffers(this.field.value, 1); } isLocked() { return this.btn.classList.contains('disabled'); } lockButton(text) { if (text !== null) this.btn.innerText = text; this.btn.classList.add('disabled'); } unlockButton() { this.btn.classList.remove('disabled'); this.btn.innerText = 'Поиск'; } } class Maps { constructor() { /** * @type {ymaps.Map} */ this.map = null; ymaps.ready(this.onInit); this.filter = null; this.places = {}; } onInit = () => { this.map = new ymaps.Map("mapContainer", { center: [59.94, 30.32], zoom: 11 }); this.map.controls.remove('searchControl'); } addPoint({geo, offersRef, hint, pharmacyName, pharmacyAddress, pharmacyPhone}) { let mark = new ymaps.Placemark(geo, { hintContent: hint, }, { preset: 'islands#dotIcon', openEmptyBalloon: true, iconColor: '#3caa3c' }); mark.events.add('balloonopen', e => { let lines = offersRef.map(offer => { return `${offer.name} (${offer.price} руб.)` }); let html = `${pharmacyName}
`; html += `${pharmacyAddress}
`; html += `тел: ${pharmacyPhone}

`; html += lines.join('
'); mark.properties.set('balloonContent', html); }); this.map.geoObjects.add(mark); return mark; } removeAllPoints() { this.map.geoObjects.removeAll(); } addOffer(offer) { // console.log('[addOffer]', offer); let hash = offer.pharmacy.hash; if (hash in this.places) this.places[hash].offers.push(offer); else this.places[hash] = {offers: [offer]}; if (!this.places[hash].mark && this.isAllowed(offer.name)) this.showPlaceOnMap(hash, offer); } showPlaceOnMap(hash, offer) { this.places[hash].mark = this.addPoint({ geo: offer.pharmacy.geo, hint: offer.pharmacy.name, pharmacyName: offer.pharmacy.name, pharmacyAddress: offer.pharmacy.address, pharmacyPhone: offer.pharmacy.phone, offersRef: this.places[hash].offers }); } hidePlaceFromMap(hash) { if (this.places[hash].mark) { this.map.geoObjects.remove(this.places[hash].mark); delete this.places[hash].mark; } } setFilter(filter) { if (!filter) filter = null; this.filter = filter; for (let hash in this.places) { if (!this.places.hasOwnProperty(hash)) continue; let pl = this.places[hash]; let ok = pl.offers.filter(o => this.isAllowed(o.name)) if (pl.mark && !ok.length) this.hidePlaceFromMap(hash); else if (!pl.mark && ok.length) this.showPlaceOnMap(hash, ok[0]); } } isAllowed(productName) { return this.filter === null || productName.indexOf(this.filter) !== -1; } } let globalMaps = null; let globalSearch = null; window.addEventListener('DOMContentLoaded', function() { globalSearch = new Search(); globalMaps = new Maps(); });