FCKeditor源代码分析(一)-----fckeditor.js的中文注释分析(原创)

学校实验室的一个老师说,要和一个日本公司合作做一个web在线编辑器,所以这几天都在研究FCKeditor的源代码     什么是FCKeditor?  但是几乎搜遍了Internet,似乎对于fckconfig.js这个文件讲解的很多,但对于fckeditor.js这个FCK的核心类文件的资料几乎为0.
所以,花了整整一天的时间,以挤牙膏的方式,对fckeditor.js这个fck核心类文件作了自己力所能及的注释,供同样学习fck的网友一个参考。
鉴于自己水平有限,在此,请广大午饭高手指出我的注释中不妥之处,以免误导他人 。
另外,源代码建议copy到自己的IDE中查看。注:本文基于FCKeditor2.6.5

更多权威资料,请参见  FCK 官方Developers Guide   附件中有这个文件。

 


  1. /**  
  2.  *  
  3.  * ***********CopyRight**************  
  4.  *-------Annotated by nileader-----  
  5.  *-----Version 1.00   2009-10-18-----   
  6.  *  
  7.  * FCKeditor  类     annotated by nileader  
  8.  * @param {Object} instanceName 编辑器的唯一名称(相当于ID) 是不可省参数,  
  9.  * width,height,toolbarset,value 都是 可选参数  
  10.  */ 
  11. var FCKeditor = function(instanceName, width, height, toolbarSet, value){  
  12.     //编辑器的基本属性   注意:这些东西优先于FCKConfig.js中的配置    
  13.     this.InstanceName = instanceName; //编辑器的唯一名称(相当于ID)(必须有!)  
  14.     this.Width = width || '100%'//宽度   默认是100%         
  15.     this.Height = height || '200'//宽度   默认是200  
  16.     this.ToolbarSet = toolbarSet || 'Default';//工具集名称,默认值是Default   
  17.     this.Value = value || ''//初始化编辑器的HTML代码,默认值为空  
  18.     //编辑器初始化的时候默认的根路径, 其作用是编写fck中,凡是用到的路径,均从FCKeditor.BasePath目录开始      默认为/Fckeditor/  
  19.     this.BasePath = FCKeditor.BasePath;  
  20.     this.CheckBrowser = true//是否在显示编辑器前检查浏览器兼容性,默认为true  
  21.     this.DisplayErrors = true//是否显示提示错误,默为true  
  22.     this.Config = new Object();  
  23.     // Events  
  24.     this.OnError = null// function( source, errorNumber, errorDescription )自定义的错误处理函数  
  25. }  
  26.  
  27. FCKeditor.BasePath = '/fckeditor/'// fck默认的根目录  
  28. FCKeditor.MinHeight = 200; //高和宽的限制  
  29. FCKeditor.MinWidth = 750;  
  30. FCKeditor.prototype.Version = '2.6.5'//版本号  
  31. FCKeditor.prototype.VersionBuild = '23959';  
  32.  
  33. /**  
  34.  * 调用CreateHtml()来生成编辑器的html代码并在页面上输出编辑器  
  35.  */ 
  36. FCKeditor.prototype.Create = function(){  
  37.     //调用createhtml()方法  
  38.     document.write(this.CreateHtml());  
  39. }  
  40.  
  41. /**  
  42.  * @return sHtml 用于生成编辑器的html代码  
  43.  */ 
  44. FCKeditor.prototype.CreateHtml = function(){  
  45.     // 检查有无InstanceName  如果没有则不生成html代码  
  46.     if (!this.InstanceName || this.InstanceName.length == 0) {  
  47.         this._ThrowError(701, 'You must specify an instance name.');  
  48.         return '';  
  49.     }  
  50.     //函数的返回值  
  51.     var sHtml = '';  
  52.     /*  
  53.      * 当用户的浏览器符合预设的几种浏览器时,  
  54.      * 生成一个id="this.instancename" name="this.instancename"的文本框,事实上的内容储存器  
  55.      */ 
  56.     if (!this.CheckBrowser || this._IsCompatibleBrowser()) {  
  57.         //将此时FCK初始值通过转义之后放入这个input  
  58.         sHtml += '<input type="hidden" id="' + this.InstanceName + '" name="' + this.InstanceName + '" value="' + this._HTMLEncode(this.Value) + '" style="display:none" />';  
  59.         //生成一个隐藏的INPUT来放置this.config中的内容   
  60.         sHtml += this._GetConfigHtml();  
  61.         //生成编辑器的iframe的代码  
  62.         sHtml += this._GetIFrameHtml();  
  63.     }  
  64.     /**  
  65.      * 如果用户的浏览器不兼容FCK默认的几种浏览器  
  66.      * 只能有传统的textarea了  
  67.      */ 
  68.     else {  
  69.         var sWidth = this.Width.toString().indexOf('%') > 0 ? this.Width : this.Width + 'px';  
  70.         var sHeight = this.Height.toString().indexOf('%') > 0 ? this.Height : this.Height + 'px';  
  71.           
  72.         sHtml += '<textarea name="' + this.InstanceName +  
  73.         '" rows="4" cols="40" style="width:' +  
  74.         sWidth +  
  75.         ';height:' +  
  76.         sHeight;  
  77.           
  78.         if (this.TabIndex)   
  79.             sHtml += '" tabindex="' + this.TabIndex;  
  80.           
  81.         sHtml += '">' +  
  82.         this._HTMLEncode(this.Value) +  
  83.         '<\/textarea>';  
  84.     }  
  85.       
  86.     return sHtml;  
  87. }  
  88.  
  89. /**  
  90.  * 用编辑器来替换对应的文本框  
  91.  */ 
  92. FCKeditor.prototype.ReplaceTextarea = function(){  
  93.     //如果已经有了 id=THIS.INSTANCENAME___Frame 的标签时,直接返回  
  94.     if (document.getElementById(this.InstanceName + '___Frame'))   
  95.         return;  
  96.     //当用户的浏览器符合预设的几种浏览器时  
  97.     if (!this.CheckBrowser || this._IsCompatibleBrowser()) {  
  98.         // We must check the elements firstly using the Id and then the name.  
  99.         //获取id=this.InstanceName的html标签  
  100.         var oTextarea = document.getElementById(this.InstanceName);  
  101.         //获取所有name=THIS.instancename的标签  
  102.         var colElementsByName = document.getElementsByName(this.InstanceName);  
  103.         var i = 0;  
  104.         /*  
  105.          * 考虑到用户html标签的命名不规范,所以进行以下编历判断     笔者指的是用户在textarea标签处用了name=this.instancename  
  106.          * 在同个页面的其它标签上也用了name=this.instancename  
  107.          */ 
  108.         while (oTextarea || i == 0) {  
  109.             //遍历,直到找到name=this.instancename的textarea标签,并赋给oTextarea  
  110.             if (oTextarea && oTextarea.tagName.toLowerCase() == 'textarea')   
  111.                 break;  
  112.             oTextarea = colElementsByName[i++];  
  113.         }  
  114.         //如果不存在id或者name为this.instancename的标签时,弹出错误框  
  115.         if (!oTextarea) {  
  116.             alert('Error: The TEXTAREA with id or name set to "' + this.InstanceName + '" was not found');  
  117.             return;  
  118.         }  
  119.         /*  
  120.          * 确定存在name=this.instancename的textarea标签后,将编辑器的代码赋给它  
  121.          */ 
  122.         oTextarea.style.display = 'none';  
  123.         //如果页面上对这样的textarea标签定义了tab键的顺序,赋给this.TabIndex待用  
  124.         if (oTextarea.tabIndex)   
  125.             this.TabIndex = oTextarea.tabIndex;  
  126.         this._InsertHtmlBefore(this._GetConfigHtml(), oTextarea);  
  127.         this._InsertHtmlBefore(this._GetIFrameHtml(), oTextarea);  
  128.     }  
  129. }  
  130.  
  131. /**  
  132.  * 在指定的页面标签前面插入html代码  
  133.  * @param {Object} 待插入的html代码  
  134.  * @param {Object} 指定的页面标签(对象)  
  135.  */ 
  136. FCKeditor.prototype._InsertHtmlBefore = function(html, element){  
  137.     if (element.insertAdjacentHTML) // IE 私有的 insertAdjacentHTML 方法  
  138.         element.insertAdjacentHTML('beforeBegin', html);  
  139.     else // 非ie浏览器  
  140.     {  
  141.       
  142.         var oRange = document.createRange();  
  143.         oRange.setStartBefore(element);  
  144.         var oFragment = oRange.createContextualFragment(html);  
  145.         element.parentNode.insertBefore(oFragment, element);  
  146.     }  
  147. }  
  148.  
  149. /*  
  150.  * 通过编历this.Config[]来生成一个隐藏域,  
  151.  * 例如:  
  152.  * this.Config['nileader']="1104",this.Config['leaderni']="nichao"……  
  153.  * 那么,sConfig=…… &nileader=1104&leaderni=nichao ……  
  154.  * 当然,最终,sConfig会被encodeURIComponent函数转换成百分比编码 放入隐藏的INPUT中去  
  155.  */ 
  156. FCKeditor.prototype._GetConfigHtml = function(){  
  157.     var sConfig = '';  
  158.     for (var o in this.Config) {  
  159.         if (sConfig.length > 0)   
  160.             sConfig += '&amp;';  
  161.         //encodeURIComponent函数转换成百分比编码  
  162.         sConfig += encodeURIComponent(o) + '=' + encodeURIComponent(this.Config[o]);  
  163.     }  
  164.     return '<input type="hidden" id="' + this.InstanceName + '___Config" value="' + sConfig + '" style="display:none" />';  
  165. }  
  166.  
  167. /*  
  168.  * 生成iframe的html  这里涉及到src的确定  
  169.  */ 
  170. FCKeditor.prototype._GetIFrameHtml = function(){  
  171.     var sFile = 'fckeditor.html';  
  172.       
  173.     //特殊情况 fckedito所在的窗口没有嵌入在浏览器中  
  174.     try {  
  175.         if ((/fcksource=true/i).test(window.top.location.search))   
  176.             sFile = 'fckeditor.original.html';  
  177.     }   
  178.     catch (e) { /* 忽略这个异常. 很多时候,fckedito所在的窗口嵌入在浏览器中. */ 
  179.     }  
  180.       
  181.     /*  
  182.      * 这里注意的一点:  
  183.      * iframe的工作原理: 当iframe处于可编辑状态时,其实编辑的是src所在的页面  
  184.      * 这里合成一个sLink以放入iframe标签中  
  185.      */ 
  186.     //sLink就是这个事实上的页面了,从fck的根目录开始,例如   sLink=/fckeditor/editor/fckeditor.html?InstanceName=nileader&Toolbar=nileadersbar  
  187.     var sLink = this.BasePath + 'editor/' + sFile + '?InstanceName=' + encodeURIComponent(this.InstanceName);  
  188.     if (this.ToolbarSet)   
  189.         sLink += '&amp;Toolbar=' + this.ToolbarSet;  
  190.       
  191.     //生成一个真正的编辑iframer的html代码  当然,放入了src=slink  
  192.     var html = '<iframe id="' + this.InstanceName +  
  193.     '___Frame" src="' +  
  194.     sLink +  
  195.     '" width="' +  
  196.     this.Width +  
  197.     '" height="' +  
  198.     this.Height;  
  199.       
  200.     //如果设定了使用"Tab"键的遍历顺序,则赋给iframe  
  201.     if (this.TabIndex)   
  202.         html += '" tabindex="' + this.TabIndex;  
  203.       
  204.     html += '" frameborder="0" scrolling="no"></iframe>';  
  205.       
  206.     return html;  
  207. }  
  208.  
  209. /*  
  210.  * 检测用户的bowser是否是fck的默认  
  211.  * 这个方法只是fck公司追求oo,无意义  
  212.  */ 
  213. FCKeditor.prototype._IsCompatibleBrowser = function(){  
  214.     return FCKeditor_IsCompatibleBrowser();  
  215. }  
  216.  
  217. /**  
  218.  * 抛出错误  
  219.  * @param {Object} errorNumber    错误编号  
  220.  * @param {Object} errorDescription   错误概述  
  221.  */ 
  222. FCKeditor.prototype._ThrowError = function(errorNumber, errorDescription){  
  223.     this.ErrorNumber = errorNumber;  
  224.     this.ErrorDescription = errorDescription;  
  225.       
  226.     //是否显示提示错误,默为true  
  227.     if (this.DisplayErrors) { //将错误编号和错误概述打印出来  
  228.         document.write('<div style="COLOR: #ff0000">');  
  229.         document.write('[ FCKeditor Error ' + this.ErrorNumber + ': ' + this.ErrorDescription + ' ]');  
  230.         document.write('</div>');  
  231.     }  
  232.     //OnError是否自定义了错误处理函数,若定义了,由其处理  
  233.     if (typeof(this.OnError) == 'function')   
  234.         this.OnError(this, errorNumber, errorDescription);  
  235. }  
  236.  
  237. /**  
  238.  * 转义文本  
  239.  * @param {Object} text   待转义的文本  
  240.  * @return String  text    转义完后的文本  
  241.  */ 
  242. FCKeditor.prototype._HTMLEncode = function(text){  
  243.     if (typeof(text) != "string")   
  244.         text = text.toString();  
  245.     //将字符串中的所有 & " < > 用对应的转义字符代换  
  246.     text = text.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");  
  247.     return text;  
  248. };  
  249. (function(){  
  250.     //把页面上的textarea元素赋给editor变量  
  251.     var textareaToEditor = function(textarea){  
  252.         var editor = new FCKeditor(textarea.name);  
  253.           
  254.         editor.Width = Math.max(textarea.offsetWidth, FCKeditor.MinWidth);  
  255.         editor.Height = Math.max(textarea.offsetHeight, FCKeditor.MinHeight);  
  256.           
  257.         return editor;  
  258.     }  
  259.       
  260.     /**  
  261.      * Replace all <textarea> elements available in the document with FCKeditor  
  262.      * instances.  
  263.      *  
  264.      *  // Replace all <textarea> elements in the page.  
  265.      *  FCKeditor.ReplaceAllTextareas() ;  
  266.      *  
  267.      *  // Replace all <textarea class="myClassName"> elements in the page.  
  268.      *  FCKeditor.ReplaceAllTextareas( 'myClassName' ) ;  
  269.      *  
  270.      *  // Selectively replace <textarea> elements, based on custom assertions.  
  271.      *  FCKeditor.ReplaceAllTextareas( function( textarea, editor )  
  272.      *      {  
  273.      *          // Custom code to evaluate the replace, returning false if it  
  274.      *          // must not be done.  
  275.      *          // It also passes the "editor" parameter, so the developer can  
  276.      *          // customize the instance.  
  277.      *      } ) ;  
  278.      */ 
  279.     FCKeditor.ReplaceAllTextareas = function(){  
  280.         //获取所有的textarea元素  
  281.         var textareas = document.getElementsByTagName('textarea');  
  282.           
  283.         for (var i = 0; i < textareas.length; i++) {  
  284.             var editor = null;  
  285.             var textarea = textareas[i];  
  286.             var name = textarea.name;  
  287.               
  288.             // The "name" attribute must exist.  
  289.             if (!name || name.length == 0)   
  290.                 continue;  
  291.               
  292.             if (typeof arguments[0] == 'string') {  
  293.                 // The textarea class name could be passed as the function  
  294.                 // parameter.  
  295.                   
  296.                 var classRegex = new RegExp('(?:^| )' + arguments[0] + '(?:$| )');  
  297.                   
  298.                 if (!classRegex.test(textarea.className))   
  299.                     continue;  
  300.             }  
  301.             else   
  302.                 if (typeof arguments[0] == 'function') {  
  303.                     // An assertion function could be passed as the function parameter.  
  304.                     // It must explicitly return "false" to ignore a specific <textarea>.  
  305.                     editor = textareaToEditor(textarea);  
  306.                     if (arguments[0](textarea, editor) === false)   
  307.                         continue;  
  308.                 }  
  309.               
  310.             if (!editor)   
  311.                 editor = textareaToEditor(textarea);  
  312.               
  313.             editor.ReplaceTextarea();  
  314.         }  
  315.     }  
  316. })();  
  317.  
  318. /**  
  319.  * 检测浏览器的兼容性  
  320.  * 利用了navigator对象返回的一些信息sAgent,判断浏览器  返回包括    浏览器的码名 浏览器名  浏览器版本  语言 等信息 并小写  
  321.  *  例如:  
  322.  * mozilla/4.0 (compatible; msie 6.0; windows nt 5.2; sv1; .net clr 1.1.4322)  
  323.  *  
  324.  * 判断IE浏览器的时候,运用了IE4.0之后支持的增加了对条件编译,  
  325.  * 由于只是IE支持,在W3C标准浏览器中,该属性是不被支持的。因此,适当的利用该特性,判断IE  
  326.  */ 
  327. function FCKeditor_IsCompatibleBrowser(){  
  328.     var sAgent = navigator.userAgent.toLowerCase();  
  329.       
  330.     // 当前浏览器是Internet Explorer 5.5+  
  331.     //利用条件编译判断IE 在IE中,/*@cc_on!@*/false == !false == true,  
  332.     //如果是非IE浏览器,则忽略,/*@cc_on!@*/false == false   
  333.     if ( /*@cc_on!@*/false && sAgent.indexOf("mac") == -1) //不是apple mac os  
  334.     {  
  335.         var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1];  
  336.         return (sBrowserVersion >= 5.5);  
  337.     }  
  338.       
  339.       
  340.     // Gecko (Opera 9 tries to behave like Gecko at this point).  
  341.     //检测是否是OPERA 9 浏览器  
  342.     if (navigator.product == "Gecko" && navigator.productSub >= 20030210 && !(typeof(opera) == 'object' && opera.postError))   
  343.         return true;  
  344.       
  345.     // Opera 9.50+  
  346.     if (window.opera && window.opera.version && parseFloat(window.opera.version()) >= 9.5)   
  347.         return true;  
  348.       
  349.     // Adobe AIR  
  350.     // Checked before Safari because AIR have the WebKit rich text editor  
  351.     // features from Safari 3.0.4, but the version reported is 420.  
  352.     if (sAgent.indexOf(' adobeair/') != -1)   
  353.         return (sAgent.match(/ adobeair\/(\d+)/)[1] >= 1); // Build must be at least v1  
  354.     // Safari 3+  
  355.     if (sAgent.indexOf(' applewebkit/') != -1)   
  356.         return (sAgent.match(/ applewebkit\/(\d+)/)[1] >= 522); // Build must be at least 522 (v3)  
  357.     return false;  

 


本文转自 nileader 51CTO博客,原文链接:http://blog.51cto.com/nileader/301614,如需转载请自行联系原作者

上一篇:带你读《重构数字战斗力: 中小企业的数字化转型之路》第二章电子电气企业覆盖 “全渠道—研发—经营—制造” 全价值链的上云之路案例2(二)


下一篇:带你读《重构数字战斗力: 中小企业的数字化转型之路》第二章电子电气企业覆盖 “全渠道—研发—经营—制造” 全价值链的上云之路案例1(一)