彈出層外掛的編寫-layer(跨iframe傳值回撥)
彈出層(layer)在往上有非常多,這裡為什麼我要把它的實現提出來,原因有以下2點:
1、寫這篇文章也算是我部落格的一個開端,他們都說:“不寫部落格成不了大神” - -
2、我見過的彈出層中都基本沒有處理跨iframe傳值回撥,或者說不是真正意義上的回撥函式。
3、一個layer 10K左右就可以完成的功能,非得引用一個jquery EasyUI、jquery 等等的方式,都是我不能接受的。
一個獨立的外掛應該儘量減少依賴,這也是設計模式中追求的解耦與減少依賴。(layer去依賴某種庫或多個庫,不是一個很好的選擇。)
先說說呼叫方式:外掛做出來是讓人使用的,我覺得從最終的展現形式入手,能讓人更直觀的瞭解這個外掛是幹嘛的。以及它的優點。
演示程式碼中我僅僅演示了Iframe彈出層的方式,主要就是看它的回撥函式。其它方式,可以在原始碼中自己檢視,稍後將放出原始碼。
呼叫方式Code:
<input type="button" onclick="iframe0()" value="iframe層,預設無底部" />
<textarea id="callBox" style="height:200px"></textarea>
function iframe0() { layer.win("回撥函式的演示", "callback.htm", function (data) { document.getElementById("callBox").value += data + "\r\n"; }) }
上面程式碼是在index.html頁面中的程式碼,這種場景很多,(比如:訂單頁面裡,需要選擇部門的資料,往往會把部門的資料做成一個單獨的頁面,通過彈出層呼叫之後選擇,然後返回對應的資料)
layer.win是彈出iframe的方式,引數1為彈出層的標題(這個引數其實非常靈活),引數2一看就是一個頁面的url,這裡設定為callback.html。引數3:這個引數很關鍵了。它是一個真正意義上的回撥函式,也就是當callback.html選擇資料返回之後執行的程式碼。文字比較抽象,還是看程式碼。
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"> function call() { var text = document.getElementById("v1").value; top.layer.callData(location.href, text); } </script> </head> <body> <h1>注意:這裡是在另外一個頁面內</h1> <input type="text" id="v1" /> <input type="button" value="觸發回撥函式,真正的父子頁面傳值" onclick="call()" /> </body> </html>
會前端的朋友都能看懂這段程式碼,非常簡單。當點選按鈕時:執行了call這個函式,函式內首先獲取id為v1的input值,賦值給text變數。然後呼叫top.layer.callData(location.href, text)
引數1,location.href 即當前的url,這個引數是固定的,不會更改的。引數2 text,就是剛剛獲取到的值。
當呼叫這個方法時:就會執行上面呼叫layer.win裡的回撥函式,這是如何辦到的,這應該是我們最關心的話題。
我見過許國其它彈出層的處理方式:
function call() {
var text = document.getElementById("v1").value;
parent.document.getElementById("callBox").value=text;
}
許多彈出層都是這樣的處理方式,或者說 直接parent.xxx(text),xxx即是父頁面的一個函式。原理就是通過parent等方式去定位呼叫之前的頁面,然後直接訪問其頁面的元素或者方法。
那麼:這樣做到底有什麼問題?可以這樣說,這種設計違背了設計模式的很多原則,別看javascript是弱型別語言。其實它很強大! - -
那到底哪裡違背了? 假設我們還是以上面的例子說明:
訂單頁面裡,需要選擇部門的資料,當我們選擇資料之後執行call時去呼叫訂單頁面的,如:parent.document.getElementById("Order-Department").value=data;
單單看上面的程式碼,貌似也沒有什麼問題
現在我又有另外一個頁面User.html(使用者設定頁面),同樣也需要呼叫選擇部門的頁面,首先:我不可能去重寫一個部門頁面,因為它已經存在了,如果去重寫一個,這......我也不好說什麼了,如果不重寫,那麼我們還是呼叫先前的部門頁面,看會發生什麼事情。程式碼可能就會變成這樣了:
function call() {
var text = document.getElementById("v1").value;
if(呼叫頁面==部門頁面){
<strong>parent.document.getElementById("Order-<span left-pos="0|6" right-pos="0|6" space="">Department</span>").value=text;</strong>
}
else if(呼叫頁面==使用者頁面){
<strong>parent.document.getElementById("User-<span left-pos="0|6" right-pos="0|6" space=""><span style="background-color: rgb(240, 240, 240);">Department</span></span>").value=text;</strong>
}
}
OK,上面的列子是很常用,很現實的一個例子,如果你是Web系統的開發人員,這種問題基本是不可能不遇到。
很明顯:上面的列子違背了太多原則:開放封閉....單一職責原則.......假設還有其它n個頁面需要呼叫到部門選擇!假設某天使用者頁面上的input換了個id!....自己可以想象下!
好了,現在我們回到如何解決這個問題,也就是layer中如何來實現這個真正意義上的回撥函式的地方。
翻看原始碼時你會發現:在呼叫layer.win方法時,它的第三個引數,也就是回撥函式,以這樣的程式碼形式在js中進行了處理:
this.win = function (parameter, src, callback) {
if (typeof (parameter) == "object") {
layerobj.init(parameter);
}
else {
layerobj.config.title = parameter;
layerobj.config.iframe.src = src;
layerobj.config.iframe.success = callback;
}
if (layerobj.config.iframe.success) {
window["layergofunc" + layerobj.config.id] = layerobj.config.iframe.success;
}
layerobj.config.type = 2;
layerobj.config.btns.count = 0;
layerobj.config.oframe.foot = false;
layerobj.run();
return layerobj;
}
第三個引數callback 以 window["layergofunc" + layerobj.config.id] = layerobj.config.iframe.success的形式進行了儲存。它給視窗註冊了一個物件,物件名稱是一個字串+時間戳的方式(layerobj.config.id實際是一個時間戳)。
而當回撥頁面執行parent.layer.callData(location.href, text);時,為什麼有一個固定的引數location.href。這個我們參考callData方法,看看它幹了什麼事情:
layer.callData = function (layerid) {
if (layerid && layerid.indexOf("http:") != -1) {
layerid = arguments[0].substring(arguments[0].indexOf("layer-id"), arguments[0].length).split("&")[0].split("=")[1];
}
var temparguments = [].slice.apply(arguments);
var temparray = [];
if (temparguments.length > 1) {
temparray = temparguments.slice(1, temparguments.length);
}
window["layergofunc" + layerid](temparray);
};
首先獲取了layerid,通過解析了location.href,這裡可以猜得到,在呼叫win方式時,我們為要呼叫的頁面動態的加了一個引數,鍵值為layer-id。這裡通過獲取到這個id,然後重新執行了在win方式時註冊的window物件。當執行到最後一句程式碼window["layergofunc" + layerid](temparray);時,實際上就執行了:function (data) { document.getElementById("callBox").value += data + "\r\n"; } 這個匿名回撥函式。
剛寫博文,風格不好,思路也比較亂。不知道有沒有解釋清楚iframe傳值原理。原始碼我已經提供了下載。這個layer還有很多其它的功能,如果有人有時間做做美化之類的,用它在實際專案中使用來代替模態視窗是個非常不錯的選擇。首先它小巧,遵循了許多設計上的原則!歡迎留言批判!!!