Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4129696
FilterTagMultiselectWidget.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
24 KB
Referenced Files
None
Subscribers
None
FilterTagMultiselectWidget.js
View Options
const
ViewSwitchWidget
=
require
(
'./ViewSwitchWidget.js'
),
SaveFiltersPopupButtonWidget
=
require
(
'./SaveFiltersPopupButtonWidget.js'
),
MenuSelectWidget
=
require
(
'./MenuSelectWidget.js'
),
FilterTagItemWidget
=
require
(
'./FilterTagItemWidget.js'
);
/**
* List displaying all filter groups.
*
* @class mw.rcfilters.ui.FilterTagMultiselectWidget
* @ignore
* @extends OO.ui.MenuTagMultiselectWidget
* @mixes OO.ui.mixin.PendingElement
*
* @param {mw.rcfilters.Controller} controller Controller
* @param {mw.rcfilters.dm.FiltersViewModel} model View model
* @param {mw.rcfilters.dm.SavedQueriesModel} savedQueriesModel Saved queries model
* @param {Object} config Configuration object
* @param {jQuery} [config.$overlay] A jQuery object serving as overlay for popups
* @param {jQuery} [config.$wrapper] A jQuery object for the wrapper of the general
* system. If not given, falls back to this widget's $element
* @param {boolean} [config.collapsed] Filter area is collapsed
*/
const
FilterTagMultiselectWidget
=
function
MwRcfiltersUiFilterTagMultiselectWidget
(
controller
,
model
,
savedQueriesModel
,
config
)
{
const
title
=
new
OO
.
ui
.
LabelWidget
(
{
label
:
mw
.
msg
(
'rcfilters-activefilters'
),
classes
:
[
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-content-title'
]
}
),
$contentWrapper
=
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper'
);
config
=
config
||
{};
this
.
controller
=
controller
;
this
.
model
=
model
;
this
.
queriesModel
=
savedQueriesModel
;
this
.
$overlay
=
config
.
$overlay
||
this
.
$element
;
this
.
$wrapper
=
config
.
$wrapper
||
this
.
$element
;
this
.
matchingQuery
=
null
;
this
.
currentView
=
this
.
model
.
getCurrentView
();
this
.
collapsed
=
false
;
this
.
isMobile
=
config
.
isMobile
;
// Has to be before the parent constructor, because the parent constructor may call setValue()
// which causes the onChangeTags handler to run (T245073)
this
.
emptyFilterMessage
=
new
OO
.
ui
.
LabelWidget
(
{
label
:
mw
.
msg
(
'rcfilters-empty-filter'
),
classes
:
[
'mw-rcfilters-ui-filterTagMultiselectWidget-emptyFilters'
]
}
);
// Parent
FilterTagMultiselectWidget
.
super
.
call
(
this
,
$
.
extend
(
true
,
{
label
:
mw
.
msg
(
'rcfilters-filterlist-title'
),
placeholder
:
mw
.
msg
(
'rcfilters-empty-filter'
),
inputPosition
:
'outline'
,
allowArbitrary
:
false
,
allowDisplayInvalidTags
:
false
,
allowReordering
:
false
,
$overlay
:
this
.
$overlay
,
menu
:
{
// Our filtering is done through the model
filterFromInput
:
false
,
hideWhenOutOfView
:
false
,
hideOnChoose
:
false
,
// Only set width and footers for desktop
isMobile
:
this
.
isMobile
,
width
:
650
,
footers
:
[
{
name
:
'viewSelect'
,
sticky
:
false
,
// View select menu, appears on default view only
$element
:
$
(
'<div>'
)
.
append
(
new
ViewSwitchWidget
(
this
.
controller
,
this
.
model
).
$element
),
views
:
[
'default'
]
}
]
},
/**
* In the presence of an onscreen keyboard (i.e. isMobile) the filter input should act as a button
* rather than a text input. Mobile screens are too small to accommodate both an
* onscreen keyboard and a popup-menu, so readyOnly is set to disable the keyboard.
* A different icon and shorter message is used for mobile as well. (See T224655 for details).
*
* @ignore
*/
input
:
{
icon
:
this
.
isMobile
?
'funnel'
:
'menu'
,
placeholder
:
this
.
isMobile
?
mw
.
msg
(
'rcfilters-search-placeholder-mobile'
)
:
mw
.
msg
(
'rcfilters-search-placeholder'
),
readOnly
:
!!
this
.
isMobile
,
classes
:
[
'oo-ui-tagMultiselectWidget-input'
]
}
},
config
)
);
this
.
input
.
$input
.
attr
(
'aria-label'
,
mw
.
msg
(
'rcfilters-search-placeholder'
)
);
this
.
savedQueryTitle
=
new
OO
.
ui
.
LabelWidget
(
{
label
:
''
,
classes
:
[
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-content-savedQueryTitle'
]
}
);
this
.
resetButton
=
new
OO
.
ui
.
ButtonWidget
(
{
framed
:
false
,
classes
:
[
'mw-rcfilters-ui-filterTagMultiselectWidget-resetButton'
]
}
);
this
.
hideShowButton
=
new
OO
.
ui
.
ButtonWidget
(
{
framed
:
false
,
flags
:
[
'progressive'
],
classes
:
[
'mw-rcfilters-ui-filterTagMultiselectWidget-hideshowButton'
]
}
);
this
.
toggleCollapsed
(
!!
config
.
collapsed
);
if
(
!
mw
.
user
.
isAnon
()
)
{
this
.
saveQueryButton
=
new
SaveFiltersPopupButtonWidget
(
this
.
controller
,
this
.
queriesModel
,
{
$overlay
:
this
.
$overlay
}
);
this
.
saveQueryButton
.
$element
.
on
(
'mousedown'
,
(
e
)
=>
{
e
.
stopPropagation
();
}
);
this
.
saveQueryButton
.
connect
(
this
,
{
click
:
'onSaveQueryButtonClick'
,
saveCurrent
:
'setSavedQueryVisibility'
}
);
this
.
queriesModel
.
connect
(
this
,
{
itemUpdate
:
'onSavedQueriesItemUpdate'
,
initialize
:
'onSavedQueriesInitialize'
,
default
:
'reevaluateResetRestoreState'
}
);
}
this
.
$content
.
append
(
this
.
emptyFilterMessage
.
$element
);
// Events
this
.
resetButton
.
connect
(
this
,
{
click
:
'onResetButtonClick'
}
);
this
.
hideShowButton
.
connect
(
this
,
{
click
:
'onHideShowButtonClick'
}
);
// Stop propagation for mousedown, so that the widget doesn't
// trigger the focus on the input and scrolls up when we click the reset button
this
.
resetButton
.
$element
.
on
(
'mousedown'
,
(
e
)
=>
{
e
.
stopPropagation
();
}
);
this
.
hideShowButton
.
$element
.
on
(
'mousedown'
,
(
e
)
=>
{
e
.
stopPropagation
();
}
);
this
.
model
.
connect
(
this
,
{
initialize
:
'onModelInitialize'
,
update
:
'onModelUpdate'
,
searchChange
:
this
.
isMobile
?
function
()
{}
:
'onModelSearchChange'
,
itemUpdate
:
'onModelItemUpdate'
,
highlightChange
:
'onModelHighlightChange'
}
);
if
(
!
this
.
isMobile
)
{
this
.
input
.
connect
(
this
,
{
change
:
'onInputChange'
}
);
}
// The filter list and button should appear side by side regardless of how
// wide the button is; the button also changes its width depending
// on language and its state, so the safest way to present both side
// by side is with a table layout
const
$rcFiltersRow
=
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-row'
)
.
append
(
this
.
$content
.
addClass
(
'mw-rcfilters-ui-cell'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-cell-filters'
)
);
if
(
!
mw
.
user
.
isAnon
()
)
{
$rcFiltersRow
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-cell'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-cell-save'
)
.
append
(
this
.
saveQueryButton
.
$element
)
);
}
// Add a selector at the right of the input
this
.
viewsSelectWidget
=
this
.
createViewsSelectWidget
();
// change the layout of the viewsSelectWidget
this
.
restructureViewsSelectWidget
();
// Event
this
.
viewsSelectWidget
.
aggregate
(
{
click
:
'buttonClick'
}
);
this
.
viewsSelectWidget
.
connect
(
this
,
{
buttonClick
:
'onViewsSelectWidgetButtonClick'
}
);
$rcFiltersRow
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-cell'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-cell-reset'
)
.
append
(
this
.
resetButton
.
$element
)
);
// Build the content
$contentWrapper
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-top'
)
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-top-title'
)
.
append
(
title
.
$element
),
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-top-queryName'
)
.
append
(
this
.
savedQueryTitle
.
$element
),
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-top-hideshow'
)
.
append
(
this
.
hideShowButton
.
$element
)
),
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-table'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-filters'
)
.
append
(
$rcFiltersRow
)
);
// Initialize
this
.
$handle
.
append
(
$contentWrapper
);
this
.
emptyFilterMessage
.
toggle
(
this
.
isEmpty
()
);
this
.
savedQueryTitle
.
toggle
(
false
);
this
.
$element
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget'
);
if
(
this
.
isMobile
)
{
this
.
$element
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-mobile'
);
}
this
.
reevaluateResetRestoreState
();
};
/* Initialization */
OO
.
inheritClass
(
FilterTagMultiselectWidget
,
OO
.
ui
.
MenuTagMultiselectWidget
);
/* Methods */
/**
* Create a OOUI ButtonGroupWidget. The buttons are framed and have additional CSS
* classes applied on mobile.
*
* @return {OO.ui.ButtonGroupWidget}
*/
FilterTagMultiselectWidget
.
prototype
.
createViewsSelectWidget
=
function
()
{
const
viewsSelectWidget
=
new
OO
.
ui
.
ButtonGroupWidget
(
{
classes
:
this
.
isMobile
?
[
'mw-rcfilters-ui-table'
,
'mw-rcfilters-ui-filterTagMultiselectWidget-mobile-view'
]
:
[
'mw-rcfilters-ui-filterTagMultiselectWidget-views-select-widget'
],
items
:
[
new
OO
.
ui
.
ButtonWidget
(
{
framed
:
!!
this
.
isMobile
,
data
:
'namespaces'
,
icon
:
'article'
,
label
:
mw
.
msg
(
'namespaces'
),
title
:
mw
.
msg
(
'rcfilters-view-namespaces-tooltip'
),
classes
:
this
.
isMobile
?
[
'mw-rcfilters-ui-cell'
]
:
[]
}
),
new
OO
.
ui
.
ButtonWidget
(
{
framed
:
!!
this
.
isMobile
,
data
:
'tags'
,
icon
:
'tag'
,
label
:
mw
.
msg
(
'tags-title'
),
title
:
mw
.
msg
(
'rcfilters-view-tags-tooltip'
),
classes
:
this
.
isMobile
?
[
'mw-rcfilters-ui-cell'
]
:
[]
}
)
]
}
);
viewsSelectWidget
.
items
.
forEach
(
(
item
)
=>
{
item
.
$button
.
attr
(
'aria-label'
,
item
.
title
);
}
);
return
viewsSelectWidget
;
};
/**
* Rearrange the DOM structure of the viewsSelectWiget so that on the namespace & tags buttons
* are at the right of the input on desktop, and below the input on mobile.
*/
FilterTagMultiselectWidget
.
prototype
.
restructureViewsSelectWidget
=
function
()
{
if
(
this
.
isMobile
)
{
// On mobile, append the search input and the extra buttons below the search input.
this
.
$element
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-views-input'
)
.
append
(
this
.
input
.
$element
)
.
append
(
this
.
viewsSelectWidget
.
$element
)
);
}
else
{
// On desktop, rearrange the UI so the select widget is at the right of the input
this
.
$element
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-table'
)
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-row'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-views'
)
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-cell'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-views-input'
)
.
append
(
this
.
input
.
$element
),
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-cell'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-views-select'
)
.
append
(
this
.
viewsSelectWidget
.
$element
)
)
)
);
}
};
/**
* Respond to button click event
*
* @param {OO.ui.ButtonWidget} buttonWidget Clicked widget
*/
FilterTagMultiselectWidget
.
prototype
.
onViewsSelectWidgetButtonClick
=
function
(
buttonWidget
)
{
this
.
controller
.
switchView
(
buttonWidget
.
getData
()
);
this
.
focus
();
};
/**
* Respond to model search change event
*
* @param {string} value Search value
*/
FilterTagMultiselectWidget
.
prototype
.
onModelSearchChange
=
function
(
value
)
{
this
.
input
.
setValue
(
value
);
};
/**
* Respond to input change event
*
* @param {string} value Value of the input
*/
FilterTagMultiselectWidget
.
prototype
.
onInputChange
=
function
(
value
)
{
this
.
controller
.
setSearch
(
value
);
};
/**
* Respond to query button click
*/
FilterTagMultiselectWidget
.
prototype
.
onSaveQueryButtonClick
=
function
()
{
this
.
getMenu
().
toggle
(
false
);
};
/**
* Respond to save query model initialization
*/
FilterTagMultiselectWidget
.
prototype
.
onSavedQueriesInitialize
=
function
()
{
this
.
setSavedQueryVisibility
();
};
/**
* Respond to save query item change. Mainly this is done to update the label in case
* a query item has been edited
*
* @param {mw.rcfilters.dm.SavedQueryItemModel} item Saved query item
*/
FilterTagMultiselectWidget
.
prototype
.
onSavedQueriesItemUpdate
=
function
(
item
)
{
if
(
this
.
matchingQuery
===
item
)
{
// This means we just edited the item that is currently matched
this
.
savedQueryTitle
.
setLabel
(
item
.
getLabel
()
);
}
};
/**
* Respond to menu toggle
*
* @param {boolean} isVisible Menu is visible
*/
FilterTagMultiselectWidget
.
prototype
.
onMenuToggle
=
function
(
isVisible
)
{
const
scrollToElement
=
this
.
isMobile
?
this
.
input
.
$input
:
this
.
$element
;
// Parent
FilterTagMultiselectWidget
.
super
.
prototype
.
onMenuToggle
.
call
(
this
);
if
(
isVisible
)
{
if
(
!
this
.
isMobile
)
{
this
.
focus
();
}
/**
* Fires when the RCFilters tag multi selector menu is toggled.
*
* @event ~'RcFilters.popup.open'
* @memberof Hooks
*/
mw
.
hook
(
'RcFilters.popup.open'
).
fire
();
if
(
!
this
.
getMenu
().
findSelectedItem
()
)
{
// If there are no selected items, scroll menu to top
// This has to be in a setTimeout so the menu has time
// to be positioned and fixed
setTimeout
(
()
=>
{
this
.
getMenu
().
scrollToTop
();
}
);
}
// Only scroll to top of the viewport if:
// - The widget is more than 20px from the top
// - The widget is not above the top of the viewport (do not scroll downwards)
// (This isn't represented because >20 is, anyways and always, bigger than 0)
this
.
scrollToTop
(
scrollToElement
,
0
,
{
min
:
20
,
max
:
Infinity
}
);
}
else
{
// Clear selection
this
.
selectTag
(
null
);
// Clear the search
this
.
controller
.
setSearch
(
''
);
this
.
blur
();
}
if
(
this
.
isMobile
)
{
this
.
input
.
setIcon
(
isVisible
?
'close'
:
'funnel'
);
}
else
{
this
.
input
.
setIcon
(
isVisible
?
'search'
:
'menu'
);
}
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
onInputFocus
=
function
()
{
// treat the input as a menu toggle rather than a text field on mobile
if
(
this
.
isMobile
)
{
this
.
input
.
$input
.
trigger
(
'blur'
);
this
.
getMenu
().
toggle
();
}
else
{
// Parent
FilterTagMultiselectWidget
.
super
.
prototype
.
onInputFocus
.
call
(
this
);
}
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
doInputEscape
=
function
()
{
// Parent
FilterTagMultiselectWidget
.
super
.
prototype
.
doInputEscape
.
call
(
this
);
// Blur the input
this
.
input
.
$input
.
trigger
(
'blur'
);
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
onMouseDown
=
function
(
e
)
{
if
(
!
this
.
collapsed
&&
!
this
.
isDisabled
()
&&
e
.
which
===
OO
.
ui
.
MouseButtons
.
LEFT
)
{
this
.
menu
.
toggle
();
return
false
;
}
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
onChangeTags
=
function
()
{
// If initialized, call parent method.
if
(
this
.
controller
.
isInitialized
()
)
{
FilterTagMultiselectWidget
.
super
.
prototype
.
onChangeTags
.
call
(
this
);
}
this
.
emptyFilterMessage
.
toggle
(
this
.
isEmpty
()
);
};
/**
* Respond to model initialize event
*/
FilterTagMultiselectWidget
.
prototype
.
onModelInitialize
=
function
()
{
this
.
setSavedQueryVisibility
();
};
/**
* Respond to model update event
*/
FilterTagMultiselectWidget
.
prototype
.
onModelUpdate
=
function
()
{
this
.
updateElementsForView
();
};
/**
* Update the elements in the widget to the current view
*/
FilterTagMultiselectWidget
.
prototype
.
updateElementsForView
=
function
()
{
let
inputValue
=
this
.
input
.
getValue
().
trim
();
const
view
=
this
.
model
.
getCurrentView
(),
inputView
=
this
.
model
.
getViewByTrigger
(
inputValue
.
slice
(
0
,
1
)
);
if
(
inputView
!==
'default'
)
{
// We have a prefix already, remove it
inputValue
=
inputValue
.
slice
(
1
);
}
if
(
inputView
!==
view
)
{
// Add the correct prefix
inputValue
=
this
.
model
.
getViewTrigger
(
view
)
+
inputValue
;
}
// Update input
this
.
input
.
setValue
(
inputValue
);
if
(
this
.
currentView
!==
view
)
{
this
.
scrollToTop
(
this
.
$element
);
this
.
currentView
=
view
;
}
};
/**
* Set the visibility of the saved query button
*/
FilterTagMultiselectWidget
.
prototype
.
setSavedQueryVisibility
=
function
()
{
if
(
mw
.
user
.
isAnon
()
)
{
return
;
}
this
.
matchingQuery
=
this
.
controller
.
findQueryMatchingCurrentState
();
this
.
savedQueryTitle
.
setLabel
(
this
.
matchingQuery
?
this
.
matchingQuery
.
getLabel
()
:
''
);
this
.
savedQueryTitle
.
toggle
(
!!
this
.
matchingQuery
);
this
.
saveQueryButton
.
setDisabled
(
!!
this
.
matchingQuery
);
this
.
saveQueryButton
.
setTitle
(
!
this
.
matchingQuery
?
mw
.
msg
(
'rcfilters-savedqueries-add-new-title'
)
:
mw
.
msg
(
'rcfilters-savedqueries-already-saved'
)
);
if
(
this
.
matchingQuery
)
{
this
.
emphasize
();
}
};
/**
* Respond to model itemUpdate event
* fixme: when a new state is applied to the model this function is called 60+ times in a row
*
* @param {mw.rcfilters.dm.FilterItem} item Filter item model
*/
FilterTagMultiselectWidget
.
prototype
.
onModelItemUpdate
=
function
(
item
)
{
if
(
!
item
.
getGroupModel
().
isHidden
()
)
{
if
(
item
.
isSelected
()
||
(
this
.
model
.
isHighlightEnabled
()
&&
item
.
getHighlightColor
()
)
)
{
this
.
addTag
(
item
.
getName
(),
item
.
getLabel
()
);
}
else
{
// Only attempt to remove the tag if we can find an item for it (T198140, T198231)
if
(
this
.
findItemFromData
(
item
.
getName
()
)
!==
null
)
{
this
.
removeTagByData
(
item
.
getName
()
);
}
}
}
this
.
setSavedQueryVisibility
();
// Re-evaluate reset state
this
.
reevaluateResetRestoreState
();
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
isAllowedData
=
function
(
data
)
{
return
(
this
.
model
.
getItemByName
(
data
)
&&
!
this
.
isDuplicateData
(
data
)
);
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
onMenuChoose
=
function
(
item
)
{
this
.
controller
.
toggleFilterSelect
(
item
.
model
.
getName
()
);
// Select the tag if it exists, or reset selection otherwise
this
.
selectTag
(
this
.
findItemFromData
(
item
.
model
.
getName
()
)
);
if
(
!
this
.
isMobile
)
{
this
.
focus
();
}
};
/**
* Respond to highlightChange event
*
* @param {boolean} isHighlightEnabled Highlight is enabled
*/
FilterTagMultiselectWidget
.
prototype
.
onModelHighlightChange
=
function
(
isHighlightEnabled
)
{
const
highlightedItems
=
this
.
model
.
getHighlightedItems
();
if
(
isHighlightEnabled
)
{
// Add capsule widgets
highlightedItems
.
forEach
(
(
filterItem
)
=>
{
this
.
addTag
(
filterItem
.
getName
(),
filterItem
.
getLabel
()
);
}
);
}
else
{
// Remove capsule widgets if they're not selected
highlightedItems
.
forEach
(
(
filterItem
)
=>
{
if
(
!
filterItem
.
isSelected
()
)
{
// Only attempt to remove the tag if we can find an item for it (T198140, T198231)
if
(
this
.
findItemFromData
(
filterItem
.
getName
()
)
!==
null
)
{
this
.
removeTagByData
(
filterItem
.
getName
()
);
}
}
}
);
}
this
.
setSavedQueryVisibility
();
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
onTagSelect
=
function
(
tagItem
)
{
const
menuOption
=
this
.
menu
.
getItemFromModel
(
tagItem
.
getModel
()
);
this
.
menu
.
setUserSelecting
(
true
);
// Parent method
FilterTagMultiselectWidget
.
super
.
prototype
.
onTagSelect
.
call
(
this
,
tagItem
);
// Switch view
this
.
controller
.
resetSearchForView
(
tagItem
.
getView
()
);
this
.
selectTag
(
tagItem
);
this
.
scrollToTop
(
menuOption
.
$element
);
this
.
menu
.
setUserSelecting
(
false
);
};
/**
* Select a tag by reference. This is what OO.ui.SelectWidget is doing.
* If no items are given, reset selection from all.
*
* @param {mw.rcfilters.ui.FilterTagItemWidget} [item] Tag to select,
* omit to deselect all
*/
FilterTagMultiselectWidget
.
prototype
.
selectTag
=
function
(
item
)
{
let
i
,
len
,
selected
;
for
(
i
=
0
,
len
=
this
.
items
.
length
;
i
<
len
;
i
++
)
{
selected
=
this
.
items
[
i
]
===
item
;
if
(
this
.
items
[
i
].
isSelected
()
!==
selected
)
{
this
.
items
[
i
].
toggleSelected
(
selected
);
}
}
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
onTagRemove
=
function
(
tagItem
)
{
// Parent method
FilterTagMultiselectWidget
.
super
.
prototype
.
onTagRemove
.
call
(
this
,
tagItem
);
this
.
controller
.
clearFilter
(
tagItem
.
getName
()
);
tagItem
.
destroy
();
};
/**
* Respond to click event on the reset button
*/
FilterTagMultiselectWidget
.
prototype
.
onResetButtonClick
=
function
()
{
if
(
this
.
model
.
areVisibleFiltersEmpty
()
)
{
// Reset to default filters
this
.
controller
.
resetToDefaults
();
}
else
{
// Reset to have no filters
this
.
controller
.
emptyFilters
();
}
};
/**
* Respond to hide/show button click
*/
FilterTagMultiselectWidget
.
prototype
.
onHideShowButtonClick
=
function
()
{
this
.
toggleCollapsed
();
};
/**
* Toggle the collapsed state of the filters widget
*
* @param {boolean} isCollapsed Widget is collapsed
*/
FilterTagMultiselectWidget
.
prototype
.
toggleCollapsed
=
function
(
isCollapsed
)
{
isCollapsed
=
isCollapsed
===
undefined
?
!
this
.
collapsed
:
!!
isCollapsed
;
this
.
collapsed
=
isCollapsed
;
if
(
isCollapsed
)
{
// If we are collapsing, close the menu, in case it was open
// We should make sure the menu closes before the rest of the elements
// are hidden, otherwise there is an unknown error in jQuery as ooui
// sets and unsets properties on the input (which is hidden at that point)
this
.
menu
.
toggle
(
false
);
}
this
.
input
.
setDisabled
(
isCollapsed
);
this
.
hideShowButton
.
setLabel
(
mw
.
msg
(
isCollapsed
?
'rcfilters-activefilters-show'
:
'rcfilters-activefilters-hide'
)
);
this
.
hideShowButton
.
setTitle
(
mw
.
msg
(
isCollapsed
?
'rcfilters-activefilters-show-tooltip'
:
'rcfilters-activefilters-hide-tooltip'
)
);
// Toggle the wrapper class, so we have min height values correctly throughout
this
.
$wrapper
.
toggleClass
(
'mw-rcfilters-collapsed'
,
isCollapsed
);
// Save the state
this
.
controller
.
updateCollapsedState
(
isCollapsed
);
};
/**
* Reevaluate the restore state for the widget between setting to defaults and clearing all filters
*/
FilterTagMultiselectWidget
.
prototype
.
reevaluateResetRestoreState
=
function
()
{
const
defaultsAreEmpty
=
this
.
controller
.
areDefaultsEmpty
(),
currFiltersAreEmpty
=
this
.
model
.
areVisibleFiltersEmpty
(),
hideResetButton
=
currFiltersAreEmpty
&&
defaultsAreEmpty
;
this
.
resetButton
.
setIcon
(
currFiltersAreEmpty
?
'history'
:
'trash'
);
this
.
resetButton
.
setLabel
(
currFiltersAreEmpty
?
mw
.
msg
(
'rcfilters-restore-default-filters'
)
:
''
);
this
.
resetButton
.
setTitle
(
currFiltersAreEmpty
?
null
:
mw
.
msg
(
'rcfilters-clear-all-filters'
)
);
this
.
resetButton
.
toggle
(
!
hideResetButton
);
this
.
emptyFilterMessage
.
toggle
(
currFiltersAreEmpty
);
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
createMenuWidget
=
function
(
menuConfig
)
{
return
new
MenuSelectWidget
(
this
.
controller
,
this
.
model
,
menuConfig
);
};
/**
* @inheritdoc
*/
FilterTagMultiselectWidget
.
prototype
.
createTagItemWidget
=
function
(
data
)
{
const
filterItem
=
this
.
model
.
getItemByName
(
data
);
if
(
filterItem
)
{
return
new
FilterTagItemWidget
(
this
.
controller
,
this
.
model
,
this
.
model
.
getInvertModel
(
filterItem
.
getGroupModel
().
getView
()
),
filterItem
,
{
$overlay
:
this
.
$overlay
}
);
}
};
FilterTagMultiselectWidget
.
prototype
.
emphasize
=
function
()
{
if
(
// eslint-disable-next-line no-jquery/no-class-state
!
this
.
$handle
.
hasClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-animate'
)
)
{
this
.
$handle
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-emphasize'
)
.
addClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-animate'
);
setTimeout
(
()
=>
{
this
.
$handle
.
removeClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-emphasize'
);
setTimeout
(
()
=>
{
this
.
$handle
.
removeClass
(
'mw-rcfilters-ui-filterTagMultiselectWidget-animate'
);
},
1000
);
},
500
);
}
};
/**
* Scroll the element to top within its container
*
* @private
* @param {jQuery} $element Element to position
* @param {number} [marginFromTop=0] When scrolling the entire widget to the top, leave this
* much space (in pixels) above the widget.
* @param {Object} [threshold] Minimum distance from the top of the element to scroll at all
* @param {number} [threshold.min] Minimum distance above the element
* @param {number} [threshold.max] Minimum distance below the element
*/
FilterTagMultiselectWidget
.
prototype
.
scrollToTop
=
function
(
$element
,
marginFromTop
,
threshold
)
{
const
container
=
OO
.
ui
.
Element
.
static
.
getClosestScrollableContainer
(
$element
[
0
],
'y'
),
pos
=
OO
.
ui
.
Element
.
static
.
getRelativePosition
(
$element
,
$
(
container
)
),
containerScrollTop
=
$
(
container
).
scrollTop
(),
effectiveScrollTop
=
$
(
container
).
is
(
'body, html'
)
?
0
:
containerScrollTop
,
newScrollTop
=
effectiveScrollTop
+
pos
.
top
-
(
marginFromTop
||
0
);
// Scroll to item
if
(
threshold
===
undefined
||
(
(
threshold
.
min
===
undefined
||
newScrollTop
-
containerScrollTop
>=
threshold
.
min
)
&&
(
threshold
.
max
===
undefined
||
newScrollTop
-
containerScrollTop
<=
threshold
.
max
)
)
)
{
$
(
container
).
animate
(
{
scrollTop
:
newScrollTop
}
);
}
};
module
.
exports
=
FilterTagMultiselectWidget
;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Aug 19, 10:02 (2 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
31/4e/456b9af422fa5298602cfe2330e1
Default Alt Text
FilterTagMultiselectWidget.js (24 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment