WebObjects/Web 应用程序/开发/WO 组件/代码模板和 WODs
WebObjects 是使用模型-视图-控制器架构设计的。这意味着您的应用程序被分成几个不同的角色。您的 EOModels 和它们的 EOEntities 构成了模型层。视图和控制器使用 WOElements 和 WOComponents 实现,这些组件分布在三个不同的部分:代码、模板和 WOD。WebObjects 的一个有趣之处在于,组件架构并不假定生成 HTML。您可以使用相同的组件系统创建 HTML、XML、CSS、XSLT、电子邮件或任何其他类型的动态数据。
无论您扩展 WODynamicElement 还是 WOComponent,您始终都有一个 Java 类来实现组件的逻辑,并可选地存储其状态。默认情况下,Java 类的名称决定了您将在整个应用程序中如何引用您的组件,您将在后面的示例中看到几个例子。在实现 WODynamicElements 和 WOComponents 之间有一些关键区别。
如果您正在构建一个WODynamicElement,您的类将没有模板或 WOD 文件。所有输出都直接在代码中生成。WODynamicElements 也不得不假设每个使用都会有一个实例,而是您的类的单个实例可能会同时为多个组件提供服务。因此,您的 WODynamicElement 必须完全线程安全。这些属性使 WODynamicElements 非常适合生成相对较小的输出或“计算”的输出。如果您发现自己编写大量代码来处理输出数据,您可能需要考虑从构建 WOComponents 中获得的模板的好处。
根据您的元素提供的或所需的交互类型,您可以在您的 WODynamicElement 中实现三种主要方法
- takeValuesFromRequest
- invokeAction
- appendToResponse
有关这些方法在运行时的执行顺序的更多信息,请阅读请求-响应循环部分。
要从请求中接收值(例如,表单数据),请实现 takeValuesFromRequest 方法。如果您的 WODynamicElement 响应用户操作,您必须实现 invokeAction 方法。如果您的 WODynamicElement 生成输出,您必须实现 appendToResponse 方法。在运行时,您的 WODynamicElement 将连接到一个 WOComponent,您可以通过传递到上述三个方法中的每个方法的 WOContext 访问它,该上下文可用于解析页面上特定外观中的绑定。例如,要检索绑定的值,它可能看起来像
private WOAssociation myNameAssociation; ... public void appendToResponse(WOResponse _response, WOContext _context) { WOComponent component = _context.component(); String name = (String) myNameAssociation.valueInComponent(component); super.appendToResponse(_response, _context); }
这种相同的模式用于无状态 WOComponents。WODynamicElement 可以提供一个 .api 文件来描述其绑定。有关 .api 文件的更多信息,请参见下面的 .API 文件部分。
WOComponents 在 WODynamicElements 之上增加了几个主要功能。主要额外功能是支持模板。当您创建 WOComponent 时,您还可以创建一个“.wo”文件夹,其中包含三个文件:一个 .html 或 .xml 模板,一个 .wod 绑定声明和一个 .woo 文件。与 WODynamicElements 一样,WOComponents 也可以提供一个可选的 .api 文件(如下所述)。WOComponent 的代码可以简单到仅声明您的类扩展 WOComponent 以及它的构造函数。例如
public class MyComponent extends WOComponent { public MyComponent(WOContext _context) { super(_context); } }
上面针对 WODynamicElements 描述的三种核心方法也存在于 WOComponent 中,但是由于 WOComponents 可以是有状态的,因此您也可以在组件中声明实例变量 (ivars),您可以将其绑定到。每次在页面上使用 WOComponent 时,都会创建一个新实例,这是与 WODynamicElement 相比的另一个重大区别。您的 WOComponents 不必是线程安全的。
默认情况下,WOComponent 模板可以是 .html 或 .xml 文件。WebObjects 模板将所有绑定声明分离到一个名为 .WOD 文件的单独文件中,这与许多其他 Web 框架不同。因此,模板通常更容易阅读,并且通常可以更轻松地交给设计师处理,而无需担心代码相关的绑定意外被修改。WOComponents 在模板中使用“webobject 标签”声明,它看起来像 HTML 标签
<webobject name = "PersonName"></webobject>
在 WebObject 标签中声明的名称将用作在您的 WOD 文件中查找相应绑定定义的键。
WOD 文件(Web 对象声明?)定义了模板中每个组件引用的绑定。例如,如果上面的引用连接到组件上的一个“public String personName()”方法,则 WOD 声明可能如下所示
PersonName : WOString { value = personName; }
此声明有几个部分。第一个标记是出现在模板中的 WebObject 标签名称。这些值必须完全匹配才能使 WebObjects 解析绑定信息。如果您在模板中引用了一个名称但没有声明相应的 WOD 条目,WebObjects 将在运行时抛出异常。
第二个标记,冒号之后,是将在您的模板中实例化的组件或元素的名称。此名称使用 NSBundle 查找规则解析,默认情况下,此规则将找到与标记名称相同的任何类,不包括包名称。例如,WOString 实际上可能是“com.webobjects.appserver._private.WOString”,但该类的名称(不包括其包名称)是“WOString”。这很重要,因为它意味着如果您使用此语法,您必须在所有包中唯一地命名您的类(一个 Objective-C 遗产,其中不存在包的概念)。但是,您可以选择完全限定您的类名,在这种情况下,上面示例中的“WOString”将变为“com.webobjects.appserver._private.WOString”,并且类解析将没有歧义。可以使用 NSUtilities 上的方法覆盖正常的 NSBundle 查找过程
NSUtilities._setClassForName(com.bla.TestComponent.class, "TestComponent");
此调用将 WOD 引用中出现的名称 "TestComponent" 绑定到类 com.bla.TestComponent,避免进一步的类“查找”。这也有助于用您自己的类替换内部组件实现。例如,您可以用您自己的类替换 WOString 的实现,Project WOnder 广泛使用此功能来提供对核心组件的错误修复和增强。
在 WOD 文件中继续前进,在花括号内,您可以提供一系列键值对,每个键值对以分号作为分隔符。等号左侧是绑定名称。绑定名称在 WOComponent 上通过尝试找到与以下命名约定之一匹配的 mutator 方法或字段来解析:public void setValue(Xxx param)、public void _setValue(Xxx param)、public Xxx value、public Xxx _value(假设上面的绑定名为“value”)。因此,如果您的 WOComponent 具有 setValue 方法,并且启用了自动绑定同步,则将调用 setValue 方法,并传入绑定右侧的计算值。在上面的示例中,右侧是 "personName"。如果 personName 在上面的示例中实际上是用引号引起来的,则它将被视为字符串文字,并将等效于调用 setValue("personName")。
但是,personName 没有引号,因此使用 Key-Value-Coding (KVC) 进行解释。Key-Value-Coding 允许您将一系列访问器方法调用或字段引用串联成一个字符串,WebObjects 将使用 Java 反射动态解析该字符串。例如,在上面的示例中,personName 将尝试查找任何名为:public String getPersonName()、public String _getPersonName()、public String personName()、public String _personName()、public String personName、public String _personName 的访问器或字段。因此,如果您的 Java 类具有 public String getPersonName() 方法,则该方法的返回值将传递到您的 WOComponent 的 setValue(..) 方法。KVC 变得更加有趣的地方在于,您可以构造一系列方法调用。例如,想象一下,您的 WOSession 有一个 "public Person person()" 方法,该方法有一个 "public Address address()" 方法,该方法有一个 "public String zipCode()" 方法。您的 WOD 文件绑定可能如下所示:"value = session.person.address.zipCode;",这会将您会话中人员地址的邮政编码绑定到 value。Foundation 提供的 NSArray 操作更有趣。例如,如果您的会话有一个 "public NSArray purchaseAmounts()" 方法,该方法返回一个 BigDecimal 数组,则可以引用绑定 "value = session.purchaseAmounts.@sum",它将返回数组中值的总和。Foundation 类中还有其他一些数组操作可用,而 Project Wonder 在其 ERXArrayUtilities 类中提供了更多操作。有关更高级的 KVC 功能,请阅读 Project Wonder 的 WOOgnl 部分。
单个 WOD 条目可以包含多个绑定声明,而 WOD 文件可以包含多个条目。例如,以下是一个实际 WOD 文件的摘录
FilterAction : WOSubmitButton { action = filter; value = "filter"; } EditAction : WOHyperlink { action = editRequest; } NoRequestsConditional : WOConditional { condition = requestsDisplayGroup.allObjects.count; negate = true; }
至少,WOO 文件声明了 WO 版本和模板的字符编码。例如,一个简单的 WOO 文件可能看起来像这样
{ "WebObjects Release" = "WebObjects 5.0"; encoding = NSMacOSRomanStringEncoding; }
此外,WOO 文件可以包含您的 WO 组件可以引用的实例化对象的定义。如果您使用 WOBuilder 构建组件,您将在组件的 WOO 文件中找到 WODisplayGroups 属性的定义。
API 文件是可选文件,它们出现在与您的 .WO 相同的文件夹中,并提供有关您的组件的元数据,IDE 可以使用这些元数据为您的组件用户提供更好的体验。例如,API 文件声明了您组件的所有绑定、绑定类型(布尔值、日期等),以及用于定义绑定验证的相当广泛的 XML 语言。
例如
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <wodefinitions> <wo wocomponentcontent="true" class="AjaxSortableList.java"> <binding name = "id"/> <binding name = "list"/> <binding name = "listItemIDKeyPath"/> <binding name = "startIndex"/> <binding name = "action"/> <validation message = "'id' is a required binding"> <unbound name = "id"/> </validation> <validation message="'listItemIDKeyPath' must be bound when 'list' is bound"> <and> <bound name = "list"/> <unbound name = "listItemIDKeyPath"/> </and> </validation> </wo> </wodefinitions>
在此示例中,API 定义了顶部的绑定,以及一系列验证。当验证内部的声明计算结果为“true”时,将在 IDE 中显示验证消息。例如,对于第一个验证,如果 "id" 值未绑定,则将显示验证消息。在第二种情况下,如果 "list" 值已绑定,但 "listItemIDKeyPath" 未绑定,则将显示消息。