最近在翻修以前寫的控件(Web Control),其中有不少操作互動要靠Javascript處理。

當初還不懂jQuery(更精準點說,是jQuery還沒出生),乖乖用Javascript一行行把功能堆出來。用慣了jQuery後,這回要改寫翻新功能,說實在的,我已喪失直接用Javascript Hard-Code的耐性。就好像跟過大哥,體驗過拿手槍汽油彈在街頭火拼的爽快後,就很難再脫離幫派回去過撿石頭木棍跟人打架的生活。(這比喻一整個怪 >_<)

不過,我遇到了一個難題是,由於控件會被其他開發者引用,是屬於被支配、被指使的人頭角色,無法主控網頁的結構,因此要引用jQuery前必須確認網頁有include jquery.js。比較消極的做法是要求開發者在引用控件時一定要自行Include控件用到的幾個js,不然控件就出錯給你看以示薄懲。

我的野心稍微大一點,希望控件可以聰明地見機行事,若網頁已經載入必要的js,就不必多事,直接給它用下去就對了;否則控件要能自行載入所需的js,以供後續使用。

要實現這個理想,有三個重點: 1) 偵測必要的js是否已載入完成 2) 使用Javascript載入js 3) 多個js必須依序載入(例如: 先jquery.js再jquery.blockUI.js)。

針對1),我們可以檢測特定物件/函數是否存在(例如:typeof jQuery != "undefined")加以判斷。動態載入JS先前有討論過,也不是難事。

第3點比較麻煩,研究了一下,IE似乎無法支援<script>的onload事件,不過我有在網路上找到高人提出的解決方案,利用setInterval持續檢查特定物件是否存在以判定載入完成並觸發後續作業,這樣就可以達成依序逐一載入的目標。(其中interval變數巧妙地運用了Closure技巧,讓我大開眼界。好用的Javascript Closure以後再專文介紹)

綜合上述的研究,我寫成以下的範例:

(function() {
    function importJS(src, look_for, onload) {
        var s = document.createElement('script');
        s.setAttribute('type', 'text/javascript');
        s.setAttribute('src', src);
        if (onload) wait_for_script_load(look_for, onload);
        if (eval("typeof " + look_for) == 'undefined') {
            var head = document.getElementsByTagName('head')[0];
            if (head) head.appendChild(s);
            else document.body.appendChild(s);
        }
    }
    function wait_for_script_load(look_for, callback) {
        var interval = setInterval(function() {
            if (eval("typeof " + look_for) != 'undefined') {
                    clearInterval(interval);
                    callback();      
                }
            }, 50);
    }
    importJS("somePath/jquery-1.2.6.js", "jQuery", function() {
        importJS("somePath/jquery.blockUI.js", "jQuery.blockUI", function() {
            $.blockUI({ message: "<span>Done!</span>" });
        });
    });
})();

註: 最外層我包了一個(function() { ... })();,由於匿名函數的封裝,其中使用的importJS, wait_for_script_load等函數名稱只會這段空間中有效,不會與網頁其他地方的函數命名衝突,理由是不希望因為插入控件而干擾到網頁其他部分,我認為這也算控件品質的衡量指標之一。


Comments

# by tim

你的第一段比喻. 還真的很有趣咧. 不過也算傳神了. 厲害厲害. 關於動態載入的做法, 小弟以為, 得看實際上的用途, 寫程式寫久了, 有時候會因為需要一些小功能而大費周章, 但也會因為有了 framework 而蓋更高的大樓了. 真的是要因案施不同工法咧.

# by BLACKBING

IE不支援script 的onload事件,但是可以用onreadystatechange,透過檢查readyState來判斷是否onload。應該會比setInterval的方式更簡單。

# by KINK

J大..你好 請問我如果想把這應用在goole的jquery上的話,這段該怎麼處理呢? <script type="text/javascript" language="javascript"> google.load("jquery", "1.3.2"); </script> 我從firebug只得知jQuery is not defined,但google卻OK?.......動態引入對效能這樣之下,不知道會不會大大折扣了XD

# by tad

感謝您,這篇文章解決了我長久以來的問題!

# by Ike

使用在 1.4.3 好像會有問題耶,不知黑大能否確認一下?

# by Ike

不好意思,我還要再確認一下,因為目前只在 Plurk 上使用時會有問題。

# by mrbigmouth

推薦一個code http://www.codeproject.com/KB/ajax/ensure.aspx 可以動態加載js,css,html檔 並擁有callback功能可在載入成功後執行語法 不過css跟html的部份有些問題.... 不會像js的部分一樣,完全載入之後才進行回呼....css內包含的圖檔亦同... 因此使用這招動態加載dialog區塊時,還是會出現圖檔讀取中慢慢出現破壞版面的情形...

# by Materia

不好意思 我用了這個方法 依序載入5隻js 但是載到第2隻的時候就停住了 最內層的alert也沒有動作 我試著吧裡面三隻js註解掉 但alert仍然沒有動作 而且也沒有錯誤發生 怪怪的

# by Materia

不好意思 我用了這個方法 依序載入5隻js 但是載到第2隻的時候就停住了 最內層的alert也沒有動作 我試著吧裡面三隻js註解掉 但alert仍然沒有動作 而且也沒有錯誤發生 怪怪的

# by Jeffrey

to Materia, 不知道你載入的是哪些JS,不太能斷定原因。但我認為最有可能導致失敗的原因是出在載入完成的偵測條件上。載入程序採用檢查typeof someObject的方式來確認是否已載入完成,所以要為每個js找出一個檢測的標的(在我的範例中即jQuery及jQuery.blockUI兩個物件),很有可能是第二隻js所指定的物件有問題,導致typeof yourObject一直都是undefined,如此會被視為第二隻js沒有載入完成,後續的動作就不再進行。

# by tras

如果look_for在js中沒有可以呼叫的指令or function 要怎麼處理 ex"http://platform.twitter.com/widgets.js" 只是要單純載入

# by Jeffrey

to tras, 所有的js都會宣告新物件、函式,或是改變DOM,而偵測這些差異就能判斷是否載入完成。以所提的twitter widgets.js為例,我找到其中有段程式宣告window.twttr.verifyCSP函式,look_for應該可以用"(window.twttr && window.twttr.verifyCSP)"判斷。

Post a comment