Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4199249
jquery.lengthLimit.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
jquery.lengthLimit.js
View Options
/**
* Limit string length.
*
* This module provides {@link jQuery} plugins that place different types of limits on strings.
* To use the plugins, load the module with {@link mw.loader}.
*
* For other methods for managing strings, see {@link module:mediawiki.String}.
*
* @example
* mw.loader.using( 'jquery.lengthLimit' ).then( () => {
* // Create an input that only accepts values <= 4 bytes. For example: 💪💪 is not a permitted value.
* $( '<input type="text" value="💪">' ).byteLimit( 4 ).appendTo(document.body);
* } );
*
* @module jquery.lengthLimit
*/
(
function
()
{
const
eventKeys
=
[
'keyup.lengthLimit'
,
'keydown.lengthLimit'
,
'change.lengthLimit'
,
'mouseup.lengthLimit'
,
'cut.lengthLimit'
,
'paste.lengthLimit'
,
'focus.lengthLimit'
,
'blur.lengthLimit'
].
join
(
' '
),
trimByteLength
=
require
(
'mediawiki.String'
).
trimByteLength
,
trimCodePointLength
=
require
(
'mediawiki.String'
).
trimCodePointLength
;
/**
* Utility function to trim down a string, based on byteLimit
* and given a safe start position.
*
* It supports insertion anywhere in the string, so "foo" to "fobaro" if
* limit is 4 will result in "fobo", not "foba". Basically emulating the
* native maxlength by reconstructing where the insertion occurred.
*
* @method '$.fn.trimByteLength'
* @memberof module:jquery.lengthLimit
* @deprecated Use {@link module:mediawiki.String.trimByteLength require( 'mediawiki.String' ).trimByteLength}
* instead.
* @static
* @param {string} safeVal Known value that was previously returned by this
* function, if none, pass empty string.
* @param {string} newVal New value that may have to be trimmed down.
* @param {number} byteLimit Number of bytes the value may be in size.
* @param {Function} [filterFunction] See jQuery#byteLimit.
* @return {module:mediawiki.String~StringTrimmed}
*/
mw
.
log
.
deprecate
(
$
,
'trimByteLength'
,
trimByteLength
,
'Use require( \'mediawiki.String\' ).trimByteLength instead.'
,
'$.trimByteLength'
);
function
lengthLimit
(
trimFn
,
limit
,
filterFunction
)
{
const
allowNativeMaxlength
=
trimFn
===
trimByteLength
;
// If the first argument is the function,
// set filterFunction to the first argument's value and ignore the second argument.
if
(
typeof
limit
===
'function'
)
{
filterFunction
=
limit
;
limit
=
undefined
;
// Either way, verify it is a function so we don't have to call
// isFunction again after this.
}
else
if
(
!
filterFunction
||
typeof
filterFunction
!==
'function'
)
{
filterFunction
=
undefined
;
}
// The following is specific to each element in the collection.
return
this
.
each
(
(
i
,
el
)
=>
{
const
$el
=
$
(
el
);
// If no limit was passed to lengthLimit(), use the maxlength value.
// Can't re-use 'limit' variable because it's in the higher scope
// that would affect the next each() iteration as well.
// Note that we use attribute to read the value instead of property,
// because in Chrome the maxLength property by default returns the
// highest supported value (no indication that it is being enforced
// by choice). We don't want to bind all of this for some ridiculously
// high default number, unless it was explicitly set in the HTML.
// Also cast to a (primitive) number (most commonly because the maxlength
// attribute contains a string, but theoretically the limit parameter
// could be something else as well).
const
elLimit
=
Number
(
limit
===
undefined
?
$el
.
attr
(
'maxlength'
)
:
limit
);
// If there is no (valid) limit passed or found in the property,
// skip this. The < 0 check is required for Firefox, which returns
// -1 (instead of undefined) for maxLength if it is not set.
if
(
!
elLimit
||
elLimit
<
0
)
{
return
;
}
if
(
filterFunction
)
{
// Save function for reference
$el
.
data
(
'lengthLimit.callback'
,
filterFunction
);
}
// Remove old event handlers (if there are any)
$el
.
off
(
'.lengthLimit'
);
if
(
filterFunction
||
!
allowNativeMaxlength
)
{
// Disable the native maxLength (if there is any), because it interferes
// with the (differently calculated) character/byte limit.
// Aside from being differently calculated,
// we also support a callback which can make it to allow longer
// values (e.g. count "Foo" from "User:Foo").
// maxLength is a strange property. Removing or setting the property to
// undefined directly doesn't work. Instead, it can only be unset internally
// by the browser when removing the associated attribute (Firefox/Chrome).
// https://bugs.chromium.org/p/chromium/issues/detail?id=136004
$el
.
removeAttr
(
'maxlength'
);
}
else
{
// For $.byteLimit only, if we don't have a callback,
// the byteLimit can only be lower than the native maxLength limit
// (that is, there are no characters less than 1 byte in size). So lets (re-)enforce
// the native limit for efficiency when possible (it will make the while-loop below
// faster by there being less left to interate over). This does not work for $.codePointLimit
// (code units for surrogates represent half a character each).
$el
.
attr
(
'maxlength'
,
elLimit
);
}
// Safe base value, used to determine the path between the previous state
// and the state that triggered the event handler below - and enforce the
// limit approppiately (e.g. don't chop from the end if text was inserted
// at the beginning of the string).
let
prevSafeVal
=
''
;
// We need to listen to after the change has already happened because we've
// learned that trying to guess the new value and canceling the event
// accordingly doesn't work because the new value is not always as simple as:
// oldValue + String.fromCharCode( e.which ); because of cut, paste, select-drag
// replacements, and custom input methods and what not.
// Even though we only trim input after it was changed (never prevent it), we do
// listen on events that input text, because there are cases where the text has
// changed while text is being entered and keyup/change will not be fired yet
// (such as holding down a single key, fires keydown, and after each keydown,
// we can trim the previous one).
// See https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for
// the order and characteristics of the key events.
function
enforceLimit
()
{
const
res
=
trimFn
(
prevSafeVal
,
this
.
value
,
elLimit
,
filterFunction
);
// Only set value property if it was trimmed, because whenever the
// value property is set, the browser needs to re-initiate the text context,
// which moves the cursor at the end the input, moving it away from wherever it was.
// This is a side-effect of limiting after the fact.
if
(
res
.
trimmed
===
true
)
{
this
.
value
=
res
.
newVal
;
// Trigger a 'change' event to let other scripts attached to this node know that the value
// was changed. This will also call ourselves again, but that's okay, it'll be a no-op.
$el
.
trigger
(
'change'
);
}
// Always adjust prevSafeVal to reflect the input value. Not doing this could cause
// trimFn to compare the new value to an empty string instead of the
// old value, resulting in trimming always from the end (T42850).
prevSafeVal
=
res
.
newVal
;
}
$el
.
on
(
eventKeys
,
function
(
e
)
{
if
(
e
.
type
===
'cut'
||
e
.
type
===
'paste'
)
{
// For 'cut'/'paste', the input value is only updated after the event handlers resolve.
setTimeout
(
enforceLimit
.
bind
(
this
)
);
}
else
{
enforceLimit
.
call
(
this
);
}
}
);
}
);
}
/**
* Enforces a byte limit on an input field, assuming UTF-8 encoding, for situations
* when, for example, a database field has a byte limit rather than a character limit.
* Plugin rationale: Browser has native maxlength for number of characters (technically,
* UTF-16 code units), this plugin exists to limit number of bytes instead.
*
* Can be called with a custom limit (to use that limit instead of the maxlength attribute
* value), a filter function (in case the limit should apply to something other than the
* exact input value), or both. Order of parameters is important!
*
* @memberof module:jquery.lengthLimit
* @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute,
* called with fetched value as argument.
* @param {Function} [filterFunction] Function to call on the string before assessing the length.
* @return {jQuery}
* @chainable
*/
$
.
fn
.
byteLimit
=
function
(
limit
,
filterFunction
)
{
return
lengthLimit
.
call
(
this
,
trimByteLength
,
limit
,
filterFunction
);
};
/**
* Enforces a codepoint (character) limit on an input field.
*
* For unfortunate historical reasons, browsers' native maxlength counts [the number of UTF-16
* code units rather than Unicode codepoints][1], which means that codepoints outside the Basic
* Multilingual Plane (such as many emojis) count as 2 characters each. This plugin exists to
* correct this.
*
* [1]: https://www.w3.org/TR/html5/sec-forms.html#limiting-user-input-length-the-maxlength-attribute
*
* Can be called with a custom limit (to use that limit instead of the maxlength attribute
* value), a filter function (in case the limit should apply to something other than the
* exact input value), or both. Order of parameters is important!
*
* @memberof module:jquery.lengthLimit
* @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute,
* called with fetched value as argument.
* @param {Function} [filterFunction] Function to call on the string before assessing the length.
* @return {jQuery}
* @chainable
*/
$
.
fn
.
codePointLimit
=
function
(
limit
,
filterFunction
)
{
return
lengthLimit
.
call
(
this
,
trimCodePointLength
,
limit
,
filterFunction
);
};
}()
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Aug 20 2026, 22:38 (12 w, 2 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
74/d2/ec6576f9b590ba7c6e65ed8f4d5a
Default Alt Text
jquery.lengthLimit.js (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment