1. 程式人生 > >彈出層外掛的編寫-layer(跨iframe傳值回撥)

彈出層外掛的編寫-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還有很多其它的功能,如果有人有時間做做美化之類的,用它在實際專案中使用來代替模態視窗是個非常不錯的選擇。首先它小巧,遵循了許多設計上的原則!歡迎留言批判!!!