Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1431848
cond-state.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
cond-state.js
View Options
/*
* HTMLForm enhancements:
* Set up 'hide-if' and 'disable-if' behaviors for form fields that have them.
*/
/**
* Helper function for conditional states to find the nearby form field.
*
* Find the closest match for the given name, "closest" being the minimum
* level of parents to go to find a form field matching the given name or
* ending in array keys matching the given name (e.g. "baz" matches
* "foo[bar][baz]").
*
* @ignore
* @private
* @param {jQuery} $root
* @param {string} name
* @return {jQuery|null}
*/
function
conditionGetField
(
$root
,
name
)
{
const
nameFilter
=
function
()
{
return
this
.
name
===
name
;
};
let
$found
=
$root
.
find
(
'[name]'
).
filter
(
nameFilter
);
if
(
!
$found
.
length
)
{
// Field cloner can load from template dynamically and fire event on sub element
$found
=
$root
.
closest
(
'form'
).
find
(
'[name]'
).
filter
(
nameFilter
);
}
return
$found
.
length
?
$found
:
null
;
}
/**
* Helper function to get the OOUI widget containing the given field, if any.
*
* @ignore
* @private
* @param {jQuery} $field
* @return {OO.ui.Widget|null}
*/
function
getWidget
(
$field
)
{
const
$widget
=
$field
.
closest
(
'.oo-ui-widget[data-ooui]'
);
if
(
$widget
.
length
)
{
return
OO
.
ui
.
Widget
.
static
.
infuse
(
$widget
);
}
return
null
;
}
/**
* Helper function for conditional states to return a test function and list of
* dependent fields for a conditional states specification.
*
* @ignore
* @private
* @param {jQuery} $root
* @param {Array} spec
* @return {Array}
* @return {Array} return.0 Dependent fields, array of jQuery objects
* @return {Function} return.1 Test function
*/
function
conditionParse
(
$root
,
spec
)
{
let
v
,
fields
,
func
;
const
op
=
spec
[
0
];
let
l
=
spec
.
length
;
switch
(
op
)
{
case
'AND'
:
case
'OR'
:
case
'NAND'
:
case
'NOR'
:
{
const
funcs
=
[];
fields
=
[];
for
(
let
i
=
1
;
i
<
l
;
i
++
)
{
if
(
!
Array
.
isArray
(
spec
[
i
]
)
)
{
throw
new
Error
(
op
+
' parameters must be arrays'
);
}
v
=
conditionParse
(
$root
,
spec
[
i
]
);
fields
=
fields
.
concat
(
v
[
0
]
);
funcs
.
push
(
v
[
1
]
);
}
l
=
funcs
.
length
;
const
valueChk
=
{
AND
:
false
,
OR
:
true
,
NAND
:
false
,
NOR
:
true
};
const
valueRet
=
{
AND
:
true
,
OR
:
false
,
NAND
:
false
,
NOR
:
true
};
func
=
function
()
{
for
(
let
j
=
0
;
j
<
l
;
j
++
)
{
if
(
valueChk
[
op
]
===
funcs
[
j
]()
)
{
return
!
valueRet
[
op
];
}
}
return
valueRet
[
op
];
};
return
[
fields
,
func
];
}
case
'NOT'
:
if
(
l
!==
2
)
{
throw
new
Error
(
'NOT takes exactly one parameter'
);
}
if
(
!
Array
.
isArray
(
spec
[
1
]
)
)
{
throw
new
Error
(
'NOT parameters must be arrays'
);
}
v
=
conditionParse
(
$root
,
spec
[
1
]
);
fields
=
v
[
0
];
func
=
v
[
1
];
return
[
fields
,
function
()
{
return
!
func
();
}
];
case
'==='
:
case
'!=='
:
{
if
(
l
!==
3
)
{
throw
new
Error
(
op
+
' takes exactly two parameters'
);
}
const
$field
=
conditionGetField
(
$root
,
spec
[
1
]
);
if
(
!
$field
)
{
return
[
[],
function
()
{
return
false
;
}
];
}
v
=
spec
[
2
];
let
widget
;
const
getVal
=
function
()
{
// When the value is requested for the first time,
// determine if we need to treat this field as a OOUI widget.
if
(
widget
===
undefined
)
{
widget
=
getWidget
(
$field
);
}
if
(
widget
)
{
if
(
widget
.
supports
(
'isSelected'
)
)
{
const
selected
=
widget
.
isSelected
();
return
selected
?
widget
.
getValue
()
:
''
;
}
else
{
return
widget
.
getValue
();
}
}
else
{
if
(
$field
.
prop
(
'type'
)
===
'radio'
||
$field
.
prop
(
'type'
)
===
'checkbox'
)
{
const
$selected
=
$field
.
filter
(
':checked'
);
return
$selected
.
length
?
$selected
.
val
()
:
''
;
}
else
{
return
$field
.
val
();
}
}
};
switch
(
op
)
{
case
'==='
:
func
=
function
()
{
return
getVal
()
===
v
;
};
break
;
case
'!=='
:
func
=
function
()
{
return
getVal
()
!==
v
;
};
break
;
}
return
[
[
$field
],
func
];
}
default
:
throw
new
Error
(
'Unrecognized operation \''
+
op
+
'\''
);
}
}
/**
* Helper function to get the list of ResourceLoader modules needed to infuse the OOUI widgets
* containing the given fields.
*
* @ignore
* @private
* @param {jQuery} $fields
* @return {string[]}
*/
function
gatherOOUIModules
(
$fields
)
{
const
$oouiFields
=
$fields
.
filter
(
'[data-ooui]'
);
const
modules
=
[];
if
(
$oouiFields
.
length
)
{
modules
.
push
(
'mediawiki.htmlform.ooui'
);
$oouiFields
.
each
(
function
()
{
const
data
=
$
(
this
).
data
(
'mw-modules'
);
if
(
data
)
{
// We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
const
extraModules
=
data
.
split
(
','
);
modules
.
push
(
...
extraModules
);
}
}
);
}
return
modules
;
}
mw
.
hook
(
'htmlform.enhance'
).
add
(
(
$root
)
=>
{
const
$exclude
=
$root
.
find
(
'.mw-htmlform-autoinfuse-lazy'
)
.
find
(
'.mw-htmlform-hide-if, .mw-htmlform-disable-if'
);
const
$fields
=
$root
.
find
(
'.mw-htmlform-hide-if, .mw-htmlform-disable-if'
).
not
(
$exclude
);
// Load modules for the fields we will hide/disable
mw
.
loader
.
using
(
gatherOOUIModules
(
$fields
)
).
done
(
()
=>
{
$fields
.
each
(
function
()
{
const
$el
=
$
(
this
);
let
spec
,
$elOrLayout
,
$form
;
if
(
$el
.
is
(
'[data-ooui]'
)
)
{
// $elOrLayout should be a FieldLayout that mixes in mw.htmlform.Element
$elOrLayout
=
OO
.
ui
.
FieldLayout
.
static
.
infuse
(
$el
);
$form
=
$elOrLayout
.
$element
.
closest
(
'form'
);
spec
=
$elOrLayout
.
condState
;
}
else
{
$elOrLayout
=
$el
;
$form
=
$el
.
closest
(
'form'
);
spec
=
$el
.
data
(
'condState'
);
}
if
(
!
spec
)
{
return
;
}
let
fields
=
[];
const
test
=
{};
[
'hide'
,
'disable'
].
forEach
(
(
type
)
=>
{
if
(
spec
[
type
]
)
{
const
v
=
conditionParse
(
$form
,
spec
[
type
]
);
fields
=
fields
.
concat
(
fields
,
v
[
0
]
);
test
[
type
]
=
v
[
1
];
}
}
);
const
func
=
function
()
{
const
shouldHide
=
spec
.
hide
?
test
.
hide
()
:
false
;
const
shouldDisable
=
shouldHide
||
(
spec
.
disable
?
test
.
disable
()
:
false
);
if
(
spec
.
hide
)
{
// Remove server-side CSS class that hides the elements, and re-compute the state
if
(
$elOrLayout
instanceof
$
)
{
$elOrLayout
.
removeClass
(
'mw-htmlform-hide-if-hidden'
);
}
else
{
$elOrLayout
.
$element
.
removeClass
(
'mw-htmlform-hide-if-hidden'
);
}
// The .toggle() method works mostly the same for jQuery objects and OO.ui.Widget
$elOrLayout
.
toggle
(
!
shouldHide
);
}
// Disable fields with either 'disable-if' or 'hide-if' rules
// Hidden fields should be disabled to avoid users meet validation failure on these fields,
// because disabled fields will not be submitted with the form.
if
(
$elOrLayout
instanceof
$
)
{
// This also finds elements inside any nested fields (in case of HTMLFormFieldCloner),
// which is problematic. But it works because:
// * HTMLFormFieldCloner::createFieldsForKey() copies '*-if' rules to nested fields
// * jQuery collections like $fields are in document order, so we register event
// handlers for parents first
// * Event handlers are fired in the order they were registered, so even if the handler
// for parent messed up the child, the handle for child will run next and fix it
$elOrLayout
.
find
(
'input, textarea, select'
).
each
(
function
()
{
const
$this
=
$
(
this
);
if
(
shouldDisable
)
{
if
(
$this
.
data
(
'was-disabled'
)
===
undefined
)
{
$this
.
data
(
'was-disabled'
,
$this
.
prop
(
'disabled'
)
);
}
$this
.
prop
(
'disabled'
,
true
);
}
else
{
$this
.
prop
(
'disabled'
,
$this
.
data
(
'was-disabled'
)
);
}
}
);
}
else
{
// $elOrLayout is a OO.ui.FieldLayout
if
(
shouldDisable
)
{
if
(
$elOrLayout
.
wasDisabled
===
undefined
)
{
$elOrLayout
.
wasDisabled
=
$elOrLayout
.
fieldWidget
.
isDisabled
();
}
$elOrLayout
.
fieldWidget
.
setDisabled
(
true
);
}
else
if
(
$elOrLayout
.
wasDisabled
!==
undefined
)
{
$elOrLayout
.
fieldWidget
.
setDisabled
(
$elOrLayout
.
wasDisabled
);
}
}
};
const
oouiNodes
=
fields
.
map
(
// We expect undefined for non-OOUI nodes (T308626)
(
$node
)
=>
$node
.
closest
(
'.oo-ui-fieldLayout[data-ooui]'
)[
0
]
).
filter
(
// Remove undefined
(
node
)
=>
!!
node
);
// Load modules for the fields whose state we will check
mw
.
loader
.
using
(
gatherOOUIModules
(
$
(
oouiNodes
)
)
).
done
(
()
=>
{
for
(
let
i
=
0
;
i
<
fields
.
length
;
i
++
)
{
const
widget
=
getWidget
(
fields
[
i
]
);
if
(
widget
)
{
fields
[
i
]
=
widget
;
}
// The .on() method works mostly the same for jQuery objects and OO.ui.Widget
fields
[
i
].
on
(
'change'
,
func
);
}
func
();
}
);
}
);
}
);
}
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, May 16, 21:08 (1 d, 20 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
73/82/6645a084e8daa5bba3a48d980f43
Default Alt Text
cond-state.js (8 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment