(function () {
'use strict';
/**
* The ToolbarAdd class provides functionality for adding content to the editor.
*
* @uses WidgetDropdown
* @uses WidgetExclusive
* @uses WidgetFocusManager
* @uses ToolbarButtons
* @uses WidgetPosition
* @uses WidgetArrowBox
*
* @class ToolbarAdd
*/
var ToolbarAdd = React.createClass({
mixins: [AlloyEditor.WidgetDropdown, AlloyEditor.WidgetExclusive, AlloyEditor.WidgetFocusManager, AlloyEditor.ToolbarButtons, AlloyEditor.WidgetPosition, AlloyEditor.WidgetArrowBox],
// Allows validating props being passed to the component.
propTypes: {
/**
* The toolbar configuration.
*
* @property {Object} config
*/
config: React.PropTypes.object,
/**
* The editor instance where the component is being used.
*
* @property {Object} editor
*/
editor: React.PropTypes.object.isRequired,
/**
* The payload from "editorInteraction" event
*
* @property {Object} editorEvent
*/
editorEvent: React.PropTypes.object,
/**
* The gutter to be applied to the widget when rendered in exclusive mode
*
* @property {Object} gutterExclusive
*/
gutterExclusive: React.PropTypes.object,
/**
* The label that should be used for accessibility purposes.
*
* @property {String} label
*/
label: React.PropTypes.string,
/**
* Provides a callback which should be executed when a dismiss key is pressed over a toolbar to return the focus to the editor.
*
* @property {Function} onDismiss
*/
onDismiss: React.PropTypes.func,
/**
* The data, returned from {{#crossLink "CKEDITOR.plugins.selectionregion/getSelectionData:method"}}{{/crossLink}}
*
* @property {Object} selectionData
*/
selectionData: React.PropTypes.object
},
// Lifecycle. Provides static properties to the widget.
statics: {
/**
* The name which will be used as an alias of the button in the configuration.
*
* @static
* @property {String} key
* @default add
*/
key: 'add'
},
/**
* Lifecycle. Returns the default values of the properties used in the widget.
*
* @method getDefaultProps
* @return {Object} The default properties.
*/
getDefaultProps: function() {
return {
circular: true,
descendants: '.ae-button',
gutterExclusive: {
left: 10,
top: 0
},
keys: {
dismiss: [27],
next: [39, 40],
prev: [37, 38]
}
};
},
/**
* Lifecycle. Invoked once, only on the client (not on the server),
* immediately after the initial rendering occurs.
*
* @method componentDidMount
*/
componentDidMount: function () {
this._updatePosition();
},
/**
* Lifecycle. Invoked immediately after the component's updates are flushed to the DOM.
* This method is not called for the initial render.
*
* @method componentDidUpdate
* @param {Object} prevProps The previous state of the component's properties.
* @param {Object} prevState Component's previous state.
*/
componentDidUpdate: function (prevProps, prevState) {
this._updatePosition();
// In case of exclusive rendering, focus the first descendant (button)
// so the user will be able to start interacting with the buttons immediately.
if (this.props.renderExclusive) {
this.focus();
}
},
/**
* Lifecycle. Renders the buttons for adding content or hides the toolbar
* if user interacted with a non-editable element.
*
* @method render
* @return {Object|null} The content which should be rendered.
*/
render: function() {
if (this.props.editorEvent && !this.props.editorEvent.data.nativeEvent.target.isContentEditable) {
return null;
}
var buttons = this._getButtons();
var className = this._getToolbarClassName();
return (
<div aria-label={AlloyEditor.Strings.add} className={className} data-tabindex={this.props.config.tabIndex || 0} onFocus={this.focus} onKeyDown={this.handleKey} role="toolbar" tabIndex="-1">
<div className="ae-container">
{buttons}
</div>
</div>
);
},
/**
* Returns a list of buttons that will eventually render to HTML.
*
* @protected
* @method _getButtons
* @return {Object} The buttons which have to be rendered.
*/
_getButtons: function() {
var buttons;
if (this.props.renderExclusive) {
buttons = this.getToolbarButtons(this.props.config.buttons);
} else {
if (this.props.selectionData && this.props.selectionData.region) {
buttons = (
<button aria-label={AlloyEditor.Strings.add} className="ae-button ae-button-add" onClick={this.props.requestExclusive.bind(this, ToolbarAdd.key)} title={AlloyEditor.Strings.add}>
<span className="ae-icon-add"></span>
</button>
);
}
}
return buttons;
},
/**
* Returns the class name of the toolbar in case of both exclusive and normal mode.
*
* @protected
* @method _getToolbarClassName
* @return {String} The class name which have to be applied to the DOM element.
*/
_getToolbarClassName: function() {
var cssClass = 'ae-toolbar-add';
if (this.props.renderExclusive) {
cssClass = 'ae-toolbar ' + this.getArrowBoxClasses();
}
return cssClass;
},
/**
* Calculates and sets the position of the toolbar in exclusive or normal mode.
*
* @protected
* @method _updatePosition
*/
_updatePosition: function() {
// If component is not mounted, there is nothing to do
if (!React.findDOMNode(this)) {
return;
}
if (this.props.renderExclusive) {
this.updatePosition();
this.show();
var region;
} else {
if (this.props.selectionData) {
region = this.props.selectionData.region;
}
if (region) {
var domNode = React.findDOMNode(this);
var domElement = new CKEDITOR.dom.element(domNode);
var startRect = region.startRect || region;
var left = this.props.editor.get('nativeEditor').editable().getClientRect().left;
domNode.style.left = left - domNode.offsetWidth - this.props.gutterExclusive.left + 'px';
domNode.style.top = Math.floor(region.top - domNode.offsetHeight/2 + startRect.height/2) + 'px';
domNode.style.opacity = 1;
domElement.removeClass('ae-arrow-box');
this.cancelAnimation();
}
}
}
});
AlloyEditor.Toolbars[ToolbarAdd.key] = AlloyEditor.ToolbarAdd = ToolbarAdd;
}());