FCKeditor bugs in Dokeos implementation - the debugging process

For the sake of remembering myself of what I did, not being an expert in JavaScript coding but still needing to fix that placeholder bug in Dokeos, I'm writing this part as a real log, not really a well-presented article, but I'm still publishing it in case it would help someone else. In Dokeos, we use FCKeditor. The first problem that comes to mind is that I don't know exactly which version. I didn't integrate it, but based on this feature request, we use 2.2, while the current version (as of today) is 2.6.2. So there are considerable differences here. Julio is working on integrating version 2.6.2 for Dokeos 1.8.6, but I had to get working faster than that on the placeholder bug, that is, with the features we have added (as plugins or new developments) to Dokeos, you are able to insert a MP3 or a video into an HTML document. Well, this only works for adding resources, and the update feature doesn't work (it does with the MP3 player, but not the video). Some of the assumptions I'm taking below might be wrong, but the overall objective of this precise article (as opposed to all other articles on this blog) does not seek the truth but rather the effectiveness of debugging this tool. As I don't know anything about FCKeditor, I'm pretty much starting from scratch. I'm using the Firefox Firebug extension to see what's happening in my code by putting breaks in the editor/js/fckeditorcode_gecko_1.js file, which is apparently where most things are happening. FCKeditor works this way: you include the generic FCKeditor file from inside a document and call a function that will display the editor. The generic file is most likely fckeditor/fckeditor_1.js This file includes fckeditor/editor/js/fck_startup.js, which checks which browser is used and loads either of the two pairs of files below:
  • fckeditor/editor/js/fckeditorcode_gecko_1.js
  • fckeditor/editor/js/fckeditorcode_gecko_2.js
OR
  • fckeditor/editor/js/fckeditorcode_ie_1.js
  • fckeditor/editor/js/fckeditorcode_ie_2.js
The version "1" of these files is actually defining most of the generic functions of FCKeditor. The version "2" just adds a few useful functions (I didn't get that initially, I though version "2" was loaded because it was a recent browser version). For the sake of simplicity, I will only use gecko in my examples below. When you install a plugin into your FCKeditor, the plugin will have to be defined in the configuration file, and it will get picked up (along with its properties and methods) by fckeditorcode_gecko_2.js (var FCKPlugins = ...). Each plugin will define its own methods to deal with common upload, insert and delete of objects defined by the plugin (if this plugin is something to add resources, that is). The tricky thing here is that, although you would think re-editing a page including one of these resources would call one of the plugins methods, it doesn't (at least in this version of FCKeditor). In fact, maybe it does but the hand is very quickly given back to fckeditorcode_gecko_1.js, through the use of a method called oEditor.FCKDocumentProcessors_CreateFakeImage(  ) In this example, I am not giving any parameters to the function, but it generally takes two: a name and an object (the resource). Somehow, however, and I haven't found the link just yet, the switch that decides what type of resource this is and how it should be handled is found in fckeditorcode_gecko_1.js as: For the sake of remembering myself of what I did, not being an expert in JavaScript coding but still needing to fix that placeholder bug in Dokeos, I'm writing this part as a real log, not really a well-presented article, but I'm still publishing it in case it would help someone else. In Dokeos, we use FCKeditor. The first problem that comes to mind is that I don't know exactly which version. I didn't integrate it, but based on this feature request, we use 2.2, while the current version (as of today) is 2.6.2. So there are considerable differences here. Julio is working on integrating version 2.6.2 for Dokeos 1.8.6, but I had to get working faster than that on the placeholder bug, that is, with the features we have added (as plugins or new developments) to Dokeos, you are able to insert a MP3 or a video into an HTML document. Well, this only works for adding resources, and the update feature doesn't work (it does with the MP3 player, but not the video). Some of the assumptions I'm taking below might be wrong, but the overall objective of this precise article (as opposed to all other articles on this blog) does not seek the truth but rather the effectiveness of debugging this tool. As I don't know anything about FCKeditor, I'm pretty much starting from scratch. I'm using the Firefox Firebug extension to see what's happening in my code by putting breaks in the editor/js/fckeditorcode_gecko_1.js file, which is apparently where most things are happening. FCKeditor works this way: you include the generic FCKeditor file from inside a document and call a function that will display the editor. The generic file is most likely fckeditor/fckeditor_1.js This file includes fckeditor/editor/js/fck_startup.js, which checks which browser is used and loads either of the two pairs of files below:
  • fckeditor/editor/js/fckeditorcode_gecko_1.js
  • fckeditor/editor/js/fckeditorcode_gecko_2.js
OR
  • fckeditor/editor/js/fckeditorcode_ie_1.js
  • fckeditor/editor/js/fckeditorcode_ie_2.js
The version "1" of these files is actually defining most of the generic functions of FCKeditor. The version "2" just adds a few useful functions (I didn't get that initially, I though version "2" was loaded because it was a recent browser version). For the sake of simplicity, I will only use gecko in my examples below. When you install a plugin into your FCKeditor, the plugin will have to be defined in the configuration file, and it will get picked up (along with its properties and methods) by fckeditorcode_gecko_2.js (var FCKPlugins = ...). Each plugin will define its own methods to deal with common upload, insert and delete of objects defined by the plugin (if this plugin is something to add resources, that is). The tricky thing here is that, although you would think re-editing a page including one of these resources would call one of the plugins methods, it doesn't (at least in this version of FCKeditor). In fact, maybe it does but the hand is very quickly given back to fckeditorcode_gecko_1.js, through the use of a method called oEditor.FCKDocumentProcessors_CreateFakeImage(  ) In this example, I am not giving any parameters to the function, but it generally takes two: a name and an object (the resource). Somehow, however, and I haven't found the link just yet, the switch that decides what type of resource this is and how it should be handled is found in fckeditorcode_gecko_1.js as:
FCKFlashProcessor.ProcessDocument=function(A) { ...
It looks like this function is called automatically from the editor when someone opens a new page for edtion. This function goes through the DOM element given and looks for EMBED or OBJECT tags, then disects them to find those resources we know are managed by plugins. I have considerably updated this code already, and right now it looks like this (pretty ugly):
var FCKFlashProcessor=new Object(); FCKFlashProcessor.ProcessDocument=function(A) { //Treating tags first is a dirty hack. Because tags are enclosed into <object> tags, // and because we treat <object> tags afterwards, we can do whatever we want here with embed tags // inside the <object> tag and then remove the object tag when we get to it to "clean" it. var B=A.getElementsByTagName('EMBED'); var C; var i=B.length-1; while (i>=0&&(C=B[i--])){ if(C.parentNode && C.parentNode != null) { //check if the parent of this tag is an <object> tag. If so, just leave the process //to the code in the next section, that treats objects specifically if(C.parentNode.nodeName.toString().toLowerCase() == 'object') continue; if (C.src.endsWith('.swf',true)){ var D=C.cloneNode(true); if (FCKBrowserInfo.IsIE){ D.setAttribute('scale',C.getAttribute('scale')); D.setAttribute('play',C.getAttribute('play')); D.setAttribute('loop',C.getAttribute('loop')); D.setAttribute('menu',C.getAttribute('menu')); }; var E=FCKDocumentProcessors_CreateFakeImage('FCK__Flash',D); E.setAttribute('_fckflash','true',0); FCKFlashProcessor.RefreshView(E,C); C.parentNode.insertBefore(E,C); C.parentNode.removeChild(C); } else if (C.src.search('/.flv&/') || C.src.search('/.avi&/') || C.src.search('/.mpg&/') || C.src.search('/.mpeg&/') || C.src.search('/.mov&/') || C.src.search('/.wmv&/') || C.src.search('/.rm&/')){ var D=C.cloneNode(true); if (FCKBrowserInfo.IsIE){ D.setAttribute('scale',C.getAttribute('scale')); D.setAttribute('play',C.getAttribute('play')); D.setAttribute('loop',C.getAttribute('loop')); D.setAttribute('menu',C.getAttribute('menu')); }; var E=FCKDocumentProcessors_CreateFakeImage('FCK__Video',D); E.setAttribute('_fckVideo','true',0); FCKFlashProcessor.RefreshView(E,C); C.parentNode.insertBefore(E,C); C.parentNode.removeChild(C); }; }; }; var B=A.getElementsByTagName('OBJECT'); var C; var i=B.length-1; while (i>=0&&(C=B[i--])){ //look for the <param name="movie" ...> child var F; var j=C.childNodes.length-1; var treated = false; while (j>=0 && (F=C.childNodes[j--]) && treated == false){ if(C.parentNode && C.parentNode!=null && F.name && F.name.toString() == 'movie') { if(F.value.toString()!='' && F.value.toString().length>1) { //we have found an attribute <param name="movie" ...> var Fval = F.value.toString(); if (Fval.endsWith('.mp3',true)){ var D=C.cloneNode(true); if (FCKBrowserInfo.IsIE){ D.setAttribute('scale',C.getAttribute('scale')); D.setAttribute('play',C.getAttribute('play')); D.setAttribute('loop',C.getAttribute('loop')); D.setAttribute('menu',C.getAttribute('menu')); }; var E=FCKDocumentProcessors_CreateFakeImage('FCK__MP3',D); E.setAttribute('_fckmp3','true',0); FCKFlashProcessor.RefreshView(E,C); C.parentNode.insertBefore(E,C); C.parentNode.removeChild(C); treated = true; }else if (Fval.search('/.avi&/') || Fval.search('/.mpg&/') || Fval.search('/.mpeg&/') || Fval.search('/.mov&/') || Fval.search('/.wmv&/') || Fval.search('/.rm&/')){ var D=C.cloneNode(true); if (FCKBrowserInfo.IsIE){ D.setAttribute('scale',C.getAttribute('scale')); D.setAttribute('play',C.getAttribute('play')); D.setAttribute('loop',C.getAttribute('loop')); D.setAttribute('menu',C.getAttribute('menu')); }; var E=FCKDocumentProcessors_CreateFakeImage('FCK__Video',D); E.setAttribute('_fckVideo','true',0); FCKFlashProcessor.RefreshView(E,C); C.parentNode.insertBefore(E,C); C.parentNode.removeChild(C); treated = true; }; } } else if(C.parentNode && C.parentNode!=null && F.name && F.name.toString() == 'FlashVars') { if(F.value.toString().length>1) { //we have found an attribute <param name="FlashVars" ...> var Fval = F.value.toString(); if (Fval.endsWith('.mp3',true)){ var D=C.cloneNode(true); if (FCKBrowserInfo.IsIE){ D.setAttribute('scale',C.getAttribute('scale')); D.setAttribute('play',C.getAttribute('play')); D.setAttribute('loop',C.getAttribute('loop')); D.setAttribute('menu',C.getAttribute('menu')); }; var E=FCKDocumentProcessors_CreateFakeImage('FCK__MP3',D); E.setAttribute('_fckmp3','true',0); FCKFlashProcessor.RefreshView(E,C); C.parentNode.insertBefore(E,C); C.parentNode.removeChild(C); treated = true; }else if (Fval.search('/.flv&/')) { var D=C.cloneNode(true); if (FCKBrowserInfo.IsIE){ D.setAttribute('scale',C.getAttribute('scale')); D.setAttribute('play',C.getAttribute('play')); D.setAttribute('loop',C.getAttribute('loop')); D.setAttribute('menu',C.getAttribute('menu')); }; var E=FCKDocumentProcessors_CreateFakeImage('FCK__Video_flv',D); E.setAttribute('_fckVideo','true',0); FCKFlashProcessor.RefreshView(E,C); C.parentNode.insertBefore(E,C); C.parentNode.removeChild(C); treated = true; }else if ( Fval.search('/.avi&/') || Fval.search('/.mpg&/') || Fval.search('/.mpeg&/') || Fval.search('/.mov&/') || Fval.search('/.wmv&/') || Fval.search('/.rm&/')){ var D=C.cloneNode(true); if (FCKBrowserInfo.IsIE){ D.setAttribute('scale',C.getAttribute('scale')); D.setAttribute('play',C.getAttribute('play')); D.setAttribute('loop',C.getAttribute('loop')); D.setAttribute('menu',C.getAttribute('menu')); }; var E=FCKDocumentProcessors_CreateFakeImage('FCK__Video',D); E.setAttribute('_fckVideo','true',0); FCKFlashProcessor.RefreshView(E,C); C.parentNode.insertBefore(E,C); C.parentNode.removeChild(C); treated = true; }; } } } }; };
So the problems are that:
  1. I'm not sure which part of the code actually gets the right tags out of the document when trying to guess the resource type
  2. This code still doesn't handle videos resizing correctly (the video disappears with as little as selecting it).
After deeper analisys, the FCKFlashProcessor.ProcessDocument() method is the one that deals with analysing an HTML document and getting the graphical placeholders in place from <object> and tags. Something of the utmost importance in there is that the call to the FCKDocumentProcessors_CreateFakeImage() method has to take as a second argument the object that we are removing while showing the placeholder. This object will then be stored in a "bin" that will be used upon submission of the changes, to replace the placeholder by the initial and true object. If the reference is wrong there, there's no way to recover the lost object. The FCK.GetRealElement() is the method used to recover the "binned" object upon saving. The corresponding changes have been added to the Dokeos SVN for Gecko browsers as of the 13th of July 2008. IE-changes will come soon.