[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 /** 2 * @class jQuery.plugin.byteLimit 3 */ 4 ( function ( $ ) { 5 6 /** 7 * Utility function to trim down a string, based on byteLimit 8 * and given a safe start position. It supports insertion anywhere 9 * in the string, so "foo" to "fobaro" if limit is 4 will result in 10 * "fobo", not "foba". Basically emulating the native maxlength by 11 * reconstructing where the insertion occurred. 12 * 13 * @private 14 * @param {string} safeVal Known value that was previously returned by this 15 * function, if none, pass empty string. 16 * @param {string} newVal New value that may have to be trimmed down. 17 * @param {number} byteLimit Number of bytes the value may be in size. 18 * @param {Function} [fn] See jQuery.byteLimit. 19 * @return {Object} 20 * @return {string} return.newVal 21 * @return {boolean} return.trimmed 22 */ 23 function trimValForByteLength( safeVal, newVal, byteLimit, fn ) { 24 var startMatches, endMatches, matchesLen, inpParts, 25 oldVal = safeVal; 26 27 // Run the hook if one was provided, but only on the length 28 // assessment. The value itself is not to be affected by the hook. 29 if ( $.byteLength( fn ? fn( newVal ) : newVal ) <= byteLimit ) { 30 // Limit was not reached, just remember the new value 31 // and let the user continue. 32 return { 33 newVal: newVal, 34 trimmed: false 35 }; 36 } 37 38 // Current input is longer than the active limit. 39 // Figure out what was added and limit the addition. 40 startMatches = 0; 41 endMatches = 0; 42 43 // It is important that we keep the search within the range of 44 // the shortest string's length. 45 // Imagine a user adds text that matches the end of the old value 46 // (e.g. "foo" -> "foofoo"). startMatches would be 3, but without 47 // limiting both searches to the shortest length, endMatches would 48 // also be 3. 49 matchesLen = Math.min( newVal.length, oldVal.length ); 50 51 // Count same characters from the left, first. 52 // (if "foo" -> "foofoo", assume addition was at the end). 53 while ( 54 startMatches < matchesLen && 55 oldVal.charAt( startMatches ) === newVal.charAt( startMatches ) 56 ) { 57 startMatches += 1; 58 } 59 60 while ( 61 endMatches < ( matchesLen - startMatches ) && 62 oldVal.charAt( oldVal.length - 1 - endMatches ) === newVal.charAt( newVal.length - 1 - endMatches ) 63 ) { 64 endMatches += 1; 65 } 66 67 inpParts = [ 68 // Same start 69 newVal.slice( 0, startMatches ), 70 // Inserted content 71 newVal.slice( startMatches, newVal.length - endMatches ), 72 // Same end 73 newVal.slice( newVal.length - endMatches ) 74 ]; 75 76 // Chop off characters from the end of the "inserted content" string 77 // until the limit is statisfied. 78 if ( fn ) { 79 // stop, when there is nothing to slice - bug 41450 80 while ( $.byteLength( fn( inpParts.join( '' ) ) ) > byteLimit && inpParts[1].length > 0 ) { 81 inpParts[1] = inpParts[1].slice( 0, -1 ); 82 } 83 } else { 84 while ( $.byteLength( inpParts.join( '' ) ) > byteLimit ) { 85 inpParts[1] = inpParts[1].slice( 0, -1 ); 86 } 87 } 88 89 newVal = inpParts.join( '' ); 90 91 return { 92 newVal: newVal, 93 trimmed: true 94 }; 95 } 96 97 var eventKeys = [ 98 'keyup.byteLimit', 99 'keydown.byteLimit', 100 'change.byteLimit', 101 'mouseup.byteLimit', 102 'cut.byteLimit', 103 'paste.byteLimit', 104 'focus.byteLimit', 105 'blur.byteLimit' 106 ].join( ' ' ); 107 108 /** 109 * Enforces a byte limit on an input field, so that UTF-8 entries are counted as well, 110 * when, for example, a database field has a byte limit rather than a character limit. 111 * Plugin rationale: Browser has native maxlength for number of characters, this plugin 112 * exists to limit number of bytes instead. 113 * 114 * Can be called with a custom limit (to use that limit instead of the maxlength attribute 115 * value), a filter function (in case the limit should apply to something other than the 116 * exact input value), or both. Order of parameters is important! 117 * 118 * @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute, 119 * called with fetched value as argument. 120 * @param {Function} [fn] Function to call on the string before assessing the length. 121 * @return {jQuery} 122 * @chainable 123 */ 124 $.fn.byteLimit = function ( limit, fn ) { 125 // If the first argument is the function, 126 // set fn to the first argument's value and ignore the second argument. 127 if ( $.isFunction( limit ) ) { 128 fn = limit; 129 limit = undefined; 130 // Either way, verify it is a function so we don't have to call 131 // isFunction again after this. 132 } else if ( !fn || !$.isFunction( fn ) ) { 133 fn = undefined; 134 } 135 136 // The following is specific to each element in the collection. 137 return this.each( function ( i, el ) { 138 var $el, elLimit, prevSafeVal; 139 140 $el = $( el ); 141 142 // If no limit was passed to byteLimit(), use the maxlength value. 143 // Can't re-use 'limit' variable because it's in the higher scope 144 // that would affect the next each() iteration as well. 145 // Note that we use attribute to read the value instead of property, 146 // because in Chrome the maxLength property by default returns the 147 // highest supported value (no indication that it is being enforced 148 // by choice). We don't want to bind all of this for some ridiculously 149 // high default number, unless it was explicitly set in the HTML. 150 // Also cast to a (primitive) number (most commonly because the maxlength 151 // attribute contains a string, but theoretically the limit parameter 152 // could be something else as well). 153 elLimit = Number( limit === undefined ? $el.attr( 'maxlength' ) : limit ); 154 155 // If there is no (valid) limit passed or found in the property, 156 // skip this. The < 0 check is required for Firefox, which returns 157 // -1 (instead of undefined) for maxLength if it is not set. 158 if ( !elLimit || elLimit < 0 ) { 159 return; 160 } 161 162 if ( fn ) { 163 // Save function for reference 164 $el.data( 'byteLimit.callback', fn ); 165 } 166 167 // Remove old event handlers (if there are any) 168 $el.off( '.byteLimit' ); 169 170 if ( fn ) { 171 // Disable the native maxLength (if there is any), because it interferes 172 // with the (differently calculated) byte limit. 173 // Aside from being differently calculated (average chars with byteLimit 174 // is lower), we also support a callback which can make it to allow longer 175 // values (e.g. count "Foo" from "User:Foo"). 176 // maxLength is a strange property. Removing or setting the property to 177 // undefined directly doesn't work. Instead, it can only be unset internally 178 // by the browser when removing the associated attribute (Firefox/Chrome). 179 // http://code.google.com/p/chromium/issues/detail?id=136004 180 $el.removeAttr( 'maxlength' ); 181 182 } else { 183 // If we don't have a callback the bytelimit can only be lower than the charlimit 184 // (that is, there are no characters less than 1 byte in size). So lets (re-)enforce 185 // the native limit for efficiency when possible (it will make the while-loop below 186 // faster by there being less left to interate over). 187 $el.attr( 'maxlength', elLimit ); 188 } 189 190 // Safe base value, used to determine the path between the previous state 191 // and the state that triggered the event handler below - and enforce the 192 // limit approppiately (e.g. don't chop from the end if text was inserted 193 // at the beginning of the string). 194 prevSafeVal = ''; 195 196 // We need to listen to after the change has already happened because we've 197 // learned that trying to guess the new value and canceling the event 198 // accordingly doesn't work because the new value is not always as simple as: 199 // oldValue + String.fromCharCode( e.which ); because of cut, paste, select-drag 200 // replacements, and custom input methods and what not. 201 // Even though we only trim input after it was changed (never prevent it), we do 202 // listen on events that input text, because there are cases where the text has 203 // changed while text is being entered and keyup/change will not be fired yet 204 // (such as holding down a single key, fires keydown, and after each keydown, 205 // we can trim the previous one). 206 // See http://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for 207 // the order and characteristics of the key events. 208 $el.on( eventKeys, function () { 209 var res = trimValForByteLength( 210 prevSafeVal, 211 this.value, 212 elLimit, 213 fn 214 ); 215 216 // Only set value property if it was trimmed, because whenever the 217 // value property is set, the browser needs to re-initiate the text context, 218 // which moves the cursor at the end the input, moving it away from wherever it was. 219 // This is a side-effect of limiting after the fact. 220 if ( res.trimmed === true ) { 221 this.value = res.newVal; 222 } 223 // Always adjust prevSafeVal to reflect the input value. Not doing this could cause 224 // trimValForByteLength to compare the new value to an empty string instead of the 225 // old value, resulting in trimming always from the end (bug 40850). 226 prevSafeVal = res.newVal; 227 } ); 228 } ); 229 }; 230 231 /** 232 * @class jQuery 233 * @mixins jQuery.plugin.byteLimit 234 */ 235 }( jQuery ) );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |