Glenn, > 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!
Did you mean to send this to my personal email account? <:-) Well, considering it was almost 5 pages, yes, Im still trying to dig thru it. May I counter with, have you had a chance to review the DatabaseBrowser code, and how it handles state? I limited all the state management to within the action, but I could see most of that code going into a generic state management service. Should I send your email and my responses to the list so that we can continue the discussion here? David > -----Original Message----- > From: Glenn Golden [mailto:[EMAIL PROTECTED]] > Sent: Friday, April 12, 2002 8:24 AM > To: 'Jetspeed Developers List' > Subject: Just wondering... > > > 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:jetspeed-dev-> [EMAIL PROTECTED]> > For > additional commands, > e-mail: <mailto:[EMAIL PROTECTED]> > > -- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>
