Tobias Gritschacher has submitted this change and it was merged.

Change subject: (bug 44683) Implementing qualifiers in the JavaScript UI
......................................................................


(bug 44683) Implementing qualifiers in the JavaScript UI

Qualifiers may be added to existing claims/statements now via the JavaScript 
user interface.
The change set extends the claim and statement templates and adds two addition 
toolbar
definitions (add and remove qualifier snak) to the claimview widget.

- patch set 8: Minor code improvement

Change-Id: I236bdd0a7e9c8004a4b1a3c2eff83d690985cd05
---
M lib/resources/Resources.php
M lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
M lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
M lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
M lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
M lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
M 
lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.definitions.js
M 
lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.js
M lib/resources/templates.php
M lib/resources/wikibase.css
M repo/Wikibase.i18n.php
M repo/includes/EntityView.php
M repo/resources/wikibase.ui.entityViewInit.js
13 files changed, 343 insertions(+), 95 deletions(-)

Approvals:
  Tobias Gritschacher: Verified; Looks good to me, approved
  jenkins-bot: Checked



diff --git a/lib/resources/Resources.php b/lib/resources/Resources.php
index c6a45e5..49ed5f1 100644
--- a/lib/resources/Resources.php
+++ b/lib/resources/Resources.php
@@ -554,6 +554,7 @@
                                'jquery.wikibase.snakview'
                        ),
                        'messages' => array(
+                               'wikibase-addqualifier',
                                'wikibase-claimview-snak-tooltip',
                                'wikibase-claimview-snak-new-tooltip'
                        )
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
index 5a4f93c..4037049 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js
@@ -74,10 +74,12 @@
                                return ( this._claim && this._claim.getGuid() ) 
|| 'new';
                        },
                        'wb-last', // class: wb-first|wb-last
-                       '' // .wb-claim-mainsnak
+                       '', // .wb-claim-mainsnak
+                       '' // Qualifiers
                ],
                templateShortCuts: {
-                       '$mainSnak': '.wb-claim-mainsnak'
+                       '$mainSnak': '.wb-claim-mainsnak',
+                       '$qualifiers': '.wb-statement-qualifiers'
                },
                value: null,
                predefined: {
@@ -108,16 +110,17 @@
        _claim: null,
 
        /**
+        * Reference to the listview widget managing the qualifier snaks. 
Basically, just a short-cut
+        * for this.$qualifiers.data( 'listview' )
+        * @type {jquery.wikibase.listview}
+        */
+       _qualifiers: null,
+
+       /**
         * Whether the Claim is currently in edit mode.
         * @type {boolean}
         */
        _isInEditMode: false,
-
-       /**
-        * Whether the widget is currently valid according to its contents.
-        * @type {boolean}
-        */
-       _isValid: false,
 
        /**
         * @see jQuery.Widget._create
@@ -132,23 +135,19 @@
                // set up event listeners:
                this.$mainSnak
                .on ( 'snakviewchange', function( event, status ) {
-                       var snakview = self.$mainSnak.data( 'snakview' );
-                       self._isValid = ( snakview.isValid() && 
!snakview.isInitialSnak() );
                        self._trigger( 'change' );
                } )
                .on( 'snakviewstopediting', function( event, dropValue ) {
                        // React on key stroke events (e.g. pressing enter or 
ESC key)
-                       if ( !self.isValid() && !dropValue ) {
+                       if (
+                               ( !self.isValid() || self.isInitialValue() )
+                               && !dropValue && !self.__continueStopEditing
+                       ) {
                                event.preventDefault();
-                               return;
-                       }
-
-                       if ( !self.__continueStopEditing ) {
+                       } else if ( !self.__continueStopEditing ) {
                                // Do not exit snakview's edit mode yet; Let 
any API request be performed first.
                                event.preventDefault();
                                self.stopEditing( dropValue );
-                       } else {
-                               self.__continueStopEditing = false;
                        }
                } )
                .on( 'valueviewchange', function( e ) {
@@ -162,6 +161,34 @@
                        locked: this.option( 'locked' ).mainSnak,
                        autoStartEditing: false // manually, after toolbar is 
there, so events can access toolbar
                } );
+
+               // Initialize qualifiers:
+               // TODO: Allow adding qualifiers when adding a new claim.
+               if ( this.option( 'value' ) ) {
+                       var $qualifiers = $( '<div/>' )
+                       .prependTo( this.$qualifiers )
+                       .snaklistview( {
+                               value: ( this._claim ) ? 
this._claim.getQualifiers() : null
+                       } )
+                       .on( 'snaklistviewchange', function( event ) {
+                               self._trigger( 'change' );
+                       } )
+                       .on( 'snaklistviewstopediting', function( event, 
dropValue ) {
+                               // React on key stroke events (e.g. pressing 
enter or ESC key)
+                               if (
+                                       ( !self.isValid() || 
self.isInitialValue() )
+                                       && !dropValue && 
!self.__continueStopEditing
+                               ) {
+                                       event.preventDefault();
+                               } else if ( !self.__continueStopEditing ) {
+                                       // Do not exit snakview's edit mode 
yet; Let any API request be performed first.
+                                       event.preventDefault();
+                                       self.stopEditing( dropValue );
+                               }
+                       } );
+
+                       this._qualifiers = $qualifiers.data( 'snaklistview' );
+               }
 
                if ( this._claim || this.options.predefined.mainSnak ) {
                        var property = this._claim
@@ -178,11 +205,27 @@
 
        /**
         * Returns whether the claimview is valid according to its current 
contents.
+        * @since 0.4
         *
         * @return {boolean}
         */
        isValid: function() {
-               return this._isValid;
+               var snakview = this.$mainSnak.data( 'snakview' );
+               return ( snakview.isValid() && ( !this._qualifiers || 
this._qualifiers.isValid() ) );
+       },
+
+       /**
+        * Returns whether the current value of this claim (including the 
qualifiers) equals the value
+        * the claim has been initialized with.
+        * @since 0.4
+        *
+        * @returns {boolean}
+        */
+       isInitialValue: function() {
+               return (
+                       this.$mainSnak.data( 'snakview' ).isInitialSnak()
+                       && ( !this._qualifiers || 
this._qualifiers.isInitialValue() )
+               );
        },
 
        /**
@@ -204,10 +247,12 @@
 
                        this.$mainSnak.data( 'snakview' ).startEditing();
 
+                       if ( this._qualifiers ) {
+                               this._qualifiers.startEditing();
+                       }
+
                        this.element.addClass( 'wb-edit' );
                        this._isInEditMode = true;
-
-                       this._isValid = ( snakview.isValid() && 
!snakview.isInitialSnak() );
 
                        this._trigger( 'afterstartediting' );
                }
@@ -225,11 +270,11 @@
        stopEditing: $.NativeEventHandler( 'stopEditing', {
                // don't stop edit mode or trigger event if not in edit mode 
currently:
                initially: function( e, dropValue ) {
-                       if( !this.isInEditMode() || !this.isValid() && 
!dropValue ) {
+                       if (
+                               !this.isInEditMode() || ( !this.isValid() || 
this.isInitialValue() ) && !dropValue
+                       ) {
                                e.cancel();
                        }
-
-                       this.__continueStopEditing = true;
 
                        this.element.removeClass( 'wb-error' );
                },
@@ -241,20 +286,37 @@
 
                        if ( dropValue ) {
                                // nothing to update
+                               this.__continueStopEditing = true;
+
                                if ( this.$mainSnak.data( 'snakview' ) ) {
                                        this.$mainSnak.data( 'snakview' 
).stopEditing( dropValue );
                                }
+
+                               if ( this._qualifiers ) {
+                                       this._qualifiers.stopEditing( dropValue 
);
+                               }
+
+                               this.__continueStopEditing = false;
 
                                self.enable();
                                self.element.removeClass( 'wb-edit' );
                                self._isInEditMode = false;
 
                                self._trigger( 'afterstopediting', null, [ 
dropValue ] );
-                       } else if ( this._claim ) {
+                       } else if ( this._claim && !this.__continueStopEditing 
) {
                                // editing an existing claim
                                self._saveClaimApiCall()
                                .done( function( savedClaim, pageInfo ) {
+                                       self.__continueStopEditing = true;
+
                                        self.$mainSnak.data( 'snakview' 
).stopEditing( dropValue );
+
+                                       if ( self._qualifiers ) {
+                                               
self._qualifiers.__continueStopEditing = true;
+                                               self._qualifiers.stopEditing();
+                                       }
+
+                                       self.__continueStopEditing = false;
 
                                        self.enable();
 
@@ -283,7 +345,7 @@
 
                                        self.__continueStopEditing = false;
                                } );
-                       } else {
+                       } else if ( !this.__continueStopEditing ) {
                                // Adding a new claim is managed in 
claimlistview and will end up in here after
                                // having performed the API call adding the 
claim.
                                self.element.removeClass( 'wb-edit' );
@@ -363,8 +425,8 @@
                        revStore = wb.getRevisionStore(),
                        guid = this.value().getGuid(),
                        claim = new wb.Claim(
-                               self.$mainSnak.data( 'snakview' ).snak(),
-                               new wb.SnakList(), // TODO: Qualifiers
+                               this.$mainSnak.data( 'snakview' ).snak(),
+                               ( this._qualifiers ) ? this._qualifiers.value() 
: null,
                                guid
                        );
 
@@ -375,6 +437,7 @@
 
                        // Update model of represented Claim:
                        self._claim = savedClaim;
+                       self._qualifiers.value( savedClaim.getQualifiers() );
                } );
        },
 
@@ -465,4 +528,101 @@
        }
 } );
 
+// Register toolbars:
+$.wikibase.toolbarcontroller.definition( 'addtoolbar', {
+       id: 'claim-qualifiers-snak',
+       selector: '.wb-claim-qualifiers',
+       events: {
+               snaklistviewstartediting: 'create',
+               snaklistviewafterstopediting: 'destroy',
+               snaklistviewchange: function( event ) {
+                       var snaklistview = $( event.target ).data( 
'snaklistview' ),
+                               addToolbar = $( event.target ).data( 
'addtoolbar' );
+                       if ( addToolbar ) {
+                               addToolbar.toolbar[snaklistview.isValid() ? 
'enable' : 'disable']();
+                       }
+               },
+               snaklistviewdisable: function( event ) {
+                       $( event.target ).data( 'addtoolbar' 
).toolbar.disable();
+               },
+               snaklistviewenable: function( event ) {
+                       var addToolbar = $( event.target ).data( 'addtoolbar' );
+                       // "add" toolbar might be remove already.
+                       if ( addToolbar ) {
+                               addToolbar.toolbar.enable();
+                       }
+               },
+               'listviewitemadded listviewitemremoved': function( event ) {
+                       // Enable "add" link when all qualifiers have been 
removed:
+                       var $listviewNode = $( event.target ),
+                               listview = $listviewNode.data( 'listview' ),
+                               $snaklistviewNode = $listviewNode.closest( 
'.wb-snaklistview' ),
+                               snaklistview = $snaklistviewNode.data( 
'snaklistview' ),
+                               addToolbar = $snaklistviewNode.data( 
'addtoolbar' );
+
+                       // Disable "add" toolbar when the last qualifier has 
been removed:
+                       if ( !snaklistview.isValid() && listview.items().length 
) {
+                               addToolbar.toolbar.disable();
+                       } else {
+                               addToolbar.toolbar.enable();
+                       }
+               }
+       },
+       options: {
+               customAction: function( event, $parent ) {
+                       $parent.data( 'snaklistview' ).enterNewItem();
+               },
+               eventPrefix: 
$.wikibase.snaklistview.prototype.widgetEventPrefix,
+               addButtonLabel: mw.msg( 'wikibase-addqualifier' )
+       }
+} );
+
+$.wikibase.toolbarcontroller.definition( 'removetoolbar', {
+       id: 'claim-qualifiers-snak',
+       selector: '.wb-claim-qualifiers',
+       events: {
+               'snakviewstartediting snakviewcreate listviewitemadded 
listviewitemremoved': function( event ) {
+                       var $target = $( event.target ),
+                               listview = $target.closest( '.wb-snaklistview' 
).data( 'snaklistview' )._listview;
+
+                       if ( event.type.indexOf( 'snakview' ) !== -1 ) {
+                               // Create toolbar for each snakview widget:
+                               $target.removetoolbar( {
+                                       action: function( event ) {
+                                               listview.removeItem( $target );
+                                       }
+                               } );
+                       }
+               },
+               snaklistviewafterstopediting: function( event ) {
+                       // Destroy the snakview toolbars:
+                       var $snaklistviewNode = $( event.target ),
+                               listview = $snaklistviewNode.data( 
'snaklistview' )._listview,
+                               lia = listview.listItemAdapter();
+
+                       $.each( listview.items(), function( i, item ) {
+                               var snakview = lia.liInstance( $( item ) );
+                               if ( snakview.element.data( 'removetoolbar' ) ) 
{
+                                       snakview.element.data( 'removetoolbar' 
).destroy();
+                                       snakview.element.children( 
'.w-removetoolbar' ).remove();
+                               }
+                       } );
+               },
+               'snaklistviewdisable snaklistviewenable': function( event ) {
+                       var $snaklistviewNode = $( event.target ),
+                               listview = $snaklistviewNode.data( 
'snaklistview' )._listview,
+                               lia = listview.listItemAdapter(),
+                               action = ( event.type.indexOf( 'disable' ) !== 
-1 ) ? 'disable' : 'enable';
+
+                       $.each( listview.items(), function( i, item ) {
+                               var $item = $( item );
+                               // Item might be about to be removed not being 
a list item instance.
+                               if ( lia.liInstance( $item ) && $item.data( 
'removetoolbar' ) ) {
+                                       $item.data( 'removetoolbar' 
).toolbar[action]();
+                               }
+                       } );
+               }
+       }
+} );
+
 }( mediaWiki, wikibase, jQuery ) );
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
index b9d0a58..1b1ecc2 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
@@ -78,14 +78,16 @@
                                //       there could be other ways for entering 
edit mode than using the toolbar!
 
                                // Whether action shall influence sub-toolbars 
of origin:
-                               var exclusive;
-                               if ( options && typeof options.exclusive === 
'boolean' ) {
-                                       exclusive = options.exclusive;
+                               // TODO: "exclusive" option/variable restricts 
arrangement of toolbars. Interaction
+                               //       between toolbars should be managed via 
the toolbar controller.
+                               var originToolbars = null;
+                               if ( options ) {
+                                       if ( typeof options.exclusive === 
'boolean' && !options.exclusive ) {
+                                               originToolbars = $( origin 
).find( '.wb-ui-toolbar' );
+                                       } else if ( $.isArray( 
options.exclusive ) !== -1 ) {
+                                               originToolbars = $( origin 
).find( options.exclusive );
+                                       }
                                }
-
-                               var originToolbars = ( origin && exclusive === 
false )
-                                       ? $( origin ).find( '.wb-ui-toolbar' )
-                                       : null;
 
                                // find and disable/enable all toolbars in this 
edit view except,...
                                self.element.find( '.wb-ui-toolbar' ).each( 
function() {
@@ -117,7 +119,13 @@
                // TODO: this should rather listen to 'valueviewstartediting' 
once implemented!
                $( this.element )
                .on( 'statementviewafterstartediting', function( event ) {
-                       $( wb ).trigger( 'startItemPageEditMode', [ 
event.target, { exclusive: true } ] );
+                       $( wb ).trigger( 'startItemPageEditMode', [
+                               event.target,
+                               {
+                                       exclusive: '.wb-claim-qualifiers 
.wb-ui-toolbar',
+                                       wbCopyrightWarningGravity: 'sw'
+                               }
+                       ] );
                } )
                .on( 'referenceviewafterstartediting', function( event ) {
                        $( wb ).trigger(
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
index a54df92..628a5a2 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.referenceview.js
@@ -161,21 +161,20 @@
 $.wikibase.toolbarcontroller.definition( 'addtoolbar', {
        id: 'referenceview-snakview',
        selector: '.wb-statement-references .wb-referenceview',
-       eventPrefix: 'referenceview',
        events: {
-               startediting: 'create',
-               afterstopediting: 'destroy',
-               change: function( event ) {
+               referenceviewstartediting: 'create',
+               referenceviewafterstopediting: 'destroy',
+               referenceviewchange: function( event ) {
                        var referenceview = $( event.target ).data( 
'referenceview' ),
                                addToolbar = $( event.target ).data( 
'addtoolbar' );
                        if ( addToolbar ) {
                                addToolbar.toolbar[referenceview.isValid() ? 
'enable' : 'disable']();
                        }
                },
-               disable: function( event ) {
+               referenceviewdisable: function( event ) {
                        $( event.target ).data( 'addtoolbar' 
).toolbar.disable();
                },
-               enable: function( event ) {
+               referenceviewenable: function( event ) {
                        var addToolbar = $( event.target ).data( 'addtoolbar' );
                        // "add" toolbar might be remove already.
                        if ( addToolbar ) {
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
index aaa665d..644c2e1 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js
@@ -67,8 +67,7 @@
        options: {
                template: 'wb-snaklistview',
                templateParams: [
-                       '', // listview widget
-                       ''  // edit section DOM
+                       '' // listview widget
                ],
                templateShortCuts: {
                        '$listview': '.wb-snaklistview-listview'
@@ -157,7 +156,8 @@
                this._lia = this._listview.listItemAdapter();
 
                this.$listview
-               .on( 'listviewitemadded', function( event, value, $newLi ) {
+               .off( '.' + this.widgetName )
+               .on( 'listviewitemadded.' + this.widgetName, function( event, 
value, $newLi ) {
                        // Listen to all the snakview "change" events to be 
able to determine whether the
                        // snaklistview itself is valid.
                        $newLi.on( self._lia.prefixedEvent( 'change' ), 
function( event ) {
@@ -165,20 +165,20 @@
                                self._trigger( 'change' );
                        } );
                } )
-               .on( self._lia.prefixedEvent( 'change' ) + ' 
listviewitemremoved', function( event ) {
-                       // Forward the "change" event to external components 
(e.g. the edit toolbar).
-                       self._trigger( 'change' );
-               } )
-               .on( self._lia.prefixedEvent( 'stopediting' ),
+               .on( self._lia.prefixedEvent( 'change.' ) + this.widgetName
+                       + ' listviewitemremoved.' + this.widgetName, function( 
event ) {
+                               // Forward the "change" event to external 
components (e.g. the edit toolbar).
+                               self._trigger( 'change' );
+                       }
+               )
+               .on( self._lia.prefixedEvent( 'stopediting.' + this.widgetName 
),
                        function( event, dropValue, newSnak ) {
                                if (
-                                       !self.isValid() && 
!self.isInitialValue()
-                                       && !dropValue && 
!self.__continueSnakviewStopEditing
+                                       ( !self.isValid() || 
self.isInitialValue() )
+                                       && !dropValue && 
!self.__continueStopEditing
                                ) {
                                        event.preventDefault();
-                                       return;
-                               }
-                               if ( !self.__continueSnakviewStopEditing ) {
+                               } else if ( !self.__continueStopEditing ) {
                                        event.preventDefault();
                                        self.stopEditing( dropValue );
                                }
@@ -224,11 +224,7 @@
         */
        stopEditing: $.NativeEventHandler( 'stopEditing', {
                initially: function( e, dropValue ) {
-                       if(
-                               !this.isInEditMode()
-                               || !this.isValid() && !this.isInitialValue()
-                               && !dropValue && !this.__continueStopEditing
-                       ) {
+                       if( !this.isInEditMode() ) {
                                e.cancel();
                        }
 
@@ -240,8 +236,6 @@
                        if ( dropValue ) {
                                // If the whole item was pending, remove the 
whole list item. This has to be
                                // performed in the widget using the 
snaklistview.
-
-                               this.__continueSnakviewStopEditing = false;
 
                                // Re-create the list view to restore snakviews 
that have been removed during
                                // editing:
@@ -255,8 +249,6 @@
                        } else if ( this.option( 'value' ) !== null && 
this.__continueStopEditing ) {
                                var self = this;
 
-                               this.__continueStopEditing = false;
-                               this.__continueSnakviewStopEditing = true;
                                $.each( this._listview.items(), function( i, 
item ) {
                                        var $item = $( item ),
                                                snakview = 
self._lia.liInstance( $item );
@@ -266,7 +258,7 @@
                                        // After saving, the property should 
not be editable anymore.
                                        snakview.options.locked.property = true;
                                } );
-                               this.__continueSnakviewStopEditing = false;
+                               this.__continueStopEditing = false;
 
                                this.enable();
 
@@ -275,8 +267,7 @@
 
                                // Transform toolbar and snak view after save 
complete
                                this._trigger( 'afterstopediting', null, [ 
dropValue ] );
-                       } else if ( !this.option( 'value' ) ) {
-                               this.__continueStopEditing = false;
+                       } else if ( !this.option( 'value' ) && 
!this.__continueStopEditing ) {
                                // Creating a new snaklistview is managed in 
the object using the snaklistview (e.g.
                                // in the statementview). Creating a 
snaklistview will end up in here after having
                                // performed the API call adding the 
snaklistview's subject (e.g. the reference) to
@@ -312,7 +303,9 @@
                        if ( !( snakList instanceof wb.SnakList ) ) {
                                throw new Error( 'Value has to be an instance 
of wikibase.SnakList' );
                        }
+
                        this._snakList = snakList;
+                       this.createListView();
                        return this._snakList;
                }
                // getter:
@@ -339,10 +332,6 @@
        isValid: function() {
                var self = this,
                        isValid = true;
-
-               if ( this._listview.items().length === 0 ) {
-                       return false;
-               }
 
                $.each( this._listview.items(), function( i, item ) {
                        var snakview = self._lia.liInstance( $( item ) );
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
index 32e1149..6f44543 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
@@ -29,11 +29,13 @@
                        },
                        '', // TODO: This toolbar placeholder should be removed 
from the template.
                        '', // .wb-claim-mainsnak
+                       '', // Qualifiers
                        '', // Rreferences heading
                        '' // List of references
                ],
                templateShortCuts: {
                        '$mainSnak': '.wb-claim-mainsnak',
+                       '$qualifiers': '.wb-statement-qualifiers',
                        '$refsHeading': '.wb-statement-references-heading',
                        '$references': '.wb-statement-references'
                }
@@ -45,12 +47,6 @@
         * @type jQuery
         */
        $refsHeading: null,
-
-       /**
-        * DOM node holding the Statement's references.
-        * @type jQuery
-        */
-       $references: null,
 
        /**
         * Shortcut to the list item adapter in use in the reference view.
@@ -212,8 +208,8 @@
                                }
                        } )
                        .on( 'listviewenternewitem', function( event, $newLi ) {
-                                       // Enter first item into the 
referenceview.
-                                       self._referenceviewLia.liInstance( 
$newLi ).enterNewItem();
+                               // Enter first item into the referenceview.
+                               self._referenceviewLia.liInstance( $newLi 
).enterNewItem();
                        } );
 
                        // Forward query for listview reference.
@@ -255,8 +251,8 @@
                        revStore = wb.getRevisionStore(),
                        guid = this.value().getGuid(),
                        statement = new wb.Statement(
-                               self.$mainSnak.data( 'snakview' ).snak(),
-                               null, // TODO: Qualifiers
+                               this.$mainSnak.data( 'snakview' ).snak(),
+                               ( this._qualifiers ) ? this._qualifiers.value() 
: null,
                                this.getReferences(),
                                null, // TODO: Rank
                                guid
@@ -269,6 +265,7 @@
 
                                // Update model of represented Claim:
                                self._claim = newStatement;
+                               self._qualifiers.value( 
newStatement.getQualifiers() );
                        } );
        },
 
@@ -378,6 +375,23 @@
                name: 'wikibase.statementview',
                prototype: $.wikibase.statementview.prototype
        },
+       events: {
+               statementviewchange: function( event ) {
+                       var $target = $( event.target ),
+                               statementview = $target.data( 'statementview' ),
+                               enable = statementview.isValid() && 
!statementview.isInitialValue(),
+                               edittoolbar = $target.data( 'edittoolbar' );
+
+                       if ( statementview._qualifiers && (
+                               !statementview._qualifiers.isValid()
+                               || statementview._qualifiers.isInitialValue() 
&& statementview.isInitialValue()
+                       ) ) {
+                               enable = false
+                       }
+
+                       edittoolbar.editGroup.btnSave[ enable ? 'enable' : 
'disable' ]();
+               }
+       },
        options: {
                interactionWidgetName: 
$.wikibase.statementview.prototype.widgetName,
                toolbarParentSelector: '.wb-statement-claim'
diff --git 
a/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.definitions.js
 
b/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.definitions.js
index b45696c..9ef517b 100644
--- 
a/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.definitions.js
+++ 
b/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.definitions.js
@@ -75,9 +75,8 @@
         *          The selector to locate the node the toolbar shall be 
initialized on.
         *          If an interaction widget is set, the widget selector will 
be used.
         *        - eventPrefix
-        *          Prefix the events the toolbar shall listen to will be 
prefixed with. This refers to
-        *          the "create" event that should trigger the toolbar creation 
and any other events
-        *          that may be registered via the "events" attribute.
+        *          Prefix the events the toolbar shall listen to will be 
prefixed with. This only
+        *          refers to the "create" event that should trigger the 
toolbar creation.
         *          If an interaction widget is defined, the widget's event 
prefix is used as event
         *          prefix.
         *        - baseClass
diff --git 
a/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.js
 
b/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.js
index 93dc50d..c8ba56e 100644
--- 
a/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.js
+++ 
b/lib/resources/jquery.wikibase/jquery.wikibase.toolbarcontroller/toolbarcontroller.js
@@ -27,11 +27,15 @@
         * @since 0.4
         *
         * @option addtoolbar {string[]} List of toolbar definition ids/widget 
names that are registered
-        *         as "addtoolbars" and shall be initialized.
+        *         as "add" toolbars and shall be initialized.
         *         Default: []
         *
         * @option edittoolbar {string[]} List of toolbar definition ids/widget 
names that are
-        *         registered as "edittoolbars" and shall be initialized.
+        *         registered as "edit" toolbars and shall be initialized.
+        *         Default: []
+        *
+        * @option removetoolbar {string[]} List of toolbar definition 
ids/widget names that are
+        *         registered as "remove" toolbars and shall be initialized.
         *         Default: []
         */
        $.widget( 'wikibase.toolbarcontroller', {
@@ -41,7 +45,8 @@
                 */
                options: {
                        addtoolbar: [],
-                       edittoolbar: []
+                       edittoolbar: [],
+                       removetoolbar: []
                },
 
                /**
@@ -73,8 +78,6 @@
                                        var $initNode = self.element.find( 
def.selector || ':' + def.widget.fullName );
 
                                        if ( def.events ) {
-                                               var eventPrefix = 
def.eventPrefix
-                                                       || ( def.widget ? 
def.widget.prototype.widgetEventPrefix : '' );
 
                                                // Toolbars that shall be 
created upon certain events.
                                                $.each( def.events, function( 
eventName, callbackOrKeyword ) {
@@ -96,13 +99,16 @@
                                                        }
 
                                                        // Create and destroy 
events have to be defined.
-                                                       $initNode.on( 
eventPrefix + eventName, function( event ) {
+                                                       $initNode.on( 
eventName, function( event ) {
                                                                
callbackOrKeyword( event, $( event.target ) );
                                                        } );
                                                } );
-                                       } else {
+                                       }
+
+                                       if ( !def.events || def.widget ) {
                                                $initNode[type]( options );
                                        }
+
                                } );
                        } );
 
diff --git a/lib/resources/templates.php b/lib/resources/templates.php
index ba32dc9..804913b 100644
--- a/lib/resources/templates.php
+++ b/lib/resources/templates.php
@@ -69,6 +69,7 @@
        <div class="wb-claim-mainsnak" dir="auto">
                $3 <!-- wb-snak (Main Snak) -->
        </div>
+       <div class="wb-claim-qualifiers">$4</div>
 </div>
 HTML;
 
@@ -97,12 +98,13 @@
                        <div class="wb-claim-mainsnak" dir="auto">
                                $3 <!-- wb-snak (Main Snak) -->
                        </div>
+                       <div class="wb-claim-qualifiers 
wb-statement-qualifiers">$4</div>
                </div>
-               $4
+               $5
        </div>
        <div class="wb-statement-references-container">
-               <div class="wb-statement-references-heading">$5</div>
-               <div class="wb-statement-references">$6 <!-- [0,*] wb-reference 
--></div>
+               <div class="wb-statement-references-heading">$6</div>
+               <div class="wb-statement-references">$7 <!-- [0,*] wb-reference 
--></div>
        </div>
 </div>
 HTML;
diff --git a/lib/resources/wikibase.css b/lib/resources/wikibase.css
index c1c7254..d66d01f 100644
--- a/lib/resources/wikibase.css
+++ b/lib/resources/wikibase.css
@@ -598,6 +598,72 @@
 
 /***** /EDIT/NEW CLAIM *****/
 
+/***** QUALIFIERS *****/
+
+.wb-claim-qualifiers {
+       padding-left: 17em;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview 
.wb-snak-property {
+       width: 12em;
+       position: absolute;
+       font-size: 90%;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview 
.wb-snak-property input {
+       width: 100%;
+       font-size: 100%;
+       top: 0;
+       position: absolute;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview 
.wb-snak-value-container {
+       margin-left: 12em;
+       position: relative;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview 
.wb-snak-value-container .wb-snak-value {
+       margin-left: 16px;
+       margin-right: 18em;
+       word-wrap: break-word;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview 
.wb-snak-value-container .wb-snak-value .valueview-value {
+       font-size: 90%;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview 
.wb-snak-value-container .wb-snak-typeselector {
+       left: 0;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview 
.wb-snak-value-container .wb-snak-value .valueview-value textarea {
+       -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+}
+
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview .wb-snakview {
+       position: relative;
+       min-height: 1.8em;
+       padding-top: 5px;
+       padding-bottom: 5px;
+}
+
+/* "remove" link at each reference's snak */
+.wb-claim-qualifiers .wb-snaklistview .wb-snaklistview-listview .wb-snakview > 
.wb-removetoolbar {
+       position: absolute;
+       top: 5px;
+       right: 0;
+}
+
+/* "add" link in one reference's snak list */
+.wb-claim-qualifiers .wb-snaklistview > .wb-addtoolbar {
+       float: right;
+       margin-bottom: 1em;
+}
+
+/***** /QUALIFIERS *****/
+
 /********** /CLAIMS **********/
 
 
@@ -624,6 +690,7 @@
 
 .wb-statement-references-container {
        padding-left: 17em;
+       clear: both;
 }
 
 .wb-statement-references-container .wb-statement-references-heading {
diff --git a/repo/Wikibase.i18n.php b/repo/Wikibase.i18n.php
index 7c19609..00cc57e 100644
--- a/repo/Wikibase.i18n.php
+++ b/repo/Wikibase.i18n.php
@@ -27,6 +27,7 @@
        'wikibase-save' => 'save',
        'wikibase-cancel' => 'cancel',
        'wikibase-add' => 'add',
+       'wikibase-addqualifier' => 'add qualifier',
        'wikibase-addreference' => 'add source',
        'wikibase-save-inprogress' => 'Saving…',
        'wikibase-remove-inprogress' => 'Removing…',
@@ -430,6 +431,7 @@
        'wikibase-add' => '[[File:Screenshot WikidataRepo 2012-05-13 
F.png|right|0x150px]]
 [[File:Screenshot WikidataRepo 2012-05-13 A.png|right|0x150px]]
 This is a generic text used for a link (fig. 3 on 
[[m:Wikidata/Notes/JavaScript ui implementation]]) that puts the user interface 
into edit mode for an additional element of some kind.',
+       'wikibase-addqualifier' => 'Label of the link to add a qualifier (see 
[[d:Wikidata:Glossary]]) to a claim or statement.',
        'wikibase-addreference' => 'Label of the link to add a reference (see 
[[d:Wikidata:Glossary]]) to a statement.',
        'wikibase-save-inprogress' => '[[File:Screenshot WikidataRepo 
2012-05-25 L.png|right|350px]]
 [[File:Screenshot WikidataRepo 2012-05-25 J.png|right|350px]]
diff --git a/repo/includes/EntityView.php b/repo/includes/EntityView.php
index 9a213b5..422211a 100644
--- a/repo/includes/EntityView.php
+++ b/repo/includes/EntityView.php
@@ -509,6 +509,7 @@
                                        $additionalCssClasses,
                                        $claim->getGuid(),
                                        $mainSnakHtml,
+                                       '', // TODO: Qualifiers
                                        $this->getHtmlForEditSection( $entity, 
$lang, '', 'span' ), // TODO: add link to SpecialPage
                                        '', // TODO: References heading
                                        '' // TODO: References
diff --git a/repo/resources/wikibase.ui.entityViewInit.js 
b/repo/resources/wikibase.ui.entityViewInit.js
index 03283f5..4494c33 100644
--- a/repo/resources/wikibase.ui.entityViewInit.js
+++ b/repo/resources/wikibase.ui.entityViewInit.js
@@ -131,9 +131,9 @@
 
                        // BUILD TOOLBARS
                        $( '.wb-entity' ).toolbarcontroller( {
-                               addtoolbar: ['claimlistview', 'claimsection', 
'references', 'referenceview-snakview'],
+                               addtoolbar: ['claimlistview', 'claimsection', 
'claim-qualifiers-snak', 'references', 'referenceview-snakview'],
                                edittoolbar: ['statementview', 'referenceview'],
-                               removetoolbar: ['referenceview-snakview-remove']
+                               removetoolbar: ['claim-qualifiers-snak', 
'referenceview-snakview-remove']
                        } );
                }
 

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I236bdd0a7e9c8004a4b1a3c2eff83d690985cd05
Gerrit-PatchSet: 8
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Henning Snater <henning.sna...@wikimedia.de>
Gerrit-Reviewer: Siebrand <siebr...@wikimedia.org>
Gerrit-Reviewer: Tobias Gritschacher <tobias.gritschac...@wikimedia.de>
Gerrit-Reviewer: jenkins-bot

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

Reply via email to