Daniel Werner has submitted this change and it was merged.

Change subject: (bug 48145) Implemented input extender jQuery widget
......................................................................


(bug 48145) Implemented input extender jQuery widget

The input extender widget may be used on an input box in order to display 
additional
information/options in an overlay beneath the input element. The basic 
implementation
will be upgraded in future commits.

Change-Id: I96af69359f555b61c48c638a483d756213e86856
---
M ValueView/ValueView.i18n.php
M ValueView/ValueView.resources.mw.php
M ValueView/ValueView.tests.qunit.php
M ValueView/resources/jquery.time/jquery.time.timeinput.js
A ValueView/resources/jquery.ui/jquery.ui.inputextender.css
A ValueView/resources/jquery.ui/jquery.ui.inputextender.js
M ValueView/tests/qunit/jquery.time/jquery.time.timeinput.tests.js
A ValueView/tests/qunit/jquery.ui/jquery.ui.inputextender.tests.js
8 files changed, 438 insertions(+), 4 deletions(-)

Approvals:
  Daniel Werner: Verified; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/ValueView/ValueView.i18n.php b/ValueView/ValueView.i18n.php
index 9ad74f8..bdb5cbc 100644
--- a/ValueView/ValueView.i18n.php
+++ b/ValueView/ValueView.i18n.php
@@ -31,6 +31,7 @@
 
 /** English
  * @author Daniel Werner < [email protected] >
+ * @author H. Snater < [email protected] >
  */
 $messages['en'] = array(
        'valueview-desc' => 'UI components for displaying and editing data 
values',
@@ -40,12 +41,16 @@
        'valueview-expert-unsupportedvalue-unsupporteddatatype' => 'Handling of 
values for "$1" data type is not yet supported.',
 
        // EmptyValue expert:
-       'valueview-expert-emptyvalue-empty' => 'empty'
+       'valueview-expert-emptyvalue-empty' => 'empty',
+
+       'valueview-inputextender-showoptions' => 'show options',
+       'valueview-inputextender-hideoptions' => 'hide options',
 );
 
 /** Message documentation (Message documentation)
  * @author Daniel Werner < [email protected] >
  * @author Shirayuki
+ * @author H. Snater < [email protected] >
  */
 $messages['qqq'] = array(
        'valueview-desc' => 
'{{desc|name=ValueView|url=http://www.mediawiki.org/wiki/Extension:ValueView}}',
@@ -53,6 +58,8 @@
        'valueview-expert-unsupportedvalue-unsupporteddatatype' => 'Error shown 
if a data value for a certain data type (see [[d:Wikidata:Glossary]]) should be 
displayed or a form for creating one should be offered while this is not yet 
possible from a technical point of view (e.g. because a valueview widget expert 
handling data values for that data type has not yet been implemented). 
Parameter $1 is the name of the data type which lacks support',
        'valueview-expert-emptyvalue-empty' => 'Message expressing that there 
is currently no value set in a jQuery valueview.
 {{Identical|Empty}}',
+       'valueview-inputextender-showoptions' => 'Message of the link displayed 
next to an input element if there are detailed options for inputting a value. 
This message is shown, when the options are currently invisible. By clicking 
the link, the options are shown and can be adjusted.',
+       'valueview-inputextender-hideoptions' => 'Message of the link displayed 
next to an input element if there are detailed options for inputting a value. 
This message is shown, when the options are currently visible. By clicking the 
link, the options will be hidden.',
 );
 
 /** Asturian (asturianu)
diff --git a/ValueView/ValueView.resources.mw.php 
b/ValueView/ValueView.resources.mw.php
index 8f72e37..cf167bf 100644
--- a/ValueView/ValueView.resources.mw.php
+++ b/ValueView/ValueView.resources.mw.php
@@ -96,6 +96,22 @@
                        ),
                ),
 
+               'jquery.ui.inputextender' => $moduleTemplate + array(
+                       'scripts' => array(
+                               'jquery.ui/jquery.ui.inputextender.js',
+                       ),
+                       'styles' => array(
+                               'jquery.ui/jquery.ui.inputextender.css',
+                       ),
+                       'dependencies' => array(
+                               'jquery.ui.widget',
+                       ),
+                       'messages' => array(
+                               'valueview-inputextender-showoptions',
+                               'valueview-inputextender-hideoptions',
+                       ),
+               ),
+
                'jquery.ui.toggler' => $moduleTemplate + array(
                        'scripts' => array(
                                'jquery.ui/jquery.ui.toggler.js',
diff --git a/ValueView/ValueView.tests.qunit.php 
b/ValueView/ValueView.tests.qunit.php
index 048e6b5..94a211e 100644
--- a/ValueView/ValueView.tests.qunit.php
+++ b/ValueView/ValueView.tests.qunit.php
@@ -62,6 +62,15 @@
                        ),
                ),
 
+               'jquery.ui.inputextender.tests' => array(
+                       'scripts' => array(
+                               
"$bp/jquery.ui/jquery.ui.inputextender.tests.js",
+                       ),
+                       'dependencies' => array(
+                               'jquery.ui.inputextender',
+                       ),
+               ),
+
                'jquery.ui.suggester.tests' => array(
                        'scripts' => array(
                                "$bp/jquery.ui/jquery.ui.suggester.tests.js",
diff --git a/ValueView/resources/jquery.time/jquery.time.timeinput.js 
b/ValueView/resources/jquery.time/jquery.time.timeinput.js
index 4e061c5..d1f6265 100644
--- a/ValueView/resources/jquery.time/jquery.time.timeinput.js
+++ b/ValueView/resources/jquery.time/jquery.time.timeinput.js
@@ -4,7 +4,7 @@
  * @licence GNU GPL v2+
  * @author H. Snater < [email protected] >
  *
- * @event change: Triggered whenever the widget's value has changed.
+ * @event update: Triggered whenever the widget's value is updated.
  *        (1) {jQuery.Event}
  *        (2) {time.Time|null} New value (null for no or an invalid value) the 
widget's value has
  *            been changed to.
@@ -35,7 +35,7 @@
                                var value = self._parse();
                                if( value !== self._value ) {
                                        self._value = value;
-                                       self._trigger( 'change', null, 
[self._value] );
+                                       self._trigger( 'update', null, 
[self._value] );
                                }
                        } );
                },
diff --git a/ValueView/resources/jquery.ui/jquery.ui.inputextender.css 
b/ValueView/resources/jquery.ui/jquery.ui.inputextender.css
new file mode 100644
index 0000000..8f6e1ac
--- /dev/null
+++ b/ValueView/resources/jquery.ui/jquery.ui.inputextender.css
@@ -0,0 +1,23 @@
+/**
+ * Default styles for input extender widget
+ *
+ * @license GNU GPL v2+
+ * @author H. Snater < [email protected] >
+ */
+
+.ui-inputextender .ui-inputextender-input {
+       display: inline;
+}
+
+.ui-inputextender .ui-inputextender-extender {
+       display: inline;
+}
+
+.ui-inputextender .ui-inputextender-contentcontainer {
+       position: absolute;
+       z-index: 1;
+       left: 2px;
+       font-size: 84%;
+       padding: 2px;
+       box-shadow: 2px 2px 6px -1px grey;
+}
diff --git a/ValueView/resources/jquery.ui/jquery.ui.inputextender.js 
b/ValueView/resources/jquery.ui/jquery.ui.inputextender.js
new file mode 100644
index 0000000..a97ce51
--- /dev/null
+++ b/ValueView/resources/jquery.ui/jquery.ui.inputextender.js
@@ -0,0 +1,249 @@
+/**
+ * Input extender widget
+ *
+ * The input extender extends an input element with additional contents 
displayed underneath the.
+ * @licence GNU GPL v2+
+ * @author H. Snater < [email protected] >
+ *
+ * @option {jQuery[]} [content] Default/"fixed" extender contents that always 
should be visible as
+ *         long as the extension itself is visible.
+ *
+ * @option {jQuery[]} [extendedContent] Additional content that should only be 
displayed after
+ *         clicking on the extender link.
+ *
+ * @option [messages] {Object} Strings used within the widget.
+ *         Messages should be specified using mwMsgOrString(<resource loader 
module message key>,
+ *         <fallback message>) in order to use the messages specified in the 
resource loader module
+ *         (if loaded).
+ *         messages['show options'] {String} (optional) Label of the link 
showing any additional
+ *         contents.
+ *         Default value: 'show options'
+ *         messages['hide options'] {String} (optional) Label of the link 
hiding any additional
+ *         contents.
+ *         Default value: 'hide options'
+ *
+ * @dependency jQuery.Widget
+ */
+( function( $ ) {
+       'use strict';
+
+       /**
+        * Whether loaded in MediaWiki context.
+        * @type {boolean}
+        */
+       var IS_MW_CONTEXT = ( typeof mw !== 'undefined' && mw.msg );
+
+       /**
+        * Whether actual entity selector resource loader module is loaded.
+        * @type {boolean}
+        */
+       var IS_MODULE_LOADED = (
+               IS_MW_CONTEXT
+               && $.inArray( 'jquery.wikibase.entityselector', 
mw.loader.getModuleNames() ) !== -1
+       );
+
+       /**
+        * Returns a message from the MediaWiki context if the input extender 
module has been loaded.
+        * If it has not been loaded, the corresponding string defined in the 
options will be returned.
+        *
+        * @param {String} msgKey
+        * @param {String} string
+        * @return {String}
+        */
+       function mwMsgOrString( msgKey, string ) {
+               return ( IS_MODULE_LOADED ) ? mw.msg( msgKey ) : string;
+       }
+
+       $.widget( 'ui.inputextender', {
+               /**
+                * Additional options
+                * @type {Object}
+                */
+               options: {
+                       content: [],
+                       extendedContent: [],
+                       messages: {
+                               'show options': mwMsgOrString( 
'valueview-inputextender-showoptions', 'show options' ),
+                               'hide options': mwMsgOrString( 
'valueview-inputextender-hideoptions', 'hide options' )
+                       }
+               },
+
+               /**
+                * The widget parent's node.
+                * @type {jQuery}
+                */
+               $parent: null,
+
+               /**
+                * Container node wrapping the widget's whole DOM structure.
+                * @type {jQuery}
+                */
+               $container: null,
+
+               /**
+                * Container node containing the input element and the extender.
+                * @type {jQuery}
+                */
+               $inputContainer: null,
+
+               /**
+                * Node of the link to extended the extenders additional 
content.
+                * @type {jQuery}
+                */
+               $extender: null,
+
+               /**
+                * Node containing all the extension content.
+                * @type {jQuery}
+                */
+               $contentContainer: null,
+
+               /**
+                * Node of the default/"fixed" extension content.
+                * @type {jQuery}
+                */
+               $content: null,
+
+               /**
+                * Node of the additional extension content shown/hidden by the 
extender link.
+                * @type {jQuery}
+                */
+               $extendedContent: null,
+
+               /**
+                * @see jQuery.Widget._create
+                */
+               _create: function() {
+                       var self = this;
+
+                       this.$parent = this.element.parent();
+
+                       if( !this.$parent.length ) {
+                               throw new Error( 'Input extender widget needs 
to be in the DOM when initializing.' );
+                       }
+
+                       this.$container = $( '<div/>' )
+                       .addClass( this.widgetBaseClass )
+                       .data( this.widgetName, this )
+                       .appendTo( this.$parent );
+
+                       this.$inputContainer = $( '<div />' )
+                       .addClass( this.widgetBaseClass + '-inputcontainer' )
+                       .append( this.element.addClass( this.widgetBaseClass + 
'-input' ).detach() )
+                       .appendTo( this.$container );
+
+                       this.$extender = $( '<a/>' )
+                       .addClass( this.widgetBaseClass + '-extender' )
+                       .attr( 'href', 'javascript:void(0);' )
+                       .text( this.options.messages['show options'] )
+                       .appendTo( this.$inputContainer )
+                       .on( 'click', function( event ) {
+                               self._toggleExtension();
+                       } )
+                       .hide();
+
+                       this.$contentContainer = $( '<div/>' )
+                       .addClass( this.widgetBaseClass + '-contentcontainer 
ui-widget-content' )
+                       .appendTo( this.$container )
+                       .hide();
+
+                       this.$content = $( '<div/>' )
+                       .addClass( this.widgetBaseClass + '-content' )
+                       .appendTo( this.$contentContainer );
+
+                       this.$extendedContent = $( '<div/>' )
+                       .addClass( this.widgetBaseClass + '-extendedcontent' )
+                       .appendTo( this.$contentContainer )
+                       .hide();
+
+                       this.element.add( this.$extender )
+                       .on( 'focus.' + this.widgetName, function( event ) {
+                               self.showContent();
+                       } )
+                       .on( 'blur.' + this.widgetName, function( event ) {
+                               self.hideContent();
+                       } );
+
+                       this._draw();
+               },
+
+               /**
+                * @see jQuery.Widget.destroy
+                */
+               destroy: function() {
+                       var $input = this.element.detach();
+                       this.$container.remove();
+                       this.$parent.append( $input );
+                       $.Widget.prototype.destroy.call( this );
+               },
+
+               /**
+                * Draws the widget according to its current state.
+                */
+               _draw: function() {
+                       var self = this;
+
+                       this.$content.empty();
+
+                       // Only show the extender when there are any additional 
options to extend:
+                       this.$extender[ ( this.options.extendedContent.length ) 
? 'show' : 'hide' ]();
+
+                       $.each( this.options.content, function( i, $node ) {
+                               self.$content.append( $node );
+                       } );
+
+                       $.each( this.options.extendedContent, function( i, 
$node ) {
+                               self.$extendedContent.append( $node );
+                       } );
+               },
+
+               /**
+                * Toggles the visibility of the additional options.
+                */
+               _toggleExtension: function() {
+                       var self = this;
+
+                       if( this.$extendedContent.is( ':visible' ) ) {
+                               this.$extendedContent.slideUp( 150, function() {
+                                       self.$extender.text( 
self.options.messages['show options'] );
+                                       self._trigger( 'toggle' );
+                               } );
+                       } else {
+                               this.element.focus();
+                               this.$extendedContent.slideDown( 150, 
function() {
+                                       self.$extender.text( 
self.options.messages['hide options'] );
+                                       self._trigger( 'toggle' );
+                               } );
+                       }
+
+               },
+
+               /**
+                * Shows all the extension contents.
+                *
+                * @param {Function} [callback] Invoked as soon as the contents 
are visible.
+                */
+               showContent: function( callback ) {
+                       this.$contentContainer.fadeIn( 150, function() {
+                               if( $.isFunction( callback ) ) {
+                                       callback();
+                               }
+                       } );
+               },
+
+               /**
+                * Hides all the extension contents.
+                *
+                * @param {Function} [callback] Invoked as soon as the contents 
are hidden.
+                */
+               hideContent: function( callback ) {
+                       this.$contentContainer.fadeOut( 150, function() {
+                               if( $.isFunction( callback ) ) {
+                                       callback();
+                               }
+                       } );
+               }
+
+       } );
+
+} )( jQuery );
diff --git a/ValueView/tests/qunit/jquery.time/jquery.time.timeinput.tests.js 
b/ValueView/tests/qunit/jquery.time/jquery.time.timeinput.tests.js
index 78d4f5a..80c46c3 100644
--- a/ValueView/tests/qunit/jquery.time/jquery.time.timeinput.tests.js
+++ b/ValueView/tests/qunit/jquery.time/jquery.time.timeinput.tests.js
@@ -34,7 +34,7 @@
                        );
                };
 
-               $input.on( 'timeinputchange', function( event, value ) {
+               $input.on( 'timeinputupdate', function( event, value ) {
                        assertValue( value );
                } );
 
diff --git a/ValueView/tests/qunit/jquery.ui/jquery.ui.inputextender.tests.js 
b/ValueView/tests/qunit/jquery.ui/jquery.ui.inputextender.tests.js
new file mode 100644
index 0000000..4e6f739
--- /dev/null
+++ b/ValueView/tests/qunit/jquery.ui/jquery.ui.inputextender.tests.js
@@ -0,0 +1,130 @@
+/**
+ * @since 0.1
+ * @ingroup ValueView
+ *
+ * @licence GNU GPL v2+
+ * @author H. Snater < [email protected] >
+ */
+
+( function( $, QUnit ) {
+       'use strict';
+
+       /**
+        * Factory for creating an input extender widget suitable for testing.
+        */
+       var newTestInputextender = function( options ) {
+               if( !options ) {
+                       options = {
+                               content: [ $( '<span/>' ).addClass( 
'defaultContent' ).text( 'default content' ) ],
+                               extendedContent: [ $( '<span/>' ).addClass( 
'extendedContent' ).text( 'extended content' ) ]
+                       }
+               }
+
+               return $( '<input/>' )
+                       .addClass( 'test_inputextender' )
+                       .appendTo( $( 'body' ) )
+                       .inputextender( options );
+       };
+
+       QUnit.module( 'jquery.ui.inputextender', QUnit.newMwEnvironment( {
+               teardown: function() {
+                       $( '.test_inputextender' ).each( function( i, node ) {
+                               $( node ).data( 'inputextender' ).destroy();
+                               $( node ).remove();
+                       } );
+               }
+       } ) );
+
+       QUnit.test( 'Initialization', 1, function( assert ) {
+               var $input = newTestInputextender(),
+                       extender = $input.data( 'inputextender' );
+
+               assert.ok(
+                       !extender.$contentContainer.is( ':visible' ),
+                       'Content is not visible.'
+               );
+       } );
+
+       QUnit.test( 'Show/Hide basic content', 4, function( assert ) {
+               var $input = newTestInputextender(),
+                       extender = $input.data( 'inputextender' );
+
+               extender.showContent( function() {
+                       assert.ok(
+                               extender.$contentContainer.is( ':visible' ),
+                               'Content visible after focusing input element.'
+                       );
+
+                       assert.ok(
+                               extender.$content.is( ':visible' ),
+                               'Default content is visible.'
+                       );
+
+                       assert.ok(
+                               !extender.$extendedContent.is( ':visible' ),
+                               'Additional content is hidden.'
+                       );
+               } );
+
+               QUnit.stop();
+
+               extender.hideContent( function() {
+                       assert.ok(
+                               !extender.$contentContainer.is( ':visible' ),
+                               'Content is hidden after blurring the input 
element.'
+                       );
+
+                       QUnit.start();
+               } );
+
+       } );
+
+       QUnit.test( 'Toggle additional content', 4, function( assert ) {
+               var $input = newTestInputextender(),
+                       extender = $input.data( 'inputextender' );
+
+               var assertions = [
+                       function() {
+                               assert.ok(
+                                       extender.$content.is( ':visible' ),
+                                       'Default content is still visible after 
having clicked the extender link.'
+                               );
+
+                               assert.ok(
+                                       extender.$extendedContent.is( 
':visible' ),
+                                       'Additional content is visible after 
having clicked the extender link.'
+                               );
+                       },
+                       function() {
+                               assert.ok(
+                                       extender.$content.is( ':visible' ),
+                                       'Default content is still visible after 
having clicked the extender link the second time.'
+                               );
+
+                               assert.ok(
+                                       !extender.$extendedContent.is( 
':visible' ),
+                                       'Additional content is hidden again 
after having clicked the extender link the second time.'
+                               );
+                       }
+               ];
+
+               $input.on( 'inputextendertoggle', function( event ) {
+                       assertions[0]();
+                       $input
+                       .off( 'inputextendertoggle' )
+                       .on( 'inputextendertoggle', function( event ) {
+                               assertions[1]();
+                               QUnit.start();
+                       } );
+                       QUnit.start();
+               } );
+
+               // clicks will result into above event listeners being triggered
+               QUnit.stop();
+               extender.$extender.click();
+
+               QUnit.stop();
+               extender.$extender.click();
+       } );
+
+}( jQuery, QUnit ) );

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I96af69359f555b61c48c638a483d756213e86856
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/DataValues
Gerrit-Branch: master
Gerrit-Owner: Henning Snater <[email protected]>
Gerrit-Reviewer: Daniel Werner <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to