學校實驗室的一個老師說,要和一個日本公司合作做一個web線上編輯器,所以這幾天都在研究FCKeditor的原始碼 什麼是FCKeditor? 但是幾乎搜遍了Internet,似乎對於fckconfig.js這個檔案講解的很多,但對於fckeditor.js這個FCK的核心類檔案的資料幾乎為0.
所以,花了整整一天的時間,以擠牙膏的方式,對fckeditor.js這個fck核心類檔案作了自己力所能及的註釋,供同樣學習fck的網友一個參考。
鑑於自己水平有限,在此,請廣大午飯高手指出我的註釋中不妥之處,以免誤導他人 。
另外,原始碼建議copy到自己的IDE中檢視。注:本文基於FCKeditor2.6.5
更多權威資料,請參見 FCK 官方Developers Guide 附件中有這個檔案。
- /**
- *
- * ***********CopyRight**************
- *-------Annotated by nileader-----
- *-----Version 1.00 2009-10-18-----
- *
- * FCKeditor 類 annotated by nileader
- * @param {Object} instanceName 編輯器的唯一名稱(相當於ID) 是不可省引數,
- * width,height,toolbarset,value 都是 可選引數
- */
- var FCKeditor = function(instanceName, width, height, toolbarSet, value){
- //編輯器的基本屬性 注意:這些東西優先於FCKConfig.js中的配置
- this.InstanceName = instanceName; //編輯器的唯一名稱(相當於ID)(必須有!)
- this.Width = width || `100%`; //寬度 預設是100%
- this.Height = height || `200`; //寬度 預設是200
- this.ToolbarSet = toolbarSet || `Default`;//工具集名稱,預設值是Default
- this.Value = value || ``; //初始化編輯器的HTML程式碼,預設值為空
- //編輯器初始化的時候預設的根路徑, 其作用是編寫fck中,凡是用到的路徑,均從FCKeditor.BasePath目錄開始 預設為/Fckeditor/
- this.BasePath = FCKeditor.BasePath;
- this.CheckBrowser = true; //是否在顯示編輯器前檢查瀏覽器相容性,預設為true
- this.DisplayErrors = true; //是否顯示提示錯誤,默為true
- this.Config = new Object();
- // Events
- this.OnError = null; // function( source, errorNumber, errorDescription )自定義的錯誤處理函式
- }
- FCKeditor.BasePath = `/fckeditor/`; // fck預設的根目錄
- FCKeditor.MinHeight = 200; //高和寬的限制
- FCKeditor.MinWidth = 750;
- FCKeditor.prototype.Version = `2.6.5`; //版本號
- FCKeditor.prototype.VersionBuild = `23959`;
- /**
- * 呼叫CreateHtml()來生成編輯器的html程式碼並在頁面上輸出編輯器
- */
- FCKeditor.prototype.Create = function(){
- //呼叫createhtml()方法
- document.write(this.CreateHtml());
- }
- /**
- * @return sHtml 用於生成編輯器的html程式碼
- */
- FCKeditor.prototype.CreateHtml = function(){
- // 檢查有無InstanceName 如果沒有則不生成html程式碼
- if (!this.InstanceName || this.InstanceName.length == 0) {
- this._ThrowError(701, `You must specify an instance name.`);
- return ``;
- }
- //函式的返回值
- var sHtml = ``;
- /*
- * 當使用者的瀏覽器符合預設的幾種瀏覽器時,
- * 生成一個id="this.instancename" name="this.instancename"的文字框,事實上的內容儲存器
- */
- if (!this.CheckBrowser || this._IsCompatibleBrowser()) {
- //將此時FCK初始值通過轉義之後放入這個input
- sHtml += `<input type="hidden" id="` + this.InstanceName + `" name="` + this.InstanceName + `" value="` + this._HTMLEncode(this.Value) + `" style="display:none" />`;
- //生成一個隱藏的INPUT來放置this.config中的內容
- sHtml += this._GetConfigHtml();
- //生成編輯器的iframe的程式碼
- sHtml += this._GetIFrameHtml();
- }
- /**
- * 如果使用者的瀏覽器不相容FCK預設的幾種瀏覽器
- * 只能有傳統的textarea了
- */
- else {
- var sWidth = this.Width.toString().indexOf(`%`) > 0 ? this.Width : this.Width + `px`;
- var sHeight = this.Height.toString().indexOf(`%`) > 0 ? this.Height : this.Height + `px`;
- sHtml += `<textarea name="` + this.InstanceName +
- `" rows="4" cols="40" style="width:` +
- sWidth +
- `;height:` +
- sHeight;
- if (this.TabIndex)
- sHtml += `" tabindex="` + this.TabIndex;
- sHtml += `">` +
- this._HTMLEncode(this.Value) +
- `</textarea>`;
- }
- return sHtml;
- }
- /**
- * 用編輯器來替換對應的文字框
- */
- FCKeditor.prototype.ReplaceTextarea = function(){
- //如果已經有了 id=THIS.INSTANCENAME___Frame 的標籤時,直接返回
- if (document.getElementById(this.InstanceName + `___Frame`))
- return;
- //當使用者的瀏覽器符合預設的幾種瀏覽器時
- if (!this.CheckBrowser || this._IsCompatibleBrowser()) {
- // We must check the elements firstly using the Id and then the name.
- //獲取id=this.InstanceName的html標籤
- var oTextarea = document.getElementById(this.InstanceName);
- //獲取所有name=THIS.instancename的標籤
- var colElementsByName = document.getElementsByName(this.InstanceName);
- var i = 0;
- /*
- * 考慮到使用者html標籤的命名不規範,所以進行以下編歷判斷 筆者指的是使用者在textarea標籤處用了name=this.instancename
- * 在同個頁面的其它標籤上也用了name=this.instancename
- */
- while (oTextarea || i == 0) {
- //遍歷,直到找到name=this.instancename的textarea標籤,並賦給oTextarea
- if (oTextarea && oTextarea.tagName.toLowerCase() == `textarea`)
- break;
- oTextarea = colElementsByName[i++];
- }
- //如果不存在id或者name為this.instancename的標籤時,彈出錯誤框
- if (!oTextarea) {
- alert(`Error: The TEXTAREA with id or name set to "` + this.InstanceName + `" was not found`);
- return;
- }
- /*
- * 確定存在name=this.instancename的textarea標籤後,將編輯器的程式碼賦給它
- */
- oTextarea.style.display = `none`;
- //如果頁面上對這樣的textarea標籤定義了tab鍵的順序,賦給this.TabIndex待用
- if (oTextarea.tabIndex)
- this.TabIndex = oTextarea.tabIndex;
- this._InsertHtmlBefore(this._GetConfigHtml(), oTextarea);
- this._InsertHtmlBefore(this._GetIFrameHtml(), oTextarea);
- }
- }
- /**
- * 在指定的頁面標籤前面插入html程式碼
- * @param {Object} 待插入的html程式碼
- * @param {Object} 指定的頁面標籤(物件)
- */
- FCKeditor.prototype._InsertHtmlBefore = function(html, element){
- if (element.insertAdjacentHTML) // IE 私有的 insertAdjacentHTML 方法
- element.insertAdjacentHTML(`beforeBegin`, html);
- else // 非ie瀏覽器
- {
- var oRange = document.createRange();
- oRange.setStartBefore(element);
- var oFragment = oRange.createContextualFragment(html);
- element.parentNode.insertBefore(oFragment, element);
- }
- }
- /*
- * 通過編歷this.Config[]來生成一個隱藏域,
- * 例如:
- * this.Config[`nileader`]="1104",this.Config[`leaderni`]="nichao"……
- * 那麼,sConfig=…… &nileader=1104&leaderni=nichao ……
- * 當然,最終,sConfig會被encodeURIComponent函式轉換成百分比編碼 放入隱藏的INPUT中去
- */
- FCKeditor.prototype._GetConfigHtml = function(){
- var sConfig = ``;
- for (var o in this.Config) {
- if (sConfig.length > 0)
- sConfig += `&`;
- //encodeURIComponent函式轉換成百分比編碼
- sConfig += encodeURIComponent(o) + `=` + encodeURIComponent(this.Config[o]);
- }
- return `<input type="hidden" id="` + this.InstanceName + `___Config" value="` + sConfig + `" style="display:none" />`;
- }
- /*
- * 生成iframe的html 這裡涉及到src的確定
- */
- FCKeditor.prototype._GetIFrameHtml = function(){
- var sFile = `fckeditor.html`;
- //特殊情況 fckedito所在的視窗沒有嵌入在瀏覽器中
- try {
- if ((/fcksource=true/i).test(window.top.location.search))
- sFile = `fckeditor.original.html`;
- }
- catch (e) { /* 忽略這個異常. 很多時候,fckedito所在的視窗嵌入在瀏覽器中. */
- }
- /*
- * 這裡注意的一點:
- * iframe的工作原理: 當iframe處於可編輯狀態時,其實編輯的是src所在的頁面
- * 這裡合成一個sLink以放入iframe標籤中
- */
- //sLink就是這個事實上的頁面了,從fck的根目錄開始,例如 sLink=/fckeditor/editor/fckeditor.html?InstanceName=nileader&Toolbar=nileadersbar
- var sLink = this.BasePath + `editor/` + sFile + `?InstanceName=` + encodeURIComponent(this.InstanceName);
- if (this.ToolbarSet)
- sLink += `&Toolbar=` + this.ToolbarSet;
- //生成一個真正的編輯iframer的html程式碼 當然,放入了src=slink
- var html = `<iframe id="` + this.InstanceName +
- `___Frame" src="` +
- sLink +
- `" width="` +
- this.Width +
- `" height="` +
- this.Height;
- //如果設定了使用"Tab"鍵的遍歷順序,則賦給iframe
- if (this.TabIndex)
- html += `" tabindex="` + this.TabIndex;
- html += `" frameborder="0" scrolling="no"></iframe>`;
- return html;
- }
- /*
- * 檢測使用者的bowser是否是fck的預設
- * 這個方法只是fck公司追求oo,無意義
- */
- FCKeditor.prototype._IsCompatibleBrowser = function(){
- return FCKeditor_IsCompatibleBrowser();
- }
- /**
- * 丟擲錯誤
- * @param {Object} errorNumber 錯誤編號
- * @param {Object} errorDescription 錯誤概述
- */
- FCKeditor.prototype._ThrowError = function(errorNumber, errorDescription){
- this.ErrorNumber = errorNumber;
- this.ErrorDescription = errorDescription;
- //是否顯示提示錯誤,默為true
- if (this.DisplayErrors) { //將錯誤編號和錯誤概述列印出來
- document.write(`<div style="COLOR: #ff0000">`);
- document.write(`[ FCKeditor Error ` + this.ErrorNumber + `: ` + this.ErrorDescription + ` ]`);
- document.write(`</div>`);
- }
- //OnError是否自定義了錯誤處理函式,若定義了,由其處理
- if (typeof(this.OnError) == `function`)
- this.OnError(this, errorNumber, errorDescription);
- }
- /**
- * 轉義文字
- * @param {Object} text 待轉義的文字
- * @return String text 轉義完後的文字
- */
- FCKeditor.prototype._HTMLEncode = function(text){
- if (typeof(text) != "string")
- text = text.toString();
- //將字串中的所有 & " < > 用對應的轉義字元代換
- text = text.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
- return text;
- };
- (function(){
- //把頁面上的textarea元素賦給editor變數
- var textareaToEditor = function(textarea){
- var editor = new FCKeditor(textarea.name);
- editor.Width = Math.max(textarea.offsetWidth, FCKeditor.MinWidth);
- editor.Height = Math.max(textarea.offsetHeight, FCKeditor.MinHeight);
- return editor;
- }
- /**
- * Replace all <textarea> elements available in the document with FCKeditor
- * instances.
- *
- * // Replace all <textarea> elements in the page.
- * FCKeditor.ReplaceAllTextareas() ;
- *
- * // Replace all <textarea class="myClassName"> elements in the page.
- * FCKeditor.ReplaceAllTextareas( `myClassName` ) ;
- *
- * // Selectively replace <textarea> elements, based on custom assertions.
- * FCKeditor.ReplaceAllTextareas( function( textarea, editor )
- * {
- * // Custom code to evaluate the replace, returning false if it
- * // must not be done.
- * // It also passes the "editor" parameter, so the developer can
- * // customize the instance.
- * } ) ;
- */
- FCKeditor.ReplaceAllTextareas = function(){
- //獲取所有的textarea元素
- var textareas = document.getElementsByTagName(`textarea`);
- for (var i = 0; i < textareas.length; i++) {
- var editor = null;
- var textarea = textareas[i];
- var name = textarea.name;
- // The "name" attribute must exist.
- if (!name || name.length == 0)
- continue;
- if (typeof arguments[0] == `string`) {
- // The textarea class name could be passed as the function
- // parameter.
- var cla***egex = new RegExp(`(?:^| )` + arguments[0] + `(?:$| )`);
- if (!cla***egex.test(textarea.className))
- continue;
- }
- else
- if (typeof arguments[0] == `function`) {
- // An assertion function could be passed as the function parameter.
- // It must explicitly return "false" to ignore a specific <textarea>.
- editor = textareaToEditor(textarea);
- if (arguments[0](textarea, editor) === false)
- continue;
- }
- if (!editor)
- editor = textareaToEditor(textarea);
- editor.ReplaceTextarea();
- }
- }
- })();
- /**
- * 檢測瀏覽器的相容性
- * 利用了navigator物件返回的一些資訊sAgent,判斷瀏覽器 返回包括 瀏覽器的碼名 瀏覽器名 瀏覽器版本 語言 等資訊 並小寫
- * 例如:
- * mozilla/4.0 (compatible; msie 6.0; windows nt 5.2; sv1; .net clr 1.1.4322)
- *
- * 判斷IE瀏覽器的時候,運用了IE4.0之後支援的增加了對條件編譯,
- * 由於只是IE支援,在W3C標準瀏覽器中,該屬性是不被支援的。因此,適當的利用該特性,判斷IE
- */
- function FCKeditor_IsCompatibleBrowser(){
- var sAgent = navigator.userAgent.toLowerCase();
- // 當前瀏覽器是Internet Explorer 5.5+
- //利用條件編譯判斷IE 在IE中,/*@cc_on!@*/false == !false == true,
- //如果是非IE瀏覽器,則忽略,/*@cc_on!@*/false == false
- if ( /*@cc_on!@*/false && sAgent.indexOf("mac") == -1) //不是apple mac os
- {
- var sBrowserVersion = navigator.appVersion.match(/MSIE (...)/)[1];
- return (sBrowserVersion >= 5.5);
- }
- // Gecko (Opera 9 tries to behave like Gecko at this point).
- //檢測是否是OPERA 9 瀏覽器
- if (navigator.product == "Gecko" && navigator.productSub >= 20030210 && !(typeof(opera) == `object` && opera.postError))
- return true;
- // Opera 9.50+
- if (window.opera && window.opera.version && parseFloat(window.opera.version()) >= 9.5)
- return true;
- // Adobe AIR
- // Checked before Safari because AIR have the WebKit rich text editor
- // features from Safari 3.0.4, but the version reported is 420.
- if (sAgent.indexOf(` adobeair/`) != -1)
- return (sAgent.match(/ adobeair/(d+)/)[1] >= 1); // Build must be at least v1
- // Safari 3+
- if (sAgent.indexOf(` applewebkit/`) != -1)
- return (sAgent.match(/ applewebkit/(d+)/)[1] >= 522); // Build must be at least 522 (v3)
- return false;
- }