學校實驗室的一個老師說,要和一個日本公司合作做一個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 cla***egex = new RegExp(`(?:^| )` + arguments[0] + `(?:$| )`);  
  297.                   
  298.                 if (!cla***egex.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;