I have a web site with inline CKEditors, on which I modify the HTML DOM with javascript and then try to fire the saveSnapshot event to save the modifications to the server. And here's the tricky part; if I make only one DOM change (remove an element) and call editor.fire("saveSnapshot"); it seems to work fine, but if I remove more than one element from the DOM I very often get the error below.
I have tried many different variations on doing what I need to do but with no success, except fire the event editor.fire("change") instead (which seems to be not recommended for some reason by CK). I have tried to delay with timeouts if things are happening to fast for the CK, using different combinations of lock/unlock snapshot events etc.
So, my questions are:
1) what is the reason using fire("change) is not recommended?
2) why is this behaviour causing CK to err?
Any clue that can take me forward on this issue is greatly appreciated.
Error message from CK:
ckeditor.js:149 Uncaught TypeError: Cannot read property 'type' of null
at a (ckeditor.js:149)
at CKEDITOR.dom.range.createBookmark2 (ckeditor.js:151)
at Array.createBookmarks2 (ckeditor.js:510)
at CKEDITOR.dom.selection.createBookmarks2 (ckeditor.js:463)
at CKEDITOR.plugins.undo.Image (ckeditor.js:1072)
at CKEDITOR.plugins.undo.UndoManager.save (ckeditor.js:1067)
at a.<anonymous> (ckeditor.js:1063)
at a.n (ckeditor.js:10)
at a.CKEDITOR.event.CKEDITOR.event.fire (ckeditor.js:12)
at a.CKEDITOR.editor.CKEDITOR.editor.fire (ckeditor.js:13)
Here's the code where I modify the DOM (remove one or several elements) and fire the saveSnapshot event for each affected editor instance:
var sectionIds = [];
if (textMarkers instanceof Array) {
for (var i = 0; i < textMarkers.length; i++) {
var textMarker = textMarkers[i];
$("#sectionText-" + textMarker.sectionId).find("span.hypo-marker[data-marker-id='" + textMarker.id + "']").remove();
if (sectionIds.indexOf(textMarker.sectionId) === -1) {
sectionIds.push(textMarker.sectionId);
}
}
} else {
$("#sectionText-" + textMarkers.sectionId).find("span.hypo-marker[data-marker-id='" + textMarkers.id + "']").remove();
sectionIds.push(textMarkers.sectionId);
}
// Trigger save event on editor for each section that's been updated
for (var c = 0; c < sectionIds.length; c++) {
var editor = CKEDITOR.instances["sectionText-" + sectionIds[c]];
// editor.fire("saveSnapshot");
editor.fire("change");
}
I use CKEditor standard 4.7 with Chrome Version 60.0.3112.113 (Official Build) (64-bit) with the following CKEditor config:
var options = {
removePlugins: 'magicline,horizontalrule',
skin: 'office2013',
extraPlugins: 'colorbutton,panelbutton,panel,floatpanel,button,scayt,html5audio,uploadwidget,youtube,image2,uploadimage,keystrokes,notification,notificationaggregator,magicline2,undo',
language: 'sv',
title: false,
entities: false,
basicEntities: false,
extraAllowedContent: 'span(*)[*]; img(*)[!src,*]; div(*)[*]; a(*)[*]; p(*)[*]; i(*); table(*)[*]; thead(*)[*]; tbody(*)[*]; tr(*)[*]; td(*)[*];',
// disallowedContent: 'span(rangySelectionBoundary)[id];',
imageUploadUrl: '/api/v2/editor/images',
//filebrowserUploadUrl: '/api/v2/editor/images',
toolbarGroups: [
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'links' },
{ name: 'insert' },
'/',
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'styles' },
{ name: 'colors' }
],
removeButtons: "Anchor",
magicline_color: "#00a651",
coreStyles_italic: { element: 'i', overrides: 'em' }
};
I can answer your first question:
It is not recommended to catch every change event because it gets fired quite often and it may prove quite heavy however firing should not be a problem.
If you intend to catch it maybe you could add extra data to that event based on which you can identify yours - https://docs.ckeditor.com/#!/api/CKEDITOR.event-method-fire.
The change event also doesn't get fired in source mode so if you use that mode, you need to find some other way of checking for code changes (maybe periodically comparing previous and current textarea value).
Related
I'm trying to achieve arrow key navigation on the tabs in ExtJs 4.2.2, right now i have one of the tabs selected, i want to use keyboard arrows to navigate to other tabs.
here is what i have done. but its not working... i'm getting error 'next is not defined'
what am i doing wrong here.
var keyNav = new Ext.util.KeyMap({
binding: [{
key: Ext.EventObject.UP,
fn: function(){ this.next().focus() }
},{
key: Ext.EventObject.DOWN,
fn: function(){ this.prev().focus() }
}, {
key: Ext.EventObject.LEFT,
fn: function(){ this.prev().focus() }
}, {
key: Ext.EventObject.RIGHT,
fn: function(){ this.next().focus() }
}
],
scope: this
});
It seems that the this in the handler still does not refer to the tab. Try to put a debugger in it and see. It might be worth tying keynav through target property with your tab's.
I am loading ckeditor.js file using $.getScript and in callback I am initiating CKEditor. But it is showing an error TypeError: c[a] is undefined. Here is my code. How can I solve this issue?
$.getScript("ckeditor.js", function (data, textStatus, jqxhr) {
if (textStatus == 'success' && jqxhr.status == 200) {
CKEDITOR.replace( 'commentBox',
{
toolbar :
[
{ name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
{ name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Blockquote'] },
{ name: 'insert', items : [ 'Table','HorizontalRule','SpecialChar' ] },
{ name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] },
{ name: 'colors', items : [ 'TextColor','BGColor' ] }
]
});
}
});
I was getting the same error in similar circumstances.
I checked the formatted source in Chrome and discovered that this was being caused by the Format plugin trying to load its labels from the CKEDITOR.language object.
Turns out I didn't have en-gb included in my build and apparently it won't automatically fall back to straight en. Adding English (United Kingdom) to the build corrected the issues.
Re. https://stackoverflow.com/a/50719171/6462713
I had same issue. I have also loaded all supported languages in "/lang" folder. Basically my issue was - CKEditor isn't identifying properly its own folder path. So I set a CKEDITOR_BASEPATH variable before loading CKEditor.
It's briefly said here: (but there might be other places where it's explained better.) http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.html#.basePath
Therefore implementation will be like this:
<script>
window.CKEDITOR_BASEPATH = 'http://example.com/path/to/libs/ckeditor/';
</script>
In my case i used window.CKEDITOR_BASEPATH = '/app/storereport/ckeditor/';
Then load the main ckeditor.js script. Hope this may help you.
<script type="application/javascript"/>
$(document).ready(function (){
CKEDITOR.replace( 'product_content' ); // ID of element
});
</script>
Ok I'm pretty sure I know exactly what I need to do here but I'm not sure how to do it. Basically I have a grid that I want to make a key column bind to an array of key/values, which I've done before with kendo (not using Angular) and I know that when I'm creating my key/value array asynchronously then that needs to complete before I can get them show-up with kendo, which I have done using promises before.
So here I have the same issue only angular is also involved. I need to fetch and format an array of data into the format in which a kendo grid column can digest it, so no problem here is my controller code:
var realm = kendo.data.Model.define({
id: 'realmID',
fields: {
realmID: { editable: false, nullable: true }
realmType: { type: 'string', validation: { required: true } }
}
})
var ds1 = kendoHelpers.dataSourceFactory('realms', realm, 'realmID')
var realmType = kendo.data.Model.define({
id: 'realmTypeID',
fields: {
realmTypeID: { editable: false, nullable: true },
name: { type: 'string', validation: { required: true } }
}
})
var ds2 = kendoHelpers.dataSourceFactory('realms/types', realmType, 'realmTypeID')
$scope.mainGridOptions = {
dataSource: ds1,
editable: true,
navigatable: true,
autoBind:false,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmID', title: 'ID' }
{ field: 'realmTypeID', title: 'Realm Type', editor: realmTypesDDL, values: $scope.realmTypeValues },
{ command: "destroy" }
]
}
$scope.secondGridOptions = {
dataSource: ds2,
editable: true,
navigatable: true,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmTypeID', title: 'ID' },
{ field: 'name', title: 'Name' }
{ command: "destroy" }
]
}
ds2.fetch(function () {
$scope.realmTypeValues = [{ text: 'Test', value: "24bc2e62-f761-4e70-804c-bc36fdeced3d" }];
//this.data().map(function (v, i) {
// $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
//});
//$scope.mainGridOptions.ds1.read()
});
function realmTypesDDL(container, options) {
$('<input />')
.appendTo(container)
.kendoDropDownList({
dataSource: ds2,
dataTextField: 'name',
dataValueField: 'realmTypeID'
});
}
I made this dataSourceFatory helper method above to return me a basic CRUD kendo dataSource that uses transport and also injects an authorization header which is working fine so don't get hung up on that, ultimately I'm going to be using this data in another grid as well as for reference values for the main grid, but I've hard coded some values that I can use to test with in the ds2.fetch callback.
My HTML is pretty plain:
<div>
<h2>Realms</h2>
<kendo-grid options="mainGridOptions"></kendo-grid>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
</div>
This all works fine and well except I am only seeing the GUID of the realmTypeID in the grid, I click it and the editor is populated correctly so that's good but I want the text value to be displayed instead of the GUID. I'm sure the issue is that the array of values is empty whenever angular is binding to the grid options. My questions are:
How do I either delay this bind operation or manually rebind it after the fetch call?
Is there a better way to handle a situation like this? I try not to expend finite resources for no reason (IE making server calls when unnecessary)
Note: When I move the creation of the text/value array to happen before the grid options, I get the desired behavior I am after
EDIT A work around is to not use the directive to create the grid and instead defer the grid creation until the callback of whatever data your column is dependent on, I was hoping for a more elegant solution but this is better than nothing. So your HTML becomes something like
<h2>Realms</h2>
<div id="realms"></div>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
Then you can create the grid in the fetch callback for example:
ds2.fetch(function () {this.data().map(function (v, i) {
$scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
});
$('#realms').kendoGrid($scope.mainGridOptions);
$scope.mainGridOptions.dataSource.fetch()
});
But this doesn't feel very angularish so I'm really hoping for a better solution!
Ok...well I think I hacked this enough and without another suggestion I'm going to go forward with this approach. I'm just going to move the binding logic to the requestEnd event of the second grid so that the values array can be populated right before the binding even. I'm also reworking the values array in this method. It is a bit weird though, I think there is some kendo black magic going on with this array because I can't just set it to a new empty array without it breaking completely...which is why I'm poping everything out prior to repopulating the array. That way when something is deleted or edited in the second grid, the DDL in the first grid is updated in the callback.
function requestEnd(e) {
for (var i = $scope.realmTypeValues.length; i >= 0; i--) $scope.realmTypeValues.pop();
var data;
if (e.type == "read")
data = e.response;
else
data = e.sender.data();
data.map(function (v, i) { $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID }); });
if ($('#realms').data('kendoGrid') == undefined) {
$('#realms').kendoGrid($scope.mainGridOptions);
}
else
$('#realms').data('kendoGrid').columns[4].values = $scope.realmTypeValues;
}
ds2.bind('requestEnd', requestEnd);
So I'm going to accept my own answer unless anyone has a better approach!
I try to create my own kind in enyo
enyo.kind(
{
name: "DeviceButton",
kind: "Button",
caption: "",
published: { userAgent: "" },
flex: 1,
onclick: "butclick",
butclick: function() { console.log("User agent changed to " + this.userAgent) }
})
But when I click there is nothing shown
If I just did
onclick: console.log("User agent changed to " + this.userAgent)
It was printed that this.userAgent is undefined
What am I doing wrong?
Btw., is it possible to send parameters via onclick (so that the function repsponding to the click get a variable)
Thanks
The problem you're having here is that the onclick property is actually giving the name of the event handler for the Enyo to send the event to when the click is received. The "butclick" event isn't dispatched to the DeviceButton, but rather to its parent.
If you want to handle the event entirely within your kind, then you need to set it up as a "handler". In Enyo 2.x, you do it like this:
enyo.kind(
{
name: "DeviceButton",
kind: "Button",
caption: "",
published: { userAgent: "" },
flex: 1,
handlers {
onclick: "butclick"
},
butclick: function() { console.log("User agent changed to " + this.userAgent) }
})
In Enyo 1.x, you'd just need to name the handler function "onclickHandler". I mention the Enyo 1 solution because I see that you have "flex: 1" in your definition. Flexbox isn't supported in Enyo 2, we have a "Fittable" system instead.
I made a little example for you how enyo can handle sending and receiving values to and from a custom kind. I also added some short comments inside the code.
http://jsfiddle.net/joopmicroop/K3azX/
enyo.kind({
name: 'App',
kind: enyo.Control,
components: [
{kind:'FittableRows', components:[
// calls the custom kind by it's default values
{kind:'DeviceButton',name:'bttn1', classes:'joop-btn',ontap:'printToTarget'},
// calls the custom kind and pass the variables to the DeviceButton kind
{kind:'DeviceButton', name:'bttn2', btnstring:'Get Agent', useragent:'chrome', classes:'joop-btn', ontap:'printToTarget'},
{tag:'div', name:'targetContainer', content:'no button clicked yet', classes:'joop-target'},
]},
],
printToTarget:function(inSender, inEvent){
// inSender = the button that was pressed
this.$.targetContainer.setContent(inSender.name+' has used the value: "'+inSender.getUseragent()+'" and sends the value of: "'+inSender.getValueToPrint()+'" back.');
},
});
enyo.kind({
name:'DeviceButton',
kind:enyo.Control,
components:[
{kind:'onyx.Button',name:'btn', ontap:'printUsrAgent'}
],
published:{
btnstring:'default btnstring', // value that will be received
useragent:'default useragent', // value that will be received
valueToPrint:'default valueToPrint' // value that will be used
},
rendered:function(){
this.inherited(arguments);
this.$.btn.setContent(this.btnstring);
},
printUsrAgent:function(inSender,inEvent){
// set a parameter with the value that was received of if not received use the default value (normaly would do some calculations with it first)
this.valueToPrint = this.useragent+' after changes';
},
});
I'm facing an strange behavior in an web application I'm currently working on. The interface is totally built with ExtJs 4. The application has two modes, Debug and Production.
In Debug mode, when user click a menu item, I load synchronously, the dependent Javascripts file. Something like this:
for ( var i = 0; i < config.dependency.length; i++ ) {
var element = document.createElement('script');
element.onload = callback;
element.onreadystatechange = function () {
if ( element.readyState == 'loaded' || element.readyState == 'complete' ) {
callback();
}
};
}
One of the module's javascript file is like this:
module.js
Ext.define('Company.view.system.Module', {
extend: 'Company.view.abstract.Panel',
alias: 'widget.system.module',
/**
* These "_items" are built into the Object in `Company.view.abstract.Panel`
* using: Ext.create(this._items[i].clazz, conf);
*
* I'm just omitting the whole business logic there
*/
_items: [{
flex: 2,
clazz: 'Company.view.system.module.Form'
}, {
flex: 5,
clazz: 'Company.view.system.module.HtmlEditor'
}]
});
Ext.define('Company.view.system.module.Form', {
extend: 'Company.view.abstract.Form',
alias: 'widget.system.module.form',
items: [{
xtype: 'fieldset',
title: 'Module Grid',
items: [{
xtype: 'system.module.grid'
}]
}]
});
Ext.define('Company.view.system.module.Grid', {
extend: 'Company.view.abstract.Grid',
alias: 'widget.system.module.grid',
columns: [{
...
}],
afterLayout: function() {
this.callParent(arguments);
alert('after layout');
}
});
Ext.define('Company.view.system.module.HtmlEditor', {
extend: 'Company.view.abstract.HtmlEditor',
alias: 'widget.system.module.htmleditor',
...
});
Everything's working as expected in Debug mode. But in Production mode, I load the whole Modules as a single generated-minified-obfuscated javascript file (modules.js) at start up (i.e. before the ViewPort is rendered), instead of loading dependent modules after menu click event.
In Production mode, every Items, which is instantiated using "xtype" (Company.view.system.module.Grid in the above sample), is not rendered at all!
Screenshots of Debug mode and Production Mode.
What do you think is going on here which I'm missing?
Update:
The problem lies with the Height of the GridPanel, as It was computed at runtime, from the browser's screen size. Hence when the component being instantiated, the Height was not computed yet! Silly mistake :)
I would try moving the system.module.grid definition before it's being used in the system.module.form.
I would advise from having two different versions. What's your reason for that?