var SmartAnnotation = (function() { 'use strict'; // utilities var Util = (function() { // helper method that binds show/hidden action function showHideBinding() { $.each(['show', 'hide'], function (i, ev) { var el = $.fn[ev]; $.fn[ev] = function () { this.trigger(ev); return el.apply(this, arguments); }; }); } var publicApi = { showHideBinding: showHideBinding }; return publicApi; })(); // stop the user annotation popover on click propagation function atwhoStopPropagation(element) { $(element).on('click', function(e) { e.stopPropagation(); e.preventDefault(); }); } function setAtWho(field) { var FilterTypeEnum = Object.freeze({ USER: {tag: "users", dataUrl: $(document.body).attr('data-atwho-users-url')}, TASK: {tag: "tsk", dataUrl: $(document.body).attr('data-atwho-task-url')}, PROJECT: {tag: "prj", dataUrl: $(document.body).attr('data-atwho-project-url')}, EXPERIMENT: {tag: "exp", dataUrl: $(document.body).attr('data-atwho-experiment-url')}, REPOSITORY: {tag: "rep", dataUrl: $(document.body).attr('data-atwho-rep-items-url')}, MENU: {tag: "menu", dataUrl: $(document.body).attr('data-atwho-menu-items')} }); var prevAt, // Default selected filter when using '#' DEFAULT_SEARCH_FILTER = FilterTypeEnum.REPOSITORY, atWhoUpdating = false; var defaultRepId; // helper methods for AtWho callback function _templateEval(_tpl, map) { var res; try { if (map.no_results) { res = noResultsTemplate(); } else { res = generateTemplate(map); } } catch (_error) { res = ''; } return res; } function _matchHighlighter(li, query, filterType) { var $li, re; function highlight(el, sel, re) { var prevVal, newVal; prevVal = el.find(sel).html(); newVal = prevVal.replace(re, '$&'); el.find(sel).html(newVal); } if (!query || $(li).data('no-results')) { return li; } $li = $(li); re = new RegExp(query, 'gi'); // search_filter is not passed for the user if(filterType) { highlight($li, '[data-val=name]', re); } else { highlight($li, '[data-val=full-name]', re); highlight($li, '[data-val=email]', re); } return $li[0].outerHTML } function _generateInputTag(value, li) { var res = ''; res += '[#' + li.attr('data-name'); res += '~' + li.attr('data-type'); res += '~' + li.attr('data-id') + ']'; return res; } // initialise dropdown dismiss button function initDismissButton($currentAtWho) { $currentAtWho.find('.dismiss').off('click') .on('click', function() { $(field).atwho('destroy'); init(); }); } // Initialize or update dropdown header buttons function updateHeaderButtons(query, filterType) { var $currentAtWho = $('.atwho-view[style]'); initDismissButton($currentAtWho); // Update the selected filter button when changing smart annotation type $currentAtWho.find('[data-filter]') .removeClass('btn-primary') .addClass('btn-default'); if(filterType.tag === 'rep') { $currentAtWho.find('[data-rep-id="' + filterType.repository_id + '"]') .removeClass('btn-default') .addClass('btn-primary'); } else { $currentAtWho.find('[data-filter="' + filterType.tag + '"]') .removeClass('btn-default') .addClass('btn-primary'); } // Update the selected filter button when clicking on one of them $currentAtWho.find('[data-filter]').off() .on('click', function(e) { if($(this).hasClass('btn-primary')) { return; } var $selectedBtn = $(this); var $prevBtn = $selectedBtn.closest('.atwho-header-res') .children('.btn-primary'); $selectedBtn.removeClass('btn-default').addClass('btn-primary'); $prevBtn.removeClass('btn-primary').addClass('btn-default'); // Updates query and dropdown elements; focuses input $(field).click().focus(); }); } // Generates suggestion dropdown filter function generateFilterMenu(active, res_data) { var rep_buttons = ''; $.ajax({ async: false, dataType: 'json', url: $(document.body).attr('data-atwho-repositories-url'), success: function(data) { $.each(data['repositories'], function(id, name) { if(defaultRepId === undefined){ defaultRepId = id; } rep_buttons += ''; }); } }); var header = '
' + '' + '' + '' + rep_buttons + '
' + '' + '
' + '
' + '
' + '<%= I18n.t("atwho.users.navigate_1") %> ' + '<%= I18n.t("atwho.users.navigate_2") %>' + '
' + '
<%= I18n.t("atwho.users.confirm_1") %> ' + '<%= I18n.t("atwho.users.confirm_2") %>' + '
' + '
' + '<%= I18n.t("atwho.users.dismiss_1") %> ' + '<%= I18n.t("atwho.users.dismiss_2") %>' + '
' + '
' + '
'; return header; } function noResultsTemplate() { var res = '
'; res += '<%= I18n.t("atwho.no_results") %>'; res += '
'; return res; } // Generates resources list items function generateTemplate(map) { var res = ''; res += '
  • '; switch(map.type) { case 'tsk': res += '' + map.type + ''; break; case 'prj': res += '' + map.type + ''; break; case 'exp': res += '' + map.type + ''; break; case 'rep_item': res += '' + map.repository_tag + ''; break; } res += ' '; res += ''; res += truncateLongString(map.name, <%= Constants::NAME_TRUNCATION_LENGTH %>); res += ''; if(map.archived) { res += '<%= I18n.t("atwho.res.archived") %>'; } else { res += ''; } res += ' '; switch (map.type) { case 'tsk': res += '< ' + map.experimentName + ' < ' + map.projectName + ''; break; case 'exp': res += '< ' + map.projectName + ''; break; case 'sam': res += '' + map.description + ''; break; } res += '
  • '; return res; } /** * Hackish wrapper function to make AtWho work when switching between * multiple AtWho instances (e.g. from # to task#). * * Prevents second execution of AtWho update callback, triggered when user * switches to different AtWho instance (e.g. from # to task#), which causes * both of them to be called. In such case, AtWhO modal needs to be * rerendered. */ function atWhoSwitchHack(filterTypeTag, remoteFilterCb) { if(atWhoUpdating || (!$(field).length && _.isUndefined(filterTypeTag))) { setTimeout(function() { $(field).click(); }, 100); return; } atWhoUpdating = true; setTimeout(function() { remoteFilterCb(); atWhoUpdating = false; }, 100); } function atWhoSettings(at, defaultFilterType) { return { at: at, callbacks: { remoteFilter: function(query, callback) { var $currentAtWho = $('.atwho-view[style]'); var filterTypeTag = $currentAtWho .find('.btn-primary') .data('filter'); atWhoSwitchHack(filterTypeTag, function() { var filterType; if (_.isUndefined(filterTypeTag)) { // Switched smart annotation type (i.e. changed input) filterType = defaultFilterType; } else { // Switched filtering type (i.e. different filter button // pressed; works also for specific annotation types, e.g. // task#, and coverts to the correct annotation type on confirm) $.each(FilterTypeEnum, function(k, v) { if (v.tag == filterTypeTag) { filterType = FilterTypeEnum[k]; return false; } }); } if (prevAt != at) { // Switching smart annotation type (i.e. chaned input) prevAt = at; filterType = defaultFilterType; // Hide current AtWho $currentAtWho.removeAttr("style"); } var params = { query: query }; if(filterType.tag === 'rep') { params.repository_id = $currentAtWho .find('.btn-primary') .data('rep-id'); if(params.repository_id === undefined) { params.repository_id = defaultRepId; } filterType.repository_id = params.repository_id; } $.getJSON( filterType.dataUrl, params, function(data) { // Updates dropdown if (data.res.length < 1) { callback([{no_results: 1}]); } else { callback(data.res); } updateHeaderButtons(query, filterType); } ); }); }, sorter: function(query, items, _searchKey) { // Sorting is already done on server-side return items; }, tplEval: function(_tpl, map) { return _templateEval(_tpl, map); }, highlighter: function(li, query) { return _matchHighlighter(li, query, true); }, beforeInsert: function(value, li) { return _generateInputTag(value, li); } }, headerTpl: generateFilterMenu(defaultFilterType), limit: <%= Constants::ATWHO_SEARCH_LIMIT %>, startWithSpace: true, acceptSpaceBar: true, displayTimeout: 120000 } } function init() { $(field) .atwho({ at: '@', callbacks: { remoteFilter: function(query, callback) { $.getJSON( FilterTypeEnum.USER.dataUrl, {query: query}, function(data) { if (data.users.length < 1) { callback([{no_results: 1}]); } else { callback(data.users); } initDismissButton($('.atwho-view[style]')); } ); }, sorter: function(query, items, _searchKey) { // Sorting is already done on server-side return items; }, tplEval: function(_tpl, map) { var res; try { if (map.no_results) { res = noResultsTemplate(); } else { res = ''; res += '
  • '; res += ''; res += ''; res += map.full_name; res += ''; res += ''; res += ' '; res += '·'; res += ' '; res += ''; res += map.email; res += ''; res += ''; res += '
  • '; } } catch (_error) { res = ''; } return res; }, highlighter: function(li, query) { return _matchHighlighter(li, query); }, beforeInsert: function(value, li) { var res = ''; res += '[@' + li.attr('data-full-name'); res += '~' + li.attr('data-id') + ']'; return res; } }, headerTpl: '
    ' + '
    <%= I18n.t("atwho.users.title") %>
    ' + '
    ' + '
    ' + '<%= I18n.t("atwho.users.navigate_1") %> ' + '<%= I18n.t("atwho.users.navigate_2") %>' + '
    ' + '
    ' + '<%= I18n.t("atwho.users.confirm_1") %> ' + '<%= I18n.t("atwho.users.confirm_2") %>' + '
    ' + '
    ' + '<%= I18n.t("atwho.users.dismiss_1") %> ' + '<%= I18n.t("atwho.users.dismiss_2") %>' + '
    ' + '
    ' + '
    ' + '' + '
    ' + '
    ', limit: <%= Constants::ATWHO_SEARCH_LIMIT %>, startsWithSpace: true, acceptSpaceBar: true, displayTimeout: 120000 }) .atwho(atWhoSettings('#', DEFAULT_SEARCH_FILTER)) // .atwho(atWhoSettings('task#', FilterTypeEnum.TASK)) Waiting for better times // .atwho(atWhoSettings('project#', FilterTypeEnum.PROJECT)) // .atwho(atWhoSettings('experiment#', FilterTypeEnum.EXPERIMENT)) // .atwho(atWhoSettings('sample#', FilterTypeEnum.REPOSITORY)); } return { init: init }; } // Closes the atwho popup * needed in repositories to close the popup // if nothing is selected and the user leaves the form * function closePopup() { $('.atwho-header-res').find('.glyphicon-remove').click(); } function initialize(field) { var atWho = new setAtWho(field); atWho.init(); } var publicApi = Object.freeze({ init: initialize, preventPropagation: atwhoStopPropagation, closePopup: closePopup }); return publicApi; })(); // initialize the smart annotations (function initSmartAnnotation() { $(document).on('focus', '[data-atwho-edit]', function() { if(_.isUndefined($(this).data('atwho'))) { SmartAnnotation.init(this); } }); })();