Skip to content

Tapestry Portlet in details

got5 edited this page Oct 17, 2012 · 8 revisions

Table of Contents

Where to start ? Portlet Module

The tapestry Portlet bridge is an autoloading module. (see http://tapestry.apache.org/autoloading-modules.html ) If you have a look inside the pom.xml

<plugin> &lt;groupid&gt;org.apache.maven.plugins&lt;/groupid&gt; &lt;artifactid&gt;maven&#45;jar&#45;plugin&lt;/artifactid&gt; &lt;configuration&gt; &amp;lt;archive&amp;gt; &amp;amp;lt;manifestentries&amp;amp;gt; &amp;amp;amp;lt;tapestry&amp;amp;amp;#45;module&amp;amp;amp;#45;classes&amp;amp;amp;gt; org.apache.tapestry5.portlet.services.PortletModule &amp;amp;amp;lt;/tapestry&amp;amp;amp;#45;module&amp;amp;amp;#45;classes&amp;amp;amp;gt; &amp;amp;lt;/manifestentries&amp;amp;gt; &amp;lt;/archive&amp;gt; &lt;/configuration&gt; </plugin>

or gradle.build jar { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.portlets.services.PortletModule' } }

you will see that the entry point for this tapestry module is org.apache.tapestry5.portlets.services.PortletModule.

Contribution to MarkupRenderer

MarkupRenderer is an object which will perform rendering of a page (or portion of a page). This interface exists to be filtered via MarkupRendererFilter.The MarkupRenderer service takes an ordered configuration of MarkupRendererFilters, which are used for ordinary page rendering (as opposed to partial page rendering for Ajax requests).

The following code show that in this module, the default JavaScriptSupport MarkupRendererFilter defined in TapestryModule.java is replaced by an other MarkupRendererFilter.

public void contributeMarkupRenderer(OrderedConfiguration configuration,

            final Environment environment, final Request request,
            final PortletRequestGlobals globals, final JavaScriptStackSource javascriptStackSource,
            final JavaScriptStackPathConstructor javascriptStackPathConstructor,
            final PortletIdAllocatorFactory iaFactory)
    {
        MarkupRendererFilter javaScriptSupport = new MarkupRendererFilter()
        {
            public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
            {
                DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
                IdAllocator idAllocator = iaFactory.getNewPortletIdAllocator();
                JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker,
                        javascriptStackSource, javascriptStackPathConstructor, idAllocator, false);
                environment.push(JavaScriptSupport.class, support);
                renderer.renderMarkup(writer);
                environment.pop(JavaScriptSupport.class);
                support.commit();
           }
        };
        configuration.override("JavaScriptSupport", javaScriptSupport, "after:DocumentLinker");
    }{{/code}}  

IdAllocator is used to "uniquify" names within a given context. A base name is passed in, and the return value is the base name, or the base name extended with a suffix to make it unique. In a portlet container, a portal page can use more than one instance of a portlet. So the portlet bridge need to give to each component id a suffix that is unique inside the portal page. To do that, the new javaScriptSupport MarkupRendererFilter pass a specific IdAllocator instance with a namespace that start with the portletId to the JavaScriptSupport service.

In practice, if you want to use your portlet more than once in your portal page, don't fix the client id in your template. \\

Ajax Form with client id (Don't do that)

 <form t:type="form" t:zone="IdConflictIfmoreThanOneInstanceOfThePortletisUsedInPortalPage" t:id="firstNameForm">
  <label t:type="label" t:for="firstName"/><input t:type="textfield" t:id="firstName"/>
  <t:submit />
 </form>{{/code}}

Instead, let's the component create a unique client ID with the namespace provided by t:id parameter.

Ajax Form without client id

  <form t:type="form" t:zone="${surnameZoneId}" t:id="surnameForm">
   <label t:type="label" t:for="surname"/><input t:type="textfield" t:id="surname"/>
   <t:submit />
  </form>{{/code}} 

and get the clientID form the component @InjectComponent

    private Zone surNameZone;
    public String getSurNameZoneId()
    {
        return surNameZone.getClientId();
    }{{/code}} 

If you want more details about What's the difference between id and t:id? see http://tapestry.apache.org/templating-and-markup-faq.html

Contribution to ServiceOverride

Ressource serving

Through the render method of the Portlet interface the Portlet produces its complete markup that is embedded as a fragment into the overall page by the portal application. However, there are use cases where the portlet would like to only replace a part of its 5 markup, e.g. via an AJAX call. Serving fragments via serveResource is under the complete control of the portlet. Typically a portlet would issue an XMLHttpRequest with a resource URL and provide either markup or data as response in the serveResource method.

In order to provide a resource URL for each event handler that are supposed to serve either partial markup or data, the portletModule replaced the default implementation of the Linksource service by a class called PortletLinkSource.

public void contributeServiceOverride(

  MappedConfiguration<Class, Object> configuration,
  @InjectService("PortletCookieSource") final CookieSource cookieSource,
  @InjectService("PortletLinkSource") final PortletLinkSource linkSource, 
  @InjectService("PortletRequestSelectorAnalyzer") final ComponentRequestSelectorAnalyzer analyzer)
 {
  configuration.add(LinkSource.class, linkSource);
  configuration.add(CookieSource.class, cookieSource);
  configuration.add(ComponentRequestSelectorAnalyzer.class, analyzer);
 }{{/code}} 

more precisely the method called createComponentEventLink return a resource URL when: 1) the event name of the component start with the keyword "serve" <t:eventlink t:event="serveresource">Download File</t:eventlink>

2) the component referenced by the link is declared as a resource sender.

The following code extracted from the PortletModule, show us how the bridge contributes values to the PortletResourceResponseIdentifier's configuration for component that come from tapestry-core and tapestry5-jQuery.

public static void contributePortletResourceResponseIdentifier(

            Configuration<DeclaredResourceResponseSender> configuration)
    {
        // declare core component that will return resource response form ajax
        // call
        configuration.add(new DeclaredResourceResponseSender(DateField.class.getName()));
        configuration.add(new DeclaredResourceResponseSender(FormInjector.class.getName()));
        configuration.add(new DeclaredResourceResponseSender(BeanEditForm.class.getName()));
        // ajaxFormLoop
        DeclaredResourceResponseSender ajaxFormLoop = new DeclaredResourceResponseSender(
                AjaxFormLoop.class.getName());
        ajaxFormLoop.addEvent(EventConstants.ADD_ROW);
        ajaxFormLoop.addEvent(EventConstants.REMOVE_ROW);
        configuration.add(ajaxFormLoop);
        // declare core mixin that will return resource response form ajax call
        configuration.add(new DeclaredResourceResponseSender(Autocomplete.class.getName(), true));
        // declare tapestry-jquery component that will return resource response
        // form ajax call
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.AjaxUpload"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.CarouselItem"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.DataTable"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.DialogAjaxLink"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.InPlaceEditor"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.ProgressiveDisplay"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.RangeSlider"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.Slider"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.Tabs"));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.TwitterView"));
        // declare tapestry-jquery mixin that will return resource response form
        // ajax call
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.mixins.Autocomplete",true));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.mixins.ZoneDroppable",true));
        configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.mixins.ZoneRefresh",true));
        // for page or mixin like  org.got5.tapestry5.jquery.mixins.Bind you have to declare the full pagename
        // and the eventname that should be treat as resource URL        
    }{{/code}} 

if you have to add a component to the list you are able to:

 - declare all the events raised by the component 

configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.components.AjaxUpload")); \\- declare only the events raised by the component that should be declared as a resource sender.

 DeclaredResourceResponseSender ajaxFormLoop = new DeclaredResourceResponseSender(
                AjaxFormLoop.class.getName());
        ajaxFormLoop.addEvent(EventConstants.ADD_ROW);
        ajaxFormLoop.addEvent(EventConstants.REMOVE_ROW);
        configuration.add(ajaxFormLoop);

- declare a mixin that raised that should be declared as a resource sender.

 configuration.add(new DeclaredResourceResponseSender("org.got5.tapestry5.jquery.mixins.Autocomplete",true));