We've used the Widget API in creating inline Ads and Maps widgets, they are both implemented as IFRAMEs inside CKEditor (which is an IFRAME also). But since IFRAMEs have some performance overhead, we want to refactor our code for faster page rendering in CKEditor's View mode.
The widget's template is like this: <div class=map-widget>, and we just inject a map IFRAME inside of it, that is, append an IFRAME with source linking to an stand-alone HTML file containing the Map JS library and an inner map DIV (<div class=map-div>). Note that mapping libraries (Google Maps, Leaflet, OpenLayers, etc), at a minimum, requires the target map DIV container. The map IFRAME is also injected when the user clicks the OK button in the Dialog window (containing the map parameters).
To improve performance, we want now to totally remove the map IFRAME. Since the widget template is already using a DIV (<div class=map-widget>) somehow it could be used directly as the map container, so that we don't need to append an IFRAME anymore.
Our existing setup has this context: a map IFRAME/window embedded in a CKEditor IFRAME/window, which is also embedded in a web page. What we want is this setup: a map DIV embedded in a CKEditor IFRAME/window, which is also embedded in a web page.
Our target setup requires that the map library to be injected inside the CKEditor IFRAME/window and use <div class=map-widget> both as widget's root template and map container. But, upon injecting the Map library in the CKEditor IFRAME's head, the map will not render properly, and apparently, the window object referenced inside the CKEditor's IFRAME is not the CKEditor window but the window of the containing page of the CKEditor. The window object is needed since the Map library attaches map variables/methods ideally in the window where the map DIV container is declared. Hence, in order for the map to work inside the CKEditor, the window referenced must be the CKEditor window, and not the one in the containing page.
Is this the right approach for our refactoring? Are we missing something? And/or is there a way to pass the desired window context to the imported map library (which is contained in a separate JS file)? One intriguing thing to note also is why is that our previous setup works, that is, a map inside an IFRAME which is embedded in CKEditor IFRAME which is then embedded in a web page.
window object referenced
I'm not 100% sure what you mean by this (I'm lost which window is which :D), but I guess that you execute script in the main window and thus the window object refers to the "outer" window. If you inserted script tag into "inner" window (inside the iframe), then it would be executed by browser in the inner window's context. I'm rather sure that there are no other dependencies and CKEditor cannot affect this - only the document to which script has been appended matters.
As for the approach - this is a much harder question. Iframes have one great feature, which makes things much simpler - they hide DOM changes from editor. Editor sees only an <iframe> tag and inside this iframe scripts can freely modify DOM and this will not affect editor's undo manager. When you pull out that content from the iframe to an element contained directly in editor's editable element, you must handle undo manager integration because otherwise it will be a total mess. And this is very challenging. I've had a "pleasure" to debug many issues related to undo manger and this is always a hell.
Iframes have one big issue, though (apart from generally worse performance). They are unloaded when detached from DOM, so some operations on content must reload them. We struggled with this many times too, but still use iframes for their great sandboxing feature. E.g. it would be possible to implement MathJax plugin without iframes because this plugin makes a total mess in the DOM. Mess which would break many editing features. The performance is poor though and we're considering adding MathJax iframes' cache, so we're able to create those embedded documents faster.
You need to analyse your situation. If the script you embed makes asynchronous DOM operations, then better use iframe, because you won't be able to easily control undo manager. You could try some tricks with editor#getSnapshot() event (e.g. removing widget element's content using regexp or parsing if performance is not a problem), but it's not easy as well (though sometimes it will be the only possible solution at all).
Piotrek (Reinmar) Koszuliński
CKEditor JavaScript Developer
--
CKSource - http://cksource.com
--
Follow CKEditor on: Twitter | Facebook | Google+
Thanks for your prompt reply,
Thanks for your prompt reply, I really appreciate it. I've read your comment just hours after you replied but I haven't that much chance to reply back. As an update in our issue, it turned that there are 2 major root causes (for the benefit of other users also):
1.) When we injected the Map library (specifically, the Nokia HERE core JS file) after the user clicks the OK button of the Dialog window (commit event), it will call 8 other JS helper files. The core/main Nokia HERE file is inserted in the CKEditor HEAD, but unfortunately the helper files were injected in the main page's HEAD. Upon further investigation, it turned out that the helper files were loaded asynchronously and when the widget element has been attached to the CKEditor's DOM, the helper files were not still loaded, and the flow of control returns to the main page's window already, consequently, they were attached there. One good solution that worked is to load the Nokia HERE core JS files in the editor's init method and use the editor's instanceReady event handling for the precise loading of the asynchronous files. After that, the Nokia HERE core file and its 8 helper files were loaded in the same CKEditor window as intended.
2.) After we resolved #1, the browser's console error notices were gone. But surprisingly, the map was still not behaving as intended (some button and behaviors were missing). Upon further debugging and research, it turned out that we need to attach a widget's ready event handler since if we don't do that we're processing an element that is not yet fully attached to CKEditor's DOM that's why the map rendering is also not properly rendered. After that, the map works as intended.
As a result, we're now implementing an iframeless map widget, and we're committed to make it work. We have considered also your suggestion to carefully test the behavior of the widget with respect to the Undo Manager, and fortunately the widget still behaves as intended, while trying also copying and pasting widgets and other related operations.
Thanks for your valuable input and insights. I, personally, also want to make the iframeless setup work as intended so that I could reimplement my Leaflet Maps plugin also later. We'll consult you again if we encounter a major stopper.
We consider this issue as 'Solved' (and also mofidied this issue's title to have a more meaningful one). More power to CKEditor team and Widget API. Cheers. :)