Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4107537
MenuSelectWidget.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
MenuSelectWidget.js
View Options
const
FilterMenuHeaderWidget
=
require
(
'./FilterMenuHeaderWidget.js'
),
HighlightPopupWidget
=
require
(
'./HighlightPopupWidget.js'
),
FilterMenuSectionOptionWidget
=
require
(
'./FilterMenuSectionOptionWidget.js'
),
FilterMenuOptionWidget
=
require
(
'./FilterMenuOptionWidget.js'
);
/**
* A floating menu widget for the filter list.
*
* @class mw.rcfilters.ui.MenuSelectWidget
* @ignore
* @extends OO.ui.MenuSelectWidget
*
* @param {mw.rcfilters.Controller} controller Controller
* @param {mw.rcfilters.dm.FiltersViewModel} model View model
* @param {Object} [config] Configuration object
* @param {boolean} [config.isMobile] a boolean flag determining whether the menu
* should display a header or not (the header is omitted on mobile).
* @param {jQuery} [config.$overlay] A jQuery object serving as overlay for popups
* @param {Object[]} [config.footers] An array of objects defining the footers for
* this menu, with a definition whether they appear per specific views.
* The expected structure is:
* [
* {
* name: {string} A unique name for the footer object
* $element: {jQuery} A jQuery object for the content of the footer
* views: {string[]} Optional. An array stating which views this footer is
* active on. Use null or omit to display this on all views.
* }
* ]
*/
const
MenuSelectWidget
=
function
MwRcfiltersUiMenuSelectWidget
(
controller
,
model
,
config
)
{
config
=
config
||
{};
this
.
controller
=
controller
;
this
.
model
=
model
;
this
.
currentView
=
''
;
this
.
views
=
{};
this
.
userSelecting
=
false
;
this
.
menuInitialized
=
false
;
this
.
$overlay
=
config
.
$overlay
||
this
.
$element
;
this
.
$body
=
$
(
'<div>'
).
addClass
(
'mw-rcfilters-ui-menuSelectWidget-body'
);
this
.
footers
=
[];
// Parent
MenuSelectWidget
.
super
.
call
(
this
,
Object
.
assign
(
config
,
{
$autoCloseIgnore
:
this
.
$overlay
,
width
:
config
.
isMobile
?
undefined
:
650
,
// Our filtering is done through the model
filterFromInput
:
false
}
)
);
this
.
setGroupElement
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-menuSelectWidget-group'
)
);
let
header
;
if
(
!
config
.
isMobile
)
{
// When hiding the header (i.e. mobile mode) avoid problems
// with clippable and the menu's fixed width.
this
.
setClippableElement
(
this
.
$body
);
this
.
setClippableContainer
(
this
.
$element
);
header
=
new
FilterMenuHeaderWidget
(
this
.
controller
,
this
.
model
,
{
$overlay
:
this
.
$overlay
}
);
}
this
.
noResults
=
new
OO
.
ui
.
LabelWidget
(
{
label
:
mw
.
msg
(
'rcfilters-filterlist-noresults'
),
classes
:
[
'mw-rcfilters-ui-menuSelectWidget-noresults'
]
}
);
// Events
this
.
model
.
connect
(
this
,
{
initialize
:
'onModelInitialize'
,
searchChange
:
'onModelSearchChange'
}
);
// Initialization
this
.
$element
.
addClass
(
'mw-rcfilters-ui-menuSelectWidget'
)
.
attr
(
'aria-label'
,
mw
.
msg
(
'rcfilters-filterlist-title'
)
)
.
append
(
config
.
isMobile
?
undefined
:
header
.
$element
)
.
append
(
this
.
$body
.
append
(
this
.
$group
,
this
.
noResults
.
$element
)
);
// Append all footers; we will control their visibility
// based on view
config
.
footers
=
config
.
isMobile
?
[]
:
config
.
footers
||
[];
config
.
footers
.
forEach
(
(
footerData
)
=>
{
const
isSticky
=
footerData
.
sticky
===
undefined
?
true
:
!!
footerData
.
sticky
,
adjustedData
=
{
// Wrap the element with our own footer wrapper
// The following classes are used here:
// * mw-rcfilters-ui-menuSelectWidget-footer-viewSelect
// * and no others (currently)
$element
:
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-menuSelectWidget-footer'
)
.
addClass
(
'mw-rcfilters-ui-menuSelectWidget-footer-'
+
footerData
.
name
)
.
append
(
footerData
.
$element
),
views
:
footerData
.
views
};
if
(
!
footerData
.
disabled
)
{
this
.
footers
.
push
(
adjustedData
);
if
(
isSticky
)
{
this
.
$element
.
append
(
adjustedData
.
$element
);
}
else
{
this
.
$body
.
append
(
adjustedData
.
$element
);
}
}
}
);
// Switch to the correct view
this
.
updateView
();
};
/* Initialize */
OO
.
inheritClass
(
MenuSelectWidget
,
OO
.
ui
.
MenuSelectWidget
);
/* Events */
/* Methods */
MenuSelectWidget
.
prototype
.
onModelSearchChange
=
function
()
{
this
.
updateView
();
};
/**
* @inheritdoc
*/
MenuSelectWidget
.
prototype
.
toggle
=
function
(
show
)
{
this
.
lazyMenuCreation
();
MenuSelectWidget
.
super
.
prototype
.
toggle
.
call
(
this
,
show
);
// Always open this menu downwards. FilterTagMultiselectWidget scrolls it into view.
this
.
setVerticalPosition
(
'below'
);
};
/**
* lazy creation of the menu
*/
MenuSelectWidget
.
prototype
.
lazyMenuCreation
=
function
()
{
const
widget
=
this
,
items
=
[],
viewGroupCount
=
{},
groups
=
this
.
model
.
getFilterGroups
();
if
(
this
.
menuInitialized
)
{
return
;
}
this
.
menuInitialized
=
true
;
// Create shared popup for highlight buttons
this
.
highlightPopup
=
new
HighlightPopupWidget
(
this
.
controller
);
this
.
$overlay
.
append
(
this
.
highlightPopup
.
$element
);
// Count groups per view
// eslint-disable-next-line no-jquery/no-each-util
$
.
each
(
groups
,
(
groupName
,
groupModel
)
=>
{
if
(
!
groupModel
.
isHidden
()
)
{
viewGroupCount
[
groupModel
.
getView
()
]
=
viewGroupCount
[
groupModel
.
getView
()
]
||
0
;
viewGroupCount
[
groupModel
.
getView
()
]
++
;
}
}
);
// eslint-disable-next-line no-jquery/no-each-util
$
.
each
(
groups
,
(
groupName
,
groupModel
)
=>
{
const
currentItems
=
[],
view
=
groupModel
.
getView
();
if
(
!
groupModel
.
isHidden
()
)
{
if
(
viewGroupCount
[
view
]
>
1
)
{
// Only add a section header if there is more than
// one group
currentItems
.
push
(
// Group section
new
FilterMenuSectionOptionWidget
(
widget
.
controller
,
groupModel
,
{
$overlay
:
widget
.
$overlay
}
)
);
}
// Add items
widget
.
model
.
getGroupFilters
(
groupName
).
forEach
(
(
filterItem
)
=>
{
currentItems
.
push
(
new
FilterMenuOptionWidget
(
widget
.
controller
,
widget
.
model
,
widget
.
model
.
getInvertModel
(
view
),
filterItem
,
widget
.
highlightPopup
,
{
$overlay
:
widget
.
$overlay
}
)
);
}
);
// Cache the items per view, so we can switch between them
// without rebuilding the widgets each time
widget
.
views
[
view
]
=
widget
.
views
[
view
]
||
[];
widget
.
views
[
view
]
=
widget
.
views
[
view
].
concat
(
currentItems
);
items
.
push
(
...
currentItems
);
}
}
);
this
.
addItems
(
items
);
this
.
updateView
();
};
/**
* Respond to model initialize event. Populate the menu from the model
*/
MenuSelectWidget
.
prototype
.
onModelInitialize
=
function
()
{
this
.
menuInitialized
=
false
;
// Set timeout for the menu to lazy build.
setTimeout
(
this
.
lazyMenuCreation
.
bind
(
this
)
);
};
/**
* Update view
*/
MenuSelectWidget
.
prototype
.
updateView
=
function
()
{
const
viewName
=
this
.
model
.
getCurrentView
();
if
(
this
.
views
[
viewName
]
&&
this
.
currentView
!==
viewName
)
{
this
.
updateFooterVisibility
(
viewName
);
// The following classes are used here:
// * mw-rcfilters-ui-menuSelectWidget-view-default
// * mw-rcfilters-ui-menuSelectWidget-view-namespaces
// * mw-rcfilters-ui-menuSelectWidget-view-tags
this
.
$element
.
data
(
'view'
,
viewName
)
.
removeClass
(
'mw-rcfilters-ui-menuSelectWidget-view-'
+
this
.
currentView
)
.
addClass
(
'mw-rcfilters-ui-menuSelectWidget-view-'
+
viewName
);
this
.
currentView
=
viewName
;
this
.
scrollToTop
();
}
this
.
postProcessItems
();
this
.
clip
();
};
/**
* Go over the available footers and decide which should be visible
* for this view
*
* @param {string} [currentView] Current view
*/
MenuSelectWidget
.
prototype
.
updateFooterVisibility
=
function
(
currentView
)
{
currentView
=
currentView
||
this
.
model
.
getCurrentView
();
this
.
footers
.
forEach
(
(
data
)
=>
{
data
.
$element
.
toggle
(
// This footer should only be shown if it is configured
// for all views or for this specific view
!
data
.
views
||
data
.
views
.
length
===
0
||
data
.
views
.
indexOf
(
currentView
)
>
-
1
);
}
);
};
/**
* Post-process items after the visibility changed. Make sure
* that we always have an item selected, and that the no-results
* widget appears if the menu is empty.
*/
MenuSelectWidget
.
prototype
.
postProcessItems
=
function
()
{
let
itemWasSelected
=
false
;
const
items
=
this
.
getItems
();
// If we are not already selecting an item, always make sure
// that the top item is selected
if
(
!
this
.
userSelecting
)
{
// Select the first item in the list
for
(
let
i
=
0
;
i
<
items
.
length
;
i
++
)
{
if
(
!
(
items
[
i
]
instanceof
OO
.
ui
.
MenuSectionOptionWidget
)
&&
items
[
i
].
isVisible
()
)
{
itemWasSelected
=
true
;
this
.
selectItem
(
items
[
i
]
);
break
;
}
}
if
(
!
itemWasSelected
)
{
this
.
selectItem
(
null
);
}
}
this
.
noResults
.
toggle
(
!
this
.
getItems
().
some
(
(
item
)
=>
item
.
isVisible
()
)
);
};
/**
* Get the option widget that matches the model given
*
* @ignore
* @param {mw.rcfilters.dm.ItemModel} model Item model
* @return {mw.rcfilters.ui.ItemMenuOptionWidget} Option widget
*/
MenuSelectWidget
.
prototype
.
getItemFromModel
=
function
(
model
)
{
this
.
lazyMenuCreation
();
return
this
.
views
[
model
.
getGroupModel
().
getView
()
].
filter
(
(
item
)
=>
item
.
getName
()
===
model
.
getName
()
)[
0
];
};
/**
* @inheritdoc
*/
MenuSelectWidget
.
prototype
.
onDocumentKeyDown
=
function
(
e
)
{
const
currentItem
=
this
.
findHighlightedItem
()
||
this
.
findSelectedItem
();
let
nextItem
;
// Call parent
MenuSelectWidget
.
super
.
prototype
.
onDocumentKeyDown
.
call
(
this
,
e
);
// We want to select the item on arrow movement
// rather than just highlight it, like the menu
// does by default
if
(
!
this
.
isDisabled
()
&&
this
.
isVisible
()
)
{
switch
(
e
.
keyCode
)
{
case
OO
.
ui
.
Keys
.
UP
:
case
OO
.
ui
.
Keys
.
LEFT
:
// Get the next item
nextItem
=
this
.
findRelativeSelectableItem
(
currentItem
,
-
1
);
break
;
case
OO
.
ui
.
Keys
.
DOWN
:
case
OO
.
ui
.
Keys
.
RIGHT
:
// Get the next item
nextItem
=
this
.
findRelativeSelectableItem
(
currentItem
,
1
);
break
;
}
nextItem
=
nextItem
&&
nextItem
.
constructor
.
static
.
selectable
?
nextItem
:
null
;
// Select the next item
this
.
selectItem
(
nextItem
);
}
};
/**
* Scroll to the top of the menu
*/
MenuSelectWidget
.
prototype
.
scrollToTop
=
function
()
{
this
.
$body
.
scrollTop
(
0
);
};
/**
* Set whether the user is currently selecting an item.
* This is important when the user selects an item that is in between
* different views, and makes sure we do not re-select a different
* item (like the item on top) when this is happening.
*
* @param {boolean} isSelecting User is selecting
*/
MenuSelectWidget
.
prototype
.
setUserSelecting
=
function
(
isSelecting
)
{
this
.
userSelecting
=
!!
isSelecting
;
};
module
.
exports
=
MenuSelectWidget
;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Aug 18, 20:06 (2 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
9b/c7/5b1f9d7dbc52a814e7dee5d1e140
Default Alt Text
MenuSelectWidget.js (10 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment