3 * Drupal Entity embed plugin.
6 (function ($, Drupal, CKEDITOR) {
10 CKEDITOR.plugins.add('drupalentity', {
11 // This plugin requires the Widgets System defined in the 'widget' plugin.
14 // The plugin initialization logic goes inside this method.
15 beforeInit: function (editor) {
16 // Configure CKEditor DTD for custom drupal-entity element.
17 // @see https://www.drupal.org/node/2448449#comment-9717735
18 var dtd = CKEDITOR.dtd, tagName;
19 dtd['drupal-entity'] = {'#': 1};
20 // Register drupal-entity element as allowed child, in each tag that can
21 // contain a div element.
22 for (tagName in dtd) {
23 if (dtd[tagName].div) {
24 dtd[tagName]['drupal-entity'] = 1;
28 // Generic command for adding/editing entities of all types.
29 editor.addCommand('editdrupalentity', {
30 allowedContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
31 requiredContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
32 modes: { wysiwyg : 1 },
34 exec: function (editor, data) {
37 var existingElement = getSelectedEmbeddedEntity(editor);
39 var existingValues = {};
40 if (existingElement && existingElement.$ && existingElement.$.firstChild) {
41 var embedDOMElement = existingElement.$.firstChild;
42 // Populate array with the entity's current attributes.
43 var attribute = null, attributeName;
44 for (var key = 0; key < embedDOMElement.attributes.length; key++) {
45 attribute = embedDOMElement.attributes.item(key);
46 attributeName = attribute.nodeName.toLowerCase();
47 if (attributeName.substring(0, 15) === 'data-cke-saved-') {
50 existingValues[attributeName] = existingElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
54 var embed_button_id = data.id ? data.id : existingValues['data-embed-button'];
56 var dialogSettings = {
57 dialogClass: 'entity-select-dialog',
61 var saveCallback = function (values) {
62 var entityElement = editor.document.createElement('drupal-entity');
63 var attributes = values.attributes;
64 for (var key in attributes) {
65 entityElement.setAttribute(key, attributes[key]);
68 editor.insertHtml(entityElement.getOuterHtml());
69 if (existingElement) {
70 // Detach the behaviors that were attached when the entity content
72 Drupal.runEmbedBehaviors('detach', existingElement.$);
73 existingElement.remove();
77 // Open the entity embed dialog for corresponding EmbedButton.
78 Drupal.ckeditor.openDialog(editor, Drupal.url('entity-embed/dialog/' + editor.config.drupal.format + '/' + embed_button_id), existingValues, saveCallback, dialogSettings);
82 // Register the entity embed widget.
83 editor.widgets.add('drupalentity', {
84 // Minimum HTML which is required by this widget to work.
85 allowedContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
86 requiredContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
88 // Simply recognize the element as our own. The inner markup if fetched
89 // and inserted the init() callback, since it requires the actual DOM
91 upcast: function (element) {
92 var attributes = element.attributes;
93 if (attributes['data-entity-type'] === undefined || (attributes['data-entity-id'] === undefined && attributes['data-entity-uuid'] === undefined) || (attributes['data-view-mode'] === undefined && attributes['data-entity-embed-display'] === undefined)) {
96 // Generate an ID for the element, so that we can use the Ajax
98 element.attributes.id = generateEmbedId();
102 // Fetch the rendered entity.
104 /** @type {CKEDITOR.dom.element} */
105 var element = this.element;
106 // Use the Ajax framework to fetch the HTML, so that we can retrieve
107 // out-of-band assets (JS, CSS...).
108 var entityEmbedPreview = Drupal.ajax({
109 base: element.getId(),
111 url: Drupal.url('embed/preview/' + editor.config.drupal.format + '?' + $.param({
112 value: element.getOuterHtml()
114 progress: {type: 'none'},
115 // Use a custom event to trigger the call.
116 event: 'entity_embed_dummy_event'
118 entityEmbedPreview.execute();
121 // Downcast the element.
122 downcast: function (element) {
123 // Only keep the wrapping element.
125 // Remove the auto-generated ID.
126 delete element.attributes.id;
131 // Register the toolbar buttons.
132 if (editor.ui.addButton) {
133 for (var key in editor.config.DrupalEntity_buttons) {
134 var button = editor.config.DrupalEntity_buttons[key];
135 editor.ui.addButton(button.id, {
138 allowedContent: 'drupal-entity[!data-entity-type,!data-entity-uuid,!data-entity-embed-display,!data-entity-embed-display-settings,!data-align,!data-caption,!data-embed-button]',
139 click: function(editor) {
140 editor.execCommand('editdrupalentity', this.data);
147 // Register context menu option for editing widget.
148 if (editor.contextMenu) {
149 editor.addMenuGroup('drupalentity');
150 editor.addMenuItem('drupalentity', {
151 label: Drupal.t('Edit Entity'),
152 icon: this.path + 'entity.png',
153 command: 'editdrupalentity',
154 group: 'drupalentity'
157 editor.contextMenu.addListener(function(element) {
158 if (isEditableEntityWidget(editor, element)) {
159 return { drupalentity: CKEDITOR.TRISTATE_OFF };
164 // Execute widget editing action on double click.
165 editor.on('doubleclick', function (evt) {
166 var element = getSelectedEmbeddedEntity(editor) || evt.data.element;
168 if (isEditableEntityWidget(editor, element)) {
169 editor.execCommand('editdrupalentity');
176 * Get the surrounding drupalentity widget element.
178 * @param {CKEDITOR.editor} editor
180 function getSelectedEmbeddedEntity(editor) {
181 var selection = editor.getSelection();
182 var selectedElement = selection.getSelectedElement();
183 if (isEditableEntityWidget(editor, selectedElement)) {
184 return selectedElement;
191 * Checks if the given element is an editable drupalentity widget.
193 * @param {CKEDITOR.editor} editor
194 * @param {CKEDITOR.htmlParser.element} element
196 function isEditableEntityWidget (editor, element) {
197 var widget = editor.widgets.getByElement(element, true);
198 if (!widget || widget.name !== 'drupalentity') {
202 var button = $(element.$.firstChild).attr('data-embed-button');
204 // If there was no data-embed-button attribute, not editable.
208 // The button itself must be valid.
209 return editor.config.DrupalEntity_buttons.hasOwnProperty(button);
213 * Generates unique HTML IDs for the widgets.
217 function generateEmbedId() {
218 if (typeof generateEmbedId.counter == 'undefined') {
219 generateEmbedId.counter = 0;
221 return 'entity-embed-' + generateEmbedId.counter++;
224 })(jQuery, Drupal, CKEDITOR);