Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4129094
SearchOverlay.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
8 KB
Referenced Files
None
Subscribers
None
SearchOverlay.js
View Options
const
mfExtend
=
require
(
'../mfExtend'
),
Overlay
=
require
(
'../Overlay'
),
util
=
require
(
'../util'
),
searchHeader
=
require
(
'./searchHeader'
),
SearchResultsView
=
require
(
'./SearchResultsView'
),
WatchstarPageList
=
require
(
'../watchstar/WatchstarPageList'
),
SEARCH_DELAY
=
300
,
SEARCH_SPINNER_DELAY
=
2000
;
/**
* Overlay displaying search results
*
* @class SearchOverlay
* @memberof module:mobile.startup/search
* @extends Overlay
* @uses SearchGateway
* @uses IconButton
*
* @param {Object} params Configuration options
* @param {string} params.placeholderMsg Search input placeholder text.
* @param {string} [params.action] of form defaults to the value of wgScript
* @param {string} [params.defaultSearchPage] The default search page e.g. Special:Search
* @param {SearchGateway} [params.gateway]
*/
function
SearchOverlay
(
params
)
{
const
header
=
searchHeader
(
params
.
placeholderMsg
,
params
.
action
||
mw
.
config
.
get
(
'wgScript'
),
(
query
)
=>
this
.
performSearch
(
query
),
params
.
defaultSearchPage
||
''
,
params
.
autocapitalize
),
options
=
util
.
extend
(
true
,
{
headerChrome
:
true
,
isBorderBox
:
false
,
className
:
'overlay search-overlay'
,
headers
:
[
header
],
events
:
{
'click .search-content'
:
'onClickSearchContent'
,
'click .overlay-content'
:
'onClickOverlayContent'
,
'click .overlay-content > div'
:
function
(
ev
)
{
ev
.
stopPropagation
();
},
'touchstart .results'
:
'hideKeyboardOnScroll'
,
'mousedown .results'
:
'hideKeyboardOnScroll'
,
'click .results a'
:
'onClickResult'
}
},
params
);
this
.
header
=
header
;
Overlay
.
call
(
this
,
options
);
this
.
api
=
options
.
api
;
// eslint-disable-next-line new-cap
this
.
gateway
=
options
.
gateway
||
new
options
.
gatewayClass
(
this
.
api
);
this
.
router
=
options
.
router
;
this
.
currentSearchId
=
null
;
}
mfExtend
(
SearchOverlay
,
Overlay
,
{
/**
* Initialize 'search within pages' functionality
*
* @memberof SearchOverlay
* @instance
*/
onClickSearchContent
()
{
const
$form
=
this
.
$el
.
find
(
'form'
),
$el
=
$form
[
0
].
parentNode
;
// Add fulltext input to force fulltext search
this
.
parseHTML
(
'<input>'
)
.
attr
(
{
type
:
'hidden'
,
name
:
'fulltext'
,
value
:
'search'
}
)
.
appendTo
(
$form
);
// history.back queues a task so might run after this call. Thus we use setTimeout
// http://www.w3.org/TR/2011/WD-html5-20110113/webappapis.html#queue-a-task
setTimeout
(
()
=>
{
// Firefox doesn't allow submission of a form not in the DOM
// so temporarily re-add it if it's gone.
if
(
!
$form
[
0
].
parentNode
)
{
$form
.
appendTo
(
$el
);
}
$form
.
trigger
(
'submit'
);
},
0
);
},
/**
* Tapping on background only should hide the overlay
*
* @memberof SearchOverlay
* @instance
*/
onClickOverlayContent
()
{
this
.
$el
.
find
(
'.cancel'
).
trigger
(
'click'
);
},
/**
* Hide the keyboard when scrolling starts (avoid weird situation when
* user taps on an item, the keyboard hides and wrong item is clicked).
*
* @memberof SearchOverlay
* @instance
*/
hideKeyboardOnScroll
()
{
this
.
$input
.
trigger
(
'blur'
);
},
/**
* Handle the user clicking a result.
*
* @memberof SearchOverlay
* @instance
* @param {jQuery.Event} ev
*/
onClickResult
(
ev
)
{
const
self
=
this
,
$link
=
this
.
$el
.
find
(
ev
.
currentTarget
);
/**
* Fired when the user clicks a search result
*
* @type {Object}
* @property {jQuery.Object} result The jQuery-wrapped DOM element that
* the user clicked
* @property {number} resultIndex The zero-based index of the
* result in the set of results
* @property {jQuery.Event} originalEvent The original event
*/
// FIXME: ugly hack that removes search from browser history
// when navigating to search results
ev
.
preventDefault
();
this
.
router
.
back
().
then
(
function
()
{
// T308288: Appends the current search id as a url param on clickthroughs
if
(
this
.
currentSearchId
)
{
const
clickUrl
=
new
URL
(
location
.
href
);
clickUrl
.
searchParams
.
set
(
'searchToken'
,
this
.
currentSearchId
);
self
.
router
.
navigateTo
(
document
.
title
,
{
path
:
clickUrl
.
toString
(),
useReplaceState
:
true
}
);
this
.
currentSearchId
=
null
;
}
// Router.navigate does not support changing href.
// FIXME: Needs upstream change T189173
// eslint-disable-next-line no-restricted-properties
window
.
location
.
href
=
$link
.
attr
(
'href'
);
}
);
},
/**
* @inheritdoc
* @memberof SearchOverlay
* @instance
*/
postRender
()
{
const
self
=
this
,
searchResults
=
new
SearchResultsView
(
{
searchContentLabel
:
mw
.
msg
(
'mobile-frontend-search-content'
),
noResultsMsg
:
mw
.
msg
(
'mobile-frontend-search-no-results'
),
searchContentNoResultsMsg
:
mw
.
message
(
'mobile-frontend-search-content-no-results'
).
parse
()
}
);
let
timer
;
this
.
$el
.
find
(
'.overlay-content'
).
append
(
searchResults
.
$el
);
Overlay
.
prototype
.
postRender
.
call
(
this
);
// FIXME: `this.$input` should not be set. Isolate to searchHeader function
this
.
$input
=
this
.
$el
.
find
(
this
.
header
).
find
(
'input'
);
// FIXME: `this.$searchContent` should not be set. Isolate to SearchResultsView class.
this
.
$searchContent
=
searchResults
.
$el
.
hide
();
// FIXME: `this.$resultContainer` should not be set. Isolate to SearchResultsView class.
this
.
$resultContainer
=
searchResults
.
$el
.
find
(
'.results-list-container'
);
// On iOS a touchstart event while the keyboard is open will result in a scroll
// leading to an accidental click (T299846)
// Stopping propagation when the input is focused will prevent scrolling while
// the keyboard is collapsed.
this
.
$resultContainer
[
0
].
addEventListener
(
'touchstart'
,
(
ev
)
=>
{
if
(
document
.
activeElement
===
this
.
$input
[
0
]
)
{
ev
.
stopPropagation
();
}
}
);
/**
* Hide the spinner and abort timed spinner shows.
* FIXME: Given this manipulates SearchResultsView this should be moved into that class
*/
function
clearSearch
()
{
self
.
$spinner
.
hide
();
clearTimeout
(
timer
);
}
// Show a spinner on top of search results
// FIXME: Given this manipulates SearchResultsView this should be moved into that class
this
.
$spinner
=
searchResults
.
$el
.
find
(
'.spinner-container'
);
this
.
on
(
'search-start'
,
(
searchData
)
=>
{
if
(
timer
)
{
clearSearch
();
}
timer
=
setTimeout
(
()
=>
self
.
$spinner
.
show
(),
SEARCH_SPINNER_DELAY
-
searchData
.
delay
);
}
);
this
.
on
(
'search-results'
,
clearSearch
);
},
/**
* Trigger a focus() event on search input in order to
* bring up the virtual keyboard.
*
* @memberof SearchOverlay
* @instance
*/
showKeyboard
()
{
const
len
=
this
.
$input
.
val
().
length
;
this
.
$input
.
trigger
(
'focus'
);
// Cursor to the end of the input
if
(
this
.
$input
[
0
].
setSelectionRange
)
{
this
.
$input
[
0
].
setSelectionRange
(
len
,
len
);
}
},
/**
* @inheritdoc
* @memberof SearchOverlay
* @instance
*/
show
()
{
// Overlay#show defines the actual overlay visibility.
Overlay
.
prototype
.
show
.
apply
(
this
,
arguments
);
this
.
showKeyboard
();
},
/**
* Perform search and render results inside current view.
* FIXME: Much of the logic for caching and pending queries inside this function should
* actually live in SearchGateway, please move out.
*
* @memberof SearchOverlay
* @instance
* @param {string} query
*/
performSearch
(
query
)
{
const
self
=
this
,
api
=
this
.
api
,
delay
=
this
.
gateway
.
isCached
(
query
)
?
0
:
SEARCH_DELAY
;
// it seems the input event can be fired when virtual keyboard is closed
// (Chrome for Android)
if
(
query
!==
this
.
lastQuery
)
{
if
(
self
.
_pendingQuery
)
{
self
.
_pendingQuery
.
abort
();
}
clearTimeout
(
this
.
timer
);
if
(
query
.
length
)
{
this
.
timer
=
setTimeout
(
()
=>
{
const
xhr
=
self
.
gateway
.
search
(
query
);
self
.
_pendingQuery
=
xhr
.
then
(
function
(
data
)
{
this
.
currentSearchId
=
data
.
searchId
;
// FIXME: Given this manipulates SearchResultsView
// this should be moved into that class
// check if we're getting the rights response in case of out of
// order responses (need to get the current value of the input)
if
(
data
&&
data
.
query
===
self
.
$input
.
val
()
)
{
self
.
$el
.
toggleClass
(
'no-results'
,
data
.
results
.
length
===
0
);
self
.
$searchContent
.
show
()
.
find
(
'p'
)
.
hide
()
.
filter
(
data
.
results
.
length
?
'.with-results'
:
'.without-results'
)
.
show
();
// eslint-disable-next-line no-new
new
WatchstarPageList
(
{
api
,
funnel
:
'search'
,
pages
:
data
.
results
,
el
:
self
.
$resultContainer
}
);
self
.
$results
=
self
.
$resultContainer
.
find
(
'li'
);
}
}
).
promise
(
{
abort
()
{
xhr
.
abort
();
}
}
);
},
delay
);
}
else
{
self
.
resetSearch
();
}
this
.
lastQuery
=
query
;
}
},
/**
* Clear results
*
* @private
*/
resetSearch
()
{
this
.
$el
.
find
(
'.overlay-content'
).
children
().
hide
();
}
}
);
module
.
exports
=
SearchOverlay
;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Aug 19, 09:36 (2 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
48/b8/5a7b251207c935186ba520be2ecb
Default Alt Text
SearchOverlay.js (8 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment