D2W Stateful ControllersFuego Digital Media QSTP-LLCDaniel Roy
• Using WebObjects since 2004
• Work with government agencies, private companies and various foundations globally
• Fuego Content Management System & Fuego Frameworks
• Presence in North America and Middle East
Fuego History
• 42% of community uses D2W
• “D2W is to WebObjects apps as the assembly line is to automobiles...you provide customization directions (D2W rules) to the assembly line (D2W) to build your end products (WOComponent pages).”
• Flow is controlled by delegates that manage the actions and direction of the application
• Use ERDBranchDelegate to define the actions displayed on the page by rules or via introspection
Direct To Web
The Basics
query select edit save
Start/Stop
Confirm
Edit
ConfirmConfirm
saveForLater
Save
Delete
edit
Edit CommentreadyToSubmit*
readyToSubmit*
saveAndSubmit
Submit
delete*
confirmDeletion
approveDeletion
confirm
edit*
List Expense Claims
selectcancel
Notes:- Cancel is present on all pages, but onlyshown for first.- Any branches with * are optional and are shown based on business logic- Any branches with the same name, are the same page configuration, but with different branches
Needs to enter comment?
NOEdit Comment
YES
WHAT?
WHAT???
??
Back in the day...
• Without Project Wonder, rely on nextPageDelegate and nextPage for navigation
• nextPageDelegate is checked first, and if found call nextPage()
• without nextPageDelegate, nextPage() is called on the D2WPage component directly
• Single class between each page
Next Page Delegate
D2WPage NPD(select)
D2WPage NPD(edit)
D2WPage NPD(query)
...
Plain WebObjects Controller Sample Code
public class ManageUserEditNextPageDelegate implements NextPageDelegate {
public WOComponent nextPage(WOComponent sender) { NSArray<User> selectedObjects = ((ERDPickPageInterface) sender).selectedObjects(); User currentUser = selectedObjects.get(0); EditPageInterface epi = (EditPageInterface) D2W.factory().pageForConfigurationNamed("ManageUsers_User_Edit_PC", sender.session()); epi.setNextPageDelegate(new ManageUserSaveNextPageDelegate()); epi.setObject(currentUser); return (WOComponent) epi; }
}
Enter Project Wonder
• ERDBranchDelegate to the rescue!
• Now you can show multiple branch choices for a page
• Pull choices from D2W context in the method branchChoicesForContext(), using the D2W rule key “branchChoices”
• Or, use introspection to find all methods that take a single WOComponent as parameter
ERDBranchDelegate
D2WPage ERDBD
D2WPage(search)
D2WPage(add)
D2WPage(cancel)
ERDBD
D2WPage(save)
D2WPage(cancel)
D2WPage(add)
ERDBranchDelegate Sample Code
public class ManageUserControllerSelectBranchDelegate extends ERDBranchDelegate {
// Return an edit page for a single selected row in the pick page public WOComponent edit(WOComponent sender) { NSArray<User> selectedObjects = ((ERDPickPageInterface) sender).selectedObjects(); User currentUser = selectedObjects.get(0); EditPageInterface epi = (EditPageInterface) D2W.factory().pageForConfigurationNamed("ManageUsers_User_Edit_PC", sender.session()); epi.setNextPageDelegate(new ManageUserControllerSaveBranchDelegate()); epi.setObject(currentUser); return (WOComponent) epi; }
// From the selected items in the pick page, perform a delete public WOComponent delete(WOComponent sender) { NSArray<User> users = ((ERDPickPageInterface) sender).selectedObjects(); for (User user : users) { user.delete(); } if (users.count() > 0) { // Assuming all in same EC. users.get(0).editingContext().saveChanges(); } return sender.pageWithName("PageWrapper"); }}
But wait...
• Must still set and get the objects on each page
• Lots of classes to write
• Why not reuse the same delegate?
Benefits of Delegate Reuse
• Enter the controller/delegate many times (one controller for many pages)
• Reuse the stored state in editing context
• Support one or many flows within the single class
• Define the visible branches using rules
Re-entrant Branch Delegate
D2WPageBD
queryPage
search()
edit()
editPage
selectPage
save()
Is ERDBranchDelegate Enough?
• Yes....but...no.
• Branch choices are defined in the rules, but can lead to a complex ruleset depending on the business logic
• Introspection on the ERDBranchDelegate returns all the methods in the class
Hello (Stateful) World!
• FDStatefulController is a re-entrant branch delegate extending ERDBranchDelegate
• Better separation of MVC
• Override branchChoicesForContext(D2WContext context)
• Reuse page configurations (define the PC in code)
• store state between pages
• editing context
• EOs, etc in subclasses
branchChoicesForContext
public class StatefulController extends ERDBranchDelegate { private NSArray<?> branches; private EOEditingContext editingContext;
public NSArray<?> getBranches() { return branches; }
public void setBranches(NSArray<?> branches) { this.branches = branches; } public EOEditingContext editingContext() { if (this.editingContext == null) { this.editingContext = ERXEC.newEditingContext(); } return this.editingContext; }
public NSArray branchChoicesForContext(D2WContext context) { NSArray choices = getBranches(); if (choices == null) { branches = (NSArray) context.valueForKey(BRANCH_CHOICES); } else { NSMutableArray translatedChoices = new NSMutableArray(); for (Iterator iter = choices.iterator(); iter.hasNext();) {
... rest of method continues the same as ERDBranchDelegate
StatefulController Code// Return an edit page for a single selected row in the pick pagepublic WOComponent edit(WOComponent sender) { NSArray<User> selectedObjects = ((ERDPickPageInterface) sender).selectedObjects(); User currentUser = selectedObjects.get(0); setBranches(new NSArray("save")); EditPageInterface epi = (EditPageInterface) D2W.factory().pageForConfigurationNamed("ManageUsers_User_Edit_PC", sender.session()); epi.setNextPageDelegate(this); epi.setObject(currentUser); return (WOComponent) epi;}
public WOComponent save(WOComponent sender) { editingContext().saveChanges(); return sender.pageWithName("PageWrapper");}
// From the selected items in the pick page, perform a deletepublic WOComponent delete(WOComponent sender) { setBranches(null); NSArray<User> users = ((ERDPickPageInterface) sender).selectedObjects(); ERXUtilities.deleteObjects(editingContext(), users); return save(sender);}
Page Utility Methods
Interaction
editPage
queryPage
listPage
inspectPage
messagePage
pickPage
selectPage
Utility
editingContext()
nestedEditingContext
save()
Utility Method Examples
protected EditPageInterface editPage(Session session, String pageConfigurationName, EOEnterpriseObject enterpriseObject) { EditPageInterface epi = (EditPageInterface) pageForConfigurationNamed(pageConfigurationName, session); epi.setNextPageDelegate(this); epi.setObject(enterpriseObject); return epi;}
protected WOComponent pageForConfigurationNamed(String pageConfigurationName, Session session) { WOComponent component = D2W.factory().pageForConfigurationNamed(pageConfigurationName, session); if (component instanceof D2WPage) { ((D2WPage) component).setNextPageDelegate(this); } return component;}
Summary
• Re-entrant controller provides code reusability
• Specify branch choices programmatically
• Storing the editing context allows easy access to associated objects during flows and encourages a single point of save
• Utility and convenience methods allow for simple setup and development of customized flows
Resources
• What is Direct To Web?http://wiki.wocommunity.org/pages/viewpage.action?pageId=1049018
• D2W Flow Controlhttp://wiki.wocommunity.org/display/documentation/D2W+Flow+Control
• ERDBranchDelegateInterfacehttp://jenkins.wocommunity.org/job/Wonder/lastSuccessfulBuild/javadoc/er/directtoweb/pages/ERD2WPage.html#pageController()
• Page controller in ERD2Whttp://osdir.com/ml/web.webobjects.wonder-disc/2006-05/msg00041.html
Q&A
Top Related