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

Change subject: [BREAKING CHANGE] Multiple surface support and demo
......................................................................


[BREAKING CHANGE] Multiple surface support and demo

Refactor the toolbar so that it can attached and detached from
a surface.

Remove (en|dis)ableFloatable and replace with a config option. Toolbars
never change their buoyancy.

Have toolbars emit a null updateState when they are detached so they
can disable all their tools.

The target now lazily creates a toolbar when you call getToolbar.

Update the standalone demo to allow creating extra surfaces, with some
future to-dos:
* Refactor the demo toolbar into target actions and surface actions
* Fix the focus-stealing issue when changing surface while a dialog is open

Change-Id: Ic999ec166d8005e0368c0dc95c0a5593e6bf2945
---
M demos/ve/demo.css
M demos/ve/demo.js
M src/init/sa/ve.init.sa.Target.js
M src/init/ve.init.Target.js
M src/ui/tools/ve.ui.AnnotationTool.js
M src/ui/tools/ve.ui.InspectorTool.js
M src/ui/tools/ve.ui.ListTool.js
M src/ui/ve.ui.TargetToolbar.js
M src/ui/ve.ui.Tool.js
M src/ui/ve.ui.Toolbar.js
M tests/ve.test.utils.js
11 files changed, 149 insertions(+), 199 deletions(-)

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



diff --git a/demos/ve/demo.css b/demos/ve/demo.css
index 762714f..c79d00f 100644
--- a/demos/ve/demo.css
+++ b/demos/ve/demo.css
@@ -93,6 +93,7 @@
 
 .ve-ui-debugBar {
        padding: 1.5em;
+       margin-bottom: 1em;
 }
 
 .ve-ui-debugBar-filibuster {
diff --git a/demos/ve/demo.js b/demos/ve/demo.js
index e7d206a..b6f3321 100644
--- a/demos/ve/demo.js
+++ b/demos/ve/demo.js
@@ -20,19 +20,18 @@
                        return items;
                }
 
-               var currentTarget,
-                       initialPage,
+               var initialPage,
 
                        lastMode = null,
 
                        $menu = $( '.ve-demo-menu' ),
                        $editor = $( '.ve-demo-editor' ),
-                       $targetContainer = $( '<div>' ),
+                       target = new ve.init.sa.Target( $( '<div>' ).appendTo( 
$editor ) ),
 
                        switching = false,
 
                        currentLang = $.i18n().locale,
-                       currentDir = $targetContainer.css( 'direction' ) || 
'ltr',
+                       currentDir = target.$element.css( 'direction' ) || 
'ltr',
 
                        // Menu widgets
                        pageDropdown = new OO.ui.DropdownWidget( {
@@ -44,6 +43,7 @@
                                { label: 'Page', input: pageDropdown }
                        ),
                        pageMenu = pageDropdown.getMenu(),
+                       addSurfaceButton = new OO.ui.ButtonWidget( { icon: 
'add' } ),
 
                        modeSelect = new OO.ui.ButtonSelectWidget().addItems( [
                                new OO.ui.ButtonOptionWidget( { data: 've', 
label: 'VE' } ),
@@ -74,6 +74,10 @@
                                history.replaceState( null, document.title, 
'#!/src/' + page );
                        }
                        switchPage( 've', page );
+               } );
+
+               addSurfaceButton.on( 'click', function () {
+                       addSurface( '' );
                } );
 
                messageKeyButton.on( 'click', function () {
@@ -124,9 +128,9 @@
 
                        switch ( lastMode ) {
                                case 've':
-                                       closePromise = 
$targetContainer.slideUp().promise();
+                                       closePromise = 
target.$element.slideUp().promise();
                                        if ( !page ) {
-                                               model = 
currentTarget.getSurface().getModel().getDocument() ;
+                                               model = 
target.getSurface().getModel().getDocument() ;
                                                doc = 
ve.dm.converter.getDomFromModel( model );
                                                html = ve.properInnerHtml( 
doc.body );
                                                currentDir = model.getDir();
@@ -155,6 +159,7 @@
                        closePromise.done( function () {
                                switch ( mode ) {
                                        case 've':
+                                               target.$element.slideDown();
                                                if ( page ) {
                                                        loadPage( page );
                                                } else if ( html ) {
@@ -165,9 +170,7 @@
                                        case 'edit':
                                                sourceTextInput.$element.show();
                                                sourceTextInput.setValue( html 
).adjustSize();
-                                               sourceTextInput.$element.hide();
-
-                                               
sourceTextInput.$element.slideDown();
+                                               
sourceTextInput.$element.hide().slideDown();
                                                break;
 
                                        case 'read':
@@ -184,6 +187,7 @@
                        $( '<div>' ).addClass( 've-demo-menu-commands' ).append(
                                pageLabel.$element,
                                pageDropdown.$element,
+                               addSurfaceButton.$element,
                                $( '<span 
class="ve-demo-menu-divider">&nbsp;</span>' ),
                                modeSelect.$element,
                                $( '<span 
class="ve-demo-menu-divider">&nbsp;</span>' ),
@@ -192,7 +196,7 @@
                        )
                );
 
-               $editor.append( $targetContainer, 
sourceTextInput.$element.hide(), $readView );
+               $editor.append( target.$element, 
sourceTextInput.$element.hide(), $readView );
 
                /**
                 * Load a page into the editor
@@ -206,69 +210,54 @@
                                currentDir = src.match( /rtl\.html$/ ) ? 'rtl' 
: 'ltr';
                        }
 
-                       $targetContainer.slideUp().promise().done( function () {
-                               $.ajax( {
-                                       url: src,
-                                       dataType: 'text'
-                               } ).always( function ( result, status ) {
-                                       var pageHtml;
+                       ve.init.platform.getInitializedPromise().done( function 
() {
+                               var promise = target.getSurface() ?
+                                       
target.getSurface().$element.slideUp().promise() :
+                                       $.Deferred().resolve().promise();
 
-                                       if ( status === 'error' ) {
-                                               pageHtml = '<p><i>Failed 
loading page ' + $( '<span>' ).text( src ).html() + '</i></p>';
-                                       } else {
-                                               pageHtml = result;
-                                       }
+                               promise.done( function () {
+                                       $.ajax( {
+                                               url: src,
+                                               dataType: 'text'
+                                       } ).always( function ( result, status ) 
{
+                                               var pageHtml;
 
-                                       loadTarget( pageHtml );
+                                               if ( status === 'error' ) {
+                                                       pageHtml = 
'<p><i>Failed loading page ' + $( '<span>' ).text( src ).html() + '</i></p>';
+                                               } else {
+                                                       pageHtml = result;
+                                               }
+
+                                               loadTarget( pageHtml );
+                                       } );
                                } );
                        } );
                }
 
                function loadTarget( pageHtml ) {
-                       if ( currentTarget ) {
-                               currentTarget.destroy();
-                       }
+                       target.clearSurfaces();
 
-                       var $container = $( '<div>' ),
-                               oldDir = currentDir === 'ltr' ? 'rtl' : 'ltr';
+                       var oldDir = currentDir === 'ltr' ? 'rtl' : 'ltr';
 
                        $( '.stylesheet-' + currentDir ).prop( 'disabled', 
false );
                        $( '.stylesheet-' + oldDir ).prop( 'disabled', true );
 
-                       // Container needs to be visually hidden, but not 
display:none
-                       // so that the toolbar can be measured
-                       $targetContainer.empty().show().css( {
-                               height: 0,
-                               overflow: 'hidden'
-                       } );
+                       target.$element.css( 'direction', currentDir );
 
-                       $targetContainer.css( 'direction', currentDir );
+                       addSurface( pageHtml );
+               }
 
-                       // The container must be attached to the DOM before
-                       // the target is initialised
-                       $targetContainer.append( $container );
-
-                       $targetContainer.show();
-                       currentTarget = new ve.init.sa.Target(
-                               $container,
+               function addSurface( html ) {
+                       var surface = target.addSurface(
                                ve.dm.converter.getModelFromDom(
-                                       ve.createDocumentFromHtml( pageHtml ),
-                                       $targetContainer.ownerDocument,
+                                       ve.createDocumentFromHtml( html ),
+                                       target.$element.ownerDocument,
                                        currentLang,
                                        currentDir
                                )
                        );
-
-                       currentTarget.on( 'surfaceReady', function () {
-                               var surfaceView = 
currentTarget.getSurface().getView();
-                               // Container must be properly hidden before 
slideDown animation
-                               $targetContainer.removeAttr( 'style' ).hide()
-                                       // Restore directionality
-                                       .css( 'direction', currentDir );
-
-                               $targetContainer.slideDown().promise().done( 
function () {
-                                       surfaceView.focus();
-                               } );
+                       surface.$element.hide().slideDown().promise().done( 
function () {
+                               surface.getView().focus();
                        } );
                }
 
diff --git a/src/init/sa/ve.init.sa.Target.js b/src/init/sa/ve.init.sa.Target.js
index c663df7..a634df7 100644
--- a/src/init/sa/ve.init.sa.Target.js
+++ b/src/init/sa/ve.init.sa.Target.js
@@ -7,25 +7,20 @@
 /**
  * Initialization Standalone target.
  *
- *     @example
- *     new ve.init.sa.Target(
- *         $( '<div>' ).appendTo( 'body' ), ve.createDocumentFromHtml( 
'<p>Hello world.</p>' )
- *     );
- *
  * @class
  * @extends ve.init.Target
  *
  * @constructor
  * @param {jQuery} $container Container to render target into
- * @param {ve.dm.Document} dmDoc Document model
  * @param {string} [surfaceType] Type of surface to use, 'desktop' or 'mobile'
  * @throws {Error} Unknown surfaceType
  */
-ve.init.sa.Target = function VeInitSaTarget( $container, dmDoc, surfaceType ) {
+ve.init.sa.Target = function VeInitSaTarget( $container, surfaceType ) {
        // Parent constructor
-       ve.init.Target.call( this, $container );
+       ve.init.Target.call( this, $container, { shadow: true, actions: true, 
floatable: true } );
 
        this.surfaceType = surfaceType || 
this.constructor.static.defaultSurfaceType;
+       this.actions = null;
 
        switch ( this.surfaceType ) {
                case 'desktop':
@@ -37,9 +32,11 @@
                default:
                        throw new Error( 'Unknown surfaceType: ' + 
this.surfaceType );
        }
-       this.setupDone = false;
 
-       ve.init.platform.getInitializedPromise().done( this.setup.bind( this, 
dmDoc ) );
+       // The following classes can be used here:
+       // ve-init-sa-target-mobile
+       // ve-init-sa-target-desktop
+       this.$element.addClass( 've-init-sa-target ve-init-sa-target-' + 
this.surfaceType );
 };
 
 /* Inheritance */
@@ -53,44 +50,16 @@
 /* Methods */
 
 /**
- * Setup the target
- *
- * @param {ve.dm.Document} dmDoc Document model
- * @fires surfaceReady
+ * @inheritdoc
  */
-ve.init.sa.Target.prototype.setup = function ( dmDoc ) {
-       var surface, target = this;
-
-       if ( this.setupDone ) {
-               return;
-       }
-       this.setupDone = true;
-       surface = this.addSurface( dmDoc );
+ve.init.sa.Target.prototype.addSurface = function () {
+       var surface = ve.init.sa.Target.super.prototype.addSurface.apply( this, 
arguments );
        this.$element.append( surface.$element );
-
-       this.setupToolbar( { classes: ['ve-init-sa-target-toolbar'] } );
-
-       // Initialization
-       // The following classes can be used here:
-       // ve-init-sa-target-mobile
-       // ve-init-sa-target-desktop
-       this.$element.addClass( 've-init-sa-target ve-init-sa-target-' + 
this.surfaceType );
-       this.getToolbar().enableFloatable();
-       this.getToolbar().initialize();
-       surface.initialize();
-
-       // HACK: On mobile place the context inside toolbar.$bar which floats
-       if ( this.surfaceType === 'mobile' ) {
-               this.getToolbar().$bar.append( surface.context.$element );
+       if ( !this.getSurface() ) {
+               this.setSurface( surface );
        }
-
-       // This must be emitted asynchronously because 
ve.init.Platform#initialize
-       // is synchronous, and if we emit it right away, then users will be
-       // unable to listen to this event as it will have been emitted before 
the
-       // constructor returns.
-       setTimeout( function () {
-               target.emit( 'surfaceReady' );
-       } );
+       surface.initialize();
+       return surface;
 };
 
 /**
@@ -111,22 +80,28 @@
 /**
  * @inheritdoc
  */
-ve.init.sa.Target.prototype.setupToolbar = function ( config ) {
-       config = ve.extendObject( { shadow: true, actions: true }, config );
-
+ve.init.sa.Target.prototype.setupToolbar = function ( surface ) {
        // Parent method
-       ve.init.sa.Target.super.prototype.setupToolbar.call( this, config );
+       ve.init.sa.Target.super.prototype.setupToolbar.call( this, surface );
 
-       var actions = new ve.ui.TargetToolbar( this, this.getSurface() );
+       if ( !this.getToolbar().initialized ) {
+               this.getToolbar().$element.addClass( 
've-init-sa-target-toolbar' );
+               this.actions = new ve.ui.TargetToolbar( this );
+               this.getToolbar().$actions.append( this.actions.$element );
+       }
+       this.getToolbar().initialize();
 
-       actions.setup( [
+       this.actions.setup( [
                {
                        type: 'list',
                        icon: 'menu',
                        title: ve.msg( 'visualeditor-pagemenu-tooltip' ),
                        include: [ 'findAndReplace', 'commandHelp' ]
                }
-       ] );
+       ], this.getSurface() );
 
-       this.toolbar.$actions.append( actions.$element );
+       // HACK: On mobile place the context inside toolbar.$bar which floats
+       if ( this.surfaceType === 'mobile' ) {
+               this.getToolbar().$bar.append( surface.context.$element );
+       }
 };
diff --git a/src/init/ve.init.Target.js b/src/init/ve.init.Target.js
index a1ce349..601e22d 100644
--- a/src/init/ve.init.Target.js
+++ b/src/init/ve.init.Target.js
@@ -13,9 +13,10 @@
  *
  * @constructor
  * @param {jQuery} $container Container to render target into, must be 
attached to the DOM
+ * @param {Object} toolbarConfig Configuration options for the toolbar
  * @throws {Error} Container must be attached to the DOM
  */
-ve.init.Target = function VeInitTarget( $container ) {
+ve.init.Target = function VeInitTarget( $container, toolbarConfig ) {
        if ( !$.contains( $container[0].ownerDocument, $container[0] ) ) {
                throw new Error( 'Container must be attached to the DOM' );
        }
@@ -29,6 +30,7 @@
        this.surfaces = [];
        this.surface = null;
        this.toolbar = null;
+       this.toolbarConfig = toolbarConfig;
        this.documentTriggerListener = new ve.TriggerListener( 
this.constructor.static.documentCommands );
        this.targetTriggerListener = new ve.TriggerListener( 
this.constructor.static.targetCommands );
 
@@ -64,18 +66,6 @@
        $( this.elementDocument ).off( 'keydown', this.onDocumentKeyDownHandler 
);
        ve.init.target = null;
 };
-
-/* Events */
-
-/**
- * Fired when the #surface is ready.
- *
- * By default the surface's document is not focused. If the target wants
- * the browsers' focus to be in the surface (ready for typing and cursoring)
- * call `surface.getView().focus()` in a handler for this event.
- *
- * @event surfaceReady
- */
 
 /* Inheritance */
 
@@ -255,9 +245,6 @@
 ve.init.Target.prototype.addSurface = function ( dmDoc, config ) {
        var surface = this.createSurface( dmDoc, config );
        this.surfaces.push( surface );
-       if ( !this.getSurface() ) {
-               this.setSurface( surface );
-       }
        surface.getView().connect( this, { focus: this.onSurfaceViewFocus.bind( 
this, surface ) } );
        return surface;
 };
@@ -288,6 +275,7 @@
 ve.init.Target.prototype.setSurface = function ( surface ) {
        if ( surface !== this.surface ) {
                this.surface = surface;
+               this.setupToolbar( surface );
        }
 };
 
@@ -306,22 +294,19 @@
  * @return {ve.ui.TargetToolbar} Toolbar
  */
 ve.init.Target.prototype.getToolbar = function () {
+       if ( !this.toolbar ) {
+               this.toolbar = new ve.ui.TargetToolbar( this, 
this.toolbarConfig );
+       }
        return this.toolbar;
 };
 
 /**
- * Set up the toolbar and insert it into the DOM.
+ * Set up the toolbar, attaching it to a surface.
  *
- * The default implementation inserts it before the surface, but subclasses 
can override this.
- *
- * @param {Object} [config] Configuration options
+ * @param {ve.ui.Surface} surface Surface
  */
-ve.init.Target.prototype.setupToolbar = function ( config ) {
-       if ( !this.surfaces.length ) {
-               throw new Error( 'Surface must be setup before Toolbar' );
-       }
-       this.toolbar = new ve.ui.TargetToolbar( this, this.getSurface(), config 
);
-       this.toolbar.setup( this.constructor.static.toolbarGroups );
-       this.toolbar.$element.insertBefore( this.getSurface().$element );
-       this.toolbar.$bar.append( this.getSurface().toolbarDialogs.$element );
+ve.init.Target.prototype.setupToolbar = function ( surface ) {
+       this.getToolbar().setup( this.constructor.static.toolbarGroups, surface 
);
+       this.getToolbar().$element.insertBefore( surface.$element );
+       this.getToolbar().$bar.append( surface.toolbarDialogs.$element );
 };
diff --git a/src/ui/tools/ve.ui.AnnotationTool.js 
b/src/ui/tools/ve.ui.AnnotationTool.js
index fbb3063..dc0a5bf 100644
--- a/src/ui/tools/ve.ui.AnnotationTool.js
+++ b/src/ui/tools/ve.ui.AnnotationTool.js
@@ -47,7 +47,7 @@
        ve.ui.Tool.prototype.onUpdateState.apply( this, arguments );
 
        this.setActive(
-               fragment.getAnnotations().hasAnnotationWithName( 
this.constructor.static.annotation.name )
+               fragment && fragment.getAnnotations().hasAnnotationWithName( 
this.constructor.static.annotation.name )
        );
 };
 
diff --git a/src/ui/tools/ve.ui.InspectorTool.js 
b/src/ui/tools/ve.ui.InspectorTool.js
index a371eac..2d40c12 100644
--- a/src/ui/tools/ve.ui.InspectorTool.js
+++ b/src/ui/tools/ve.ui.InspectorTool.js
@@ -57,7 +57,7 @@
        // Parent method
        ve.ui.Tool.prototype.onUpdateState.apply( this, arguments );
 
-       models = fragment.getSelectedModels();
+       models = fragment ? fragment.getSelectedModels() : [];
        for ( i = 0, len = models.length; i < len; i++ ) {
                if ( this.constructor.static.isCompatibleWith( models[i] ) ) {
                        active = true;
diff --git a/src/ui/tools/ve.ui.ListTool.js b/src/ui/tools/ve.ui.ListTool.js
index 4f0791a..bcd029a 100644
--- a/src/ui/tools/ve.ui.ListTool.js
+++ b/src/ui/tools/ve.ui.ListTool.js
@@ -50,7 +50,7 @@
        ve.ui.Tool.prototype.onUpdateState.apply( this, arguments );
 
        var i, len,
-               nodes = fragment.getSelectedLeafNodes(),
+               nodes = fragment ? fragment.getSelectedLeafNodes() : [],
                style = this.constructor.static.style,
                all = !!nodes.length;
 
diff --git a/src/ui/ve.ui.TargetToolbar.js b/src/ui/ve.ui.TargetToolbar.js
index acb2b27..503f17b 100644
--- a/src/ui/ve.ui.TargetToolbar.js
+++ b/src/ui/ve.ui.TargetToolbar.js
@@ -12,12 +12,11 @@
  *
  * @constructor
  * @param {ve.init.Target} target Target to control
- * @param {ve.ui.Surface} surface Surface to control
- * @param {Object} [options] Configuration options
+ * @param {Object} [config] Configuration options
  */
-ve.ui.TargetToolbar = function VeUiTargetToolbar( target, surface, options ) {
+ve.ui.TargetToolbar = function VeUiTargetToolbar( target, config ) {
        // Parent constructor
-       ve.ui.TargetToolbar.super.call( this, surface, options );
+       ve.ui.TargetToolbar.super.call( this, config );
 
        // Properties
        this.target = target;
diff --git a/src/ui/ve.ui.Tool.js b/src/ui/ve.ui.Tool.js
index 4e490be..dfe3296 100644
--- a/src/ui/ve.ui.Tool.js
+++ b/src/ui/ve.ui.Tool.js
@@ -62,13 +62,13 @@
  * Handle the toolbar state being updated.
  *
  * @method
- * @param {ve.dm.SurfaceFragment} fragment Surface fragment
- * @param {Object} direction Context direction with 'inline' & 'block' 
properties
+ * @param {ve.dm.SurfaceFragment|null} fragment Surface fragment
+ * @param {Object|null} direction Context direction with 'inline' & 'block' 
properties
  */
 ve.ui.Tool.prototype.onUpdateState = function ( fragment ) {
        var command = this.getCommand();
        if ( command !== null ) {
-               this.setDisabled( !command || ( fragment && 
!command.isExecutable( fragment ) ) );
+               this.setDisabled( !command || !fragment || 
!command.isExecutable( fragment ) );
        }
 };
 
diff --git a/src/ui/ve.ui.Toolbar.js b/src/ui/ve.ui.Toolbar.js
index 43bb99f..9c7dea1 100644
--- a/src/ui/ve.ui.Toolbar.js
+++ b/src/ui/ve.ui.Toolbar.js
@@ -11,30 +11,26 @@
  * @extends OO.ui.Toolbar
  *
  * @constructor
- * @param {ve.ui.Surface} surface Surface to control
  * @param {Object} [options] Configuration options
+ * @cfg {boolean} [floatable] Toolbar floats when scrolled off the page
  */
-ve.ui.Toolbar = function VeUiToolbar( surface, options ) {
-       var toolbar = this;
-
-       // Configuration initialization
-       options = options || {};
+ve.ui.Toolbar = function VeUiToolbar( config ) {
+       config = config || {};
 
        // Parent constructor
-       OO.ui.Toolbar.call( this, ve.ui.toolFactory, ve.ui.toolGroupFactory, 
options );
+       OO.ui.Toolbar.call( this, ve.ui.toolFactory, ve.ui.toolGroupFactory, 
config );
 
        // Properties
-       this.surface = surface;
        this.floating = false;
-       this.floatable = false;
+       this.floatable = !!config.floatable;
        this.$window = null;
        this.elementOffset = null;
        this.windowEvents = {
                // Must use Function#bind (or a closure) instead of direct 
reference
                // because we need a unique function references for each 
Toolbar instance
                // to avoid $window.off() from unbinding other toolbars' event 
handlers.
-               resize: toolbar.onWindowResize.bind( toolbar ),
-               scroll: toolbar.onWindowScroll.bind( toolbar )
+               resize: this.onWindowResize.bind( this ),
+               scroll: this.onWindowScroll.bind( this )
        };
        // Default directions
        this.contextDirection = { inline: 'ltr', block: 'ltr' };
@@ -47,12 +43,6 @@
                .addClass( 've-ui-toolbar' )
                .addClass( 've-ui-dir-inline-' + this.contextDirection.inline )
                .addClass( 've-ui-dir-block-' + this.contextDirection.block );
-       // Events
-       this.surface.getModel().connect( this, { contextChange: 
'onContextChange' } );
-       this.surface.toolbarDialogs.connect( this, {
-               opening: 'onToolbarWindowOpeningOrClosing',
-               closing: 'onToolbarWindowOpeningOrClosing'
-       } );
 };
 
 /* Inheritance */
@@ -63,11 +53,30 @@
 
 /**
  * @event updateState
- * @param {ve.dm.SurfaceFragment} fragment Surface fragment
- * @param {Object} direction Context direction with 'inline' & 'block' 
properties
+ * @param {ve.dm.SurfaceFragment|null} fragment Surface fragment. Null if no 
surface is active.
+ * @param {Object|null} direction Context direction with 'inline' & 'block' 
properties if a surface exists. Null if no surface is active.
  */
 
 /* Methods */
+
+/**
+ * inheritdoc
+ */
+ve.ui.Toolbar.prototype.setup = function ( groups, surface ) {
+       this.detach();
+
+       this.surface = surface;
+
+       // Parent method
+       ve.ui.Toolbar.super.prototype.setup.call( this, groups );
+
+       // Events
+       this.getSurface().getModel().connect( this, { contextChange: 
'onContextChange' } );
+       this.getSurface().toolbarDialogs.connect( this, {
+               opening: 'onToolbarWindowOpeningOrClosing',
+               closing: 'onToolbarWindowOpeningOrClosing'
+       } );
+};
 
 /**
  * inheritdoc
@@ -156,8 +165,13 @@
  * Update the state of the tools
  */
 ve.ui.Toolbar.prototype.updateToolState = function () {
+       if ( !this.getSurface() ) {
+               this.emit( 'updateState', null, null );
+               return;
+       }
+
        var dirInline, dirBlock, fragmentAnnotation,
-               fragment = this.surface.getModel().getFragment();
+               fragment = this.getSurface().getModel().getFragment();
 
        // Update context direction for button icons UI.
        // By default, inline and block directions are the same.
@@ -266,13 +280,30 @@
 };
 
 /**
+ * Detach toolbar from surface and all event listeners
+ */
+ve.ui.Toolbar.prototype.detach = function () {
+       this.unfloat();
+
+       // Events
+       if ( this.$window ) {
+               this.$window.off( this.windowEvents );
+       }
+       if ( this.getSurface() ) {
+               this.getSurface().getModel().disconnect( this );
+               this.getSurface().toolbarDialogs.disconnect( this );
+               this.getSurface().toolbarDialogs.clearWindows();
+               this.surface = null;
+       }
+};
+
+/**
  * Destroys toolbar, removing event handlers and DOM elements.
  *
  * Call this whenever you are done using a toolbar.
  */
 ve.ui.Toolbar.prototype.destroy = function () {
-       this.disableFloatable();
-       this.surface.getModel().disconnect( this, { contextChange: 
'onContextChange' } );
+       this.detach();
 
        // Parent method
        OO.ui.Toolbar.prototype.destroy.call( this );
@@ -310,34 +341,4 @@
                this.floating = false;
                this.surface.setToolbarHeight( 0 );
        }
-};
-
-/**
- * Set automatic floating behavior to the toolbar.
- *
- * Toolbar floating is not enabled by default, call this on setup to enable it.
- * This will not make it float, but it will start listening for events that
- * will result in it potentially being floated and defloated accordingly.
- */
-ve.ui.Toolbar.prototype.enableFloatable = function () {
-       this.floatable = true;
-
-       if ( this.initialized ) {
-               this.$window.on( this.windowEvents );
-       }
-};
-
-/**
- * Remove automatic floating behavior to the toolbar.
- */
-ve.ui.Toolbar.prototype.disableFloatable = function () {
-       if ( this.$window ) {
-               this.$window.off( this.windowEvents );
-       }
-
-       if ( this.floating ) {
-               this.unfloat();
-       }
-
-       this.floatable = false;
 };
diff --git a/tests/ve.test.utils.js b/tests/ve.test.utils.js
index b250b63..ae430d5 100644
--- a/tests/ve.test.utils.js
+++ b/tests/ve.test.utils.js
@@ -165,8 +165,8 @@
         * @returns {ve.ui.Surface} UI surface
         */
        ve.test.utils.createSurfaceFromDocument = function ( doc ) {
-               var target = new ve.init.sa.Target( $( '#qunit-fixture' ), doc 
);
-               target.setup( doc );
+               var target = new ve.init.sa.Target( $( '#qunit-fixture' ) );
+               target.addSurface( doc );
                return target.surface;
        };
 

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ic999ec166d8005e0368c0dc95c0a5593e6bf2945
Gerrit-PatchSet: 13
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to