How do I sendKeys to a ui-ace instance with Protractor? - javascript

I want to test some keypresses against a ui-ace instance. However, Protractor can't get focus on the ace element. Ace includes a <textarea> that I can get focus on (and thus send keys to) but ui-ace doesn't recognize keys inputted there (and subsequently doesn't update the model).
Selecting method (by.model, etc) has no impact.
My current workaround is to use evaluate to set the scope value manually, but this breaks the point of using Protractor.

Good question! Seems that the workaround is double clicking first.
The following works at least on Chrome:
"use strict";
describe('Testing ui-ace', function() {
var divInput = $('div.ace_content');
var inputElm = $('textarea.ace_text-input');
it('opens a ui-ace page', function() {
browser.ignoreSynchronization = true; // sorry but no angular page
browser.get('http://angular-ui.github.io/ui-ace/');
browser.sleep(3000); // sorry but no angular page
});
it('starts editing', function() {
browser.actions().doubleClick(divInput).perform();
});
it('enters some text', function() {
inputElm.sendKeys('Hola');
browser.sleep(5000); // to let you see the result
});
});

Related

Testing event handlers

I have an event handler that modifies some global variable based on the click action. How can I test it? For example:
function initEvent() {
function enable() {
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
if (this.checked) {
enable();
} else {
disable();
}
});
};
This is the function I am calling after the HTML has been rendered. It calls enable() and disable() functions based on the user action. I want to test these functions and check if they behave correctly. How can I do that?
You want to test your code. You should never test code with console.log or alert. These are great to debug something on the fly, but they are not test tools. These promote manual testing, where you need to manually run the code and see that pass, that's horrible time waste.
You should use Jasmine in this case (you can use other testing frameworks, though Jasmine is super easy) to test your code. You can setup browser tests or headless tests, which is out of the scope of this question, there are tons of tutorials on the subject.
Now, in order to test this code, I assume that the Context has a static method get which returns an array which is on the Context IIFE scope. If the case is different feel free to fiddle around with the specs and make it serve your needs, or alternatively if you get stuck, update this question or ask another one on Stackoverflow.
I have setup Jasmine, with jasmine-fixture to test your code, the jQuery click event behavior. In this plunk you will find everything you need.
I am using the browser to test the code, so I need jasmine's html reporter.
The real tests are in script-spec.js, where I am using Jasmine's API, by describing a spec suite (with describe) and defining each spec with it method.
In beforeEach I prepare the code to run before each spec executes. Essentially here, I create a simple div with #content id and a child input element of type checkbox with #element id. I do this by using the
setFixtures('<div id="content"><input type="checkbox" id="element" /></div>');
Which is a method jasmine-fixture library provides.
Now I can test the code, wiring up the specs:
it("Should return an array with 'aaa' element when #element is checked", function() {
// Arrange
initEvent();
var checkbox = $("#content").find("#element");
// Act
checkbox.click();
// Assert
expect(Context.get('arr').length).toBe(1);
expect(Context.get('arr')).toEqual(['aaa']);
});
I run the initEvent method and get a reference of the checkbox element. In Act section I click the element manually, marking it as checked, which is the normal behavior. In Assert, I test the Context.get('arr') return value.
Again, link to plunk is here.
Hope this helps.
One simple test you can do to test enable, disable and the click handler is to create a function that checks the contents of arr in Context, and call it after each of the functions within the click handler that add something to arr.
The general way to test conditions in your code is with assertions which will throw an error if the condition you pass into them is false. You can use console.assert just for that:
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'aaa',
'enable() works'
);
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'bbb',
'disable() works'
);
}
});
If any of the tests run after you click your element, you know initEvent assigned the click handler and it works. Then, you just toggle the checked flag to test enable()/disable() as well.
If there are no errors in your browser console, the tests have passed. Otherwise, there will be an error in your console containing the message passed as the second argument to console.assert.
You could even make a helper function to simplify the testing a bit:
function assertLastElementInContextArr(elem, msg) {
var arr = Context.get('arr');
// assert last item in `arr` is equal to `elem`
console.assert(arr[arr.length - 1] === elem, msg);
}
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
assertLastElementInContextArr('aaa', 'enable() works');
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
assertLastElementInContextArr('bbb', 'disable() works');
}
});
EDIT based on your comment
But how do I mock the click event? I mean, I want to automatically test all those events, no I have to somehow trigger the click automatically. How do I do that?
If you want to programmatically invoke click events, you can use JS to trigger them in code. Since you're using jQuery, it already comes with a method trigger to do just that.
All you need to do is:
$('#content').trigger('click')
And it will activate your click handler and run the assertions tests from above.
In fact, jQuery even comes with aliased handlers for specific events so you can just do:
$('#content').click();
To automate the testing, you can create a function that will trigger the clicks and set the checked state as well, to test both cases.
function test(checked) {
var elem = $('#content');
elem.prop('checked', checked);
elem.click();
}
Important thing to be careful about is that these events will happen asynchronously so you must do something to manage a proper testing order if you're going to trigger multiple clicks. Otherwise you will set checked to true, trigger the click and then run the second test that will set checked to false before the click events even happen.
For demonstration purposes, here's one way to safely test multiple successive clicks by adding an event handler just for testing and removing it once you're done. One requirement for this to work is to attach the handler after all your other handlers have been attached, to make sure the test handler runs last. Additionally, you can run your assertions here as well to not pollute your code and keep the testing fully separated:
function test(checked, value, msg, done) {
var elem = $('#content');
elem.prop('checked', checked);
// attach a test event handler and trigger the click
elem.on('click', testClick);
elem.click();
// once the click is executed,
// remove the test handler,
// run the assertions and then
// call the callback to signal the test is done
function testClick() {
elem.off('click', runTest);
assertLastElementInContextArr(value, msg);
done();
}
}
// run your code before the tests
initEvent();
// test enable(), then once that's done, test disable()
test(true, 'aaa', 'enable() works', function() {
test(false, 'bbb', 'disable() works', function() {
console.log('All tests finished');
});
});
If you're going to be testing your entire app like this, you'd probably want to use a test framework like QUnit, Mocha, Jasmine which will handle all these async issues for you and give you a nice API to work with.
Just add console.log(<some variable>) or alert(<some variable>) at function calls. e.g.:
function initEvent() {
function enable() {
alert("enable called!");
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
alert("disable called!");
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
alert("click occured!");
if (this.checked) {
enable();
} else {
disable();
}
});
};
Or use your browsers developer tools setting breakpoints at these spots.

How can I disable automatic filtering in selectize.js? Built-in / plugin / modilfy source?

I have a selectize.js drop-down, which loads a list of items from the server using ajax. The server provides an autocomplete from the given string, so I don't need selectize's native filtering. Besides, I really need to turn it off: The server output may be totally different from selectize's one.
The data is fed into JavaScript objects fine, but selectize doesn't even show a popup, since those items doesn't match selectize's filter. How can I disable or modify native filtering and the matches highlighting algorithm? Either with a built-in option, or with a plugin? Or is the only way to go to modify the source?
EDIT:
searchField: false / function() doesn't work (and documentation doesn't mention them as available option values)
EDIT2:
Eventually came up with this trick: Add a fake field to each item, assign a search string to it and tell selectize to use is as a searchField. But obviously, there should a better way, so the question is still open.
I use this solution (if results from server is ordered correctly):
score: function() { return function() { return 1; }; },
or this (if need order)
score: function(search) {
var score = this.getScoreFunction(search);
return function(item) {
return 1 + score(item);
};
},
Sifter uses the score function to filter. Result of score must be > 0.
I solved with onInitialize method in selectize parameters:
$("select").selectize({
onInitialize: function() {
this.$control_input.attr('readonly', true);
}
});
I needed to disable searching so iPhones won't be displaying the keyboard. The solution I settled on makes the search field readonly by hooking into the selectize setup (without modifying the actual source, so selectize is still updatable). Here's the code, if anybody needs it:
// Put this code after you've included Selectize
// but before any selectize fields are initialized
var prevSetup = Selectize.prototype.setup;
Selectize.prototype.setup = function () {
prevSetup.call(this);
// This property is set in native setup
// Unless the source code changes, it should
// work with any version
this.$control_input.prop('readonly', true);
};
So, searching the code, I've found out, that Sifter module (searching/sorting engine, which Selectize relies on), it does have an option to disable filtering, we just need to forward it up, to Selectize. I can suggest the following patch:
Locate the function getSearchOptions() in Selectize main .js file:
https://github.com/brianreavis/selectize.js/blob/master/dist/js/selectize.js
Here is the before:
getSearchOptions: function () {
var settings = this.settings;
var sort = settings.sortField;
if (typeof sort === 'string') {
sort = [{field: sort}];
}
return {
fields: settings.searchField,
conjunction: settings.searchConjunction,
sort: sort
};
}
And here's the after: (added a comma, 5 lines of comments, and the patch itself)
...
getSearchOptions: function () {
var settings = this.settings;
var sort = settings.sortField;
if (typeof sort === 'string') {
sort = [{field: sort}];
}
return {
fields: settings.searchField,
conjunction: settings.searchConjunction,
sort: sort,
// A patch to allow to disable native filtering, in the case,
// when we want to provide search results on the server side.
// Negative form of the setting is to avoid changing the standard
// behaviour, (and, possibly, ruining the existing code), when this
// parameter is missing.
filter : !settings.dontFilter
};
},
...
Sorry, I just don't have time to create a branch on Github, the project deadline is near, and also, actually not sure, that I'll manage to be a fine contributor for now, due to some lack of experience working in Github. So, just posting a quick workaround.
With a little bit of CSS and a little bit of JS we can create this. And it looks perfect.
var select = $("#my-select-input");
$(select).next().find("div.selectize-input").addClass("no-searchable"); // Adding style to the div
$(select).next().find("div.selectize-input > input").addClass("no-searchable"); // Adding style to the input
$(select).next().find("div.selectize-input > input").prop("readonly", true); // Setting the input to read-only
$(select).next().find("div.selectize-input > input").prop("inputmode", "none"); // Guarantee in case it opens on the cell phone and click on the input no keyboard is opened
$(select).next().find("div.selectize-input > input").focus(function () { // Hack for when the search input gets the focus it will automatically blur.
$(this).blur();
});
.no-searchable {
cursor: pointer !important;
background-color: #FFFFFF !important;
}
.has-items input.no-searchable {
width: 1px !important;
}

Qunit test alternates between pass and fail on page refresh

I have a two tests that are causing side effects with each other. I understand why as I am replacing a jQuery built-in function that is being called internally in the second test. However what I don't understand is why the test alternately passes and fails.
This question is similar However, I am not doing anything directly on the qunit-fixture div.
Here are my tests
test('always passing test', function() { // Always passes
var panelId = '#PanelMyTab';
var event = {};
var ui = {
tab: {
name: 'MyTab',
},
panel: panelId,
};
$('<div id="' + panelId + '">')
.append('Test')
.append('Show Form')
.appendTo('#qunit-fixture');
jQuery.fn.on = function(event, callback) {
ok(this.selector == panelId + ' .export', 'Setting export click event');
equal(callback, tickets.search.getReport, 'Callback being set');
};
loadTab(event, ui);
});
test('alternately passing and failing', function() { // Alternates between passing and failing on page refresh
expect(5);
var testUrl = 'test';
$('<div class="ui-tabs-panel">')
.append('Get Report')
.append('<form action="notest" target="" class="ticketSearch"></form>')
.appendTo('#qunit-fixture');
// Setup form mocking
$('form.ticketSearch').submit(function() {
var urlPattern = new RegExp(testUrl + '$');
ok(urlPattern.test($(this).prop('action')), 'Form action set to link href');
equal($(this).prop('target'), '_blank', 'Open form on a new page');
});
var event = {
target: 'a#getReport',
};
var result = getReport(event);
var form = $('form.ticketSearch');
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
});
The tests will start off passing but when I refresh they will alternate between passing and failing.
Why is this happening? Even adding GET parameters to the url result in the same behavior on the page.
In the failing cases, the test is failing because internal jQuery is calling .on() when the submit() handler is set. But why isn't the test always failing in that case? What is the browser doing that a state is being retained during page refresh?
Update:
Here is the code that is being tested:
var tickets = function() {
var self = {
loadTab: function(event, ui) {
$(panel).find('.export').button().on('click', this.getReport);
},
search: {
getReport: function(event) {
var button = event.target;
var form = $(button).closest('div.ui-tabs-panel').find('form.ticketSearch').clone(true);
$(form).prop('action', $(button).prop('href'));
$(form).prop('target', '_blank');
$(form).submit();
return false;
}
}
};
return self;
}();
I've modified #Ben's fiddle to include your code with both of your tests. I modified some of your code to make it run correctly. When you hit the run button all of the tests will pass. When you hit the run button again, the second test ("alternately passing and failing") will fail -- this is basically simulating your original issue.
The issue is your first test ("always passing test") alters the global state by replacing the jQuery.fn.on function with an overridden one. Because of this, when the tests are run in order, the second test ("alternately passing and failing") uses the incorrect overridden jQuery.fn.on function and fails. Each unit test should return the global state back to its pre-test state so that other tests can run based on the same assumptions.
The reason why it's alternating between pass and fail is that under the hood QUnit always runs failed tests first (it remembers this somehow via cookie or local storage, I'm not exactly sure). When it runs the failed tests first, the second test runs before the first one; as a result, the second test gets jQuery's native on function and works. When you run it a third time, the tests will run in their "original" order and the second test will use the overridden on function and fail.
Here's the working fiddle. I've add the fix to "un-override" the on function after the test by caching the original var jQueryOn = jQuery.fn.on; function and resetting it at the end of the test via: jQuery.fn.on = jQueryOn;. You can probably better implement this using QUnit's module teardown() method instead.
You can check out https://github.com/jquery/qunit/issues/74 for more info.
I'm not sure I can solve this without some more info, but I can point out some possible issues.
The first test seems to have invalid syntax on line 2
var panelId = '#PanelMyTab');
But that's probably a type mistake, seeing as you say the first always passes.
I'm assuming that for the first test to pass(and be valid) the loadTab(event,ui) must run the jQuery.fn.on(), without it no assertions have been run. Which doing some testing with jQuery UI Tabs, seems to be the case (just not sure if it was your intention).
I'm not sure it's advisable putting these assertions within that function, and you must understand that you have overwritten the jquery function with a function that doesn't do anything, so it's likely to cause issues.
You seem to be doing something similar in the second test, you are expecting 5 assertions, but I can only see how the final 3 can be run
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
The other 2 are within a submit function that doesn't look like it is invoked as part of the test.
Remember these tests are synchronous so it won't wait for you to hit submit before running the test and failing.
Here is an example
test('asynchronous test', function() {
setTimeout(function() {
ok(true);
}, 100)
})
Would fail as the ok is run 100ms after the test.
test('asynchronous test', function() {
// Pause the test first
stop();
setTimeout(function() {
ok(true);
// After the assertion has been called,
// continue the test
start();
}, 100)
})
The stop() tells qunit to wait and the start() to go!
There is also a asyncTest() detailed in the api here
Finally, it seems like you are trying to debug your code with these tests. It would be much easier to use chrome developer tools or firebug in firefox to set breakpoints on your code, and use console.log() and console.dir() to output information.
That being said I have no idea how it works for you at all, so I could be missing something :) If you're still stuck, see if you can add some more of the surrounding code and what your trying to achieve. Hope this helps.
PS: there is also a }; at the end which is invalid in the code you have given us, probably relevant in the actual application though ;)

Redisplay results list on AutoCompleteExtender through javascript

I have a working AutoCompleteExtender implementation.
What I want, is that if I have exited the text box, and the list of items have dissappeared, I want to re-display the list from javascript code without having to write something in the text box again (just redisplay list based on current filter value in text box by click on a button or something). I know how to get the AutoCompleteExtender Behaviour object from code, so all I need is to know the javascript API on that object that enables me to redisplay the list.
I have tried this as suggested in the comments on this answer, but not working:
AutoCompleteEx.showPopup();
I have also tried this as suggested in this answer, but not working:
AutoCompleteEx._onTimerTick(AutoCompleteEx._timer, Sys.EventArgs.Empty);
EDIT:
After some investigation in the back end code used by the AutoComplete, I think maybe the problem is that once shown, it checks on future calls if the value in the search box has changed since last time, and if not it doesn't show it again. I have not found out how to come around this. I have tried different approaches to reset the value, and then set the value again, but with no success.
Enjoy :). That's was an interesting task.
function redisplayAutocompleteExtender() {
var extender = $find("AutoCompleteEx");
var ev = { keyCode: 65, preventDefault: function () { }, stopPropagation: function () { } };
extender._currentPrefix = "";
extender._onKeyDown.call(extender, ev);
}
Or you can set EnableCaching property to true on extender and use script below. This solution allows to avoid additional web service call.
function redisplayAutoComplete() {
var extender = $find("AutoCompleteEx");
var textBox = extender.get_element();
textBox.focus();
var showSuggestions = function(){
extender._update.call(extender, textBox.value, extender._cache[textBox.value], true);
};
setTimeout(showSuggestions, 0);
}

CKEditor instance already exists

I am using jquery dialogs to present forms (fetched via AJAX). On some forms I am using a CKEditor for the textareas. The editor displays fine on the first load.
When the user cancels the dialog, I am removing the contents so that they are loaded fresh on a later request. The issue is, once the dialog is reloaded, the CKEditor claims the editor already exists.
uncaught exception: [CKEDITOR.editor] The instance "textarea_name" already exists.
The API includes a method for destroying existing editors, and I have seen people claiming this is a solution:
if (CKEDITOR.instances['textarea_name']) {
CKEDITOR.instances['textarea_name'].destroy();
}
CKEDITOR.replace('textarea_name');
This is not working for me, as I receive a new error instead:
TypeError: Result of expression 'i.contentWindow' [null] is not an object.
This error seems to occur on the "destroy()" rather than the "replace()". Has anyone experienced this and found a different solution?
Is is possible to 're-render' the existing editor, rather than destroying and replacing it?
UPDATED
Here is another question dealing with the same problem, but he has provided a downloadable test case.
For this to work you need to pass boolean parameter true when destroying instance:
var editor = CKEDITOR.instances[name];
if (editor) { editor.destroy(true); }
CKEDITOR.replace(name);
function loadEditor(id)
{
var instance = CKEDITOR.instances[id];
if(instance)
{
CKEDITOR.remove(instance);
}
CKEDITOR.replace(id);
}
I had this problem too, but I solved it in a much simpler way...
I was using the class "ckeditor" in my jQuery script as the selector for which textareas I wanted use for CKEditor. The default ckeditor JS script also uses this class to identify which textareas to use for CKEditor.
This meant there is a conflict between my jQuery script and the default ckeditor script.
I simply changed the class of the textarea and my jQuery script to 'do_ckeditor'(you could use anything except "ckeditor") and it worked.
This is the simplest (and only) solution that worked for me:
if(CKEDITOR.instances[editorName])
delete CKEDITOR.instances[editorName];
CKEDITOR.replace(editorName);
Deleting this entry in the array prevents this form safety check from destroying your application.
destroy() and remove() did not work for me.
Perhaps this will help you out - I've done something similar using jquery, except I'm loading up an unknown number of ckeditor objects. It took my a while to stumble onto this - it's not clear in the documentation.
function loadEditors() {
var $editors = $("textarea.ckeditor");
if ($editors.length) {
$editors.each(function() {
var editorID = $(this).attr("id");
var instance = CKEDITOR.instances[editorID];
if (instance) { instance.destroy(true); }
CKEDITOR.replace(editorID);
});
}
}
And here is what I run to get the content from the editors:
var $editors = $("textarea.ckeditor");
if ($editors.length) {
$editors.each(function() {
var instance = CKEDITOR.instances[$(this).attr("id")];
if (instance) { $(this).val(instance.getData()); }
});
}
UPDATE: I've changed my answer to use the correct method - which is .destroy(). .remove() is meant to be internal, and was improperly documented at one point.
var e= CKEDITOR.instances['sample'];
e.destroy();
e= null;
I've had similar issue where we were making several instances of CKeditor for the content loaded via ajax.
CKEDITOR.remove()
Kept the DOM in the memory and didn't remove all the bindings.
CKEDITOR.instance[instance_id].destroy()
Gave the error i.contentWindow error whenever I create new instance with new data from ajax. But this was only until I figured out that I was destroying the instance after clearing the DOM.
Use destroy() while the instance & it's DOM is present on the page, then it works perfectly fine.
For ajax requests,
for(k in CKEDITOR.instances){
var instance = CKEDITOR.instances[k];
instance.destroy()
}
CKEDITOR.replaceAll();
this snipped removes all instances from document.
Then creates new instances.
The i.contentWindow is null error seems to occur when calling destroy on an editor instance that was tied to a textarea no longer in the DOM.
CKEDITORY.destroy takes a parameter noUpdate.
The APIdoc states:
If the instance is replacing a DOM element, this parameter indicates whether or not to update the element with the instance contents.
So, to avoid the error, either call destroy before removing the textarea element from the DOM, or call destory(true) to avoid trying to update the non-existent DOM element.
if (CKEDITOR.instances['textarea_name']) {
CKEDITOR.instances['textarea_name'].destroy(true);
}
(using version 3.6.2 with jQuery adapter)
This is what worked for me:
for(name in CKEDITOR.instances)
{
CKEDITOR.instances[name].destroy()
}
CKEDITOR.instances = new Array();
I am using this before my calls to create an instance (ones per page load). Not sure how this affects memory handling and what not. This would only work if you wanted to replace all of the instances on a page.
I've prepared my own solution based on all above codes.
$("textarea.ckeditor")
.each(function () {
var editorId = $(this).attr("id");
try {
var instance = CKEDITOR.instances[editorId];
if (instance) { instance.destroy(true); }
}
catch(e) {}
finally {
CKEDITOR.replace(editorId);
}
});
It works perfectly for me.
Sometimes after AJAX request there is wrong DOM structure.
For instace:
<div id="result">
<div id="result>
//CONTENT
</div>
</div>
This will cause issue as well, and ckEditor will not work. So make sure that you have correct DOM structure.
i had the same problem with instances, i was looking everywhere and finally this implementation works for me:
//set my instance id on a variable
myinstance = CKEDITOR.instances['info'];
//check if my instance already exist
if (myinstance) {
CKEDITOR.remove(info)
}
//call ckeditor again
$('#info').ckeditor({
toolbar: 'Basic',
entities: false,
basicEntities: false
});
You can remove any ckeditor instance by remove method of ckeditor. Instance will be id or name of the textarea.
if (CKEDITOR.instances[instance_name]) {
CKEDITOR.remove(CKEDITOR.instances[instance_name]);
}
Indeed, removing the ".ckeditor" class from your code solves the issue. Most of us followed the jQuery integration example from the ckeditor's documentation:
$('.jquery_ckeditor')
.ckeditor( function() { /* callback code */ }, { skin : 'office2003' } );
and thought "... maybe I can just get rid or the '.jquery_' part".
I've been wasting my time tweaking the callback function (because the {skin:'office2003'} actually worked), while the problem was coming from elsewhere.
I think the documentation should mention that the use of "ckeditor" as a class name is not recommended, because it is a reserved keyword.
Cheers.
I learned that
delete CKEDITOR.instances[editorName];
by itself, actually removed the instance. ALL other methods i have read and seen, including what was found here at stackoverflow from its users, did not work for me.
In my situation, im using an ajax call to pull a copy of the content wrapped around the and 's. The problem happens to be because i am using a jQuery .live event to bind a "Edit this document" link and then applying the ckeditor instance after success of the ajax load. This means, that when i click another link a link with another .live event, i must use the delete CKEDITOR.instances[editorName] as part of my task of clearing the content window (holding the form), then re-fetching content held in the database or other resource.
I hade the same problem with a jQuery Dialog.
Why destroy the instance if you just want to remove previous data ?
function clearEditor(id)
{
var instance = CKEDITOR.instances[id];
if(instance)
{
instance.setData( '' );
}
}
I chose to rename all instances instead of destroy/replace - since sometimes the AJAX loaded instance doesn't really replace the one on the core of the page... keeps more in RAM, but less conflict this way.
if (CKEDITOR && CKEDITOR.instances) {
for (var oldName in CKEDITOR.instances) {
var newName = "ajax"+oldName;
CKEDITOR.instances[newName] = CKEDITOR.instances[oldName];
CKEDITOR.instances[newName].name = newName;
delete CKEDITOR.instances[oldName];
}
}
I am in the situation where I have to controls that spawn dialogs, each of them need to have a ckeditor embedded inside these dialogs. And it just so happens the text areas share the same id. (normally this is very bad practice, but I have 2 jqGrids, one of assigned items and another of unassigned items.) They share almost identical configuration. Thus, I am using common code to configure both.
So, when I load a dialog, for adding rows, or for editing them, from either jqGrid; I must remove all instances of CKEDITOR in all textareas.
$('textarea').each(function()
{
try
{
if(CKEDITOR.instances[$(this)[0].id] != null)
{
CKEDITOR.instances[$(this)[0].id].destroy();
}
}
catch(e)
{
}
});
This will loop over all textareas, and if there is a CKEDITOR instance, then destroy it.
Alternatively if you use pure jQuery:
$('textarea').each(function()
{
try
{
$(this).ckeditorGet().destroy();
}
catch(e)
{
}
});
remove class="ckeditor" , it might have triggered ckeditor initialization
I had the same problem where I was receiving a null reference exception and the word "null" would be displayed in the editor. I tried a handful of solutions, including upgrading the editor to 3.4.1 to no avail.
I ended up having to edit the source. At about line 416 to 426 in _source\plugins\wysiwygarea\plugin.js, there's a snippet like this:
iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' + ... + '></iframe>' );
In FF at least, the iframe isn't completely instantiated by the time it's needed. I surrounded the rest of the function after that line with a setTimeout function:
iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' + ... + '></iframe>' );
setTimeout(function()
{
// Running inside of Firefox chrome the load event doesn't bubble like in a normal page (#5689)
...
}, 1000);
};
// The script that launches the bootstrap logic on 'domReady', so the document
...
The text renders consistently now in the modal dialogs.
To support dynamic (Ajax) loading of forms (without page refreshes between) which contain textareas with the same (same form is called again) or different ID's (previously unloaded form) and convert them to CKEditor elements I did the following (using the JQuery adapter):
After the page has finished every Ajax call that delivers a textarea to be converted, I make a call to the following function:
setupCKeditor()
This looks like this (it assumes your textareas to be converted to RTE's have class="yourCKClass"):
/* Turns textAreas into TinyMCE Rich Text Editors where
* class: tinymce applied to textarea.
*/
function setupCKeditor(){
// define editor configuration
var config = {skin : 'kama'};
// Remove and recreate any existing CKEditor instances
var count = 0;
if (CKEDITOR.instances !== 'undefined') {
for(var i in CKEDITOR.instances) {
var oEditor = CKEDITOR.instances[i];
var editorName = oEditor.name;
// Get the editor data.
var data = $('#'+editorName).val();
// Check if current instance in loop is the same as the textarea on current page
if ($('textarea.yourCKClass').attr('id') == editorName) {
if(CKEDITOR.instances[editorName]) {
// delete and recreate the editor
delete CKEDITOR.instances[editorName];
$('#'+editorName).ckeditor(function() { },config);
count++;
}
}
}
}
// If no editor's exist in the DOM, create any that are needed.
if (count == 0){
$('textarea.yourCKClass').each( function(index) {
var editorName = $(this).attr('id');
$('#'+editorName).ckeditor(function() { $('#'+editorName).val(data); },config);
});
}
}
I should mention that the line:
$('#'+editorName).ckeditor(function() { $('#'+editorName).val(data); },config);
could (and should) be simply:
$('#'+editorName).ckeditor(function() { },config);
however I found that the editor would often show the correct content for a second after loading and them empty the editor of the desired content. So that line with the callback code forces the CKEditor content to be the same as the originating textarea content. Causes a flicker when used. If you can avoid using it, do so..
I had exactly the same problem like jackboberg. I was using dynamic form loading into jquery dialogs then attaching various widgets (datepickers, ckeditors etc...).
And I tried all solutions noted above, none of them worked for me.
For some reason ckeditor only attached the first time I loaded form, the second time I got exactly the same error message jackboberg did.
I've analyzed my code and discovered that if you attach ckeditor in "mid-air" that is while form content is still not placed into dialog, ckeditor won't properly attach its bindings. That is since ckeditor is attached in "mid-air", second time you attach it in "mid-air"... poof ... an error is thrown since the first instance was not properly removed from DOM.
This was my code that ptoduced the error:
var $content = $(r.content); // jQuery can create DOM nodes from html text gotten from <xhr response> - so called "mid-air" DOM creation
$('.rte-field',$content).ckeditor(function(){});
$content.dialog();
This is the fix that worked:
var $content = $(r.content).dialog(); // first create dialog
$('.rte-field',$content).ckeditor(function(){}); // then attach ckeditor widget
I ran into this exact same thing and the problem was that the wordcount plugin was taking too long to initialize. 30+ seconds. The user would click into the view displaying the ckeditor, then cancel, thereby ajax-loading a new page into the dom. The plugin was complaining because the iframe or whatever contentWindow is pointing to was no longer visible by the time it was ready to add itself to the contentWindow. You can verify this by clicking into your view and then waiting for the Word Count to appear in the bottom right of the editor. If you cancel now, you won't have a problem. If you don't wait for it, you'll get the i.contentWindow is null error. To fix it, just scrap the plugin:
if (CKEDITOR.instances['textarea_name'])
{
CKEDITOR.instances['textarea_name'].destroy();
}
CKEDITOR.replace('textarea_name', { removePlugins: "wordcount" } );
If you need a word counter, register for the paste and keyup events on the editor with a function that counts the words.
For those using the jquery "adapter" and having trouble (as I was), as super hackish yet working solution is to do something like this:
// content editor plugin
(function($){
$.fn.contentEditor = function( params ) {
var xParams = $.extend({}, $.fn.contentEditor.defaultParams, params);
return this.each( function() {
var $editor = $(this);
var $params = $.extend({}, xParams, $editor.data());
// if identifier is set, detect type based on identifier in $editor
if( $params.identifier.type ) {
$params.type = $editor.find($params.identifier.type).val();
}
$editor.data('type', $params.type);
// edit functionality
editButton = $('<button>Edit Content</button>').on('click',function(){
// content container
var $cc = $('#' + $editor.data('type'));
// editor window
var $ew = $('<form class="editorWindow" />');
$ew.appendTo('body');
// editor content
$ec = $('<textarea name="editorContent" />').val($cc.html());
$ec.appendTo($ew);
$ec.ckeditor();
//$ec.ckeditorGet().setMode('source');
$ew.dialog({
"autoOpen": true,
"modal": true,
"draggable": false,
"resizable": false,
"width": 850,
"height": 'auto',
"title": "Content Editor",
"buttons": {
'Save': function() {
$cc.html( $ec.val() );
$ec.ckeditorGet().destroy();
$ew.remove();
},
'Cancel / Close': function() {
$ec.ckeditorGet().destroy();
$ew.remove();
}
},
'close': function() {
$ec.ckeditorGet().destroy();
},
'open': function() {
$ew.find('a.cke_button_source').click();
setTimeout(function(){
$ew.find('a.cke_button_source.cke_on').click();
}, 500);
}
});
return false;
});
editButton.appendTo( $editor );
});
}
// set default option values
$.fn.contentEditor.defaultParams = {
'identifier': {
'type': 'input[name="type"]'
}
};
})(jQuery);
$(function(){
$('form.contentEditor').contentEditor();
});
The key point being this part:
'open': function() {
$ew.find('a.cke_button_source').click();
setTimeout(function(){
$ew.find('a.cke_button_source.cke_on').click();
}, 500);
}
This fixes the problem with the editor text not being visible the next time you open the dialog. I realise this is very hackish, but considering that most of these are going to be used for admin tools, I don't think that's as big a concern as it normally would be.. and this works, so hopefully it will save someone some time ;)
This is the fully working code for jquery .load() api and ckeditor, in my case I am loading a page with ckeditor into div with some jquery effects. I hope it will help you.
$(function() {
runEffect = function(fileload,lessonid,act) {
var selectedEffect = 'drop';
var options = {};
$( "#effect" ).effect( selectedEffect, options, 200, callback(fileload,lessonid,act) );
};
function callback(fileload,lessonid,act) {
setTimeout(function() {//load the page in effect div
$( "#effect" ).load(fileload,{lessonid:lessonid,act:act});
$("#effect").show( "drop",
{direction: "right"}, 200 );
$("#effect").ajaxComplete(function(event, XMLHttpRequest, ajaxOptions) {
loadCKeditor(); //call the function after loading page
});
}, 100 );
};
function loadCKeditor()
{//you need to destroy the instance if already exist
if (CKEDITOR.instances['introduction'])
{
CKEDITOR.instances['introduction'].destroy();
}
CKEDITOR.replace('introduction').getSelection().getSelectedText();
}
});
===================== button for call the function ================================
<input type="button" name="button" id="button" onclick="runEffect('lesson.php','','add')" >
Its pretty simple. In my case, I ran the below jquery method that will destroy ckeditor instances during a page load. This did the trick and resolved the issue -
JQuery method -
function resetCkEditorsOnLoad(){
for(var i in CKEDITOR.instances) {
editor = CKEDITOR.instances[i];
editor.destroy();
editor = null;
}
}
$(function() {
$(".form-button").button();
$(".button").button();
resetCkEditorsOnLoad(); // CALLING THE METHOD DURING THE PAGE LOAD
.... blah.. blah.. blah.... // REST OF YOUR BUSINESS LOGIC GOES HERE
});
That's it. I hope it helps you.
Cheers,
Sirish.
This functions works for me in CKEditor version 4.4.5, it does not have any memory leaks
function CKEditor_Render(CkEditor_id) {
var instance = CKEDITOR.instances[CkEditor_id];
if (CKEDITOR.instances.instance) {
CKEDITOR.instances.instance.destroy();
}
CKEDITOR.replace(CkEditor_id);
}
// call this function as below
var id = 'ckeditor'; // Id of your textarea
CKEditor_Render(id);
CKeditor 4.2.1
There is a lot of answers here but for me I needed something more (bit dirty too so if anyone can improve please do). For me MODALs where my issue.
I was rendering the CKEditor in a modal, using Foundation. Ideally I would have destoryed the editor upon closing, however I didn't want to mess with Foundation.
I called delete, I tried remove and another method but this was what I finally settled with.
I was using textarea's to populate not DIVs.
My Solution
//hard code the DIV removal (due to duplication of CKeditors on page however they didn't work)
$("#cke_myckeditorname").remove();
if (CKEDITOR.instances['myckeditorname']) {
delete CKEDITOR.instances['myckeditorname'];
CKEDITOR.replace('myckeditorname', GetCKEditorSettings());
} else {
CKEDITOR.replace('myckeditorname', GetCKEditorSettings());
}
this was my method to return my specific formatting, which you might not want.
function GetCKEditorSettings()
{
return {
linkShowAdvancedTab: false,
linkShowTargetTab: false,
removePlugins: 'elementspath,magicline',
extraAllowedContent: 'hr blockquote div',
fontSize_sizes: 'small/8px;normal/12px;large/16px;larger/24px;huge/36px;',
toolbar: [
['FontSize'],
['Bold', 'Italic', 'Underline', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink'],
['Smiley']
]
};
}
Try this:
for (name in CKEDITOR.instances)
{
CKEDITOR.instances[name].destroy(true);
}

Categories

Resources