I have grapesjs editor with mjml plugin and I decided to mark some components as not removable to protect users from remove critical parts of email template.
Sugested way how to do this set attribute of mjml element like this:
<div data-gjs-removable="false">...</div>
But it does not work for <mjml> and <mj-body> tags.
Can I do this after load a template?
yes you can.
After init of editor you need to traverse ComponentDom and set componenets to not removable. Do not forget to remove button from toolbox - it will not work anyway.
const editor = grapesJS.init({...});
editor.on('load', () => {
const notRemovableTags = ['mjml', 'mj-body'];
// recursive function to traverse component tree
const updateRecursive = componentModel => {
if (notRemovableTags.indexOf(componentModel.attributes.tagName) !== -1) {
// set not removable
componentModel.set({removable: false});
// remove remove icon from toolbar
componentModel.set({
toolbar: componentModel.get('toolbar')?.filter(tlb => tlb.command !== 'tlb-delete')
});
}
// recurse
componentModel.get('components').each(model => updateRecursive(model));
}
// start recursion
updateRecursive(this.editor.DomComponents.getComponent());
});
I'm working in Angular 8, jqgrid working perfectly.
Is there a way I can alter the drag and drop image when DnD between two grids?
I'm playing with:
this.jqgSource.jqGrid(
'gridDnD',
{
connectWith: this.jqgSelectorTarget,
onstart: (ev, ui) => {
$(this).html("new content here");
},
ondrop: (ev, ui, dragdata) => {
const registrationId = dragdata.registrationId;
const moveToTeamId = this.targetTeamId;
this.swapComplete(registrationId, moveToTeamId);
}
});
but no luck...
Oleg got me going.
To change the content of the dragged object
Instead of:
$(this).html("new content here")
I needed to access the ui.helper:
ui.helper.html("new content here")
I have a menu item named as Transform table after clicking on it more items are shown for selection. These are all present inside an iframe. I want to click on the options available inside Transform Table, but when I am searching for the options the main pop gets closed. Please suggest some way to keep it open and the select the options in it.
const body = $iframe.contents().find('body');
let getElement = cy.wrap(body);
getElement.find(getdatasourcelocator.SearchBox).should('be.visible', { timeout: 30000 }).type(datasourceType);
getElement = cy.wrap(body);
getElement.find('.textCsv').click();
getElement = cy.wrap(body);
getElement.find(getdatasourcelocator.URL).type(entityURL);
getElement = cy.wrap(body);
getElement.find('button').contains('Next').click();
getElement = cy.wrap(body);
getElement.wait(15000);
getElement.find('button').contains('Transform table').click().then(()=>{
cy.get('button[data-automation-id="PromoteHeaders"]').should('be.visible');
cy.contains('li','Use first row as headers').should('be.visible', { timeout: 30000 }).click()
});
});
}```
Unfortunately, you cannot target elements or interact with anything in an iframe - regardless of it being the same domain or cross-domain iframe. Here is a known issue.
As workaround, you can try to use something from the comments:
Add custom command.
Cypress.Commands.add('iframe', { prevSubject: 'element' }, $iframe => {
return new Cypress.Promise(resolve => {
$iframe.on('load', () => {
resolve($iframe.contents().find('body'));
});
});
});
And use this command.
// for <iframe id="foo" src="bar.html"></iframe>
cy.get('#foo').iframe().find('.bar').should('contain', 'Success!');
I'm currently creating a 'smartobject' widget. In the widgets dialog, the user can choose a 'smartobject', which simply put, generates some html, which should be added to the editor. Here comes the tricky part: the html sometimes div elements and sometimes simply span elements. In the case of the div variant, the widget should be wrapped in a div 'template'. In the case of a span variant, the widget should be wrapped in a span and the html should be added 'inline'.
In the widgets API I see the following way to define a template:
editor.widgets.add('smartobject', {
dialog: 'smartobject',
pathName: lang.pathName,
template: '<div class="cke_smartobject"></div>', // <------
upcast: function(element) {
return element.hasClass('smartObject');
},
init: function() {
this.setData('editorHtml', this.element.getOuterHtml());
},
data: function() {
var editorHtml = this.data.editorHtml;
var newElement = new CKEDITOR.dom.element.createFromHtml(editorHtml);
newElement.copyAttributes(this.element);
this.element.setText(newElement.getText());
}
});
But in my case, the template is more dynamic: sometimes a div and sometimes the span will do the correct thing..
How can I fix this without needing to create two widgets which will do the exact same thing, with only the wrapping element as difference?
I've already tried to replace the entire element in the 'data' method, like:
newElement.replace(this.element);
this.element = newElement;
But this seemed not supported: resulted in undefined errors after calling editor.getData().
I'm using ckeditor v4.5.9
Thanks for your help!
It seems I got it working (with a workaround).
The code:
CKEDITOR.dialog.add('smartobject', this.path + 'dialogs/smartobject.js');
editor.widgets.add('smartobject', {
pathName: lang.pathName,
// This template is needed, to activate the widget logic, but does nothing.
// The entire widgets html is defined and created in the dialog.
template: '<div class="cke_smartobject"></div>',
init: function() {
var widget = this;
widget.on('doubleclick', function(evt) {
editor.execCommand('smartobject');
}, null, null, 5);
},
upcast: function(element) {
return element.hasClass('smartObject');
}
});
// Add a custom command, instead of using the default widget command,
// otherwise multiple smartobject variants (div / span / img) are not supported.
editor.addCommand('smartobject', new CKEDITOR.dialogCommand('smartobject'));
editor.ui.addButton && editor.ui.addButton('CreateSmartobject', {
label: lang.toolbar,
command: 'smartobject',
toolbar: 'insert,5',
icon: 'smartobject'
});
And in the dialog, to insert code looks like:
return {
title: lang.title,
minWidth: 300,
minHeight: 80,
onOk: function() {
var element = CKEDITOR.dom.element.createFromHtml(smartobjectEditorHtml);
editor.insertElement(element);
// Trigge the setData method, so the widget html is transformed,
// to an actual widget!
editor.setData(editor.getData());
},
...etc.
UPDATE
I made the 'onOk' method a little bit better: the smartobject element is now selected after the insertion.
onOk: function() {
var element = CKEDITOR.dom.element.createFromHtml(smartobjectEditorHtml);
var elementId = "ckeditor-element-" + element.getUniqueId();
element.setAttribute("id", elementId);
editor.insertElement(element);
// Trigger the setData method, so the widget html is transformed,
// to an actual widget!
editor.setData(editor.getData());
// Get the element 'fresh' by it's ID, because the setData method,
// makes the element change into a widget, and thats the element which should be selected,
// after adding.
var refreshedElement = CKEDITOR.document.getById(elementId);
var widgetWrapperElement = CKEDITOR.document.getById(elementId).getParent();
// Better safe then sorry: if the fresh element doesn't have a parent, simply select the element itself.
var elementToSelect = widgetWrapperElement != null ? widgetWrapperElement : refreshedElement;
// Normally the 'insertElement' makes sure the inserted element is selected,
// but because we call the setData method (to ensure the element is transformed to a widget)
// the selection is cleared and the cursor points to the start of the editor.
editor.getSelection().selectElement(elementToSelect);
},
So in short, I partially used the widget API for the parts I wanted:
- Make the html of the widget not editable
- Make it moveable
But I created a custom dialog command, which simply bypasses the default widget insertion, so I can entirely decide my own html structure for the widget.
All seems to work like this.
Any suggestions, to make it better are appreciated:)!
As suggested in this ckeditor forum thread, the best approach would be to set the template to include all possible content elements. Then, in the data function, remove the unnecessary parts according to your specific logic.
I'm building a simple dialog plugin to replace the default link tool. The design calls for a particular layout that is difficult to achieve with the CKEdit dialog definition: We want a single field to appear above the tab elements in the dialog (see illustration).
Can anyone suggest a way that this might be implemented? Thanks!
As far as I can tell it is not possible to achieve this using the built-in dialog definition.
I was able to get around this limitation by building my dialog plugin using the iframedialog plugin. This basically pops up a CKEditor dialog window and loads an external URL into it. You can do anything you want in that iframe, and then return the text to CKEditor when the user presses the OK button.
A simple example:
// plugins/iframelink/plugin.js
CKEDITOR.plugins.add('iframelink', {
requires: ['iframedialog'],
init: function(editor){
CKEDITOR.dialog.addIframe('iframelinkDialog',
// title
'Insert a Link',
// src
this.path + 'dialogs/link.html',
// minWidth
500,
// minHeight
250,
// onContentLoad
);
var cmd = editor.addCommand('iframelink', {exec: iframelinkOnclick});
editor.ui.addButton('iframelink', {
label: 'Insert a Link (Special Link Tool)',
command: 'iframelink',
icon: this.path + 'images/world_link.png'
});
}
});
function iframelinkOnclick(editor){
dialog = editor.openDialog('msiteslinkDialog');
};
// plugins/iframelink/dialogs/iframelink.js
$(function() {
if (typeof(window.parent.CKEDITOR) != 'undefined') {
CKEDITOR = window.parent.CKEDITOR;
var dialog = CKEDITOR.dialog.getCurrent();
var editor = dialog.getParentEditor();
// Get value of the selected text:
var selection = editor.getSelection().getSelectedText();
// Do something when the user presses the OK button:
var okListener = function(ev) {
link = yourFunctionToDoSomethingClever();
this._.editor.insertHtml(link);
dialog.removeListener("ok", okListener);
};
// Bind the OK button to your okListener method:
dialog.on("ok", okListener);
};
}
So you can make the dialog look any way you want: