Henning Snater has uploaded a new change for review. https://gerrit.wikimedia.org/r/74116
Change subject: Split off adaptLetterCase and autocompleteString from suggester ...................................................................... Split off adaptLetterCase and autocompleteString from suggester Change-Id: I83d751ca22f2b0579d21849233da011417b92e5d --- M ValueView/ValueView.resources.mw.php M ValueView/ValueView.tests.qunit.php M ValueView/resources/jquery.ui/jquery.ui.suggester.js A ValueView/resources/jquery.util/jquery.util.adaptlettercase.js A ValueView/resources/jquery/jquery.autocompletestring.js M ValueView/tests/qunit/jquery.ui/jquery.ui.suggester.tests.js A ValueView/tests/qunit/jquery.util/jquery.util.adaptlettercase.tests.js A ValueView/tests/qunit/jquery/jquery.autocompletestring.tests.js 8 files changed, 306 insertions(+), 158 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DataValues refs/changes/16/74116/1 diff --git a/ValueView/ValueView.resources.mw.php b/ValueView/ValueView.resources.mw.php index d135403..d29a412 100644 --- a/ValueView/ValueView.resources.mw.php +++ b/ValueView/ValueView.resources.mw.php @@ -86,7 +86,10 @@ 'jquery.ui/jquery.ui.suggester.css' ), 'dependencies' => array( - 'jquery.ui.autocomplete' + 'jquery.autocompletestring', + 'jquery.ui.autocomplete', + 'jquery.ui.widget', + 'jquery.util.adaptlettercase', ) ), @@ -127,7 +130,6 @@ ), ), - 'jquery.ui.listrotator' => $moduleTemplate + array( 'scripts' => array( 'jquery.ui/jquery.ui.listrotator.js', @@ -144,6 +146,22 @@ 'valueview-listrotator-auto', ), ), + + 'jquery.autocompletestring' => $moduleTemplate + array( + 'scripts' => array( + 'jquery/jquery.autocompletestring.js', + ), + 'dependencies' => array( + 'jquery.util.adaptlettercase', + ), + ), + + 'jquery.util.adaptlettercase' => $moduleTemplate + array( + 'scripts' => array( + 'jquery.util/jquery.util.adaptlettercase.js', + ), + ), + ); // return jQuery.valueview's native resources plus those required by the MW extension: diff --git a/ValueView/ValueView.tests.qunit.php b/ValueView/ValueView.tests.qunit.php index 3b4dac0..010fa47 100644 --- a/ValueView/ValueView.tests.qunit.php +++ b/ValueView/ValueView.tests.qunit.php @@ -174,6 +174,25 @@ 'jquery.valueview.experts.timeinput', ), ), + + 'jquery.autocompletestring.tests' => array( + 'scripts' => array( + "$bp/jquery/jquery.autocompletestring.tests.js", + ), + 'dependencies' => array( + 'jquery.autocompletestring', + ), + ), + + 'jquery.util.adaptlettercase.tests' => array( + 'scripts' => array( + "$bp/jquery.util/jquery.util.adaptlettercase.tests.js", + ), + 'dependencies' => array( + 'jquery.util.adaptlettercase', + ), + ), + ); } ); diff --git a/ValueView/resources/jquery.ui/jquery.ui.suggester.js b/ValueView/resources/jquery.ui/jquery.ui.suggester.js index db1ca3e..d52e9a7 100644 --- a/ValueView/resources/jquery.ui/jquery.ui.suggester.js +++ b/ValueView/resources/jquery.ui/jquery.ui.suggester.js @@ -78,7 +78,10 @@ * (2) {String} Error text status. * (3) {Object} Detailed error information. * + * @dependency jquery.autocompletestring + * @dependency jquery.eachchange * @dependency jquery.ui.autocomplete + * @dependency jquery.util.adaptlettercase */ ( function( $ ) { 'use strict'; @@ -230,7 +233,10 @@ var resultSet = $.ui.autocomplete.filter( this.options.source, request.term ); if ( resultSet.length && this.options.adaptLetterCase ) { - this.term = this._adaptLetterCase( this.term, resultSet[0] ); + this.term = $.util.adaptLetterCase( this.term, + resultSet[0], + this.options.adaptLetterCase + ); this.element.val( this.term ); } @@ -289,10 +295,18 @@ // auto-complete input box text (because of the API call lag, this is // avoided when hitting backspace, since the value would be reset too slow) if ( this._lastKeyDown !== 8 && response[1].length > 0 ) { - this.autocompleteString( - response[0], - response[1][0] - ); + var incomplete = response[0], + complete = response[1][0]; + + if ( this.options.adaptLetterCase ) { + this.term = incomplete = $.util.adaptLetterCase( + incomplete, + complete, + this.options.adaptLetterCase + ); + } + + this.element.autocompletestring( incomplete, complete ); } suggest( response[1] ); // pass array of returned values to callback @@ -519,24 +533,6 @@ }, /** - * Adjusts the letter case of a source string to the letter case in a destination string - * according to the adaptLetterCase option. - * - * @param {String} source - * @param {String} destination - * @return {String} Altered source string - */ - _adaptLetterCase: function( source, destination ) { - if ( this.options.adaptLetterCase === 'all' ) { - return destination.substr( 0, source.length ); - } else if ( this.options.adaptLetterCase === 'first' ) { - return destination.substr( 0, 1 ) + source.substr( 1 ); - } else { - return source; - } - }, - - /** * Sets/gets the plain input box value. * * @param {String} [value] Value to be set @@ -556,65 +552,6 @@ this.menu.element.position( $.extend( { of: this.element }, this.options.position ) ); - }, - - /** - * Completes the input box with the remaining characters of a given string. The characters - * of the remaining part are text-highlighted, so the will be overwritten if typing - * characters is continue. Tabbing or clicking outside of the input box will leave the - * completed string in the input box. - * - * @param incomplete {String} - * @param complete {String} - * @return {Number} number of characters added (and highlighted) at the end of the - * incomplete string - */ - autocompleteString: function( incomplete, complete ) { - if( - // if nothing to complete, just return and don't move the cursor - // (can be annoying in this situation) - incomplete === complete - // The following statement is a work-around for a technically unexpected search - // behaviour: e.g. in English Wikipedia opensearch for "Allegro [...]" returns - // "Allegro" as first result instead of "Allegro (music)", so auto-completion should - // probably be prevented here since it would always reset the input box's value to - // "Allegro" - || complete.toLowerCase().indexOf( this.element.val().toLowerCase() ) === -1 - ) { - return 0; - } - - // set value to complete value... - if ( this.options.adaptLetterCase ) { - this.term = this._adaptLetterCase( incomplete, complete ); - if ( complete.indexOf( this.term ) === 0 ) { - this.element.val( complete ); - } - } else if ( incomplete === complete.substr( 0, incomplete.length ) ) { - this.element.val( incomplete + complete.substr( incomplete.length ) ); - } - - // ... and select the suggested, not manually typed part of the value - var start = incomplete.length, - end = complete.length, - node = this.element[0]; - - // highlighting takes some browser specific implementation - if( node.createTextRange ) { // opera < 10.5 and IE - var selRange = node.createTextRange(); - selRange.collapse( true ); - selRange.moveStart( 'character', start); - selRange.moveEnd( 'character', end); - selRange.select(); - } else if( node.setSelectionRange ) { // major modern browsers - // make a 'backward' selection so pressing arrow left won't put the cursor near the - // selections end but rather at the typing position - node.setSelectionRange( start, end, 'backward' ); - } else if( node.selectionStart ) { - node.selectionStart = start; - node.selectionEnd = end; - } - return ( end - start ); } } ); diff --git a/ValueView/resources/jquery.util/jquery.util.adaptlettercase.js b/ValueView/resources/jquery.util/jquery.util.adaptlettercase.js new file mode 100644 index 0000000..0215dee --- /dev/null +++ b/ValueView/resources/jquery.util/jquery.util.adaptlettercase.js @@ -0,0 +1,46 @@ +/** + * adaptlettercase helper function + * + * @licence GNU GPL v2+ + * @author H. Snater < mediaw...@snater.com > + * + * @dependency jQuery + */ +jQuery.util = jQuery.util || {}; + +jQuery.util.adaptlettercase = ( function( $ ) { + 'use strict'; + + /** + * Adapts the letter case of a source string to a destination string. The destination string is + * supposed to consist our of the source string's first letter(s). + * + * @param {string} source + * @param {string} destination + * @param {string|undefined} method "all" will adapt source's letter case for all destination + * characters, "first" will adapt the first letter only. By default, no adaption is + * taking place. + * @return {string} + * + * @throws {Error} if source and/or destination string is not specified. + * @throws {Error} if source string does not start with destination string. + */ + return function( source, destination, method ) { + if( !source || !destination ) { + throw new Error( 'Source and destination need to be specified.' ); + } + + if( source.toLowerCase().indexOf( destination.toLowerCase() ) === -1 ) { + throw new Error( source + ' does not start with ' + destination + '.' ); + } + + if ( method === 'all' ) { + return destination.substr( 0, source.length ); + } else if ( method === 'first' ) { + return destination.substr( 0, 1 ) + source.substr( 1 ); + } else { + return source; + } + }; + +} )( jQuery ); diff --git a/ValueView/resources/jquery/jquery.autocompletestring.js b/ValueView/resources/jquery/jquery.autocompletestring.js new file mode 100644 index 0000000..6408add --- /dev/null +++ b/ValueView/resources/jquery/jquery.autocompletestring.js @@ -0,0 +1,79 @@ +/** + * autocompletestring jQuery plugin + * + * @licence GNU GPL v2+ + * @author H. Snater < mediaw...@snater.com > + * + * @dependency jQuery + */ +jQuery.fn.autocompletestring = ( function( $ ) { + 'use strict'; + + /** + * Applied to an input or textarea element, jQuery.fn.autocompletestring is fed with a + * "complete" string and an "incomplete" - latter is supposed to consist out of the first + * letter(s) of the "complete" string. The form element is filled with the "complete" string + * while a text selection is applied to the characters missing in the "incomplete" string. + * + * @param {string} incomplete + * @param {string} complete + * @return {jQuery} + */ + var autocompletestring = function( incomplete, complete ) { + if( + !incomplete || !complete + || complete.toLowerCase().indexOf( incomplete.toLowerCase() ) !== 0 + ) { + return this; + } + + return this.each( function() { + var $this = $( this ); + + // Only auto-complete when incomplete string actually is a part of the complete string: + if( incomplete === complete.substr( 0, incomplete.length ) ) { + $this.val( incomplete + complete.substr( incomplete.length ) ); + } + + $.fn.autocompletestring.selectText( this, incomplete.length, complete.length ); + } ); + }; + + /** + * Creates a text selection. + * + * @param {object} node + * @param {number} start + * @param {number} end + * @return {number} Text selection length. + */ + autocompletestring.selectText = function( node, start, end ) { + if( end > node.value.length ) { + end = node.value.length; + } + + if( start > end ) { + return 0; + } + + if( node.createTextRange ) { // Opera < 10.5 and IE + var selRange = node.createTextRange(); + selRange.collapse( true ); + selRange.moveStart( 'character', start ); + selRange.moveEnd( 'character', end ); + selRange.select(); + } else if( node.setSelectionRange ) { // major modern browsers + // Make a 'backward' selection so pressing arrow left won't put the cursor near the + // selections end but rather at the typing position: + node.setSelectionRange( start, end, 'backward' ); + } else if( node.selectionStart ) { + node.selectionStart = start; + node.selectionEnd = end; + } + + return ( end - start ); + }; + + return autocompletestring; + +} )( jQuery ); diff --git a/ValueView/tests/qunit/jquery.ui/jquery.ui.suggester.tests.js b/ValueView/tests/qunit/jquery.ui/jquery.ui.suggester.tests.js index 2885696..b5e88a8 100644 --- a/ValueView/tests/qunit/jquery.ui/jquery.ui.suggester.tests.js +++ b/ValueView/tests/qunit/jquery.ui/jquery.ui.suggester.tests.js @@ -103,83 +103,9 @@ 'Detected scrollbar width.' ); - // Firefox will throw an error when the input element is not part of the DOM while trying to - // set the selection range which is part of the following assertion - $( 'body' ).append( $input ); - assert.equal( - suggester.autocompleteString( $input.val(), 'ab' ), - 1, - 'Auto-completed text.' - ); - suggester.destroy(); $input.remove(); - } ); - - QUnit.test( 'Adapt letter case', function( assert ) { - var $input = newTestSuggester(); - var suggester = $input.data( 'suggester' ); - - assert.equal( - suggester._adaptLetterCase( 'abc', 'AbC' ), - 'abc', - "adaptLetterCase: Did not adapt any letter case." - ); - - $input.val( 'ef' ); - suggester.search( 'EF' ); // simulate case-insensitive search - - assert.equal( - $input.val(), - 'ef', - "Did not adjusted input value's letter case according to suggestion list's first result set." - ); - - suggester.destroy(); - $input.remove(); - - $input = newTestSuggester( { adaptLetterCase: 'all' } ); - suggester = $input.data( 'suggester' ); - - assert.equal( - suggester._adaptLetterCase( 'abc', 'AbC' ), - 'AbC', - "adjustLetterCase: Adapted the case of all letters." - ); - - $input.val( 'ef' ); - suggester.search( 'EF' ); - - assert.equal( - $input.val(), - 'EF', - "Adjusted input value's letter case according to suggestion list's first result set." - ); - - suggester.destroy(); - $input.remove(); - - $input = newTestSuggester( { adaptLetterCase: 'first' } ); - suggester = $input.data( 'suggester' ); - - assert.equal( - suggester._adaptLetterCase( 'abc', 'AbC' ), - 'Abc', - "adaptLetterCase: Adapted the case of the first letter." - ); - - $input.val( 'ef' ); - suggester.search( 'EF' ); - - assert.equal( - $input.val(), - 'Ef', - "Capitalized input value's letters according to suggestion list's first result set." - ); - - suggester.destroy(); - $input.remove(); } ); QUnit.test( 'automatic height adjustment', function( assert ) { diff --git a/ValueView/tests/qunit/jquery.util/jquery.util.adaptlettercase.tests.js b/ValueView/tests/qunit/jquery.util/jquery.util.adaptlettercase.tests.js new file mode 100644 index 0000000..932238a --- /dev/null +++ b/ValueView/tests/qunit/jquery.util/jquery.util.adaptlettercase.tests.js @@ -0,0 +1,53 @@ +/** + * @licence GNU GPL v2+ + * @author H. Snater < mediaw...@snater.com > + */ + +( function( $, QUnit ) { + 'use strict'; + + QUnit.module( 'jquery.util.adaptlettercase' ); + + QUnit.test( 'Basic tests', function( assert ) { + + assert.equal( + $.util.adaptlettercase( 'abc', 'AbC' ), + 'abc', + 'Not adapting any letter-case when omitting \'method\' parameter.' + ); + + assert.equal( + $.util.adaptlettercase( 'abc', 'AbC', 'all' ), + 'AbC', + 'Adapting the case of all letters when specifying \'all\' as method.' + ); + + assert.equal( + $.util.adaptlettercase( 'ABC', 'abc', 'first' ), + 'aBC', + 'Adapting the first letter\'s case when specifying \'first\' as method.' + ); + + assert.equal( + $.util.adaptlettercase( 'AB', 'ab', 'first' ), + 'aB', + 'Adapting the first letter\'s case when specifying \'first\' as method with ' + + 'destination being a a part of source.' + ); + + assert.equal( + $.util.adaptlettercase( '123', '123', 'all' ), + '123', + 'No replacement taking place when not passing letters.' + ); + + assert.throws( + function() { + $.util.adaptlettercase( 'abc', '123', 'all' ); + }, + 'Error thrown when destination does not match source.' + ); + + } ); + +}( jQuery, QUnit ) ); \ No newline at end of file diff --git a/ValueView/tests/qunit/jquery/jquery.autocompletestring.tests.js b/ValueView/tests/qunit/jquery/jquery.autocompletestring.tests.js new file mode 100644 index 0000000..6be97af --- /dev/null +++ b/ValueView/tests/qunit/jquery/jquery.autocompletestring.tests.js @@ -0,0 +1,70 @@ +/** + * @licence GNU GPL v2+ + * @author H. Snater < mediaw...@snater.com > + */ + +( function( $, QUnit ) { + 'use strict'; + + /** + * Creates an input element suitable for testing. + * @return {jQuery} + */ + function createTestInput() { + return $( '<input/>' ).addClass( 'test-autocompletestring' ).appendTo( 'body' ); + } + + QUnit.module( 'jquery.autocompletestring', { + teardown: function() { + $( '.test-autocompletestring' ).remove(); + } + } ); + + QUnit.test( 'Adapt letter case', function( assert ) { + var $input = createTestInput(); + + assert.equal( + $input.autocompletestring( 'a', 'abc' ).val(), + 'abc', + 'Auto-completed \'a\' to \'abc\'.' + ); + + assert.equal( + $input.autocompletestring( '12', '123' ).val(), + '123', + 'Auto-completed \'12\' to \'123\'.' + ); + + assert.equal( + $input.autocompletestring( 'abc', 'abc' ).val(), + 'abc', + 'Value remains the same when \'incomplete\' and \'complete\' string match.' + ); + + assert.equal( + $input.autocompletestring( 'a', 'ABC' ).val(), + 'abc', + 'No auto-completion is performed when \'incomplete\' is not part of \'complete\' ' + + 'string. Input value remains unchanged.' + ); + } ); + + QUnit.test( 'selectText()', function( assert ) { + var $input = createTestInput().val( '0123456789' ); + + assert.equal( + $.fn.autocompletestring.selectText( $input[0], 0, 1 ), + 1, + 'Applied text selection with length of 1.' + ); + + assert.equal( + $.fn.autocompletestring.selectText( $input[0], 0, 20 ), + 10, + 'Applied a text selection with the input value\'s character length since it is shorter ' + + 'than the selection length trying to apply.' + ); + + } ); + +}( jQuery, QUnit ) ); -- To view, visit https://gerrit.wikimedia.org/r/74116 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I83d751ca22f2b0579d21849233da011417b92e5d Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/DataValues Gerrit-Branch: master Gerrit-Owner: Henning Snater <henning.sna...@wikimedia.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits