/*
* Copyright (C) eZ Systems AS. All rights reserved.
* For full copyright and license information view LICENSE file distributed with this source code.
*/
/* global CKEDITOR */
YUI.add('ez-alloyeditor-plugin-embed', function (Y) {
"use strict";
var IMAGE_TYPE_CLASS = 'ez-embed-type-image',
DATA_ALIGNMENT_ATTR = 'ezalign';
if (CKEDITOR.plugins.get('ezembed')) {
return;
}
/**
* CKEditor plugin to configure the widget plugin so that it recognizes the
* `div[data-ezelement="embed"]` elements as widget.
*
* @class ezembed
* @namespace CKEDITOR.plugins
* @constructor
*/
CKEDITOR.plugins.add('ezembed', {
requires: 'widget,ezaddcontent',
init: function (editor) {
editor.widgets.add('ezembed', {
defaults: {
href: "ezcontent://57",
content: "home",
view: "embed",
},
draggable: false,
template: '<div data-ezelement="ezembed" data-href="{href}" data-ezview="{view}">{content}</div>',
requiredContent: 'div',
upcast: function (element) {
return (
element.name === 'div' &&
element.attributes['data-ezelement'] === 'ezembed'
);
},
/**
* Insert an `ezembed` widget in the editor. It overrides the
* default implementation to make sure that in the case where an
* embed widget is focused, a new one is added after it.
*
* @method insert
*/
insert: function () {
var element = CKEDITOR.dom.element.createFromHtml(this.template.output(this.defaults)),
wrapper = editor.widgets.wrapElement(element, this.name),
temp = new CKEDITOR.dom.documentFragment(wrapper.getDocument()),
instance;
temp.append(wrapper);
editor.widgets.initOn(element, this.name);
editor.eZ.appendElement(wrapper);
instance = editor.widgets.getByElement(wrapper);
instance.ready = true;
instance.fire('ready');
instance.focus();
},
/**
* It's not possible to *edit* an embed widget in AlloyEditor,
* so `edit` directly calls `insert` instead. This is needed
* because by default, the CKEditor engine calls this method
* when an embed widget has the focus and the `ezembed` command
* is executed. In AlloyEditor, we want to insert a new widget,
* not to `edit` the focused widget as the editing process is
* provided by the style toolbar.
*
* @method edit
*/
edit: function () {
this.insert();
},
init: function () {
this.on('focus', this._fireEditorInteraction);
this._syncAlignment();
this._getEzConfigElement();
this.setWidgetContent('');
this._cancelEditEvents();
},
/**
* Cancels the widget events that trigger the `edit` event as
* an embed widget can not be edited in a *CKEditor way*.
*
* @method _cancelEditEvents
* @private
*/
_cancelEditEvents: function () {
var cancel = function (e) {
e.cancel();
};
this.on('doubleclick', cancel, null, null, 5);
this.on('key', cancel, null, null, 5);
},
/**
* Initializes the alignment on the widget wrapper if the widget
* is aligned.
*
* @method _syncAlignment
* @protected
*/
_syncAlignment: function () {
var align = this.element.data(DATA_ALIGNMENT_ATTR);
if ( align ) {
this._setAlignment(align);
} else {
this._unsetAlignment();
}
},
/**
* Sets the alignment of the embed widget to `type`. The
* alignment is set by adding the `data-ezalign` attribute
* on the widget wrapper and the widget element.
*
* @method _setAlignment
* @protected
* @param {String} type
*/
_setAlignment: function (type) {
this.wrapper.data(DATA_ALIGNMENT_ATTR, type);
this.element.data(DATA_ALIGNMENT_ATTR, type);
},
/**
* Sets the alignment of the embed widget to `type` and fires
* the corresponding `editorInteraction` event.
*
* @method setAlignment
* @param {String} type
*/
setAlignment: function (type, fireEvent) {
this._setAlignment(type);
this._fireEditorInteraction('setAlignment' + type);
},
/**
* Removes the alignment of the widget.
*
* @method _unsetAlignment
* @protected
*/
_unsetAlignment: function () {
this.wrapper.data(DATA_ALIGNMENT_ATTR, false);
this.element.data(DATA_ALIGNMENT_ATTR, false);
},
/**
* Removes the alignment of the widget and fires the
* corresponding `editorInteraction` event.
*
* @method unsetAlignment
*/
unsetAlignment: function () {
this._unsetAlignment();
this._fireEditorInteraction('unsetAlignment');
},
/**
* Checks whether the embed is aligned with `type` alignment.
*
* @method isAligned
* @param {String} type
* @return {Boolean}
*/
isAligned: function (type) {
return (this.wrapper.data(DATA_ALIGNMENT_ATTR) === type);
},
/**
* Set the embed as an embed representing an image
*
* @method setImageType
* @return {CKEDITOR.plugins.widget}
*/
setImageType: function () {
this.element.addClass(IMAGE_TYPE_CLASS);
return this;
},
/**
* Check whether the embed widget represents an image or not.
*
* @method isImage
* @return {Boolean}
*/
isImage: function () {
return this.element.hasClass(IMAGE_TYPE_CLASS);
},
/**
* Sets the `href` of the embed is URI to the embed content or
* location. (ezcontent://32 for instance).
*
* @method setHref
* @param {String} href
* @return {CKEDITOR.plugins.widget}
*/
setHref: function (href) {
this.element.data('href', href);
return this;
},
/**
* Returns the `href`of the embed.
*
* @method getHref
* @return {String}
*/
getHref: function () {
return this.element.data('href');
},
/**
* Sets the widget content. It makes sure the config element is
* not overwritten.
*
* @method setWidgetContent
* @param {String|CKEDITOR.dom.node} content
* @return {CKEDITOR.plugins.widget}
*/
setWidgetContent: function (content) {
var element = this.element.getFirst(), next;
while ( element ) {
next = element.getNext();
if ( !element.data || !element.data('ezelement') ) {
element.remove();
}
element = next;
}
if ( content instanceof CKEDITOR.dom.node ) {
this.element.append(content);
} else {
this.element.appendText(content);
}
return this;
},
/**
* Moves the widget after the given element. It also fires the
* `editorInteraction` event so that the UI can respond to that
* change.
*
* @method moveAfter
* @param {CKEDITOR.dom.element} element
*/
moveAfter: function (element) {
this.wrapper.insertAfter(element);
this._fireEditorInteraction('moveAfter');
},
/**
* Moves the widget before the given element. It also fires the
* `editorInteraction` event so that the UI can respond to that
* change.
*
* @method moveAfter
* @param {CKEDITOR.dom.element} element
*/
moveBefore: function (element) {
this.wrapper.insertBefore(element);
this._fireEditorInteraction('moveBefore');
},
/**
* Sets a config value under the `key` for the embed.
*
* @method setConfig
* @param {String} key
* @param {String} value
* @return {CKEDITOR.plugins.widget}
*/
setConfig: function (key, value) {
var valueElement = this._getValueElement(key);
if ( !valueElement ) {
valueElement = new CKEDITOR.dom.element('span');
valueElement.data('ezelement', 'ezvalue');
valueElement.data('ezvalue-key', key);
this._getEzConfigElement().append(valueElement);
}
valueElement.setText(value);
return this;
},
/**
* Returns the config value for the `key` or undefined if the
* config key is not found.
*
* @method getConfig
* @return {String}
*/
getConfig: function (key) {
var valueElement = this._getValueElement(key);
return valueElement ? valueElement.getText() : undefined;
},
/**
* Returns the Element holding the config under `key`
*
* @method _getValueElement
* @param {String} key
* @return {CKEDITOR.dom.element}
*/
_getValueElement: function (key) {
return this.element.findOne('[data-ezelement="ezvalue"][data-ezvalue-key="' + key + '"]');
},
/**
* Returns the element used as a container the config values. if
* it does not exist, it is created.
*
* @method _getEzConfigElement
* @private
* @return {CKEDITOR.dom.element}
*/
_getEzConfigElement: function () {
var config = this.element.findOne('[data-ezelement="ezconfig"]');
if ( !config ) {
config = new CKEDITOR.dom.element('span');
config.data('ezelement', 'ezconfig');
this.element.append(config, true);
}
return config;
},
/**
* Fires the editorInteraction event so that AlloyEditor editor
* UI remains visible and is updated. This method also computes
* `selectionData.region` and the `pageX` and `pageY` properties
* so that the add toolbar is correctly positioned on the
* widget.
*
* @method _fireEditorInteraction
* @protected
* @param {Object|String} evt this initial event info object or
* the event name for which the `editorInteraction` is fired.
*/
_fireEditorInteraction: function (evt) {
var wrapperRegion = this._getWrapperRegion(),
name = evt.name || evt,
e = {
editor: editor,
target: this.element.$,
name: "widget" + name,
pageX: wrapperRegion.left,
pageY: wrapperRegion.top + wrapperRegion.height,
};
editor.focus();
this.focus();
editor.fire('editorInteraction', {
nativeEvent: e,
selectionData: {
element: this.element,
region: wrapperRegion,
},
});
},
/**
* Returns the wrapper element region.
*
* @method _getWrapperRegion
* @private
* @return {Object}
*/
_getWrapperRegion: function () {
var scroll = this.wrapper.getWindow().getScrollPosition(),
region = this.wrapper.getClientRect();
region.top += scroll.y;
region.bottom += scroll.y;
region.left += scroll.x;
region.right += scroll.x;
region.direction = CKEDITOR.SELECTION_TOP_TO_BOTTOM;
return region;
},
});
},
});
});