差点被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界就没有任何传承性。说不定这些东西在馒头界早成历史。