第一部分:源码
AbstractLink.java
// Copyright 2007, 2008, 2009 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.base;
import org.apache.tapestry5.*;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.SetupRender;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
/**
* Provides base utilities for classes that generate clickable links.
*/
@SupportsInformalParameters
public abstract class AbstractLink implements ClientElement
{
/**
* An anchor value to append to the generated URL (the hash separator will be added automatically).
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String anchor;
/**
* If true, then then no link element is rendered (and no informal parameters as well). The body is, however, still
* rendered.
*/
@Parameter("false")
private boolean disabled;
@Inject
private ComponentResources resources;
@Inject
private JavaScriptSupport jsSupport;
private Link link;
private Element element;
private String clientId;
private String buildHref(Link link)
{
String href = link.toURI();
if (anchor == null)
return href;
return href + "#" + anchor;
}
@SetupRender
void resetElementAndClientId()
{
element = null;
clientId = null;
}
/**
* Writes an <a> element with the provided link as the href attribute. A call to
* {@link org.apache.tapestry5.MarkupWriter#end()} is <em>not</em> provided. Automatically appends an anchor if
* the component's anchor parameter is non-null. Informal parameters are rendered as well.
*
* @param writer
* to write markup to
* @param link
* the link that will form the href
* @param namesAndValues
* additional attributes to write
*/
protected final void writeLink(MarkupWriter writer, Link link, Object... namesAndValues)
{
element = writer.element("a", "href", buildHref(link));
writer.attributes(namesAndValues);
resources.renderInformalParameters(writer);
this.link = link;
}
/**
* Returns the most recently rendered {@link org.apache.tapestry5.Link} for this component. Subclasses calculate
* their link value as they render, and the value is valid until the end of the request, or the next time the same
* component renders itself (if inside a loop).
*
* @return the most recent link, or null
*/
public Link getLink()
{
return link;
}
/**
* Returns the unique client id for this element. This is valid only after the component has rendered (its start
* tag). A client id is generated the first time this method is invoked, after the link renders its start tag.
*/
public final String getClientId()
{
if (clientId == null)
{
if (element == null)
throw new IllegalStateException(String.format(
"Client id for %s is not available as it did not render yet (or was disabled).",
resources.getCompleteId()));
clientId = jsSupport.allocateClientId(resources);
element.forceAttributes("id", clientId);
}
return clientId;
}
/**
* Returns true if the component is disabled (as per its disabled parameter). Disabled link components should not
* render a tag, but should still render their body.
*/
public boolean isDisabled()
{
return disabled;
}
/**
* Used for testing.
*/
final void inject(String anchor, ComponentResources resources)
{
this.anchor = anchor;
this.resources = resources;
}
}
PageLink.java
// Copyright 2007, 2008 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.components;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.corelib.base.AbstractLink;
import org.apache.tapestry5.ioc.annotations.Inject;
/**
* Generates a render request link to some other page in the application. If an activation context is supplied (as the
* context parameter), then the context values will be encoded into the URL. If no context is supplied, then the target
* page itself will supply the context via a passivate event.
* <p/>
* Pages are not required to have an activation context. When a page does have an activation context, the value
* typically represents the identity of some object displayed or otherwise manipulated by the page.
*/
public class PageLink extends AbstractLink
{
/**
* The logical name of the page to link to.
*/
@Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.LITERAL)
private String page;
@Inject
private ComponentResources resources;
/**
* If provided, this is the activation context for the target page (the information will be encoded into the URL).
* If not provided, then the target page will provide its own activation context.
*/
@Parameter
private Object[] context;
void beginRender(MarkupWriter writer)
{
if (isDisabled()) return;
Link link = resources.createPageLink(page, resources.isBound("context"), context);
writeLink(writer, link);
}
void afterRender(MarkupWriter writer)
{
if (isDisabled()) return;
writer.end(); // <a>
}
}
AbstractComponentEventLink.java
// Copyright 2008, 2009 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.base;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.MarkupConstants;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.ClientBehaviorSupport;
import org.apache.tapestry5.services.Request;
/**
* Base class for link-generating components that are based on a component event request. Such events have an event
* context and may also update a {@link org.apache.tapestry5.corelib.components.Zone}.
*/
public abstract class AbstractComponentEventLink extends AbstractLink
{
/**
* The context for the link (optional parameter). This list of values will be converted into strings and included in
* the URI. The strings will be coerced back to whatever their values are and made available to event handler
* methods.
*/
@Parameter
private Object[] context;
/**
* Binding the zone parameter turns the link into a an Ajax control that causes the related zone to be updated.
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String zone;
@Environmental
private ClientBehaviorSupport clientBehaviorSupport;
@Inject
private Request request;
void beginRender(MarkupWriter writer)
{
if (isDisabled()) return;
Link link = createLink(context);
writeLink(writer, link);
if (zone != null)
{
if (!request.isXHR())
writer.getElement().forceAttributes(MarkupConstants.ONCLICK, MarkupConstants.WAIT_FOR_PAGE);
clientBehaviorSupport.linkZone(getClientId(), zone, link);
}
}
/**
* Invoked to create the Link that will become the href attribute of the output.
*
* @param eventContext the context as an object array, possibly null
*/
protected abstract Link createLink(Object[] eventContext);
void afterRender(MarkupWriter writer)
{
if (isDisabled()) return;
writer.end(); // <a>
}
}
ActionLink.java
// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.components;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.EventConstants;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.annotations.Events;
import org.apache.tapestry5.corelib.base.AbstractComponentEventLink;
import org.apache.tapestry5.ioc.annotations.Inject;
/**
* Component that triggers an action on the server with a subsequent full page refresh.
*/
@Events(EventConstants.ACTION)
public class ActionLink extends AbstractComponentEventLink
{
@Inject
private ComponentResources resources;
@Override
protected Link createLink(Object[] contextArray)
{
return resources.createEventLink(EventConstants.ACTION, contextArray);
}
}
EventLink.java
// Copyright 2008 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.components;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.corelib.base.AbstractComponentEventLink;
import org.apache.tapestry5.ioc.annotations.Inject;
/**
* A close relative of {@link org.apache.tapestry5.corelib.components.ActionLink} except in two ways.
* <p/>
* First, the event that it triggers is explicitly controlled, rather than always "action".
* <p/>
* Second, the event is triggered in its container.
* <p/>
* This allows slightly shorter URLs but also allows multiple components within the same container to generate identical
* URLs for common actions.
*/
public class EventLink extends AbstractComponentEventLink
{
/**
* The name of the event to be triggered in the parent component. Defaults to the id of the component. An {@link
* org.apache.tapestry5.corelib.components.ActionLink} triggers an "action" event on itself, and EventLink component
* triggers any arbitrary event on <em>its container</em>.
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String event;
@Inject
private ComponentResources resources;
String defaultEvent()
{
return resources.getId();
}
@Override
protected Link createLink(Object[] eventContext)
{
ComponentResources containerResources = resources.getContainerResources();
return containerResources.createEventLink(event, eventContext);
}
}
第二部分:简介
从AbstractLink抽象类继承的子类都是实现链接的组件。
<t:pageLink/>提供了页面跳转的link组件。
<t:actionLink/>提供了一个执行action方法事件的组件。
<t:eventlink/>提供了执行一个任意方法事件的组件。
三个组件类都是从AbstractLink继承而来,在AbstractLink中声明了anchor和disabled两个组件参数,以上三个组件全部集成了这两个组件参数。
anchor参数的作用是增加一个锚点,HTML中的锚点应该很熟悉吧!不说了。
disabled参数的作用看名字就知道了,就是禁用不禁用的意思,如果值为true则不渲染任何内容,直接输出组件内部文本,如果为false(默认)则渲染一个a标签出来,然后输出到HTML。
<t:pageLink/>组件类PageLink中声明了一个page的参数,用来指定要跳转到的page名。
<t:actionlink/>和<t:eventlink/>两个组件类ActionLink和EventLink都继承了AbstractComponentEventLink,而AbstractComponentEventLink又是AbstractLink的实现子类。
其中在AbstractComponentEventLink中声明了context和zone两个组件参数,context用来做参数传递的,而zone用来进行ajax更新的,涉及<t:zone/>的使用,详细介绍以后会讲解。
context参数的作用是用来传递参数,被跳转到的页面的onActivate方法接收,如果<t:actionlink/>和<t:eventlink/>未指定context属性,那么则使用跳转到的页面的页面类的onPassivate方法进行参数传递,如果指定了context,则onPassivate不执行,具体看示例代码。
<t:actionlink/>组件是<t:eventlink/>的一个简化实现,可以类似于<t:eventlink/>组件指定event属性为action。其中的event是需要在页面类触发的事件方法名,方法名的格式是:on+eventName。其中eventName是event的参数值,方法名中的eventName第一个字母大写。
第三部分:测试
以下是测试用的最终代码
Start.tml
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>link component</title>
</head>
<body>
<t:pagelink page="another" context="param">another</t:pagelink>
<t:pagelink page="another" anchor="pre2">another_pre2</t:pagelink>
<t:actionlink t:id="actionId" zone="actionlinkzone">actionlinkzone</t:actionlink>
<t:eventlink event="testevent" zone="eventlinkzone">eventlinkzone</t:eventlink>
<t:zone t:id="actionlinkzone" id="actionlinkzone">
actionlinkzone count:${count}
</t:zone>
<t:zone t:id="eventlinkzone" id="eventlinkzone">
eventlinkzone count:${count}
</t:zone>
</body>
</html>
Start.java
package jumpstart.web.pages;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Zone;
public class Start {
@Property
private String param="context param";
@Property
@Persist
private int count;
@InjectComponent
private Zone actionlinkzone;
@InjectComponent
private Zone eventlinkzone;
Object onActionFromActionId() {
count--;
return actionlinkzone.getBody();
}
Object onTestevent() {
count++;
return eventlinkzone.getBody();
}
}
Another.tml
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>page another</title>
<style>
.red{color:red;}
</style>
</head>
<body>
<div class="red">${message}</div>
<pre id="pre1">
pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1 pre1
适当复制内容才可以看到锚点的效果。
<pre id="pre2">
pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2 pre2
适当复制内容才可以看到锚点的效果。
</pre>
</body>
</html>
Another.java
package jumpstart.web.pages;
import org.apache.tapestry5.annotations.Property;
public class Another {
@Property
private String message = "default message";
void onActivate(String message) {
this.message = message;
}
String onPassivate() {
return "from passivate";
}
}
最终的效果
第四部分:分析
上述测试代码最终效果不完整或者与你看到的效果有些出入,根据简介内容自己研究吧!
这里我说一下我的理解:
1.eventlink和actionlink两个组件的重要区别就是各自组件类中的createLink方法内容,其中的actionlink组件类中的createLink方法内容是
return resources.createEventLink(EventConstants.ACTION, contextArray);
其中EventConstants.ACTION的常量值就是action,contextArray就是context的属性值,这就是简介中提到<t:actionlink/>组件是<t:eventlink/>的一个简化实现,可以类似于<t:eventlink/>组件指定event属性为action。为了验证这一点,我们看看eventlink组件类中的createLink方法内容
ComponentResources containerResources = resources.getContainerResources();
return containerResources.createEventLink(event, eventContext);
第一行的内容是获得当前组件的容器组件,我的理解应该就是主组件,因为组件有可能是一个mixin,而这个mixin是附着在另外一个组件的,这句就是这个作用。(源码中写的)
第二行的内容就和actionlink的类似了,只不过EventConstants.ACTION参数换成了event,而event就是eventlink的一个属性。
2.Zone参数的功能实现涉及到org.apache.tapestry5.services.ClientBehaviorSupport类,这个以后再研究。
3.pagelink组件类中的重要代码是resources.createPageLink(page, resources.isBound("context"), context);该句代码创建了一个跳转到page参数的org.apache.tapestry5.Link,该方法已经被声明为废弃的了,被org.apache.tapestry5.services.PageRenderLinkSource#createPageRenderLink(String)和org.apache.tapestry5.services.PageRenderLinkSource#createPageRenderLinkWithContext(String, Object[])代替,一个方法被两个方法分解,岂是就是将resources.isBound("context")的作用进行分解了,createPageRenderLink方法是一个不带context(就是组件中context未指定)参数的实现,createPageRenderLinkWithContext方法是一个带context参数的实现。
以上是我对这三个组件的源码研究,如果不正确的,请大家指出。