Jonas Kress (WMDE) has uploaded a new change for review.

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

Change subject: [WIP] Refactor Visual Editor
......................................................................

[WIP] Refactor Visual Editor

Change-Id: Id31057b8c5726ce5be45a6293c8ea90fda5ebb26
---
M index.html
M wikibase/queryService/ui/App.js
A wikibase/queryService/ui/visualEditor/SparqlQueryVerbalizer.js
M wikibase/queryService/ui/visualEditor/VisualEditor.js
M wikibase/tests/VisualEditor.html
5 files changed, 598 insertions(+), 437 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/wikidata/query/gui 
refs/changes/87/287987/1

diff --git a/index.html b/index.html
index 8373f9a..0d25b5c 100644
--- a/index.html
+++ b/index.html
@@ -242,6 +242,7 @@
        <script src="wikibase/queryService/ui/editor/Editor.js"></script>
        <script 
src="wikibase/queryService/ui/visualEditor/VisualEditor.js"></script>
        <script 
src="wikibase/queryService/ui/visualEditor/SparqlQuery.js"></script>
+       <script 
src="wikibase/queryService/ui/visualEditor/SparqlQueryVerbalizer.js"></script>
        <script 
src="wikibase/queryService/ui/visualEditor/SelectorBox.js"></script>
        <script src="wikibase/queryService/ui/QueryExampleDialog.js"></script>
        <script 
src="wikibase/queryService/ui/resultBrowser/helper/FormatterHelper.js"></script>
diff --git a/wikibase/queryService/ui/App.js b/wikibase/queryService/ui/App.js
index 8d5579d..8df5dd0 100644
--- a/wikibase/queryService/ui/App.js
+++ b/wikibase/queryService/ui/App.js
@@ -173,7 +173,7 @@
                        this._visualEditor = new 
wikibase.queryService.ui.visualEditor.VisualEditor();
                }
                this._visualEditor.setChangeListener( function( ve ) {
-                       self._editor.setValue( ve.getQuery() );
+                       self._editor.setValue( self._visualEditor.getQuery() );
                } );
 
                if ( this._editor ) {
diff --git a/wikibase/queryService/ui/visualEditor/SparqlQueryVerbalizer.js 
b/wikibase/queryService/ui/visualEditor/SparqlQueryVerbalizer.js
new file mode 100644
index 0000000..72b9c51
--- /dev/null
+++ b/wikibase/queryService/ui/visualEditor/SparqlQueryVerbalizer.js
@@ -0,0 +1,567 @@
+var wikibase = wikibase || {};
+wikibase.queryService = wikibase.queryService || {};
+wikibase.queryService.ui = wikibase.queryService.ui || {};
+wikibase.queryService.ui.visualEditor = wikibase.queryService.ui.visualEditor 
|| {};
+
+wikibase.queryService.ui.visualEditor.SparqlQueryVerbalizer = ( function( $, 
wikibase ) {
+       'use strict';
+
+       var FILTER_PREDICATES = {
+               'http://www.w3.org/2000/01/rdf-schema#label': true,
+               'http://schema.org/description': true,
+               'http://www.bigdata.com/queryHints#optimizer': true,
+               'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': true
+       };
+
+       /**
+        * A SPARQL verbalizer - creates verbal output of a SPARQL query
+        *
+        * @class wikibase.queryService.ui.visualEditor.SparqlQueryVerbalizer
+        * @license GNU GPL v2+
+        *
+        * @author Jonas Kress
+        * @constructor
+        * @param {function} i18n
+        * @param {wikibase.queryService.api.Wikibase} api
+        * @param {wikibase.queryService.ui.visualEditor.SelectorBox} 
selectorBox
+        */
+       function SELF( i18n, api, selectorBox ) {
+
+               this._i18n = i18n;
+
+               this._api = api;
+               if ( !this._api ) {
+                       this._api = new wikibase.queryService.api.Wikibase();
+               }
+
+               this._selectorBox = selectorBox;
+               if ( !this._selectorBox ) {
+                       this._selectorBox = new 
wikibase.queryService.ui.visualEditor.SelectorBox( this._api );
+               }
+       }
+
+       /**
+        * @property {wikibase.queryService.api.Wikibase}
+        * @private
+        */
+       SELF.prototype._api = null;
+
+       /**
+        * @property {wikibase.queryService.ui.visualEditor.SelectorBox}
+        * @private
+        */
+       SELF.prototype._selectorBox = null;
+
+       /**
+        * @property {function}
+        * @private
+        */
+       SELF.prototype._i18n = null;
+
+       /**
+        * @property {Function}
+        * @private
+        */
+       SELF.prototype._changeListener = null;
+
+       /**
+        * @property {wikibase.queryService.ui.visualEditor.SparqlQuery}
+        * @private
+        */
+       SELF.prototype._query = null;
+
+       /**
+        * Set the SPARQL query
+        *
+        * @param {wikibase.queryService.ui.visualEditor.SparqlQuery} query
+        */
+       SELF.prototype.setQuery = function( query ) {
+               this._query = query;
+       };
+
+       /**
+        * Set the change listener
+        *
+        * @param {Function} listener a function called when query changed
+        */
+       SELF.prototype.setChangeListener = function( listener ) {
+               this._changeListener = listener;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype.getHtml = function() {
+
+               this._triples = this._query.getTriples();
+               var subqueries = this._query.getSubQueries();
+               while ( subqueries.length > 0 ) {
+                       var q = subqueries.pop();
+                       this._triples = this._triples.concat( q.getTriples() );
+                       subqueries.concat( q.getSubQueries() );
+               }
+
+               this._isSimpleMode = this._isSimpleQuery();
+
+               return this._getHtml();
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getHtml = function() {
+               var self = this;
+               var $html = $( '<div>' ), $find = this._getFindSection(), $show 
= this._getShowSection(), $spacer = $(
+                               '<div>' ).addClass( 'spacer' );
+
+               $html.append( $find, $spacer.clone(), $show, $spacer.clone(), 
this._getLimitSection() );
+
+               $.each( this._triples, function( k, triple ) {
+                       if ( self._isNotRelevant( triple.triple ) ) {
+                               return;
+                       }
+
+                       if ( self._isInShowSection( triple.triple ) ) {
+                               if ( $show.children().length > 1 ) {
+                                       $show.append( $( '<span>' ).text( ', ' 
) );
+                               }
+                               $show.append( self._getTripleHtml( triple ) );
+                               return;
+                       }
+                       if ( $find.children().length > 1 ) {
+                               if ( $find.children().length === 2 ) {
+                                       $find.append( $( '<span>' ).text( 
self._i18n( 'with' ) + ' ' ) );
+                               } else {
+                                       $find.append(  $( '<span>' ).text( 
self._i18n( 'and' ) + ' ' ) );
+                               }
+                       }
+                       $find.append( self._getTripleHtml( triple ) );
+
+               } );
+
+               if ( $find.children().length === 1 ) {
+                       $find.append( $( '<span>' ).text( this._i18n( 
'anything' ) + ' ' ) );
+               }
+
+               return $html;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isNotRelevant = function( triple ) {
+
+               if ( FILTER_PREDICATES[triple.predicate] ) {
+                       return true;
+               }
+
+               if ( this._isSimpleMode && this._isInShowSection( triple ) &&
+                               ( this._query.hasVariable( triple.object ) === 
false &&
+                                               this._query.hasVariable( 
triple.object + 'Label' ) === false ) ) {
+                       return true;
+               }
+
+               return false;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isInShowSection = function( triple ) {
+
+               // Must match ?value wdt:Pxx ?item
+               if ( this._isVariable( triple.subject ) && this._isVariable( 
triple.object ) ) {
+                       return true;
+               }
+
+               return false;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isVariable = function( entity ) {
+               if ( typeof entity === 'string' && entity.startsWith( '?' ) ) {
+                       return true;
+               }
+               return false;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isSimpleQuery = function() {
+               var boundVariables = {};
+
+               var self = this;
+               $.each( this._triples, function( k, t ) {
+                       // Must match ?value wdt:Pxx ?item
+                       if ( self._isVariable( t.triple.subject ) &&
+                                       self._isVariable( t.triple.object ) === 
false ) {
+                               boundVariables[t.triple.subject] = true;
+                       }
+
+               } );
+
+               if ( Object.keys( boundVariables ).length > 1 ) {
+                       return false;
+               }
+
+               return true;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getLimitSection = function() {
+               var $limitSection = $( '<div>' ), $limit = $( '<a 
data-type="number">' ).attr( 'href', '#' )
+                               .text( 'Limit' ).data( 'value', 
this._query.getLimit() ), $value = $( '<span>' )
+                               .text( this._query.getLimit() ? 
this._query.getLimit() : '' );
+
+               var self = this;
+               this._selectorBox.add( $limit, function( value ) {
+                       if ( value === '0' ) {
+                               value = null;
+                       }
+
+                       $value.text( value ? value : '' );
+                       self._query.setLimit( value );
+
+                       if ( self._changeListener ) {
+                               self._changeListener( self );
+                       }
+               }, {
+                       trash: function() {
+                               self._query.setLimit( null );
+                               $limit.data( 'value', '' );
+                               $value.text( '' );
+                               if ( self._changeListener ) {
+                                       self._changeListener( self );
+                               }
+                               return true;//close popover
+                       }
+               } );
+
+               return $limitSection.append( $limit.append( ' ', $value ) );
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getFindSection = function() {
+               var $findSection = $( '<div>' );
+               // Show link
+               var $link = $( '<a class="btn btn-default">' ).text( 
this._i18n( 'find' ) );
+               $link.attr( 'href', '#' ).prepend(
+                               '<span class="glyphicon glyphicon-search" 
aria-hidden="true"></span>', ' ' )
+                               .tooltip( {
+                                       title: 'Click to add new item'
+                               } ).attr( 'data-type', 'item' ).attr( 
'data-auto_open', true );
+
+               // SelectorBox
+               var self = this;
+               this._selectorBox.add( $link, function( id, name ) {
+                       var entity = 'http://www.wikidata.org/entity/' + id;// 
FIXME technical debt
+
+                       var variable = self._query.getBoundVariables().shift();
+                       if ( !variable ) {
+                               variable = '?' + '_' + name.replace( /( 
|[^a-z0-9])/gi, '_' );
+                       }
+
+                       var prop = 'http://www.wikidata.org/prop/direct/P31';// 
FIXME technical debt
+                       var triple = self._query.addTriple( variable, prop, 
entity, false );
+                       if ( !self._query.hasVariable( variable ) ) {
+                               self._query.addVariable( variable );
+                       }
+
+                       if ( $findSection.children().length >= 3 ) {
+                               if ( $findSection.children().length === 3 ) {
+                                       $findSection.append( $( '<span>' 
).text( self._i18n( 'with' ) + ' ' ) );
+                               } else {
+                                       $findSection.append( $( '<span>' 
).text( self._i18n( 'and' ) + ' ' ) );
+                               }
+                       }
+                       $findSection.append( self._getTripleHtml( triple ) );
+
+                       if ( self._changeListener ) {
+                               self._changeListener( self );
+                       }
+               } );
+
+               $findSection.append( $link, ' ' );
+               return $findSection;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getShowSection = function() {
+               var $showSection = $( '<div>' );
+               // Show link
+               var $link = $( '<a class="btn btn-default">' ).text( 
this._i18n( 'show' ) );
+               $link.attr( 'href', '#' ).prepend(
+                               '<span class="glyphicon glyphicon-eye-open" 
aria-hidden="true"></span>', ' ' )
+                               .tooltip( {
+                                       title: 'Click to add new property'
+                               } ).attr( 'data-type', 'property' ).attr( 
'data-auto_open', true );
+
+               // SelectorBox
+               var self = this;
+               this._selectorBox.add( $link, function( id, name ) {
+                       var prop = 'http://www.wikidata.org/prop/direct/' + 
id;// FIXME technical debt
+
+                       var subject = self._query.getBoundVariables().shift();
+                       if ( !subject ) {
+                               return;
+                       }
+                       var variable2 = '?_' + name.replace( /( |[^a-z0-9])/gi, 
'_' );// FIXME technical debt
+
+                       var triple = self._query.addTriple( subject, prop, 
variable2, true );
+                       self._query.addVariable( variable2 );
+
+                       if ( $showSection.children().length > 2 ) {
+                               $showSection.append( $( '<span>' ).text( ', ' ) 
);
+                       }
+                       $showSection.append( self._getTripleHtml( triple ) );
+
+                       if ( self._changeListener ) {
+                               self._changeListener( self );
+                       }
+               } );
+
+               return $showSection.append( $link, ' ' );
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isNotRelevant = function( triple ) {
+
+               if ( FILTER_PREDICATES[triple.predicate] ) {
+                       return true;
+               }
+
+               if ( this._isSimpleMode && this._isInShowSection( triple ) &&
+                               ( this._query.hasVariable( triple.object ) === 
false &&
+                                               this._query.hasVariable( 
triple.object + 'Label' ) === false ) ) {
+                       return true;
+               }
+
+               return false;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isInShowSection = function( triple ) {
+
+               // Must match ?value wdt:Pxx ?item
+               if ( this._isVariable( triple.subject ) && this._isVariable( 
triple.object ) ) {
+                       return true;
+               }
+
+               return false;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getTripleHtml = function( triple ) {
+               var self = this;
+
+               var $triple = $( '<span>' );
+               $.each( triple.triple, function( k, entity ) {
+
+                       if ( self._isSimpleMode && self._isVariable( entity ) ) 
{
+                               return;
+                       }
+
+                       if ( entity.type && entity.type === 'path' ) {
+                               $triple.append( self._getTripleEntityPathHtml( 
entity, triple, k ), ' ' );
+                       } else {
+                               $triple.append( self._getTripleEntityHtml( 
entity, triple, k ), ' ' );
+                       }
+               } );
+
+               return $triple;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isVariable = function( entity ) {
+               if ( typeof entity === 'string' && entity.startsWith( '?' ) ) {
+                       return true;
+               }
+               return false;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._isSimpleQuery = function() {
+               var boundVariables = {};
+
+               var self = this;
+               $.each( this._triples, function( k, t ) {
+                       // Must match ?value wdt:Pxx ?item
+                       if ( self._isVariable( t.triple.subject ) &&
+                                       self._isVariable( t.triple.object ) === 
false ) {
+                               boundVariables[t.triple.subject] = true;
+                       }
+
+               } );
+
+               if ( Object.keys( boundVariables ).length > 1 ) {
+                       return false;
+               }
+
+               return true;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getTripleEntityPathHtml = function( path, triple ) {
+               var self = this, $path = $( '<span>' );
+               $.each( path.items, function( k, v ) {
+                       if ( v.type && v.type === 'path' ) {
+                               $path.append( self._getTripleEntityPathHtml( v, 
triple ) );
+                               return;
+                       }
+
+                       if ( k > 0 && path.pathType === '/' ) {
+                               $path.append( ' ' + self._i18n( 'or' ) + ' ' + 
self._i18n( 'subtype' ) + ' ' );
+                       }
+                       if ( path.pathType === '*' ) {
+                               $path.append( ' ' + self._i18n( 'any' ) + ' ' );
+                       }
+
+                       // FIXME: Do not fake triple here
+                       var newTriple = path.items.reduce( function( o, v, i ) {
+                               o[i] = v;
+                               return o;
+                       }, {} );
+                       newTriple = $.extend( newTriple, triple.triple );
+                       triple = $.extend( {}, triple );
+                       triple.triple = newTriple;
+
+                       $path.append( self._getTripleEntityHtml( v, triple, k ) 
);
+
+               } );
+
+               return $path;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getTripleEntityHtml = function( entity, triple, key ) {
+               var $label = $( '<span>' );
+
+               var self = this;
+               this._getLabel( entity ).done( function( label, id, 
description, type ) {
+                       var $link = $( '<a>' ).attr( 'href', '#' );
+                       $link.text( label );
+                       $link.attr( 'data-type', type );
+                       $link.attr( 'data-id', id );
+                       $link.appendTo( $label );
+
+                       $label.tooltip( {
+                               'title': '(' + id + ') ' + description
+                       } );
+                       $( $label ).on( 'show.bs.tooltip', function() {
+                               if ( $( '.tooltip' ).is( ':visible' ) ) {
+                                       $( '.tooltip' ).not( this ).hide();
+                               }
+                       } );
+
+                       //TODO: refactor method
+                       self._selectorBox.add( $link, function( selectedId ) {
+                               var newEntity = entity.replace( new RegExp( id 
+ '$' ), '' ) + selectedId;// TODO: technical debt
+
+                               $label.replaceWith( self._getTripleEntityHtml( 
newEntity, triple, key ) );
+                               triple.triple[key] = newEntity;
+
+                               if ( self._changeListener ) {
+                                       self._changeListener();
+                               }
+                       }, {
+                               trash: function() {
+                                       triple.remove();
+
+                                       var variable = triple.triple.object;
+                                       if ( triple.triple.object === entity ||
+                                                       ( 
triple.triple.object.startsWith( '?' ) === false && triple.triple.predicate === 
entity ) ) {
+                                               variable = 
triple.triple.subject;
+                                       }
+                                       if ( $label.parent().next( 'span' 
).length ) {
+                                               $label.parent().next( 'span' 
).remove();
+                                       } else {
+                                               $label.parent().prev( 'span' 
).remove();
+                                       }
+                                       $label.parent().remove();
+
+                                       self._query.removeVariable( variable );
+                                       self._query.removeVariable( variable + 
'Label' );
+
+                                       if ( self._changeListener ) {
+                                               self._changeListener();
+                                       }
+                                       $( '.tooltip' ).hide();
+                                       return true;//close popover
+                               },
+                               tag: function() {
+                                       if ( triple.triple.object.startsWith( 
'?' ) ) {
+                                               self._query
+                                                               .addVariable( 
triple.triple.object +
+                                                                               
'Label' );
+                                       } else {
+                                               self._query
+                                                               .addVariable( 
triple.triple.subject +
+                                                                               
'Label' );
+                                       }
+                                       if ( self._changeListener ) {
+                                               self._changeListener();
+                                       }
+                                       return true;
+                               }
+                       }
+                       );
+               } ).fail( function() {
+                       $label.text( entity );
+               } );
+
+               return $label;
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._getLabel = function( url ) {
+               var deferred = $.Deferred();
+
+               var entity = url.match( /(Q|P)([0-9]+)/ );// TODO: make use of 
Rdf namespaces
+               if ( !entity ) {
+                       return deferred.reject().promise();
+               }
+
+               var type = {
+                       P: 'property',
+                       Q: 'item'
+               };
+               type = type[entity[1]];
+               var term = entity[0];
+
+               this._api.searchEntities( term, type ).done( function( data ) {
+                       $.each( data.search, function( key, value ) {
+                               deferred.resolve( value.label, value.id, 
value.description, type );
+                               return false;
+                       } );
+               } );
+
+               return deferred.promise();
+       };
+
+       return SELF;
+}( jQuery, wikibase ) );
diff --git a/wikibase/queryService/ui/visualEditor/VisualEditor.js 
b/wikibase/queryService/ui/visualEditor/VisualEditor.js
index 507e016..c7931ab 100644
--- a/wikibase/queryService/ui/visualEditor/VisualEditor.js
+++ b/wikibase/queryService/ui/visualEditor/VisualEditor.js
@@ -6,14 +6,20 @@
 wikibase.queryService.ui.visualEditor.VisualEditor = ( function( $, wikibase ) 
{
        'use strict';
 
-       var FILTER_PREDICATES = {
-               'http://www.w3.org/2000/01/rdf-schema#label': true,
-               'http://schema.org/description': true,
-               'http://www.bigdata.com/queryHints#optimizer': true,
-               'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': true
-       };
+       var PACKAGE = wikibase.queryService.ui.visualEditor;
 
        var I18N_PREFIX = 'wdqs-ve-';
+
+       var DEFAULT_LABELS = {
+               find: 'Find',
+               show: 'Show',
+               anything: 'anything',
+               'with': 'with',
+               and: 'and',
+               any: 'any',
+               or: 'or',
+               subtype: 'subtype'
+       };
 
        /**
         * A visual SPARQL editor for the Wikibase query service
@@ -35,10 +41,12 @@
 
                this._selectorBox = selectorBox;
                if ( !this._selectorBox ) {
-                       this._selectorBox = new 
wikibase.queryService.ui.visualEditor.SelectorBox( this._api );
+                       this._selectorBox = new PACKAGE.SelectorBox( this._api 
);
                }
 
-               this._query = new 
wikibase.queryService.ui.visualEditor.SparqlQuery();
+               this._query = new PACKAGE.SparqlQuery();
+
+               this._verbalizer = new PACKAGE.SparqlQueryVerbalizer( 
this._i18n, api, selectorBox );
        }
 
        /**
@@ -54,10 +62,10 @@
        SELF.prototype._selectorBox = null;
 
        /**
-        * @property {Function}
+        * @property 
{wikibase.queryService.ui.visualEditor.SparqlQueryVerbalizer}
         * @private
         */
-       SELF.prototype._changeListener = null;
+       SELF.prototype._verbalizer = null;
 
        /**
         * @property {wikibase.queryService.ui.visualEditor.SparqlQuery}
@@ -84,18 +92,15 @@
        SELF.prototype._isSimpleMode = false;
 
        /**
-        * @property {Object}
         * @private
         */
-       SELF.prototype._labels = {
-               find: 'Find',
-               show: 'Show',
-               anything: 'anything',
-               'with': 'with',
-               and: 'and',
-               any: 'any',
-               or: 'or',
-               subtype: 'subtype'
+       SELF.prototype._i18n = function( key ) {
+
+               if ( !$.i18n ) {
+                       return DEFAULT_LABELS[key];
+               }
+
+               return $.i18n( I18N_PREFIX + key );
        };
 
        /**
@@ -165,17 +170,8 @@
         * @param {jQuery} $element
         */
        SELF.prototype.draw = function( $element ) {
-
-               this._triples = this._query.getTriples();
-               var subqueries = this._query.getSubQueries();
-               while ( subqueries.length > 0 ) {
-                       var q = subqueries.pop();
-                       this._triples = this._triples.concat( q.getTriples() );
-                       subqueries.concat( q.getSubQueries() );
-               }
-
-               this._isSimpleMode = this._isSimpleQuery();
-               $element.html( this._getHtml() );
+               this._verbalizer.setQuery( this._query );
+               $element.html( this._verbalizer.getHtml() );
        };
 
        /**
@@ -184,411 +180,7 @@
         * @param {Function} listener a function called when query changed
         */
        SELF.prototype.setChangeListener = function( listener ) {
-               this._changeListener = listener;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._i18n = function( key ) {
-
-               if ( !$.i18n ) {
-                       return this._labels[key];
-               }
-
-               return $.i18n( I18N_PREFIX + key );
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getHtml = function() {
-               var self = this;
-               var $html = $( '<div>' ), $find = this._getFindSection(), $show 
= this._getShowSection(), $spacer = $(
-                               '<div>' ).addClass( 'spacer' );
-
-               $html.append( $find, $spacer.clone(), $show, $spacer.clone(), 
this._getLimitSection() );
-
-               $.each( this._triples, function( k, triple ) {
-                       if ( self._isNotRelevant( triple.triple ) ) {
-                               return;
-                       }
-
-                       if ( self._isInShowSection( triple.triple ) ) {
-                               if ( $show.children().length > 1 ) {
-                                       $show.append( $( '<span>' ).text( ', ' 
) );
-                               }
-                               $show.append( self._getTripleHtml( triple ) );
-                               return;
-                       }
-                       if ( $find.children().length > 1 ) {
-                               if ( $find.children().length === 2 ) {
-                                       $find.append( $( '<span>' ).text( 
self._i18n( 'with' ) + ' ' ) );
-                               } else {
-                                       $find.append(  $( '<span>' ).text( 
self._i18n( 'and' ) + ' ' ) );
-                               }
-                       }
-                       $find.append( self._getTripleHtml( triple ) );
-
-               } );
-
-               if ( $find.children().length === 1 ) {
-                       $find.append( $( '<span>' ).text( this._i18n( 
'anything' ) + ' ' ) );
-               }
-
-               return $html;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getLimitSection = function() {
-               var $limitSection = $( '<div>' ), $limit = $( '<a 
data-type="number">' ).attr( 'href', '#' )
-                               .text( 'Limit' ).data( 'value', 
this._query.getLimit() ), $value = $( '<span>' )
-                               .text( this._query.getLimit() ? 
this._query.getLimit() : '' );
-
-               var self = this;
-               this._selectorBox.add( $limit, function( value ) {
-                       if ( value === '0' ) {
-                               value = null;
-                       }
-
-                       $value.text( value ? value : '' );
-                       self._query.setLimit( value );
-
-                       if ( self._changeListener ) {
-                               self._changeListener( self );
-                       }
-               }, {
-                       trash: function() {
-                               self._query.setLimit( null );
-                               $limit.data( 'value', '' );
-                               $value.text( '' );
-                               if ( self._changeListener ) {
-                                       self._changeListener( self );
-                               }
-                               return true;//close popover
-                       }
-               } );
-
-               return $limitSection.append( $limit.append( ' ', $value ) );
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getFindSection = function() {
-               var $findSection = $( '<div>' );
-               // Show link
-               var $link = $( '<a class="btn btn-default">' ).text( 
this._i18n( 'find' ) );
-               $link.attr( 'href', '#' ).prepend(
-                               '<span class="glyphicon glyphicon-search" 
aria-hidden="true"></span>', ' ' )
-                               .tooltip( {
-                                       title: 'Click to add new item'
-                               } ).attr( 'data-type', 'item' ).attr( 
'data-auto_open', true );
-
-               // SelectorBox
-               var self = this;
-               this._selectorBox.add( $link, function( id, name ) {
-                       var entity = 'http://www.wikidata.org/entity/' + id;// 
FIXME technical debt
-
-                       var variable = self._query.getBoundVariables().shift();
-                       if ( !variable ) {
-                               variable = '?' + '_' + name.replace( /( 
|[^a-z0-9])/gi, '_' );
-                       }
-
-                       var prop = 'http://www.wikidata.org/prop/direct/P31';// 
FIXME technical debt
-                       var triple = self._query.addTriple( variable, prop, 
entity, false );
-                       if ( !self._query.hasVariable( variable ) ) {
-                               self._query.addVariable( variable );
-                       }
-
-                       if ( $findSection.children().length >= 3 ) {
-                               if ( $findSection.children().length === 3 ) {
-                                       $findSection.append( $( '<span>' 
).text( self._i18n( 'with' ) + ' ' ) );
-                               } else {
-                                       $findSection.append( $( '<span>' 
).text( self._i18n( 'and' ) + ' ' ) );
-                               }
-                       }
-                       $findSection.append( self._getTripleHtml( triple ) );
-
-                       if ( self._changeListener ) {
-                               self._changeListener( self );
-                       }
-               } );
-
-               return $findSection.append( $link, ' ' );
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getShowSection = function() {
-               var $showSection = $( '<div>' );
-               // Show link
-               var $link = $( '<a class="btn btn-default">' ).text( 
this._i18n( 'show' ) );
-               $link.attr( 'href', '#' ).prepend(
-                               '<span class="glyphicon glyphicon-eye-open" 
aria-hidden="true"></span>', ' ' )
-                               .tooltip( {
-                                       title: 'Click to add new property'
-                               } ).attr( 'data-type', 'property' ).attr( 
'data-auto_open', true );
-
-               // SelectorBox
-               var self = this;
-               this._selectorBox.add( $link, function( id, name ) {
-                       var prop = 'http://www.wikidata.org/prop/direct/' + 
id;// FIXME technical debt
-
-                       var subject = self._query.getBoundVariables().shift();
-                       if ( !subject ) {
-                               return;
-                       }
-                       var variable2 = '?_' + name.replace( /( |[^a-z0-9])/gi, 
'_' );// FIXME technical debt
-
-                       var triple = self._query.addTriple( subject, prop, 
variable2, true );
-                       self._query.addVariable( variable2 );
-
-                       if ( $showSection.children().length > 2 ) {
-                               $showSection.append( $( '<span>' ).text( ', ' ) 
);
-                       }
-                       $showSection.append( self._getTripleHtml( triple ) );
-
-                       if ( self._changeListener ) {
-                               self._changeListener( self );
-                       }
-               } );
-
-               return $showSection.append( $link, ' ' );
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._isNotRelevant = function( triple ) {
-
-               if ( FILTER_PREDICATES[triple.predicate] ) {
-                       return true;
-               }
-
-               if ( this._isSimpleMode && this._isInShowSection( triple ) &&
-                               ( this._query.hasVariable( triple.object ) === 
false &&
-                                               this._query.hasVariable( 
triple.object + 'Label' ) === false ) ) {
-                       return true;
-               }
-
-               return false;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._isInShowSection = function( triple ) {
-
-               // Must match ?value wdt:Pxx ?item
-               if ( this._isVariable( triple.subject ) && this._isVariable( 
triple.object ) ) {
-                       return true;
-               }
-
-               return false;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getTripleHtml = function( triple ) {
-               var self = this;
-
-               var $triple = $( '<span>' );
-               $.each( triple.triple, function( k, entity ) {
-
-                       if ( self._isSimpleMode && self._isVariable( entity ) ) 
{
-                               return;
-                       }
-
-                       if ( entity.type && entity.type === 'path' ) {
-                               $triple.append( self._getTripleEntityPathHtml( 
entity, triple, k ), ' ' );
-                       } else {
-                               $triple.append( self._getTripleEntityHtml( 
entity, triple, k ), ' ' );
-                       }
-               } );
-
-               return $triple;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._isVariable = function( entity ) {
-               if ( typeof entity === 'string' && entity.startsWith( '?' ) ) {
-                       return true;
-               }
-               return false;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._isSimpleQuery = function() {
-               var boundVariables = {};
-
-               var self = this;
-               $.each( this._triples, function( k, t ) {
-                       // Must match ?value wdt:Pxx ?item
-                       if ( self._isVariable( t.triple.subject ) &&
-                                       self._isVariable( t.triple.object ) === 
false ) {
-                               boundVariables[t.triple.subject] = true;
-                       }
-
-               } );
-
-               if ( Object.keys( boundVariables ).length > 1 ) {
-                       return false;
-               }
-
-               return true;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getTripleEntityPathHtml = function( path, triple ) {
-               var self = this, $path = $( '<span>' );
-               $.each( path.items, function( k, v ) {
-                       if ( v.type && v.type === 'path' ) {
-                               $path.append( self._getTripleEntityPathHtml( v, 
triple ) );
-                               return;
-                       }
-
-                       if ( k > 0 && path.pathType === '/' ) {
-                               $path.append( ' ' + self._i18n( 'or' ) + ' ' + 
self._i18n( 'subtype' ) + ' ' );
-                       }
-                       if ( path.pathType === '*' ) {
-                               $path.append( ' ' + self._i18n( 'any' ) + ' ' );
-                       }
-
-                       // FIXME: Do not fake triple here
-                       var newTriple = path.items.reduce( function( o, v, i ) {
-                               o[i] = v;
-                               return o;
-                       }, {} );
-                       newTriple = $.extend( newTriple, triple.triple );
-                       triple = $.extend( {}, triple );
-                       triple.triple = newTriple;
-
-                       $path.append( self._getTripleEntityHtml( v, triple, k ) 
);
-
-               } );
-
-               return $path;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getTripleEntityHtml = function( entity, triple, key ) {
-               var $label = $( '<span>' );
-
-               var self = this;
-               this._getLabel( entity ).done( function( label, id, 
description, type ) {
-                       var $link = $( '<a>' ).attr( 'href', '#' );
-                       $link.text( label );
-                       $link.attr( 'data-type', type );
-                       $link.attr( 'data-id', id );
-                       $link.appendTo( $label );
-
-                       $label.tooltip( {
-                               'title': '(' + id + ') ' + description
-                       } );
-                       $( $label ).on( 'show.bs.tooltip', function() {
-                               if ( $( '.tooltip' ).is( ':visible' ) ) {
-                                       $( '.tooltip' ).not( this ).hide();
-                               }
-                       } );
-
-                       //TODO: refactor method
-                       self._selectorBox.add( $link, function( selectedId ) {
-                               var newEntity = entity.replace( new RegExp( id 
+ '$' ), '' ) + selectedId;// TODO: technical debt
-
-                               $label.replaceWith( self._getTripleEntityHtml( 
newEntity, triple, key ) );
-                               triple.triple[key] = newEntity;
-
-                               if ( self._changeListener ) {
-                                       self._changeListener( self );
-                               }
-                       }, {
-                               trash: function() {
-                                       triple.remove();
-
-                                       var variable = triple.triple.object;
-                                       if ( triple.triple.object === entity ||
-                                                       ( 
triple.triple.object.startsWith( '?' ) === false && triple.triple.predicate === 
entity ) ) {
-                                               variable = 
triple.triple.subject;
-                                       }
-                                       if ( $label.parent().next( 'span' 
).length ) {
-                                               $label.parent().next( 'span' 
).remove();
-                                       } else {
-                                               $label.parent().prev( 'span' 
).remove();
-                                       }
-                                       $label.parent().remove();
-
-                                       self._query.removeVariable( variable );
-                                       self._query.removeVariable( variable + 
'Label' );
-
-                                       if ( self._changeListener ) {
-                                               self._changeListener( self );
-                                       }
-                                       $( '.tooltip' ).hide();
-                                       return true;//close popover
-                               },
-                               tag: function() {
-                                       if ( triple.triple.object.startsWith( 
'?' ) ) {
-                                               self._query
-                                                               .addVariable( 
triple.triple.object +
-                                                                               
'Label' );
-                                       } else {
-                                               self._query
-                                                               .addVariable( 
triple.triple.subject +
-                                                                               
'Label' );
-                                       }
-                                       if ( self._changeListener ) {
-                                               self._changeListener( self );
-                                       }
-                                       return true;
-                               }
-                       }
-                       );
-               } ).fail( function() {
-                       $label.text( entity );
-               } );
-
-               return $label;
-       };
-
-       /**
-        * @private
-        */
-       SELF.prototype._getLabel = function( url ) {
-               var deferred = $.Deferred();
-
-               var entity = url.match( /(Q|P)([0-9]+)/ );// TODO: make use of 
Rdf namespaces
-               if ( !entity ) {
-                       return deferred.reject().promise();
-               }
-
-               var type = {
-                       P: 'property',
-                       Q: 'item'
-               };
-               type = type[entity[1]];
-               var term = entity[0];
-
-               this._api.searchEntities( term, type ).done( function( data ) {
-                       $.each( data.search, function( key, value ) {
-                               deferred.resolve( value.label, value.id, 
value.description, type );
-                               return false;
-                       } );
-               } );
-
-               return deferred.promise();
+               this._verbalizer.setChangeListener( listener );
        };
 
        return SELF;
diff --git a/wikibase/tests/VisualEditor.html b/wikibase/tests/VisualEditor.html
index c04cf8f..2c4c557 100644
--- a/wikibase/tests/VisualEditor.html
+++ b/wikibase/tests/VisualEditor.html
@@ -25,6 +25,7 @@
        <script src="../queryService/ui/visualEditor/VisualEditor.js"></script>
        <script src="../queryService/ui/visualEditor/SelectorBox.js"></script>
        <script src="../queryService/ui/visualEditor/SparqlQuery.js"></script>
+       <script 
src="../queryService/ui/visualEditor/SparqlQueryVerbalizer.js"></script>
 <!-- Tests -->
        <script 
src="queryService/ui/visualEditor/VisualEditor.test.js"></script>
        <script src="queryService/ui/visualEditor/SparqlQuery.test.js"></script>

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Id31057b8c5726ce5be45a6293c8ea90fda5ebb26
Gerrit-PatchSet: 1
Gerrit-Project: wikidata/query/gui
Gerrit-Branch: master
Gerrit-Owner: Jonas Kress (WMDE) <[email protected]>

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

Reply via email to