jenkins-bot has submitted this change and it was merged.

Change subject: Refactor gui.js
......................................................................


Refactor gui.js

Refactor gui.js to be oo and split up into several classes.

Bug: T116464
Change-Id: I771c0113503c97c14a4fc6b210ad7e2226570261
---
D gui/gui.js
M gui/index.html
D gui/js/wikibase/queryService/Api.js
D gui/js/wikibase/queryService/ui/App.js
D gui/js/wikibase/queryService/ui/Editor.js
M gui/style.css
A gui/wikibase/init.js
A gui/wikibase/queryService/RdfNamespaces.js
A gui/wikibase/queryService/api/QuerySamples.js
A gui/wikibase/queryService/api/Sparql.js
A gui/wikibase/queryService/ui/App.js
A gui/wikibase/queryService/ui/Editor.js
12 files changed, 1,053 insertions(+), 889 deletions(-)

Approvals:
  Smalyshev: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/gui/gui.js b/gui/gui.js
deleted file mode 100644
index e0c3819..0000000
--- a/gui/gui.js
+++ /dev/null
@@ -1,756 +0,0 @@
-window.mediaWiki = window.mediaWiki || {};
-window.EDITOR = {};
-
-( function ( $, mw ) {
-       var SERVICE = '/bigdata/namespace/wdq/sparql',
-               SHORTURL = 'http://tinyurl.com/create.php?url=',
-               EXPLORE_URL = 'http://www.wikidata.org/entity/Q',
-               NAMESPACE_SHORTCUTS = {
-                       'Wikidata': {
-                               'wikibase': 'http://wikiba.se/ontology#',
-                               'wd': 'http://www.wikidata.org/entity/',
-                               'wdt': 'http://www.wikidata.org/prop/direct/',
-                               'wds': 
'http://www.wikidata.org/entity/statement/',
-                               'p': 'http://www.wikidata.org/prop/',
-                               'wdref': 'http://www.wikidata.org/reference/',
-                               'wdv': 'http://www.wikidata.org/value/',
-                               'ps': 'http://www.wikidata.org/prop/statement/',
-                               'psv': 
'http://www.wikidata.org/prop/statement/value/',
-                               'pq': 'http://www.wikidata.org/prop/qualifier/',
-                               'pqv': 
'http://www.wikidata.org/prop/qualifier/value/',
-                               'pr': 'http://www.wikidata.org/prop/reference/',
-                               'prv': 
'http://www.wikidata.org/prop/reference/value/',
-                               'wdno': 'http://www.wikidata.org/prop/novalue/',
-                               'wdata': 
'http://www.wikidata.org/wiki/Special:EntityData/'
-                       },
-                       'W3C': {
-                               'rdf': 
'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
-                               'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
-                               'owl': 'http://www.w3.org/2002/07/owl#',
-                               'skos': 'http://www.w3.org/2004/02/skos/core#',
-                               'xsd': 'http://www.w3.org/2001/XMLSchema#',
-                               'prov': 'http://www.w3.org/ns/prov#'
-                       },
-                       'Social/Other': {
-                               'schema': 'http://schema.org/'
-                       },
-                       'Blazegraph': {
-                               'bd': 'http://www.bigdata.com/rdf#',
-                               'bds': 'http://www.bigdata.com/rdf/search#',
-                               'gas': 'http://www.bigdata.com/rdf/gas#',
-                               'hint': 'http://www.bigdata.com/queryHints#'
-                       }
-               },
-               STANDARD_PREFIXES = [
-                       'PREFIX wd: <http://www.wikidata.org/entity/>',
-                       'PREFIX wdt: <http://www.wikidata.org/prop/direct/>',
-                       'PREFIX wikibase: <http://wikiba.se/ontology#>',
-                       'PREFIX p: <http://www.wikidata.org/prop/>',
-                       'PREFIX v: <http://www.wikidata.org/prop/statement/>',
-                       'PREFIX q: <http://www.wikidata.org/prop/qualifier/>',
-                       'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>'
-               ].join( '\n' ),
-               QUERY_START = 0,
-               CODEMIRROR_DEFAULTS = {
-                       lineNumbers: true,
-                       matchBrackets: true,
-                       mode: 'sparql',
-                       extraKeys: { 'Ctrl-Space': 'autocomplete' },
-                       viewportMargin: Infinity
-               },
-               ERROR_LINE_MARKER = null,
-               ERROR_CHARACTER_MARKER = null,
-               LAST_RESULT = null,
-               DOWNLOAD_FORMATS = {
-                       'CSV': {
-                               handler: getCsvData,
-                               mimetype: 'text/csv;charset=utf-8'
-                       },
-                       'JSON': {
-                               handler: getJsonData,
-                               mimetype: 'application/json;charset=utf-8'
-                       },
-                       'TSV': {
-                               handler: getSparqlTsvData,
-                               mimetype: 
'text/tab-separated-values;charset=utf-8'
-                       },
-                       'Simple TSV': {
-                               handler: getSimpleTsvData,
-                               mimetype: 
'text/tab-separated-values;charset=utf-8',
-                               ext: 'tsv'
-                       },
-                       'Full JSON': {
-                               handler: getAllJsonData,
-                               mimetype: 'application/json;charset=utf-8',
-                               ext: 'json'
-                       }
-               };
-
-       /**
-        * Submit SPARQL query.
-        *
-        * @param {Event} e
-        */
-       function submitQuery( e ) {
-               e.preventDefault();
-               EDITOR.save();
-
-               LAST_RESULT = null;
-
-               var query = $( '#query-form' ).serialize(),
-                       hash = encodeURIComponent( EDITOR.getValue() ),
-                       url = SERVICE + '?' + query,
-                       settings = {
-                               headers: {
-                                       'Accept': 
'application/sparql-results+json'
-                               },
-                               success: showQueryResults,
-                               error: queryResultsError
-                       };
-               $( '#query-result' ).empty( '' );
-               $( '#query-result' ).hide();
-               $( '#total' ).hide();
-               $( '#query-error' ).show();
-               $( '#query-error' ).text( 'Running query...' );
-               if ( window.location.hash !== hash ) {
-                       window.location.hash = hash;
-               }
-               QUERY_START = Date.now();
-               $.ajax( url, settings );
-       }
-
-       /**
-        * Handle SPARQL error.
-        *
-        * @param {Object} jqXHR
-        * @param {string} textStatus
-        * @param {string} errorThrown
-        */
-       function queryResultsError( jqXHR, textStatus, errorThrown ) {
-               var response,
-                       message = 'ERROR: ';
-
-               if ( jqXHR.status === 0 ) {
-                       message += 'Could not contact server';
-               } else {
-                       response = $( '<div>' ).append( jqXHR.responseText );
-                       message += response.text();
-                       highlightError( jqXHR.responseText );
-                       if ( jqXHR.responseText.match( /Query deadline is 
expired/ ) ) {
-                               message = 'QUERY TIMEOUT\n' + message;
-                       }
-               }
-               $( '#query-error' ).html( $( '<pre>' ).text( message ) ).show();
-       }
-
-       /**
-        * Show results of the query.
-        *
-        * @param {Object} data
-        */
-       function showQueryResults( data ) {
-               var results, thead, i, tr, td, linkText, j, binding,
-                       table = $( '<table>' )
-                               .attr( 'class', 'table' )
-                               .appendTo( $( '#query-result' ) );
-               $( '#query-error' ).hide();
-               $( '#query-result' ).show();
-
-               if ( typeof data.boolean !== 'undefined' ) {
-                       // ASK query
-                       table.append( '<tr><td>' + data.boolean + '</td></tr>' 
).addClass( 'boolean' );
-                       return;
-               }
-
-               LAST_RESULT = data;
-               results = data.results.bindings.length;
-               $( '#total-results' ).text( results );
-               $( '#query-time' ).text( Date.now() - QUERY_START );
-               $( '#total' ).show();
-               $( '#shorturl' ).attr( 'href', SHORTURL + encodeURIComponent( 
window.location ) );
-
-               thead = $( '<thead>' ).appendTo( table );
-               tr = $( '<tr>' );
-               for ( i = 0; i < data.head.vars.length; i++ ) {
-                       tr.append( '<th>' + data.head.vars[i] + '</th>' );
-               }
-               thead.append( tr );
-               table.append( thead );
-
-               for ( i = 0; i < results; i++ ) {
-                       tr = $( '<tr>' ) ;
-                       for ( j = 0; j < data.head.vars.length; j++ ) {
-                               td = $( '<td>' ) ;
-                               if ( data.head.vars[j] in 
data.results.bindings[i] ) {
-                                       binding = 
data.results.bindings[i][data.head.vars[j]];
-                                       var text = binding.value;
-                                       if ( binding.type === 'uri' ) {
-                                               text = abbreviate( text );
-                                       }
-                                       linkText = $( '<pre>' ).text( 
text.trim() );
-                                       if ( binding.type === 'typed-literal' ) 
{
-                                               td.attr( {
-                                                       'class': 'literal',
-                                                       'data-datatype': 
binding.datatype
-                                               } ).append( linkText );
-                                       } else {
-                                               td.attr( 'class', binding.type 
);
-                                               if ( binding.type === 'uri' ) {
-                                                       td.append( $( '<a>' )
-                                                               .attr( 'href', 
binding.value )
-                                                               .append( 
linkText )
-                                                       );
-                                                       if ( 
binding.value.match( EXPLORE_URL ) ) {
-                                                               td.append( $( 
'<a>' )
-                                                                       .attr( 
'href', '#' )
-                                                                       .bind( 
'click', exploreUrl.bind( undefined, binding.value ) )
-                                                                       .text( 
'*' )
-                                                               );
-                                                       }
-                                               } else {
-                                                       td.append( linkText );
-                                               }
-
-                                               if ( binding['xml:lang'] ) {
-                                                       td.attr( {
-                                                               'data-lang': 
binding['xml:lang'],
-                                                               title: 
binding.value + '@' + binding['xml:lang']
-                                                       } );
-                                               }
-                                       }
-                               } else {
-                                       // no binding
-                                       td.attr( 'class', 'unbound' );
-                               }
-                               tr.append( td );
-                       }
-                       table.append( tr );
-               }
-       }
-
-       /**
-        * Produce abbreviation of the URI.
-        *
-        * @param {string} uri
-        * @returns {string}
-        */
-       function abbreviate( uri ) {
-               var nsGroup, ns;
-
-               for ( nsGroup in NAMESPACE_SHORTCUTS ) {
-                       for ( ns in NAMESPACE_SHORTCUTS[nsGroup] ) {
-                               if ( uri.indexOf( 
NAMESPACE_SHORTCUTS[nsGroup][ns] ) === 0 ) {
-                                       return uri.replace( 
NAMESPACE_SHORTCUTS[nsGroup][ns], ns + ':' );
-                               }
-                       }
-               }
-               return '<' + uri + '>';
-       }
-
-       /**
-        * Add standard prefixes to editor window.
-        */
-       function addPrefixes() {
-               var current = EDITOR.getValue();
-               EDITOR.setValue( STANDARD_PREFIXES + '\n\n' + current );
-       }
-
-       /**
-        * Populate namespace shortcut selector.
-        */
-       function populateNamespaceShortcuts() {
-               var category, select, ns,
-                       container = $( '.namespace-shortcuts' );
-
-               container.click( function ( e ) {
-                       e.stopPropagation();
-               } );
-
-               // add namespaces to dropdowns
-               for ( category in NAMESPACE_SHORTCUTS ) {
-                       select = $( '<select>' )
-                               .attr( 'class', 'form-control' )
-                               .append( $( '<option>' ).text( category ) )
-                               .appendTo( container );
-                       for ( ns in NAMESPACE_SHORTCUTS[category] ) {
-                               select.append( $( '<option>' ).text( ns ).attr( 
{
-                                       value: NAMESPACE_SHORTCUTS[category][ns]
-                               } ) );
-                       }
-               }
-       }
-
-       /**
-        * Add selected namespace's prefix to editor.
-        */
-       function selectNamespace() {
-               var ns,
-                       uri = this.value,
-                       current = EDITOR.getValue();
-
-               if ( current.indexOf( '<' + uri + '>' ) === -1 ) {
-                       ns = $( this ).find( ':selected' ).text();
-                       EDITOR.setValue( 'prefix ' + ns + ': <' + uri + '>\n' + 
current );
-               }
-
-               // reselect group label
-               this.selectedIndex = 0;
-       }
-
-       /**
-        * Show/hide help text.
-        *
-        * @param {Event} e
-        */
-       function showHideHelp( e ) {
-               var $seeAlso = $( '#seealso' );
-
-               e.preventDefault();
-               $seeAlso.toggle();
-               if ( $seeAlso.is( ':visible' ) ) {
-                       $( '#showhide' ).text( 'hide' );
-               } else {
-                       $( '#showhide' ).text( 'show' );
-               }
-       }
-
-       /**
-        * Initialize query editor window.
-        */
-       function initQuery() {
-               if ( window.location.hash !== '' ) {
-                       EDITOR.setValue( decodeURIComponent( 
window.location.hash.substr( 1 ) ) );
-                       EDITOR.refresh();
-               }
-       }
-
-       /**
-        * Setup editor window.
-        */
-       function setupEditor() {
-               EDITOR = CodeMirror.fromTextArea( $( '#query' )[0], 
CODEMIRROR_DEFAULTS );
-               EDITOR.on( 'change', function () {
-                       if ( ERROR_LINE_MARKER ) {
-                               ERROR_LINE_MARKER.clear();
-                               ERROR_CHARACTER_MARKER.clear();
-                       }
-               } );
-               EDITOR.addKeyMap( { 'Ctrl-Enter': submitQuery } );
-               EDITOR.focus();
-
-               new WikibaseRDFTooltip(EDITOR);
-       }
-
-       /**
-        * Highlight SPARQL error in editor window.
-        *
-        * @param {string} description
-        */
-       function highlightError( description ) {
-               var line, character,
-                       match = description.match( /line (\d+), column (\d+)/ );
-               if ( match ) {
-                       // highlight character at error position
-                       line = match[1] - 1;
-                       character = match[2] - 1;
-                       ERROR_LINE_MARKER = EDITOR.doc.markText(
-                               { line: line, ch: 0 },
-                               { line: line },
-                               { className: 'error-line' }
-                       );
-                       ERROR_CHARACTER_MARKER = EDITOR.doc.markText(
-                               { line: line, ch: character },
-                               { line: line, ch: character + 1 },
-                               { className: 'error-character' }
-                       );
-               }
-       }
-
-       /**
-        * Show explorer window for given URL.
-        *
-        * @param {string} url
-        */
-       function exploreUrl( url ) {
-               var id,
-                       match = url.match( EXPLORE_URL + '(.+)' );
-               if ( !match ) {
-                       return;
-               }
-               if ( $( '#hide-explorer' ).is( ':visible' ) ) {
-                       $( '#explore' ).empty( '' );
-               } else {
-                       $( '#hide-explorer' ).show();
-                       $( '#show-explorer' ).hide();
-               }
-               id = match[1];
-               mw.config = { get: function () {
-                       return id;
-               } };
-               $( 'html, body' ).animate( { scrollTop: $( '#explore' 
).offset().top }, 500 );
-               EXPLORER( $, mw, $( '#explore' ) );
-       }
-
-       /**
-        * Hide explorer window.
-        *
-        * @param {Event} e
-        */
-       function hideExplorer( e ) {
-               e.preventDefault();
-               $( '#explore' ).empty( '' );
-               $( '#hide-explorer' ).hide();
-               $( '#show-explorer' ).show();
-       }
-
-       /**
-        * Setup query examples.
-        */
-       function setupExamples() {
-               var exampleQueries = document.getElementById( 'exampleQueries' 
);
-
-               $.ajax( {
-                       url: 
'https://www.mediawiki.org/w/api.php?action=query&prop=revisions&titles=Wikibase/'
-                               + 
'Indexing/SPARQL_Query_Examples&rvprop=content',
-                       data: {
-                               format: 'json'
-                       },
-                       dataType: 'jsonp'
-               } ).done( function ( data ) {
-                       var wikitext = data.query.pages[Object.keys( 
data.query.pages )].revisions[0]['*'];
-                       var paragraphs = wikitext.split( '==' );
-
-                       $.each( paragraphs, function ( key, paragraph ) {
-                               if ( paragraph.match( /SPARQL\|.*query\=/ ) ) {
-                                       var query = paragraph.substring(
-                                               paragraph.indexOf( '|query=' ) 
+ 7,
-                                               paragraph.lastIndexOf( '}}' )
-                                       ).trim();
-                                       var title = paragraphs[key - 1] || '';
-                                       title = title.replace( '=', '' ).trim();
-
-                                       if ( paragraph.match( 'extraprefix=' ) 
) {
-                                               var prefix = 
paragraph.substring( paragraph.indexOf( '|extraprefix=' ) + 13, 
paragraph.indexOf( '|query=' ) ).trim();
-                                               query = prefix + '\n\n' + query;
-                                       }
-
-                                       if ( title ) {
-                                               exampleQueries.add( new Option( 
title, query ) );
-                                       }
-                               }
-                       } )
-               } );
-       }
-
-       /**
-        * Add query example to editor window.
-        */
-       function pasteExample() {
-               var text = this.value;
-               this.selectedIndex = 0;
-               if ( !text || !text.trim() ) {
-                       return;
-               }
-               EDITOR.setValue( text );
-               addPrefixes();
-       }
-
-       /**
-        * Setup event handlers.
-        */
-       function setupHandlers() {
-               $( '#query-form' ).submit( submitQuery );
-               $( '.namespace-shortcuts' ).on( 'change', 'select', 
selectNamespace );
-               $( '.exampleQueries' ).on( 'change', pasteExample );
-               $( '.addPrefixes' ).click( addPrefixes );
-               $( '#showhide' ).click( showHideHelp );
-               $( '#hide-explorer' ).click( hideExplorer );
-               $( '#clear-button' ).click( function () {
-                       EDITOR.setValue( '' );
-               } );
-               for ( format in DOWNLOAD_FORMATS ) {
-                       var extension = DOWNLOAD_FORMATS[format].ext || 
format.toLowerCase();
-                       var formatName = format.replace( /\s/g, '-' );
-                       $( '#download' + formatName ).click( downloadHandler( 
'query.' + extension,
-                               DOWNLOAD_FORMATS[format].handler, 
DOWNLOAD_FORMATS[format].mimetype
-                       ) );
-               }
-       }
-
-       /**
-        * Create download handler function.
-        *
-        * @param {string} filename
-        * @param {Function} handler
-        * @param {string} mimetype
-        * @return {Function}
-        */
-       function downloadHandler( filename, handler, mimetype ) {
-               return function ( e ) {
-                       e.preventDefault();
-                       if ( !LAST_RESULT ) {
-                               return '';
-                       }
-                       download( filename, handler( LAST_RESULT ), mimetype );
-               }
-       }
-
-       /**
-        * Fetch last DB update time.
-        */
-       function getDbUpdated() {
-               var query = encodeURI( 'prefix schema: <http://schema.org/> '
-                       + 'SELECT * WHERE {<http://www.wikidata.org> 
schema:dateModified ?y}' );
-               var url = SERVICE + '?query=' + query,
-                       settings = {
-                               headers: {
-                                       'Accept': 
'application/sparql-results+json'
-                               },
-                               success: showDbQueryResults,
-                               error: dbQueryResultsError
-                       };
-               $.ajax( url, settings );
-       }
-
-       /**
-        * Show results for last DB update time.
-        *
-        * @param {Object} data
-        */
-       function showDbQueryResults( data ) {
-               try {
-                       var updateDate = new Date( 
data.results.bindings[0][data.head.vars[0]].value );
-                       $( '#dbUpdated' ).text( updateDate.toLocaleTimeString(
-                               navigator.language,
-                               { timeZoneName: 'short' }
-                       ) + ', ' + updateDate.toLocaleDateString(
-                               navigator.language,
-                               { month: 'short', day: 'numeric', year: 
'numeric' }
-                       ) );
-               } catch ( err ) {
-                       $( '#dbUpdated' ).text( '[unable to connect]' );
-               }
-       }
-
-       /**
-        * Show error for last DB update time.
-        *
-        * @param {Object} jqXHR
-        * @param {string} textStatus
-        * @param {string} errorThrown
-        */
-       function dbQueryResultsError( jqXHR, textStatus, errorThrown ) {
-               $( '#dbUpdated' ).text( '[unable to connect]' );
-       }
-
-       /**
-        * Initialize GUI
-        */
-       function startGui() {
-               setupEditor();
-               setupExamples();
-               populateNamespaceShortcuts();
-               setupHandlers();
-               initQuery();
-               getDbUpdated();
-       }
-
-       /**
-        * Process SPARQL query result.
-        *
-        * @param {Object} data
-        * @param {Function} rowHandler
-        * @param {*} context
-        * @return {*} The provided context, modified by the rowHandler.
-        */
-       function processData( data, rowHandler, context ) {
-               var results = data.results.bindings.length;
-               for ( var i = 0; i < results; i++ ) {
-                       var rowBindings = {};
-                       for ( var j = 0; j < data.head.vars.length; j++ ) {
-                               if ( data.head.vars[j] in 
data.results.bindings[i] ) {
-                                       rowBindings[data.head.vars[j]] = 
data.results.bindings[i][data.head.vars[j]];
-                               }
-                       }
-                       context = rowHandler( rowBindings, context );
-               }
-               return context;
-       }
-
-       /**
-        * Encode string as CSV.
-        *
-        * @param {string} string
-        * @return {string}
-        */
-       function encodeCsv( string ) {
-               var result = string.replace( /"/g, '""' );
-               if ( result.search( /("|,|\n)/g ) >= 0 ) {
-                       result = '"' + result + '"';
-               }
-               return result;
-       }
-
-       /**
-        * Get CSV rendering of the result data.
-        *
-        * @param {Object} data
-        * @return {string}
-        */
-       function getCsvData( data ) {
-               var out = data.head.vars.map( encodeCsv ).join( ',' ) + '\n';
-               out = processData( data, function ( row, out ) {
-                       var rowOut = '';
-                       for ( rowVar in row ) {
-                               var rowCSV = encodeCsv( row[rowVar].value );
-                               if ( rowOut.length > 0 ) {
-                                       rowOut += ',';
-                               }
-                               rowOut += rowCSV;
-                       }
-                       if ( rowOut.length > 0 ) {
-                               rowOut += '\n';
-                       }
-                       return out + rowOut;
-               }, out );
-               return out;
-       }
-
-       /**
-        * Get TSV rendering of the result data.
-        *
-        * @param {Object} data
-        * @return {string}
-        */
-       function getSimpleTsvData( data ) {
-               var out = data.head.vars.join( '\t' ) + '\n';
-               out = processData( data, function ( row, out ) {
-                       var rowOut = '';
-                       for ( rowVar in row ) {
-                               var rowTSV = row[rowVar].value.replace( /\t/g, 
'' );
-                               if ( rowOut.length > 0 ) {
-                                       rowOut += '\t';
-                               }
-                               rowOut += rowTSV;
-                       }
-                       if ( rowOut.length > 0 ) {
-                               rowOut += '\n';
-                       }
-                       return out + rowOut;
-               }, out );
-               return out;
-       }
-
-       /**
-        * Render value as per 
http://www.w3.org/TR/sparql11-results-csv-tsv/#tsv
-        *
-        * @param {Object} binding
-        * @return {string}
-        */
-       function renderValueTSV( binding ) {
-               var value = binding.value.replace( /\t/g, '' );
-               switch ( binding.type ) {
-                       case 'uri':
-                               return '<' + value + '>';
-                       case 'bnode':
-                               return '_:' + value;
-                       case 'literal':
-                               var lvalue = JSON.stringify( value );
-                               if ( binding['xml:lang'] ) {
-                                       return lvalue + '@' + 
binding['xml:lang'];
-                               }
-                               if ( binding.datatype ) {
-                                       if ( binding.datatype === 
'http://www.w3.org/2001/XMLSchema#integer' ||
-                                               binding.datatype === 
'http://www.w3.org/2001/XMLSchema#decimal' ||
-                                               binding.datatype === 
'http://www.w3.org/2001/XMLSchema#double'
-                                       ) {
-                                               return value;
-                                       }
-                                       return lvalue + '^^<' + 
binding.datatype + '>';
-                               }
-                               return lvalue;
-               }
-               return value;
-       }
-
-       /**
-        * Get TSV rendering of the result data according to SPARQL standard.
-        * See: http://www.w3.org/TR/sparql11-results-csv-tsv/#tsv
-        *
-        * @param {Object} data
-        * @return {string}
-        */
-       function getSparqlTsvData( data ) {
-               var out = data.head.vars.map( function ( vname ) {
-                       return '?' + vname;
-               } ).join( '\t' ) + '\n';
-               out = processData( data, function ( row, out ) {
-                       var rowOut = '';
-                       for ( rowVar in row ) {
-                               var rowTSV = renderValueTSV( row[rowVar] );
-                               if ( rowOut.length > 0 ) {
-                                       rowOut += '\t';
-                               }
-                               rowOut += rowTSV;
-                       }
-                       if ( rowOut.length > 0 ) {
-                               rowOut += '\n';
-                       }
-                       return out + rowOut;
-               }, out );
-               return out;
-       }
-
-       /**
-        * Get JSON rendering of the result data.
-        *
-        * @param {Object} data
-        * @return {string}
-        */
-       function getJsonData( data ) {
-               var out = [];
-               out = processData( data, function ( row, out ) {
-                       var extractRow = {};
-                       for ( rowVar in row ) {
-                               extractRow[rowVar] = row[rowVar].value;
-                       }
-                       out.push( extractRow );
-                       return out;
-               }, out );
-               return JSON.stringify( out );
-       }
-
-       /**
-        * @param {Object} data
-        * @returns {string}
-        */
-       function getAllJsonData( data ) {
-               return JSON.stringify( data );
-       }
-
-       /**
-        * Produce file download.
-        *
-        * @param {string} filename
-        * @param {string} text
-        * @param {string} contentType
-        */
-       function download( filename, text, contentType ) {
-               if ( !text ) {
-                       return;
-               }
-               var element = document.createElement( 'a' );
-               element.setAttribute( 'href', 'data:' + contentType + ',' + 
encodeURIComponent( text ) );
-               element.setAttribute( 'download', filename );
-
-               element.style.display = 'none';
-               document.body.appendChild( element );
-               element.click();
-               document.body.removeChild( element );
-       }
-
-       $( document ).ready( function () {
-               startGui();
-       } );
-       $( window ).on( 'popstate', initQuery );
-} )( jQuery, mediaWiki );
diff --git a/gui/index.html b/gui/index.html
index c98cf70..db2efc2 100644
--- a/gui/index.html
+++ b/gui/index.html
@@ -10,10 +10,18 @@
 <script src="wikibase/codemirror/addon/hint/wikibase-sparql-hint.js"></script>
 <script src="wikibase/codemirror/addon/hint/wikibase-rdf-hint.js"></script>
 <script src="wikibase/codemirror/addon/tooltip/WikibaseRDFTooltip.js"></script>
-<script src="gui.js"></script>
+
+<script src="wikibase/queryService/ui/App.js"></script>
+<script src="wikibase/queryService/ui/Editor.js"></script>
+<script src="wikibase/queryService/api/Sparql.js"></script>
+<script src="wikibase/queryService/api/QuerySamples.js"></script>
+<script src="wikibase/queryService/RdfNamespaces.js"></script>
+<script src="wikibase/init.js"></script>
+
 <script src="vis.js"></script>
 <script src="wdqs.js"></script>
 <script src="wdqs-explorer.js"></script>
+
 <link rel="stylesheet" href="vendor/bootstrap/bootstrap.min.css">
 <link rel="stylesheet" href="vendor/codemirror/lib/codemirror.css">
 <link rel="stylesheet" href="vendor/codemirror/addon/hint/show-hint.css">
@@ -24,7 +32,7 @@
 <title>Wikidata Query Service (Beta)</title>
 </head>
 <body>
-       <div class="container-fluid">
+       <div class="wikibase-queryservice container-fluid">
                <div class="row">
                        <!-- Editor Navbar -->
                        <nav class="navbar navbar-default">
@@ -55,7 +63,7 @@
                                                                <li><a 
target="_blank" 
href="https://www.mediawiki.org/wiki/Wikibase/Indexing/RDF_Dump_Format";>RDF 
Data Model</a></li>
                                                                <li><a 
target="_blank" 
href="https://www.mediawiki.org/wiki/Wikibase/Indexing/RDF_Dump_Format#Full_list_of_prefixes";>List
 of prefixes</a></li>
                                                        </ul></li>
-                                               <li><select id="exampleQueries" 
class="exampleQueries form-control navbar-btn"><option value="">Query 
Examples</option></select></li>
+                                               <li><select 
class="exampleQueries form-control navbar-btn"><option value="">Query 
Examples</option></select></li>
                                        </ul>
                                </div>
                        </nav>
@@ -63,7 +71,7 @@
                        <div class="col-md-12" id="query-box">
                                <form class="form-horizontal" id="query-form">
                                        <div class="form-group">
-                                               <textarea id="query" 
name="query" placeholder="(Input a SPARQL query or choose a query 
example)"></textarea>
+                                               <textarea class="queryEditor" 
name="query" placeholder="(Input a SPARQL query or choose a query 
example)"></textarea>
                                                <div class="alert alert-success 
alert-dismissible" role="alert">
                                                        <button type="button" 
class="close" data-dismiss="alert" aria-label="Close"><span 
aria-hidden="true">&times;</span></button>
                                                        Press 
<i>[CTRL-SPACE]</i> to activate auto completion. Data last updated: <span 
id="dbUpdated">[connecting]</span>
@@ -82,8 +90,7 @@
                <div class="row" id="total">
                        <div class="col-md-4 pull-left">
                                Total results: <span 
id="total-results"></span>, duration: <span
-                                       id="query-time"></span> ms. <span 
id="show-explorer">Click
-                                       on <b>*</b> to explore.
+                                       id="query-time"></span> ms. <span 
id="show-explorer">
                                </span>
                                <button id="hide-explorer" href="#">Hide 
explorer</button>
                        </div>
diff --git a/gui/js/wikibase/queryService/Api.js 
b/gui/js/wikibase/queryService/Api.js
deleted file mode 100644
index 7462900..0000000
--- a/gui/js/wikibase/queryService/Api.js
+++ /dev/null
@@ -1,44 +0,0 @@
-var wikibase = wikibase || { queryService: { ui: {}} };
-
-wikibase.queryService.Api = ( function( $ ) {
-       "use strict";
-
-       /**
-        * API for the Wikibase query service
-        *
-        * @class wikibase.queryService.Api
-        * @licence GNU GPL v2+
-        * @author Jonas Kress
-        * @constructor
-        */
-       function SELF() {
-       }
-
-       /**
-        * Submit a query to the API
-        *
-        * @param {string[]} data
-        * @return {jQuery.Promise}
-        **/
-       SELF.prototype.submitQuery = function( data ){
-       };
-
-       /**
-        * Get the result of the submitted query
-        *
-        * @return {string}
-        **/
-       SELF.prototype.getResult = function(){
-       };
-
-       /**
-        * Get the result of the submitted query as CSV
-        *
-        * @return {string}
-        **/
-       SELF.prototype.getResultAsCSV = function(){
-       };
-
-       return SELF;
-
-}( jQuery ) );
diff --git a/gui/js/wikibase/queryService/ui/App.js 
b/gui/js/wikibase/queryService/ui/App.js
deleted file mode 100644
index 705d559..0000000
--- a/gui/js/wikibase/queryService/ui/App.js
+++ /dev/null
@@ -1,53 +0,0 @@
-var wikibase = wikibase || { queryService: { ui: {}} };
-
-wikibase.queryService.ui.App = ( function( $ ) {
-       "use strict";
-
-       /**
-        * A ui application for the Wikibase query service
-        *
-        * @class wikibase.queryService.ui.App
-        * @licence GNU GPL v2+
-        * @author Jonas Kress
-        * @constructor
-        *
-        * @param {jQuery} $element
-        * @param {wikibase.queryService.ui.Editor}
-        * @param {wikibase.queryService.API}
-        */
-       function SELF( $element, editor, api ) {
-               this.$element = $element;
-               this.editor = editor;
-               this.api = api;
-
-               if( !this.api ){
-                       this.api = new wikibase.queryService.Api();
-               }
-
-               if( !this.editor ){
-                       this.editor = new wikibase.queryService.ui.Editor();
-               }
-       }
-
-       /**
-        * @property {string}
-        * @private
-        **/
-       this.$element = null;
-
-       /**
-        * @property {wikibase.queryService.API}
-        * @private
-        **/
-       this.api = null;
-
-       /**
-        * @property {wikibase.queryService.ui.Editor}
-        * @type wikibase.queryService.ui.Editor
-        * @private
-        **/
-       this.editor = null;
-
-
-       return SELF;
-}( jQuery ) );
diff --git a/gui/js/wikibase/queryService/ui/Editor.js 
b/gui/js/wikibase/queryService/ui/Editor.js
deleted file mode 100644
index 30bae83..0000000
--- a/gui/js/wikibase/queryService/ui/Editor.js
+++ /dev/null
@@ -1,29 +0,0 @@
-var wikibase = wikibase || { queryService: { ui: {}} };
-
-wikibase.queryService.ui.Editor = ( function( $ ) {
-       "use strict";
-
-       /**
-        * An ui editor for the Wikibase query service
-        *
-        * @class wikibase.queryService.ui.Editor
-        * @licence GNU GPL v2+
-        * @author Jonas Kress
-        * @constructor
-        */
-       function SELF() {
-       }
-
-       /**
-        * Construct an editor on the given textarea DOM element
-        *
-        * @param {jQuery} $element
-        * @throws {Error} If given element is not a valid jQuery textarea
-        **/
-       SELF.prototype.fromTextArea = function( $element ){
-       };
-
-
-       return SELF;
-
-}( jQuery ) );
diff --git a/gui/style.css b/gui/style.css
index 039611c..0064d22 100644
--- a/gui/style.css
+++ b/gui/style.css
@@ -16,7 +16,7 @@
 }
 
 .error-line {
-       background: red;
+       border-bottom: 2px dotted red;
 }
 
 .error-character {
diff --git a/gui/wikibase/init.js b/gui/wikibase/init.js
new file mode 100644
index 0000000..7922f7c
--- /dev/null
+++ b/gui/wikibase/init.js
@@ -0,0 +1,4 @@
+$( document ).ready( function () {
+       new wikibase.queryService.ui.App($('.wikibase-queryservice '));
+} );
+
diff --git a/gui/wikibase/queryService/RdfNamespaces.js 
b/gui/wikibase/queryService/RdfNamespaces.js
new file mode 100644
index 0000000..2bf73b2
--- /dev/null
+++ b/gui/wikibase/queryService/RdfNamespaces.js
@@ -0,0 +1,56 @@
+var wikibase = wikibase || {};
+wikibase.queryService = wikibase.queryService || {};
+
+wikibase.queryService.RdfNamespaces = {};
+
+(function(RdfNamespaces){
+
+       RdfNamespaces.NAMESPACE_SHORTCUTS = {
+               'Wikidata' : {
+                       'wikibase': 'http://wikiba.se/ontology#',
+                       'wd': 'http://www.wikidata.org/entity/',
+                       'wdt': 'http://www.wikidata.org/prop/direct/',
+                       'wds': 'http://www.wikidata.org/entity/statement/',
+                       'p': 'http://www.wikidata.org/prop/',
+                       'wdref': 'http://www.wikidata.org/reference/',
+                       'wdv': 'http://www.wikidata.org/value/',
+                       'ps': 'http://www.wikidata.org/prop/statement/',
+                       'psv': 'http://www.wikidata.org/prop/statement/value/',
+                       'pq': 'http://www.wikidata.org/prop/qualifier/',
+                       'pqv': 'http://www.wikidata.org/prop/qualifier/value/',
+                       'pr': 'http://www.wikidata.org/prop/reference/',
+                       'prv': 'http://www.wikidata.org/prop/reference/value/',
+                       'wdno': 'http://www.wikidata.org/prop/novalue/',
+                       'wdata': 
'http://www.wikidata.org/wiki/Special:EntityData/'
+               },
+               'W3C' : {
+                       'rdfs' : 'http://www.w3.org/2000/01/rdf-schema#',
+                       'rdf' : 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+                       'owl' : 'http://www.w3.org/2002/07/owl#',
+                       'skos' : 'http://www.w3.org/2004/02/skos/core#',
+                       'xsd' : 'http://www.w3.org/2001/XMLSchema#',
+                       'prov' : 'http://www.w3.org/ns/prov#'
+               },
+               'Social/Other' : {
+                       'schema' : 'http://schema.org/'
+               },
+               'Blazegraph' : {
+                       'bd' : 'http://www.bigdata.com/rdf#',
+                       'bds' : 'http://www.bigdata.com/rdf/search#',
+                       'gas' : 'http://www.bigdata.com/rdf/gas#',
+                       'hint' : 'http://www.bigdata.com/queryHints#'
+               }
+       };
+
+       RdfNamespaces.STANDARD_PREFIXES = [
+               'PREFIX wd: <http://www.wikidata.org/entity/>',
+               'PREFIX wdt: <http://www.wikidata.org/prop/direct/>',
+               'PREFIX wikibase: <http://wikiba.se/ontology#>',
+               'PREFIX p: <http://www.wikidata.org/prop/>',
+               'PREFIX v: <http://www.wikidata.org/prop/statement/>',
+               'PREFIX q: <http://www.wikidata.org/prop/qualifier/>',
+               'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>'
+               ];
+
+})(wikibase.queryService.RdfNamespaces);
+
diff --git a/gui/wikibase/queryService/api/QuerySamples.js 
b/gui/wikibase/queryService/api/QuerySamples.js
new file mode 100644
index 0000000..c3417ec
--- /dev/null
+++ b/gui/wikibase/queryService/api/QuerySamples.js
@@ -0,0 +1,68 @@
+var wikibase = wikibase || {};
+wikibase.queryService = wikibase.queryService || {};
+wikibase.queryService.api = wikibase.queryService.api || {};
+
+wikibase.queryService.api.QuerySamples = ( function( $ ) {
+       "use strict";
+
+       /**
+        * QuerySamples API for the Wikibase query service
+        *
+        * @class wikibase.queryService.api.QuerySamples
+        * @licence GNU GPL v2+
+        *
+        * @author Stanislav Malyshev
+        * @author Jonas Kress
+        * @constructor
+        */
+       function SELF() {
+       }
+
+       /**
+        * @return {jQuery.Deferred} Object taking list of example queries  { 
title:, query: }
+        **/
+       SELF.prototype.getExamples = function() {
+
+               var examples = {}, deferred = $.Deferred();
+
+               $.ajax( {
+                       url: 
'https://www.mediawiki.org/w/api.php?action=query&prop=revisions&titles=Wikibase/'
 +
+                       'Indexing/SPARQL_Query_Examples&rvprop=content',
+                       data: {
+                               format: 'json'
+                       },
+                       dataType: 'jsonp'
+               } ).done( function ( data ) {
+                       var wikitext = data.query.pages[Object.keys( 
data.query.pages )].revisions[0]['*'];
+                       var paragraphs = wikitext.split( '==' );
+
+                       //TODO extract and make more robust against wrong 
formatting on the wiki page
+                       $.each( paragraphs, function ( key, paragraph ) {
+                               if ( paragraph.match( /SPARQL\|.*query\=/ ) ) {
+                                       var query = paragraph.substring(
+                                               paragraph.indexOf( '|query=' ) 
+ 7,
+                                               paragraph.lastIndexOf( '}}' )
+                                       ).trim();
+                                       var title = paragraphs[key - 1] || '';
+                                       title = title.replace( '=', '' ).trim();
+
+                                       if ( paragraph.match( 'extraprefix=' ) 
) {
+                                               var prefix = 
paragraph.substring( paragraph.indexOf( '|extraprefix=' ) + 13, 
paragraph.indexOf( '|query=' ) ).trim();
+                                               query = prefix + '\n\n' + query;
+                                       }
+
+                                       if ( title ) {
+                                               examples[title] = query;
+                                       }
+                               }
+                       } );
+
+                       deferred.resolve( examples );
+               } );
+
+               return deferred;
+       };
+
+       return SELF;
+
+}( jQuery ) );
diff --git a/gui/wikibase/queryService/api/Sparql.js 
b/gui/wikibase/queryService/api/Sparql.js
new file mode 100644
index 0000000..2de8db1
--- /dev/null
+++ b/gui/wikibase/queryService/api/Sparql.js
@@ -0,0 +1,425 @@
+var wikibase = wikibase || {};
+wikibase.queryService = wikibase.queryService || {};
+wikibase.queryService.api = wikibase.queryService.api || {};
+
+wikibase.queryService.api.Sparql = (function($) {
+       "use strict";
+
+       var SERVICE = '/bigdata/namespace/wdq/sparql';
+
+       /**
+        * SPARQL API for the Wikibase query service
+        *
+        * @class wikibase.queryService.api.Sparql
+        * @licence GNU GPL v2+
+        *
+        * @author Stanislav Malyshev
+        * @author Jonas Kress
+        * @constructor
+        */
+       function SELF() {
+       }
+
+       /**
+        * @property {int}
+        * @private
+        **/
+       SELF.prototype._executionTime = null;
+
+       /**
+        * @property {string}
+        * @private
+        **/
+       SELF.prototype._errorMessage = null;
+
+       /**
+        * @property {int}
+        * @private
+        **/
+       SELF.prototype._resultLength = null;
+
+       /**
+        * @property {Object}
+        * @private
+        **/
+       SELF.prototype._rawData = null;
+
+       /**
+        * Submit a query to the API
+        *
+        * @return {jQuery.Promise}
+        */
+       SELF.prototype.queryDataUpdatedTime = function() {
+
+               var deferred = $.Deferred(),
+               query = encodeURI('prefix schema: <http://schema.org/> ' +
+                               'SELECT * WHERE {<http://www.wikidata.org> 
schema:dateModified ?y}'),
+               url = SERVICE + '?query=' + query, settings = {
+                       'headers' : {
+                               'Accept' : 'application/sparql-results+json'
+                       },
+               };
+
+               $.ajax( url, settings ).done(function( data ){
+                       var updateDate = new Date( 
data.results.bindings[0][data.head.vars[0]].value ),
+                       dateText = updateDate.toLocaleTimeString(
+                               navigator.language,
+                               { timeZoneName: 'short' }
+                       ) + ', ' + updateDate.toLocaleDateString(
+                               navigator.language,
+                               { month: 'short', day: 'numeric', year: 
'numeric' }
+                       );
+
+                       deferred.resolve( dateText );
+               }).fail(function(){
+                       deferred.reject();
+               });
+
+               return deferred;
+       };
+
+       /**
+        * Submit a query to the API
+        *
+        * @param {string[]}
+        *            query
+        * @return {jQuery.Promise}
+        */
+       SELF.prototype.query = function(query) {
+               var deferred = $.Deferred(), self = this;
+
+               var url = SERVICE + '?' + query, settings = {
+                       'headers' : {
+                               'Accept' : 'application/sparql-results+json'
+                       },
+               };
+
+               this._executionTime = Date.now();
+               $.ajax( url, settings ).done(function( data, textStatus, jqXHR 
) {
+                       self._executionTime = Date.now() - self._executionTime;
+                       self._resultLength = data.results.bindings.length || 
null;
+                       self._rawData = data;
+
+                       deferred.resolve();
+               }).fail(function( jqXHR, textStatus, errorThrown ) {
+                       self._executionTime = null;
+                       self._rawData = null;
+                       self._resultLength = null;
+                       self._generateErrorMessage(jqXHR);
+
+                       deferred.reject();
+               });
+
+               return deferred;
+       };
+
+       /**
+        * Get execution time in ms of the submitted query
+        *
+        * @return {int}
+        */
+       SELF.prototype._generateErrorMessage = function( jqXHR ) {
+               var message = 'ERROR: ';
+
+               if ( jqXHR.status === 0 ) {
+                       message += 'Could not contact server';
+               } else {
+                       message += jqXHR.responseText;
+                       if ( jqXHR.responseText.match( /Query deadline is 
expired/ ) ) {
+                               message = 'QUERY TIMEOUT\n' + message;
+                       }
+               }
+               this._errorMessage = message;
+       };
+
+       /**
+        * Produce abbreviation of the URI.
+        *
+        * @param {string} uri
+        * @returns {string}
+        */
+       SELF.prototype.abbreviateUri = function( uri ) {
+               var nsGroup, ns, NAMESPACE_SHORTCUTS = 
wikibase.queryService.RdfNamespaces.NAMESPACE_SHORTCUTS;
+
+               for ( nsGroup in NAMESPACE_SHORTCUTS ) {
+                       for ( ns in NAMESPACE_SHORTCUTS[nsGroup] ) {
+                               if ( uri.indexOf( 
NAMESPACE_SHORTCUTS[nsGroup][ns] ) === 0 ) {
+                                       return uri.replace( 
NAMESPACE_SHORTCUTS[nsGroup][ns], ns + ':' );
+                               }
+                       }
+               }
+               return '<' + uri + '>';
+       };
+
+       /**
+        * Get execution time in seconds of the submitted query
+        *
+        * @return {int}
+        */
+       SELF.prototype.getExecutionTime = function() {
+               return this._executionTime;
+       };
+
+       /**
+        * Get error message of the submitted query if it has failed
+        *
+        * @return {int}
+        */
+       SELF.prototype.getErrorMessage = function() {
+               return this._errorMessage;
+       };
+
+       /**
+        * Get result length of the submitted query if it has failed
+        *
+        * @return {int}
+        */
+       SELF.prototype.getResultLength = function() {
+               return this._resultLength;
+       };
+
+       /**
+        * Get the result as table
+        *
+        * @return {jQuery}
+        */
+       SELF.prototype.getResultAsTable = function() {
+               var data = this._rawData, thead, tr, td, binding,
+               table = $( '<table>' ).attr( 'class', 'table' );
+
+               if ( typeof data.boolean !== 'undefined' ) {
+                       // ASK query
+                       table.append( '<tr><td>' + data.boolean + '</td></tr>' 
).addClass( 'boolean' );
+                       return;
+               }
+
+               thead = $( '<thead>' ).appendTo( table );
+               tr = $( '<tr>' );
+               for ( i = 0; i < data.head.vars.length; i++ ) {
+                       tr.append( '<th>' + data.head.vars[i] + '</th>' );
+               }
+               thead.append( tr );
+               table.append( thead );
+
+               for (var i = 0; i < this._resultLength; i++ ) {
+                       tr = $( '<tr>' ) ;
+                       for ( var j = 0; j < data.head.vars.length; j++ ) {
+                               td = $( '<td>' ) ;
+                               if ( data.head.vars[j] in 
data.results.bindings[i] ) {
+                                       binding = 
data.results.bindings[i][data.head.vars[j]];
+                                       var text = binding.value;
+                                       if ( binding.type === 'uri' ) {
+                                               text = this.abbreviateUri( text 
);
+                                       }
+                                       var linkText = $( '<pre>' ).text( 
text.trim() );
+                                       if ( binding.type === 'typed-literal' ) 
{
+                                               td.attr( {
+                                                       'class': 'literal',
+                                                       'data-datatype': 
binding.datatype
+                                               } ).append( linkText );
+                                       } else {
+                                               td.attr( 'class', binding.type 
);
+                                               if ( binding.type === 'uri' ) {
+                                                       td.append( $( '<a>' )
+                                                               .attr( 'href', 
binding.value )
+                                                               .append( 
linkText )
+                                                       );
+                                               } else {
+                                                       td.append( linkText );
+                                               }
+
+                                               if ( binding['xml:lang'] ) {
+                                                       td.attr( {
+                                                               'data-lang': 
binding['xml:lang'],
+                                                               title: 
binding.value + '@' + binding['xml:lang']
+                                                       } );
+                                               }
+                                       }
+                               } else {
+                                       // no binding
+                                       td.attr( 'class', 'unbound' );
+                               }
+                               tr.append( td );
+                       }
+                       table.append( tr );
+               }
+
+               return table;
+       };
+
+       /**
+        * Process SPARQL query result.
+        *
+        * @param {Object} data
+        * @param {Function} rowHandler
+        * @param {*} context
+        * @private
+        * @return {*} The provided context, modified by the rowHandler.
+        */
+       SELF.prototype._processData = function( data, rowHandler, context ) {
+               var results = data.results.bindings.length;
+               for ( var i = 0; i < results; i++ ) {
+                       var rowBindings = {};
+                       for ( var j = 0; j < data.head.vars.length; j++ ) {
+                               if ( data.head.vars[j] in 
data.results.bindings[i] ) {
+                                       rowBindings[data.head.vars[j]] = 
data.results.bindings[i][data.head.vars[j]];
+                               }
+                       }
+                       context = rowHandler( rowBindings, context );
+               }
+               return context;
+       };
+
+       /**
+        * Encode string as CSV.
+        *
+        * @param {string} string
+        * @return {string}
+        */
+       SELF.prototype._encodeCsv = function( string ) {
+               var result = string.replace( /"/g, '""' );
+               if ( result.search( /("|,|\n)/g ) >= 0 ) {
+                       result = '"' + result + '"';
+               }
+               return result;
+       };
+
+       /**
+        * Get the result of the submitted query as CSV
+        *
+        * @return {string} csv
+        */
+       SELF.prototype.getResultAsCsv = function() {
+               var self = this, data = self._rawData;
+               var out = data.head.vars.map( this._encodeCsv ).join( ',' ) + 
'\n';
+               out = this._processData( data, function ( row, out ) {
+                       var rowOut = '';
+                       for ( var rowVar in row ) {
+                               var rowCSV = self._encodeCsv( row[rowVar].value 
);
+                               if ( rowOut.length > 0 ) {
+                                       rowOut += ',';
+                               }
+                               rowOut += rowCSV;
+                       }
+                       if ( rowOut.length > 0 ) {
+                               rowOut += '\n';
+                       }
+                       return out + rowOut;
+               }, out );
+               return out;
+       };
+
+       /**
+        * Get the result of the submitted query as JSON
+        *
+        * @return {string}
+        */
+       SELF.prototype.getResultAsJson = function() {
+               var out = [], data = this._rawData;
+               out = this._processData( data, function ( row, out ) {
+                       var extractRow = {};
+                       for ( var rowVar in row ) {
+                               extractRow[rowVar] = row[rowVar].value;
+                       }
+                       out.push( extractRow );
+                       return out;
+               }, out );
+               return JSON.stringify( out );
+       };
+
+       /**
+        * Get the result of the submitted query as JSON
+        *
+        * @return {string}
+        */
+       SELF.prototype.getResultAsAllJson = function() {
+               return JSON.stringify( this._rawData );
+       };
+
+       /**
+        * Render value as per 
http://www.w3.org/TR/sparql11-results-csv-tsv/#tsv
+        *
+        * @param {Object} binding
+        * @return {string}
+        */
+       SELF.prototype._renderValueTSV = function( binding ) {
+               var value = binding.value.replace( /\t/g, '' );
+               switch ( binding.type ) {
+                       case 'uri':
+                               return '<' + value + '>';
+                       case 'bnode':
+                               return '_:' + value;
+                       case 'literal':
+                               var lvalue = JSON.stringify( value );
+                               if ( binding['xml:lang'] ) {
+                                       return lvalue + '@' + 
binding['xml:lang'];
+                               }
+                               if ( binding.datatype ) {
+                                       if ( binding.datatype === 
'http://www.w3.org/2001/XMLSchema#integer' ||
+                                               binding.datatype === 
'http://www.w3.org/2001/XMLSchema#decimal' ||
+                                               binding.datatype === 
'http://www.w3.org/2001/XMLSchema#double'
+                                       ) {
+                                               return value;
+                                       }
+                                       return lvalue + '^^<' + 
binding.datatype + '>';
+                               }
+                               return lvalue;
+               }
+               return value;
+       };
+
+       /**
+        * Get the result of the submitted query as TSV
+        *
+        * @return {string}
+        */
+       SELF.prototype.getSparqlTsv = function() {
+               var data = this._rawData, self = this,
+                       out = data.head.vars.map( function ( vname ) {
+                       return '?' + vname;
+               } ).join( '\t' ) + '\n';
+               out = this._processData( data, function ( row, out ) {
+                       var rowOut = '';
+                       for ( var rowVar in row ) {
+                               var rowTSV = self._renderValueTSV( row[rowVar] 
);
+                               if ( rowOut.length > 0 ) {
+                                       rowOut += '\t';
+                               }
+                               rowOut += rowTSV;
+                       }
+                       if ( rowOut.length > 0 ) {
+                               rowOut += '\n';
+                       }
+                       return out + rowOut;
+               }, out );
+               return out;
+       };
+
+       /**
+        * Get the result of the submitted query as TSV
+        *
+        * @return {string}
+        */
+       SELF.prototype.getSimpleTsv = function() {
+               var data = this._rawData,
+                       out = data.head.vars.join( '\t' ) + '\n';
+               out = this._processData( data, function ( row, out ) {
+                       var rowOut = '';
+                       for ( var rowVar in row ) {
+                               var rowTSV = row[rowVar].value.replace( /\t/g, 
'' );
+                               if ( rowOut.length > 0 ) {
+                                       rowOut += '\t';
+                               }
+                               rowOut += rowTSV;
+                       }
+                       if ( rowOut.length > 0 ) {
+                               rowOut += '\n';
+                       }
+                       return out + rowOut;
+               }, out );
+               return out;
+       };
+
+       return SELF;
+
+}(jQuery));
diff --git a/gui/wikibase/queryService/ui/App.js 
b/gui/wikibase/queryService/ui/App.js
new file mode 100644
index 0000000..1107902
--- /dev/null
+++ b/gui/wikibase/queryService/ui/App.js
@@ -0,0 +1,363 @@
+var wikibase = wikibase || {};
+wikibase.queryService = wikibase.queryService || {};
+wikibase.queryService.ui = wikibase.queryService.ui || {};
+window.mediaWiki = window.mediaWiki || {};
+
+wikibase.queryService.ui.App = ( function( $, mw ) {
+       "use strict";
+
+       var SHORTURL = 'http://tinyurl.com/create.php?url=';
+       var EXPLORE_URL = 'http://www.wikidata.org/entity/Q';
+
+       /**
+        * A ui application for the Wikibase query service
+        *
+        * @class wikibase.queryService.ui.App
+        * @licence GNU GPL v2+
+        *
+        * @author Stanislav Malyshev
+        * @author Jonas Kress
+        * @constructor
+        *
+        * @param {jQuery} $element
+        * @param {wikibase.queryService.ui.Editor}
+        * @param {wikibase.queryService.api.Sparql}
+        */
+       function SELF( $element, editor, sparqlApi, querySamplesApi ) {
+
+               this._$element = $element;
+               this._editor = editor;
+               this._sparqlApi = sparqlApi;
+               this._querySamplesApi = querySamplesApi;
+
+               this._init();
+       }
+
+       /**
+        * @property {string}
+        * @private
+        **/
+       SELF.prototype._$element = null;
+
+       /**
+        * @property {wikibase.queryService.api.Sparql}
+        * @private
+        **/
+       SELF.prototype._sparqlApi = null;
+
+       /**
+        * @property {wikibase.queryService.api.QuerySamplesApi}
+        * @private
+        **/
+       SELF.prototype._querySamplesApi = null;
+
+       /**
+        * @property {wikibase.queryService.ui.Editor}
+        * @type wikibase.queryService.ui.Editor
+        * @private
+        **/
+       SELF.prototype._editor = null;
+
+       /**
+        * Initialize private members and call delegate to specific init methods
+        * @private
+        **/
+       SELF.prototype._init = function() {
+
+               if( !this._sparqlApi ){
+                       this._sparqlApi = new 
wikibase.queryService.api.Sparql();
+               }
+
+               if( !this._querySamplesApi ){
+                       this._querySamplesApi = new 
wikibase.queryService.api.QuerySamples();
+               }
+
+               if( !this._editor ){
+                       this._editor = new wikibase.queryService.ui.Editor();
+               }
+
+               this._initEditor();
+               this._initExamples();
+               this._initDataUpdated();
+               this._initQuery();
+               this._initRdfNamespaces();
+               this._initHandlers();
+
+       };
+
+       /**
+        * @private
+        **/
+       SELF.prototype._initEditor = function() {
+
+               this._editor.fromTextArea( this._$element.find( '.queryEditor' 
)[0] );
+               this._editor.addKeyMap( 'Ctrl-Enter',  $.proxy( 
this._handleQuerySubmit, this ) );
+       };
+
+       /**
+        * @private
+        **/
+       SELF.prototype._initExamples = function() {
+
+               var self = this;
+               this._querySamplesApi.getExamples().done( function( examples ){
+                       $.each( examples, function( title, query ) {
+                               self._$element.find( '.exampleQueries' 
).append( $(new Option( title, query ) )  );
+                       });
+               } );
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._initRdfNamespaces = function() {
+               var category, select, ns, container = $( '.namespace-shortcuts' 
),
+                       namespaces = 
wikibase.queryService.RdfNamespaces.NAMESPACE_SHORTCUTS;
+
+               container.click( function( e ) {
+                       e.stopPropagation();
+               } );
+
+               // add namespaces to dropdowns
+               for ( category in namespaces ) {
+                       select = $( '<select>' )
+                               .attr( 'class', 'form-control' )
+                               .append( $( '<option>' ).text( category ) )
+                               .appendTo( container );
+                       for ( ns in namespaces[category] ) {
+                               select.append( $( '<option>' ).text( ns ).attr( 
{
+                                       value: namespaces[category][ns]
+                               } ) );
+                       }
+               }
+       };
+
+       /**
+        * @private
+        **/
+       SELF.prototype._initQuery = function() {
+               if ( window.location.hash !== '' ) {
+                       this._editor.setValue( decodeURIComponent( 
window.location.hash.substr( 1 ) ) );
+                       this._editor.refresh();
+               }
+       };
+
+
+
+       /**
+        * @private
+        **/
+       SELF.prototype._initDataUpdated = function() {
+               this._sparqlApi.queryDataUpdatedTime().done( function( time ){
+                       $( '#dbUpdated' ).text( time );
+               } ).fail(function(){
+                       $( '#dbUpdated' ).text( '[unable to connect]' );
+               });
+       };
+
+
+       /**
+        * @private
+        **/
+       SELF.prototype._initHandlers = function() {
+               var self = this;
+
+               $( '#query-form' ).submit(  $.proxy( this._handleQuerySubmit, 
this ) );
+               $( '.namespace-shortcuts' ).on( 'change', 'select', $.proxy( 
this._handleNamespaceSelected, this ) );
+               $( '.exampleQueries' ).on( 'change',  $.proxy( 
this._handleExampleSelected, this )  );
+
+               $( '.addPrefixes' ).click( function() {
+                       var prefixes = 
wikibase.queryService.RdfNamespaces.STANDARD_PREFIXES.join( '\n' );
+                       self._editor.prepandValue( prefixes + '\n\n' );
+               } );
+
+               $( '#clear-button' ).click( function () {
+                       self._editor.setValue( '' );
+               } );
+
+               $( '#hide-explorer' ).click( function( e ){
+                       e.preventDefault();
+                       $( '#explore' ).empty( '' );
+                       $( '#hide-explorer' ).hide();
+                       $( '#show-explorer' ).show();
+               } );
+
+               $( window ).on( 'popstate', $.proxy( this._initQuery(), this ) 
);
+
+               this._initHandlersDownloads();
+       };
+
+       /**
+        * @private
+        **/
+       SELF.prototype._initHandlersDownloads = function() {
+
+               var api = this._sparqlApi;
+               var DOWNLOAD_FORMATS = {
+                               'CSV': {
+                                       handler: $.proxy( api.getResultAsCsv, 
api ),
+                                       mimetype: 'text/csv;charset=utf-8'
+                               },
+                               'JSON': {
+                                       handler: $.proxy( api.getResultAsJson, 
api ),
+                                       mimetype: 
'application/json;charset=utf-8'
+                               },
+                               'TSV': {
+                                       handler: $.proxy( api.getSparqlTsv, api 
),
+                                       mimetype: 
'text/tab-separated-values;charset=utf-8'
+                               },
+                               'Simple TSV': {
+                                       handler: $.proxy( api.getSimpleTsv, api 
),
+                                       mimetype: 
'text/tab-separated-values;charset=utf-8',
+                                       ext: 'tsv'
+                               },
+                               'Full JSON': {
+                                       handler: $.proxy( 
api.getResultAsAllJson, api ),
+                                       mimetype: 
'application/json;charset=utf-8',
+                                       ext: 'json'
+                               }
+                       };
+
+               var download = function( filename, handler, mimetype ) {
+
+                       return function ( e ) {
+                               e.preventDefault();
+
+                               if ( api.getResultLength() === null ) {
+                                       return '';
+                               }
+
+                               var link = document.createElement("a");
+                           link.download = filename;
+                           link.href = 'data:' + mimetype + ',' + 
encodeURIComponent( handler() );
+                           link.click();
+                       };
+               };
+
+               for ( var format in DOWNLOAD_FORMATS ) {
+                       var extension = DOWNLOAD_FORMATS[format].ext || 
format.toLowerCase();
+                       var formatName = format.replace( /\s/g, '-' );
+                       $( '#download' + formatName ).click(
+                                       download( 'query.' + extension, 
DOWNLOAD_FORMATS[format].handler,
+                                                       
DOWNLOAD_FORMATS[format].mimetype )
+                        );
+               }
+       };
+
+       /**
+        * @private
+        **/
+       SELF.prototype._handleQuerySubmit = function( e ) {
+               var self = this;
+
+               e.preventDefault();
+               this._editor.save();
+
+               var hash = encodeURIComponent( this._editor.getValue() );
+               if ( window.location.hash !== hash ) {
+                       window.location.hash = hash;
+               }
+               $( '#shorturl' ).attr( 'href', SHORTURL + encodeURIComponent( 
window.location ) );
+
+               $( '#query-result' ).empty( '' );
+               $( '#query-result' ).hide();
+               $( '#total' ).hide();
+               $( '#query-error' ).show();
+               $( '#query-error' ).text( 'Running query...' );
+
+               var query = $( '#query-form' ).serialize();
+               this._sparqlApi.query(query)
+               .done( $.proxy( this._handleQueryResult, this ) )
+               .fail(function(){
+                       $( '#query-error' ).html( $( '<pre>' ).text( 
self._sparqlApi.getErrorMessage() ) ).show();
+                       self._editor.highlightError( 
self._sparqlApi.getErrorMessage() );
+               } );
+
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._handleQueryResult = function() {
+
+               var api = this._sparqlApi;
+               $( '#total-results' ).text( api.getResultLength() );
+               $( '#query-time' ).text( api.getExecutionTime() );
+               $( '#total' ).show();
+               $( '#query-result' ).append( api.getResultAsTable() ).show();
+               $( '#query-error' ).hide();
+
+               var $linkableItems = $( '#query-result' ).find('a').filter( 
function() {
+                       return this.href.match( EXPLORE_URL + '(.+)' );
+               } );
+
+               var $exploreLink = $( '<a href="#" title="Explore item" 
class="glyphicon glyphicon-search" aria-hidden="true"></a>' );
+               $exploreLink.click( $.proxy( this._handleExploreItem, this ) );
+               $linkableItems.after( $exploreLink );
+
+               //$linkableItems.attr( 'href', '#');
+       };
+
+
+       /**
+        * @private
+        */
+       SELF.prototype._handleNamespaceSelected = function( e ) {
+               var ns, uri = e.target.value, current = this._editor.getValue();
+
+               if ( current.indexOf( '<' + uri + '>' ) === -1 ) {
+                       ns = $( e.target ).find(':selected').text();
+                       this._editor.setValue('PREFIX ' + ns + ': <' + uri + 
'>\n' + current);
+               }
+
+               // reselect group label
+               e.target.selectedIndex = 0;
+       };
+
+
+       /**
+        * @private
+        */
+       SELF.prototype._handleExampleSelected = function( e ) {
+               var text = e.target.value;
+               e.target.selectedIndex = 0;
+               if ( !text || !text.trim() ) {
+                       return;
+               }
+               this._editor.setValue( text );
+
+               var prefixes = 
wikibase.queryService.RdfNamespaces.STANDARD_PREFIXES.join( '\n' );
+
+               this._editor.prepandValue( prefixes + '\n\n' );
+
+       };
+
+       /**
+        * @private
+        */
+       SELF.prototype._handleExploreItem = function( e ) {
+               var id, url = $(e.target).prev().attr( 'href' ) || '', match;
+
+               match = url.match( EXPLORE_URL + '(.+)' );
+               if ( !match ) {
+                       return;
+               }
+
+               if ( $( '#hide-explorer' ).is( ':visible' ) ) {
+                       $( '#explore' ).empty( '' );
+               } else {
+                       $( '#hide-explorer' ).show();
+                       $( '#show-explorer' ).hide();
+               }
+               id = match[1];
+               mw.config = { get: function () {
+                       return 'Q'+id;
+               } };
+               $( 'html, body' ).animate( { scrollTop: $( '#explore' 
).offset().top }, 500 );
+               EXPLORER( $, mw, $( '#explore' ) );
+
+               return false;
+       };
+
+       return SELF;
+}( jQuery, mediaWiki ) );
diff --git a/gui/wikibase/queryService/ui/Editor.js 
b/gui/wikibase/queryService/ui/Editor.js
new file mode 100644
index 0000000..84e195e
--- /dev/null
+++ b/gui/wikibase/queryService/ui/Editor.js
@@ -0,0 +1,123 @@
+var wikibase = wikibase || {};
+wikibase.queryService = wikibase.queryService || {};
+wikibase.queryService.ui = wikibase.queryService.ui || {};
+
+wikibase.queryService.ui.Editor = ( function( $ ) {
+       "use strict";
+
+       var CODEMIRROR_DEFAULTS = {
+                       "lineNumbers": true,
+                       "matchBrackets": true,
+                       "mode": 'sparql',
+                       "extraKeys": { 'Ctrl-Space': 'autocomplete' },
+                       "viewportMargin": Infinity
+               },
+               ERROR_LINE_MARKER = null,
+               ERROR_CHARACTER_MARKER = null;
+
+       /**
+        * An ui this._editor for the Wikibase query service
+        *
+        * @class wikibase.queryService.ui.this._editor
+        * @licence GNU GPL v2+
+        *
+        * @author Stanislav Malyshev
+        * @author Jonas Kress
+        * @constructor
+        */
+       function SELF() {
+       }
+
+       /**
+        * @property {CodeMirror}
+        * @type CodeMirror
+        * @private
+        **/
+       SELF.prototype._editor = null;
+
+       /**
+        * Construct an this._editor on the given textarea DOM element
+        *
+        * @param {Element} element
+        **/
+       SELF.prototype.fromTextArea = function( element ) {
+
+               this._editor = CodeMirror.fromTextArea( element, 
CODEMIRROR_DEFAULTS );
+               this._editor.on( 'change', function () {
+                       if ( ERROR_LINE_MARKER ) {
+                               ERROR_LINE_MARKER.clear();
+                               ERROR_CHARACTER_MARKER.clear();
+                       }
+               } );
+               this._editor.focus();
+
+               new WikibaseRDFTooltip(this._editor);
+       };
+
+       /**
+        * Construct an this._editor on the given textarea DOM element
+        *
+        * @param {string} keyMap
+        * @throws {function} callback
+        **/
+       SELF.prototype.addKeyMap = function( keyMap, callback ) {
+               this._editor.addKeyMap( { keyMap : callback } );
+       };
+
+       /**
+        * @param {string} value
+        **/
+       SELF.prototype.setValue = function( value ) {
+               this._editor.setValue( value );
+       };
+
+       /**
+        * @return {string}
+        **/
+       SELF.prototype.getValue = function() {
+               return this._editor.getValue();
+       };
+
+       SELF.prototype.save = function() {
+               this._editor.save();
+       };
+
+       /**
+        * @param {string} value
+        **/
+       SELF.prototype.prepandValue = function( value ) {
+               this._editor.setValue( value + this._editor.getValue() );
+       };
+
+       SELF.prototype.refresh = function() {
+               this._editor.refresh();
+       };
+
+       /**
+        * Highlight SPARQL error in editor window.
+        *
+        * @param {string} description
+        */
+       SELF.prototype.highlightError = function( description ) {
+               var line, character,
+                       match = description.match( /line (\d+), column (\d+)/ );
+               if ( match ) {
+                       // highlight character at error position
+                       line = match[1] - 1;
+                       character = match[2] - 1;
+                       ERROR_LINE_MARKER = this._editor.doc.markText(
+                               { 'line': line, 'ch': 0 },
+                               { 'line': line },
+                               { 'className': 'error-line' }
+                       );
+                       ERROR_CHARACTER_MARKER = this._editor.doc.markText(
+                               { 'line': line, 'ch': character },
+                               { 'line': line, 'ch': character + 1 },
+                               { 'className': 'error-character' }
+                       );
+               }
+       };
+
+       return SELF;
+
+}( jQuery ) );

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I771c0113503c97c14a4fc6b210ad7e2226570261
Gerrit-PatchSet: 6
Gerrit-Project: wikidata/query/rdf
Gerrit-Branch: master
Gerrit-Owner: Jonas Kress (WMDE) <jonas.kr...@wikimedia.de>
Gerrit-Reviewer: JanZerebecki <jan.wikime...@zerebecki.de>
Gerrit-Reviewer: Jonas Kress (WMDE) <jonas.kr...@wikimedia.de>
Gerrit-Reviewer: Lydia Pintscher <lydia.pintsc...@wikimedia.de>
Gerrit-Reviewer: Smalyshev <smalys...@wikimedia.org>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <thiemo.maet...@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