(function () { window.initStagSuggest = function () { let suggestionsOuter = null; const debounce = (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }; var lastTerm = ''; var returnedFunction = debounce(function (elem) { let term = elem.val(); let ep = $(elem).attr('stag-suggest-ep'), extra = $(elem).attr('stag-suggest-extra'); if(!!ep) { // remote lookup if (!!term && term.length >= 2) { $.get(ep + '?term=' + $.trim(term) + (!!extra ? '&' + extra : ''), function (_data) { /* expected return format: { success: true, data: [ { x: ..., y: ..., text: ... // "text" key is "mandatory" }, { x: ..., y: ..., text: ... // "text" key is "mandatory" }, ... ] } */ suggestionsOuter.empty(); if(!hasResponseError(_data) && _data.data && _data.data.length) { for (let i = 0; i < _data.data.length; i++) { let item = $(''); for(let x in _data.data[i]) { if(_data.data[i].hasOwnProperty(x) && x !== 'text') { item.attr('data-' + x, _data.data[i][x]); } } item.data('suggest-data', _data.data[i]); item.html(_data.data[i].text); if(_data.data[i].sub_text) { item.append($('') .addClass('ml-1 text-sm text-secondary') .append(' (') .append(_data.data[i].sub_text) .append(')') ); } if(_data.data[i].text2) { item.append($('
') .addClass('text-sm text-secondary') .html(_data.data[i].text2) ); } if(_data.data[i].tooltip) { item.attr('title', _data.data[i].tooltip); } if(i === 0) { item.addClass('active'); } suggestionsOuter.append(item); } } else { suggestionsOuter.html('No matches!'); } suggestionsOuter.removeClass('d-none'); }, 'json'); lastTerm = term; } else { suggestionsOuter.addClass('d-none'); } } else { // local lookup let optionList = $(elem).next().next('.data-option-list'); if(optionList.length) { let matches = []; optionList.find('>div').each(function() { if(!term || $(this).text().toLowerCase().indexOf(term.toLowerCase()) !== -1) { matches.push($(this).text()); } }); suggestionsOuter.empty(); if(matches.length) { for (let i = 0; i < matches.length; i++) { let item = $(''); item.data('suggest-data', {}); item.html(matches[i]); suggestionsOuter.append(item); } } else { suggestionsOuter.html('No matches!'); } suggestionsOuter.removeClass('d-none'); } } }, 250); function handleKeydown(elem, e) { let term = $.trim(elem.val()); let activeItem = suggestionsOuter.find('.suggest-item.active'); switch (e.which) { case 27: if(suggestionsOuter.is(':visible')) { suggestionsOuter.addClass('d-none'); markEventAsConsumed(e); return false; } break; case 38: if(suggestionsOuter.is(':visible')) { if (activeItem.prev().length) { activeItem.prev() .addClass('active') .siblings().removeClass('active'); activeItem = suggestionsOuter.find('.suggest-item.active'); if (activeItem.length) { activeItem[0].scrollIntoView(); } } return false; } break; case 40: if(suggestionsOuter.is(':visible')) { if (activeItem.next().length) { activeItem.next() .addClass('active') .siblings().removeClass('active'); activeItem = suggestionsOuter.find('.suggest-item.active'); if (activeItem.length) { activeItem[0].scrollIntoView(); } } return false; } break; case 13: if(suggestionsOuter.is(':visible')) { if (activeItem.length) { activeItem.first().trigger('mousedown'); } return false; } break; default: if (!!term) { suggestionsOuter .html('Searching...') .removeClass('d-none'); returnedFunction(elem); } else { suggestionsOuter.addClass('d-none'); } break; } } function handleKeypress(elem, e) { var term = $.trim(elem.val()); if (!!term || !$(elem).is('[stag-suggest-ep]')) { suggestionsOuter .html('Searching...') .removeClass('d-none'); returnedFunction(elem); } else { suggestionsOuter.addClass('d-none'); } } $('[stag-suggest]:not([stag-suggest-initialized])').each(function () { let elem = $(this); elem.next('.stag-suggestions-container').remove(); $('
' + '
' + '
').insertAfter(elem); elem .off('focus.stag-suggest') .on('focus.stag-suggest', function (e) { if(!$(this).is('[stag-suggest-ep]')) { suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer'); return handleKeypress($(this), e); } }) .off('keydown.stag-suggest') .on('keydown.stag-suggest', function (e) { suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer'); return handleKeydown($(this), e); }) .off('paste.stag-suggest') .on('paste.stag-suggest', function (e) { window.setTimeout(() => { suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer'); return handleKeypress($(this), e); }, 100); }) .off('keypress.stag-suggest') .on('keypress.stag-suggest', function (e) { suggestionsOuter = $(this).next('.stag-suggestions-container').find('>.suggestions-outer'); return handleKeypress($(this), e); }); $(this).attr('stag-suggest-initialized', 1); }); // on auto-suggest selection $(document).off('mousedown.stag-suggest', '.suggest-item.stag-suggest'); $(document).on('mousedown.stag-suggest', '.suggest-item.stag-suggest', function () { $('.suggestions-outer.stag-suggestions').addClass('d-none'); let data = $(this).data('suggest-data'), label = $.trim($(this).text()); // set value let input = $(this).closest('.position-relative').prev('[stag-suggest]'); // if input is [stag-suggest-text-only] - use only the text bit in label if(input.is('[stag-suggest-text-only]')) { label = data.text; } input.val(label); input.data('suggest-data', data); let scope = input.attr('stag-suggest-scope'); if(!scope) scope = 'form'; scope = $(scope); for(let x in data) { if(data.hasOwnProperty(x)) { input.attr('data-' + x, data[x]); // auto-populate if there's a field matching data-name="x" in the scope if(scope.find('[data-name="' + x + '"]').length) { scope.find('[data-name="' + x + '"]').val(data[x]).trigger('change'); } } } input.trigger('input'); input.trigger('change'); input.trigger('stag-suggest-selected', [input, data]); return false; }); // outside click $(document) .off('mousedown.stag-suggest-outer-click') .on('mousedown.stag-suggest-outer-click', function (_e) { let elem = $(_e.target); // if mousedown is on the input whose stag-suggest is on display, ignore if(elem.is('[stag-suggest]') && elem.next('.stag-suggestions-container').find('>.suggestions-outer').is(':visible')) { return false; } if(!elem.is('.stag-suggestions-container') && !elem.closest('.stag-suggestions-container').length) { if($('.stag-suggestions-container .suggestions-outer:not(.d-none)').length) { $('.stag-suggestions-container .suggestions-outer').addClass('d-none'); return false; } } }); } addMCInitializer('stag-suggest', window.initStagSuggest); })();