dojo 移动应用_编写定制的Dojo应用程序

dojo 移动应用

我们最近完成了Web 2.0 Dojo原型的开发。 该原型非常广泛,为信息管理提供了新功能。 我们与用户体验团队合作,以确保应用程序可用。 屏幕是由图形Web设计人员设计的,具有专业的外观。

本文记录了我们对该原型的Web 2.0开发的经验。 因为Web 2.0是相对较新的,所以在需要时可能很难入门或不知道如何进行自定义。 我们的Dojo应用程序没有提供开箱即用的外观。 我们需要一个与我们的产品线品牌保持一致的图形设计。 因此,我们不得不使用Dojo进行自定义。 定制是大多数开发人员最终要花费大量时间的地方,特别是如果他们不确切地知道如何解决该问题。

因为本文着重于Dojo应用程序的定制,所以我们没有描述示例中需要或显示的每个小部件属性的详细信息。 假定您具有Dojo和CSS的基本知识。 本文中的示例基于Dojo 1.1.0(请参阅参考资料中的链接)。

创建一个Dojo应用程序

设计方法

作为经验丰富的软件工程师,我们习惯于使用面向对象(OO)技术进行开发。 我们希望Web 2.0应用程序遵循我们多年来在Java™编程中实践的原则。 我们发现使用Dojo小部件和模板模式以及JavaScript / Dojo对象可以在很大程度上做到这一点。

使用Dojo小部件和模板模式

我们编写了包含JavaScript类和HTML模板进行渲染的自定义小部件。 这使我们能够将更多的OO方法应用于我们的应用程序,而不仅仅是编写使用全局JavaScript函数HTML文件。 借助自定义窗口小部件,我们获得了各种功能:使用定义明确的窗口小部件进行对象隔离,使用窗口小部件HTML动态内容以在需要时更新DOM,以及使用自定义窗口小部件生成不同HTML ID的同一HTML模板的多个实例。 我们还能够扩展我们编写的自定义小部件,从而添加更多专业版本。

清单1显示了自定义小部件JavaScript文件的一部分,该文件建立在dijit._Widgetdijit._Templated

清单1.定制小部件JavaScript文件
dojo.provide("mywidgets.MyWidget");
// put any other requires the widget needs here
dojo.declare(
  'mywidgets.MyWidget',
  [dijit._Widget, dijit._Templated],
{
  widgetsInTemplate: true,

templatePath: dojo.moduleUrl( "mywidgets", 
"../templates/mywidgets/templateMyWidget.html");
// put any other variables and methods for this widget here
// can also override methods from the base classes here

});

widgetsInTemplate属性告诉Dojo它需要解析模板文件,因为它包含Dojo小部件,而不仅仅是HTML标记。 templatePath属性告诉Dojo,此小部件将使用指定HTML模板进行呈现。 实例化窗口小部件时(例如使用new mywidgets.MyWidget()实例),模板HTML会在此对象插入DOM的位置处呈现。

清单2显示了此小部件HTML模板文件。

清单2. HTML模板
<div class="templateMyWidget">
    <!-- Other Widgets and HTML can be included here -->
<button dojoType="dijit.form.Button" id="myButton_${id}" 
label="My Button" dojoAttachPoint="myButton"></button>
</div>

在此示例中,变量替换用于ID,以防您的代码需要通过其ID访问按钮。 ${id}替换为从dijit._Widget类继承的ID属性的值。 该ID是唯一的; 因此,将其用作模板ID的一部分可以使它实例化,因此,如果您实例化多个窗口小部件,则每个窗口小部件都将具有唯一的ID。 我们还包括了dojoAttachPoint属性。 这将在您的小部件中创建一个指向DOM节点的属性。 因此,如果您有权访问小部件(例如, myWidgetObj ),则可以使用myWidgetObj.myButton进入DOM节点。 您不需要知道ID,并且可以省略该属性,让系统为该元素创建自己的唯一ID。 您可以使用myWidgetObj.myButton.id检索系统生成的ID。

如果下载Dojo源代码,您会注意到Dojo小部件也采用这种方式编码。 每个Dojo小部件都有一个JavaScript文件和与之关联HTML文件(通常都具有相同的名称)。 模板位于窗口小部件JavaScript文件级别的名为模板的目录中。 例如,Button小部件JavaScript文件是<dojo_root> \ dijit \ form \ Button.js,其模板文件是<dojo_root> \ dijit \ form \ templates \ Button.html,其中<dojo_root>是Dojo代码的目录已下载。

使用JavaScript和Dojo对象

当我们开始原型时,我们没有太多JavaScript经验,尽管它确实允许使用JavaScript Object使用OO原理进行开发。 Dojo提供了使用dojo.declare构造定义您自己的类的更好方法。 我们建议您阅读developerWorks文章“ Java开发人员的Dojo概念”(请参阅参考资料 )。 与任何原型一样,我们的要求一直超出初始设计的范围,因此代码变得混乱。 我们希望我们采用更多的设计模式。 关于设计模式的文章很多,其中包括JavaScript和Dojo的示例。 我们建议使用一种称为“带有JavaScript的MVC”(请参阅参考资料 )。

错误处理

像Java代码一样,JavaScript确实具有异常处理,我们建议将其用于错误处理。

如清单3所示,JavaScript中的try / catch处理非常类似于Java代码。 您可以在异常对象中看到堆栈跟踪。

清单3.尝试/捕获处理
try {
     // your logic
}catch(e){
     // error handling
}

可以从JavaScript中的方法引发异常,就像Java编程一样。

清单4.引发的异常
if( <test for error condition> ) throw "meaningful error message";

Dojo的开发环境

  • 起点-Dojo示例/演示和测试代码: Dojo的入门非常简单快捷,因为可以使用任何Web浏览器运行大量示例,然后根据需要进行修改。 大多数示例位于Dojo源代码中的tests目录中。
  • 集成开发环境(IDE):在开始我们的项目时,几乎没有Ajax / Dojo支持。 我们在基于Eclipse的IDE中进行了所有开发,因为这是我们进行所有Java开发的地方。 我们确实找到了一些具有JavaScript和Dojo代码辅助功能的插件。 但是,对于需要偏离示例代码的情况,我们经常求助于阅读实际的Dojo类以了解如何使用它们。 我们面临的最大调整是没有使用JavaScript进行编译时检查。 这意味着在执行带有错误的路径之前,您不会找到错误。
  • 帮助-Dojo社区:由于Dojo非常受欢迎,因此有大量的支持社区。 在许多论坛上,其他开发人员可以发布他们的问题以及针对问题的代码解决方案。 我们经常发现这些发布的示例很有帮助; 如果不是直接的话,他们通常会给我们一些想法,以帮助我们解决我们试图解决的问题。
  • 调试– Firebug插件:对于我们的原型,我们需要一个Web浏览器并选择Mozilla Firefox。 为此,我们添加了Firebug插件。 Firebug可从许多位置下载,您可以在参考资料中获得指向它的链接。 Firebug做的一件事是记录HTTP请求/响应以及计时。 您可以在JavaScript文件中设置断点进行调试。 它还允许您检查/编辑HTML,CSS和DOM代码。 Firebug在弄清楚Dojo小部件中正在使用哪些样式属性方面也非常有帮助。

    此外,Firebug将名为“ console”的全局变量添加到您的Web应用程序。 此变量具有许多用于日志和跟踪的方法,包括: debuginfowarnerrorlog 。 这些方法将消息以及JavaScript文件名和所跟踪消息的行号一起写入控制台。 我们启用了条件跟踪,因此我们可以使用全局属性来打开和关闭控制台跟踪。

我们可以提供的一个提示是,我们发现当使用dojo.require语句包含我们自己的小部件时,如清单5所示,除非在控制台中添加了显式脚本src,否则控制台中的跟踪消息文件名和行号不可用。语句,如清单6所示。

清单5.使用dojo.require语句包含小部件
dojo.require("acme.MyWidget");
清单6.添加一个显式脚本src语句
<script type="text/javaacript"src="widget/MyWidget.js"></script>

自定义Dojo应用

我们研究了几个JavaScript框架,尽管我们实际上想要一个比Dojo高一点的框架,但它具有所有正确的构建块,定制点和广泛的支持社区。

如何定制

因此,我们决定使用Dojo,但要对其进行自定义,并且此过程涉及很多方面。 自定义Dojo小部件的外观的最简单方法是使用自定义样式表(CSS)。 您还可以重写Dojo小部件方法,将现有Dojo小部件子类化以实现特殊行为,或创建自己的小部件。 在本节中,我们描述此原型使用的各种自定义。

注意:此列表代表为我们的应用程序实际开发的内容的子集。 当前,知识产权(IP)流程中包含许多本文未包括的其他自定义项。

重新样式化Dojo小部件

Dojo具有三种可以直接使用的基本外观:索里亚(Soria),苔原(苔原)和nihilo。 通过在应用程序的整体HTML主体标签的class属性中指定外观,这些外观也可以用作基础。 然后,您可以使用自己CSS覆盖选择的样式。 您也可以从头开始编写所有CSS。 但是,如果您的应用程序使用许多小部件,则此过程很耗时。 要查看具有预定义外观的Dojo窗口小部件,可以运行Dijit Theme Tester,它位于<dojo_home> /dijit/themes/themeTester.html中,其中<dojo_home>是下载Dojo源代码的目录。

Dojo小部件的模板包含可用于定义自定义样式表的类属性,但是在任何地方都找不到这些样式名。 为了找到小部件的类名,我们发现了两种有用的方法。 第一种方法是查看与窗口小部件关联的模板.html文件。 dijit.form.Button小部件的模板文件类似于清单7。

清单7. dijit.form.Button小部件的模板
<div class="dijit dijitReset dijitLeft dijitInline"
    dojoAttachEvent="onclick:_onButtonClick,onmouseenter:_onMouse,onmouseleave:
  _onMouse,onmousedown:_onMouse"
    waiRole="presentation"
    ><button class="dijitReset dijitStretch dijitButtonNode 
dijitButtonContents" dojoAttachPoint="focusNode,titleNode"
        type="${type}" waiRole="button" waiState="labelledby-${id}_label"
        ><span class="dijitReset dijitInline ${iconClass}" dojoAttachPoint="iconNode" 
             ><span class="dijitReset dijitToggleButtonIconChar">✓</span 
        ></span
        ><div class="dijitReset dijitInline"><center class="dijitReset dijitButtonText"
id="${id}_label" dojoAttachPoint="containerNode">${label}</center></div
    ></button
></div>

对于样式,您将关注模板的类属性的值。 由${}包围的类值,例如${iconClass} ,将替换为在Button标记中指定的iconClass属性的值。

在上面的示例中, dijitButtonText用于设置按钮中显示的文本的样式。 因此,如果您希望文本为蓝色,则可以在样式表中包含以下代码:

清单8.将按钮文本设置为蓝色的代码
.dijitButtonText
{
   color: blue;
}

您可以更加具体,甚至可以进一步限定样式。 如果该类名也用于其他小部件,则很好。 例如, .dijitButtonText也用于ComboButtonDropdownButton模板中。 因此,在上面给出CSS示例中,所有这些控件都将具有蓝色文本。 这些控件JavaScript文件包含在最外层使用的baseClass属性,并且可以在CSS中用来区分它们。 该baseClassButtondijitButton ,而其他两个基类是dijitComboButtondijitDropDownButton分别。 如果只希望常规按钮具有蓝色文本,那么CSS类似于清单9。

清单9.常规按钮具有蓝色文本的代码
.dijitButton .dijitButtonText
{ 
   color: blue;
}

如果您使用Dojo外观作为基础,我们建议在该行的前面添加.soria ,甚至可能添加更多级别,例如。 dijitButtonNode 。 这是因为,如果在两个不同的文件中使用相同CSS类,则可能会使用您不期望CSS类。 它将使用更合格的那个。 您可以在CSS规范的6.4.3节中看到如何执行此计算(请参阅参考资料 )。 您还可以通过添加CSS !important限定(在规范的6.4.2节中进行说明)来强制使用它。

当使用各种外观和样式时,有时很难跟踪将用于特定类的样式定义。 这是Firebug派上用场的地方。 要在网页上打开Firebug控制台,请单击浏览器窗口左下角的图标。 Firebug控制台的左侧有一个HTML标签,用于显示屏幕上显示HTML代码,包括类属性。 如果单击HTML标记,则会在右侧的“样式”选项卡下显示影响此标记的关联CSS。 如果在代码中多次定义了相同的class属性,它将向您显示所有定义以及哪些定义被覆盖以及哪些定义正在使用。 如果要在屏幕上查看特定区域的样式,请在Firebug工具栏中单击“检查”,然后在屏幕上单击该区域/小部件。 这将带您到负责该区域HTML代码,并在右侧显示其关联CSS。

图1显示了Dijit Theme Tester中soria主题的Create按钮的检查。 (有关大图,请单击此处 。)在Firebug中,您可以看到dijits.css中指定的填充样式被soria.css中指定的填充样式覆盖,后者由.soria .dijitButtonNode而不是just进一步限定.dijitButtonNode

图1.应用程序的默认Dojo样式
我们应用程序的默认Dojo样式

使用Dojo的基本窗口小部件时,我们遇到了一些限制。 一个局限性是无法将圆角添加到某些小部件。 使用Mozilla特定CSS样式可能会产生圆角,但这会强制您具有纯色背景,并且仅适用于Mozilla Firefox。 因此,除非控件是固定大小的图像,否则您可能无法在控件的背景上使用四舍五入的渐变图像,这对于按钮和标题栏(其中包含的文本是可变的,或者它们需要而不是固定的宽度和高度占据屏幕的百分比。

Dojo确实通过某些小部件提供了此功能,例如dijit.form.Button 。 Dojo的模板包含具有类属性dijitLeftdijitRight <div>元素,这dijitLeft元素可分别用于显示舍入的左侧图像和舍入的右侧图像。 请注意,在1.1.0的初始版本中, dijitRight属性已从模板中删除,因此上面显示的Button的模板示例中未显示该属性。 但是,此问题已得到解决。 如果你发现这样的情况下,如果修复可能是您的应用程序的关键,你可以得到修改后的文件从每晚构建(见Dojo工具包的链接相关的话题 )。

图2显示了开箱即用Dojo soria主题的应用程序的早期外观。

图2.应用程序的默认Dojo样式
我们应用程序的默认Dojo样式

图3显示了带有样式自定义功能的应用程序的最终外观。 (单击此处查看大图。)

图3.我们的应用程序的定制Dojo样式
针对我们的应用程序的定制Dojo风格

导航辅助工具-面包屑小径

面包屑路径在许多网站上用于跟踪导航。 Dojo提供了一个dijit.layout.StackController小部件,您可以将其与dijit.layout.StackContainer小部件结合使用,从而可以在StackContainer浏览页面。 清单10显示了如何使用它的基本示例。 该代码可供下载,可以从Dojo源代码的<dojo_home> \ dijit \ tests \ layout \目录中运行。

清单10.将dijit.layout.StackController小部件与dijit.layout.StackContainer小部件一起使用
<div dojoType="dijit.layout.StackController" containerId="myStackContainer"></div>
    
<div id="myStackContainer" dojoType="dijit.layout.StackContainer"
    style="width: 90%; border: 2px solid; height: 100px;">
    <div id="page1" dojoType="dijit.layout.ContentPane" 
  title="page 1">This is page 1.</div>
    <div id="page2" dojoType="dijit.layout.ContentPane" 
  title="page 2">This is page 2.</div>
    <div id="page3" dojoType="dijit.layout.ContentPane" 
  title="page 3">This is page 3.</div>
</div>

使用Dojo的soria样式表,此代码类似于图4。

图4.使用soria样式表的StackContainer应用程序
使用soria样式表的StackContainer应用程序

单击StackController每个按钮将显示其对应的页面。 但是,如果您希望具有典型的面包屑行为,则在向下钻取时添加其他页面,然后单击第1页将返回第一页,并从面包屑轨迹(以及StackContainer )中删除所有其他页面。 ,则需要一些自定义代码。

从清单10中的相同代码开始,但StackContainer没有任何初始静态页面,如清单11所示。

清单11.没有初始静态页面的代码
<div dojoType="dijit.layout.StackController" containerId="myStackContainer">
</div>
    
<div id="myStackContainer" dojoType="dijit.layout.StackContainer"
    style="width: 90%; border: 2px solid; height: 100px;">
</div>

为了模拟向下钻取页面,您添加了一个按钮,以将页面动态添加到StackContainer。

清单12.添加一个按钮以动态添加页面
<button dojoType="dijit.form.Button"
    showLabel="true"    
    label="Add Page"
    onClick="loadPage">
</button>

loadPage代码创建一个dijit.layout.ContentPane并将其添加到myStackContainer ,如清单13所示。

清单13.创建dijit.layout.Content.Pane
var nPages = 0;
  function loadPage()
  {
    nPages = nPages + 1;
    var container = dijit.byId('myStackContainer');
    if( container )
    {
      var pageid = "page" + nPages;
      add(container,"Page " + nPages, pageid);
      container.selectChild(pageid);
    }
  }
  
  function add(parent,name,id)
  {
    var node = document.createElement("div");
    var child = new dijit.layout.ContentPane
      ( {title: name, id: id },node );
    parent.addChild(child);

    //add content to the new page
    dojo.byId(id).innerHTML = name;    
  }

有了这些添加,现在的初始屏幕将如图5所示。

图5使用soria样式表修改的应用程序-初始屏幕
使用Soria样式表修改的应用程序-初始屏幕

两次按Add Page按钮之后,屏幕将如图6所示。

图6使用soria样式表修改的应用程序-添加了两个页面
使用Soria样式表修改的应用程序-添加了两个页面

单击第1页,仍将第2页保留在StackController 。 当按下堆栈控制器中的按钮,并且尚未显示该页面时,它将触发特定于StackContainer.selectChild事件StackContainer. 我们需要订阅该事件,以便当用户选择返回到面包屑路径中的上一页时,我们可以操纵堆栈控制器。 清单14中的代码执行了此操作,其中selectedPage是我们将实现以处理此操作的方法的名称。

清单14.实现selectedPage方法
dojo.subscribe("myStackContainer-selectChild","","selectedPage");

selectedPage方法将调用一个名为removeDownstream的帮助器方法。 removeDownstream获取被单击页面的索引,并删除该给定索引之后的所有页面,如清单15所示。

清单15.使用removeDownstream方法
function selectedPage(page)
 {
    removeDownstream(page.id) ;
 }

function removeDownstream( pageid )
{
    var widget = dijit.byId('myStackContainer');
    if(widget)
    {
      var children = widget.getChildren();
      var index = dojo.indexOf(children, dijit.byId(pageid)); 
        //get index of page that was selected from the breadcrumb trail
      index = index+1; //start removing from the page to the right
      while( children.length > index )
      {
        widget.removeChild(children[ index ]);
        index = index + 1;
       }
    }
}

包含此代码后,返回到该路径中的任何上一页将删除面包屑路径中的所有下游页面以及StackContainer wid get(可以通过下载并运行此代码并检查HTML源代码中的内容轻松地进行验证。萤火虫)。

为了使它看起来更像典型的面包屑轨迹,可以使用CSS和DOM样式操作为面包屑节点设置样式。 样式包含在完整的示例中,可以下载,但在此不再详细说明。 最后一个示例的外观和操作如下。

通过单击四次“ 添加页面”按钮添加四页之后,面包屑轨迹将如图7所示。

图7.带有面包屑痕迹的修改后的应用程序-4页
带有面包屑痕迹的修改后的应用程序-4页

单击页面2会得到图8所示的内容。

图8.带有面包屑痕迹的修改后的应用程序–返回第2页
修改后的应用程序带有痕迹–返回第2页

树-启用/禁用的节点

Dojo的dijit库包含具有基本树功能的Tree小部件。 有一些方法可让您扩展和折叠每个节点。 但是,我们需要一棵还提供禁用节点功能的树。 有几种方法可以执行此操作:可以从头开始创建一个全新的小部件,我们可以使用Dojo的Tree小部件作为创建自己的小部件的基础,也可以使用Dojo提供的功能完成任务。 我们将引导您完成Dojo的完成。

Dojo树的每个级别都包含一个图标和一个标签。 Dojo提供了一种在Tree的标签中指定回调方法的方法,以获取应用于树中每个项目的标签和图标样式类。 我们将使用它来设置禁用节点的样式,使其看起来已禁用。 在此示例中,我们将禁用所有非叶子节点,因为通常将非叶子节点仅用于对要在其上执行任务的项目进行分类。

首先,定义CSS样式,如清单16所示。

清单16.定义CSS样式
.enabled
 {
        color: black;
        cursor: pointer;
 }
 
 .disabled
 {
        color:gray;
        cursor: not-allowed;
       background-color: transparent !important;
 }

单击该节点后,它将获得一个新的类属性dijitTreeLabelFocused ,添加到该属性。 通过对禁用样式使用!important ,我们确定它将在dijitTreeLabelFocused设置的背景颜色上使用透明背景颜色。 这就是我们使它看起来像在单击时未真正选择节点的样子。

标签样式的回调方法采用两个由Tree小部件传递的参数,一个是单击的项目,另一个是指定该树节点当前是否打开的布尔参数。 我们的方法类似于清单17。

清单17.原型的回调方法
function getLabelClassForMyTree(
            /*dojo.data.Item*/ item, /*Boolean*/ opened)
 {
        if( item && item.children )
                  return "disabled";
      
        return "enabled";
 }

要使用此方法,我们需要指定树小部件标签的getLabelClass属性,如清单18所示。

清单18.指定getLabelClass属性
<div dojoType="dijit.Tree" id="myTree" model="model"
        onClick="onSelectItem"
        getIconClass="getIconClassForMyTree"
        getLabelClass="getLabelClassForMyTree">
        </div>

请注意,此样式仅样式化标签本身。 因此,如果将鼠标悬停在图标或标签右侧的空格上,将不会看到已禁用的光标样式。 要使用自定义图像设置图标的样式并在非叶节点上将其显示为在鼠标悬停时处于禁用状态,可以使用清单19中所示的回调方法。然后在树小部件标签的getIconClass属性中指定此函数,如下所示:如清单18所示。

清单19.用自定义图像样式化图标
function getIconClassForMyTree(
/*dojo.data.Item*/ item, /*Boolean*/ opened)
     {
          var style = "";
          if( item )
          {
               if( item.children )
               {
                    //add icon image style
          style = opened ? "customFolderOpenedIcon" : 
"customFolderClosedIcon";
                         
                    //add disabled style
                    style = style + " disabled";
                     }
                    else
{   
                          //add icon image styling and enabled styling
                    style = "noteIcon enabled";
                }
          }
          return style;
}

但是,该行的其余部分在悬停时仍不会显示为已禁用。 因为Dojo仅提供了图标类和标签的回调,所以没有一种简便的方法来禁用其余行的外观。 一种选择是在构建树之后遍历所有节点,并将禁用或启用的样式添加到封装标签和图标的行中。 可以使用每个树节点的rowNode属性访问该DOM节点。

要禁用禁用节点功能,我们将简单地覆盖onClick以返回禁用节点。 清单20中所示的方法可用于通过简单地返回是否单击了禁用的节点来忽略onClick事件。

清单20.覆盖onClick
function onSelectItem(item) {            
       if(item.children) //disabled node, ignore click event
           return;

       if(item) {    
              // Display basic attribute values
              dojo.byId('uLabel').value = item ? 
                  store.getLabel(item) : "";
       }
}

清单20中所示的方法可以进一步进行修改,如清单21中所示,这样,在已选择启用节点之后,单击禁用节点时,所选节点将保留其样式。

清单21.使节点保持样式
function onSelectItem(item,node){
            
        if(item.children)
        {
            if(previouslySelectedNode)
                  //keep currently selected node highlighted
dojo.addClass(previouslySelectedNode,"dijitTreeLabelFocused");                
            return;
        }

        if(item){
            if(previouslySelectedNode)
                //in case we set that ourself, remove it 
dojo.removeClass(previouslySelectedNode,"dijitTreeLabelFocused"); 
                
            //keep track of enabled selected node
            previouslySelectedNode = node.labelNode;
                
            // Display basic attribute values
            dojo.byId('uLabel').value = item ? store.getLabel(item) : "";
        }
        }

要查看此示例的工作test_Tree_disable_style.htmltest_Tree_disable_style.html下载到您的<dojo_home> / dijit / tests /目录中,然后在浏览器中运行文件。 第一个文件演示了仅使用回调来禁用节点的用法,而第二个文件则使用rowNode使节点看起来已禁用。

网格编辑器,每行具有不同的选项

今天,大多数Web 2.0技术都为称为网格的复杂表提供了小部件。 这些网格表包括用于列值选择的功能,类似于HTML SELECT。 然而,这列选择的局限性是它们是静态的,并且只有一个组的整个表的选项(例如,第1列的所有行具有相同的选项)。 图9示出了一个简单的Dojo网格应用程序,其中对于所有的第1列的选项是针对每行(正常,注意和重要)是相同的。

图9 Dojo网格编辑器选择-标准
Dojo网格编辑器选择-标准

我们需要对于网格表的每一行都可以不同的动态选项,因此我们必须进行一些自定义。 有几种解决此问题的方法。 至少可以覆盖网格“选择”编辑器的格式程序,以返回所选行的选项信息。 但是,在此之后,网格模型中列的选项和值没有意义。 您必须注意将网格的行与网格外部的任何类型的模型数据相关联,因为可以随时对网格中的行进行重新排序(例如,使用排序操作)。 如果将扩展的模型信息保留在实际网格模型的外部,则在网格模型中修改的行可能不是您认为它在网格外部数据中的行。 为了避免这种类型的不匹配,我们发现最好在网格模型本身中保留其他非可视信息,例如该行的选项和值。 然后,格式化程序替代可以返回该扩展的模型信息。 清单22至清单26中显示了代码片段。

总共有3个基本步骤可完成此操作:

  1. 每行使用其他选项信息扩展网格的数据模型
  2. 创建一个自定义网格编辑器,专门使用format方法
  3. 使用自定义选择器挂钩网格布局

使用每行选项的附加信息扩展网格的数据模型

清单22显示了修改前针对不同选项/行的网格数据。

清单22.修改之前的网格数据
var data = [ 
    ["normal",   "message-1","Priority-1"],
    ["note",     "message-2","Priority-2"],
    ["important","message-3","Priority-3"]
 ];

清单23显示了修改后的数据数组第4列中不同选项/行的网格数据。

清单23.修改不同的选项/行
var data = [ 
    ["normal-1",   "message-1","Priority-1",["normal-1","note-1","important-1"]],
     "note-2",     "message-2","Priority-2",["normal-2","note-2","important-2"] ],
    ["important-3","message-3","Priority-3",["normal-3","note-3","important-3"] ]
];

创建自定义网格选择编辑器以返回选定行的选项

我们通过创建dojox.grid.editors.Select的子类并选择format方法来dojox.grid.editors.Select此目的。 我们将编辑器的选项设置为当前行的选项,然后让父级对该信息进行实际格式化。 这导致每一行的选项不同。 请注意,在此示例中,选项和值都相同。 我们可以通过为网格模型数据中的值添加另一列来使其与众不同。 清单24中的示例通过创建新编辑器MySelect说明这一点。

清单24.使选项和值不同
dojo.declare("com.ibm.editor.MySelect", [dojox.grid.editors.Select], {
     format: function(inDatum, inRowIndex){
           var row = this.cell.grid.model.data[inRowIndex]; 
            this.options = this.values = row[OPTION_INDEX];   // could have diff values
            return this.inherited("format",arguments);        // return HTML select stmt
      }, 
});

使用自定义选择器挂钩网格布局

清单25中的示例显示了使用标准dojo网格编辑器的网格布局。 请注意,选项和值包含在网格布局中,因为表中所有行的选项和值都相同。

清单25.使用标准的dojo网格编辑器进行网格布局
gridLayout = [{
         type: 'dojox.GridRowView', width: '20px'
         },{
        defaultCell: { width: 8, editor: dojox.grid.editors.Input, styles: 'text-
        align: right;' },
        rows: [[
                {name: 'Source Schema: Current',styles: '',    width: '40%',    editor: 
               dojox.grid.editors.Select, options: ["normal", "note", "important"], 
               values: [0, 1, 2], formatter: function(inDatum) { return 
               this.options[inDatum]} },
               {name: 'Mapping',    styles: '',    width: '20%'}, 
               {name: 'Target Schema: ',    styles: '',    width: '40%'}
        ]]
}];

清单26中的示例显示了网格布局如何使用自定义网格编辑器。 注意,我们不需要为整个表定义选项,因为每行的选项都不同。

清单26.使用自定义网格编辑器的网格布局
gridLayout = [{
      type: 'dojox.GridRowView', width: '20px'
      },{
      defaultCell: { width: 8, editor: dojox.grid.editors.Input, styles: 
                               'text-align: right;' },
      rows: [[
            {name: 'Source Schema: Current',styles: '', width: '40%', 
              editor: com.ibm.editor.MySelect }
            },
            {name: 'Mapping',	styles: '',	width: '20%'}, 
            {name: 'Target Schema: ',	styles: '',	width: '40%'}

      ]]
}];

图10显示了针对网格表选择编辑器的Dojo小部件定制的最终结果,每行具有不同的选项。 请注意,普通3,注释3和重要3的最后一行的选择与普通1和注释2的其他行的选择选项不同。

图10 Dojo网格编辑器选择-自定义
Dojo网格编辑器选择-定制

Dojox网格样本<dojo_home> /dojox/grid/tests/test_edit.html已修改为支持每行不同的选项。 要查看这些示例的运行情况,请将test_edit_simple.html和test_edit_diff_options.html下载到您的<dojo_home> / dojox / grid / tests目录中,然后在浏览器中运行文件。 第一个文件演示了正常的网格表操作,并在一个非常简单的应用程序中为所有行提供了相同的选项,而第二个文件演示了自定义网格编辑器select的用法。

树–可通过样式指示进行编辑

我们的应用程序允许用户在图形树上进行一些最少的编辑。 用户可以删除(拖放)树中的一个节点及其子节点(可选),重命名该节点上的标签,或撤消/清除对该节点所做的任何编辑。 节点的编辑包含两个可视步骤:

  1. 根据编辑操作(例如,更改颜色,删除线文本等)定义样式并将其应用于所选节点。
  2. 对于某些编辑操作,请更改节点(例如,更改标签,折叠节点等)。

为此,我们正常创建了一棵树,并在HTML模板中创建了商店,模型(未显示)和树。

清单27.创建一棵树
<div dojoType="dijit.Tree" 
        id="current_tree_${id}" 
        class="container"
        model="current_tree_model_${id}"
        labelAttr="name"
        childrenAttr="children, items" 
        dojoAttachEvent="onClick:_onClickSelect"
        getLabel="ourCustomLabel"
        getLabelClass="getOurLabelClass"
        getIconClass="getOurIconClass">            
</div>

我们在树上添加了带有编辑选项的右键菜单。 该connect清单28中附加的菜单树。

清单28.附加正确的上下文菜单
<script type="dojo/connect">
        var menu = dijit.byId("edit_tree_menu_${id}");                
        menu.bindDomNode(this.domNode);
</script>

注意:此示例假定用户使用鼠标左键选择感兴趣的节点。

HTML模板中菜单的定义可以如清单29所示。

清单29.菜单的定义
<ul dojoType="dijit.Menu" 
id="edit_tree_menu_${id}" 
style="display: none;" 
iconClass="dijitMenuItemIcon">
        
        <li id="edit_clear_menu_${id}"
        dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickClear" 
        iconClass="dijitEditorIcon dijitEditorIconUndo" 
        disabled="true">
        Clear Edit</li>
        
        <li dojoType="dijit.MenuItem" 
        iconClass="dijitEditorIcon dijitEditorIconWikiword" 
        disabled="true" >
        Create Expression</li>
        
        <li id="edit_drop_menu_${id}" 
        dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickDrop" 
        iconClass="dijitEditorIcon dijitEditorIconDelete" 
        disabled="false" >
        Drop</li>                    
        
        <li dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickRename" 
        iconClass="dijitEditorIcon dijitEditorIconCreateLink">
        Rename</li>
        
        <li id="edit_menu_remove_${id}" 
        dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickRemove" 
        iconClass="dijitEditorIcon dijitEditorIconCut">
        Remove</li>                    
    
</ul>

我们还在树中添加了可编辑节点的技巧,这是我们在Dojo论坛之一中找到的技巧。

清单30.使用替代禁用按键
<script type="dojo/method" event="_onKeyPress">
        //disable keypress via override, so we can use inline editor on nodes
</script>

最后,根据选择的选项,我们为编辑操作创建了一个适当的处理程序,该处理程序将应用样式并可能更改节点。

清单31中显示了remove节点处理程序的代码段。(用户在调用处理程序之前先选择节点。节点选择将设置this.item 。)发生的视觉动作是应用了样式来指示节点已被删除(红色删除线),并且此节点现在启用了撤消菜单项。

清单31. remove节点的片段
/**
* User clicked on remove button or right context menu
* @param {Object} e
*/
_onClickRemove: function (/*Event*/ e) {
        . . .
        this.store.setValue(this.item,"edit","editRemove"); //add remove style
        dijit.byId("edit_clear_menu_"+this.id).setDisabled(false);//enable undo
        // process remove, e.g. report to server . . .
},

删除树节点样式的定义如清单32所示。

清单32.定义remove树节点样式
#edit .editRemove
{
        color: red;
        text-decoration: line-through;
}

清单33中显示了rename节点处理程序的代码片段。它使用户可以重命名树中节点上的标签。

清单33.重命名节点处理程序的片段
/**
 * User clicked on rename button or right context menu
 * Bring up editor for tree node name
 * @param {Object} e
 */
    _onClickRename: function (/*Event*/ e) {
        . . .
        this.store.setValue(this.item,"edit","editRename"); //add rename style
        
        var labelNode = tree._itemNodeMap[ this.item.ID ].labelNode;
        var txt = document.createElement('span');
        txt.innerHTML = labelNode.innerHTML;
        labelNode.innerHTML = "";
        labelNode.appendChild(txt);    
        
        var editor = new dijit.InlineEditBox({
            autoSave: true, // false=save/cancel button shown in html
            onChange: function(value) {
                this.setDisabled(true); // done, disable editing
                // process rename, e.g. report to server . . .
            },
            renderAsHtml: true,
            width: "150px"
        }, txt);
        editor.setDisabled(false); //enable editing
        editor._edit();
        
        dijit.byId("edit_clear_menu_"+this.id).setDisabled(false);//enable undo
        . . .
},

重命名树节点样式的定义如清单34所示。

清单34.重命名树节点样式
#edit .editRename
{
        color: blue;
}

图11显示了节点重命名操作的树节点编辑器外观。

图11.编辑树节点-重命名
编辑树节点-重命名

最终结果如图12所示。右侧的上下文菜单显示了可用的不同编辑选项。 删除或删除的树节点的样式为红色删除线,重命名的树节点的样式为蓝色。

图12.右侧上下文树菜单和带有样式的已编辑节点
右侧上下文树菜单和带有样式的已编辑节点

树-带有右键菜单和右键选择

在上一节中,需要在节点上单击鼠标左键以将所选节点存储在变量中,该变量可以在从右键单击上下文菜单中选择一个选项时使用。 这样做是因为右键单击上下文菜单绑定到整个树,而不是树的每个节点。 为了获得取决于右键单击哪个节点的特定行为,可以连接到菜单的_openMyself方法,如清单35所示。

清单35.连接到_openMyself方法
<div dojoType="dijit.Tree" … >

        <script type="dojo/connect">
            var menu = dijit.byId("tree_menu"); 
            
            dojo.connect(menu, "_openMyself", this, function(e){

                // get the tree node that was the source of this open event
                var tn = dijit.getEnclosingWidget(e.target);

                // if this tree node does not have any children,
        // disable all of the menu items
        // note: these lines are not related to the above section, just 
        // shown to illustrate how menu items would be disabled 
        // depending on which node was clicked.
                menu.getChildren().forEach(
            function(i){ 
                i.setDisabled(!tn.item.children);
            });

                // IMPLEMENT CUSTOM MENU BEHAVIOR HERE
            });
        </script>
</div>

tn变量可以存储为全局变量,单击菜单选项后可以访问该全局变量。 这样,不会强迫用户首先左键单击应将菜单操作应用于的节点。

常见错误

本节说明了我们遇到的一些常见错误。 这些包括:

  • 多个具有相同名称的全局JavaScript方法。 没有错误报告,并且您不知道哪种方法将被调用。
  • 没有足够的对象隔离,因此,您可以使用太多的全局函数。 这使代码更难维护,也可能导致上述常见错误。
  • 太多的日志/跟踪或太少的日志消息过滤都会导致日志中的信息过载。
  • 低效的错误处理使调试变得困难。 需要更多的异常处理和更多的考虑到错误路径中。
  • 滥用对象属性的范围(全局与局部)。
  • 在需要的地方不使用dojo.hitch进行回调。 This can cause late detection that the callback was not running in the correct object context (for example, incorrect object instance variable values).

Testing the Dojo app

Local and remote

Web 2.0 applications use RESTful services to get their information. REST is the acronym for REpresentational State Transfer. It is the architectural model on which the World Wide Web is based. Principles of REST include: a resource-centric approach, all relevant resources are addressable using URIs, uniform access using HTTP – GET, POST, PUT, DELETE, and so on. We wanted to be able to test in a disconnected, local mode. Therefore, we saved the RESTful service results into test files, and created an abstraction method to obtain the URL for a given REST call.

Automatic detection and switch example

The abstraction determined whether the access was local or remote using the JavaScript value of document.location.protocol , and returned the appropriate URL. This was accomplished by placing the local test files into a directory structure similar to the server URI. For example, for the URI: myui/catalog/types on server http://<server>:<port>, we simply placed the URI under a local base test directory <local-test-base-dir>/myui/catalog/types. Only the base portion of the URI changes, depending on whether the access is local or remote. We set a configuration object BASE_URL attribute as shown below.

config.BASE_URL = (document.location.protocol=="file:") ? "data" : "..";

结论

In general, we found that the learning curve to start Dojo development was minimal, with help from a large set of samples available in the Dojo toolkit and from the community surrounding Dojo, along with a plethora of JavaScript information on the Internet. But as Java developers, we missed strong IDE support, good API documentation, lack of strong typing, different runtime behavior per Web browser differences, and compile time checking with JavaScript development. Learning how to do customizations were sometimes painful, and often a trial and error process. However, after we learned the processes, they were repeatable. The end result of our prototype was a professional-looking application, with all the performance benefits expected from an Ajax-enabled Web application.


翻译自: https://www.ibm.com/developerworks/web/library/wa-aj-custom20/index.html

dojo 移动应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值