Improved perforamnce of global search (reduced throttle of search queries, ony start queries if query has changed).

This commit is contained in:
Martin Edenhofer 2020-01-22 09:25:54 +01:00
parent ab6634bcfe
commit dd54f8a787
3 changed files with 134 additions and 64 deletions

View file

@ -17,6 +17,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
'dblclick form.search-holder .icon-magnifier': 'openExtendedSearch' 'dblclick form.search-holder .icon-magnifier': 'openExtendedSearch'
'focus #global-search': 'searchFocus' 'focus #global-search': 'searchFocus'
'blur #global-search': 'searchBlur' 'blur #global-search': 'searchBlur'
'paste #global-search': 'searchPaste'
'keyup #global-search': 'listNavigate' 'keyup #global-search': 'listNavigate'
'click .js-global-search-result': 'emptyAndCloseDelayed' 'click .js-global-search-result': 'emptyAndCloseDelayed'
'click .js-details-link': 'openExtendedSearch' 'click .js-details-link': 'openExtendedSearch'
@ -27,8 +28,6 @@ class App.Navigation extends App.ControllerWidgetPermanent
super super
@render() @render()
@throttledSearch = _.throttle @search, 200
@globalSearch = new App.GlobalSearch( @globalSearch = new App.GlobalSearch(
render: @renderResult render: @renderResult
) )
@ -171,20 +170,14 @@ class App.Navigation extends App.ControllerWidgetPermanent
type: 'personal' type: 'personal'
) )
renderResult: (result = [], noChange) => renderResult: (result = []) =>
if noChange
return
@removePopovers() @removePopovers()
# remove result if not result exists # remove result if not result exists
if _.isEmpty(result) if _.isEmpty(result)
@searchContainer.removeClass('loading').addClass('no-match')
@searchResult.html(App.view('navigation/no_result')()) @searchResult.html(App.view('navigation/no_result')())
return return
@searchContainer.removeClass('no-match loading')
# build markup # build markup
html = App.view('navigation/result')( html = App.view('navigation/result')(
result: result result: result
@ -215,11 +208,21 @@ class App.Navigation extends App.ControllerWidgetPermanent
searchFocus: (e) => searchFocus: (e) =>
@clearDelay('emptyAndCloseDelayed') @clearDelay('emptyAndCloseDelayed')
@throttledSearch() @query = undefined
@search(10)
App.PopoverProvidable.anyPopoversDestroy() App.PopoverProvidable.anyPopoversDestroy()
@searchContainer.addClass('focused') @searchContainer.addClass('focused')
@selectAll(e) @selectAll(e)
searchPaste: (e) =>
update = =>
@clearDelay('emptyAndCloseDelayed')
@query = undefined
@search(10)
App.PopoverProvidable.anyPopoversDestroy()
@searchContainer.addClass('focused')
@delay(update, 10, 'searchFocus')
searchBlur: (e) => searchBlur: (e) =>
# delay to be able to "click/execute" x if query is '' # delay to be able to "click/execute" x if query is ''
@ -249,7 +252,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
return return
# on other keys, show result # on other keys, show result
@throttledSearch() @search(0)
nudge: (e, position) => nudge: (e, position) =>
@ -307,22 +310,41 @@ class App.Navigation extends App.ControllerWidgetPermanent
@globalSearch.close() @globalSearch.close()
@delayedRemoveAnyPopover() @delayedRemoveAnyPopover()
search: => search: (delay) =>
query = @searchInput.val().trim() query = @searchInput.val().trim()
@searchContainer.toggleClass('filled', !!query) @searchContainer.toggleClass('filled', !!query)
# if we started a new search and already typed something in return if @query is query
if query != '' and @query == ''
@searchContainer.addClass('open no-match loading')
@query = query @query = query
if @query == '' if delay is 0
@searchContainer.removeClass('open loading') 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 return
@globalSearch.search(
delay: delay
query: @query
callbackLongerAsExpected: =>
@searchContainer.removeClass('open')
callbackNoMatch: =>
@searchContainer.addClass('no-match')
@searchContainer.addClass('open') @searchContainer.addClass('open')
@globalSearch.search(query: @query) callbackMatch: =>
@searchContainer.removeClass('no-match')
@searchContainer.addClass('open')
callbackStop: =>
@searchContainer.removeClass('loading')
callbackStart: =>
@searchContainer.addClass('loading')
)
filterNavbar: (values, parent = null) -> filterNavbar: (values, parent = null) ->
return _.filter values, (item) => return _.filter values, (item) =>

View file

@ -24,8 +24,6 @@ class App.Search extends App.Controller
# update taskbar with new meta data # update taskbar with new meta data
App.TaskManager.touch(@taskKey) App.TaskManager.touch(@taskKey)
@throttledSearch = _.throttle @search, 200
@globalSearch = new App.GlobalSearch( @globalSearch = new App.GlobalSearch(
render: @renderResult render: @renderResult
limit: 50 limit: 50
@ -59,9 +57,11 @@ class App.Search extends App.Controller
@table.show() @table.show()
@navupdate(url: '#search', type: 'menu') @navupdate(url: '#search', type: 'menu')
return if _.isEmpty(params.query) return if _.isEmpty(params.query)
@$('.js-search').val(params.query).trigger('change') @$('.js-search').val(params.query).trigger('change')
return if @shown return if @shown
@throttledSearch(true)
@search(1000, true)
hide: -> hide: ->
@shown = false @shown = false
@ -102,7 +102,7 @@ class App.Search extends App.Controller
) )
if @query if @query
@throttledSearch(true) @search(500, true)
listNavigate: (e) => listNavigate: (e) =>
if e.keyCode is 27 # close on esc if e.keyCode is 27 # close on esc
@ -110,7 +110,7 @@ class App.Search extends App.Controller
return return
# on other keys, show result # on other keys, show result
@throttledSearch(200) @search(0)
empty: => empty: =>
@searchInput.val('') @searchInput.val('')
@ -120,7 +120,7 @@ class App.Search extends App.Controller
@delayedRemoveAnyPopover() @delayedRemoveAnyPopover()
search: (force = false) => search: (delay, force = false) =>
query = @searchInput.val().trim() query = @searchInput.val().trim()
if !force if !force
return if !query return if !query
@ -128,7 +128,17 @@ class App.Search extends App.Controller
@query = query @query = query
@updateTask() @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 = []) => renderResult: (result = []) =>
@result = result @result = result

View file

@ -1,4 +1,5 @@
class App.GlobalSearch extends App.Controller class App.GlobalSearch extends App.Controller
ajaxCount: 0
constructor: -> constructor: ->
super super
@searchResultCache = {} @searchResultCache = {}
@ -12,10 +13,25 @@ class App.GlobalSearch extends App.Controller
# use cache for search result # use cache for search result
currentTime = new Date currentTime = new Date
if @searchResultCache[query] && @searchResultCache[query].time > currentTime.setSeconds(currentTime.getSeconds() - 20) 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 return
App.Ajax.request( delayCallback = =>
@ajaxStart(params)
delayCallback = ->
if params.callbackLongerAsExpected
params.callbackLongerAsExpected()
@delay(delayCallback, 10000, 'global-search-ajax-longer-as-expected')
@ajaxRequestId = App.Ajax.request(
id: @ajaxId id: @ajaxId
type: 'GET' type: 'GET'
url: "#{@apiPath}/search" url: "#{@apiPath}/search"
@ -24,6 +40,7 @@ class App.GlobalSearch extends App.Controller
limit: @limit || 10 limit: @limit || 10
processData: true processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>
@clearDelay('global-search-ajax-longer-as-expected')
App.Collection.loadAssets(data.assets) App.Collection.loadAssets(data.assets)
result = {} result = {}
for item in data.result for item in data.result
@ -38,17 +55,38 @@ class App.GlobalSearch extends App.Controller
App.Log.error('_globalSearchSingleton', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()") App.Log.error('_globalSearchSingleton', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()")
else 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) @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()
ajaxStop: (params) =>
@ajaxCount--
if @ajaxCount == 0 && params.callbackStop
params.callbackStop()
renderTry: (result, query, params) =>
if query
if _.isEmpty(result)
if params.callbackNoMatch
params.callbackNoMatch()
else
if params.callbackMatch
params.callbackMatch()
# if result hasn't changed, do not rerender # if result hasn't changed, do not rerender
if @lastQuery is query && @searchResultCache[query] if @lastQuery is query && @searchResultCache[query]
diff = difference(@searchResultCache[query].result, result) diff = difference(@searchResultCache[query].result, result)
if _.isEmpty(diff) if _.isEmpty(diff)
@render(result, true)
return return
@lastQuery = query @lastQuery = query