Daniel Werner has submitted this change and it was merged.

Change subject: (bug 44745) Detaching toolbar from listview and claimlistview 
widget
......................................................................


(bug 44745) Detaching toolbar from listview and claimlistview widget

The change set adds a new addtoolbar widget which basically acts as a wrapper 
for a
widget offering add functionality (similar to the edittoolbar widget). All 
direct
toolbar references are removed from the listview and claimlistview widget with 
the
interaction between the listview widget and the toolbar widget being managed via
events.
Any toolbar initialization will be moved into a separate controller in an 
upcoming
change set.

Change-Id: I77272d012dc2b240178f42e01844008f1e09a329
---
M lib/resources/Resources.php
A lib/resources/jquery.wikibase/jquery.wikibase.addtoolbar.js
M lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
M lib/resources/jquery.wikibase/jquery.wikibase.edittoolbar.js
M lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
M lib/resources/jquery.wikibase/jquery.wikibase.listview.js
M lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
M lib/resources/wikibase.css
M selenium/lib/modules/statement_module.rb
9 files changed, 320 insertions(+), 257 deletions(-)

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



diff --git a/lib/resources/Resources.php b/lib/resources/Resources.php
index f29de55..a588255 100644
--- a/lib/resources/Resources.php
+++ b/lib/resources/Resources.php
@@ -318,12 +318,25 @@
                        )
                ),
 
+               'jquery.wikibase.addtoolbar' => $moduleTemplate + array(
+                       'scripts' => array(
+                               'jquery.wikibase/jquery.wikibase.addtoolbar.js',
+                       ),
+                       'dependencies' => array(
+                               'jquery.ui.widget',
+                               'wikibase.ui.Toolbar',
+                       ),
+                       'messages' => array(
+                               'wikibase-add'
+                       )
+               ),
+
                'jquery.wikibase.edittoolbar' => $moduleTemplate + array(
                        'scripts' => array(
                                
'jquery.wikibase/jquery.wikibase.edittoolbar.js',
                        ),
                        'dependencies' => array(
-                               'wikibase.ui.PropertyEditTool',
+                               'wikibase.ui.PropertyEditTool', // needs 
wikibase.ui.Toolbar.EditGroup
                        )
                ),
 
@@ -377,7 +390,7 @@
                        ),
                        'dependencies' => array(
                                'jquery.ui.TemplatedWidget',
-                               'jquery.wikibase.edittoolbar'
+                               'jquery.wikibase.edittoolbar',
                        )
                ),
 
@@ -446,6 +459,7 @@
                                
'jquery.wikibase/jquery.wikibase.statementview.js',
                        ),
                        'dependencies' => array(
+                               'jquery.wikibase.addtoolbar',
                                'jquery.wikibase.claimview',
                                'jquery.wikibase.listview',
                                'jquery.wikibase.referenceview',
@@ -477,6 +491,7 @@
                                'jquery.wikibase/jquery.wikibase.entityview.js'
                        ),
                        'dependencies' => array(
+                               'jquery.wikibase.addtoolbar',
                                'jquery.wikibase.statementview',
                                'jquery.wikibase.claimlistview',
                                'wikibase.templates'
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.addtoolbar.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.addtoolbar.js
new file mode 100644
index 0000000..9cec3fe
--- /dev/null
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.addtoolbar.js
@@ -0,0 +1,193 @@
+/**
+ * @file
+ * @ingroup WikibaseLib
+ *
+ * @licence GNU GPL v2+
+ * @author H. Snater < [email protected] >
+ */
+( function( mw, wb, $ ) {
+       'use strict';
+
+       /**
+        * "Add" toolbar widget
+        * @since 0.4
+        *
+        * This widget offers an "add" button which will allow interaction with 
a given widget.
+        * The widget the toolbar shall interact with has to have implemented 
certain methods listed in
+        * the _requiredMethods attribute.
+        *
+        * @option interactionWidgetName {string} Name of the widget the 
toolbar shall interact with.
+        *         (That widget needs to be initialized on the same DOM node 
this toolbar is initialized
+        *         on.) If the interactionWidgetName option is omitted, the 
toolbar will be initialized
+        *         as well but proper interaction is not ensured (see _create() 
method).
+        *         When omitting the interactionWidgetName option, the "action" 
option should be set.
+        *         Default value: null (no interaction widget)
+        *
+        * @option toolbarParentSelector {string} (required) jQuery selector to 
find the node the actual
+        *         toolbar buttons shall be appended to.
+        *
+        * @option action {function} Custom action the add button shall 
trigger. The action
+        *         will be triggered only when no interaction widget via the 
interactionWidgetName
+        *         option is set.
+        *         Default value: null (no custom action)
+        *
+        * @option eventPrefix {string} Custom event prefix the events the 
toolbar will listen to will
+        *         be prefixed with. If not set but an interaction widget is 
defined via the
+        *         interactionWidgetName option, the interaction widget's event 
prefix will be used.
+        *         Default value: '' (use interaction widget's event prefix or 
no prefix if no
+        *         interaction widget is defined.
+        *
+        * @option addButtonLabel {string} The add button's label
+        *         Default value: mw.msg( 'wikibase-add' )
+        */
+       $.widget( 'wikibase.addtoolbar', {
+               widgetName: 'wikibase-addtoolbar',
+               widgetBaseClass: 'wb-addtoolbar',
+
+               /**
+                * Options
+                * @type {Object}
+                */
+               options: {
+                       interactionWidgetName: null,
+                       toolbarParentSelector: null,
+                       action: null,
+                       eventPrefix: '',
+                       addButtonLabel: mw.msg( 'wikibase-add' )
+               },
+
+               /**
+                * Names of methods that are required in the interaction widget 
to ensure proper toolbar
+                * interaction.
+                * @type {string[]}
+                */
+               _requiredMethods: [
+                       'enterNewItem'
+               ],
+
+               /**
+                * The widget the toolbar interacts with.
+                * @type {Object}
+                */
+               _interactionWidget: null,
+
+               /**
+                * The visible toolbar elements' parent node.
+                * @type {jQuery}
+                */
+               $toolbarParent: null,
+
+               /**
+                * The toolbar object.
+                * @type {wb.ui.Toolbar}
+                */
+               toolbar: null,
+
+               /**
+                * @see jQuery.Widget._create
+                */
+               _create: function() {
+                       var self = this;
+
+                       if ( !this.options.toolbarParentSelector ) {
+                               throw new Error( 'jquery.wikibase.addtoolbar: 
Missing toolbar parent selector' );
+                       }
+
+                       this.$toolbarParent = this.element.find( 
this.options.toolbarParentSelector );
+
+                       this.toolbar = new wb.ui.Toolbar();
+                       this.toolbar.innerGroup = new wb.ui.Toolbar.Group();
+                       this.toolbar.btnAdd = new wb.ui.Toolbar.Button( 
this.options.addButtonLabel );
+                       this.toolbar.innerGroup.addElement( this.toolbar.btnAdd 
);
+                       this.toolbar.addElement( this.toolbar.innerGroup );
+
+                       if ( this.options.interactionWidgetName ) {
+                               this._interactionWidget = this.element.data( 
this.options.interactionWidgetName );
+
+                               var missingMethods = 
this.checkRequiredMethods();
+                               if ( missingMethods.length > 0 ) {
+                                       var m = missingMethods.join( ', ' );
+                                       throw new Error( 
'jquery.wikibase.addtoolbar: Missing required method(s) ' + m );
+                               }
+                       }
+
+                       /**
+                        * Prefixes an event name with the custom event prefix 
(if set in the options) or the
+                        * interaction's widget's event prefix.
+                        *
+                        * @param {string} eventName
+                        * @return {string}
+                        */
+                       function prefixed( eventName ) {
+                               var eventPrefix = self.options.eventPrefix;
+                               if ( eventPrefix === '' && 
self._interactionWidget ) {
+                                       eventPrefix = 
self._interactionWidget.widgetEventPrefix;
+                               }
+                               return eventPrefix + eventName;
+                       }
+
+                       // Register events for focusing "add" button.
+                       // TODO: replace this with more generic handling and 
perhaps don't use those events
+                       //  since they are rather specific
+                       this.element
+                       .on(
+                               prefixed( 'itemadded ' ) + prefixed( 
'itemremoved ' ) + prefixed( 'canceled' ),
+                               function( event, value, $node ) {
+                                       if ( value === null ) {
+                                               return;
+                                       }
+                                       if ( $node !== undefined && 
$node.parent()[0] !== self.element.parent()[0] ) {
+                                               // The event does not belong to 
this "add" button but rather to an "add"
+                                               // button encapsulated in a 
descendant node.
+                                               // TODO: This handling is 
rather specific for the claim section lists that
+                                               //  is built within the 
claimview widget.
+                                               $node.trigger( event.type );
+                                       } else {
+                                               
event.stopImmediatePropagation();
+                                               self.toolbar.btnAdd.setFocus();
+                                       }
+                               }
+                       );
+
+                       $( this.toolbar.btnAdd ).on( 'action', function( event 
) {
+                               if ( self._interactionWidget ) {
+                                       self._interactionWidget.enterNewItem();
+                               } else if ( self.options.action ) {
+                                       self.options.action();
+                               }
+                       } );
+
+                       this.toolbar.appendTo(
+                               $( '<div/>' ).addClass( 'wb-editsection' 
).appendTo( this.$toolbarParent )
+                       );
+               },
+
+               /**
+                * @see $.widget.destroy
+                */
+               destroy: function() {
+                       this.toolbar.destroy();
+                       $.Widget.prototype.destroy.call( this );
+               },
+
+               /**
+                * Checks whether all methods required in the interaction 
widget are defined and will return
+                * the names of any missing methods.
+                * @since 0.4
+                *
+                * @return {string[]}
+                */
+               checkRequiredMethods: function() {
+                       var self = this,
+                               missingMethods = [];
+                       $.each( this._requiredMethods, function( i, methodName 
) {
+                               if ( !$.isFunction( 
self._interactionWidget[methodName] ) ) {
+                                       missingMethods.push( methodName );
+                               }
+                       } );
+                       return missingMethods;
+               }
+
+       } );
+
+}( mediaWiki, wikibase, jQuery ) );
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
index d37ffc2..48d9901 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js
@@ -20,16 +20,18 @@
  *         will only display an add button, for adding new claims.
  *
  * @option {Function} listMembersWidget The constructor of the widget used for 
the list members.
+ *
+ * @event itemadded: Triggered after a list item got added to the list.
+ *        (1) {jQuery.Event}
+ *        (2) {wb.Snak|null} The snak added to the list.
+ *        (3) {jQuery} The DOM node with the widget, representing the value.
+ *
+ * @event canceled: Triggered after canceling the process of adding a new 
claim.
+ *        (1) {jQuery.Event}
  */
 $.widget( 'wikibase.claimlistview', PARENT, {
        widgetName: 'wikibase-claimlistview',
        widgetBaseClass: 'wb-claimlistview',
-
-       /**
-        * Node of the toolbar
-        * @type jQuery
-        */
-       $toolbar: null,
 
        /**
         * Section node containing the list of claims of the entity
@@ -48,8 +50,7 @@
                        ''
                ],
                templateShortCuts: {
-                       '$claims': '.wb-claims',
-                       '$toolbar': '.wb-claims-toolbar'
+                       '$claims': '.wb-claims'
                },
                entityId: null,
                value: null,
@@ -70,9 +71,8 @@
                PARENT.prototype._create.call( this );
 
                this._createClaims(); // build this.$claims
-               this._createToolbar();
 
-               // remove Claim (and eventually section) after remove event got 
triggered by its toolbar:
+               // remove Claim (and eventually section) after remove operation 
has finished
                this.element.on( lmwAfterRemoveEvent, function( e ) {
                        var $claim = $( e.target ),
                                $claimsSection = $claim.parents( 
'.wb-claim-section' );
@@ -80,6 +80,7 @@
                        self._lmwInstance( $claim ).destroy();
 
                        if( $claimsSection.find( '.' + self._lmwClass() 
).length > 0 ) {
+                               // TODO: Re-implement the following focus logic:
                                // Focus the next claim container's "edit" 
button. The user might want to alter that
                                // claim as well. Furthermore, the browser's 
viewport will not be scrolled when
                                // having a long claim section and directly 
focusing the section's "add" button.
@@ -190,37 +191,6 @@
        },
 
        /**
-        * Creates the toolbar holding the 'add' button for adding new claims.
-        * @since 0.3
-        */
-       _createToolbar: function() {
-               // display 'add' button at the end of the claims:
-               var self = this,
-                       toolbar = this._buildAddToolbar();
-
-               $( toolbar.btnAdd ).on( 'action', function( event ) {
-                       self.enterNewClaim();
-               } );
-
-               toolbar.appendTo( $( '<div/>' ).addClass( 'wb-editsection' 
).appendTo( this.$toolbar ) );
-       },
-
-       /**
-        * Creates a toolbar with an 'add' button
-        */
-       _buildAddToolbar: function() {
-               // display 'add' button at the end of the claims:
-               var toolbar = new wb.ui.Toolbar();
-
-               toolbar.innerGroup = new wb.ui.Toolbar.Group();
-               toolbar.btnAdd = new wb.ui.Toolbar.Button( mw.msg( 
'wikibase-add' ) );
-               toolbar.innerGroup.addElement( toolbar.btnAdd );
-               toolbar.addElement( toolbar.innerGroup );
-
-               return toolbar;
-       },
-
-       /**
         * Adds one claim to the list and renders it in the view.
         * @since 0.3
         *
@@ -252,7 +222,7 @@
                var $claimRows = this._serveClaimSection( 
claimSectionPropertyId );
 
                if ( this._lmwInstance( $content ) ) {
-                       $content.editToolbar( {
+                       $content.edittoolbar( {
                                interactionWidgetName: this._lmwInstance( 
$content ).widgetName,
                                toolbarParentSelector: '.wb-statement-claim 
.wb-claim-toolbar'
                        } );
@@ -286,8 +256,6 @@
                if( $section.length === 0 ) {
                        var property = wb.entities[ mainSnakPropertyId ],
                                $addClaim,
-                               $toolbarParent = $( '<span/>' ).addClass( 
'wb-editsection' ),
-                               toolbar = this._buildAddToolbar(),
                                self = this;
 
                        if( !property ) {
@@ -297,18 +265,12 @@
                                        + 'in local store' );
                        }
 
-                       // 'add' button at the end of each claim section:
-                       $( toolbar.btnAdd ).on( 'action', function( event ) {
-                               self.enterNewClaimInSection( mainSnakPropertyId 
);
-                       } );
-                       toolbar.appendTo( $toolbarParent );
-
                        // TODO: use another template for this, this isn't 
really a claim!
                        $addClaim = mw.template( 'wb-claim',
                                'wb-claim-add',
                                'add', // class='wb-claim-$2'
                                '', // .wb-claim-mainsnak
-                               $toolbarParent // .wb-claim-toolbar
+                               '' // .wb-claim-toolbar
                        );
 
                        // create new section for first Claim in this section
@@ -317,6 +279,12 @@
                                wb.utilities.ui.buildEntityLink( property ), // 
property name
                                $addClaim // claim
                        ).appendTo( this.$claims );
+
+                       $section.addtoolbar( {
+                               toolbarParentSelector: '.wb-claim-add 
.wb-claim-toolbar',
+                               action: function() { 
self.enterNewClaimInSection( mainSnakPropertyId ) },
+                               eventPrefix: this.widgetEventPrefix
+                       } );
                }
 
                return $section;
@@ -376,7 +344,7 @@
                // initialize view after node is in DOM, so the 'startediting' 
event can bubble
                this._lmwInstantiate( $newClaim, options ).element.addClass( 
'wb-claim-new' );
 
-               $newClaim.editToolbar( {
+               $newClaim.edittoolbar( {
                        interactionWidgetName: this._lmwInstance( $newClaim 
).widgetName,
                        toolbarParentSelector: '.wb-statement-claim 
.wb-claim-toolbar',
                        enableRemove: false
@@ -433,24 +401,13 @@
 
                                        self._lmwInstance( $( event.target ) 
).stopEditing( dropValue );
 
-                                       // TODO: Depending on how the actual 
interaction flow of adding a new
-                                       // claim will be, the focus should 
probably be set to somewhere more
-                                       // elegant like the new claim's "add 
qualifiers" link.
-                                       if ( $newClaim.parent()[0] === 
self.$claims[0] ) {
-                                               self.$toolbar.find( 
'.wb-ui-toolbar' )
-                                                       .data( 'wb-toolbar' 
).btnAdd.setFocus();
-                                       } else {
-                                               self.findClaimSection( 
$newClaim )
-                                                       .find( '.wb-claim-add 
.wb-ui-toolbar' )
-                                                       .data( 'wb-toolbar' 
).btnAdd.setFocus();
-                                       }
+                                       self._trigger( 'itemadded', null, [ 
newSnak, $newClaim ] );
 
                                        // destroy new claim input form and add 
claim to this list
                                        self._lmwInstance( $newClaim 
).destroy();
                                        $newClaim.remove();
                                        self._addClaim( newClaimWithGUID ); // 
display new claim with final GUID
                                        // TODO: add newly created claim to 
model of represented entity!
-
                                } )
                                .fail( function( errorCode, details ) {
                                        var claimview = self._lmwInstance( 
$newClaim ),
@@ -462,27 +419,29 @@
                                        claimview.element.addClass( 'wb-error' 
);
 
                                        claimview._trigger( 'toggleerror', 
null, [error] );
-
                                } );
                        }
                } )
                .on( this._lmwEvent( 'afterstopediting' ), function( event, 
dropValue ) {
                        if( dropValue || !self._lmwInstance( $newClaim 
).$mainSnak.data( 'snakview' ).snak() ) {
                                // if new claim is canceled before saved, or if 
it is invalid, we simply remove
-                               // and forget about it after having figured out 
which "add" link to re-set the
-                               // focus on
-                               if ( $newClaim.parent()[0] === self.$claims[0] 
) {
-                                       self.$toolbar.find( '.wb-ui-toolbar' 
).data( 'wb-toolbar' ).btnAdd.setFocus();
-                               } else {
-                                       self.findClaimSection( $newClaim 
).find( '.wb-claim-add .wb-ui-toolbar' )
-                                               .data( 'wb-toolbar' 
).btnAdd.setFocus();
-                               }
+                               // and forget about it
+                               self._trigger( 'canceled', null, [ $newClaim ] 
);
+
                                self._lmwInstance( $newClaim ).destroy();
                                $newClaim.remove();
                                self.element.find( '.wb-claim-section' )
                                        .not( ':has( >.wb-edit )' 
).removeClass( 'wb-edit' );
                        }
                } );
+       },
+
+       /**
+        * Initializes adding a new item to the list; At the moment, just a 
generically named shortcut
+        * to this.enterNewClaim() which may be used by external components 
(e.g. a toolbar).
+        */
+       enterNewItem: function() {
+               this.enterNewClaim();
        },
 
        /**
@@ -495,68 +454,8 @@
         */
        enterNewClaimInSection: function( sectionPropertyId ) {
                this.enterNewClaim( sectionPropertyId );
-       },
-
-       /**
-        * Will save a new Claim to the represented entity. Does all the 
related API action.
-        * @since 0.4
-        *
-        * @param {jQuery} $newClaim List node representing the value
-        * @param {wb.Snak} mainSnak Main Snak of the Claim to be saved
-        */
-       _saveNewClaim: function( $newClaim, mainSnak ) {
-               var api = new wb.RepoApi(),
-                       self = this;
-
-               api.createClaim(
-                       self.option( 'entityId' ),
-                       wb.getRevisionStore().getBaseRevision(),
-                       mainSnak
-               )
-               .done( function( newClaimWithGUID, pageInfo ) {
-                       wb.getRevisionStore().setClaimRevision(
-                               pageInfo.lastrevid,
-                               newClaimWithGUID.getGuid()
-                       );
-
-                       // Continue stopEditing event by triggering it again 
skipping
-                       // claimlistview's API call.
-                       self.__continueStopEditing = true;
-
-                       self._lmwInstance( $( event.target ) ).stopEditing( 
dropValue );
-
-                       // TODO: Depending on how the actual interaction flow 
of adding a new
-                       // claim will be, the focus should probably be set to 
somewhere more
-                       // elegant like the new claim's "add qualifiers" link.
-                       if ( $newClaim.parent()[0] === self.$claims[0] ) {
-                               self.$toolbar.find( '.wb-ui-toolbar' )
-                                       .data( 'wb-toolbar' ).btnAdd.setFocus();
-                       } else {
-                               self.findClaimSection( $newClaim )
-                                       .find( '.wb-claim-add .wb-ui-toolbar' )
-                                       .data( 'wb-toolbar' ).btnAdd.setFocus();
-                       }
-
-                       // destroy new claim input form and add claim to this 
list
-                       self._lmwInstance( $newClaim ).destroy();
-                       $newClaim.remove();
-                       self._addClaim( newClaimWithGUID ); // display new 
claim with final GUID
-                       // TODO: add newly created claim to model of 
represented entity!
-               } )
-               .fail( function( errorCode, details ) {
-                       var claimview = self._lmwInstance( $newClaim ),
-                               toolbar = $newClaim.find( '.wb-ui-toolbar' 
).data( 'wb-toolbar' ),
-                               btnSave = toolbar.editGroup.btnSave,
-                               error = wb.RepoApiError.newFromApiResponse(
-                                       errorCode, details, 'save'
-                               );
-
-                       claimview.toggleActionMessage( function() {
-                               claimview.displayError( error, btnSave );
-                               claimview.enable();
-                       } );
-               } );
        }
+
 } );
 
 }( mediaWiki, wikibase, jQuery ) );
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.edittoolbar.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.edittoolbar.js
index db16376..39ee47c 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.edittoolbar.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.edittoolbar.js
@@ -9,10 +9,10 @@
        'use strict';
 
        /**
-        * Edit toolbar widget
+        * "Edit" toolbar widget
         * @since 0.4
         *
-        * This widget offers a generic edit toolbar which will allow 
editing-related interaction with
+        * This widget offers a generic "edit" toolbar which will allow 
editing-related interaction with
         * a given widget.
         * The widget the toolbar shall interact with has to have implemented 
certain methods listed in
         * the _requiredMethods attribute. The interaction widget may have an 
isEmpty() method which
@@ -22,19 +22,19 @@
         * The toolbar will also trigger a "toggleerror" event on the 
interaction widget when the error
         * tooltip that will show up gets hidden.
         *
-        * @option interactionWidgetName {string} Name of the widget the 
toolbar shall interact with.
-        *         (That widget needs to be initialized on the same DOM node 
this toolbar is initialized
-        *         on.)
+        * @option interactionWidgetName {string} (required) Name of the widget 
the toolbar shall
+        *         interact with. (That widget needs to be initialized on the 
same DOM node this toolbar
+        *         is initialized on.)
         *
-        * @option toolbarParentSelector {string} CSS3 selector to find the 
node the actual toolbar
-        *         buttons shall be appended to.
+        * @option toolbarParentSelector {string} (required) jQuery selector to 
find the node the actual
+        *         toolbar buttons shall be appended to.
         *
-        * @option enableRemove {boolean} (optional) Whether a remove button 
shall be shown. Regardless
-        *         of setting this options, the "remove" button will not be 
shown if the interaction
-        *         object has no "remove" method.
+        * @option enableRemove {boolean} Whether a remove button shall be 
shown. Regardless of setting
+        *         this options, the "remove" button will not be shown if the 
interaction object has no
+        *         "remove" method.
         *         Default value: true
         */
-       $.widget( 'wikibase.editToolbar', {
+       $.widget( 'wikibase.edittoolbar', {
                widgetName: 'wikibase-edittoolbar',
                widgetBaseClass: 'wb-edittoolbar',
 
@@ -149,7 +149,7 @@
 
                        var prefix = this._interactionWidget.widgetEventPrefix;
 
-                       this._interactionWidget.element
+                       this.element
                        .on( prefix + 'afterstartediting', function( event ) {
                                self.editGroup.toEditMode();
                        } )
@@ -195,6 +195,15 @@
                },
 
                /**
+                * @see $.widget.destroy
+                */
+               destroy: function() {
+                       this.editGroup.destroy();
+                       this.toolbar.destroy();
+                       $.Widget.prototype.destroy.call( this );
+               },
+
+               /**
                 * Checks whether all methods required in the interaction 
widget are defined and will return
                 * the names of any missing methods.
                 * @since 0.4
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
index a4d9538..a7818ae 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.entityview.js
@@ -49,6 +49,11 @@
                        listMembersWidget: $.wikibase.statementview
                } );
 
+               this.$claims.addtoolbar( {
+                       interactionWidgetName: 'claimlistview',
+                       toolbarParentSelector: '.wb-claims-toolbar'
+               } );
+
                // append all the stuff:
                // NOTE: doing this here will prevent events from bubbling 
during widget initializations!
                //       Shouldn't harm and will increase performance because 
DOM needs to render once only.
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.listview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.listview.js
index 8dd8e2b..256cb94 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.listview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.listview.js
@@ -22,10 +22,7 @@
  * @option {jQuery.wikibase.listview.ListItemAdapter} listItemAdapter 
(required) Can not
  *         be changed after initialization.
  *
- * @option {boolean|string} showAddButton Whether or not a 'add' button should 
be displayed for the
- *         user to add new values to the list. If this is a string, it defines 
the button's label.
- *
- * @event addlistitem Triggered before a list item will be added to the list.
+ * @event additem: Triggered before a list item will be added to the list.
  *        (1) {jQuery.Event}
  *        (2) {*|null} The value the new list item will represent. This can 
also be null in case a
  *            new, empty list item, not yet representing any value but ready 
for the user to enter
@@ -34,34 +31,25 @@
  *            be initialized. The widget will be initialized on this DOM node 
after the DOM node is
  *            appended to the list, so events can bubble during widget 
initialization.
  *
- * @event listitemadded Triggered after a list item got added to the list.
+ * @event itemadded: Triggered after a list item got added to the list.
  *        (1) {jQuery.Event}
  *        (2) {*|null} The value the new list item is representing. null for 
empty value.
  *        (3) {jQuery} The DOM node with the widget, representing the value.
  *
- * @event removelistitem Triggered before a list item will be removed from the 
list.
+ * @event removeitem: Triggered before a list item will be removed from the 
list.
  *        (1) {jQuery.Event}
  *        (2) {*|null} The value of the list item which will be removed. null 
for empty value.
  *        (3) {jQuery} The list item's DOM node, which will be removed.
  *
- * @event listitemremoved Triggered after a list got removed from the list.
+ * @event itemremoved: Triggered after a list got removed from the list.
  *        (1) {jQuery.Event}
  *        (2) {*|null} The value of the list item which will be removed. null 
for empty value.
  *        (3) {jQuery} The list item's DOM node, removed.
+ *
+ * @event canceled: Triggered after canceling the process of adding a new item.
+ *        (1) {jQuery.Event}
  */
 $.widget( 'wikibase.listview', PARENT, {
-       /**
-        * Node of the toolbar
-        * @type jQuery
-        */
-       $toolbar: null,
-
-       /**
-        * The toolbar object
-        * @type wb.Toolbar
-        */
-       _toolbar: null,
-
        /**
         * Section node containing the list items
         * @type jQuery
@@ -85,12 +73,10 @@
                        '' // toolbar
                ],
                templateShortCuts: {
-                       '$listItems': '.wb-listview-items',
-                       '$toolbar': '.wb-listview-toolbar'
+                       '$listItems': '.wb-listview-items'
                },
                value: null,
-               listItemAdapter: null,
-               showAddButton: true
+               listItemAdapter: null
        },
 
        /**
@@ -113,24 +99,11 @@
                PARENT.prototype._create.call( this );
 
                this._createList(); // fill this.$listItems
-               this._createToolbar();
 
-               // remove list item after remove event got triggered by its 
toolbar:
+               // remove list item after remove operation has finished
                this.element.on( liAfterRemoveEvent, function( e ) {
                        var $listItem = $( e.target );
-
-                       // Focus the next list item's "edit" button. The user 
might want to alter that list
-                       // item as well.
-                       /*
-                       var nextToolbar = $listItem.next().find( 
'.wb-ui-toolbar' ).data( 'wb-toolbar' );
-                       if( $listItem.next().hasClass( 'wb-claim-add' ) ) { // 
TODO: claim specific!
-                               nextToolbar.btnAdd.setFocus();
-                       } else {
-                               nextToolbar.editGroup.btnEdit.setFocus();
-                       }
-                       */
-                       self._toolbar.btnAdd.setFocus();
-                       self.removeListItem( $listItem );
+                       self.removeItem( $listItem );
                } );
        },
 
@@ -149,12 +122,6 @@
        _setOption: function( key, value ) {
                if( key === 'listItemAdapter' ) {
                        throw new Error( 'Can not change the ListItemAdapter 
after initialization' );
-               } else if( key === 'showAddButton' ) {
-                       var toolbar = this._toolbar,
-                               addBtnLabel = typeof value === 'string' ? value 
: mw.msg( 'wikibase-add' );
-
-                       toolbar.btnAdd.setContent( addBtnLabel );
-                       toolbar.innerGroup[ value ? 'addElement' : 
'removeElement' ]( toolbar.btnAdd );
                }
                PARENT.prototype._setOption.call( this, key, value );
        },
@@ -170,43 +137,8 @@
 
                // initialize view for each of the list item values:
                for( i in items ) {
-                       this.addListItem( items[i] );
+                       this.addItem( items[i] );
                }
-       },
-
-       /**
-        * Creates the toolbar holding the 'add' button for adding new list 
items.
-        *
-        * @since 0.4
-        */
-       _createToolbar: function() {
-               // display 'add' button at the end of the list:
-               var self = this,
-                       toolbar = this._buildToolbar();
-
-               $( toolbar.btnAdd ).on( 'action', function( event ) {
-                       self.enterNewListItem();
-               } );
-
-               toolbar.appendTo( $( '<div/>' ).addClass( 'wb-editsection' 
).appendTo( this.$toolbar ) );
-               this._toolbar = toolbar;
-
-               // will append 'add' button to DOM or not, depending on related 
option:
-               this._setOption( 'showAddButton', this.options.showAddButton );
-       },
-
-       /**
-        * Creates a toolbar with an 'add' button
-        */
-       _buildToolbar: function() {
-               // display 'add' button at the end of the list:
-               var toolbar = new wb.ui.Toolbar();
-
-               toolbar.innerGroup = new wb.ui.Toolbar.Group();
-               toolbar.btnAdd = new wb.ui.Toolbar.Button( mw.msg( 
'wikibase-add' ) );
-               toolbar.addElement( toolbar.innerGroup );
-
-               return toolbar;
        },
 
        /**
@@ -216,7 +148,7 @@
         *
         * @return {jQuery}
         */
-       listItems: function() {
+       items: function() {
                return this.$listItems.children();
        },
 
@@ -227,9 +159,9 @@
         *
         * @return {jQuery}
         */
-       nonEmptyListItems: function() {
+       nonEmptyItems: function() {
                var lia = this._lia;
-               return this.listItems().filter( function( i ) {
+               return this.items().filter( function( i ) {
                        return !!lia.liValue( $( this ) );
                } );
        },
@@ -246,14 +178,14 @@
         * Adds one list item into the list and renders it in the view.
         * @since 0.4
         *
-        * @triggers addlistitem
-        * @triggers listitemadded If default was not prevented by 
'addlistitem' event.
+        * @triggers additem
+        * @triggers itemadded If default was not prevented by 'additem' event.
         *
         * @param {*} value
         * @return {jQuery} The DOM node representing the value. If default was 
prevented in the
-        *         'addlistitem' event, the node will be returned even though 
not appended to the list.
+        *         'additem' event, the node will be returned even though not 
appended to the list.
         */
-       addListItem: $.NativeEventHandler( 'addListItem', {
+       addItem: $.NativeEventHandler( 'additem', {
                initially: function( event, value ) {
                        // in custom handlers, we provide the DOM node without 
initialized value widget because
                        // we want to initialize widget AFTER the node is in 
the DOM, so we can have events
@@ -267,13 +199,13 @@
                        this.$listItems.append( $newLi );
                        this._lia.newListItem( $newLi, value );
 
-                       $newLi.editToolbar( {
+                       $newLi.edittoolbar( {
                                interactionWidgetName: this._lia.liInstance( 
$newLi ).widgetName,
                                toolbarParentSelector: '.wb-claim-toolbar',
                                enableRemove: !!value
                        } );
 
-                       this._trigger( 'listitemadded', null, [ value, $newLi ] 
);
+                       this._trigger( 'itemadded', null, [ value, $newLi ] );
                }
        } ),
 
@@ -281,12 +213,12 @@
         * Removes one list item from the list and renders the update in the 
view.
         * @since 0.4
         *
-        * @triggers removelistitem
-        * @triggers listitemremoved If default was not prevented by 
'removelistitems' event.
+        * @triggers removeitem
+        * @triggers itemremoved If default was not prevented by 'removeitem' 
event.
         *
         * @param {jQuery} $itemNode The list item's node to be removed
         */
-       removeListItem: $.NativeEventHandler( 'removeListItem', {
+       removeItem: $.NativeEventHandler( 'removeitem', {
                initially: function( event, $itemNode ) {
                        // check whether given node actually is in this list. 
If not, fail!
                        if( !$itemNode.parent( this.$listItems ).length ) {
@@ -302,9 +234,12 @@
                natively: function( event, value, $itemNode ) {
                        // destroy widget representing the list item's value 
and remove node from list:
                        this._lia.liInstance( $itemNode ).destroy();
-                       $itemNode.remove();
 
-                       this._trigger( 'listitemremoved', null, [ value, 
$itemNode ] );
+                       // Trigger itemremoved event before removing the DOM 
node to be able to query for its
+                       // context.
+                       this._trigger( 'itemremoved', null, [ value, $itemNode 
] );
+
+                       $itemNode.remove();
                }
        } ),
 
@@ -314,9 +249,9 @@
         *
         * @since 0.4
         */
-       enterNewListItem: function() {
+       enterNewItem: function() {
                var self = this,
-                       $newLi = this.addListItem();
+                       $newLi = this.addItem();
 
                this._lia.liInstance( $newLi ).startEditing();
 
@@ -364,12 +299,10 @@
 
                                        self._lia.liInstance( $( event.target ) 
).stopEditing( dropValue );
 
-                                       self._toolbar.btnAdd.setFocus();
-
                                        // Replace the new list item which was 
initially initialized as empty by
                                        // destroying it and adding a new list 
item with the value provided by the API.
-                                       self.removeListItem( $newLi );
-                                       self.addListItem( newRefWithHash ); // 
display new reference with final hash
+                                       self.removeItem( $newLi );
+                                       self.addItem( newRefWithHash ); // 
display new reference with final hash
                                } )
                                .fail( function( errorCode, details ) {
                                        var listview = self._lia.liInstance( 
$newLi ),
@@ -386,16 +319,16 @@
                } )
                .on( this._lia.prefixedEvent( 'afterstopediting' ), function( 
event, dropValue ) {
                        if( dropValue || !self._lia.liInstance( $newLi 
).$mainSnak.data( 'snakview' ).snak() ) {
-                               // set focus on 'add' button:
-                               self._toolbar.btnAdd.setFocus();
-
-                               self.removeListItem( $newLi );
+                               self.removeItem( $newLi );
 
                                self.element.find( '.wb-claim-section' )
                                        .not( ':has( >.wb-edit )' 
).removeClass( 'wb-edit' );
+
+                               self._trigger( 'canceled' );
                        }
                } );
        }
+
 } );
 
 }( mediaWiki, wikibase, jQuery ) );
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
index 0487448..fc25b13 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js
@@ -89,12 +89,18 @@
                                statementGuid: statementGuid, // not set for 
new Statement!
                                value: refs,
                                showAddButton: mw.msg( 'wikibase-addreference' )
-                       } ).on( 'listviewlistitemadded 
listviewlistitemremoved', function( e, value, $li ) {
+                       } ).on( 'listviewitemadded listitemremoved', function( 
e, value, $li ) {
                                self.drawReferencesCounter();
                        } );
 
+                       this.$references.addtoolbar( {
+                               interactionWidgetName: 'listview',
+                               toolbarParentSelector: '.wb-listview-toolbar',
+                               addButtonLabel: mw.msg( 'wikibase-addreference' 
)
+                       } );
+
                        // Collapse references if there is at least one.
-                       if ( 
this.$references.data('listview').listItems().length > 0 ) {
+                       if ( this.$references.data( 'listview' ).items().length 
> 0 ) {
                                this.$references.css( 'display', 'none' );
                        }
 
@@ -147,8 +153,8 @@
         */
        drawReferencesCounter: function() {
                var listView = this.$references.data( 'listview' ),
-                       numberOfValues = listView.nonEmptyListItems().length,
-                       numberOfPendingValues = listView.listItems().length - 
numberOfValues;
+                       numberOfValues = listView.nonEmptyItems().length,
+                       numberOfPendingValues = listView.items().length - 
numberOfValues;
 
                // build a nice counter, displaying fixed and pending values:
                var $counterMsg = wb.utilities.ui.buildPendingCounter(
diff --git a/lib/resources/wikibase.css b/lib/resources/wikibase.css
index 3d710f2..fb96e95 100644
--- a/lib/resources/wikibase.css
+++ b/lib/resources/wikibase.css
@@ -490,6 +490,9 @@
        background-color: #F8F8F8;
        min-height: 3em;
 }
+div.wb-claim-section .wb-claim-add div.wb-editsection {
+       position: absolute;
+}
 
 /* General add new claim link */
 div.wb-claims-section div.wb-editsection {
diff --git a/selenium/lib/modules/statement_module.rb 
b/selenium/lib/modules/statement_module.rb
index 46b22a1..f4410d6 100644
--- a/selenium/lib/modules/statement_module.rb
+++ b/selenium/lib/modules/statement_module.rb
@@ -12,7 +12,7 @@
   include ReferencePage
   # statements UI elements
   link(:addStatement, :xpath => "//div[contains(@class, 
'wb-claims-toolbar')]/div/span/span/a")
-  link(:addClaimToFirstStatement, :xpath => "//div[contains(@class, 
'wb-claim-section')][1]/div[contains(@class, 
'wb-claim-add')]/div[contains(@class, 'wb-claim-toolbar')]/span/span/span/a")
+  link(:addClaimToFirstStatement, :xpath => "//div[contains(@class, 
'wb-claim-section')][1]/div[contains(@class, 
'wb-claim-add')]/div[contains(@class, 'wb-claim-toolbar')]/div/span/span/a")
   link(:editFirstStatement, :xpath => "//div[contains(@class, 
'wb-claim-toolbar')]/span/span/span[contains(@class, 
'wb-ui-toolbar-editgroup-innoneditmode')]/span/a")
   link(:saveStatement, :xpath => "//div[contains(@class, 
'wb-claim-toolbar')]/span/span/span[contains(@class, 
'wb-ui-toolbar-editgroup-ineditmode')]/span/a[text()='save']")
   link(:cancelStatement, :xpath => "//div[contains(@class, 
'wb-claim-toolbar')]/span/span/span[contains(@class, 
'wb-ui-toolbar-editgroup-ineditmode')]/span/a[text()='cancel']")

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I77272d012dc2b240178f42e01844008f1e09a329
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Henning Snater <[email protected]>
Gerrit-Reviewer: Daniel Werner <[email protected]>

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

Reply via email to