Improved some small features for extended search.
This commit is contained in:
parent
613cc7ef20
commit
5ccde0c6f1
7 changed files with 113 additions and 105 deletions
|
@ -147,6 +147,8 @@ class App.Controller extends Spine.Controller
|
||||||
scrollToIfNeeded: (element, position = true) ->
|
scrollToIfNeeded: (element, position = true) ->
|
||||||
return if !element
|
return if !element
|
||||||
return if !element.get(0)
|
return if !element.get(0)
|
||||||
|
if position is true
|
||||||
|
return if element.visible(true)
|
||||||
element.get(0).scrollIntoView(position)
|
element.get(0).scrollIntoView(position)
|
||||||
|
|
||||||
shake: (element) ->
|
shake: (element) ->
|
||||||
|
|
|
@ -906,7 +906,7 @@ class App.ChannelEmailNotificationWizard extends App.WizardModal
|
||||||
processData: true
|
processData: true
|
||||||
success: (data, status, xhr) =>
|
success: (data, status, xhr) =>
|
||||||
if data.result is 'ok'
|
if data.result is 'ok'
|
||||||
@el.remove()
|
@el.modal('hide')
|
||||||
else
|
else
|
||||||
@showSlide('js-outbound')
|
@showSlide('js-outbound')
|
||||||
@showAlert('js-outbound', data.message_human || data.message)
|
@showAlert('js-outbound', data.message_human || data.message)
|
||||||
|
|
|
@ -3,19 +3,21 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'#global-search': 'searchInput'
|
'#global-search': 'searchInput'
|
||||||
'.js-global-search-result': 'searchResult'
|
|
||||||
'.search': 'searchContainer'
|
'.search': 'searchContainer'
|
||||||
|
'.js-global-search-result': 'searchResult'
|
||||||
|
'.js-details-link': 'searchDetails'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .js-toggleNotifications': 'toggleNotifications'
|
'click .js-toggleNotifications': 'toggleNotifications'
|
||||||
'click .js-emptySearch': 'emptyAndClose'
|
'click .js-emptySearch': 'emptyAndClose'
|
||||||
'submit form.search-holder': 'preventDefault'
|
'submit form.search-holder': 'preventDefault'
|
||||||
|
'dblclick form.search-holder .icon-magnifier': 'openExtendedSearch'
|
||||||
'focus #global-search': 'searchFocus'
|
'focus #global-search': 'searchFocus'
|
||||||
'blur #global-search': 'searchBlur'
|
'blur #global-search': 'searchBlur'
|
||||||
'keydown #global-search': 'listNavigate'
|
'keyup #global-search': 'listNavigate'
|
||||||
'click .js-global-search-result': 'andClose'
|
'click .js-global-search-result': 'andClose'
|
||||||
'change .js-menu .js-switch input': 'switch'
|
|
||||||
'click .js-details-link': 'openExtendedSearch'
|
'click .js-details-link': 'openExtendedSearch'
|
||||||
|
'change .js-menu .js-switch input': 'switch'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
@ -58,7 +60,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
@$('.bell').addClass('show')
|
@$('.bell').addClass('show')
|
||||||
App.Audio.play( 'https://www.sounddogs.com/previews/2193/mp3/219024_SOUNDDOGS__be.mp3' )
|
App.Audio.play( 'https://www.sounddogs.com/previews/2193/mp3/219024_SOUNDDOGS__be.mp3' )
|
||||||
@delay(
|
@delay(
|
||||||
-> App.Event.trigger('bell', 'off' )
|
-> App.Event.trigger('bell', 'off')
|
||||||
3000
|
3000
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
@ -214,6 +216,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
listNavigate: (e) =>
|
listNavigate: (e) =>
|
||||||
if e.keyCode is 27 # close on esc
|
if e.keyCode is 27 # close on esc
|
||||||
@emptyAndClose()
|
@emptyAndClose()
|
||||||
|
@searchInput.blur()
|
||||||
return
|
return
|
||||||
else if e.keyCode is 38 # up
|
else if e.keyCode is 38 # up
|
||||||
@nudge(e, -1)
|
@nudge(e, -1)
|
||||||
|
@ -222,9 +225,12 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
@nudge(e, 1)
|
@nudge(e, 1)
|
||||||
return
|
return
|
||||||
else if e.keyCode is 13 # enter
|
else if e.keyCode is 13 # enter
|
||||||
|
if @$('.global-search-menu .js-details-link.is-hover').get(0)
|
||||||
|
@openExtendedSearch()
|
||||||
|
return
|
||||||
href = @$('.global-search-result .nav-tab.is-hover').attr('href')
|
href = @$('.global-search-result .nav-tab.is-hover').attr('href')
|
||||||
return if !href
|
return if !href
|
||||||
@locationExecute(href)
|
@navigate(href)
|
||||||
@emptyAndClose()
|
@emptyAndClose()
|
||||||
@searchInput.blur()
|
@searchInput.blur()
|
||||||
return
|
return
|
||||||
|
@ -234,11 +240,21 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
|
|
||||||
nudge: (e, position) =>
|
nudge: (e, position) =>
|
||||||
|
|
||||||
|
return if !@searchContainer.hasClass('open')
|
||||||
|
|
||||||
# get current
|
# get current
|
||||||
current = @searchResult.find('.nav-tab.is-hover')
|
current = @searchResult.find('.nav-tab.is-hover')
|
||||||
if !current.get(0)
|
if !current.get(0)
|
||||||
@searchResult.find('.nav-tab').first().addClass('is-hover')
|
|
||||||
return
|
# if down, select detail search of first result
|
||||||
|
if position is 1
|
||||||
|
if !@searchDetails.hasClass('is-hover')
|
||||||
|
@searchDetails.addClass('is-hover')
|
||||||
|
return
|
||||||
|
|
||||||
|
@searchDetails.removeClass('is-hover')
|
||||||
|
@searchResult.find('.nav-tab').first().addClass('is-hover').popover('show')
|
||||||
|
return
|
||||||
|
|
||||||
if position is 1
|
if position is 1
|
||||||
next = current.closest('li').nextAll('li').not('.divider').first().find('.nav-tab')
|
next = current.closest('li').nextAll('li').not('.divider').first().find('.nav-tab')
|
||||||
|
@ -250,6 +266,9 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
if prev.get(0)
|
if prev.get(0)
|
||||||
current.removeClass('is-hover').popover('hide')
|
current.removeClass('is-hover').popover('hide')
|
||||||
prev.addClass('is-hover').popover('show')
|
prev.addClass('is-hover').popover('show')
|
||||||
|
else
|
||||||
|
current.removeClass('is-hover').popover('hide')
|
||||||
|
@searchDetails.addClass('is-hover')
|
||||||
|
|
||||||
if next
|
if next
|
||||||
@scrollToIfNeeded(next, true)
|
@scrollToIfNeeded(next, true)
|
||||||
|
@ -275,49 +294,9 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
@query = query
|
@query = query
|
||||||
@searchContainer.toggleClass('filled', !!@query)
|
@searchContainer.toggleClass('filled', !!@query)
|
||||||
|
|
||||||
# use cache for search result
|
App.GlobalSearch.execute(
|
||||||
if @searchResultCache[@query]
|
query: @query
|
||||||
@renderResult(@searchResultCache[@query].result)
|
render: @renderResult
|
||||||
currentTime = new Date
|
|
||||||
return if @searchResultCache[@query].time > currentTime.setSeconds(currentTime.getSeconds() - 20)
|
|
||||||
|
|
||||||
App.Ajax.request(
|
|
||||||
id: 'search'
|
|
||||||
type: 'GET'
|
|
||||||
url: "#{@apiPath}/search"
|
|
||||||
data:
|
|
||||||
query: @query
|
|
||||||
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
|
|
||||||
else
|
|
||||||
@log 'error', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()"
|
|
||||||
else
|
|
||||||
@log 'error', "No such model App.#{item.type}"
|
|
||||||
|
|
||||||
diff = false
|
|
||||||
if @searchResultCache[@query]
|
|
||||||
diff = difference(@searchResultCache[@query].resultRaw, data.result)
|
|
||||||
|
|
||||||
# cache search result
|
|
||||||
@searchResultCache[@query] =
|
|
||||||
result: result
|
|
||||||
resultRaw: data.result
|
|
||||||
time: new Date
|
|
||||||
|
|
||||||
# if result hasn't changed, do not rerender
|
|
||||||
return if diff isnt false && _.isEmpty(diff)
|
|
||||||
|
|
||||||
@renderResult(result)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
getItems: (data) ->
|
getItems: (data) ->
|
||||||
|
@ -424,7 +403,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
url = params.url
|
url = params.url
|
||||||
type = params.type
|
type = params.type
|
||||||
if type is 'menu'
|
if type is 'menu'
|
||||||
@$('.js-menu .is-active').removeClass('is-active')
|
@$('.js-menu .is-active, .js-details-link.is-active').removeClass('is-active')
|
||||||
else
|
else
|
||||||
@$('.is-active').removeClass('is-active')
|
@$('.is-active').removeClass('is-active')
|
||||||
return if !url || url is '#'
|
return if !url || url is '#'
|
||||||
|
@ -478,8 +457,9 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@notificationWidget.toggle()
|
@notificationWidget.toggle()
|
||||||
|
|
||||||
openExtendedSearch: (event) ->
|
openExtendedSearch: (e) ->
|
||||||
event.preventDefault()
|
if e
|
||||||
|
e.preventDefault()
|
||||||
query = @searchInput.val()
|
query = @searchInput.val()
|
||||||
@searchInput.val('').blur()
|
@searchInput.val('').blur()
|
||||||
if query
|
if query
|
||||||
|
@ -487,6 +467,4 @@ class App.Navigation extends App.ControllerWidgetPermanent
|
||||||
return
|
return
|
||||||
@navigate('#search')
|
@navigate('#search')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
App.Config.set('navigation', App.Navigation, 'Navigations')
|
App.Config.set('navigation', App.Navigation, 'Navigations')
|
||||||
|
|
|
@ -6,7 +6,7 @@ class App.Search extends App.Controller
|
||||||
events:
|
events:
|
||||||
'click .js-emptySearch': 'empty'
|
'click .js-emptySearch': 'empty'
|
||||||
'submit form.search-holder': 'preventDefault'
|
'submit form.search-holder': 'preventDefault'
|
||||||
'keydown .js-search': 'listNavigate'
|
'keyup .js-search': 'listNavigate'
|
||||||
'click .js-tab': 'showTab'
|
'click .js-tab': 'showTab'
|
||||||
'input .js-search': 'updateFilledClass'
|
'input .js-search': 'updateFilledClass'
|
||||||
|
|
||||||
|
@ -25,6 +25,11 @@ class App.Search extends App.Controller
|
||||||
|
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
|
# rerender view, e. g. on langauge change
|
||||||
|
@bind('ui:rerender', =>
|
||||||
|
@render()
|
||||||
|
)
|
||||||
|
|
||||||
meta: =>
|
meta: =>
|
||||||
if @query
|
if @query
|
||||||
title = App.Utils.htmlEscape(@query)
|
title = App.Utils.htmlEscape(@query)
|
||||||
|
@ -95,7 +100,9 @@ class App.Search extends App.Controller
|
||||||
|
|
||||||
empty: =>
|
empty: =>
|
||||||
@searchInput.val('')
|
@searchInput.val('')
|
||||||
|
@query = ''
|
||||||
@updateFilledClass()
|
@updateFilledClass()
|
||||||
|
@updateTask()
|
||||||
|
|
||||||
# remove not needed popovers
|
# remove not needed popovers
|
||||||
@delay(@anyPopoversDestroy, 100, 'removePopovers')
|
@delay(@anyPopoversDestroy, 100, 'removePopovers')
|
||||||
|
@ -106,53 +113,11 @@ class App.Search extends App.Controller
|
||||||
return if !query
|
return if !query
|
||||||
return if query is @query
|
return if query is @query
|
||||||
@query = query
|
@query = query
|
||||||
|
|
||||||
# use cache for search result
|
|
||||||
if @searchResultCache[@query]
|
|
||||||
@renderResult(@searchResultCache[@query].result)
|
|
||||||
currentTime = new Date
|
|
||||||
return if @searchResultCache[@query].time > currentTime.setSeconds(currentTime.getSeconds() - 20)
|
|
||||||
|
|
||||||
@updateTask()
|
@updateTask()
|
||||||
|
|
||||||
App.Ajax.request(
|
App.GlobalSearch.execute(
|
||||||
id: 'search'
|
query: @query
|
||||||
type: 'GET'
|
render: @renderResult
|
||||||
url: "#{@apiPath}/search"
|
|
||||||
data:
|
|
||||||
query: @query
|
|
||||||
limit: 200
|
|
||||||
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
|
|
||||||
else
|
|
||||||
@log 'error', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()"
|
|
||||||
else
|
|
||||||
@log 'error', "No such model App.#{item.type}"
|
|
||||||
|
|
||||||
diff = false
|
|
||||||
if @searchResultCache[@query]
|
|
||||||
diff = difference(@searchResultCache[@query].resultRaw, data.result)
|
|
||||||
|
|
||||||
# cache search result
|
|
||||||
@searchResultCache[@query] =
|
|
||||||
result: result
|
|
||||||
resultRaw: data.result
|
|
||||||
time: new Date
|
|
||||||
|
|
||||||
# if result hasn't changed, do not rerender
|
|
||||||
return if diff isnt false && _.isEmpty(diff)
|
|
||||||
|
|
||||||
@renderResult(result)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
renderResult: (result = []) =>
|
renderResult: (result = []) =>
|
||||||
|
@ -210,6 +175,7 @@ class App.Search extends App.Controller
|
||||||
|
|
||||||
updateTask: =>
|
updateTask: =>
|
||||||
current = App.TaskManager.get(@task_key).state
|
current = App.TaskManager.get(@task_key).state
|
||||||
|
return if !current
|
||||||
current.query = @query
|
current.query = @query
|
||||||
current.model = @model
|
current.model = @model
|
||||||
App.TaskManager.update(@task_key, { state: current })
|
App.TaskManager.update(@task_key, { state: current })
|
||||||
|
|
|
@ -94,9 +94,9 @@ class App.OnlineNotificationWidget extends App.Controller
|
||||||
prev.addClass('is-hover')
|
prev.addClass('is-hover')
|
||||||
|
|
||||||
if next
|
if next
|
||||||
@scrollToIfNeeded(next, false)
|
@scrollToIfNeeded(next, true)
|
||||||
if prev
|
if prev
|
||||||
@scrollToIfNeeded(prev, true)
|
@scrollToIfNeeded(prev, false)
|
||||||
|
|
||||||
counterUpdate: (count, force = false) =>
|
counterUpdate: (count, force = false) =>
|
||||||
count = '' if count is 0
|
count = '' if count is 0
|
||||||
|
|
62
app/assets/javascripts/app/lib/app_post/global_search.coffee
Normal file
62
app/assets/javascripts/app/lib/app_post/global_search.coffee
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
class App.GlobalSearch
|
||||||
|
_instance = undefined
|
||||||
|
|
||||||
|
@execute: (args) ->
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _globalSearchSingleton
|
||||||
|
_instance.execute(args)
|
||||||
|
|
||||||
|
class _globalSearchSingleton extends Spine.Module
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
@searchResultCache = {}
|
||||||
|
@apiPath = App.Config.get('api_path')
|
||||||
|
|
||||||
|
execute: (params) ->
|
||||||
|
query = params.query
|
||||||
|
render = params.render
|
||||||
|
|
||||||
|
# use cache for search result
|
||||||
|
if @searchResultCache[query]
|
||||||
|
render(@searchResultCache[query].result)
|
||||||
|
currentTime = new Date
|
||||||
|
return if @searchResultCache[query].time > currentTime.setSeconds(currentTime.getSeconds() - 20)
|
||||||
|
|
||||||
|
App.Ajax.request(
|
||||||
|
id: 'search'
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/search"
|
||||||
|
data:
|
||||||
|
query: query
|
||||||
|
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
|
||||||
|
else
|
||||||
|
App.Log.error('_globalSearchSingleton', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()")
|
||||||
|
else
|
||||||
|
App.Log.error('_globalSearchSingleton', "No such model App.#{item.type}")
|
||||||
|
|
||||||
|
diff = false
|
||||||
|
if @searchResultCache[query]
|
||||||
|
diff = difference(@searchResultCache[query].resultRaw, data.result)
|
||||||
|
|
||||||
|
# cache search result
|
||||||
|
@searchResultCache[query] =
|
||||||
|
result: result
|
||||||
|
resultRaw: data.result
|
||||||
|
time: new Date
|
||||||
|
|
||||||
|
# if result hasn't changed, do not rerender
|
||||||
|
return if diff isnt false && _.isEmpty(diff)
|
||||||
|
|
||||||
|
render(result)
|
||||||
|
)
|
|
@ -16,7 +16,7 @@
|
||||||
<%- @Icon('searchdetail') %>
|
<%- @Icon('searchdetail') %>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-tab-name flex u-textTruncate">
|
<div class="nav-tab-name flex u-textTruncate">
|
||||||
<%= @T('Show Search Details') %>
|
<%- @T('Show Search Details') %>
|
||||||
<%- @Icon('long-arrow-right') %>
|
<%- @Icon('long-arrow-right') %>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
Loading…
Reference in a new issue