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:
@@ -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 ) );
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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 |
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 );
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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' ] }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user