var App = App || {}; App.Controls = App.Controls || {}; App.Controls.AutoCompleteInput = function($input) { var _ = App.DI.get('_'); var jQuery = App.DI.get('jQuery'); var tagList = App.DI.get('tagList'); var KEY_RETURN = 13; var KEY_ESCAPE = 27; var KEY_UP = 38; var KEY_DOWN = 40; var options = { caseSensitive: false, source: null, maxResults: 15, minLengthToArbitrarySearch: 3, onApply: null, additionalFilter: null, }; var showTimeout = null; var cachedSource = null; var results = []; var activeResult = -1; if ($input.length === 0) { throw new Error('Input element was not found'); } if ($input.length > 1) { throw new Error('Cannot add autocompletion to more than one element at once'); } if ($input.attr('data-autocomplete')) { throw new Error('Autocompletion was already added for this element'); } $input.attr('data-autocomplete', true); $input.attr('autocomplete', 'off'); var $div = jQuery('<div>'); var $list = jQuery('<ul>'); $div.addClass('autocomplete'); $div.append($list); jQuery(document.body).append($div); function getSource() { if (cachedSource) { return cachedSource; } else { var source = tagList.getTags(); source = _.sortBy(source, function(a) { return -a.usages; }); source = _.filter(source, function(a) { return a.usages > 0; }); source = _.map(source, function(a) { return { tag: a.name, caption: a.name + ' (' + a.usages + ')', }; }); cachedSource = source; return source; } } $input.bind('keydown', function(e) { if (isShown() && e.which === KEY_ESCAPE) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); hide(); } else if (isShown() && e.which === KEY_DOWN) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); selectNext(); } else if (isShown() && e.which === KEY_UP) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); selectPrevious(); } else if (isShown() && e.which === KEY_RETURN && activeResult >= 0) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); applyAutocomplete(); hide(); } else { window.clearTimeout(showTimeout); showTimeout = window.setTimeout(showOrHide, 250); } }); $input.blur(function(e) { window.clearTimeout(showTimeout); window.setTimeout(function() { hide(); }, 50); }); function getSelectionStart(){ var input = $input.get(0); if (!input) { return; } if ('selectionStart' in input) { return input.selectionStart; } else if (document.selection) { input.focus(); var sel = document.selection.createRange(); var selLen = document.selection.createRange().text.length; sel.moveStart('character', -input.value.length); return sel.text.length - selLen; } else { return 0; } } function getTextToFind() { var val = $input.val(); var start = getSelectionStart(); return val.substring(0, start).replace(/.*\s/, ''); } function showOrHide() { var textToFind = getTextToFind(); if (textToFind.length === 0) { hide(); } else { updateResults(textToFind); refreshList(); } } function isShown() { return $div.is(':visible'); } function hide() { $div.hide(); } function selectPrevious() { select(activeResult === -1 ? results.length - 1 : activeResult - 1); } function selectNext() { select(activeResult === -1 ? 0 : activeResult + 1); } function select(newActiveResult) { if (newActiveResult >= 0 && newActiveResult < results.length) { activeResult = newActiveResult; refreshActiveResult(); } else { activeResult = - 1; refreshActiveResult(); } } function getResultsFilter(textToFind) { if (textToFind.length < options.minLengthToArbitrarySearch) { return options.caseSensitive ? function(resultItem) { return resultItem.tag.indexOf(textToFind) === 0; } : function(resultItem) { return resultItem.tag.toLowerCase().indexOf(textToFind.toLowerCase()) === 0; }; } else { return options.caseSensitive ? function(resultItem) { return resultItem.tag.indexOf(textToFind) >= 0; } : function(resultItem) { return resultItem.tag.toLowerCase().indexOf(textToFind.toLowerCase()) >= 0; }; } } function updateResults(textToFind) { var oldResults = results.slice(); var source = getSource(); var filter = getResultsFilter(textToFind); results = _.filter(source, filter); if (options.additionalFilter) { results = options.additionalFilter(results); } results = results.slice(0, options.maxResults); if (!_.isEqual(oldResults, results)) { activeResult = -1; } } function applyAutocomplete() { if (options.onApply) { options.onApply(results[activeResult].tag); } else { var val = $input.val(); var start = getSelectionStart(); var prefix = ''; var suffix = val.substring(start); var middle = val.substring(0, start); var index = middle.lastIndexOf(' '); if (index !== -1) { prefix = val.substring(0, index + 1); middle = val.substring(index + 1); } $input.val(prefix + results[activeResult].tag + ' ' + suffix.trimLeft()); $input.focus(); } } function refreshList() { if (results.length === 0) { hide(); return; } $list.empty(); _.each(results, function(resultItem, resultIndex) { var $listItem = jQuery('<li/>'); $listItem.text(resultItem.caption); $listItem.attr('data-key', resultItem.tag); $listItem.hover(function(e) { e.preventDefault(); activeResult = resultIndex; refreshActiveResult(); }); $listItem.mousedown(function(e) { e.preventDefault(); activeResult = resultIndex; applyAutocomplete(); hide(); }); $list.append($listItem); }); refreshActiveResult(); $div.css({ left: ($input.offset().left) + 'px', top: ($input.offset().top + $input.outerHeight() - 2) + 'px', }); $div.show(); } function refreshActiveResult() { $list.find('li.active').removeClass('active'); if (activeResult >= 0) { $list.find('li').eq(activeResult).addClass('active'); } } return options; };