2013-04-18 20:54:37 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor ContentEditable FocusableNode class.
|
|
|
|
*
|
|
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2013-08-19 15:22:29 +00:00
|
|
|
* ContentEditable focusable node.
|
2013-04-18 20:54:37 +00:00
|
|
|
*
|
2013-05-15 23:31:02 +00:00
|
|
|
* Focusable elements have a special treatment by ve.ce.Surface. When the user selects only a single
|
|
|
|
* node, if it is focusable, the surface will set the focusable node's focused state. Other systems,
|
|
|
|
* such as the context, may also use a focusable node's $focusable property as a hint of where the
|
|
|
|
* primary element in the node is. Typically, and by default, the primary element is the root
|
|
|
|
* element, but in some cases it may need to be configured to be a specific child element within the
|
|
|
|
* node's DOM rendering.
|
|
|
|
*
|
2013-06-05 20:59:19 +00:00
|
|
|
* If your focusable node changes size and the highlight must be redrawn, call redrawHighlight().
|
2013-10-14 11:02:08 +00:00
|
|
|
* 'resizeEnd' and 'rerender' are already bound to call this.
|
2013-06-05 20:59:19 +00:00
|
|
|
*
|
2013-04-18 20:54:37 +00:00
|
|
|
* @class
|
|
|
|
* @abstract
|
|
|
|
*
|
|
|
|
* @constructor
|
2013-08-19 15:22:29 +00:00
|
|
|
* @param {jQuery} [$focusable=this.$] Primary element user is focusing on
|
2013-04-18 20:54:37 +00:00
|
|
|
*/
|
2013-05-15 23:31:02 +00:00
|
|
|
ve.ce.FocusableNode = function VeCeFocusableNode( $focusable ) {
|
2013-04-18 20:54:37 +00:00
|
|
|
// Properties
|
|
|
|
this.focused = false;
|
2013-05-15 23:31:02 +00:00
|
|
|
this.$focusable = $focusable || this.$;
|
2013-06-05 20:59:19 +00:00
|
|
|
this.$highlights = $( [] );
|
|
|
|
this.surface = null;
|
|
|
|
|
|
|
|
// Events
|
|
|
|
this.connect( this, {
|
|
|
|
'setup': 'onFocusableSetup',
|
2013-10-14 11:44:26 +00:00
|
|
|
'resizeEnd': 'onFocusableResizeEnd',
|
|
|
|
'resizing': 'onFocusableResizing',
|
2013-07-01 20:23:51 +00:00
|
|
|
'rerender': 'onFocusableRerender',
|
|
|
|
'live': 'onFocusableLive'
|
2013-06-05 20:59:19 +00:00
|
|
|
} );
|
2013-04-18 20:54:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Events */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event focus
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event blur
|
|
|
|
*/
|
|
|
|
|
2013-10-11 13:59:04 +00:00
|
|
|
/* Static Methods */
|
|
|
|
|
|
|
|
ve.ce.FocusableNode.static = {};
|
|
|
|
|
|
|
|
ve.ce.FocusableNode.static.isFocusable = true;
|
|
|
|
|
2013-04-18 20:54:37 +00:00
|
|
|
/* Methods */
|
|
|
|
|
2013-06-05 20:59:19 +00:00
|
|
|
/**
|
|
|
|
* Handle setup event.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.onFocusableSetup = function () {
|
|
|
|
this.surface = this.root.getSurface();
|
|
|
|
};
|
|
|
|
|
2013-07-01 20:23:51 +00:00
|
|
|
/**
|
|
|
|
* Handle node live.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.onFocusableLive = function () {
|
|
|
|
var surfaceModel = this.root.getSurface().getModel();
|
|
|
|
|
|
|
|
if ( this.live ) {
|
|
|
|
surfaceModel.connect( this, { 'history': 'onFocusableHistory' } );
|
|
|
|
} else {
|
|
|
|
surfaceModel.disconnect( this, { 'history': 'onFocusableHistory' } );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle history event.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.onFocusableHistory = function () {
|
|
|
|
if ( this.focused ) {
|
|
|
|
this.redrawHighlight();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-06-05 20:59:19 +00:00
|
|
|
/**
|
2013-10-14 11:44:26 +00:00
|
|
|
* Handle resize end event.
|
2013-06-05 20:59:19 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
2013-10-14 11:44:26 +00:00
|
|
|
ve.ce.FocusableNode.prototype.onFocusableResizeEnd = function () {
|
2013-07-01 20:23:51 +00:00
|
|
|
if ( this.focused ) {
|
2013-06-19 15:04:02 +00:00
|
|
|
this.redrawHighlight();
|
2013-10-14 11:44:26 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle resizing event.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.onFocusableResizing = function () {
|
|
|
|
if ( this.focused && !this.outline ) {
|
|
|
|
this.redrawHighlight();
|
2013-06-19 15:04:02 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle rerender event.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.onFocusableRerender = function () {
|
2013-07-01 20:23:51 +00:00
|
|
|
if ( this.focused ) {
|
2013-06-19 15:04:02 +00:00
|
|
|
this.redrawHighlight();
|
2013-08-15 09:43:31 +00:00
|
|
|
// reposition menu
|
2013-10-23 13:35:55 +00:00
|
|
|
this.surface.getSurface().getContext().update( true, true );
|
2013-06-19 15:04:02 +00:00
|
|
|
}
|
2013-06-05 20:59:19 +00:00
|
|
|
};
|
|
|
|
|
2013-04-18 20:54:37 +00:00
|
|
|
/**
|
|
|
|
* Check if node is focused.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {boolean} Node is focused
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.isFocused = function () {
|
|
|
|
return this.focused;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the selected state of the node.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {boolean} value Node is focused
|
2013-10-22 17:54:59 +00:00
|
|
|
* @fires focus
|
|
|
|
* @fires blur
|
2013-04-18 20:54:37 +00:00
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.setFocused = function ( value ) {
|
|
|
|
value = !!value;
|
|
|
|
if ( this.focused !== value ) {
|
|
|
|
this.focused = value;
|
|
|
|
if ( this.focused ) {
|
|
|
|
this.emit( 'focus' );
|
2013-08-19 15:22:29 +00:00
|
|
|
this.$focusable.addClass( 've-ce-node-focused' );
|
2013-06-05 20:59:19 +00:00
|
|
|
this.createHighlight();
|
2013-04-18 20:54:37 +00:00
|
|
|
} else {
|
|
|
|
this.emit( 'blur' );
|
2013-08-19 15:22:29 +00:00
|
|
|
this.$focusable.removeClass( 've-ce-node-focused' );
|
2013-06-05 20:59:19 +00:00
|
|
|
this.clearHighlight();
|
2013-04-18 20:54:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2013-06-05 20:59:19 +00:00
|
|
|
|
|
|
|
/**
|
2013-06-19 15:04:02 +00:00
|
|
|
* Creates highlight.
|
2013-06-05 20:59:19 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.createHighlight = function () {
|
2013-08-19 15:22:29 +00:00
|
|
|
this.$focusable.find( '*' ).add( this.$focusable ).each(
|
2013-06-05 20:59:19 +00:00
|
|
|
ve.bind( function( i, element ) {
|
2013-06-27 17:25:28 +00:00
|
|
|
var offset, $element = $( element );
|
2013-07-03 20:49:11 +00:00
|
|
|
if ( !$element.is( ':visible' ) ) {
|
2013-06-28 20:46:08 +00:00
|
|
|
return true;
|
|
|
|
}
|
2013-10-09 20:09:59 +00:00
|
|
|
offset = OO.ui.Element.getRelativePosition(
|
2013-06-27 17:25:28 +00:00
|
|
|
$element, this.getRoot().getSurface().getSurface().$
|
|
|
|
);
|
2013-06-05 20:59:19 +00:00
|
|
|
this.$highlights = this.$highlights.add(
|
|
|
|
$( '<div>' )
|
|
|
|
.css( {
|
2013-06-28 22:03:12 +00:00
|
|
|
height: $element.height(),
|
|
|
|
width: $element.width(),
|
2013-06-27 17:25:28 +00:00
|
|
|
top: offset.top,
|
|
|
|
left: offset.left
|
2013-06-05 20:59:19 +00:00
|
|
|
} )
|
|
|
|
.addClass( 've-ce-focusableNode-highlight' )
|
|
|
|
);
|
|
|
|
}, this )
|
|
|
|
);
|
|
|
|
|
|
|
|
this.surface.replaceHighlight( this.$highlights );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2013-06-19 15:04:02 +00:00
|
|
|
* Clears highlight.
|
2013-06-05 20:59:19 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.clearHighlight = function () {
|
|
|
|
this.$highlights = $( [] );
|
|
|
|
this.surface.replaceHighlight( null );
|
|
|
|
};
|
2013-06-19 15:04:02 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Redraws highlight.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ce.FocusableNode.prototype.redrawHighlight = function () {
|
|
|
|
this.clearHighlight();
|
|
|
|
this.createHighlight();
|
|
|
|
};
|