差點被dojo搞死

做WEB界面編程很痛苦。菜鳥做WEB界面編程比黃連還苦。
Dojo 提供了一個Tooltip Widget。當我們的鼠標移到指定的地方時,一個提示框就彈出來了。
再把Tooltip稍稍擴展一下,就可以在彈出來的Tooltip裏放任意HTML代碼。加上一點AJAX調用,一個內容豐富的提示框就做出來了:
鼠標可以在tooltip裏自由移動。鼠標移出tooltip,該tooltip自動消失。夠簡單吧?可惜接着我就被dojo玩兒死了。
展開任何一個的下拉框,把鼠標移進下拉框。Tooltip立刻消失。難道Ellen Ullman的 The Bug這一刻靈魂附體?
順藤摸瓜到dojo.widgets.tooltip的代碼裏。既然跟鼠標移動有關,就找和事件onmousemove有關的代碼。很容易地找到了dojo.widgets.tooltip._onMouseMove = function(e){…}。代碼很簡單,靠一個幫助函數dojo.html.isOnElement(element, e)來判斷鼠標的位置是否在Tooltip內。加上一行dojo.debug(…),刷新頁面,發現問題所在:當鼠標在一般HTML控件上,比如table, div, select一類,用來判斷座標的mouseEvent.pageX都是鼠標相對於document的座標。可是當鼠標移動到展開的option下拉框裏,座標就編程相對於包含該option的div的座標了。怒。不過還好。不就是累加所有mouseEvent.targe和targetParent的(offsetX, offsetY)麼:
刷新,再試。「Select size」下拉框可以正常工作了。不過作爲一個負責的程序員,不能只測試一種情況吧?於是把鼠標移到了第二個下拉框。我的心立馬哇啦哇啦地涼了。鼠標移到第二行選項時,整個tooltip消失樂。
仔細觀察了一下,注意到兩件事:1. 雖然鼠標移到第二行選項,這一行並沒有顯示高亮,高亮仍然停留在第一行。2. 這種情況只存在於第二個下拉框。第一個和第三個都沒有問題。3. 鼠標的座標奇蹟般地從(600, 400)變成了(20, 10)。嗯,老實的程序員從來不在遇到問題的時候懷疑工具和機器,所以我開始沉痛檢討自己的程序。多加一條dojo.debug後,發現雖然鼠標在下拉框裏,丫對應的MouseMove.target居然是HTMLSelectElement!也就是說,牛皮轟轟的瀏覽器把下拉框背後被遮蓋的select控件框當成MouseEvent.target返回,而MouseEvent.pageX/Y仍然是針對option的相對座標。而我上面的代碼只針對option做了修復,自然跳過了當target爲HTMLSelectElement的情況。在if語句里加上 || element instanceof HTMLSelectElement也不行,因爲沒有重疊時,鼠標移到Select上面產生的MouseEvent.pageX不需要累加targetParent的座標。更重要的是,爲什麼會發生這種情況?爲什麼選項沒有高亮顯示?現在的自然選擇是檢查select的代碼。純HTML代碼,有點CSS樣式,沒有問題阿。波利亞老大在 How to Solve It裏反覆強調的話響起來了: 你已經知道什麼?什麼肯定有效?什麼是已經解決了的相關問題?能排上用場麼?嗯,迴歸原始。一個陽春select肯定沒有問題,於是我開始一項一項去掉附加的CSS樣式。結果當width被去掉後,問題消失。進一步試用不同的寬度,發現當選項長度超過選項控件長度時,問題就會出現,而且只在配合dojo使用時出現。不爽啊不爽。
墨菲定律說,壞事扎堆。推論就是,測試時,應該在出現問題的時候測試周邊情況,多半能發現更多錯誤。有了上面的經驗,自然聯想到如果下拉框超出tooltip邊界會怎麼樣?不出所料,鼠標移到超出邊界的選項時,下拉框消失了。這個錯誤應該是dojo的問題:單單比較鼠標和控件的座標是不夠的。當然也可以說是我們的問題:也許tooltip根本就不是設計來做我們這種信息框的。幸好解決方法不難:我們其實不關心鼠標的具體座標。我們只關心鼠標是不是還在tooltip內的任何一個控件上。既然這樣,我們可以找到鼠標所在的控件,然後比較該控件是不是在tooltip裏。這個簡單。又是累加offsetX和offsetY的問題:
再判斷得到的座標是不是在tooltip內。刷新。嗯,所有情況都正常了。不對,好像過猶不及。現在把鼠標移出tooltip,tooltip也不消失了。以頭戧地一分鐘。定神一下,不由再次感嘆臥春,臥室達春綠。不同的HTML元素可以重疊。所以哪怕鼠標在tooltip之外,它所在的HTML元素(比如DIV)的一角完全可能在tooltip內。於是改爲判斷四個角都在tooltip內。這步其實不可靠。不過我們在tooltip裏只支持簡單的HTML form元素,所以還是比較可靠的。這下終於成功了。
靠,其實是俺的幻覺。IE不認HTMLOptionElement,拋出異常。加入跨瀏覽器的代碼。這個終於世界清靜了。哪位老大有更好的解決方法麼?
教訓:
§ UI編程麻煩,處理太多特例。
§ 抽象層泄漏是個現實問題。用不成熟的框架抽象層泄漏就成了不可避免的現實問題。
§UI編程一點都不好玩兒。一個多小時就折騰出這點玩意兒。獲得的知識出了UI界就沒有任何傳承性。說不定這些東西在饅頭界早成歷史。