diff --git a/app/assets/javascripts/app/controllers/navigation.coffee b/app/assets/javascripts/app/controllers/navigation.coffee index b311edbf5..8a01ada8e 100644 --- a/app/assets/javascripts/app/controllers/navigation.coffee +++ b/app/assets/javascripts/app/controllers/navigation.coffee @@ -17,6 +17,7 @@ class App.Navigation extends App.ControllerWidgetPermanent 'dblclick form.search-holder .icon-magnifier': 'openExtendedSearch' 'focus #global-search': 'searchFocus' 'blur #global-search': 'searchBlur' + 'paste #global-search': 'searchPaste' 'keyup #global-search': 'listNavigate' 'click .js-global-search-result': 'emptyAndCloseDelayed' 'click .js-details-link': 'openExtendedSearch' @@ -27,8 +28,6 @@ class App.Navigation extends App.ControllerWidgetPermanent super @render() - @throttledSearch = _.throttle @search, 200 - @globalSearch = new App.GlobalSearch( render: @renderResult ) @@ -171,20 +170,14 @@ class App.Navigation extends App.ControllerWidgetPermanent type: 'personal' ) - renderResult: (result = [], noChange) => - if noChange - return - + renderResult: (result = []) => @removePopovers() # remove result if not result exists if _.isEmpty(result) - @searchContainer.removeClass('loading').addClass('no-match') @searchResult.html(App.view('navigation/no_result')()) return - @searchContainer.removeClass('no-match loading') - # build markup html = App.view('navigation/result')( result: result @@ -215,11 +208,21 @@ class App.Navigation extends App.ControllerWidgetPermanent searchFocus: (e) => @clearDelay('emptyAndCloseDelayed') - @throttledSearch() + @query = undefined + @search(10) App.PopoverProvidable.anyPopoversDestroy() @searchContainer.addClass('focused') @selectAll(e) + searchPaste: (e) => + update = => + @clearDelay('emptyAndCloseDelayed') + @query = undefined + @search(10) + App.PopoverProvidable.anyPopoversDestroy() + @searchContainer.addClass('focused') + @delay(update, 10, 'searchFocus') + searchBlur: (e) => # delay to be able to "click/execute" x if query is '' @@ -249,7 +252,7 @@ class App.Navigation extends App.ControllerWidgetPermanent return # on other keys, show result - @throttledSearch() + @search(0) nudge: (e, position) => @@ -307,22 +310,41 @@ class App.Navigation extends App.ControllerWidgetPermanent @globalSearch.close() @delayedRemoveAnyPopover() - search: => + search: (delay) => query = @searchInput.val().trim() @searchContainer.toggleClass('filled', !!query) - # if we started a new search and already typed something in - if query != '' and @query == '' - @searchContainer.addClass('open no-match loading') - + return if @query is query @query = query - if @query == '' - @searchContainer.removeClass('open loading') + if delay is 0 + delay = 500 + if query.length > 2 + delay = 350 + else if query.length > 4 + delay = 200 + + # if we started a new search and already typed something in + if query is '' + @searchContainer.removeClass('open') return - @searchContainer.addClass('open') - @globalSearch.search(query: @query) + @globalSearch.search( + delay: delay + query: @query + callbackLongerAsExpected: => + @searchContainer.removeClass('open') + callbackNoMatch: => + @searchContainer.addClass('no-match') + @searchContainer.addClass('open') + callbackMatch: => + @searchContainer.removeClass('no-match') + @searchContainer.addClass('open') + callbackStop: => + @searchContainer.removeClass('loading') + callbackStart: => + @searchContainer.addClass('loading') + ) filterNavbar: (values, parent = null) -> return _.filter values, (item) => diff --git a/app/assets/javascripts/app/controllers/search.coffee b/app/assets/javascripts/app/controllers/search.coffee index f396805d1..33751184f 100644 --- a/app/assets/javascripts/app/controllers/search.coffee +++ b/app/assets/javascripts/app/controllers/search.coffee @@ -24,8 +24,6 @@ class App.Search extends App.Controller # update taskbar with new meta data App.TaskManager.touch(@taskKey) - @throttledSearch = _.throttle @search, 200 - @globalSearch = new App.GlobalSearch( render: @renderResult limit: 50 @@ -59,9 +57,11 @@ class App.Search extends App.Controller @table.show() @navupdate(url: '#search', type: 'menu') return if _.isEmpty(params.query) + @$('.js-search').val(params.query).trigger('change') return if @shown - @throttledSearch(true) + + @search(1000, true) hide: -> @shown = false @@ -102,7 +102,7 @@ class App.Search extends App.Controller ) if @query - @throttledSearch(true) + @search(500, true) listNavigate: (e) => if e.keyCode is 27 # close on esc @@ -110,7 +110,7 @@ class App.Search extends App.Controller return # on other keys, show result - @throttledSearch(200) + @search(0) empty: => @searchInput.val('') @@ -120,7 +120,7 @@ class App.Search extends App.Controller @delayedRemoveAnyPopover() - search: (force = false) => + search: (delay, force = false) => query = @searchInput.val().trim() if !force return if !query @@ -128,7 +128,17 @@ class App.Search extends App.Controller @query = query @updateTask() - @globalSearch.search(query: @query) + if delay is 0 + delay = 500 + if query.length > 2 + delay = 350 + else if query.length > 4 + delay = 200 + + @globalSearch.search( + delay: delay + query: @query + ) renderResult: (result = []) => @result = result diff --git a/app/assets/javascripts/app/lib/app_post/global_search.coffee b/app/assets/javascripts/app/lib/app_post/global_search.coffee index 1e115fdf1..ff8bf69dd 100644 --- a/app/assets/javascripts/app/lib/app_post/global_search.coffee +++ b/app/assets/javascripts/app/lib/app_post/global_search.coffee @@ -1,4 +1,5 @@ class App.GlobalSearch extends App.Controller + ajaxCount: 0 constructor: -> super @searchResultCache = {} @@ -12,51 +13,88 @@ class App.GlobalSearch extends App.Controller # use cache for search result currentTime = new Date if @searchResultCache[query] && @searchResultCache[query].time > currentTime.setSeconds(currentTime.getSeconds() - 20) - @renderTry(@searchResultCache[query].result, query) + if @ajaxRequestId + App.Ajax.abort(@ajaxRequestId) + @ajaxStart(params) + @renderTry(@searchResultCache[query].result, query, params) + delayCallback = => + @ajaxStop(params) + @delay(delayCallback, 700) return - App.Ajax.request( - id: @ajaxId - type: 'GET' - url: "#{@apiPath}/search" - data: - query: query - limit: @limit || 10 - processData: true - success: (data, status, xhr) => - App.Collection.loadAssets(data.assets) - result = {} - for item in data.result - if App[item.type] && App[item.type].find - if !result[item.type] - result[item.type] = [] - item_object = App[item.type].find(item.id) - if item_object.searchResultAttributes - item_object_search_attributes = item_object.searchResultAttributes() - result[item.type].push item_object_search_attributes + delayCallback = => + + @ajaxStart(params) + + delayCallback = -> + if params.callbackLongerAsExpected + params.callbackLongerAsExpected() + @delay(delayCallback, 10000, 'global-search-ajax-longer-as-expected') + + @ajaxRequestId = App.Ajax.request( + id: @ajaxId + type: 'GET' + url: "#{@apiPath}/search" + data: + query: query + limit: @limit || 10 + processData: true + success: (data, status, xhr) => + @clearDelay('global-search-ajax-longer-as-expected') + App.Collection.loadAssets(data.assets) + result = {} + for item in data.result + if App[item.type] && App[item.type].find + if !result[item.type] + result[item.type] = [] + item_object = App[item.type].find(item.id) + if item_object.searchResultAttributes + item_object_search_attributes = item_object.searchResultAttributes() + result[item.type].push item_object_search_attributes + else + App.Log.error('_globalSearchSingleton', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()") else - App.Log.error('_globalSearchSingleton', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()") - else - App.Log.error('_globalSearchSingleton', "No such model App.#{item.type}") + App.Log.error('_globalSearchSingleton', "No such model App.#{item.type}") + @ajaxStop(params) + @renderTry(result, query, params) + error: => + @clearDelay('global-search-ajax-longer-as-expected') + @ajaxStop(params) + ) + @delay(delayCallback, params.delay || 1, 'global-search-ajax') - @renderTry(result, query) - ) + ajaxStart: (params) => + @ajaxCount++ + if params.callbackStart + params.callbackStart() - renderTry: (result, query) => + ajaxStop: (params) => + @ajaxCount-- + if @ajaxCount == 0 && params.callbackStop + params.callbackStop() - # if result hasn't changed, do not rerender - if @lastQuery is query && @searchResultCache[query] - diff = difference(@searchResultCache[query].result, result) - if _.isEmpty(diff) - @render(result, true) - return + renderTry: (result, query, params) => - @lastQuery = query + if query + if _.isEmpty(result) + if params.callbackNoMatch + params.callbackNoMatch() + else + if params.callbackMatch + params.callbackMatch() - # cache search result - @searchResultCache[query] = - result: result - time: new Date + # if result hasn't changed, do not rerender + if @lastQuery is query && @searchResultCache[query] + diff = difference(@searchResultCache[query].result, result) + if _.isEmpty(diff) + return + + @lastQuery = query + + # cache search result + @searchResultCache[query] = + result: result + time: new Date @render(result)