Daniel Werner has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/49999


Change subject: Moved jquery.inputAutoExpand module from WikibaseLib extension
......................................................................

Moved jquery.inputAutoExpand module from WikibaseLib extension

Also moved tests into new test module.

Change-Id: I21530780db401e8488d60a014161ab1629955780
---
R DataTypes/resources/jquery/jquery.eachchange.js
A DataTypes/resources/jquery/jquery.inputAutoExpand.js
R DataTypes/tests/qunit/jquery/jquery.eachchange.tests.js
3 files changed, 554 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DataValues 
refs/changes/99/49999/1

diff --git a/DataTypes/resources/jquery.eachchange.js 
b/DataTypes/resources/jquery/jquery.eachchange.js
similarity index 100%
rename from DataTypes/resources/jquery.eachchange.js
rename to DataTypes/resources/jquery/jquery.eachchange.js
diff --git a/DataTypes/resources/jquery/jquery.inputAutoExpand.js 
b/DataTypes/resources/jquery/jquery.inputAutoExpand.js
new file mode 100644
index 0000000..7c06df2
--- /dev/null
+++ b/DataTypes/resources/jquery/jquery.inputAutoExpand.js
@@ -0,0 +1,554 @@
+/**
+ * Makes input or textarea elements automatically expand/contract their size 
according to their
+ * input while typing. Vertical resizing will of course work for textareas 
only.
+ *
+ * Based on autoGrowInput plugin by James Padolsey
+ * (see: 
http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields)
+ * and Autosize plugin by Jack Moore (license: MIT)
+ * (see: http://www.jacklmoore.com/autosize)
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Werner < daniel.wer...@wikimedia.de >
+ * @author H. Snater < mediawiki at snater.com >
+ *
+ * @since 0.1 (moved from WikibaseLib 0.4 alpha)
+ *
+ * @example $( 'input' ).inputAutoExpand();
+ * @desc Enhances an input element with horiontal auto-expanding functionality.
+ *
+ * @example $( 'textarea' ).inputAutoExpand( { expandWidth: false, 
expandHeight: true } );
+ * @desc Enhances an input element with vertical auto-expanding functionality.
+ *
+ * @option expandWidth {Boolean} Whether to horizontally expand/contract the 
input element.
+ *         default: true
+ *
+ * @option expandHeight {Boolean} Whether to vertically expand/contract the 
input element.
+ *         default: false
+ *
+ * @option maxWidth {Number} Maximum width the input element may stretch.
+ *         default: 1000
+ *
+ * @option minWidth {Number} Minimum width. If not set or false, the space 
required by the input
+ *         elements placeholder text will be determined automatically (taking 
placeholder into
+ *         account).
+ *         default: false
+ *
+ * @option maxHeigth {Number} Maximum height the input element may stretch. 
Set to false for not
+ *         constraining the height to a  maximum.
+ *         default: false
+ *
+ * @option minHeight {Number} Minimum height. Set to false for not 
constraining the height to a
+ *         minimum.
+ *         default: false
+ *
+ * @option comfortZone {Number} White space behind the input text. If set to 
false, an
+ *         appropriate amount of space will be calculated automatically.
+ *         default: false
+ *
+ * @option expandOnResize {Boolean} Whether width should be re-calculated when 
the browser
+ *         window has been resized.
+ *         default: true
+ *
+ * @option suppressNewLine {Boolean} Whether to suppress new-line characters.
+ *         default: false
+ *
+ * @dependency jquery.eachchange
+ *
+ * @todo Make expandWidth and expandHeight work simultaneously.
+ * @todo Destroy mechanism
+ */
+( function( $ ) {
+       'use strict';
+
+       /**
+        * Tests if the user client is capable of assigning a height of 0 to a 
textarea. (E.g. Firefox
+        * on Mac will always set the minimum height to the text height as long 
as the textarea is
+        * attached to the body element.)
+        *
+        * @return {boolean}
+        */
+       function supports0Height() {
+               var support = true,
+                       $t = $( '<textarea/>' );
+
+               $t.attr( 'style', 'height: 0 !important; width: 0 !important; 
top:-9999px; left: -9999px;' )
+               .text( 'text' )
+               .appendTo( $( 'body' ) );
+
+               if( $t.height() >= 1 ) { // addressing rounding
+                       support = false;
+               }
+               $t.remove();
+
+               return support;
+       }
+
+       /**
+        * Whether the user client is capable of setting the textarea height to 
0.
+        * @type {boolean}
+        */
+       var browserSupports0Height;
+
+       $( document ).ready( function() {
+               browserSupports0Height = supports0Height();
+       } );
+
+
+       $.fn.inputAutoExpand = function( options ) {
+               if( ! options ) {
+                       options = {};
+               }
+
+               // inject default options for missing ones:
+               var fullOptions = $.extend( {
+                       expandWidth: true,
+                       expandHeight: false,
+                       maxWidth: 1000,
+                       minWidth: false,
+                       maxHeight: false,
+                       minHeight: false,
+                       comfortZone: false,
+                       expandOnResize: true,
+                       suppressNewLine: false
+               }, options );
+
+               // expand input fields:
+               this.filter( 'input:text, textarea' ).each( function() {
+                       var input = $( this );
+                       var inputAE = input.data( 'AutoExpandInput' );
+
+                       if( inputAE ) {
+                               // AutoExpand initialized already, update 
options only (will also expand)
+                               if( options ) {
+                                       inputAE.setOptions( options ); // also 
triggers re-calculation of width
+                               } else {
+                                       inputAE.expand();
+                               }
+
+                       } else {
+                               // initialize new auto expand:
+                               var autoExpandInput = new AutoExpandInput( 
this, fullOptions );
+                               $( this ).data( 'AutoExpandInput', 
autoExpandInput );
+                       }
+               } );
+
+               return this;
+       };
+
+       /**
+        * Prototype for auto expanding input elements.
+        * @constructor
+        *
+        * @param inputElem
+        * @param options
+        */
+       var AutoExpandInput = function( inputElem, options ) {
+               this.input = $( inputElem );
+               this._o = options;
+               this._active = false;
+
+               var self = this;
+
+               var domCheck = function() {
+                       return !!self.input.closest( 'html' ).length; // false 
if input is not in DOM
+               };
+
+               if( ! domCheck() ) {
+                       // use timeout till input is in DOM. This might not be 
the prettiest way but seems
+                       // necessary in some situations.
+                       window.setTimeout( function() {
+                               if( domCheck() ) {
+                                       window.clearTimeout( this );
+                                       self.expand();
+                               }
+                       }, 10 );
+               }
+
+               // set width on all important related events:
+               $( this.input )
+               .eachchange( function( e, oldValue ) {
+                       // NOTE/FIXME: won't be triggered if placeholder has 
changed (via JS) but not input text
+
+                       // Got to check on each change since value might have 
been pasted or dragged into the
+                       // input element
+                       if ( self._o.suppressNewLine ) {
+                               self.input.val( self.input.val().replace( 
/\r?\n/g, '' ) );
+                       }
+                       self.expand();
+               } );
+
+               var rulers = AutoExpandInput.initRulers();
+               this.$rulerX = rulers[0];
+               this.$rulerY = rulers[1];
+
+               this.expand(); // calculate width initially
+
+               // do not show any resize handle for manual resizing
+               this.input.css( 'resize', 'none' );
+
+               // make sure box will consider window size after resize:
+               AutoExpandInput.activateResizeHandler();
+       };
+
+       AutoExpandInput.initRulers = function() {
+               var $rulerX = $( '#AutoExpandInput_rulerX' );
+               if ( !$rulerX.length ) {
+                       $rulerX = $( '<div/>' )
+                               .attr( 'id', 'AutoExpandInput_rulerX' )
+                               .css( {
+                                       width: 'auto',
+                                       whiteSpace: 'nowrap',
+                                       position: 'absolute',
+                                       top: '-9999px',
+                                       left: '-9999px',
+                                       visibility: 'hidden',
+                                       display: 'none'
+                               } )
+                               .appendTo( 'body' );
+               }
+
+               var $rulerY = $( '#AutoExpandInput_rulerY' );
+               if ( !$rulerY.length ) {
+                       $rulerY = $( '<textarea style="minHeight: 0!important; 
height: 0!important;"/>' )
+                               .attr( 'id', 'AutoExpandInput_rulerY' )
+                               .attr( 'tabindex', '-1' )
+                               .css( {
+                                       position: 'absolute',
+                                       top: '-9999px',
+                                       left: '-9999px',
+                                       right: 'auto',
+                                       bottom: 'auto',
+                                       wordWrap: 'break-word'
+                               } )
+                               .appendTo( 'body' );
+               }
+
+               return [$rulerX, $rulerY];
+       };
+
+       /**
+        * Once called, this will make sure AutoExpandInput's will adjust on 
resize. When called for a
+        * second time the resize handler will not be initialized again.
+        *
+        * @return bool false if the handler was active before.
+        */
+       AutoExpandInput.activateResizeHandler = function() {
+               if( AutoExpandInput.activateResizeHandler.active ) {
+                       return false; // don't initialize this more than once
+               }
+               AutoExpandInput.activateResizeHandler.active = true;
+
+               ( function() {
+                       var oldWidth; // width before resize
+                       var resizeHandler = function() {
+                               var newWidth = $( this ).width();
+
+                               if( oldWidth === newWidth ) {
+                                       // no change in horizontal width after 
resize
+                                       return;
+                               }
+
+                               $.each( AutoExpandInput.getActiveInstances(), 
function() {
+                                       // NOTE: this could be more efficient 
in case many inputs are set. We could just
+                                       //  calculate the inputs (new) 
max-width and check whether it is exceeded in
+                                       //  which case we set it to the max 
width. Basically the same but other way
+                                       //  around for minWidth.
+                                       if( this.getOptions().expandOnResize ) {
+                                               this.expand();
+                                       }
+                               } );
+
+                               oldWidth = newWidth;
+                       };
+
+                       $( window ).on( 'resize', resizeHandler );
+               }() );
+
+               return true;
+       };
+
+       /**
+        * Returns all active instances whose related input is still in the DOM
+        *
+        * @return AutoExpandInput[]
+        */
+       AutoExpandInput.getActiveInstances = function() {
+               var instances = [];
+
+               // get all AutoExpandInput by checking input $.data().
+               // If $.remove() was called, the data was removed!
+               $( 'input' ).each( function() {
+                       var instance = $.data( this, 'AutoExpandInput' );
+                       if( instance ) {
+                               instances.push( instance );
+                       }
+               } );
+
+               return instances;
+       };
+
+       AutoExpandInput.prototype = {
+               /**
+                * sets the input boxes width to fit the boxes content.
+                *
+                * @return number how much the input with grew. If the value is 
negative, it shrank.
+                */
+               expand: function() {
+
+                       if ( this._o.expandWidth ) {
+                               this.copyStyles( this.$rulerX );
+                       }
+                       if ( this._o.expandHeight ) {
+                               this.copyStyles( this.$rulerY );
+                       }
+
+                       if ( this._o.expandWidth ) {
+                               var minWidth = this.getMinWidth(),
+                                       maxWidth = this.getMaxWidth(),
+                                       comfortZone = this.getComfortZone();
+
+                               // give min width higher priority than max 
width:
+                               maxWidth = ( maxWidth > minWidth ) ? maxWidth : 
minWidth;
+
+                               //console.log( '=== START EXPANSION ===' );
+                               //console.log( 'min: ' + minWidth + ' | max: ' 
+ maxWidth + ' | comfort: ' + comfortZone );
+
+                               var val = this.input.val();
+                               var valWidth = this.getWidthFor( val ); // pure 
width of the input, without additional calculation
+
+                               //console.log( 'valWidth: ' + valWidth + ' | 
val: ' + val );
+
+                               // add comfort zone or take min-width if too 
short
+                               var newWidth = ( valWidth + comfortZone ) > 
minWidth
+                                               ? valWidth + comfortZone
+                                               : minWidth,
+                                       oldWidth = this.getWidth();
+
+                               if( newWidth >= maxWidth  ) {
+                                       // NOTE: check for this in all cases, 
FF had some bug not returning false for
+                                       //  isValidWidthChange due to some 
floating point issues apparently make sure
+                                       //  we set the width if the content is 
too long from the start.
+                                       this.input.width( maxWidth );
+                                       //console.log( 'set to max width!' );
+                               }
+                               else {
+                                       // Calculate new width + whether to 
change
+                                       var isValidWidthChange =
+                                                       ( newWidth < oldWidth 
&& newWidth >= minWidth )
+                                                       || ( newWidth >= 
minWidth && newWidth < maxWidth );
+
+                                       //console.log( 'newWidth: ' + newWidth 
+ ' | oldWidth: ' + oldWidth
+                                       //      + ' | isValidChange: ' + ( 
isValidWidthChange ? 'true' : 'false' ) );
+
+                                       // Animate width
+                                       if( isValidWidthChange ) {
+                                               this.input.width( newWidth );
+                                               //console.log( 'set to 
calculated width!' );
+                                       }
+                               }
+
+                               //console.log( '=== END EXPANSION (' + ( 
this.getWidth() - oldWidth ) + ') ===' );
+
+                               // return change
+                               return this.getWidth() - oldWidth;
+                       }
+                       if ( this._o.expandHeight ) {
+                               var valHeight = this.getHeightFor( 
this.input.val() ),
+                                       input = this.input[0],
+                                       minHeight = this._o.minHeight || 0, // 
will keep one line in any case
+                                       maxHeight = this._o.maxHeight,
+                                       oldHeight = this.input.height();
+
+                               if ( maxHeight && valHeight > maxHeight ) {
+                                       input.style.height = maxHeight + 'px';
+                                       input.style.overflow = 'scroll';
+                               } else {
+                                       if ( minHeight && valHeight < minHeight 
) {
+                                               input.style.height = minHeight 
+ 'px';
+                                       } else {
+                                               input.style.height = ( !isNaN( 
valHeight ) ? valHeight : 0 ) + 'px';
+                                       }
+                                       this.input.css( 'overflow', 'hidden' );
+                               }
+                               return valHeight - oldHeight;
+                       }
+               },
+
+               /**
+                * Copy styles that affect spacing from the original element to 
the element used to measure
+                * any size change.
+                *
+                * @param {jQuery} $to Element used to determine the size change
+                */
+               copyStyles: function( $to ) {
+                       // line-height is omitted because IE7/IE8 doesn't 
return the correct value.
+                       var $input = this.input,
+                               stylesToCopy = [
+                                       'fontFamily',
+                                       'fontSize',
+                                       'fontWeight',
+                                       'fontStyle',
+                                       'letterSpacing',
+                                       'textTransform',
+                                       'wordSpacing',
+                                       'textIndent',
+                                       'overflowY'
+                               ];
+
+                       // test that line-height can be accurately copied to 
avoid
+                       // incorrect value reporting in old IE and old Opera
+                       $to.css( 'lineHeight', '99px' );
+                       if ( $to.css( 'lineHeight' ) === '99px' ) {
+                               stylesToCopy.push( 'lineHeight' );
+                       }
+
+                       $.each( stylesToCopy, function( i, styleName ) {
+                               $to.css( styleName, $input.css( styleName ) );
+                       } );
+
+                       // styles not being influenced by copying styles
+                       $to.css( {
+                               overflow: 'hidden',
+                               overflowY: 'hidden',
+                               wordWrap: 'break-word'
+                       } );
+               },
+
+               /**
+                * Calculates the width which would be required for the input 
field if the given text were
+                * inserted. This does not consider the comfort zone given in 
the options and doesn't check
+                * for min/max width restraints.
+                *
+                * @param {String} text
+                * @return {String}
+                */
+               getWidthFor: function( text ) {
+                       var input = this.input,
+                               ruler = this.$rulerX;
+
+                       ruler.html( text // escape stuff
+                               .replace(/&/g, '&amp;')
+                               .replace(/</g, '&lt;')
+                               .replace(/>/g, '&gt;')
+                               .replace(/\s/g,'&nbsp;')
+                       );
+
+                       return ruler.width();
+               },
+
+               /**
+                * Calculates the height the given text would require to not 
show any scrollbar within the
+                * input element.
+                *
+                * @param {String} text
+                */
+               getHeightFor: function( text ) {
+                       var active = this._active;
+
+                       if ( !active ) {
+                               active = true;
+
+                               var input = this.input[0],
+                                       ruler = this.$rulerY[0];
+
+                               ruler.value = text;
+
+                               // Update the width in case the original 
textarea width has changed
+                               ruler.style.width = this.input.width() + 'px';
+
+                               // Needed for IE to reliably return the correct 
scrollHeight
+                               ruler.scrollTop = 0;
+
+                               // Set a very high value for scrollTop to be 
sure the
+                               // mirror is scrolled all the way to the bottom.
+                               ruler.scrollTop = 9e4;
+
+                               // This small timeout gives IE a chance to draw 
its scrollbar
+                               // before adjust can be run again (prevents an 
infinite loop).
+                               setTimeout( function () {
+                                       active = false;
+                               }, 10 );
+
+                               var border = parseInt( this.input.css( 
'borderTopWidth' ), 10 )
+                                       + parseInt( this.input.css( 
'borderBottomWidth' ), 10 );
+
+                               return ( browserSupports0Height )
+                                       ? ruler.scrollTop + border
+                                       : ruler.scrollTop + border + 
ruler.clientHeight;
+                       }
+               },
+
+               /**
+                * Returns the current width.
+                *
+                * @return number
+                */
+               getWidth: function() {
+                       return this._normalizeWidth( this.input.width() );
+               },
+
+               getMaxWidth: function() {
+                       return this._normalizeWidth( this._o.maxWidth );
+               },
+
+               getMinWidth: function() {
+                       var width = this._o.minWidth;
+
+                       if( width === false ) {
+                               // dynamic min width, depending on 
placeholder...
+                               // always calculate in case placeholder changes!
+                               if( ! this.input.attr( 'placeholder' ) ) {
+                                       return 0; // ... or 0 if no placeholder
+                               }
+                               // don't need comfort zone in this case just 
some sane space
+                               width = this.getWidthFor( this.input.attr( 
'placeholder' ) + ' ' );
+                       }
+                       return this._normalizeWidth( width );
+               },
+
+               getComfortZone: function() {
+                       var width = this._o.comfortZone;
+                       if( width === false ) {
+                               // this is much faster for getting a good 
estimation for the perfect comfort zone
+                               // compared to the method where we did 
"this.getWidthFor( '@%_MW' ) / 5 * 1.25;"
+                               width = this.input.height();
+                       }
+                       return this._normalizeWidth( width );
+               },
+
+               /**
+                * Normalizes the width, allowing integers as well as objects 
to get their current width as
+                * return value. This can also be a callback to return the 
value.
+                *
+                * @param number|function|jQuery width
+                * @param jQuery elem
+                * @return number
+                */
+               _normalizeWidth: function( width ) {
+                       if( $.isFunction( width ) ) {
+                               return width.call( this );
+                       }
+                       if( width instanceof $ ) {
+                               width = width.width();
+                       }
+                       // round it up to avoid issues where we can't round 
down because it wouldn't fit
+                       return Math.ceil( width );
+               },
+
+               getOptions: function() {
+                       return this._o;
+               },
+
+               /**
+                * Updates the options of the object. After the options are 
set, expand() will be called.
+                *
+                * @param Array options one or more options which will extend 
the current options.
+                */
+               setOptions: function( options ) {
+                       this._o = $.extend( this._o, options );
+                       this.expand();
+               }
+
+       };
+
+}( jQuery ) );
diff --git a/DataTypes/tests/qunit/jquery.eachchange.tests.js 
b/DataTypes/tests/qunit/jquery/jquery.eachchange.tests.js
similarity index 100%
rename from DataTypes/tests/qunit/jquery.eachchange.tests.js
rename to DataTypes/tests/qunit/jquery/jquery.eachchange.tests.js

-- 
To view, visit https://gerrit.wikimedia.org/r/49999
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I21530780db401e8488d60a014161ab1629955780
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/DataValues
Gerrit-Branch: master
Gerrit-Owner: Daniel Werner <daniel.wer...@wikimedia.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to