setReadOnly causes error when called on instanceReady of CKEditor - javascript

I'm trying to set my CKEditor instance to be "readOnly" after the instance has fully loaded but I'm getting a Javascript error: Cannot call method 'setReadOnly' of null. When I dig into it, the error is coming from this line in the ckeditor.js, within the editor.setReadOnly method: this.editable().setReadOnly(a); That means that the editor exists, but the editable method/attribute (on the CKEditor instance) does not.
Below is my code, and I'll explain it a little. My app is a combination of GWT and Backbone. The CKEditor itself is created by the Backbone code but the parent element is in GWT so that's where I initiate the setEnabled action.
private native void setEnabledOnLoad(boolean enabled, String id) /*-{
CKEDITOR.on("instanceReady", function(evt) {
if(evt.editor.name === id) {
Namespace.trigger(Namespace.Events.SET_ENABLED, enabled);
}
});
}-*/;
setEnabled: function(enabled) {
this.editor.setReadOnly(!enabled);
if(enabled){
this.editor.focusManager.focus();
} else {
this.editor.focusManager.blur();
}
}
The Backbone class has a listener for Namespace.Events.SET_ENABLED that triggers setEnabled.
Is there another CKEditor event that I should listen for? There doesn't appear to be an instanceReady event on editable. What am I missing?
EDIT
this.editor is created in the Backbone class render function like this:
this.editor = CKEDITOR.replace(this.$(this.id)[0], config);
The reason I don't add the instanceReady listener right after it's created is because the function setEnabledOnLoad is called in GWT before the instance has been fully initialized. This is a result of having the code in two places. GWT has said "ok, create the instance" but Backbone hasn't finished by the time GWT goes to the next line of code and wants to set it enabled/disabled.

Two years later, but here is my solution. Maybe someone else will find it useful.
As stated above, the event is appearantly triggered before the editable() function is fully set up, and therefore one solution is to simply wait for it to finish before setting it to readonly. This may be an ugly way to do it, but it works.
//Delayed execution - ckeditor must be properly initialized before setting readonly
var retryCount = 0;
var delayedSetReadOnly = function () {
if (CKEDITOR.instances['bodyEditor'].editable() == undefined && retryCount++ < 10) {
setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration
} else {
CKEDITOR.instances['bodyEditor'].setReadOnly();
}
};
setTimeout(delayedSetReadOnly, 50);

You could try subscribing to instanceReady event this way:
CKEDITOR.instances.editor.on("instanceReady", onInstanceReadyHandler)
However, the editor instance must have been already created by then (inspect CKEDITOR.instances in the debugger).
I'm a bit confused about the difference between editable and editor. Could you show the fragments of your code where this.editor and this.editable get assigned?
[EDITED] I guess I see what's going on. CKEDITOR is a global object, you may think of it as of a class which holds all CKEDITOR instances. Trying to handle events with CKEDITOR.on isn't right, you need to do it on a specific instance (like I've shown above). I assume, "editor" is the ID of your parent element you want to attach a CKEDITOR instance to (please correct me if I'm wrong). I'm not familiar with Backbone, but usually it's done with replace:
var editorInstance = CKEDITOR.replace("editor", { on: {
instanceReady: function(ev) { alert("editor is ready!"); }}});
Here we attach a new instance of CKEDITOR to the editor parent element and subscribe to the instanceReady event at the same time. The returned object editorInstance should provide all the APIs you may need, including setReadOnly. You could also access it through the global CKEDITOR object using the parent element ID, i.e. CKEDITOR.instances.editor. On the other hand, editable is rather a service object available on editor. I can't think of any specific case where you might need to use it directly.

I apologize for never updating this with my solution. I needed to decouple the GWT function further from the CKEditor behavior. So, I added a function in GWT 'setEnabled' that is called from the parent object when it wants to update the enabled state of the CKEditor object.
public void setEnabled(boolean enabled) {
this.enabled = enabled;
toggleCKEditorEnabled(enabled);
}
Then changed the function referenced above 'setEnabledOnLoad' to be 'toggleCKEditorEnabled' which triggers the SET_ENABLED event with the enabled value.
Instead of attaching the listener to the specific instance of CKEditor, I added in to the Backbone MessageEntryView class that is the container of the CKEditor instance. In the initialize function of the MessageEntryView, I added this line
Namespace.on(Namespace.Events.SET_ENABLED, this.setEnabled);
This only works because I have one instance of CKEditor loaded on the screen at any given time. This problem and its solution stopped us from being able to add more CKEditor instances to the page at a time, which is something we discussed before moving on and replacing our whole client with Backbone.

Related

Why does changing a property from the mediaRecorder.onstop event handler not change the page?

Trying to transfer functionality to Angular, I ran into an interesting problem, when setting a property from a method, everything works fine, but when setting from an event, it changes, but is not displayed on the page. However, if you call this property after that in any method, everything is displayed at once,
Blitz
-- Reference --
Property - record;
Handler hangs on streamRecorder, onstop event;
Using one-way binding to the src element of video
Using DomSanitizer to generate a secure link using window.URL.createUrlObject(blob)
I would be grateful even for a tomato in the face!) I ran through the docks in a few days, my knowledge is still at the level of guides from the docks.
My suggestion would be scope/change detection. Something screwy with the particular reader.onstop vs reader.addEventListener('stop', ...)
Change from
this.streamRecorder.onstop = () => {
...
}
to
this.streamRecorder.addEventListener('stop', () => {
...
}
and it works.

js triggering event from prototype from different script

I have a script which holds the prototype of a game. Since we are going to reuse this prototype a lot of times I have this in a separate script.
There also is a second script which instantiates the prototype from the main script. In this script I want to catch a event trigger from the prototype.
prototype
$("#trigger").trigger("noPossibilities");
second script:
$("#trigger").on("noPossibilities", function() {
console.log("no possibilities triggered");
});
I'm probaly missing out on something since the second script doesn't catch the event. The second script is wrapped in a jQuery document ready function and the prototype one is not. Because when I also wrap the first script (which holds the prototype) the second script doesn't know the prototype.
I've searched the internet and I believe it has something to do with the document ready.
I was trying to dispatch the event from the prototype in js which unfortunately doesn't work because i'm probaly doing something wrong there.
This is that part of the script that should trigger the event:
game.triggerEvent("id", "trigger", "noPossibilities");
game.prototype.triggerEvent = function(idOrClass, elementName, eventName){
if (idOrClass == "id")
{
var element = document.getElementById(elementName);
}
else
{
var element = document.getElementsByClassName(elementName);
}
// Create the event
var event = new CustomEvent(eventName, { "detail": "js event" });
// Dispatch/Trigger/Fire the event
element.dispatchEvent(event);
}
I tried to catch it in the same way as mentioned before:
$("#trigger").on("noPossibilities", function() {
console.log("no possibilities triggered");
});
Really hope someone can help me out.
I found the solution. The problem was with the order of my code in the second script.
I instantiate a lot of elements of the game in the prototype. Also the element with id "trigger".
Before I instantiated the prototype (and the elements of the game) I tried to bind the event "noPossibilities" on the element with id trigger. Since this object wasn't instantiated yet there was nothing to bind so it wasn't working.

Overwrite listener without handler reference, is it ok to access the (possibly private) events property on a Ext.data.store?

I'm working on replacing an Ext.data.Store load event handler.
The variable me is different every time within the code block but me.store is the same (obtained via StoreManager.lookup). I want the store event listener to update the various me references. Best way i could find was to add another listener (and delete the old one since i don't need it anymore)
I haven't been able to use un / removeListener i.e. it had not effect.
I've found that i could replace the it by accesing the me.store.events and popping the listener from the load event. However this feels hacky and it might make the code dependant on a specific ExtJS version (4.2) since i don't know if it's a private property or not.
Also me.store.hasListeners['load'] doesn't get notified so it only helps because it removes the actual listener but not in the intended manner. The docs don't mention it, but i'm wondering if it may be an inherited property which can be accessed freely.
Are there any alterntives to the working approach i've come to? Can i remove all event handlers for an event without having a reference to the handler? Or is there a simpler approach i'm missing?
var me = this; // an enriched Ext.form.FormPanel, different every time code runs
me.store //obtained via StoreManger.lookup - so the same every time
me.storeLoaded = function (store, records,successful, opts) {
// some code to select a record from records and use it
me.loadRecord(record);
}
};
if (!me.store.hasListener('load')) {
me.store.on('load', me.storeLoaded);
} else{
//tried this, but it doesn't remove it, probably because me.storeLoaded is different each time (parentForm is different)
me.store.un('load', me.storeLoaded);
//this feels hacky, i couldn't find out if events is a private property
if (me.store.events && me.store.events['load']){
me.store.events['load'].listeners.pop()
}
me.store.on('load', me.storeLoaded);
}
The easiest way to implement adding/removing listeners is using the destroyable parameter as described in the addListener function. That way, you can always be sure which one is removed.
Example:
setActive:function(cmp) {
cmp.myActiveListeners = cmp.eventStore.on({
destroyable: true,
load:cmp.refreshStores,
filterchange:cmp.refreshStores,
scope:cmp
});
},
setInactive:function(cmp) {
Ext.destroy(cmp.myActiveListeners);
},
I cannot recommend to blindly remove ALL listeners, since they may be added by other components (e.g. combobox) that you add later. To track down these bugs will grow you quite some gray hairs.
I was able to find an answer in this article ExtJS overwrite listener:
Sometimes you need to overwrite an event listener in ExtJS. Usually
listeners are registered like this myStore.on('load',
this.myFunction, this); then to remove our previously registered
listener, all we have to do is call un (which is an alias for
removeListener): myStore.un('load', this.myFunction, this);
But, what happens when you don't know what function is registered?
Sometimes you will not have a reference to the original function that
was registered. This situation may arise if there is code that exists
in a different flow or may even come as a package! If that is true,
the you may not be able to get a reference to the javascript function
or edit the existing code. In this case, we will have to look at all
of the functions that are registered for this event. We can then
remove the listeners just for a certain event by calling
clearListeners.
clearListeners was the method i was looking for.
It would seem he uses the events property so i assume it is a valid use. It could be translated in my case to:
me.store.events.load.clearListeners()
However since i will only be using the load event on this particular store, i will simply call on them all.
me.store.clearListeners()
Thanks to Alexander, by suggesting not to remove all listeners that actually helped me find the article. However i will stil go with his solution, even if it polutes the store object because i like it better than clearing all listeners on a store, even if only for a specific event.

extend a javascript option to add functionality

I need to call "MyOtherFunction" when "MyFunction"(which creates an element) completes, without MyFunction knowing what MyOtherFunction is.
The reason I need this is for extension of a jquery powered fileupload User Control that is used in several places with different functionality. A specific page shows a header and file count for it, and when the upload completes, I need to modify the file count according to how many files are displayed(by created elements) I thought :
$(UserControl).on(MyFunction, UploadElem, MyOtherFunction);
but this route is not accomplishing anything. The most I can alter the User Control is add in a function call, but without effecting the original user control functionality.
I'm not sure if because MyFunction isn't an event and doesn't bubble up or if it just isn't possible to use a defined function as a parameter of .on() is the reason I cannot get this code to work. Any suggestions?
Easiest way I can think of, is duck punching respectively hooking that method:
var _oldMyFunction = MyFunction;
MyFunction = function() {
_oldMyFunction.apply( this, arguments );
MyOtherFunction();
};
I managed to solve my own issue, but the context is important for the answer:
// Using a Global JavaScript object I created:
GlobalNameSpace.ExtensionFunction = function(oParam1, oParam2, oParam3)
{
/// <summary>All parameters are optional</summary>
return; // For instances when it is not being overwritten, simply return
}
//In the Code for the user control:
GlobalNameSpace.UploadControl.UploadComplete(oSender, oArgs)
{
///<summary>Handles the Upload process</summary>
// process the upload
GlobalNameSpace.ExtensionFunction(oSender, oArgs);
}
//and finally in the code to extend the functionality
GlobalNameSpace.Page.Init
{
///<summary>Initializes the page</summary>
// redefine the extension function
GlobalNameSpace.ExtensionFunction = function(oSender, oArgs)
{
GlobalNameSpace.Page.Function(oSender, oArgs);
}
}
This allows me to extend anything I need it to without polluting my objects, and having something generic already existing to call on to make my changes. This solution solves my problem of needing a onCreate function for the elements I create to represent my uploaded items to trigger the header displaying the number of files. Very useful

Issue with partial refresh triggered twice

I have a dojo button bar which is bound to a csjs function. This function does a partialrefreshget() on a datable control. The datatable control contains a view as its datasource.
In the this.keys property I have defined some logic to see if the partialrefresh was triggered by checking for the context.getSubmittedValue(). While experimenting with this technique I noticed that the following code is triggered twice.
<xp:this.keys><![CDATA[#{javascript:
var vec = new java.util.Vector()
vec.add("category");
if(context.getSubmittedValue()!=null){
var x = context.getSubmittedValue().trim();
print("--")
}
return vec;}]]></xp:this.keys>
the print statement is printed twice to the console and the logic is therefore triggered twice. Can someone explain to me why this happens and what I can do about it? Should i check for submittedvalues somewhere else or?
I think if you implement a phase listener to print out each phase step, you'll see that this.keys is evaluated twice during the LifeCycle. Probably once during Render Response, and the other during Restore View or something. I would avoid putting application logic within property calculations as it can be triggered at times you would not think it should be unless you are very in tuned with the application lifecycle.
I actually see the submit two or three times on some controls. I have heard that it is an anomalie in the JSP engine that has not been resolved.
What I do is write the vec to a request scope variable after it is computed. then add logic before it is computed to fetch the request scope variable and if it exisits, return it instead of recomputing the value.
After a bit of testing i gave up calling my own partialrefreshget method.the extlib dojo toolbar contains a onclick event which is triggerd when on a node the submitvalue is set. In this onclik event i added code like
Var v = context.getsubmittedvaleu();
If("action".equals(v)){
// do stuff that changes the dataset..
}
The event handler is set to partial refresh a datatable wich receives the new data. This is a much cleaner implementation than checking the submittedvalue in the datasource ( as stated by (jeremy hodge).
This way the datasource is only refreshed once.
As a sidenote i would like add that it would be nice to add such an event directly to the treenode(s) as I would do in standard java swing /awt dev by adding a controllistener to a button.

Categories