[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/resources/src/jquery/ -> jquery.byteLimit.js (source)

   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 ) );


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1