Henning Snater has uploaded a new change for review.

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

Change subject: Implemented jQuery.wikibase.descriptionview
......................................................................

Implemented jQuery.wikibase.descriptionview

jQuery.wikibase.descriptionview replaces DescriptionEditTool and 
EditableDescription

Change-Id: I3bbc60aac809e5268e38ecca3013ae4fc0b66fde
---
M lib/WikibaseLib.hooks.php
M lib/resources/Resources.php
M lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js
A lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js
A 
lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css
M lib/resources/jquery.wikibase/toolbar/edittoolbar.js
M lib/resources/templates.php
M lib/resources/wikibase.css
D lib/resources/wikibase.ui.DescriptionEditTool.js
D lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js
M lib/resources/wikibase.ui.PropertyEditTool.css
A lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js
D lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js
D lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js
M repo/includes/EntityView.php
M repo/resources/Resources.php
M repo/resources/wikibase.ui.entityViewInit.js
M repo/resources/wikibase.ui.initTermBox.js
18 files changed, 1,031 insertions(+), 488 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase 
refs/changes/88/154388/4

diff --git a/lib/WikibaseLib.hooks.php b/lib/WikibaseLib.hooks.php
index 65505d9..f7c859a 100644
--- a/lib/WikibaseLib.hooks.php
+++ b/lib/WikibaseLib.hooks.php
@@ -83,10 +83,8 @@
                                
'tests/qunit/wikibase.RepoApi/wikibase.RepoApi.tests.js',
                                
'tests/qunit/wikibase.RepoApi/wikibase.RepoApiError.tests.js',
 
-                               
'tests/qunit/wikibase.ui.DescriptionEditTool.tests.js',
                                
'tests/qunit/wikibase.ui.LabelEditTool.tests.js',
                                
'tests/qunit/wikibase.ui.PropertyEditTool.tests.js',
-                               
'tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js',
                                
'tests/qunit/wikibase.ui.PropertyEditTool.EditableLabel.tests.js',
                                
'tests/qunit/wikibase.ui.PropertyEditTool.EditableValue.tests.js',
                                
'tests/qunit/wikibase.ui.PropertyEditTool.EditableValue.Interface.tests.js',
@@ -163,6 +161,15 @@
                        ),
                );
 
+               $testModules['qunit']['jquery.wikibase.descriptionview.tests'] 
= $moduleBase + array(
+                       'scripts' => array(
+                               
'tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js',
+                       ),
+                       'dependencies' => array(
+                               'jquery.wikibase.descriptionview',
+                       ),
+               );
+
                $testModules['qunit']['jquery.wikibase.listview.tests'] = 
$moduleBase + array(
                        'scripts' => array(
                                
'tests/qunit/jquery.wikibase/jquery.wikibase.listview.tests.js',
diff --git a/lib/resources/Resources.php b/lib/resources/Resources.php
index 8d3aab4..dc36178 100644
--- a/lib/resources/Resources.php
+++ b/lib/resources/Resources.php
@@ -299,10 +299,8 @@
                                'wikibase.ui.PropertyEditTool.js',
                                'wikibase.ui.PropertyEditTool.EditableValue.js',
                                
'wikibase.ui.PropertyEditTool.EditableValue.Interface.js',
-                               
'wikibase.ui.PropertyEditTool.EditableDescription.js',
                                'wikibase.ui.PropertyEditTool.EditableLabel.js',
                                'wikibase.ui.LabelEditTool.js',
-                               'wikibase.ui.DescriptionEditTool.js',
                        ),
                        'styles' => array(
                                'wikibase.ui.PropertyEditTool.css'
@@ -487,6 +485,23 @@
                        ),
                ),
 
+               'jquery.wikibase.descriptionview' => $moduleTemplate + array(
+                       'scripts' => array(
+                               
'jquery.wikibase/jquery.wikibase.descriptionview.js'
+                       ),
+                       'styles' => array(
+                               
'jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css',
+                       ),
+                       'dependencies' => array(
+                               'jquery.inputautoexpand',
+                               'jquery.ui.TemplatedWidget',
+                               'jquery.wikibase.edittoolbar',
+                               'jquery.wikibase.toolbarcontroller',
+                               'wikibase',
+                               'wikibase.RepoApiError',
+                       ),
+               ),
+
                'jquery.wikibase.sitelinkgroupview' => $moduleTemplate + array(
                        'scripts' => array(
                                
'jquery.wikibase/jquery.wikibase.sitelinkgroupview.js'
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js
index 866defa..2fa0374 100644
--- a/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js
@@ -66,7 +66,8 @@
 
                PARENT.prototype._create.call( this );
 
-               var value = this.options.value;
+               var self = this,
+                       value = this.options.value;
 
                if(
                        value && value.aliases.length
@@ -79,7 +80,7 @@
                // TODO: Move that code to a sensible place (see 
jQuery.wikibase.entityview):
                .on( 'aliasesviewafterstartediting.' + this.widgetName, 
function( event ) {
                        $( wb ).trigger( 'startItemPageEditMode', [
-                               event.target,
+                               self.element,
                                {
                                        exclusive: false,
                                        wbCopyrightWarningGravity: 'sw'
@@ -88,7 +89,7 @@
                } )
                .on( 'aliasesviewafterstopediting.' + this.widgetName, 
function( event, dropValue ) {
                        $( wb ).trigger( 'stopItemPageEditMode', [
-                               event.target,
+                               self.element,
                                { save: dropValue !== true }
                        ] );
                } );
diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js 
b/lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js
new file mode 100644
index 0000000..07b654b
--- /dev/null
+++ b/lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js
@@ -0,0 +1,447 @@
+/**
+ * @licence GNU GPL v2+
+ * @author H. Snater < mediaw...@snater.com >
+ */
+( function( $, mw, wb ) {
+       'use strict';
+
+       var PARENT = $.ui.TemplatedWidget;
+
+/**
+ * Manages a description.
+ * @since 0.5
+ * @extends jQuery.ui.TemplatedWidget
+ *
+ * @option {Object|null} value
+ *         Object representing the widget's value.
+ *         Structure: { language: <{string}>, description: <{string|null}> }
+ *
+ * @option {string} [helpMessage]
+ *         Default: mw.msg( 'wikibase-description-input-help-message' )
+ *
+ * @options {string} entityId
+ *
+ * @option {wikibase.RepoApi} api
+ *
+ * @option {wikibase.store.EntityStore} entityStore
+ */
+$.widget( 'wikibase.descriptionview', PARENT, {
+       /**
+        * @see jQuery.ui.TemplatedWidget.options
+        */
+       options: {
+               template: 'wikibase-descriptionview',
+               templateParams: [
+                       '', // additional class
+                       '', // text
+                       '' // toolbar
+               ],
+               templateShortCuts: {
+                       '$text': '.wikibase-descriptionview-text'
+               },
+               value: null,
+               helpMessage: mw.msg( 'wikibase-description-input-help-message' 
),
+               entityId: null,
+               api: null
+       },
+
+       /**
+        * @type {boolean}
+        */
+       _isInEditMode: false,
+
+       /**
+        * @see jQuery.ui.TemplatedWidget._create
+        *
+        * @throws {Error} if required parameters are not specified properly.
+        */
+       _create: function() {
+               if( !this.options.entityId || !this.options.api ) {
+                       throw new Error( 'Required parameter(s) missing' );
+               }
+
+               this.options.value = this._checkValue( this.options.value );
+
+               PARENT.prototype._create.call( this );
+
+               var self = this,
+                       value = this.options.value;
+
+               if( value && value.description !== '' && this.$text.text() === 
'' ) {
+                       this._draw();
+               }
+
+               this.element
+               // TODO: Move that code to a sensible place (see 
jQuery.wikibase.entityview):
+               .on(
+                       'descriptionviewafterstartediting.' + this.widgetName
+                       + ' eachchange.' + this.widgetName,
+               function( event ) {
+                       if( !self.value().description ) {
+                               // Since the widget shall not be in view mode 
when there is no value, triggering
+                               // the event without a proper value is only 
done when creating the widget. Disabling
+                               // other edit buttons shall be avoided.
+                               // TODO: Move logic to a sensible place.
+                               self.element.addClass( 'wb-empty' );
+                               return;
+                       }
+
+                       self.element.removeClass( 'wb-empty' );
+
+                       $( wb ).trigger( 'startItemPageEditMode', [
+                               self.element,
+                               {
+                                       exclusive: false,
+                                       wbCopyrightWarningGravity: 'sw'
+                               }
+                       ] );
+               } )
+               .on(
+                       'descriptionviewafterstopediting.' + this.widgetName
+                               + ' eachchange.' + this.widgetName,
+               function( event, dropValue ) {
+                       if(
+                               event.type !== 'eachchange'
+                               || !self.options.value.description && 
!self.value().description
+                       ) {
+                               $( wb ).trigger( 'stopItemPageEditMode', [
+                                       self.element,
+                                       { save: dropValue !== true }
+                               ] );
+                       }
+               } );
+       },
+
+       /**
+        * @see jQuery.ui.TemplatedWidget.destroy
+        */
+       destroy: function() {
+               if( this._isInEditMode ) {
+                       var self = this;
+
+                       this.element.one( this.widgetEventPrefix + 
'afterstopediting', function( event ) {
+                               PARENT.prototype.destroy.call( self );
+                       } );
+
+                       this.cancelEditing();
+               } else {
+                       PARENT.prototype.destroy.call( this );
+               }
+       },
+
+       /**
+        * Main draw routine.
+        */
+       _draw: function() {
+               if( !this._isInEditMode ) {
+                       this.element.removeClass( 'wb-edit' );
+                       this.$text.text( this.options.value.description );
+                       return;
+               }
+
+               // TODO: Inject correct placeholder via options
+               var self = this,
+                       languageName = wb.getLanguageNameByCode( 
this.options.value.language ),
+                       placeholder = mw.msg( 
'wikibase-description-edit-placeholder' );
+
+               if( languageName ) {
+                       placeholder = mw.msg(
+                               
'wikibase-description-edit-placeholder-language-aware',
+                               languageName
+                       );
+               }
+
+               var $input = $( '<input/>' )
+               .attr( 'placeholder', placeholder )
+               .on( 'eachchange.' + this.widgetName, function( event ) {
+                       self._trigger( 'change' );
+               } );
+
+               if( this.options.value.description ) {
+                       $input.val( this.options.value.description );
+               }
+
+               this.element.addClass( 'wb-edit' );
+               this.$text.empty().append( $input );
+       },
+
+       /**
+        * Starts the widget's edit mode.
+        */
+       startEditing: function() {
+               if( this._isInEditMode ) {
+                       return;
+               }
+
+               this._isInEditMode = true;
+               this._draw();
+
+               this._trigger( 'afterstartediting' );
+       },
+
+       /**
+        * Stops the widget's edit mode.
+        *
+        * @param {boolean} dropValue
+        */
+       stopEditing: function( dropValue ) {
+               var self = this;
+
+               if( !this._isInEditMode || ( !this.isValid() || 
this.isInitialValue() ) && !dropValue ) {
+                       return;
+               }
+
+               if( dropValue ) {
+                       this._afterStopEditing( dropValue );
+                       return;
+               }
+
+               this.disable();
+
+               this._trigger( 'stopediting', null, [dropValue] );
+
+               // TODO: Performing API interaction should be managed in parent 
component (probably
+               // entityview)
+               this._save()
+               .done( function( response ) {
+                       wb.getRevisionStore().setDescriptionRevision( 
response.entity.lastrevid );
+                       self.enable();
+                       self._afterStopEditing( dropValue );
+               } )
+               .fail( function( errorCode, details ) {
+                       // TODO: API should return an Error object
+                       var error = wb.RepoApiError.newFromApiResponse( 
errorCode, details, 'save' );
+                       self.setError( error );
+                       self.enable();
+               } );
+       },
+
+       /**
+        * @return {jQuery.Promise}
+        */
+       _save: function() {
+               return this.options.api.setDescription(
+                       this.options.entityId,
+                       wb.getRevisionStore().getDescriptionRevision(),
+                       this.value().description || '',
+                       this.options.value.language
+               );
+       },
+
+       /**
+        * Cancels the widget's edit mode.
+        */
+       cancelEditing: function() {
+               this.stopEditing( true );
+       },
+
+       /**
+        * Callback tearing down edit mode.
+        *
+        * @param {boolean} dropValue
+        */
+       _afterStopEditing: function( dropValue ) {
+               if( !dropValue ) {
+                       this.options.value = this.value();
+               } else if( !this.options.value.description ) {
+                       this.$text.children( 'input' ).val( '' );
+                       this._trigger( 'change' );
+               }
+
+               this._isInEditMode = false;
+               this._draw();
+
+               this._trigger( 'afterstopediting', null, [dropValue] );
+       },
+
+       /**
+        * @return {boolean}
+        */
+       isValid: function() {
+               // Function is required by edittoolbar definition.
+               return true;
+       },
+
+       /**
+        * @return {boolean}
+        */
+       isInitialValue: function() {
+               var initialValue = this.options.value,
+                       currentValue = this.value();
+
+               return currentValue.language === initialValue.language
+                       && currentValue.description === 
initialValue.description;
+       },
+
+       /**
+        * Toggles error state.
+        *
+        * @param {Error} error
+        */
+       setError: function( error ) {
+               if( error ) {
+                       this.element.addClass( 'wb-error' );
+                       this._trigger( 'toggleerror', null, [error] );
+               } else {
+                       this.element.removeClass( 'wb-error' );
+                       this._trigger( 'toggleerror' );
+               }
+       },
+
+       /**
+        * @see jQuery.ui.TemplatedWidget._setOption
+        */
+       _setOption: function( key, value ) {
+               if( key === 'value' ) {
+                       value = this._checkValue( value );
+               }
+               return PARENT.prototype._setOption.call( this, key, value );
+       },
+
+       /**
+        * @param {*} value
+        * @return {Object}
+        *
+        * @throws {Error} if value is not defined properly.
+        */
+       _checkValue: function( value ) {
+               if( !$.isPlainObject( value ) ) {
+                       throw new Error( 'Value needs to be an object' );
+               } else if( !value.language ) {
+                       throw new Error( 'Value needs language to be specified' 
);
+               }
+
+               if( !value.description ) {
+                       value.description = null;
+               }
+
+               return value;
+       },
+
+       /**
+        * Gets/Sets the widget's value.
+        *
+        * @param {Object} [value]
+        * @return {Object|undefined}
+        */
+       value: function( value ) {
+               if( value !== undefined ) {
+                       this.option( 'value', value );
+                       return;
+               }
+
+               if( !this._isInEditMode ) {
+                       return this.option( 'value' );
+               }
+
+               var text = $.trim( this.$text.children( 'input' ).val() );
+
+               return {
+                       language: this.options.value.language,
+                       description: text !== '' ? text : null
+               };
+       },
+
+       /**
+        * Puts Keyboard focus on the widget.
+        */
+       focus: function() {
+               if( this._isInEditMode ) {
+                       this.$text.children( 'input' ).focus();
+               }
+       },
+
+       /**
+        * @see jQuery.ui.TemplatedWidget.disable
+        */
+       disable: function() {
+               if( this._isInEditMode ) {
+                       this.$text.children( 'input' ).prop( 'disabled', true );
+               }
+
+               return PARENT.prototype.disable.call( this );
+       },
+
+       /**
+        * @see jQuery.ui.TemplatedWidget.enable
+        */
+       enable: function() {
+               if( this._isInEditMode ) {
+                       this.$text.children( 'input' ).prop( 'disabled', false 
);
+               }
+
+               return PARENT.prototype.enable.call( this );
+       }
+
+} );
+
+$.wikibase.toolbarcontroller.definition( 'edittoolbar', {
+       id: 'descriptionview',
+       events: {
+               descriptionviewcreate: function( event, toolbarcontroller ) {
+                       var $descriptionview = $( event.target ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' );
+
+                       $descriptionview.edittoolbar( {
+                               $container: $descriptionview.find( 
'.wikibase-descriptionview-container' ),
+                               interactionWidgetName: 
$.wikibase.descriptionview.prototype.widgetName,
+                               enableRemove: false
+                       } );
+
+                       $descriptionview.on( 'keyup', function( event ) {
+                               if( event.keyCode === $.ui.keyCode.ESCAPE ) {
+                                       descriptionview.stopEditing( true );
+                               } else if( event.keyCode === $.ui.keyCode.ENTER 
) {
+                                       descriptionview.stopEditing( false );
+                               }
+                       } );
+
+                       if( !descriptionview.value().description ) {
+                               descriptionview.startEditing();
+                       }
+
+                       $descriptionview
+                       .off( 'descriptionviewafterstopediting.edittoolbar' )
+                       .on( 'descriptionviewafterstopediting', function( event 
) {
+                               var edittoolbar = $( event.target ).data( 
'edittoolbar' );
+                               if( descriptionview.value().description ) {
+                                       
edittoolbar.toolbar.editGroup.toNonEditMode();
+                               } else {
+                                       descriptionview.startEditing();
+                               }
+
+                               edittoolbar.enable();
+                               edittoolbar.toggleActionMessage( function() {
+                                       
edittoolbar.toolbar.editGroup.getButton( 'edit' ).focus();
+                               } );
+                       } );
+               },
+               'descriptionviewchange descriptionviewafterstartediting': 
function( event ) {
+                       var $descriptionview = $( event.target ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' ),
+                               toolbar = $descriptionview.data( 'edittoolbar' 
).toolbar,
+                               $btnSave = toolbar.editGroup.getButton( 'save' 
),
+                               btnSave = $btnSave.data( 'toolbarbutton' ),
+                               enable = descriptionview.isValid() && 
!descriptionview.isInitialValue(),
+                               $btnCancel = toolbar.editGroup.getButton( 
'cancel' ),
+                               btnCancel = $btnCancel.data( 'toolbarbutton' ),
+                               currentDescription = 
descriptionview.value().description,
+                               disableCancel = !currentDescription && 
descriptionview.isInitialValue();
+
+                       btnSave[enable ? 'enable' : 'disable']();
+                       btnCancel[disableCancel ? 'disable' : 'enable']();
+               },
+               toolbareditgroupedit: function( event, toolbarcontroller ) {
+                       var $descriptionview = $( event.target ).closest( 
':wikibase-edittoolbar' ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' );
+
+                       if( !descriptionview ) {
+                               return;
+                       }
+
+                       descriptionview.focus();
+               }
+       }
+} );
+
+}( jQuery, mediaWiki, wikibase ) );
diff --git 
a/lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css
 
b/lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css
new file mode 100644
index 0000000..5ad3b4d
--- /dev/null
+++ 
b/lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css
@@ -0,0 +1,53 @@
+/**
+ * @licence GNU GPL v2+
+ * @author H. Snater < mediaw...@snater.com >
+ */
+
+.wikibase-descriptionview {
+       float: left;
+       margin-top: -2px; /* move to top decreasing space between label and 
description */
+       padding-left: 10px;
+       width: 100%;
+}
+
+div.wikibase-descriptionview.wb-edit {
+       background-color: #D6F3FF;
+}
+div.wikibase-descriptionview.wb-empty {
+       background-color: inherit;
+}
+
+.wikibase-descriptionview .wikibase-descriptionview-container {
+       position: relative;
+}
+
+.wikibase-descriptionview .wb-editsection {
+       line-height: 2;
+       position: absolute;
+       right: 10px;
+       top: 0.1em;
+}
+
+.wikibase-descriptionview.wb-edit .wb-editsection {
+       right: 7px;
+}
+
+.wikibase-descriptionview.wb-edit {
+       padding-left: 7px;
+}
+
+.wikibase-descriptionview .wikibase-descriptionview-text {
+       display: block;
+       padding: 0.1em 19em 0.1em 0;
+       line-height: 2; /* force height to be able to align toolbar */
+}
+
+.wikibase-descriptionview .wikibase-descriptionview-text > div {
+       display: inline;
+}
+
+.wikibase-descriptionview input {
+       width: 100%;
+       padding: 0 2px;
+       font-size: 1em; /* prevent IE from automatically resizing the font 
within the input box */
+}
diff --git a/lib/resources/jquery.wikibase/toolbar/edittoolbar.js 
b/lib/resources/jquery.wikibase/toolbar/edittoolbar.js
index c7c80b2..6d3df82 100644
--- a/lib/resources/jquery.wikibase/toolbar/edittoolbar.js
+++ b/lib/resources/jquery.wikibase/toolbar/edittoolbar.js
@@ -197,7 +197,7 @@
                                        self.toggleActionMessage( { message: 
'wikibase-save-inprogress' } );
                                }
                        } )
-                       .on( prefix + 'afterstopediting', function( event ) {
+                       .on( prefix + 'afterstopediting.' + this.widgetName, 
function( event ) {
                                editGroup.toNonEditMode();
                                self.enable();
                                self.toggleActionMessage( function() {
diff --git a/lib/resources/templates.php b/lib/resources/templates.php
index 72ff2dd..813b00c 100644
--- a/lib/resources/templates.php
+++ b/lib/resources/templates.php
@@ -169,11 +169,13 @@
 <h1 id="wb-firstHeading-$1" class="wb-firstHeading wb-value-row">$2</h1>
 HTML;
 
-       $templates['wb-description'] =
+       $templates['wikibase-descriptionview'] =
 <<<HTML
-<div class="wb-property-container wb-value-row wb-description" dir="auto">
-       <div class="wb-property-container-key" title="description"></div>
-       $1
+<div class="wikibase-descriptionview $1" dir="auto">
+       <div class="wikibase-descriptionview-container">
+               <span class="wikibase-descriptionview-text">$2</span>
+               <!-- wikibase-toolbar -->$3
+       </div>
 </div>
 HTML;
 
diff --git a/lib/resources/wikibase.css b/lib/resources/wikibase.css
index 7425325..a6c905b 100644
--- a/lib/resources/wikibase.css
+++ b/lib/resources/wikibase.css
@@ -162,8 +162,7 @@
 
 /********** LABEL & DESCRIPTION **********/
 
-.wb-ui-labeledittool .wb-value-container,
-.wb-ui-descriptionedittool .wb-value-container {
+.wb-ui-labeledittool .wb-value-container {
        padding-right: 19em; /* width of toolbar + white space border */
 }
 
@@ -240,29 +239,6 @@
 
 /********** /LABEL **********/
 
-/********** DESCRIPTION **********/
-
-.wb-description {
-       margin-top: -2px; /* move to top decreasing space between label and 
description */
-}
-
-.wb-description .wb-value-container {
-       padding-left: 10px;
-       padding-top: 0.1em;
-       padding-bottom: 0.1em;
-}
-
-.wb-description .wb-value {
-       line-height: 2; /* force height to be able to align toolbar */
-}
-
-.wb-description .wb-editsection {
-       top: 0.1em; /* see padding-top of editablevalue container */
-       line-height: 2;
-}
-
-/********** /DESCRIPTION **********/
-
 /********** /LABEL & DESCRIPTION **********/
 
 
@@ -317,13 +293,11 @@
        background: #F8F8F8;
 }
 
-table.wb-terms td.wb-terms-label,
-table.wb-terms td.wb-terms-description {
+table.wb-terms td.wb-terms-label {
        padding: 10px;
 }
 
-table.wb-terms .wb-edit td.wb-terms-label,
-table.wb-terms .wb-edit td.wb-terms-description {
+table.wb-terms .wb-edit td.wb-terms-label {
        padding: 8px 8px 9px 7px;
 }
 
@@ -354,6 +328,26 @@
        width: 100%;
 }
 
+.wb-terms .wikibase-descriptionview .wikibase-descriptionview-text {
+       padding-right: 0;
+}
+
+.wb-terms .wikibase-descriptionview {
+       display: block;
+       float: none;
+       margin-top: 0;
+       padding: 10px;
+       width: auto;
+}
+
+.wb-terms .wikibase-descriptionview.wb-edit {
+       padding: 9px 8px 9px 7px;
+}
+
+.wb-terms .wb-error {
+       background-color: #FFDFC9;
+}
+
 /********** /TERMS **********/
 
 
diff --git a/lib/resources/wikibase.ui.DescriptionEditTool.js 
b/lib/resources/wikibase.ui.DescriptionEditTool.js
deleted file mode 100644
index 0fec694..0000000
--- a/lib/resources/wikibase.ui.DescriptionEditTool.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @licence GNU GPL v2+
- * @author Daniel Werner < daniel.werner at wikimedia.de >
- */
-( function( mw, wb, util, $ ) {
-'use strict';
-var PARENT = wb.ui.PropertyEditTool;
-
-/**
- * Module for 'Wikibase' extensions user interface functionality for editing 
the description of an item.
- *
- * @constructor
- * @see wb.ui.PropertyEditTool
- * @since 0.1
- */
-wb.ui.DescriptionEditTool = util.inherit( PARENT, {
-       /**
-        * @see wb.ui.SECONDARY_UI_CLASSES
-        */
-       SECONDARY_UI_CLASSES: PARENT.prototype.SECONDARY_UI_CLASSES + ' 
wb-ui-descriptionedittool',
-
-       /**
-        * @see wb.ui.PropertyEditTool._init()
-        */
-       _init: function( subject, options ) {
-               // setting default options
-               options = $.extend( {}, PARENT.prototype._options, {
-                       /**
-                        * @see 
wikibase.ui.PropertyEditTool.allowsMultipleValues
-                        */
-                       allowsMultipleValues: false
-               }, options );
-               PARENT.prototype._init.call( this, subject, options );
-       },
-
-       /**
-        * @see wb.ui.PropertyEditTool.getEditableValuePrototype
-        */
-       getEditableValuePrototype: function() {
-               return wb.ui.PropertyEditTool.EditableDescription;
-       }
-
-} );
-
-} )( mediaWiki, wikibase, util, jQuery );
diff --git a/lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js 
b/lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js
deleted file mode 100644
index 3c1242a..0000000
--- a/lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * @licence GNU GPL v2+
- * @author Daniel Werner
- * @author Tobias Gritschacher
- */
-( function( mw, wb, util, $ ) {
-'use strict';
-
-var PARENT = wb.ui.PropertyEditTool.EditableValue;
-
-/**
- * Serves the input interface for an item description, extends EditableValue.
- * @constructor
- * @extends wb.ui.PropertyEditTool.EditableValue
- * @since 0.1
- */
-var SELF = wb.ui.PropertyEditTool.EditableDescription = util.inherit( PARENT, {
-       /**
-        * @see wikibase.ui.PropertyEditTool.EditableValue.API_VALUE_KEY
-        */
-       API_VALUE_KEY: 'descriptions',
-
-       /**
-        * @see wb.ui.PropertyEditTool.EditableValue._options
-        */
-       _options: $.extend( {}, PARENT.prototype._options, {
-               inputHelpMessageKey: 'wikibase-description-input-help-message'
-       } ),
-
-       /**
-        * @see 
wikibase.ui.PropertyEditTool.EditableValue._setRevisionIdFromApiResponse
-        */
-       _setRevisionIdFromApiResponse: function( response ) {
-               wb.getRevisionStore().setDescriptionRevision( 
response.lastrevid );
-               return true;
-       },
-
-       /**
-        * Calling the corresponding method in the wikibase.RepoApi
-        *
-        * @return {jQuery.Promise}
-        */
-       queryApi: function() {
-               return this._api.setDescription(
-                       mw.config.get( 'wbEntityId' ),
-                       wb.getRevisionStore().getDescriptionRevision(),
-                       this.getValue().toString(),
-                       this.getValueLanguageContext()
-               );
-       }
-} );
-
-/**
- * @see wb.ui.PropertyEditTool.EditableValue.newFromDom
- */
-SELF.newFromDom = function( subject, options, toolbar ) {
-       var ev = wb.ui.PropertyEditTool.EditableValue,
-               $subject = $( subject ),
-               $interfaceParent = $subject.children( '.wb-value' ).first(),
-               languageName, placeHolderMsg, simpleInterface;
-
-       options = options || {};
-       options.valueLanguageContext =
-               options.valueLanguageContext || 
ev.getValueLanguageContextFromDom( $interfaceParent );
-
-       languageName = wb.getLanguageNameByCode( options.valueLanguageContext );
-
-       if ( languageName ) {
-               placeHolderMsg = mw.msg(
-                       'wikibase-description-edit-placeholder-language-aware',
-                       languageName
-               );
-       } else {
-               placeHolderMsg = mw.msg( 
'wikibase-description-edit-placeholder' );
-       }
-
-       simpleInterface = new ev.Interface( $interfaceParent, {
-               'inputPlaceholder': placeHolderMsg,
-               'autoExpand': false
-       } );
-
-       return new SELF( $subject, options, simpleInterface, toolbar );
-};
-
-}( mediaWiki, wikibase, util, jQuery ) );
diff --git a/lib/resources/wikibase.ui.PropertyEditTool.css 
b/lib/resources/wikibase.ui.PropertyEditTool.css
index 81f9820..d335f53 100644
--- a/lib/resources/wikibase.ui.PropertyEditTool.css
+++ b/lib/resources/wikibase.ui.PropertyEditTool.css
@@ -78,25 +78,6 @@
 /***** /LABEL *****/
 
 
-/***** DESCRIPTION *****/
-
-.wb-ui-descriptionedittool .wb-ui-propertyedittool-editablevalue-ineditmode {
-       margin-right: .3em;
-       padding-left: 7px;
-}
-
-.wb-ui-descriptionedittool .wb-ui-propertyedittool-editablevalue-ineditmode 
.wb-value {
-       padding: 0;
-}
-
-.wb-ui-descriptionedittool input {
-       width: 100%;
-       padding: 0 2px;
-       font-size: 1em; /* prevent IE from automatically resizing the font 
within the input box */
-}
-
-/***** /DESCRIPTION *****/
-
 /********** TAGADATA **********/
 
 ul.tagadata {
diff --git 
a/lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js 
b/lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js
new file mode 100644
index 0000000..dd1bfcb
--- /dev/null
+++ b/lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js
@@ -0,0 +1,191 @@
+/**
+ * @licence GNU GPL v2+
+ * @author H. Snater < mediaw...@snater.com >
+ */
+
+( function( $, jQuery, QUnit ) {
+'use strict';
+
+/**
+ * @param {Object} [options]
+ * @param {jQuery} [$node]
+ * @return {jQuery}
+ */
+var createDescriptionview = function( options, $node ) {
+       options = $.extend( {
+               entityId: 'i am an entity id',
+               api: 'i am an api',
+               value: {
+                       language: 'en',
+                       description: 'test description'
+               }
+       }, options || {} );
+
+       $node = $node || $( '<div/>' ).appendTo( 'body' );
+
+       var $descriptionview = $node
+               .addClass( 'test_descriptionview' )
+               .descriptionview( options );
+
+       $descriptionview.data( 'descriptionview' )._save = function() {
+               return $.Deferred().resolve( {
+                       entity: {
+                               lastrevid: 'i am a revision id'
+                       }
+               } ).promise();
+       };
+
+       return $descriptionview;
+};
+
+QUnit.module( 'jquery.wikibase.descriptionview', QUnit.newMwEnvironment( {
+       teardown: function() {
+               $( '.test_descriptionview' ).each( function() {
+                       var $descriptionview = $( this ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' );
+
+                       if( descriptionview ) {
+                               descriptionview.destroy();
+                       }
+
+                       $descriptionview.remove();
+               } );
+       }
+} ) );
+
+QUnit.test( 'Create & destroy', function( assert ) {
+       assert.throws(
+               function() {
+                       createDescriptionview( { value: null } );
+               },
+               'Throwing error when trying to initialize widget without a 
value.'
+       );
+
+       var $descriptionview = createDescriptionview(),
+               descriptionview = $descriptionview.data( 'descriptionview' );
+
+       assert.ok(
+               descriptionview !== 'undefined',
+               'Created widget.'
+       );
+
+       descriptionview.destroy();
+
+       assert.ok(
+               $descriptionview.data( 'descriptionview' ) === undefined,
+               'Destroyed widget.'
+       );
+} );
+
+QUnit.test( 'startEditing() & stopEditing()', 5, function( assert ) {
+       var $descriptionview = createDescriptionview(),
+               descriptionview = $descriptionview.data( 'descriptionview' );
+
+       $descriptionview
+       .on( 'descriptionviewafterstartediting', function( event ) {
+               assert.ok(
+                       true,
+                       'Started edit mode.'
+               );
+       } )
+       .on( 'descriptionviewafterstopediting', function( event, dropValue ) {
+               assert.ok(
+                       true,
+                       'Stopped edit mode.'
+               );
+       } );
+
+       descriptionview.startEditing();
+
+       assert.ok(
+               descriptionview.$text.find( 'input' ).length === 1,
+               'Generated input element.'
+       );
+
+       descriptionview.startEditing(); // should not trigger event
+       descriptionview.stopEditing( true );
+       descriptionview.stopEditing( true ); // should not trigger event
+       descriptionview.stopEditing(); // should not trigger event
+       descriptionview.startEditing();
+
+       descriptionview.$text.find( 'input' ).val( '' );
+
+       descriptionview.stopEditing();
+} );
+
+QUnit.test( 'isInitialValue()', function( assert ) {
+       var $descriptionview = createDescriptionview(),
+               descriptionview = $descriptionview.data( 'descriptionview' );
+
+       descriptionview.startEditing();
+
+       assert.ok(
+               descriptionview.isInitialValue(),
+               'Verified isInitialValue() returning true.'
+       );
+
+       descriptionview.$text.find( 'input' ).val( 'changed' );
+
+       assert.ok(
+               !descriptionview.isInitialValue(),
+               'Verified isInitialValue() returning false after changing 
value.'
+       );
+
+       descriptionview.$text.find( 'input' ).val( 'test description' );
+
+       assert.ok(
+               descriptionview.isInitialValue(),
+               'Verified isInitialValue() returning true after resetting to 
initial value.'
+       );
+} );
+
+QUnit.test( 'setError()', function( assert ) {
+       var $descriptionview = createDescriptionview(),
+               descriptionview = $descriptionview.data( 'descriptionview' );
+
+       $descriptionview
+       .on( 'descriptionviewtoggleerror', function( event, error ) {
+               assert.ok(
+                       true,
+                       'Triggered "toggleerror" event.'
+               );
+       } );
+
+       descriptionview.setError();
+} );
+
+QUnit.test( 'value()', function( assert ) {
+       var $descriptionview = createDescriptionview(),
+               descriptionview = $descriptionview.data( 'descriptionview' );
+
+       assert.throws(
+               function() {
+                       descriptionview.value( null );
+               },
+               'Trying to set no value fails.'
+       );
+
+       descriptionview.value( {
+               language: 'de',
+               description: 'changed description'
+       } );
+
+       assert.ok(
+               descriptionview.value().language === 'de'
+                       && descriptionview.value().description === 'changed 
description',
+               'Set new value.'
+       );
+
+       descriptionview.value( {
+               language: 'en',
+               description: null
+       } );
+
+       assert.ok(
+               descriptionview.value().language === 'en'
+                       && descriptionview.value().description === null,
+               'Set another value.'
+       );
+} );
+
+}( jQuery, wikibase, QUnit ) );
diff --git a/lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js 
b/lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js
deleted file mode 100644
index 72b9414..0000000
--- a/lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * QUnit tests description edit tool
- * @see https://www.mediawiki.org/wiki/Extension:Wikibase
- *
- * @since 0.1
- *
- * @licence GNU GPL v2+
- * @author H. Snater <mediaw...@snater.com>
- */
-
-( function( mw, wb, $, QUnit, undefined ) {
-       'use strict';
-
-       QUnit.module( 'wikibase.ui.DescriptionEditTool', 
QUnit.newWbEnvironment( {
-               setup: function() {
-                       this.parentNode = $( '<div/>' );
-                       this.text = 'Text';
-                       this.node = $( '<div/>' ).append( $( '<div/>', {
-                               text: this.text,
-                               'class': 'wb-property-container-value'
-                       } ) );
-                       this.parentNode.append( this.node );
-                       this.subject = new wb.ui.DescriptionEditTool( 
this.parentNode, { api: {} } );
-               },
-               teardown: function() {}
-       } ) );
-
-       QUnit.test( 'basic check', function( assert ) {
-
-               assert.ok(
-                       this.subject instanceof wb.ui.DescriptionEditTool,
-                       'instantiated DescriptionEditTool'
-               );
-
-               assert.equal(
-                       this.subject.getEditableValuePrototype(),
-                       wb.ui.PropertyEditTool.EditableDescription,
-                       'retrieved prototype'
-               );
-
-               assert.equal(
-                       this.subject.getOption( 'allowsMultipleValues' ),
-                       false,
-                       'does not allow multiple values'
-               );
-
-               this.subject.destroy();
-
-               assert.equal(
-                       this.node.children().length + 
this.node.children().first().children().length,
-                       1,
-                       'cleaned DOM'
-               );
-
-               assert.equal(
-                       this.node.text(),
-                       this.text,
-                       'plain text remains'
-               );
-
-       } );
-
-}( mediaWiki, wikibase, jQuery, QUnit ) );
diff --git 
a/lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js 
b/lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js
deleted file mode 100644
index aa8aa6f..0000000
--- a/lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * QUnit tests for editable description component
- * @see https://www.mediawiki.org/wiki/Extension:Wikibase
- *
- * @since 0.1
- *
- * @licence GNU GPL v2+
- * @author H. Snater <mediaw...@snater.com>
- * @author Marius Hoch < h...@online.de >
- */
-
-( function( mw, wb, $, QUnit, undefined ) {
-       'use strict';
-
-       function setup( options ) {
-               options = $.extend( { api: {} }, options || {} );
-
-               var $node = $( '<div><div class="wb-value"/></div>' );
-               $( '<div/>', { id: 'parent' } ).append( $node );
-
-               var propertyEditTool = new wb.ui.PropertyEditTool( $node ),
-                       subject = 
wb.ui.PropertyEditTool.EditableDescription.newFromDom( $node, options ),
-                       toolbar = propertyEditTool._buildSingleValueToolbar();
-
-               subject.setToolbar( toolbar );
-
-               return subject;
-       }
-
-       QUnit.module( 'wikibase.ui.PropertyEditTool.EditableDescription', 
QUnit.newWbEnvironment( {
-               setup: function() {
-                       this.subject = setup();
-               },
-               teardown: function() {}
-       } ) );
-
-       QUnit.test( 'basic', function( assert ) {
-
-               assert.ok(
-                       this.subject instanceof 
wb.ui.PropertyEditTool.EditableDescription,
-                       'instantiated editable description'
-               );
-
-               assert.equal(
-                       this.subject._interfaces.length,
-                       1,
-                       'initialized single interface'
-               );
-
-               assert.ok(
-                       this.subject.getInputHelpMessage() !== '',
-                       'help message not empty'
-               );
-
-               this.subject.destroy();
-
-               assert.equal(
-                       this.subject._toolbar,
-                       null,
-                       'destroyed toolbar'
-               );
-
-               assert.equal(
-                       this.subject._instances,
-                       null,
-                       'destroyed instances'
-               );
-
-       } );
-
-       QUnit.test( 'placeholder', function( assert ) {
-               var oldGetLanguageNameByCode = wb.getLanguageNameByCode;
-
-               wb.getLanguageNameByCode = function( code ) {
-                       if ( code === 'de' ) {
-                               return 'Deutsch';
-                       } else {
-                               return '';
-                       }
-               };
-
-               var withLanguage = setup( { valueLanguageContext: 'de' } ),
-                       withoutLanguage = setup( { valueLanguageContext: 'ru' } 
);
-
-               assert.equal(
-                       withLanguage._interfaces[0]._options.inputPlaceholder,
-                       mw.msg(
-                               
'wikibase-description-edit-placeholder-language-aware',
-                               'Deutsch'
-                       )
-               );
-
-               assert.equal(
-                       
withoutLanguage._interfaces[0]._options.inputPlaceholder,
-                       mw.msg( 'wikibase-description-edit-placeholder' )
-               );
-
-               wb.getLanguageNameByCode = oldGetLanguageNameByCode;
-       } );
-
-}( mediaWiki, wikibase, jQuery, QUnit ) );
diff --git a/repo/includes/EntityView.php b/repo/includes/EntityView.php
index b4b5ef9..8ea7402 100644
--- a/repo/includes/EntityView.php
+++ b/repo/includes/EntityView.php
@@ -443,15 +443,19 @@
 
                if ( $entityId !== null && $editable ) {
                        $idString = $entityId->getSerialization();
-                       $editSection .= $this->getHtmlForEditSection( 
'SetDescription', array( $idString, $languageCode ) );
+                       $editSection .= $this->getHtmlForEditSection(
+                                       'SetDescription',
+                                       array( $idString, $languageCode )
+                               );
                }
 
-               $html = wfTemplate( 'wb-description',
-                       wfTemplate( 'wb-property',
-                               $description === false ? 'wb-value-empty' : '',
-                               htmlspecialchars( $description === false ? 
wfMessage( 'wikibase-description-empty' )->text() : $description ),
-                               $editSection
-                       )
+               $html = wfTemplate( 'wikibase-descriptionview',
+                       $description === false ? 'wb-empty' : '',
+                       htmlspecialchars( $description === false
+                               ? wfMessage( 'wikibase-description-empty' 
)->text()
+                               : $description
+                       ),
+                       $editSection
                );
 
                wfProfileOut( __METHOD__ );
diff --git a/repo/resources/Resources.php b/repo/resources/Resources.php
index e06381c..5fe5b10 100644
--- a/repo/resources/Resources.php
+++ b/repo/resources/Resources.php
@@ -28,6 +28,7 @@
                                'mediawiki.user',
                                'wikibase.ui.PropertyEditTool',
                                'jquery.wikibase.aliasesview',
+                               'jquery.wikibase.descriptionview',
                                'jquery.wikibase.entityview',
                                'jquery.wikibase.toolbarcontroller',
                                'jquery.wikibase.wbtooltip',
@@ -81,7 +82,9 @@
                                'wikibase.ui.initTermBox.js',
                        ),
                        'dependencies' => array(
+                               'jquery.wikibase.edittoolbar',
                                'jquery.wikibase.toolbar',
+                               'jquery.wikibase.toolbarcontroller',
                                'jquery.wikibase.toolbareditgroup',
                                'mediawiki.Title',
                                'wikibase',
diff --git a/repo/resources/wikibase.ui.entityViewInit.js 
b/repo/resources/wikibase.ui.entityViewInit.js
index d282f33..81dec62 100644
--- a/repo/resources/wikibase.ui.entityViewInit.js
+++ b/repo/resources/wikibase.ui.entityViewInit.js
@@ -53,12 +53,6 @@
                        $( wb ).on( 'stopItemPageEditMode', fn );
                }
 
-               // add an edit tool for all properties in the data view:
-               $( '.wb-property-container:has( > 
.wb-property-container-key[title=description] )' ).each( function() {
-                       // TODO: Make this nicer when we have implemented the 
data model
-                       new wb.ui.DescriptionEditTool( this, { api: repoApi } );
-               } );
-
                registerEditRestrictionHandlers();
 
                if( mw.config.get( 'wbEntity' ) !== null ) {
@@ -248,6 +242,26 @@
                wb.compileEntityStoreFromMwConfig( entityStore );
 
                // TODO: Integrate into entityview
+               $( '.wikibase-descriptionview' )
+               .toolbarcontroller( {
+                       edittoolbar: ['descriptionview']
+               } )
+               .descriptionview( {
+                       value: {
+                               language: mw.config.get( 'wgUserLanguage' ),
+                               description: $( '.wikibase-descriptionview' 
).hasClass( 'wb-empty' )
+                                       ? null
+                                       // FIXME: entity object should not 
contain fallback strings
+                                       : entity.getDescription( mw.config.get( 
'wgUserLanguage' ) )
+                       },
+                       helpMessage: mw.msg(
+                               'wikibase-description-input-help-message',
+                               wb.getLanguageNameByCode( mw.config.get( 
'wgUserLanguage' ) )
+                       ),
+                       entityId: entity.getId(),
+                       api: repoApi
+               } );
+
                $( '.wikibase-aliasesview' )
                .toolbarcontroller( {
                        edittoolbar: ['aliasesview']
@@ -303,9 +317,9 @@
                // it to a sensible place.
                $( wb )
                .on( 'startItemPageEditMode', function( event, target, options 
) {
-                       $( ':wikibase-aliasesview, :wikibase-sitelinklistview' )
+                       $( ':wikibase-descriptionview, :wikibase-aliasesview, 
:wikibase-sitelinklistview' )
+                       .not( target )
                        .find( ':wikibase-toolbar' )
-                       .not( $( target ).find( ':wikibase-toolbar' ) )
                        .each( function() {
                                $( this ).data( 'toolbar' ).disable();
                        } );
@@ -314,6 +328,17 @@
                        $( ':wikibase-aliasesview' ).find( ':wikibase-toolbar' 
).each( function() {
                                $( this ).data( 'toolbar' ).enable();
                        } );
+                       $( ':wikibase-descriptionview' ).each( function() {
+                               var $descriptionview = $( this ),
+                                       descriptionview = 
$descriptionview.data( 'descriptionview' );
+
+                               if( descriptionview.value().description ) {
+                                       $descriptionview.find( 
':wikibase-toolbar' ).each( function() {
+                                               $( this ).data( 'toolbar' 
).enable();
+                                       } );
+                               }
+                       } );
+
                        $( ':wikibase-sitelinklistview' ).each( function() {
                                var $sitelinklistview = $( this ),
                                        sitelinklistview = 
$sitelinklistview.data( 'sitelinklistview' );
diff --git a/repo/resources/wikibase.ui.initTermBox.js 
b/repo/resources/wikibase.ui.initTermBox.js
index ae71b1a..57438e3 100644
--- a/repo/resources/wikibase.ui.initTermBox.js
+++ b/repo/resources/wikibase.ui.initTermBox.js
@@ -4,54 +4,56 @@
  * @author: H. Snater < mediaw...@snater.com >
  */
 ( function( $, mw, wb ) {
-       'use strict';
+'use strict';
 
-       /**
-        * Term box initialization.
-        * The term box displays label and description in languages other than 
the user language.
-        * @since 0.5
-        *
-        * @param {wikibase.datamodel.Entity} entity
-        * @param {wikibase.RepoApi} api
-        */
-       wb.ui.initTermBox = function( entity, api ) {
-               mw.hook( 'wikibase.domready' ).add( function() {
-                       var termsValueTools = [],
-                               $termBoxRows = $( 'tr.wb-terms-label, 
tr.wb-terms-description' ),
-                               userSpecifiedLanguages = mw.config.get( 
'wbUserSpecifiedLanguages' ),
-                               hasSpecifiedLanguages = userSpecifiedLanguages 
&& userSpecifiedLanguages.length,
-                               isUlsDefined = mw.uls !== undefined
-                                       && $.uls !== undefined
-                                       && $.uls.data !== undefined;
+/**
+ * Term box initialization.
+ * The term box displays label and description in languages other than the 
user language.
+ * @since 0.5
+ *
+ * @param {wikibase.datamodel.Entity} entity
+ * @param {wikibase.RepoApi} api
+ */
+wb.ui.initTermBox = function( entity, api ) {
+       mw.hook( 'wikibase.domready' ).add( function() {
+               var termsValueTools = [],
+                       $termBoxRows = $( 'tr.wb-terms-label, 
tr.wb-terms-description' ),
+                       userSpecifiedLanguages = mw.config.get( 
'wbUserSpecifiedLanguages' ),
+                       hasSpecifiedLanguages = userSpecifiedLanguages && 
userSpecifiedLanguages.length,
+                       isUlsDefined = mw.uls !== undefined
+                               && $.uls !== undefined
+                               && $.uls.data !== undefined;
 
-                       // Skip if having no extra languages is what the user 
wants
-                       if( !$termBoxRows.length && !hasSpecifiedLanguages && 
isUlsDefined ) {
-                               // No term box present; Ask ULS to provide 
languages and generate plain HTML
-                               var languageCodes = 
mw.uls.getFrequentLanguageList(),
-                                       title = new mw.Title(
-                                               mw.config.get( 'wgTitle' ),
-                                               mw.config.get( 
'wgNamespaceNumber' )
-                                       );
+               $( '.wb-terms' ).toolbarcontroller( {
+                       edittoolbar: ['terms-descriptionview']
+               } );
 
-                               if( !languageCodes.length ) {
-                                       return;
-                               }
-
-                               var $sectionHeading = addTermBoxSection();
-                               $sectionHeading.after(
-                                       renderTermBox( title, entity, 
languageCodes.slice( 1, 4 ) )
+               // Skip if having no extra languages is what the user wants
+               if( !$termBoxRows.length && !hasSpecifiedLanguages && 
isUlsDefined ) {
+                       // No term box present; Ask ULS to provide languages 
and generate plain HTML
+                       var languageCodes = mw.uls.getFrequentLanguageList(),
+                               title = new mw.Title(
+                                       mw.config.get( 'wgTitle' ),
+                                       mw.config.get( 'wgNamespaceNumber' )
                                );
 
-                               $termBoxRows = $( 'tr.wb-terms-label, 
tr.wb-terms-description' );
+                       if( !languageCodes.length ) {
+                               return;
                        }
 
-                       $termBoxRows.each( function() {
-                               var $termsRow = $( this ),
-                                       editTool = wb.ui.PropertyEditTool[
-                                               $termsRow.hasClass( 
'wb-terms-label' )
-                                                       ? 'EditableLabel'
-                                                       : 'EditableDescription'
-                                       ],
+                       var $sectionHeading = addTermBoxSection();
+                       $sectionHeading.after(
+                               renderTermBox( title, entity, 
languageCodes.slice( 1, 4 ) )
+                       );
+
+                       $termBoxRows = $( 'tr.wb-terms-label, 
tr.wb-terms-description' );
+               }
+
+               $termBoxRows.each( function() {
+                       var $termsRow = $( this );
+
+                       if( $termsRow.hasClass( 'wb-terms-label' ) ) {
+                               var editTool = 
wb.ui.PropertyEditTool.EditableLabel,
                                        $toolbar = mw.template( 
'wikibase-toolbar', '', '' ).toolbar(),
                                        toolbar = $toolbar.data( 'toolbar' ),
                                        $editGroup = mw.template( 
'wikibase-toolbareditgroup', '', '' )
@@ -65,100 +67,212 @@
                                termsValueTools.push( editTool.newFromDom( 
$termsRow, {
                                        api: api
                                }, toolbar ) );
+
+                               return true;
+                       }
+
+                       var languageCode;
+
+                       // TODO: Find more sane way to figure out language code.
+                       $.each( $termsRow.attr( 'class' ).split( ' ' ), 
function( i, cssClass ) {
+                               if(
+                                       cssClass.indexOf( 'wb-terms-' ) === 0
+                                       && cssClass.indexOf( 
'wb-terms-description' ) === -1
+                               ) {
+                                       languageCode =  cssClass.replace( 
/wb-terms-/, '' );
+                                       return false;
+                               }
                        } );
 
-                       $( wb )
-                       .on( 'startItemPageEditMode', function( event, origin ) 
{
-                               // Disable language terms table's editable 
value or mark it as the active one if it
-                               // is the one being edited by the user and 
therefore the origin of the event
-                               $.each( termsValueTools, function( i, 
termValueTool ) {
-                                       if(
-                                               !( origin instanceof 
wb.ui.PropertyEditTool.EditableValue )
-                                               || origin.getSubject() !== 
termValueTool.getSubject()
-                                       ) {
-                                               termValueTool.disable();
-                                       } else if( origin && 
origin.getSubject() === termValueTool.getSubject() ) {
-                                               $( 'table.wb-terms' ).addClass( 
'wb-edit' );
-                                       }
-                               } );
-                       } )
-                       .on( 'stopItemPageEditMode', function( event, origin ) {
-                               $( 'table.wb-terms' ).removeClass( 'wb-edit' );
-                               $.each( termsValueTools, function( i, 
termValueTool ) {
-                                       termValueTool.enable();
-                               } );
+                       $termsRow.children( 'td' ).first().descriptionview( {
+                               value: {
+                                       language: languageCode,
+                                       description: entity.getDescription( 
languageCode )
+                               },
+                               helpMessage: mw.msg(
+                                       
'wikibase-description-input-help-message',
+                                       wb.getLanguageNameByCode( languageCode )
+                               ),
+                               entityId: entity.getId(),
+                               api: api
                        } );
 
                } );
-       };
 
-       /**
-        * @return {jQuery}
-        */
-       function addTermBoxSection() {
-               var $sectionHeading = mw.template( 'wb-terms-heading', mw.msg( 
'wikibase-terms' ) ),
-                       $toc = $( '#toc' ),
-                       $precedingNode;
-
-               if( $toc.length ) {
-                       $toc
-                       .children( 'ul' ).prepend(
-                               $( '<li>' )
-                               .addClass( 'toclevel-1' )
-                               .append(
-                                       $( '<a>' )
-                                       .attr( 'href', '#wb-terms' )
-                                       .text( mw.msg( 'wikibase-terms' ) )
-                               )
-                       )
-                       .find( 'li' ).each( function( i, li ) {
-                               $( li )
-                               .removeClass( 'tocsection-' + i )
-                               .addClass( 'tocsection-' + ( i + 1 ) );
+               $( wb )
+               .on( 'startItemPageEditMode', function( event, origin ) {
+                       // Disable language terms table's editable value or 
mark it as the active one if it
+                       // is the one being edited by the user and therefore 
the origin of the event
+                       $.each( termsValueTools, function( i, termValueTool ) {
+                               if(
+                                       !( origin instanceof 
wb.ui.PropertyEditTool.EditableValue )
+                                       || origin.getSubject() !== 
termValueTool.getSubject()
+                               ) {
+                                       termValueTool.disable();
+                               } else if( origin && origin.getSubject() === 
termValueTool.getSubject() ) {
+                                       $( 'table.wb-terms' ).addClass( 
'wb-edit' );
+                               }
                        } );
 
-                       $precedingNode = $toc;
-               } else {
-                       $precedingNode = $( '.wb-aliases' );
-               }
+                       $termBoxRows.find( ':wikibase-descriptionview' )
+                       .not( origin )
+                       .each( function() {
+                               $( this ).data( 'edittoolbar' 
).toolbar.disable();
+                       } );
+               } )
+               .on( 'stopItemPageEditMode', function( event, origin ) {
+                       $( 'table.wb-terms' ).removeClass( 'wb-edit' );
+                       $.each( termsValueTools, function( i, termValueTool ) {
+                               termValueTool.enable();
+                       } );
 
-               $precedingNode.after( $sectionHeading );
+                       $termBoxRows.find( ':wikibase-descriptionview' ).each( 
function() {
+                               var descriptionview = $( this ).data( 
'descriptionview' );
 
-               return $sectionHeading;
+                               if( descriptionview.value().description ) {
+                                       var toolbar = $( this ).data( 
'edittoolbar' ).toolbar,
+                                               btnEdit = 
toolbar.editGroup.getButton( 'edit' ).data( 'toolbarbutton' );
+
+                                       $( this ).data( 'edittoolbar' 
).toolbar.enable();
+
+                                       // FIXME: Get rid of StatableObject 
making things complicated
+                                       btnEdit.setState( btnEdit.STATE.ENABLED 
);
+                               }
+                       } );
+               } );
+
+       } );
+};
+
+/**
+ * @return {jQuery}
+ */
+function addTermBoxSection() {
+       var $sectionHeading = mw.template( 'wb-terms-heading', mw.msg( 
'wikibase-terms' ) ),
+               $toc = $( '#toc' ),
+               $precedingNode;
+
+       if( $toc.length ) {
+               $toc
+               .children( 'ul' ).prepend(
+                       $( '<li>' )
+                       .addClass( 'toclevel-1' )
+                       .append(
+                               $( '<a>' )
+                               .attr( 'href', '#wb-terms' )
+                               .text( mw.msg( 'wikibase-terms' ) )
+                       )
+               )
+               .find( 'li' ).each( function( i, li ) {
+                       $( li )
+                       .removeClass( 'tocsection-' + i )
+                       .addClass( 'tocsection-' + ( i + 1 ) );
+               } );
+
+               $precedingNode = $toc;
+       } else {
+               $precedingNode = $( '.wb-aliases' );
        }
 
-       /**
-        * @param {mediaWiki.Title} title
-        * @param {wikibase.datamodel.Entity} entity
-        * @param {string[]} languageCodes
-        * @return {jQuery|undefined}
-        */
-       function renderTermBox( title, entity, languageCodes ) {
-               if( languageCodes === undefined ) {
-                       return;
-               }
-               var labels = entity.getLabels(),
-                       descriptions = entity.getDescriptions(),
-                       $tbody = $( '<tbody>' );
+       $precedingNode.after( $sectionHeading );
 
-               for( var i = 0; i < languageCodes.length; i++ ) {
-                       var languageCode = languageCodes[i];
+       return $sectionHeading;
+}
 
-                       $tbody.append( mw.template( 'wb-term',
-                               languageCode,
-                               $.uls.data.getAutonym( languageCode ),
-                               labels.hasOwnProperty( languageCode ) ? 
labels[languageCode] : '',
-                               descriptions.hasOwnProperty( languageCode ) ? 
descriptions[languageCode] : '',
-                               '',
-                               '',
-                               '',
-                               '',
-                               title.getUrl( { setlang: languageCode } )
-                       ) );
-               }
+/**
+ * @param {mediaWiki.Title} title
+ * @param {wikibase.datamodel.Entity} entity
+ * @param {string[]} languageCodes
+ * @return {jQuery|undefined}
+ */
+function renderTermBox( title, entity, languageCodes ) {
+       if( languageCodes === undefined ) {
+               return;
+       }
+       var labels = entity.getLabels(),
+               descriptions = entity.getDescriptions(),
+               $tbody = $( '<tbody>' );
 
-               return mw.template( 'wb-terms-table', $tbody );
+       for( var i = 0; i < languageCodes.length; i++ ) {
+               var languageCode = languageCodes[i];
+
+               $tbody.append( mw.template( 'wb-term',
+                       languageCode,
+                       $.uls.data.getAutonym( languageCode ),
+                       labels.hasOwnProperty( languageCode ) ? 
labels[languageCode] : '',
+                       descriptions.hasOwnProperty( languageCode ) ? 
descriptions[languageCode] : '',
+                       '',
+                       '',
+                       '',
+                       '',
+                       title.getUrl( { setlang: languageCode } )
+               ) );
        }
 
+       return mw.template( 'wb-terms-table', $tbody );
+}
+
+// TODO: Merge with native descriptionview toolbar definiton
+$.wikibase.toolbarcontroller.definition( 'edittoolbar', {
+       id: 'terms-descriptionview',
+       selector: '.wb-terms-description',
+       events: {
+               descriptionviewcreate: function( event, toolbarcontroller ) {
+                       var $descriptionview = $( event.target ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' );
+
+                       $descriptionview.edittoolbar( {
+                               $container: $descriptionview.next(),
+                               interactionWidgetName: 
$.wikibase.descriptionview.prototype.widgetName,
+                               enableRemove: false
+                       } );
+
+                       $descriptionview.on( 'keyup', function( event ) {
+                               if( event.keyCode === $.ui.keyCode.ESCAPE ) {
+                                       descriptionview.stopEditing( true );
+                               } else if( event.keyCode === $.ui.keyCode.ENTER 
) {
+                                       descriptionview.stopEditing( false );
+                               }
+                       } );
+
+                       if( !descriptionview.value().description ) {
+                               descriptionview.startEditing();
+                       }
+               },
+               'descriptionviewchange descriptionviewafterstartediting': 
function( event ) {
+                       var $descriptionview = $( event.target ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' ),
+                               toolbar = $descriptionview.data( 'edittoolbar' 
).toolbar,
+                               $btnSave = toolbar.editGroup.getButton( 'save' 
),
+                               btnSave = $btnSave.data( 'toolbarbutton' ),
+                               enable = descriptionview.isValid() && 
!descriptionview.isInitialValue(),
+                               $btnCancel = toolbar.editGroup.getButton( 
'cancel' ),
+                               btnCancel = $btnCancel.data( 'toolbarbutton' ),
+                               currentDescription = 
descriptionview.value().description,
+                               disableCancel = !currentDescription && 
descriptionview.isInitialValue();
+
+                       btnSave[enable ? 'enable' : 'disable']();
+                       btnCancel[disableCancel ? 'disable' : 'enable']();
+               },
+               descriptionviewafterstopediting: function( event, dropValue ) {
+                       var $descriptionview = $( event.target ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' );
+
+                       if( !descriptionview.value().description ) {
+                               descriptionview.startEditing();
+                       }
+               },
+               toolbareditgroupedit: function( event, toolbarcontroller ) {
+                       var $descriptionview = $( event.target ).closest( 
':wikibase-edittoolbar' ),
+                               descriptionview = $descriptionview.data( 
'descriptionview' );
+
+                       if( !descriptionview ) {
+                               return;
+                       }
+
+                       descriptionview.focus();
+               }
+       }
+} );
 
 } )( jQuery, mediaWiki, wikibase );

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I3bbc60aac809e5268e38ecca3013ae4fc0b66fde
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Henning Snater <henning.sna...@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