Replace the image plugin in CKeditor - image

I want to override the image plugin in CKeditor. When I right click on an image I want to open my own dialog. Can anyone point me in the right direction. I've done a basic plugin which I copied from the CKeditor site - How do I swap this to replace the image editor.
CKEDITOR.plugins.add('myplugin',
{
init: function (editor) {
editor.addCommand('mydialog', new CKEDITOR.dialogCommand('mydialog'));
if (editor.contextMenu) {
editor.addMenuGroup('mygroup', 10);
editor.addMenuItem('My Dialog',
{
label: 'Open dialog',
command: 'mydialog',
group: 'mygroup'
});
editor.contextMenu.addListener(function (element) {
return { 'My Dialog': CKEDITOR.TRISTATE_OFF };
});
}
CKEDITOR.dialog.add('mydialog', function (api) {
// CKEDITOR.dialog.definition
var dialogDefinition =
{
title: 'Sample dialog',
minWidth: 390,
minHeight: 130,
contents: [
{
id: 'tab1',
label: 'Label',
title: 'Title',
expand: true,
padding: 0,
elements:
[
{
type: 'html',
html: '<p>This is some sample HTML content.</p>'
},
{
type: 'textarea',
id: 'textareaId',
rows: 4,
cols: 40
}
]
}
],
buttons: [CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton],
onOk: function () {
// "this" is now a CKEDITOR.dialog object.
// Accessing dialog elements:
var textareaObj = this.getContentElement('tab1', 'textareaId');
alert("You have entered: " + textareaObj.getValue());
}
};
return dialogDefinition;
});
}
});

Hi the reason I wanted to do this was that we have our image editor control which for "usability" reasons we need to carry on using. It gets used in different bits of the site and two dialogs would confuse people. In summary what I did was
Remove the image plugin CKEDITOR.config.removePlugins = 'image, forms, div,flash,iframe,table';
Add extra plugins extraPlugins: 'tinsertimage,teditimage,teditlink,tinsertlink,teditimagelink' on creating the CKEditor
In the plugin run some JS which intercept the right click on the image
CKEDITOR.plugins.add('teditimage',
{
init: function (editor) {
editor.addCommand('tEditImage',
{
exec: function (editor) {
//This opens the custom editor
ZWSInlineEditor.openImageProperties(editor, false);
}
});
if (editor.addMenuItem) {
// A group menu is required
// order, as second parameter, is not required
editor.addMenuGroup('gImage');
// Create a manu item
editor.addMenuItem('gEditImageItem', {
label: 'Edit Image Properties',
command: 'tEditImage',
group: 'gImage'
});
}
if (editor.contextMenu) {
editor.contextMenu.addListener(function (element, selection) {
// Get elements parent, strong parent first
var parents = element.getParents("img");
// Check if it's strong
if (parents[0].getName() != "img")
return null; // No item
return { gEditImageItem: CKEDITOR.TRISTATE_ON };
});
}
}
});

I don't understand what's the point in what you're doing (or please explain us). Maybe you should rather customize dialogs than do things from scratch?

Related

ExtJS loadMask on the form is not working?

I have a form and I am trying to apply mask when submit button is clicked but somehow mask is not displaying on form.
var objMask = new Ext.LoadMask({
target: target,
msg: 'test',
//hideMode: 'display',
listeners: {
beforedestroy: function (lmask) {
//this.hide();
},
hide: function (lmask) {
//this.hide();
}
}
});
It is working in panel but in form, we are not getting anything.
Thanks in advance.
You need to call show method on Ext.LoadMask instance.
Example on https://fiddle.sencha.com/#view/editor&fiddle/3cuq
let target = Ext.create("Ext.form.Panel", {
renderTo: Ext.getBody(),
width: 400,
height: 400
});
var objMask = new Ext.LoadMask({
target: target,
msg: 'test',
//hideMode: 'display',
listeners: {
beforedestroy: function (lmask) {
//this.hide();
},
hide: function (lmask) {
//this.hide();
}
}
});
objMask.show();

Get the new resource id after an event has been dragged or resized in FullCalendar

I'm using FullCalendar with the Scheduler plugin and I'm trying to get the new resource id for the event that has just been dragged or resized. If I console.log the event argument of eventResize or eventDragStop functions I always get the initial resource id of the event.
Any idea how can I achieve this?
Bellow is the code I have so far:
$('#calendar').fullCalendar({
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
locale: 'ro',
header: {
left: '',
center: 'title',
right: ''
},
defaultView: 'agendaDay',
views: {
agenda: {
titleFormat: 'dddd, D MMMM'
}
},
minTime: '07:00:00',
maxTime: '24:00:00',
slotDuration: '00:30:00',
slotLabelFormat: 'HH(:mm)',
allDaySlot: false,
resources: {
url: '/some/endpoint/here',
type: 'POST',
data: {
type: $('#type').val()
}
},
events: '/some/other/endpoint/here',
eventOverlap: false,
eventConstraint: {
start: '07:00',
end: '24:00'
},
dayClick: function(date, jsEvent, view, resourceObj) {
var check = moment(date).format('YYYY-MM-DD');
var today = moment(new Date()).format('YYYY-MM-DD');
if (check >= today) {
// Some logic here
}
},
eventClick: function(calEvent, jsEvent, view) {
var check = moment(calEvent.start).format('YYYY-MM-DD');
var today = moment(new Date()).format('YYYY-MM-DD');
if (check >= today) {
// Some logic here
}
},
eventResize: function(event, delta, revertFunc, jsEvent, ui, view) {
console.log('Resize', event, jsEvent);
},
eventDragStop: function(event, jsEvent, ui, view) {
console.log('Drag', event);
}
});
The documentation for "eventDragStop" (https://fullcalendar.io/docs/eventDragStop) explicitly states
It is triggered before the event’s information has been modified
So that explains why the resource ID has not changed when you log it from there.
The callback you want to be handling instead is "eventDrop" (https://fullcalendar.io/docs/eventDrop), which is triggered when the dragging stops and the event data has been updated to reflect its new location.
For example:
eventDrop: function( event, delta, revertFunc, jsEvent, ui, view ) {
console.log("Resource: " + event.resourceId);
}
should get you the information you want.
Obviously if you only resize an event that cannot change the resource it belongs to, so that situation is not relevant to your issue.

CKEditor add Format combo items inside context (nested) menu

I am using CKEditor 4.5.7 in one of my project. We have customized Format combo as shown in the below screen shot, the ask is to have all the items appearing inside this combo on right click.
And below is the code for Format combo:
config.format_tags = 'p;h3;h4;pre;ImageInline;ImageCentered;ImageCenteredWithDropShadow;FigureHeading;Equation;EquationDefinition;TableWithoutBorder';
config.format_ImageInline = { name: 'Image inline', element: 'img', attributes: { 'class': 'noborder' } };
config.format_ImageCentered = { name: 'Image centered', element: 'img', attributes: { 'class': 'noborderblock' } };
config.format_ImageCenteredWithDropShadow = { name: 'Image centered drop shadow', element: 'img', attributes: { 'class': 'border' } };
config.format_FigureHeading = { name: 'Figure/Table heading', element: 'p'/*['p', 'td']*/, attributes: { 'class': 'footing' } };
config.format_Equation = { name: 'Equation', element: 'table', attributes: { 'class': 'equation' } };
config.format_EquationDefinition = { name: 'Equation definition', element: 'table', attributes: { 'class': 'where' } };
config.format_TableWithoutBorder = { name: 'Table without border', element: 'table', attributes: { 'class': 'nobordertable' } };
I was able to get them displayed in context menu as shown in below screen shot:
But I am not sure what will be the command name for each one of them. i.e.
command: 'format_ImageCentered' /*I need help here*/
command: 'format_ImageCenteredWithDropShadow' /*I need help here*/
I have already downloaded full source code of CKEditor and gone thru ckeditor\plugins\format\plugin.js but wasn't able to figure out what to specify as command.
Below is my code for customizing Context menu:
var ck_article = CKEDITOR.replace("content", { customConfig: '<config js file path>', bodyClass: '<css class>' });
ck_article.on("instanceReady", function (evt) {
var editor = evt.editor;
/*Code for checking if editor has context menu or not removed for brevity*/
//...
//...
editor.addMenuGroup('ck_group');
editor.addMenuItem('bold', {
label: 'Bold',
command: 'bold',
group: 'ck_group'
});
editor.addMenuItem('iconselector', {
label: '...',
command: 'iconselector',
group: 'ck_group'
});
editor.addMenuItem('numberedlist', {
label: 'Numbered List',
command: 'numberedlist',
group: 'ck_group'
});
editor.addMenuItem('bulletedlist', {
label: 'Bulleted List',
command: 'bulletedlist',
group: 'ck_group'
});
editor.addMenuItem('link', {
label: 'Link',
command: 'link',
group: 'ck_group'
});
editor.addMenuItems({
formatting: {
label: 'Formatting',
group: 'ck_group',
getItems: function () {
var selection = editor.getSelection();
//This is to nest items inside context menu of CKEditor
return {
format_ImageCentered: CKEDITOR.TRISTATE_ON,
format_ImageCenteredWithDropShadow: CKEDITOR.TRISTATE_ON
}
}
},
format_ImageCentered: {
label: "Image centered",
group: 'ck_group',
command: 'format_ImageCentered' /*I need help here*/
},
format_ImageCenteredWithDropShadow: {
label: "Image centered drop shadow",
group: 'ck_group',
command: 'format_ImageCenteredWithDropShadow' /*I need help here*/
}
});
editor.contextMenu.addListener(function (element, selection, elementPath) {
var contentMenuItems = {
link: CKEDITOR.TRISTATE_ON,
bold: CKEDITOR.TRISTATE_ON,
numberedlist: CKEDITOR.TRISTATE_ON,
bulletedlist: CKEDITOR.TRISTATE_ON,
iconselector: CKEDITOR.TRISTATE_ON,
formatting: CKEDITOR.TRISTATE_ON
};
if (element.getAscendant('a', true)) {
//If we are already inside 'a' tag then remove link from Context menu else we will end up with two "Link" menus
delete contentMenuItems.link
}
if ($.trim(selection.getSelectedText()) === '') {
//If no text is selected then remove bold from context menu
delete contentMenuItems.bold;
//contentMenuItems.bold = CKEDITOR.TRISTATE_DISABLED; //This doesn't work as the menu item is disabled but hover effect is still there
//Similarly remove link if nothing is selected as it will insert hyperlink text as words inside CKEditor
delete contentMenuItems.link;
//contentMenuItems.link = CKEDITOR.TRISTATE_DISABLED; //This doesn't work as the menu item is disabled but hover effect is still there
}
return contentMenuItems;
});
});
References:
I have used below URL as references:
http://blog.ale-re.net/2010/06/ckeditor-context-menu.html
http://docs.ckeditor.com/#!/guide/plugin_sdk_sample_2
CKEditor - Possible to have context menu for basic styles?
I ended up creating plugin for each and every option listed in Format combo. If there is any other better way than this then please let me know. I am passing code just in case if someone else stumbles with the similar issue.
Below screen shot shows how the plugin folder looks inside CKEditor folder:
I am pasting just one plugin code as all of plugins have the exact same code the only difference is value for pluginName, which is exact same as folder name:
//All the files inside folder stating with context_<name> have exact same code except pluginName variable.
//I need to this to support format inside right click
(function () {
"use strict";
var pluginName = 'contextmenu_tablewithoutborder'; //This name will be used to add to 'config.extraPlugins' string
var commandName = pluginName;
// Register the plugin within the editor.
CKEDITOR.plugins.add(pluginName, {
// Register the icons. They must match command names.
icons: pluginName,
// The plugin initialization logic goes inside this method.
init: function (editor) {
// Define an editor command.
editor.addCommand(commandName, { //Command name must match with name provided in editor.ui.addButton
// Define the function that will be fired when the command is executed.
exec: function (editor) {
if (typeof editor.applyFormatStyles === 'function')
editor.applyFormatStyles(pluginName.split('_')[1]);
else
throw new Error('applyFormatStyles is not defined as function (' + pluginName + ')');
}
});
}
});
})();
I then extended CKEditor and added below methods:
CKEDITOR.editor.prototype.getFormatStyles = function () {
var styles = {}
var editor = this;
var config = editor.config,
lang = editor.lang.format;
// Gets the list of tags from the settings.
var tags = config.format_tags.split(';');
for (var i = 0; i < tags.length; i++) {
var tag = tags[i];
var style = new CKEDITOR.style(config['format_' + tag]);
if (!editor.filter.customConfig || editor.filter.check(style)) {
styles[tag] = style;
styles[tag]._.enterMode = editor.config.enterMode;
}
}
return styles;
}
CKEDITOR.editor.prototype.applyFormatStyles = function (styleName) {
var editor = this;
var styles = editor.getFormatStyles();
editor.focus();
editor.fire('saveSnapshot');
var style = styles[styleName],
elementPath = editor.elementPath();
editor[style.checkActive(elementPath, editor) ? 'removeStyle' : 'applyStyle'](style);
// Save the undo snapshot after all changes are affected. (#4899)
setTimeout(function () {
editor.fire('saveSnapshot');
}, 0);
}
I then modified my config file and added all these plugins as extra plugins:
CKEDITOR.editorConfig = function (config) {
var extraPlugins = [];
//Remove other code for brevity
//...
//...
config.format_tags = 'p;h3;h4;pre;imageinline;imagecentered;imagecenteredwithdropshadow;figureheading;equation;equationdefinition;tablewithoutborder';
config.format_imageinline = { name: 'Image inline', element: 'img', attributes: { 'class': 'noborder' } };
config.format_imagecentered = { name: 'Image centered', element: 'img', attributes: { 'class': 'noborderblock' } };
config.format_imagecenteredwithdropshadow = { name: 'Image centered drop shadow', element: 'img', attributes: { 'class': 'border' } };
config.format_figureheading = { name: 'Figure/Table heading', element: 'p'/*['p', 'td']*/, attributes: { 'class': 'footing' } };
config.format_equation = { name: 'Equation', element: 'table', attributes: { 'class': 'equation' } };
config.format_equationdefinition = { name: 'Equation definition', element: 'table', attributes: { 'class': 'where' } };
config.format_tablewithoutborder = { name: 'Table without border', element: 'table', attributes: { 'class': 'nobordertable' } };
var contextmenu_plugins = config.format_tags.split(";");
for (var i = 0; i < contextmenu_plugins.length; i++) {
var pluginName = contextmenu_plugins[i];
extraPlugins.push("contextmenu_{0}".format(pluginName))
}
config.extraPlugins = extraPlugins.join(',');
}
And then finally when I create editor I extent the context menu. I could add this logic in plugin.js file but since I wanted plugin.js code to be exact same except a line or two's difference I didn't bother adding it there.
var ck_article = CKEDITOR.replace("content", { customConfig: '<config js file path>', bodyClass: '<css class>' });
ck_article.on("instanceReady", function (evt) {
var editor = evt.editor;
editor.addMenuGroup('ck_group');
editor.addMenuItem('bold', {
label: 'Bold',
command: 'bold',
group: 'ck_group'
});
editor.addMenuItem('iconselector', {
label: '...',
command: 'iconselector',
group: 'ck_group'
});
editor.addMenuItem('numberedlist', {
label: 'Numbered List',
command: 'numberedlist',
group: 'ck_group'
});
editor.addMenuItem('bulletedlist', {
label: 'Bulleted List',
command: 'bulletedlist',
group: 'ck_group'
});
editor.addMenuItem('link', {
label: 'Link',
command: 'link',
group: 'ck_group'
});
editor.addMenuItems({
formatting: {
label: 'Formatting',
group: 'ck_group',
getItems: function () {
var selection = editor.getSelection();
//This is to nest items inside context menu of CKEditor
var tags = editor.config.format_tags.split(";");
var menu_items = {
};
for (var i = 0; i < tags.length; i++) {
menu_items[tags[i]] = CKEDITOR.TRISTATE_ON;
}
//menu_items - will have object something like below
//{p: 1, h3: 1, h4: 1......}
return menu_items;
}
},
p: {
label: "Normal",
group: 'ck_group',
command: 'contextmenu_p'
},
h3: {
label: "Heading",
group: 'ck_group',
command: 'contextmenu_h3'
},
h4: {
label: "Sub Heading",
group: 'ck_group',
command: 'contextmenu_h4'
},
pre: {
label: "Formatted",
group: 'ck_group',
command: 'contextmenu_pre'
},
imageinline: {
label: "Image inline",
group: 'ck_group',
command: 'contextmenu_imageinline'
},
imagecentered: {
label: "Image centered",
group: 'ck_group',
command: 'contextmenu_imagecentered'
},
imagecenteredwithdropshadow: {
label: "Image centered drop shadow",
group: 'ck_group',
command: 'contextmenu_imagecenteredwithdropshadow'
},
figureheading: {
label: "Figure/Table heading",
group: 'ck_group',
command: 'contextmenu_figureheading'
},
equation: {
label: "Equation",
group: 'ck_group',
command: 'contextmenu_equation'
},
equationdefinition: {
label: "Equation definition",
group: 'ck_group',
command: 'contextmenu_equationdefinition'
},
tablewithoutborder: {
label: "Table without border",
group: 'ck_group',
command: 'contextmenu_tablewithoutborder'
}
});
editor.contextMenu.addListener(function (element, selection, elementPath) {
var contentMenuItems = {
link: CKEDITOR.TRISTATE_ON,
bold: CKEDITOR.TRISTATE_ON,
numberedlist: CKEDITOR.TRISTATE_ON,
bulletedlist: CKEDITOR.TRISTATE_ON,
iconselector: CKEDITOR.TRISTATE_ON,
formatting: CKEDITOR.TRISTATE_ON
};
if (element.getAscendant('a', true)) {
//If we are already inside 'a' tag then remove link from Context menu else we will end up with two "Link" menus
delete contentMenuItems.link
}
if ($.trim(selection.getSelectedText()) === '') {
//If no text is selected then remove bold from context menu
delete contentMenuItems.bold;
//Similarly remove link if nothing is selected as it will insert hyperlink text as words inside CKEditor
delete contentMenuItems.link;
}
return contentMenuItems;
});
});
I know there is too much of code but I wasn't able to find any other simple way to do this.
When all done this is how it looks:
Quick recap:
Created plugin for each item appearing in Format combo
Only change in all the plugin.js file is pluginName (it is name of the folder)
Extend CKEditor and add applyFormatStyles method which will be invoked by plugin.js file
In your config file add all these as extra plugins
And on creation of CKEditor instance extend context menu

Call parent window function from child window

I'm trying to call a function on the parent page from a popup window. I keep getting the error Object doesn't support property or method 'GetValueFromChild'. I believe the error is on this line in the popup window -
window.top.opener.parent.Xrm.Page.GetValueFromChild(person);. I tried using window.opener.GetValueFromChild(person); but still get the same error. Any help is much appreciated. Here's the code -
//parent
$(document).ready(function () {
// This needs to be called from Child window
function GetValueFromChild(person) {
alert(person.Id);
alert(person.Name);
alert(person.Market);
}
});
//parent - jqgrid
colModel: [
{
name: 'Person', index: 'PersonName', width: 70, editable: true, edittype: 'button',
editoptions: {
value: 'Select',
dataEvents: [{
type: 'click',
fn: function (elem) {
var left = (screen.width / 2) - (700 / 2);
var top = (screen.height / 2) - (550 / 2);
var popup = window.open("popup.htm", "popup", "resizable=1,copyhistory=0,menubar=0,width=700,height=550,left='+left+',top='+top");
popup.focus();
}
}]
}
},
//popup window. this page has another jqgrid and an OK button.
$('#btnOK').click(function() {
var person= {
Id: grid.jqGrid('getCell', grid.getGridParam('selrow'), 'Id'),
Name: grid.jqGrid('getCell', grid.getGridParam('selrow'), 'Name'),
Market: grid.jqGrid('getCell', grid.getGridParam('selrow'), 'Market')
};
window.top.opener.parent.Xrm.Page.GetValueFromChild(person); //Error is on this line.
window.close();
});
Your GetValueFromChild is scoped to your ready callback. If it doesn't need to access other scoped functions and variables then simply declare it at the top level instead. If it does need to access them, you need to create a global reference to it in your callback.
Declare at top level:
// This needs to be called from Child window
function GetValueFromChild(person) {
alert(person.Id);
alert(person.Name);
alert(person.Market);
}
Export from scope:
$(document).ready(function () {
// This needs to be called from Child window
function GetValueFromChild(person) {
alert(person.Id);
alert(person.Name);
alert(person.Market);
}
window.GetValueFromChild = GetValueFromChild;
});

Sencha Touch, dock panel inside Nested List

I have a Sencha Touch app that has a nested list.
The nestedList automatically creates its own toolBar.
I would like to dock a panel below the toolbar, but above the list-items. And I only need this on the top-level of the list. I am hoping to have it disappear after the first leaf is selected.
Does this sound like something doable? As you can see in my code, I only have the ability to dock an item panel on top of the current toolbar.
Thanks so much in advance. I really appreciate any advice you guys might have.
Al.
Below is what I have so far...
// Menu Pages
var menu = new Ext.NestedList({
fullscreen: true,
title: 'Menu',
displayField: 'text',
store: menu_store,
// ** This is the dockedItem I would like to insert between the toolbar and list-items
dockedItems: [ {
xtype : 'panel',
dock : 'top',
html : '<span>This is the logo panel</span>',
cls : 'front-logo-panel',
flex : 1
}],
// Add Panel for Leaf nodes
getDetailCard: function(item, parent) {
var itemData = item.attributes.record.data,
parentData = parent.attributes.record.data,
detailCard = new Ext.Panel({
scroll: 'vertical',
cls: 'menu-item-panel',
styleHtmlContent : true,
tpl: menuTemplate,
// add button to Leaf Node
listeners: {
activate: function() {
Ext.getCmp('itemToolbar').setTitle('New Title Bar');
}
}
});
detailCard.update(itemData);
this.backButton.setText(parentData.text);
return detailCard;
},
// add template for all nodes
getItemTextTpl: function() {
var tplConstructor =
'<tpl if="newItem">' +
'<span class="list-new-item">New Item!</span>' +
'</tpl>'+
'{text}' +
'<tpl>'+
'<div class="metadata">' +
'{description:ellipsis(40)}' +
'</div>' +
'</tpl>';
return tplConstructor;
}
});
Old question, I know, but to solve this, you can remove the toolbar from the list (without destroying it) and add it to a panel above the list. All nestedList functionality stays the same on the toolbar. Here's the solution I have:
First, I created a view that extends from NestedList and contains a toolbar:
Ext.define('MyApp.view.DynamicList', {
extend: 'Ext.dataview.NestedList',
alias: 'widget.dynamiclist',
config: {
toolbar: {
xtype: 'toolbar',
docked: 'top',
itemId: 'header-bar',
layout: {
pack: 'end',
type: 'hbox'
},
items: [
{
xtype: 'spacer'
},
{
xtype: 'button',
itemId: 'show-nav-sheet-button',
ui: 'plain',
width: 45,
iconCls: 'more'
}
]
}
}
});
Next, I created a main panel with a vbox layout that contains a child panel and this toolbar:
Ext.define('MyApp.view.MainContainer', {
extend: 'Ext.Container',
requires: [
'MyApp.view.DynamicList'
],
config: {
id: 'main-container',
layout: {
type: 'vbox'
},
items: [
{
xtype: 'panel',
flex: 1,
itemId: 'info-container'
},
{
xtype: 'dynamiclist',
flex: 1
}
]
}
});
Then, in a controller, I listen for the initialize event of the nested list. When it's fired, I remove the toolbar from the nested list and add it to the panel.
onNestedListInitialize: function() {
// need to wait until everythin is initialized;
var me = this;
var renderFn = function renderPanels() {
var main = me.getMainContainer();
// wait until main is intialized;
if(!main) {
Ext.defer(renderFn, 50, this);
return;
}
var list = main.down('#my-list');
var infocont = main.down('#info-container');
// wait until the container's components are initialized
if(!list || !infocont) {
Ext.defer(renderFn, 50, this);
return;
}
// remove the toolbar from the list, and add it to the container.
var toolbar = list.down('#header-bar');
list.remove(toolbar, false);
infocont.add(toolbar);
}
// call the function for the first time.
renderFn();
}
Note, I had to add a few checks to make sure the components were correctly initialized before using them, but the heart of it comes to the list.remove(toolbar, false) followed by the infocont.add(toolbar) commands. The false flag in the .remove() method means that the item won't be destroyed, so it's available to be re-added to the panel.

Resources