使用 Dojo 开发基于 iPad 的 Web 应用程序

Comments

在 iPad 上开发 Web 应用

随着 iPad 风靡全球,越来越多的 Web 应用开始转向 iPad。Google 公司为 iPad 做了专用版的 Gmail 邮箱界面。由于 iPad 拥有比手机更大的屏幕和 1024 × 768 的分辨率,google 采用了类似于 iPad 内置 Mail 邮件客户端的左右双栏式界面设计,这带给用户全新的界面体验。网易公司也推出了 iPad 版网易邮箱,不仅界面清爽,而且支持邮件附件在线预览。

开发前的准备

在开发 iPad 的 Web 应用之前,我们最好有一台 iPad 可以随时对网页进行测试。我们还可以在 Mac 中使用 iPhone Simulator ,在 Hardware> Device中选择 iPad即切换到 iPad 模拟平台。在模拟平台中看到的网页和在 iPad 上基本是一样的。我们也可以在电脑上通过更改用户代理(User Agent)的方法模拟 iPad 行为,注意这只是模拟 iPad 发送 HTTP 请求,使服务器以为你是在用 iPad 访问,从而帮你转向到 iPad 版的页面(如果提供了的话),你在电脑上看到的页面和在 iPad 上还是有区别的。打开最新版本的 Safari,在 开发> 用户代理中选择 Mobile Safari 3.2 – iPad。 如果没有这一项,就在 其它中输入:

 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) 
 AppleWebKit/531.21.10 (KHTML, like Gecko) 
 Version/4.0.4 Mobile/7B334b Safari/531.21.10

当 iPad Safari 发布新版本时,字符串中的版本号可能会有所变化,所以任何代码检查用户代理字符串不应该依赖于版本号。

一般来说,我们首先在电脑上将功能测试通过,然后再在 iPad 上调整页面外观与用户体验。毕竟在 iPad 上调试不如在电脑上方便。打开 Safari 的调试功能:偏好设置> 高级,勾上 在菜单栏中显示“开发”菜单,如图 1 所示。

图 1. iPad 上 Safari 的设置:
iPad 上 Safari 的设置:
iPad 上 Safari 的设置:

然后在“开发”菜单里选择 显示 Web 检查器打开调试窗口。“元素”标签用于检查 HTML 和 CSS,在页面中想要检查的元素上右击,选择“检查元素”,调试窗口会自动跳到该元素对应的 HTML。“脚本”标签用于调试 JavaScript,通过单击行号设置断点,如图 2 所示。“控制台”标签不但可以显示记录的日志,而且还可以在命令行中输入 JavaScript 代码,代码可以立即被执行。

图 2. JavaScript 调试
JavaScript 调试
JavaScript 调试

使用 Web 标准进行开发

iPad 的 Mobile Safari 不支持插件,所以一个设计原则就是只使用基于 Web 标准的技术,而不要使用插件技术,这可以使页面在 iPad 上和在其它平台上的显示保持一致。基于 Webkit 引擎的 Safari 浏览器支持以下 Web 标准:

  • HTML 4.01 和部分 HTML5
  • XHTML 1.0
  • CSS 2.1 和部分 CSS3
  • ECMAScript 3 (JavaScript)
  • DOM Level 2
  • AJAX 技术 , 包括 XMLHttpRequest

关于使用 Web 标准开发 iPad Web 应用的细节,可以参考苹果开发者(Apple Developer)网站,具体见 参考资源

选择 Dojo 简化开发

由于 iPad 具有比一般移动设备更强大的处理性能,我们完全可以使用 JavaScript 库来简化前台页面开发,增强用户体验。Dojo 是一种流行的开源 JavaScript 工具包,为基于 Web 应用的 JavaScript 快速开发而设计,它已经被广泛地使用在 Web 开发中。Dojo 提供了丰富的 Widget,让你更容易地为 Web 页面添加动态能力,提升 Web 应用程序的可用性和交互能力,同时提高了程序员的开发效率。你可以在 IBM 中国 developer Works 的 Dojo 技术专题中找到很多 Dojo 的学习资料。

Dojo 不是专门为 iPad 设计的,有些控件在 iPad 上的用户体验不能令人满意,这就需要我们进行一些扩展和改进,有时这会遇到一些麻烦。本文就是这样的一些实践总结,但也只包含了 Dojo 控件中的一小部分。我们更期待 Dojo 在以后的版本中会针对移动设备进行优化。

在正式开始之前,我先说明一个最佳实践:尽量少用 Dojo 控件。一些原生 HTML 控件在 iPad 中会被重新渲染,即使不加任何 CSS 也不会像 windows 中那么难看。比如 input 输入框,在 iPad 中会有圆角和阴影效果,至于 select 元素就更炫了。你完全没必要为了美化而加上“dojoType”。

本文使用的 Dojo 版本为 1.5,如果读者使用其他版本,可能有些代码需要略作调整,如果有任何有关问题,可以联系作者。

改进 dijit.form.DateTextBox

我们经常需要在表单中让用户选择一个日期。iPad 的本地应用程序中自带的 Date Picker 控件很酷,可惜无法在 Safari 里使用。目前版本的 Mobile Safari 也不支持 HTML5 的 Date Pickers。你完全可以使用三个 <select>,分别让用户选择年、月、日,这在 iPad 里已经可以显示出很酷的效果了,用户体验也不错。除此之外,我们还可以使用 JavaScript 日期控件。

Dojo 提供了这样一个日期控件 dijit.form.DateTextBox,在 iPad 中看起来像是这样。

图 3. 日期控件显示
日期控件显示
日期控件显示

这个页面在 PC 上看起来没什么问题,但是在 iPad 中,每个日期项的字体太小,导致在 iPad 上日期很难被选择。一个简单的方法是,增加日期项的字体,注意 DateTextBox 里包含一个 input 控件,这会导致 iPad 的软键盘弹出来,并占掉屏幕一半的大小。你可以提示用户将 iPad 竖起来,这样软键盘只占到不到三分之一的屏幕空间。

清单 1.增大日期面板的字体
.dijitPopup{
    font-size:34px;}

即使这样,当我们从弹出的面板里选择完日期后,DateTextBox 会自动将焦点设置在它的输入框中,于是软键盘再次弹了出来,还需要手动将软键盘缩回去,很麻烦。所以我们想到禁止软键盘的弹出,让用户只能从日期控件中进行日期的选择。禁止软键盘弹出有一些麻烦,我的做法是,首先将 DateTextBox 控件设为 readOnly 状态。

清单 2.将 DateTextBox 控件设为 readOnly
 <input id="local" value="2010-09-16" type="text" dojoType="dijit.form.DateTextBox" 
 readOnly="true" />

然后需要对 DateTextBox 进行扩展,这里其实是对 DateTextBox 的内部实现稍作修改,代码见清单 3。

清单 3.扩展后的 DateTextBox
 dojo.provide("widget.MyDateTextBox"); 

 dojo.require("dijit.form.DateTextBox"); 

 dojo.declare("widget.MyDateTextBox", [dijit.form.DateTextBox], { 
    _open: function(){ 
        // 打开 TimePicker, 给 onValueSelected 赋值。
        // 我们主要做了两件事,一是去掉 readOnly 的限制,二是选择完日期后强制文本框焦点离开
        
        if (this.disabled || !this.popupClass) {  // 去掉 this.readOnly 限制
            return; 
        } 
        
        var textBox = this; 
        
        if (!this._picker) { 
            var PopupProto = dojo.getObject(this.popupClass, false); 
            this._picker = new PopupProto({ 
                onValueSelected: function(value){ 
                    if (textBox._tabbingAway) { 
                        delete textBox._tabbingAway; 
                    } 
                    else { 
                        textBox.focus(); 
                    } 
                    setTimeout(dojo.hitch(textBox, "_close"), 1); 
                    
                    dijit.form._DateTimeTextBox.superclass._setValueAttr.call(
                    textBox, value, true); 
                    
                    textBox.textbox.blur(); // 强制文本框焦点离开,否则可能会有无法连续点击的问题
                }, 
                id: this.id + "_popup", 
                dir: textBox.dir, 
                lang: textBox.lang, 
                value: this.get('value') || new this.dateClassObj(), 
                constraints: textBox.constraints, 
                
                datePackage: textBox.datePackage, 
                
                isDisabledDate: function(/*Date*/date){ 
                    var compare = dojo.date.compare; 
                    var constraints = textBox.constraints; 
                    return constraints && 
                    ((constraints.min && compare(constraints.min, 
                    date, textBox._selector) > 0) || 
                    (constraints.max && compare(constraints.max, date,
                     textBox._selector) < 0)); 
                } 
            }); 
        } 
        if (!this._opened) { 
            dijit.popup.open({ 
                parent: this, 
                popup: this._picker, 
                orient: { 
                    'BL': 'TL', 
                    'TL': 'BL'
                }, 
                around: this.domNode, 
                onCancel: dojo.hitch(this, this._close), 
                onClose: function(){ 
                    textBox._opened = false; 
                } 
            }); 
            this._opened = true; 
        } 
        
        dojo.marginBox(this._picker.domNode, { 
            w: this.domNode.offsetWidth 
        });        
    } 
 })

在 HTML 页面中,我们使用自己定制的 DateTextBox。

清单 4. 使用扩展后的 DateTextBox
 <script type="text/javascript"> 
    dojo.require("widget.MyDateTextBox"); 
 </script> 

 <input id="local" value="2010-09-16" type="text" dojoType="widget.MyDateTextBox" 
 readonly="true" />

看一下效果。

图 4. 日期控件显示效果:
日期控件显示效果:
日期控件显示效果:

大功告成了吗?还差一步,我们在按日期面板的时候,整个面板会高亮,而且是透明的灰色。这是因为 Mobile Safari 默认会将一个链接或者一个可点击的元素时显示为灰色的高亮。使用 CSS 属性 -webkit-tap-highlight-color ,你可以修改颜色或者禁止高亮。清单 5 是禁止高亮的代码。

清单 5.禁止高亮
body{
    -webkit-tap-highlight-color:rgba(0,0,0,0);
 }

如果你想在页面中使用 dojo 的时间控件 dijit.form.TimeTextBox,也需要对代码进行修改。在 TimeTextBox 控件选择时间时,你经常会使用鼠标移动到上箭头和下箭头进行翻页,但是在 iPad 里,你点了上箭头或者下箭头之后,你会发现时间不停地在滚动,要选择个时间真是太困难了。这是因为 iPad 的鼠标事件是 Mobile Safari 模拟出来的,你可能需要更改一下箭头的事件。我更推荐的方法是使用 HTML 的 <select> 元素,效果绝对不比 TimeTextBox 差。

清单 6.select 标签示例
 <select id="time" name="time"> 
    <option value="0900" selected="true">09:00</option> 
    <option value="0930">09:30</option> 
    <option value="1000">10:00</option> 
    <option value="1030">10:30</option> 
    <option value="1100">11:00</option> 
    <option value="1130">11:30</option> 
    <option value="1200">12:00</option> 
    <option value="1230">12:30</option> 
    <option value="1300">13:00</option> 
    <option value="1330">13:30</option> 
    <option value="1400">14:00</option> 
    <option value="1430">14:30</option> 
    <option value="1500">15:00</option> 
    <option value="1530">15:30</option> 
    <option value="1600">16:00</option> 
    <option value="1630">16:30</option> 
    <option value="1700">17:00</option> 
    <option value="1730">17:30</option> 
 </select>

对 DateTextBox 加上一些 CSS,如图 5 所示。

图 5. 增加了 CSS 效果的日期控件
增加了 CSS 效果的日期控件
增加了 CSS 效果的日期控件

改进 dijit.form.Slider

HTML5 有一个滑动条,用于包含一定范围内数字值的输入域。目前 iPad 上的 Mobile Safari 对此表示暂不支持。

清单 7.HTML5 的滚动条
 <input type="range" name="points" min="1" max="10" />

Dojo 提供了 dijit.form.Slider 这样一个图形化的控件,用于选择一定范围内的值,如图 6 所示。

图 6.Slider 控件
Slider 控件
Slider 控件

示例代码见清单 8。

清单 8.一个 slider 的示例
 <script type="text/javascript"> 
    dojo.require("dijit.form.Slider"); 
 </script> 

 <div id="slider" dojoType="dijit.form.HorizontalSlider" value="2" 
 maximum="12" minimum="2" discreteValues="6" showButtons="true" 
 slideDuration="0" clickSelect="false" style="width:400px;"> 
    <ol dojoType="dijit.form.HorizontalRuleLabels" 
    container="topDecoration" style="height:1.2em;
    font-size:80%;" count="6" maximum="12" minimum="2" 
    constraints="{pattern:'#'}"> 
    </ol> 
    <div dojoType="dijit.form.HorizontalRule" 
    container="topDecoration" count="6" style="height:5px;"> 
    </div> 
 </div>

将 slideDuration 设为 0 可以使在 iPad 上的滑动更加流畅;将 clickSelect 设为“false”禁止点击进度条来更改刻度值,因为我想要把滑动条改为用手指在触摸屏上滑动来改变数值,而不是通过点击,这个功能 dojo 默认是没有提供的。

先简单介绍一下 TouchEvent。TouchEvent 对象封装了一次 touch 事件的信息,当手指触摸屏幕或者在上面移动时,系统会不断向程序发送 TouchEvent 对象,这构成了一个 touch 事件序列。这个序列开始于第一个手指刚触摸屏幕,结束于最后一个手指离开屏幕。Touch 事件和 mouse 事件基本类似,区别在于 touch 事件可能同时发生在屏幕的不同位置,而 mouse 事件只会发生在一处。

不同类型的 TouchEvent 对象可能会在以下情况触发:

  • touchstart 手指触摸屏幕
  • touchmove 手指在屏幕上移动
  • touchend 手指从屏幕移开
  • touchcancel 系统取消对 touch 进行跟踪

我们只需要为上述的几种事件添加对应的响应函数。清单 9 是给 Slider 加上触摸事件的代码。

清单 9. 给 Slider 加上触摸事件
 dojo.require("dijit.form.Slider"); 

 varINTERVAL = 80; // 手指滑动引起 slider 值发生改变所需要的最小像素值
			
 dojo.addOnLoad(function() { 
 // 处理 slider 的 touch 事件
    dojo.connect(dojo.byId("slider"), "touchstart", "onSliderTouchStart"); 
 }); 

 functiononSliderTouchStart(e){ 
    // 当手指刚触摸 slider 时触发该函数
    if(e.targetTouches.length != 1) 
        returnfalse; 
    startX = e.targetTouches[0].clientX; 
    
    sliderTouchMove = dojo.connect(dojo.byId("slider"), "touchmove", 
    "onSliderTouchMove"); 
    sliderTouchEnd = dojo.connect(dojo.byId("slider"), "touchend", "onSliderTouchEnd"); 
 } 

 functiononSliderTouchMove(e){ 
    // 阻止浏览器的默认行为 (scroll, zoom) 
    e.preventDefault(); 
    
    // 如果是多点触摸的话就不再处理了 ( 那是一个 gesture 事件 ) 
    if(e.targetTouches.length != 1) 
        returnfalse; 

    // 计算手指滑动的距离,如果超过我们设的 INTERVAL 的值,则改变 slider 的值
    vardeltaX = e.targetTouches[0].clientX - startX; 
    varslider = dijit.byId("slider"); 
    if(deltaX >= INTERVAL) { 
        slider.increment(e); 
        startX += INTERVAL; 
    } 
    if(deltaX <= (INTERVAL * -1)) { 
        slider.decrement(e); 
        startX -= INTERVAL; 
    } 
 } 

 functiononSliderTouchEnd(e){ 
    // 阻止浏览器的默认行为 (scroll, zoom) 
    e.preventDefault(); 
    
    // 最后一个手指离开 slider 就不再进行处理
    if(e.targetTouches.length > 0) 
        returnfalse; 
    
    dojo.disconnect(sliderTouchMove); 
    dojo.disconnect(sliderTouchEnd); 
 }

改进 dojox.grid.DataGrid

Dojox.grid.DataGrid 是一个很常用的 dojox 控件,可以应付较为复杂的数据展示及数据操作。DataGrid 需要和一个 store 绑定在一起,可以显示该 store 里的数据,常见的 store 有 ItemFileReadStore,ItemFileWriteStore,XmlStore 等。一个 DataGrid 的示例见清单 10。

清单 10.DataGrid 示例
 <script type="text/javascript"> 
 dojo.require("dojox.grid.DataGrid"); 
 dojo.require("dojo.data.ItemFileWriteStore"); 
 </script> 

 <span dojoType="dojo.data.ItemFileWriteStore" jsId="jsonStore" 
 url="countries.json"></span> 
 <table dojoType="dojox.grid.DataGrid" jsid="grid" id="grid" query="{ name: '*' }"> 
    <thead> 
        <tr> 
            <th field="name" width="300px"> 
                Country/Continent Name 
            </th> 
            <th field="type" width="auto"> 
                Type 
            </th> 
        </tr> 
    </thead> 
 </table>

构造出来的数据表格在电脑上看起来像是这样。

图 7. 网格控件:
网格控件:
网格控件:

当 grid 转向 iPad 后,会出现滚动条问题:图 7 中的滚动条在 iPad 里将不会显示,需要你自己实现在 iPad 中的滚动条功能(也可以期待 dojo 在以后的版本中实现,但至少目前只能靠自己了)。

有一个开源项目 iScroll(基于 MIT License,见 参考资源)提供了在移动设备上的滚动条功能。图 8 为使用了 iScroll 的 DataGrid,图里黑色的滚动条是由 iScroll 创建的。

图 8.iScroll 网格控件:
iScroll 网格控件:
iScroll 网格控件:

由于 DataGrid 的特殊性,我们还需要一些额外的操作。首先,需要禁掉 document 的默认 touchmove 事件,否则拖动 Grid 会带动整个页面一起拖动。然后找到要为其创建滚动的 <div>(DataGrid 里是 class=”dojoxGridContent”的节点)。iScroll 的构造函数有两个参数,第一个参数就是刚才找到的节点,第二个参数是一个对象。需要确保对象里的 checkDOMChangesvScrollbar为 true(目前的 iScroll 版本默认值都是 true)。最后,DataGrid 的 onCellClick 事件需要被重写,否则点击单元格时会出现一些问题。完整的代码见清单 11。

清单 11.给 DataGrid 增加 iPad 滚动功能
 <script type="text/javascript" src="iscroll.js" /> 
 <script type="text/javascript"> 
    dojo.require("dojox.grid.DataGrid"); 
    dojo.require("dojo.data.ItemFileWriteStore"); 
    
    dojo.addOnLoad(function(){ 
        // 阻止浏览器的默认行为
        document.addEventListener('touchmove', function(e){ 
            e.preventDefault(); 
        }); 
        
        // 找到需要加上滚动条的节点
        varscrollNode = dojo.query("#grid .dojoxGridContent")[0]; 

        // 新建 iScroll 对象
        myScroll = newiScroll(scrollNode); 
		
        grid.setStore(jsonStore); 

        // 重写 onCellClick 事件,否则在 iPad 可能有不兼容的情况。这里简单地禁止了事件,你可以改写为其它的方法。
        grid.onCellClick = dojo.stopEvent; 
    }) 
 </script>

还有一点要注意的是,DataGrid 有数据延迟加载的功能,这本来是件好事,在 PC 上你只要把滚动条往下拖即可自动加载数据;而在 iPad 上,拖动我们的滚动条是不会自动加载的,导致后面的数据无法显示。一个简单的方法是,将 DataGrid 的 rowsPerPage属性设的大一些,使数据在开始时全部加载进来。

总结

本文是作者在使用 Dojo 开发基于 iPad 的 Web 应用时的一些经验之谈,希望可以给其他 Web 开发者一些借鉴和参考。主要包括以下几方面内容:

  • 对 dijit.form.DateTextBox 的改进:包括将字体调大,禁止软键盘弹出,禁止面板高亮,以及一些 CSS 美化。
  • 对 dijit.form.Slider 的改进:增加了触摸滑动的支持。
  • 对 dojox.grid.DataGrid 的改进:由于默认的滚动条在 iPad 中不会显示,需要手动增加滚动条。iScroll 提供了一个苹果风格的滚动条。

由于水平和时间有限,作者不可能把所有控件都在 iPad 上一一试过,因此开发者也可以从本文找到扩展 Dojo 控件的思路,这也体现了 Dojo 强大的可扩展性。如果你需要一些比较特殊的功能,你可以很方便地建立属于自己的 Dojo 控件库。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=627409
ArticleTitle=使用 Dojo 开发基于 iPad 的 Web 应用程序
publish-date=02172011