David -

Just wondering, are you going to dig through the large email I sent re:
using turbine actions and state for portlets?  Or is it too large,
undigestable!

If the later, I can raise some points that were in there individually for
discussion, such as the one at the very end about the velocity context...

Thanks.

- Glenn
 
--------------------------------------------
Glenn R. Golden, Systems Research Programmer
University of Michigan School of Information
[EMAIL PROTECTED]               734-615-1419
--------------------------------------------


Here's the big one:

David -

Let me try again with this; from your response, I'm not feeling that I've
communicated this clearly, and I think this might be a wonderful idea, and I
really want your ideas on this...

Consider that we are writing a "portlet" that creates different output based
on many things:
- how it's configured (registry)
- how it's customized (psml parameters)
- what the user has done so far (our new portlet instance state) (and of
course some sort of "model" from a db or service behind the scenes).

There are various html forms that this portlet will produce for the user,
with various buttons the user will press to send data back to the portlet.
We want to use the actionEvent model of "doWhatever()" methods in our
"portlet" to handle each different button press.

That's how input processing is handled; output processing is handled by
another method in our "portlet" class, something like getContent() to
produce our html, or buildNormalContext() to setup our velocity context and
choose our velocity template.

So far, we have entry points in the "portlet" for input processing, entry
points in the "portlet" for output preparation, all in one "portlet" class.
Nice, yes?

Now, I'm "quoting" "portlet" because there's a difference between the
portlet api portlet and the object model portlet, and I'm not sure which I
mean here.  The object model portlet is what identifies that there is a
portlet instance in a portal page, and carries the configuration and
customization information for that instance.

The portlet api portlet is what is called upon at runtime to handle
requests.

Granted that we want a better distinction between these two, and stronger
lines, and clearer ideas of when which is used and who is responsible for
what...

But for this email, what we need in our "portlet" is the information from
customization, configuration, and portlet instance state, and to be called
upon at the appropriate time to handle our input and produce our output.

What sort of object would this be?

To work with the Turbine ActionEvent model, our "portlet" would need to
encode "action=OurNameHere" in the form action URL or form fields of our
html.  And we would have to extend ActionEvent from Turbine.  And we would
have to use "eventSubmit_doWhatever" for button names, matching our
"doWhatever()" methods.

Then, when the user hits our button, our "portlet" object is constructed and
called as a real turbine action.  Call to *ONLY* process the form input.

We process the form input, make changes to the portlet instance state, and
that's all for now.  (Stand by, Turbine is about to aggregate the page and
we will be called again to provide our current content).

But, alas, how do we get at the information (configuration, customization,
state) that we need to process the input, and how do we modify information
(state only) based on this input?

We are so clever - we encode a unique key into our forms, use that key with
a clever new state manager service to gain access to the state for the
portlet instance.  If we need to get at customization and configuration
(i.e. om aspects of the portlet instance), I imagine that that key is all we
need to find this information (it uniquely id's the portal page and portlet
element).  Or, we have cleverly filled our portlet instance state with
whatever we need to process our form input.

So, see how we use state to process inputs?

Now, we are done processing out inputs, we have read the form parameters and
modified the state that is keyed by the key from the form.  Wait - is our
code the proper code to handle this request?  Sure - the "action=us" in the
request picked us.  And did we modify the correct state? Sure - the state
key in the request identified that.  And how do we know that there was state
already waiting with this key?  Because the very first thing that happens to
a portlet instance is that it is called upon to produce html, getContent()
or buildNormalContext(), and we wrote that routine and made sure that it
initialized a state object, wrote the proper form with the proper key, etc.

Ok, on with the show.

Turbine, after the "action=" processing, processes the screen, and that
kicks off the Jetspeed aggregator, which, based on the OM portal page
definition, picks the appropriate portlet objects to produce output, and
gives aspects of the OM information (configuration and customization) to the
portlet class to work with (maybe not as we would like, portletConfig and
all, but for now we live with it).  Thus, our code is not only an
ActionEvent, but a Portlet, too!  So, we get another call, and have all our
Portlet information available, and we can make our key (based on our id and
our portal page's id), and get our state as well, and we are, as they say,
Golden.

What just happened?  We have a way to write portlets and process the
responses so only the intended "portlet" (portlet instance) gets the
response.  We don't have to worry about form name conflicts in the aggregate
of ours and unknown other portlets.  If we have two instances on the same
page, the key keeps us separate (which is what I propose we want,
separateness).

Can we do this now?

For VelocityPortlets, it's easy.  When we do velocity portlets, we never
write Portlet API code, we just write Action classes.  We just have to write
them in the way described, with our do() routines and our
buildNormalContext() routines.  We don't have to also be a "Portlet" for
aggregation.  VelocityPortlet recignizes the case where there was an action
in the request and then skips its own version of action processing, calling
only buildNormalContext() for all portlets.

For Portlet portlets, I think we can make a new abstract portlet that
extends ActionEvent and implements Portlet, so we get do() routines and
getContent().  We'd have to figure out how this works with the wrappers, but
it looks possible.

Here's an example of a velocity portlet action class that is coded in this
way, using the proposed state manager.  The state is just an integer and a
message.  When the user presses "next", the state int ++, and when the user
presses "prev", the state int --.  The message remembers which the user last
pressed. (Warning: may not compile, I cut out some stuff for this example):

public class TestAction
    extends VelocityPortletAction
{
    // context processing ONLY
    protected void buildNormalContext(  VelocityPortlet portlet, 
                                        Context context,
                                        RunData rundata )
    {
        // find our state
        Integer state = (Integer) portlet.getState("state", rundata);
        if (state == null)
        {
            // construct our state
            state = new Integer(0);
            portlet.setState("state", state, rundata);
        }
        
        // combine some configuration/customization with state # for the
message
        context.put("message", 
            portlet.getPortletConfig().getInitParameter("message")
            + " state: " + state.toString());
        context.put("state", portlet.getStateKey(rundata));
        String stateMsg = (String) portlet.getState("message", rundata);
        if (stateMsg != null) context.put("state-msg", stateMsg);

        // do this and the action will be processed in turbine action space
        context.put("action", "TestAction");

        context.put("formAction", new DynamicURI(rundata).toString());
        context.put("doPrev", BUTTON + "doPrev");
        context.put("doNext", BUTTON + "doNext");
        context.put("stateKey", "state");

    }   // buildNormalContext

    /**
    * doNext called for form input tags type="submit"
named="eventSubmit_doNext"
    * input processing only!
    */
    public void doNext(RunData data, Context context)
    {
        // which portlet instance state key 
        String key = data.getParameters().getString("state");

        // get the state from the manager
        StateManagerService mgr =
(StateManagerService)TurbineServices.getInstance()
                .getService(StateManagerService.SERVICE_NAME);
        Integer state = (Integer) mgr.getState(key, "state");
        mgr.setState(key, "state", new Integer(state.intValue() + 1));
        mgr.setState(key, "message", "from next");

    }   // doNext

    /**
    * doPrev called for form input tags type="submit"
named="eventSubmit_doPrev"
    * input processing only!
    */
    public void doPrev(RunData data, Context context)
    {
        // which portlet instance state key 
        String key = data.getParameters().getString("state");

        // get the state from the manager
        StateManagerService mgr =
(StateManagerService)TurbineServices.getInstance()
                .getService(StateManagerService.SERVICE_NAME);
        Integer state = (Integer) mgr.getState(key, "state");
        mgr.setState(key, "state", new Integer(state.intValue() - 1));
        mgr.setState(key, "message", "from prev");

    }   // doPrev

}   // TestAction


Here's the .vm:

## a form to send to my actions
<form action="$formAction" method="post">
    #if ($state-msg) <h2>$message $state-msg</h2> #end
    <input type="hidden" name="action" value="$action" />
    <input type="hidden" name="$stateKey" value="$state" />
    <input type="submit" name="$doPrev" value="<-- prev" />
    <input type="submit" name="$doNext" value="next -->" /> </form>

* * * * * * * * * * * * *

So, thanks for staying with me so far!  Portlet instance state lets us do
this neat separation.  Unique ids help, too.  I think this is perfect for
input processing, proper portlet (and instance) targeting, and output
processing.  To be real clean, I'd in my first getContent() or
buildNormalContext() pull any info about the configuration / customization
that I'd need for action do() routines and place it in my state, so it's
easily available when my forms come back.

The only problem left, and it's a biggy, is the name space conflict in the
context.  If we do lots of portlets this way, we will likely all be placeing
"$action" with out class name and "$state" with our state key and ... Why
does the context need to be kept from one portlet aggregation to the next?
Can't we setup the context, run the template, then clear the context and go
to the next portlet?  That would take care of that little problem.

So, have I made my case?

Thanks so much!

- Glenn



--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to