1
0
mirror of https://github.com/gnosygnu/xowa.git synced 2026-03-02 03:49:30 +00:00

Refactor: Pull more classes into baselib

This commit is contained in:
gnosygnu
2021-12-19 16:19:19 -05:00
parent 48559edffe
commit 0e80d7ef6d
7999 changed files with 1375876 additions and 1365947 deletions

View File

@@ -1,55 +1,55 @@
( function ( $, mw ) {
mw.hook( 'wikipage.content' ).add( function ( $content ) {
/**
* Replace a graph image by the vega graph.
*
* If dependencies aren't loaded yet, they are loaded first
* before rendering the graph.
*
* @param {jQuery} $el Graph container.
*/
function loadAndReplaceWithGraph( $el ) {
// TODO, Performance BUG: loading vega and calling api should happen in parallel
// Lazy loading dependencies
mw.loader.using( 'ext.graph.vega2', function () {
new mw.Api().get( {
formatversion: 2,
action: 'graph',
title: mw.config.get( 'wgPageName' ),
hash: $el.data( 'graphId' )
} ).done( function ( data ) {
mw.drawVegaGraph( $el[ 0 ], data.graph, function ( error ) {
var $layover = $el.find( '.mw-graph-layover' );
if ( !error ) {
$el.find( 'img' ).remove();
$layover.remove();
} else {
mw.log.warn( error );
}
$el.removeClass( 'mw-graph-interactable' );
// TODO: handle error by showing some message
} );
} );
} );
}
// Make graph containers clickable
$content.find( '.mw-graph.mw-graph-interactable' ).on( 'click', function () {
var $this = $( this ),
$button = $this.find( '.mw-graph-switch' );
// Prevent multiple clicks
$this.off( 'click' );
// Add a class to decorate loading
$button.addClass( 'mw-graph-loading' );
// Replace the image with the graph
loadAndReplaceWithGraph( $this );
} );
} );
}( jQuery, mediaWiki ) );
( function ( $, mw ) {
mw.hook( 'wikipage.content' ).add( function ( $content ) {
/**
* Replace a graph image by the vega graph.
*
* If dependencies aren't loaded yet, they are loaded first
* before rendering the graph.
*
* @param {jQuery} $el Graph container.
*/
function loadAndReplaceWithGraph( $el ) {
// TODO, Performance BUG: loading vega and calling api should happen in parallel
// Lazy loading dependencies
mw.loader.using( 'ext.graph.vega2', function () {
new mw.Api().get( {
formatversion: 2,
action: 'graph',
title: mw.config.get( 'wgPageName' ),
hash: $el.data( 'graphId' )
} ).done( function ( data ) {
mw.drawVegaGraph( $el[ 0 ], data.graph, function ( error ) {
var $layover = $el.find( '.mw-graph-layover' );
if ( !error ) {
$el.find( 'img' ).remove();
$layover.remove();
} else {
mw.log.warn( error );
}
$el.removeClass( 'mw-graph-interactable' );
// TODO: handle error by showing some message
} );
} );
} );
}
// Make graph containers clickable
$content.find( '.mw-graph.mw-graph-interactable' ).on( 'click', function () {
var $this = $( this ),
$button = $this.find( '.mw-graph-switch' );
// Prevent multiple clicks
$this.off( 'click' );
// Add a class to decorate loading
$button.addClass( 'mw-graph-loading' );
// Replace the image with the graph
loadAndReplaceWithGraph( $this );
} );
} );
}( jQuery, mediaWiki ) );

View File

@@ -1,76 +1,76 @@
( function ( $, mw ) {
var oldContent, ccw,
resizeCodeEditor = $.noop;
$( function () {
var viewportHeight = $( window ).height(),
sandboxHeight = viewportHeight - 150,
initialPosition = sandboxHeight - 100;
$( '#mw-graph-sandbox' ).width( '100%' ).height( sandboxHeight ).split( {
orientation: 'vertical',
limit: 100,
position: '40%'
} );
$( '#mw-graph-left' ).split( {
orientation: 'horizontal',
limit: 100,
position: initialPosition
} );
} );
mw.hook( 'codeEditor.configure' ).add( function ( session ) {
var $json = $( '#mw-graph-json' )[ 0 ],
$graph = $( '.mw-graph' ),
$graphEl = $graph[ 0 ],
$rightPanel = $( '#mw-graph-right' ),
$editor = $( '.editor' );
if ( ccw ) {
ccw.release();
}
ccw = mw.confirmCloseWindow( {
test: function () {
return session.getValue().length > 0;
},
message: mw.msg( 'editwarning-warning' )
} );
resizeCodeEditor = function () {
$editor.parent().height( $rightPanel.height() - 57 );
$.wikiEditor.instances[ 0 ].data( 'wikiEditor-context' ).codeEditor.resize();
};
// I tried to resize on $( window ).resize(), but that didn't work right
resizeCodeEditor();
session.on( 'change', $.debounce( 300, function () {
var content = session.getValue();
if ( oldContent === content ) {
return;
}
oldContent = content;
$graph.empty();
new mw.Api().post( {
formatversion: 2,
action: 'graph',
text: content
} ).done( function ( data ) {
if ( session.getValue() !== content ) {
// Just in case the content has changed since we made the api call
return;
}
$json.textContent = JSON.stringify( data.graph, null, 2 );
mw.drawVegaGraph( $graphEl, data.graph, function ( error ) {
if ( error ) {
$graphEl.textContent = ( error.exception || error ).toString();
}
} );
} ).fail( function ( errCode, error ) {
$graphEl.textContent = errCode.toString() + ':' + ( error.exception || error ).toString();
} );
} ) );
} );
}( jQuery, mediaWiki ) );
( function ( $, mw ) {
var oldContent, ccw,
resizeCodeEditor = $.noop;
$( function () {
var viewportHeight = $( window ).height(),
sandboxHeight = viewportHeight - 150,
initialPosition = sandboxHeight - 100;
$( '#mw-graph-sandbox' ).width( '100%' ).height( sandboxHeight ).split( {
orientation: 'vertical',
limit: 100,
position: '40%'
} );
$( '#mw-graph-left' ).split( {
orientation: 'horizontal',
limit: 100,
position: initialPosition
} );
} );
mw.hook( 'codeEditor.configure' ).add( function ( session ) {
var $json = $( '#mw-graph-json' )[ 0 ],
$graph = $( '.mw-graph' ),
$graphEl = $graph[ 0 ],
$rightPanel = $( '#mw-graph-right' ),
$editor = $( '.editor' );
if ( ccw ) {
ccw.release();
}
ccw = mw.confirmCloseWindow( {
test: function () {
return session.getValue().length > 0;
},
message: mw.msg( 'editwarning-warning' )
} );
resizeCodeEditor = function () {
$editor.parent().height( $rightPanel.height() - 57 );
$.wikiEditor.instances[ 0 ].data( 'wikiEditor-context' ).codeEditor.resize();
};
// I tried to resize on $( window ).resize(), but that didn't work right
resizeCodeEditor();
session.on( 'change', $.debounce( 300, function () {
var content = session.getValue();
if ( oldContent === content ) {
return;
}
oldContent = content;
$graph.empty();
new mw.Api().post( {
formatversion: 2,
action: 'graph',
text: content
} ).done( function ( data ) {
if ( session.getValue() !== content ) {
// Just in case the content has changed since we made the api call
return;
}
$json.textContent = JSON.stringify( data.graph, null, 2 );
mw.drawVegaGraph( $graphEl, data.graph, function ( error ) {
if ( error ) {
$graphEl.textContent = ( error.exception || error ).toString();
}
} );
} ).fail( function ( errCode, error ) {
$graphEl.textContent = errCode.toString() + ':' + ( error.exception || error ).toString();
} );
} ) );
} );
}( jQuery, mediaWiki ) );

View File

@@ -1,27 +1,27 @@
( function ( $, mw ) {
// mw.hook( 'wikipage.content' ).add( function ( $content ) {
var specs = {} // mw.config.get( 'wgGraphSpecs' );
vg.data.load.sanitizeUrl = function () {
// mw.log.warn( 'Vega 1.x does not allow external URLs. Switch to Vega 2.' );
return false;
};
if ( specs ) {
var $content = $('#content'); // XOWA
$content.find( '.mw-graph.mw-graph-always' ).each( function () {
var graphId = $( this ).data( 'graph-id' ),
el = this;
if ( !specs[ graphId ] ) {
// mw.log.warn( graphId );
} else {
vg.parse.spec( specs[ graphId ], function ( chart ) {
if ( chart ) {
chart( { el: el } ).update();
}
} );
}
} );
}
// } );
}( jQuery, null ) );
( function ( $, mw ) {
// mw.hook( 'wikipage.content' ).add( function ( $content ) {
var specs = {} // mw.config.get( 'wgGraphSpecs' );
vg.data.load.sanitizeUrl = function () {
// mw.log.warn( 'Vega 1.x does not allow external URLs. Switch to Vega 2.' );
return false;
};
if ( specs ) {
var $content = $('#content'); // XOWA
$content.find( '.mw-graph.mw-graph-always' ).each( function () {
var graphId = $( this ).data( 'graph-id' ),
el = this;
if ( !specs[ graphId ] ) {
// mw.log.warn( graphId );
} else {
vg.parse.spec( specs[ graphId ], function ( chart ) {
if ( chart ) {
chart( { el: el } ).update();
}
} );
}
} );
}
// } );
}( jQuery, null ) );

View File

@@ -1,107 +1,107 @@
( function ( $, mw, vg ) {
'use strict';
/* global require */
/*
var VegaWrapper = require( 'mw-graph-shared' );
// eslint-disable-next-line no-new
new VegaWrapper( {
datalib: vg.util,
useXhr: true,
isTrusted: true, //mw.config.get( 'wgGraphIsTrusted' ),
domains: '', // mw.config.get( 'wgGraphAllowedDomains' ),
domainMap: false,
logger: function ( warning ) {
// mw.log.warn( warning );
},
parseUrl: function ( opt ) {
// Parse URL
var uri = new mw.Uri( opt.url );
// reduce confusion, only keep expected values
if ( uri.port ) {
uri.host += ':' + uri.port;
delete uri.port;
}
// If url begins with protocol:///... mark it as having relative host
if ( /^[a-z]+:\/\/\//.test( opt.url ) ) {
uri.isRelativeHost = true;
}
if ( uri.protocol ) {
// All other libs use trailing colon in the protocol field
uri.protocol += ':';
}
// Node's path includes the query, whereas pathname is without the query
// Standardizing on pathname
uri.pathname = uri.path;
delete uri.path;
return uri;
},
formatUrl: function ( uri, opt ) {
// Format URL back into a string
// Revert path into pathname
uri.path = uri.pathname;
delete uri.pathname;
if ( location.host.toLowerCase() === uri.host.toLowerCase() ) {
// if ( !mw.config.get( 'wgGraphIsTrusted' ) ) {
// Only send this header when hostname is the same.
// This is broader than the same-origin policy,
// but playing on the safer side.
// opt.headers = { 'Treat-as-Untrusted': 1 };
// }
} else if ( opt.addCorsOrigin ) {
// All CORS api calls require origin parameter.
// It would be better to use location.origin,
// but apparently it's not universal yet.
uri.query.origin = location.protocol + '//' + location.host;
}
uri.protocol = VegaWrapper.removeColon( uri.protocol );
return uri.toString();
},
// languageCode: mw.config.get( 'wgUserLanguage' )
} );
*/
/**
* Set up drawing canvas inside the given element and draw graph data
*
* @param {HTMLElement} element
* @param {Object|string} data graph spec
* @param {Function} [callback] function(error) called when drawing is done
*/
window.drawVegaGraph = function ( element, data, callback ) {
vg.parse.spec( data, function ( error, chart ) {
if ( !error ) {
chart( { el: element } ).update();
}
if ( callback ) {
callback( error );
}
} );
};
// mw.hook( 'wikipage.content' ).add( function ( $content ) {
var $content = $('#content');
var specs = {} // mw.config.get( 'wgGraphSpecs' );
if ( !specs ) {
return;
}
$content.find( '.mw-graph.mw-graph-always' ).each( function () {
var graphId = $( this ).data( 'graph-id' );
if ( !specs.hasOwnProperty( graphId ) ) {
// mw.log.warn( graphId );
} else {
window.drawVegaGraph( this, specs[ graphId ], function ( error ) {
if ( error ) {
// mw.log.warn( error );
}
} );
}
} );
// } );
}( jQuery, null, vg ) );
( function ( $, mw, vg ) {
'use strict';
/* global require */
/*
var VegaWrapper = require( 'mw-graph-shared' );
// eslint-disable-next-line no-new
new VegaWrapper( {
datalib: vg.util,
useXhr: true,
isTrusted: true, //mw.config.get( 'wgGraphIsTrusted' ),
domains: '', // mw.config.get( 'wgGraphAllowedDomains' ),
domainMap: false,
logger: function ( warning ) {
// mw.log.warn( warning );
},
parseUrl: function ( opt ) {
// Parse URL
var uri = new mw.Uri( opt.url );
// reduce confusion, only keep expected values
if ( uri.port ) {
uri.host += ':' + uri.port;
delete uri.port;
}
// If url begins with protocol:///... mark it as having relative host
if ( /^[a-z]+:\/\/\//.test( opt.url ) ) {
uri.isRelativeHost = true;
}
if ( uri.protocol ) {
// All other libs use trailing colon in the protocol field
uri.protocol += ':';
}
// Node's path includes the query, whereas pathname is without the query
// Standardizing on pathname
uri.pathname = uri.path;
delete uri.path;
return uri;
},
formatUrl: function ( uri, opt ) {
// Format URL back into a string
// Revert path into pathname
uri.path = uri.pathname;
delete uri.pathname;
if ( location.host.toLowerCase() === uri.host.toLowerCase() ) {
// if ( !mw.config.get( 'wgGraphIsTrusted' ) ) {
// Only send this header when hostname is the same.
// This is broader than the same-origin policy,
// but playing on the safer side.
// opt.headers = { 'Treat-as-Untrusted': 1 };
// }
} else if ( opt.addCorsOrigin ) {
// All CORS api calls require origin parameter.
// It would be better to use location.origin,
// but apparently it's not universal yet.
uri.query.origin = location.protocol + '//' + location.host;
}
uri.protocol = VegaWrapper.removeColon( uri.protocol );
return uri.toString();
},
// languageCode: mw.config.get( 'wgUserLanguage' )
} );
*/
/**
* Set up drawing canvas inside the given element and draw graph data
*
* @param {HTMLElement} element
* @param {Object|string} data graph spec
* @param {Function} [callback] function(error) called when drawing is done
*/
window.drawVegaGraph = function ( element, data, callback ) {
vg.parse.spec( data, function ( error, chart ) {
if ( !error ) {
chart( { el: element } ).update();
}
if ( callback ) {
callback( error );
}
} );
};
// mw.hook( 'wikipage.content' ).add( function ( $content ) {
var $content = $('#content');
var specs = {} // mw.config.get( 'wgGraphSpecs' );
if ( !specs ) {
return;
}
$content.find( '.mw-graph.mw-graph-always' ).each( function () {
var graphId = $( this ).data( 'graph-id' );
if ( !specs.hasOwnProperty( graphId ) ) {
// mw.log.warn( graphId );
} else {
window.drawVegaGraph( this, specs[ graphId ], function ( error ) {
if ( error ) {
// mw.log.warn( error );
}
} );
}
} );
// } );
}( jQuery, null, vg ) );

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g id="graph">
<path id="axes" d="M5 6v12h14v-1H6V6H5z"/>
<path id="data" d="M7 16v-3l4-3 3 2 4-4v8H7"/>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g id="graph">
<path id="axes" d="M5 6v12h14v-1H6V6H5z"/>
<path id="data" d="M7 16v-3l4-3 3 2 4-4v8H7"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 271 B

View File

@@ -1,9 +1,9 @@
.mw-graph {
display: inline-block;
border: 1px solid transparent;
position: relative;
}
.ve-ce-mwGraphNode-plot {
position: absolute;
}
.mw-graph {
display: inline-block;
border: 1px solid transparent;
position: relative;
}
.ve-ce-mwGraphNode-plot {
position: absolute;
}

View File

@@ -1,169 +1,169 @@
/*!
* VisualEditor ContentEditable MWGraphNode class.
*
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable MediaWiki graph node.
*
* @class
* @extends ve.ce.MWBlockExtensionNode
* @mixins ve.ce.MWResizableNode
*
* @constructor
* @param {ve.dm.MWGraphNode} model Model to observe
* @param {Object} [config] Configuration options
*/
ve.ce.MWGraphNode = function VeCeMWGraphNode( model, config ) {
this.$graph = $( '<div>' ).addClass( 'mw-graph' );
this.$plot = $( '<div>' ).addClass( 've-ce-mwGraphNode-plot' );
// Parent constructor
ve.ce.MWGraphNode.super.apply( this, arguments );
// Mixin constructors
ve.ce.MWResizableNode.call( this, this.$plot, config );
this.$element
.addClass( 'mw-graph-container' )
.append( this.$graph );
this.showHandles( [ 'se' ] );
};
/* Inheritance */
OO.inheritClass( ve.ce.MWGraphNode, ve.ce.MWBlockExtensionNode );
// Need to mix in the base class as well
OO.mixinClass( ve.ce.MWGraphNode, ve.ce.ResizableNode );
OO.mixinClass( ve.ce.MWGraphNode, ve.ce.MWResizableNode );
/* Static Properties */
ve.ce.MWGraphNode.static.name = 'mwGraph';
ve.ce.MWGraphNode.static.primaryCommandName = 'graph';
ve.ce.MWGraphNode.static.tagName = 'div';
/* Static Methods */
/**
* Attempt to render the graph through Vega.
*
* @param {Object} spec The graph spec
* @param {HTMLElement} element Element to render the graph in
* @return {jQuery.Promise} Promise that resolves when the graph is rendered.
* Promise is rejected with an error message key if there was a problem rendering the graph.
*/
ve.ce.MWGraphNode.static.vegaParseSpec = function ( spec, element ) {
var deferred = $.Deferred(),
node = this,
canvasNode, view;
// Check if the spec is currently valid
if ( ve.isEmptyObject( spec ) ) {
deferred.reject( 'graph-ve-no-spec' );
} else if ( !ve.dm.MWGraphModel.static.specHasData( spec ) ) {
deferred.reject( 'graph-ve-empty-graph' );
} else {
vg.parse.spec( spec, function ( chart ) {
try {
view = chart( { el: element } ).update();
// HACK: If canvas is blank, this means Vega didn't render properly.
// Once Vega allows for proper rendering validation, this should be
// swapped for a validation check.
canvasNode = element.children[ 0 ].children[ 0 ];
if ( node.isCanvasBlank( canvasNode ) ) {
deferred.reject( 'graph-ve-vega-error-no-render' );
} else {
deferred.resolve( view );
}
} catch ( err ) {
deferred.reject( 'graph-ve-vega-error' );
}
} );
}
return deferred.promise();
};
/**
* Check if a canvas is blank
*
* @author Austin Brunkhorst http://stackoverflow.com/a/17386803/2055594
* @param {HTMLElement} canvas The canvas to Check
* @return {boolean} The canvas is blank
*/
ve.ce.MWGraphNode.static.isCanvasBlank = function ( canvas ) {
var blank = document.createElement( 'canvas' );
blank.width = canvas.width;
blank.height = canvas.height;
return canvas.toDataURL() === blank.toDataURL();
};
/* Methods */
/**
* Render a Vega graph inside the node
*/
ve.ce.MWGraphNode.prototype.update = function () {
var node = this;
// Clear element
this.$graph.empty();
this.$element.toggleClass( 'mw-graph-vega1', this.getModel().isGraphLegacy() );
mw.loader.using( 'ext.graph.vega2' ).done( function () {
node.$plot.detach();
node.constructor.static.vegaParseSpec( node.getModel().getSpec(), node.$graph[ 0 ] ).then(
function ( view ) {
// HACK: We need to know which padding values Vega computes in case
// of automatic padding, but it isn't properly exposed in the view
node.$graph.append( node.$plot );
// eslint-disable-next-line no-underscore-dangle
node.$plot.css( view._padding );
node.calculateHighlights();
},
function ( failMessageKey ) {
node.$graph.text( ve.msg( failMessageKey ) );
}
);
} );
};
/**
* @inheritdoc
*/
ve.ce.MWGraphNode.prototype.getAttributeChanges = function ( width, height ) {
var attrChanges = {},
newSpec = ve.dm.MWGraphModel.static.updateSpec( this.getModel().getSpec(), {
width: width,
height: height
} );
ve.setProp( attrChanges, 'mw', 'body', 'extsrc', JSON.stringify( newSpec ) );
return attrChanges;
};
/**
* @inheritdoc
*/
ve.ce.MWGraphNode.prototype.getFocusableElement = function () {
return this.$graph;
};
/* Registration */
ve.ce.nodeFactory.register( ve.ce.MWGraphNode );
/*!
* VisualEditor ContentEditable MWGraphNode class.
*
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable MediaWiki graph node.
*
* @class
* @extends ve.ce.MWBlockExtensionNode
* @mixins ve.ce.MWResizableNode
*
* @constructor
* @param {ve.dm.MWGraphNode} model Model to observe
* @param {Object} [config] Configuration options
*/
ve.ce.MWGraphNode = function VeCeMWGraphNode( model, config ) {
this.$graph = $( '<div>' ).addClass( 'mw-graph' );
this.$plot = $( '<div>' ).addClass( 've-ce-mwGraphNode-plot' );
// Parent constructor
ve.ce.MWGraphNode.super.apply( this, arguments );
// Mixin constructors
ve.ce.MWResizableNode.call( this, this.$plot, config );
this.$element
.addClass( 'mw-graph-container' )
.append( this.$graph );
this.showHandles( [ 'se' ] );
};
/* Inheritance */
OO.inheritClass( ve.ce.MWGraphNode, ve.ce.MWBlockExtensionNode );
// Need to mix in the base class as well
OO.mixinClass( ve.ce.MWGraphNode, ve.ce.ResizableNode );
OO.mixinClass( ve.ce.MWGraphNode, ve.ce.MWResizableNode );
/* Static Properties */
ve.ce.MWGraphNode.static.name = 'mwGraph';
ve.ce.MWGraphNode.static.primaryCommandName = 'graph';
ve.ce.MWGraphNode.static.tagName = 'div';
/* Static Methods */
/**
* Attempt to render the graph through Vega.
*
* @param {Object} spec The graph spec
* @param {HTMLElement} element Element to render the graph in
* @return {jQuery.Promise} Promise that resolves when the graph is rendered.
* Promise is rejected with an error message key if there was a problem rendering the graph.
*/
ve.ce.MWGraphNode.static.vegaParseSpec = function ( spec, element ) {
var deferred = $.Deferred(),
node = this,
canvasNode, view;
// Check if the spec is currently valid
if ( ve.isEmptyObject( spec ) ) {
deferred.reject( 'graph-ve-no-spec' );
} else if ( !ve.dm.MWGraphModel.static.specHasData( spec ) ) {
deferred.reject( 'graph-ve-empty-graph' );
} else {
vg.parse.spec( spec, function ( chart ) {
try {
view = chart( { el: element } ).update();
// HACK: If canvas is blank, this means Vega didn't render properly.
// Once Vega allows for proper rendering validation, this should be
// swapped for a validation check.
canvasNode = element.children[ 0 ].children[ 0 ];
if ( node.isCanvasBlank( canvasNode ) ) {
deferred.reject( 'graph-ve-vega-error-no-render' );
} else {
deferred.resolve( view );
}
} catch ( err ) {
deferred.reject( 'graph-ve-vega-error' );
}
} );
}
return deferred.promise();
};
/**
* Check if a canvas is blank
*
* @author Austin Brunkhorst http://stackoverflow.com/a/17386803/2055594
* @param {HTMLElement} canvas The canvas to Check
* @return {boolean} The canvas is blank
*/
ve.ce.MWGraphNode.static.isCanvasBlank = function ( canvas ) {
var blank = document.createElement( 'canvas' );
blank.width = canvas.width;
blank.height = canvas.height;
return canvas.toDataURL() === blank.toDataURL();
};
/* Methods */
/**
* Render a Vega graph inside the node
*/
ve.ce.MWGraphNode.prototype.update = function () {
var node = this;
// Clear element
this.$graph.empty();
this.$element.toggleClass( 'mw-graph-vega1', this.getModel().isGraphLegacy() );
mw.loader.using( 'ext.graph.vega2' ).done( function () {
node.$plot.detach();
node.constructor.static.vegaParseSpec( node.getModel().getSpec(), node.$graph[ 0 ] ).then(
function ( view ) {
// HACK: We need to know which padding values Vega computes in case
// of automatic padding, but it isn't properly exposed in the view
node.$graph.append( node.$plot );
// eslint-disable-next-line no-underscore-dangle
node.$plot.css( view._padding );
node.calculateHighlights();
},
function ( failMessageKey ) {
node.$graph.text( ve.msg( failMessageKey ) );
}
);
} );
};
/**
* @inheritdoc
*/
ve.ce.MWGraphNode.prototype.getAttributeChanges = function ( width, height ) {
var attrChanges = {},
newSpec = ve.dm.MWGraphModel.static.updateSpec( this.getModel().getSpec(), {
width: width,
height: height
} );
ve.setProp( attrChanges, 'mw', 'body', 'extsrc', JSON.stringify( newSpec ) );
return attrChanges;
};
/**
* @inheritdoc
*/
ve.ce.MWGraphNode.prototype.getFocusableElement = function () {
return this.$graph;
};
/* Registration */
ve.ce.nodeFactory.register( ve.ce.MWGraphNode );

View File

@@ -1,264 +1,264 @@
/*!
* VisualEditor DataModel MWGraphNode class.
*
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* DataModel MediaWiki graph node.
*
* @class
* @extends ve.dm.MWBlockExtensionNode
* @mixins ve.dm.ResizableNode
*
* @constructor
* @param {Object} [element]
*/
ve.dm.MWGraphNode = function VeDmMWGraphNode() {
var mw, extsrc;
// Parent constructor
ve.dm.MWGraphNode.super.apply( this, arguments );
// Mixin constructors
ve.dm.ResizableNode.call( this );
// Properties
this.spec = null;
// Events
this.connect( this, {
attributeChange: 'onAttributeChange'
} );
// Initialize specificiation
mw = this.getAttribute( 'mw' );
extsrc = ve.getProp( mw, 'body', 'extsrc' );
if ( extsrc ) {
this.setSpecFromString( extsrc );
} else {
this.setSpec( ve.dm.MWGraphNode.static.defaultSpec );
}
};
/* Inheritance */
OO.inheritClass( ve.dm.MWGraphNode, ve.dm.MWBlockExtensionNode );
OO.mixinClass( ve.dm.MWGraphNode, ve.dm.ResizableNode );
/* Static Members */
ve.dm.MWGraphNode.static.name = 'mwGraph';
ve.dm.MWGraphNode.static.extensionName = 'graph';
ve.dm.MWGraphNode.static.defaultSpec = {
version: 2,
width: 400,
height: 200,
data: [
{
name: 'table',
values: [
{
x: 0,
y: 1
},
{
x: 1,
y: 3
},
{
x: 2,
y: 2
},
{
x: 3,
y: 4
}
]
}
],
scales: [
{
name: 'x',
type: 'linear',
range: 'width',
zero: false,
domain: {
data: 'table',
field: 'x'
}
},
{
name: 'y',
type: 'linear',
range: 'height',
nice: true,
domain: {
data: 'table',
field: 'y'
}
}
],
axes: [
{
type: 'x',
scale: 'x'
},
{
type: 'y',
scale: 'y'
}
],
marks: [
{
type: 'area',
from: {
data: 'table'
},
properties: {
enter: {
x: {
scale: 'x',
field: 'x'
},
y: {
scale: 'y',
field: 'y'
},
y2: {
scale: 'y',
value: 0
},
fill: {
value: 'steelblue'
},
interpolate: {
value: 'monotone'
}
}
}
}
]
};
/* Static Methods */
/**
* Parses a spec string and returns its object representation.
*
* @param {string} str The spec string to validate. If the string is null or represents an empty object, the spec will be null.
* @return {Object} The object specification. On a failed parsing, the object will be returned empty.
*/
ve.dm.MWGraphNode.static.parseSpecString = function ( str ) {
var result;
try {
result = JSON.parse( str );
// JSON.parse can return other types than Object, we don't want that
// The error will be caught just below as this counts as a failed process
if ( $.type( result ) !== 'object' ) {
throw new Error();
}
return result;
} catch ( err ) {
return {};
}
};
/**
* Return the indented string representation of a spec.
*
* @param {Object} spec The object specificiation.
* @return {string} The stringified version of the spec.
*/
ve.dm.MWGraphNode.static.stringifySpec = function ( spec ) {
var result = JSON.stringify( spec, null, '\t' );
return result || '';
};
/* Methods */
/**
* @inheritdoc
*/
ve.dm.MWGraphNode.prototype.createScalable = function () {
var width = ve.getProp( this.spec, 'width' ),
height = ve.getProp( this.spec, 'height' );
return new ve.dm.Scalable( {
currentDimensions: {
width: width,
height: height
},
minDimensions: ve.dm.MWGraphModel.static.minDimensions,
fixedRatio: false
} );
};
/**
* Get the specification string
*
* @return {string} The specification JSON string
*/
ve.dm.MWGraphNode.prototype.getSpecString = function () {
return this.constructor.static.stringifySpec( this.spec );
};
/**
* Get the parsed JSON specification
*
* @return {Object} The specification object
*/
ve.dm.MWGraphNode.prototype.getSpec = function () {
return this.spec;
};
/**
* Set the specificiation
*
* @param {Object} spec The new spec
*/
ve.dm.MWGraphNode.prototype.setSpec = function ( spec ) {
// Consolidate all falsy values to an empty object for consistency
this.spec = spec || {};
};
/**
* Set the specification from a stringified version
*
* @param {string} str The new specification JSON string
*/
ve.dm.MWGraphNode.prototype.setSpecFromString = function ( str ) {
this.setSpec( this.constructor.static.parseSpecString( str ) );
};
/**
* React to node attribute changes
*
* @param {string} attributeName The attribute being updated
* @param {Object} from The old value of the attribute
* @param {Object} to The new value of the attribute
*/
ve.dm.MWGraphNode.prototype.onAttributeChange = function ( attributeName, from, to ) {
if ( attributeName === 'mw' ) {
this.setSpecFromString( to.body.extsrc );
}
};
/**
* Is this graph using a legacy version of Vega?
*
* @return {boolean}
*/
ve.dm.MWGraphNode.prototype.isGraphLegacy = function () {
return !!( this.spec && this.spec.hasOwnProperty( 'version' ) && this.spec.version < 2 );
};
/* Registration */
ve.dm.modelRegistry.register( ve.dm.MWGraphNode );
/*!
* VisualEditor DataModel MWGraphNode class.
*
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* DataModel MediaWiki graph node.
*
* @class
* @extends ve.dm.MWBlockExtensionNode
* @mixins ve.dm.ResizableNode
*
* @constructor
* @param {Object} [element]
*/
ve.dm.MWGraphNode = function VeDmMWGraphNode() {
var mw, extsrc;
// Parent constructor
ve.dm.MWGraphNode.super.apply( this, arguments );
// Mixin constructors
ve.dm.ResizableNode.call( this );
// Properties
this.spec = null;
// Events
this.connect( this, {
attributeChange: 'onAttributeChange'
} );
// Initialize specificiation
mw = this.getAttribute( 'mw' );
extsrc = ve.getProp( mw, 'body', 'extsrc' );
if ( extsrc ) {
this.setSpecFromString( extsrc );
} else {
this.setSpec( ve.dm.MWGraphNode.static.defaultSpec );
}
};
/* Inheritance */
OO.inheritClass( ve.dm.MWGraphNode, ve.dm.MWBlockExtensionNode );
OO.mixinClass( ve.dm.MWGraphNode, ve.dm.ResizableNode );
/* Static Members */
ve.dm.MWGraphNode.static.name = 'mwGraph';
ve.dm.MWGraphNode.static.extensionName = 'graph';
ve.dm.MWGraphNode.static.defaultSpec = {
version: 2,
width: 400,
height: 200,
data: [
{
name: 'table',
values: [
{
x: 0,
y: 1
},
{
x: 1,
y: 3
},
{
x: 2,
y: 2
},
{
x: 3,
y: 4
}
]
}
],
scales: [
{
name: 'x',
type: 'linear',
range: 'width',
zero: false,
domain: {
data: 'table',
field: 'x'
}
},
{
name: 'y',
type: 'linear',
range: 'height',
nice: true,
domain: {
data: 'table',
field: 'y'
}
}
],
axes: [
{
type: 'x',
scale: 'x'
},
{
type: 'y',
scale: 'y'
}
],
marks: [
{
type: 'area',
from: {
data: 'table'
},
properties: {
enter: {
x: {
scale: 'x',
field: 'x'
},
y: {
scale: 'y',
field: 'y'
},
y2: {
scale: 'y',
value: 0
},
fill: {
value: 'steelblue'
},
interpolate: {
value: 'monotone'
}
}
}
}
]
};
/* Static Methods */
/**
* Parses a spec string and returns its object representation.
*
* @param {string} str The spec string to validate. If the string is null or represents an empty object, the spec will be null.
* @return {Object} The object specification. On a failed parsing, the object will be returned empty.
*/
ve.dm.MWGraphNode.static.parseSpecString = function ( str ) {
var result;
try {
result = JSON.parse( str );
// JSON.parse can return other types than Object, we don't want that
// The error will be caught just below as this counts as a failed process
if ( $.type( result ) !== 'object' ) {
throw new Error();
}
return result;
} catch ( err ) {
return {};
}
};
/**
* Return the indented string representation of a spec.
*
* @param {Object} spec The object specificiation.
* @return {string} The stringified version of the spec.
*/
ve.dm.MWGraphNode.static.stringifySpec = function ( spec ) {
var result = JSON.stringify( spec, null, '\t' );
return result || '';
};
/* Methods */
/**
* @inheritdoc
*/
ve.dm.MWGraphNode.prototype.createScalable = function () {
var width = ve.getProp( this.spec, 'width' ),
height = ve.getProp( this.spec, 'height' );
return new ve.dm.Scalable( {
currentDimensions: {
width: width,
height: height
},
minDimensions: ve.dm.MWGraphModel.static.minDimensions,
fixedRatio: false
} );
};
/**
* Get the specification string
*
* @return {string} The specification JSON string
*/
ve.dm.MWGraphNode.prototype.getSpecString = function () {
return this.constructor.static.stringifySpec( this.spec );
};
/**
* Get the parsed JSON specification
*
* @return {Object} The specification object
*/
ve.dm.MWGraphNode.prototype.getSpec = function () {
return this.spec;
};
/**
* Set the specificiation
*
* @param {Object} spec The new spec
*/
ve.dm.MWGraphNode.prototype.setSpec = function ( spec ) {
// Consolidate all falsy values to an empty object for consistency
this.spec = spec || {};
};
/**
* Set the specification from a stringified version
*
* @param {string} str The new specification JSON string
*/
ve.dm.MWGraphNode.prototype.setSpecFromString = function ( str ) {
this.setSpec( this.constructor.static.parseSpecString( str ) );
};
/**
* React to node attribute changes
*
* @param {string} attributeName The attribute being updated
* @param {Object} from The old value of the attribute
* @param {Object} to The new value of the attribute
*/
ve.dm.MWGraphNode.prototype.onAttributeChange = function ( attributeName, from, to ) {
if ( attributeName === 'mw' ) {
this.setSpecFromString( to.body.extsrc );
}
};
/**
* Is this graph using a legacy version of Vega?
*
* @return {boolean}
*/
ve.dm.MWGraphNode.prototype.isGraphLegacy = function () {
return !!( this.spec && this.spec.hasOwnProperty( 'version' ) && this.spec.version < 2 );
};
/* Registration */
ve.dm.modelRegistry.register( ve.dm.MWGraphNode );

View File

@@ -1,39 +1,39 @@
/**
* MediaWiki UserInterface graph tool.
*
* @class
* @extends ve.ui.FragmentWindowTool
* @constructor
* @param {OO.ui.ToolGroup} toolGroup
* @param {Object} [config] Configuration options
*/
ve.ui.MWGraphDialogTool = function VeUiMWGraphDialogTool() {
ve.ui.MWGraphDialogTool.super.apply( this, arguments );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWGraphDialogTool, ve.ui.FragmentWindowTool );
/* Static properties */
ve.ui.MWGraphDialogTool.static.name = 'graph';
ve.ui.MWGraphDialogTool.static.group = 'object';
ve.ui.MWGraphDialogTool.static.icon = 'graph';
ve.ui.MWGraphDialogTool.static.title =
OO.ui.deferMsg( 'graph-ve-dialog-button-tooltip' );
ve.ui.MWGraphDialogTool.static.modelClasses = [ ve.dm.MWGraphNode ];
ve.ui.MWGraphDialogTool.static.commandName = 'graph';
/* Registration */
ve.ui.toolFactory.register( ve.ui.MWGraphDialogTool );
/* Commands */
ve.ui.commandRegistry.register(
new ve.ui.Command(
'graph', 'window', 'open',
{ args: [ 'graph' ], supportedSelections: [ 'linear' ] }
)
);
/**
* MediaWiki UserInterface graph tool.
*
* @class
* @extends ve.ui.FragmentWindowTool
* @constructor
* @param {OO.ui.ToolGroup} toolGroup
* @param {Object} [config] Configuration options
*/
ve.ui.MWGraphDialogTool = function VeUiMWGraphDialogTool() {
ve.ui.MWGraphDialogTool.super.apply( this, arguments );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWGraphDialogTool, ve.ui.FragmentWindowTool );
/* Static properties */
ve.ui.MWGraphDialogTool.static.name = 'graph';
ve.ui.MWGraphDialogTool.static.group = 'object';
ve.ui.MWGraphDialogTool.static.icon = 'graph';
ve.ui.MWGraphDialogTool.static.title =
OO.ui.deferMsg( 'graph-ve-dialog-button-tooltip' );
ve.ui.MWGraphDialogTool.static.modelClasses = [ ve.dm.MWGraphNode ];
ve.ui.MWGraphDialogTool.static.commandName = 'graph';
/* Registration */
ve.ui.toolFactory.register( ve.ui.MWGraphDialogTool );
/* Commands */
ve.ui.commandRegistry.register(
new ve.ui.Command(
'graph', 'window', 'open',
{ args: [ 'graph' ], supportedSelections: [ 'linear' ] }
)
);

View File

@@ -1,11 +1,11 @@
/*!
* VisualEditor UserInterface Graph icon styles.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
.oo-ui-icon-graph {
/* @embed */
background-image: url( graph.svg );
}
/*!
* VisualEditor UserInterface Graph icon styles.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
.oo-ui-icon-graph {
/* @embed */
background-image: url( graph.svg );
}

View File

@@ -1,362 +1,362 @@
/*!
* VisualEditor DataModel RowWidgetModel class
*
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* RowWidget model.
*
* @class
* @mixins OO.EventEmitter
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {Array} [data] An array containing all values of the row
* @cfg {Array} [keys] An array of keys for easy cell selection
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
* @cfg {string} [label=''] Row label. Defaults to empty string.
* @cfg {boolean} [showLabel=true] Show row label. Defaults to true.
* @cfg {boolean} [deletable=true] Allow row to be deleted. Defaults to true.
*/
ve.dm.RowWidgetModel = function VeDmRowWidgetModel( config ) {
config = config || {};
// Mixin constructors
OO.EventEmitter.call( this, config );
this.data = config.data || [];
this.validate = config.validate;
this.index = ( config.index !== undefined ) ? config.index : -1;
this.label = ( config.label !== undefined ) ? config.label : '';
this.showLabel = ( config.showLabel !== undefined ) ? !!config.showLabel : true;
this.isDeletable = ( config.deletable !== undefined ) ? !!config.deletable : true;
this.initializeProps( config.keys );
};
/* Inheritance */
OO.mixinClass( ve.dm.RowWidgetModel, OO.EventEmitter );
/* Events */
/**
* @event valueChange
*
* Fired when a value inside the row has changed.
*
* @param {number} The column index of the updated cell
* @param {number} The new value
*/
/**
* @event insertCell
*
* Fired when a new cell is inserted into the row.
*
* @param {Array} The initial data
* @param {number} The index in which to insert the new cell
*/
/**
* @event removeCell
*
* Fired when a cell is removed from the row.
*
* @param {number} The removed cell index
*/
/**
* @event clear
*
* Fired when the row is cleared
*
* @param {boolean} Clear cell properties
*/
/**
* @event labelUpdate
*
* Fired when the row label might need to be updated
*/
/* Methods */
/**
* Initializes and ensures the proper creation of the cell property array.
* If data exceeds the number of cells given, new ones will be created.
*
* @private
* @param {Array} props The initial cell props
*/
ve.dm.RowWidgetModel.prototype.initializeProps = function ( props ) {
var i, len;
this.cells = [];
if ( Array.isArray( props ) ) {
for ( i = 0, len = props.length; i < len; i++ ) {
this.cells.push( {
index: i,
key: props[ i ]
} );
}
}
};
/**
* Triggers the initialization process and builds the initial row.
*
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.setupRow = function () {
this.verifyData();
this.buildRow();
};
/**
* Verifies if the table data is complete and synced with
* cell properties, and adds empty strings as cell data if
* cells are missing
*
* @private
*/
ve.dm.RowWidgetModel.prototype.verifyData = function () {
var i, len;
for ( i = 0, len = this.cells.length; i < len; i++ ) {
if ( this.data[ i ] === undefined ) {
this.data.push( '' );
}
}
};
/**
* Build initial row
*
* @private
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.buildRow = function () {
var i, len;
for ( i = 0, len = this.cells.length; i < len; i++ ) {
this.emit( 'insertCell', this.data[ i ], i );
}
};
/**
* Refresh the entire row with new data
*
* @private
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.refreshRow = function () {
// TODO: Clear existing table
this.buildRow();
};
/**
* Set the value of a particular cell
*
* @param {number|string} handle The index or key of the cell
* @param {mixed} value The new value
* @fires valueChange
*/
ve.dm.RowWidgetModel.prototype.setValue = function ( handle, value ) {
var index;
if ( typeof handle === 'number' ) {
index = handle;
} else if ( typeof handle === 'string' ) {
index = this.getCellProperties( handle ).index;
}
if ( typeof index === 'number' && this.data[ index ] !== undefined &&
this.data[ index ] !== value ) {
this.data[ index ] = value;
this.emit( 'valueChange', index, value );
}
};
/**
* Set the row data
*
* @param {Array} data The new row data
*/
ve.dm.RowWidgetModel.prototype.setData = function ( data ) {
if ( Array.isArray( data ) ) {
this.data = data;
this.verifyData();
this.refreshRow();
}
};
/**
* Set the row index
*
* @param {number} index The new row index
* @fires labelUpdate
*/
ve.dm.RowWidgetModel.prototype.setIndex = function ( index ) {
this.index = index;
this.emit( 'labelChange' );
};
/**
* Set the row label
*
* @param {number} label The new row label
* @fires labelUpdate
*/
ve.dm.RowWidgetModel.prototype.setLabel = function ( label ) {
this.label = label;
this.emit( 'labelChange' );
};
/**
* Inserts a row into the table. If the row isn't added at the end of the table,
* all the following data will be shifted back one row.
*
* @param {number|string} [data] The data to insert to the cell.
* @param {number} [index] The index in which to insert the new cell.
* If unset or set to null, the cell will be added at the end of the row.
* @param {string} [key] A key to quickly select this cell.
* If unset or set to null, no key will be set.
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.insertCell = function ( data, index, key ) {
var insertIndex = ( typeof index === 'number' ) ? index : this.cells.length,
insertData, i, len;
// Add the new cell metadata
this.cells.splice( insertIndex, 0, {
index: insertIndex,
key: key || undefined
} );
// Add the new row data
insertData = ( $.type( data ) === 'string' || $.type( data ) === 'number' ) ? data : '';
this.data.splice( insertIndex, 0, insertData );
// Update all indexes in following cells
for ( i = insertIndex + 1, len = this.cells.length; i < len; i++ ) {
this.cells[ i ].index++;
}
this.emit( 'insertCell', data, insertIndex );
};
/**
* Removes a cell from the table. If the cell removed isn't at the end of the table,
* all the following cells will be shifted back one cell.
*
* @param {number|string} handle The key or numerical index of the cell to remove
* @fires removeCell
*/
ve.dm.RowWidgetModel.prototype.removeCell = function ( handle ) {
var cellProps = this.getCellProperties( handle ),
i, len;
// Exit early if the row couldn't be found
if ( cellProps === null ) {
return;
}
this.cells.splice( cellProps.index, 1 );
this.data.splice( cellProps.index, 1 );
// Update all indexes in following cells
for ( i = cellProps.index, len = this.cells.length; i < len; i++ ) {
this.cells[ i ].index--;
}
this.emit( 'removeCell', cellProps.index );
};
/**
* Clears the row data
*
* @fires clear
*/
ve.dm.RowWidgetModel.prototype.clear = function () {
this.data = [];
this.verifyData();
this.emit( 'clear', false );
};
/**
* Clears the row data, as well as all cell properties
*
* @fires clear
*/
ve.dm.RowWidgetModel.prototype.clearWithProperties = function () {
this.data = [];
this.cells = [];
this.emit( 'clear', true );
};
/**
* Get the validation pattern to test cells against
*
* @return {RegExp|Function|string}
*/
ve.dm.RowWidgetModel.prototype.getValidationPattern = function () {
return this.validate;
};
/**
* Get all row properties
*
* @return {Object}
*/
ve.dm.RowWidgetModel.prototype.getRowProperties = function () {
return {
index: this.index,
label: this.label,
showLabel: this.showLabel,
isDeletable: this.isDeletable
};
};
/**
* Get properties of a given cell
*
* @param {string|number} handle The key (or numeric index) of the cell
* @return {Object|null} An object containing the `key` and `index` properties of the cell.
* Returns `null` if the cell can't be found.
*/
ve.dm.RowWidgetModel.prototype.getCellProperties = function ( handle ) {
var cell = null,
i, len;
if ( typeof handle === 'string' ) {
for ( i = 0, len = this.cells.length; i < len; i++ ) {
if ( this.cells[ i ].key === handle ) {
cell = this.cells[ i ];
break;
}
}
} else if ( typeof handle === 'number' ) {
if ( handle < this.cells.length ) {
cell = this.cells[ handle ];
}
}
return cell;
};
/**
* Get properties of all cells
*
* @return {Array} An array of objects containing `key` and `index` properties for each cell
*/
ve.dm.RowWidgetModel.prototype.getAllCellProperties = function () {
return this.cells.slice();
};
/*!
* VisualEditor DataModel RowWidgetModel class
*
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* RowWidget model.
*
* @class
* @mixins OO.EventEmitter
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {Array} [data] An array containing all values of the row
* @cfg {Array} [keys] An array of keys for easy cell selection
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
* @cfg {string} [label=''] Row label. Defaults to empty string.
* @cfg {boolean} [showLabel=true] Show row label. Defaults to true.
* @cfg {boolean} [deletable=true] Allow row to be deleted. Defaults to true.
*/
ve.dm.RowWidgetModel = function VeDmRowWidgetModel( config ) {
config = config || {};
// Mixin constructors
OO.EventEmitter.call( this, config );
this.data = config.data || [];
this.validate = config.validate;
this.index = ( config.index !== undefined ) ? config.index : -1;
this.label = ( config.label !== undefined ) ? config.label : '';
this.showLabel = ( config.showLabel !== undefined ) ? !!config.showLabel : true;
this.isDeletable = ( config.deletable !== undefined ) ? !!config.deletable : true;
this.initializeProps( config.keys );
};
/* Inheritance */
OO.mixinClass( ve.dm.RowWidgetModel, OO.EventEmitter );
/* Events */
/**
* @event valueChange
*
* Fired when a value inside the row has changed.
*
* @param {number} The column index of the updated cell
* @param {number} The new value
*/
/**
* @event insertCell
*
* Fired when a new cell is inserted into the row.
*
* @param {Array} The initial data
* @param {number} The index in which to insert the new cell
*/
/**
* @event removeCell
*
* Fired when a cell is removed from the row.
*
* @param {number} The removed cell index
*/
/**
* @event clear
*
* Fired when the row is cleared
*
* @param {boolean} Clear cell properties
*/
/**
* @event labelUpdate
*
* Fired when the row label might need to be updated
*/
/* Methods */
/**
* Initializes and ensures the proper creation of the cell property array.
* If data exceeds the number of cells given, new ones will be created.
*
* @private
* @param {Array} props The initial cell props
*/
ve.dm.RowWidgetModel.prototype.initializeProps = function ( props ) {
var i, len;
this.cells = [];
if ( Array.isArray( props ) ) {
for ( i = 0, len = props.length; i < len; i++ ) {
this.cells.push( {
index: i,
key: props[ i ]
} );
}
}
};
/**
* Triggers the initialization process and builds the initial row.
*
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.setupRow = function () {
this.verifyData();
this.buildRow();
};
/**
* Verifies if the table data is complete and synced with
* cell properties, and adds empty strings as cell data if
* cells are missing
*
* @private
*/
ve.dm.RowWidgetModel.prototype.verifyData = function () {
var i, len;
for ( i = 0, len = this.cells.length; i < len; i++ ) {
if ( this.data[ i ] === undefined ) {
this.data.push( '' );
}
}
};
/**
* Build initial row
*
* @private
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.buildRow = function () {
var i, len;
for ( i = 0, len = this.cells.length; i < len; i++ ) {
this.emit( 'insertCell', this.data[ i ], i );
}
};
/**
* Refresh the entire row with new data
*
* @private
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.refreshRow = function () {
// TODO: Clear existing table
this.buildRow();
};
/**
* Set the value of a particular cell
*
* @param {number|string} handle The index or key of the cell
* @param {mixed} value The new value
* @fires valueChange
*/
ve.dm.RowWidgetModel.prototype.setValue = function ( handle, value ) {
var index;
if ( typeof handle === 'number' ) {
index = handle;
} else if ( typeof handle === 'string' ) {
index = this.getCellProperties( handle ).index;
}
if ( typeof index === 'number' && this.data[ index ] !== undefined &&
this.data[ index ] !== value ) {
this.data[ index ] = value;
this.emit( 'valueChange', index, value );
}
};
/**
* Set the row data
*
* @param {Array} data The new row data
*/
ve.dm.RowWidgetModel.prototype.setData = function ( data ) {
if ( Array.isArray( data ) ) {
this.data = data;
this.verifyData();
this.refreshRow();
}
};
/**
* Set the row index
*
* @param {number} index The new row index
* @fires labelUpdate
*/
ve.dm.RowWidgetModel.prototype.setIndex = function ( index ) {
this.index = index;
this.emit( 'labelChange' );
};
/**
* Set the row label
*
* @param {number} label The new row label
* @fires labelUpdate
*/
ve.dm.RowWidgetModel.prototype.setLabel = function ( label ) {
this.label = label;
this.emit( 'labelChange' );
};
/**
* Inserts a row into the table. If the row isn't added at the end of the table,
* all the following data will be shifted back one row.
*
* @param {number|string} [data] The data to insert to the cell.
* @param {number} [index] The index in which to insert the new cell.
* If unset or set to null, the cell will be added at the end of the row.
* @param {string} [key] A key to quickly select this cell.
* If unset or set to null, no key will be set.
* @fires insertCell
*/
ve.dm.RowWidgetModel.prototype.insertCell = function ( data, index, key ) {
var insertIndex = ( typeof index === 'number' ) ? index : this.cells.length,
insertData, i, len;
// Add the new cell metadata
this.cells.splice( insertIndex, 0, {
index: insertIndex,
key: key || undefined
} );
// Add the new row data
insertData = ( $.type( data ) === 'string' || $.type( data ) === 'number' ) ? data : '';
this.data.splice( insertIndex, 0, insertData );
// Update all indexes in following cells
for ( i = insertIndex + 1, len = this.cells.length; i < len; i++ ) {
this.cells[ i ].index++;
}
this.emit( 'insertCell', data, insertIndex );
};
/**
* Removes a cell from the table. If the cell removed isn't at the end of the table,
* all the following cells will be shifted back one cell.
*
* @param {number|string} handle The key or numerical index of the cell to remove
* @fires removeCell
*/
ve.dm.RowWidgetModel.prototype.removeCell = function ( handle ) {
var cellProps = this.getCellProperties( handle ),
i, len;
// Exit early if the row couldn't be found
if ( cellProps === null ) {
return;
}
this.cells.splice( cellProps.index, 1 );
this.data.splice( cellProps.index, 1 );
// Update all indexes in following cells
for ( i = cellProps.index, len = this.cells.length; i < len; i++ ) {
this.cells[ i ].index--;
}
this.emit( 'removeCell', cellProps.index );
};
/**
* Clears the row data
*
* @fires clear
*/
ve.dm.RowWidgetModel.prototype.clear = function () {
this.data = [];
this.verifyData();
this.emit( 'clear', false );
};
/**
* Clears the row data, as well as all cell properties
*
* @fires clear
*/
ve.dm.RowWidgetModel.prototype.clearWithProperties = function () {
this.data = [];
this.cells = [];
this.emit( 'clear', true );
};
/**
* Get the validation pattern to test cells against
*
* @return {RegExp|Function|string}
*/
ve.dm.RowWidgetModel.prototype.getValidationPattern = function () {
return this.validate;
};
/**
* Get all row properties
*
* @return {Object}
*/
ve.dm.RowWidgetModel.prototype.getRowProperties = function () {
return {
index: this.index,
label: this.label,
showLabel: this.showLabel,
isDeletable: this.isDeletable
};
};
/**
* Get properties of a given cell
*
* @param {string|number} handle The key (or numeric index) of the cell
* @return {Object|null} An object containing the `key` and `index` properties of the cell.
* Returns `null` if the cell can't be found.
*/
ve.dm.RowWidgetModel.prototype.getCellProperties = function ( handle ) {
var cell = null,
i, len;
if ( typeof handle === 'string' ) {
for ( i = 0, len = this.cells.length; i < len; i++ ) {
if ( this.cells[ i ].key === handle ) {
cell = this.cells[ i ];
break;
}
}
} else if ( typeof handle === 'number' ) {
if ( handle < this.cells.length ) {
cell = this.cells[ handle ];
}
}
return cell;
};
/**
* Get properties of all cells
*
* @return {Array} An array of objects containing `key` and `index` properties for each cell
*/
ve.dm.RowWidgetModel.prototype.getAllCellProperties = function () {
return this.cells.slice();
};

View File

@@ -1,33 +1,33 @@
.ve-ui-rowWidget {
clear: left;
float: left;
margin-bottom: -1px;
width: 100%;
}
.ve-ui-rowWidget-label {
display: block;
margin-right: 5%;
padding-top: 0.5em;
width: 35%;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-label {
float: left;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-cells {
float: left;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-cells > .oo-ui-inputWidget {
float: left;
margin-right: -1px;
width: 8em;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-cells > .oo-ui-inputWidget > input,
.ve-ui-rowWidget > .ve-ui-rowWidget-delete-button > .oo-ui-buttonElement-button {
margin: 0;
border-radius: 0;
}
.ve-ui-rowWidget {
clear: left;
float: left;
margin-bottom: -1px;
width: 100%;
}
.ve-ui-rowWidget-label {
display: block;
margin-right: 5%;
padding-top: 0.5em;
width: 35%;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-label {
float: left;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-cells {
float: left;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-cells > .oo-ui-inputWidget {
float: left;
margin-right: -1px;
width: 8em;
}
.ve-ui-rowWidget > .ve-ui-rowWidget-cells > .oo-ui-inputWidget > input,
.ve-ui-rowWidget > .ve-ui-rowWidget-delete-button > .oo-ui-buttonElement-button {
margin: 0;
border-radius: 0;
}

View File

@@ -1,333 +1,333 @@
/**
* A RowWidget is used in conjunction with {@link ve.ui.TableWidget table widgets}
* and should not be instantiated by themselves. They group together
* {@link OO.ui.TextInputWidget text input widgets} to form a unified row of
* editable data.
*
* @class
* @extends OO.ui.Widget
* @mixins OO.ui.mixin.GroupElement
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {Array} [data] The data of the cells
* @cfg {Array} [keys] An array of keys for easy cell selection
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
* @cfg {number} [index] The row index.
* @cfg {string} [label] The row label to display. If not provided, the row index will
* be used be default. If set to null, no label will be displayed.
* @cfg {boolean} [showLabel=true] Show row label. Defaults to true.
* @cfg {boolean} [deletable=true] Whether the table should provide deletion UI tools
* for this row or not. Defaults to true.
*/
ve.ui.RowWidget = function VeUiRowWidget( config ) {
config = config || {};
// Parent constructor
ve.ui.RowWidget.super.call( this, config );
// Mixin constructor
OO.ui.mixin.GroupElement.call( this, config );
// Set up model
this.model = new ve.dm.RowWidgetModel( config );
// Set up group element
this.setGroupElement(
$( '<div>' )
.addClass( 've-ui-rowWidget-cells' )
);
// Set up label
this.labelCell = new OO.ui.LabelWidget( {
classes: [ 've-ui-rowWidget-label' ]
} );
// Set up delete button
if ( this.model.getRowProperties().isDeletable ) {
this.deleteButton = new OO.ui.ButtonWidget( {
icon: { 'default': 'remove' },
classes: [ 've-ui-rowWidget-delete-button' ],
flags: 'destructive',
title: ve.msg( 'graph-ve-dialog-edit-table-row-delete' )
} );
}
// Events
this.model.connect( this, {
valueChange: 'onValueChange',
insertCell: 'onInsertCell',
removeCell: 'onRemoveCell',
clear: 'onClear',
labelUpdate: 'onLabelUpdate'
} );
this.aggregate( {
change: 'cellChange'
} );
this.connect( this, {
cellChange: 'onCellChange',
disable: 'onDisable'
} );
if ( this.model.getRowProperties().isDeletable ) {
this.deleteButton.connect( this, {
click: 'onDeleteButtonClick'
} );
}
// Initialization
this.$element.addClass( 've-ui-rowWidget' );
this.$element.append(
this.labelCell.$element,
this.$group
);
if ( this.model.getRowProperties().isDeletable ) {
this.$element.append( this.deleteButton.$element );
}
this.setLabel( this.model.getRowProperties().label );
this.model.setupRow();
};
/* Inheritance */
OO.inheritClass( ve.ui.RowWidget, OO.ui.Widget );
OO.mixinClass( ve.ui.RowWidget, OO.ui.mixin.GroupElement );
/* Events */
/**
* @event inputChange
*
* Change when an input contained within the row is updated
*
* @param {number} The index of the cell that changed
* @param {string} The new value of the cell
*/
/**
* @event deleteButtonClick
*
* Fired when the delete button for the row is pressed
*/
/* Methods */
/**
* @private
* @inheritdoc
*/
ve.ui.RowWidget.prototype.addItems = function ( items, index ) {
var i, len;
OO.ui.mixin.GroupElement.prototype.addItems.call( this, items, index );
for ( i = index, len = items.length; i < len; i++ ) {
items[ i ].setData( i );
}
};
/**
* @private
* @inheritdoc
*/
ve.ui.RowWidget.prototype.removeItems = function ( items ) {
var i, len, cells;
OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
cells = this.getItems();
for ( i = 0, len = cells.length; i < len; i++ ) {
cells[ i ].setData( i );
}
};
/**
* Get the row index
*
* @return {number} The row index
*/
ve.ui.RowWidget.prototype.getIndex = function () {
return this.model.getRowProperties().index;
};
/**
* Set the row index
*
* @param {number} index The new index
*/
ve.ui.RowWidget.prototype.setIndex = function ( index ) {
this.model.setIndex( index );
};
/**
* Get the label displayed on the row. If no custom label is set, the
* row index is used instead.
*
* @return {string} The row label
*/
ve.ui.RowWidget.prototype.getLabel = function () {
var props = this.model.getRowProperties();
if ( props.label === null ) {
return '';
} else if ( !props.label ) {
return props.index.toString();
} else {
return props.label;
}
};
/**
* Set the label to be displayed on the widget.
*
* @param {string} label The new label
* @fires labelUpdate
*/
ve.ui.RowWidget.prototype.setLabel = function ( label ) {
this.model.setLabel( label );
};
/**
* Set the value of a particular cell
*
* @param {number} index The cell index
* @param {string} value The new value
*/
ve.ui.RowWidget.prototype.setValue = function ( index, value ) {
this.model.setValue( index, value );
};
/**
* Insert a cell at a specified index
*
* @param {string} data The cell data
* @param {index} index The index to insert the cell at
* @param {string} key A key for easy cell selection
*/
ve.ui.RowWidget.prototype.insertCell = function ( data, index, key ) {
this.model.insertCell( data, index, key );
};
/**
* Removes a column at a specified index
*
* @param {number} index The index to removeColumn
*/
ve.ui.RowWidget.prototype.removeCell = function ( index ) {
this.model.removeCell( index );
};
/**
* Clear the field values
*/
ve.ui.RowWidget.prototype.clear = function () {
this.model.clear();
};
/**
* Handle model value changes
*
* @param {number} index The column index of the updated cell
* @param {number} value The new value
*
* @fires inputChange
*/
ve.ui.RowWidget.prototype.onValueChange = function ( index, value ) {
this.getItems()[ index ].setValue( value );
this.emit( 'inputChange', index, value );
};
/**
* Handle model cell insertions
*
* @param {string} data The initial data
* @param {number} index The index in which to insert the new cell
*/
ve.ui.RowWidget.prototype.onInsertCell = function ( data, index ) {
this.addItems( [
new OO.ui.TextInputWidget( {
data: index,
value: data,
validate: this.model.getValidationPattern()
} )
], index );
};
/**
* Handle model cell removals
*
* @param {number} index The removed cell index
*/
ve.ui.RowWidget.prototype.onRemoveCell = function ( index ) {
this.removeItems( [ index ] );
};
/**
* Handle clear requests
*/
ve.ui.RowWidget.prototype.onClear = function () {
var i, len,
cells = this.getItems();
for ( i = 0, len = cells.length; i < len; i++ ) {
cells[ i ].setValue( '' );
}
};
/**
* Update model label changes
*/
ve.ui.RowWidget.prototype.onLabelUpdate = function () {
this.labelCell.setLabel( this.getLabel() );
};
/**
* React to cell input change
*
* @private
* @param {OO.ui.TextInputWidget} input The input that fired the event
* @param {string} value The value of the input
*/
ve.ui.RowWidget.prototype.onCellChange = function ( input, value ) {
// FIXME: The table itself should know if it contains invalid data
// in order to pass form state to the dialog when it asks if the Apply
// button should be enabled or not. This probably requires the table
// and each individual row to handle validation through an array of promises
// fed from the cells within.
// Right now, the table can't know if it's valid or not because the events
// don't get passed through.
var self = this;
input.getValidity().done( function () {
self.model.setValue( input.getData(), value );
} );
};
/**
* Handle delete button clicks
*
* @private
* @fires deleteButtonClick
*/
ve.ui.RowWidget.prototype.onDeleteButtonClick = function () {
this.emit( 'deleteButtonClick' );
};
/**
* Handle disabled state changes
*
* @param {boolean} disabled The new disabled state
*/
ve.ui.RowWidget.prototype.onDisable = function ( disabled ) {
var i,
cells = this.getItems();
for ( i = 0; i < cells.length; i++ ) {
cells[ i ].setDisabled( disabled );
}
};
/**
* A RowWidget is used in conjunction with {@link ve.ui.TableWidget table widgets}
* and should not be instantiated by themselves. They group together
* {@link OO.ui.TextInputWidget text input widgets} to form a unified row of
* editable data.
*
* @class
* @extends OO.ui.Widget
* @mixins OO.ui.mixin.GroupElement
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {Array} [data] The data of the cells
* @cfg {Array} [keys] An array of keys for easy cell selection
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
* @cfg {number} [index] The row index.
* @cfg {string} [label] The row label to display. If not provided, the row index will
* be used be default. If set to null, no label will be displayed.
* @cfg {boolean} [showLabel=true] Show row label. Defaults to true.
* @cfg {boolean} [deletable=true] Whether the table should provide deletion UI tools
* for this row or not. Defaults to true.
*/
ve.ui.RowWidget = function VeUiRowWidget( config ) {
config = config || {};
// Parent constructor
ve.ui.RowWidget.super.call( this, config );
// Mixin constructor
OO.ui.mixin.GroupElement.call( this, config );
// Set up model
this.model = new ve.dm.RowWidgetModel( config );
// Set up group element
this.setGroupElement(
$( '<div>' )
.addClass( 've-ui-rowWidget-cells' )
);
// Set up label
this.labelCell = new OO.ui.LabelWidget( {
classes: [ 've-ui-rowWidget-label' ]
} );
// Set up delete button
if ( this.model.getRowProperties().isDeletable ) {
this.deleteButton = new OO.ui.ButtonWidget( {
icon: { 'default': 'remove' },
classes: [ 've-ui-rowWidget-delete-button' ],
flags: 'destructive',
title: ve.msg( 'graph-ve-dialog-edit-table-row-delete' )
} );
}
// Events
this.model.connect( this, {
valueChange: 'onValueChange',
insertCell: 'onInsertCell',
removeCell: 'onRemoveCell',
clear: 'onClear',
labelUpdate: 'onLabelUpdate'
} );
this.aggregate( {
change: 'cellChange'
} );
this.connect( this, {
cellChange: 'onCellChange',
disable: 'onDisable'
} );
if ( this.model.getRowProperties().isDeletable ) {
this.deleteButton.connect( this, {
click: 'onDeleteButtonClick'
} );
}
// Initialization
this.$element.addClass( 've-ui-rowWidget' );
this.$element.append(
this.labelCell.$element,
this.$group
);
if ( this.model.getRowProperties().isDeletable ) {
this.$element.append( this.deleteButton.$element );
}
this.setLabel( this.model.getRowProperties().label );
this.model.setupRow();
};
/* Inheritance */
OO.inheritClass( ve.ui.RowWidget, OO.ui.Widget );
OO.mixinClass( ve.ui.RowWidget, OO.ui.mixin.GroupElement );
/* Events */
/**
* @event inputChange
*
* Change when an input contained within the row is updated
*
* @param {number} The index of the cell that changed
* @param {string} The new value of the cell
*/
/**
* @event deleteButtonClick
*
* Fired when the delete button for the row is pressed
*/
/* Methods */
/**
* @private
* @inheritdoc
*/
ve.ui.RowWidget.prototype.addItems = function ( items, index ) {
var i, len;
OO.ui.mixin.GroupElement.prototype.addItems.call( this, items, index );
for ( i = index, len = items.length; i < len; i++ ) {
items[ i ].setData( i );
}
};
/**
* @private
* @inheritdoc
*/
ve.ui.RowWidget.prototype.removeItems = function ( items ) {
var i, len, cells;
OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
cells = this.getItems();
for ( i = 0, len = cells.length; i < len; i++ ) {
cells[ i ].setData( i );
}
};
/**
* Get the row index
*
* @return {number} The row index
*/
ve.ui.RowWidget.prototype.getIndex = function () {
return this.model.getRowProperties().index;
};
/**
* Set the row index
*
* @param {number} index The new index
*/
ve.ui.RowWidget.prototype.setIndex = function ( index ) {
this.model.setIndex( index );
};
/**
* Get the label displayed on the row. If no custom label is set, the
* row index is used instead.
*
* @return {string} The row label
*/
ve.ui.RowWidget.prototype.getLabel = function () {
var props = this.model.getRowProperties();
if ( props.label === null ) {
return '';
} else if ( !props.label ) {
return props.index.toString();
} else {
return props.label;
}
};
/**
* Set the label to be displayed on the widget.
*
* @param {string} label The new label
* @fires labelUpdate
*/
ve.ui.RowWidget.prototype.setLabel = function ( label ) {
this.model.setLabel( label );
};
/**
* Set the value of a particular cell
*
* @param {number} index The cell index
* @param {string} value The new value
*/
ve.ui.RowWidget.prototype.setValue = function ( index, value ) {
this.model.setValue( index, value );
};
/**
* Insert a cell at a specified index
*
* @param {string} data The cell data
* @param {index} index The index to insert the cell at
* @param {string} key A key for easy cell selection
*/
ve.ui.RowWidget.prototype.insertCell = function ( data, index, key ) {
this.model.insertCell( data, index, key );
};
/**
* Removes a column at a specified index
*
* @param {number} index The index to removeColumn
*/
ve.ui.RowWidget.prototype.removeCell = function ( index ) {
this.model.removeCell( index );
};
/**
* Clear the field values
*/
ve.ui.RowWidget.prototype.clear = function () {
this.model.clear();
};
/**
* Handle model value changes
*
* @param {number} index The column index of the updated cell
* @param {number} value The new value
*
* @fires inputChange
*/
ve.ui.RowWidget.prototype.onValueChange = function ( index, value ) {
this.getItems()[ index ].setValue( value );
this.emit( 'inputChange', index, value );
};
/**
* Handle model cell insertions
*
* @param {string} data The initial data
* @param {number} index The index in which to insert the new cell
*/
ve.ui.RowWidget.prototype.onInsertCell = function ( data, index ) {
this.addItems( [
new OO.ui.TextInputWidget( {
data: index,
value: data,
validate: this.model.getValidationPattern()
} )
], index );
};
/**
* Handle model cell removals
*
* @param {number} index The removed cell index
*/
ve.ui.RowWidget.prototype.onRemoveCell = function ( index ) {
this.removeItems( [ index ] );
};
/**
* Handle clear requests
*/
ve.ui.RowWidget.prototype.onClear = function () {
var i, len,
cells = this.getItems();
for ( i = 0, len = cells.length; i < len; i++ ) {
cells[ i ].setValue( '' );
}
};
/**
* Update model label changes
*/
ve.ui.RowWidget.prototype.onLabelUpdate = function () {
this.labelCell.setLabel( this.getLabel() );
};
/**
* React to cell input change
*
* @private
* @param {OO.ui.TextInputWidget} input The input that fired the event
* @param {string} value The value of the input
*/
ve.ui.RowWidget.prototype.onCellChange = function ( input, value ) {
// FIXME: The table itself should know if it contains invalid data
// in order to pass form state to the dialog when it asks if the Apply
// button should be enabled or not. This probably requires the table
// and each individual row to handle validation through an array of promises
// fed from the cells within.
// Right now, the table can't know if it's valid or not because the events
// don't get passed through.
var self = this;
input.getValidity().done( function () {
self.model.setValue( input.getData(), value );
} );
};
/**
* Handle delete button clicks
*
* @private
* @fires deleteButtonClick
*/
ve.ui.RowWidget.prototype.onDeleteButtonClick = function () {
this.emit( 'deleteButtonClick' );
};
/**
* Handle disabled state changes
*
* @param {boolean} disabled The new disabled state
*/
ve.ui.RowWidget.prototype.onDisable = function ( disabled ) {
var i,
cells = this.getItems();
for ( i = 0; i < cells.length; i++ ) {
cells[ i ].setDisabled( disabled );
}
};

View File

@@ -1,9 +1,9 @@
.ve-ui-tableWidget > .ve-ui-tableWidget-rows {
float: left;
clear: left;
width: 100%;
}
.ve-ui-tableWidget.ve-ui-tableWidget-no-labels .ve-ui-rowWidget-label {
display: none;
}
.ve-ui-tableWidget > .ve-ui-tableWidget-rows {
float: left;
clear: left;
width: 100%;
}
.ve-ui-tableWidget.ve-ui-tableWidget-no-labels .ve-ui-rowWidget-label {
display: none;
}