|
Code Walk Thru |
| Home.About Strandz.Example.Code Walk Thru |
|
|
|
|
The Model
We are going to do a bottom-up tour of the Wombat Rescue Rostering Application user interaction code. Before we start the model needs to be vaguely understood. These two diagrams show all the nodes in RosterWorkersStrand and the cells inside
This reference table completes our look at the model. It is a list of all the attributes, taken from the source code file
private RosterWorkersDT dt;
Thus
/*
* No roster slot actually exists, yet one is manufactured from
* all the items - and that includes many DOs. So you end up
* with a 'deep' roster slot with all its object references.
*/
rosterSlot =
(RosterSlot)dt.rosterslotReferenceDetailCell.getItemValue();
As the comment explains, all the values on the screen for a particular visible record are used to manufacture a new DO graph.
When the user has inserted a new visible record then the strand is in a NEW state (coded as
In the following code we manufacture a RosterSlot if one is currently not available. Once we have the rosterSlot we call the method
private void calculateRSSentence(StateEnum stateId)
{
if(dt.ui1 != null) //possible that a dt would not have any panels
{
if(dt.ui1.getLSentence() != null)
{
RosterSlot rosterSlot = null;
if(stateId.isNew())
{
/*
* No roster slot actually exists, yet one is manufactured from
* all the items - and that includes many DOs. So you end up with
* a 'deep' roster slot with all its object references.
*/
rosterSlot = (RosterSlot) dt.rosterslotReferenceDetailCell.getItemValue();
}
else
{
VisibleExtent ve = dt.rosterslotReferenceDetailCell.getDataRecords();
int row = dt.rosterslotReferenceDetailNode.getRow();
if(row != -1) //when stateId == StateEnum.FROZEN/UNKNOWN row will be -1
{
rosterSlot = (RosterSlot) ve.get(row);
}
}
if(rosterSlot != null)
{
String sentence = rosterSlot.toSentence();
Err.pr(SdzNote.tightenRecordValidation, "SENTENCE (from creational method) : " + sentence);
dt.ui1.getLSentence().setText(NameUtils.toHTML(sentence));
}
else
{
dt.ui1.getLSentence().setText("");
}
}
else
{
Err.error("If have a panel then it s/have a sentence label. dt config problem");
}
}
}
From above we can see that when the strand's state is not NEW it is possible to use the model to get the current rosterSlot.
Let us now move on and find out the callers of
sdzBag.getStrand().addPostControlActionPerformedTrigger(
new PostOperationTrigger() );
...
public class PostOperationTrigger
implements PostOperationPerformedTrigger
{
public void postOperationPerformed(OutNodeControllerEvent evt)
{
if(evt.getNode() == dt.rosterslotReferenceDetailNode
&& evt.getID() == OperationEnum.GOTO_NODE
&& evt.getNode().getState() != StateEnum.FROZEN)
{
workerOps.toggleMonthlyRestart(((Boolean) dt.monthlyRestartAttribute.getItemValue()).booleanValue());
outer.calculateRSSentence(evt.getNode().getState());
}
}
}
The second
dt.rosterslotReferenceDetailNode.addNavigationTrigger(
new RosterSlotNavigationT() );
...
class RosterSlotNavigationT implements NavigationTrigger
{
public void navigated(OperationEvent evt)
{
if(dt.strand.getCurrentNode().getState() != StateEnum.FROZEN)
{
outer.workerOps.toggleMonthlyRestart(((Boolean) dt.monthlyRestartAttribute.getItemValue()).booleanValue());
}
outer.calculateRSSentence(dt.strand.getCurrentNode().getState());
}
}
Our third
public class PasteAction extends AbstractAction
{
public void actionPerformed(ActionEvent ae)
{
execute();
}
public void execute()
{
sdzBag.pasteItemValues();
workerOps.toggleMonthlyRestart(((Boolean) dt.monthlyRestartAttribute.getItemValue()).booleanValue());
outer.calculateRSSentence(dt.strand.getCurrentNode().getState());
setEnabled(false);
}
}
The
sdzBag.getStrand().addStateChangeTrigger( new ToFrozenTrigger());
...
private class ToFrozenTrigger implements StateChangeTrigger
{
public void stateChangePerformed(StateChangeEvent e)
{
StateEnum current = e.getCurrentState();
if(current == StateEnum.FROZEN)
{
outer.calculateRSSentence(current);
}
}
}
Thus
As we have seen a
private void copyPasteButton(List abilities)
{
copyAction = new CopyAction();
copyAction.putValue(Action.NAME, "Copy");
copyAction.putValue(Action.SHORT_DESCRIPTION,
"Copy this screen out to the buffer");
abilities.add(copyAction);
//
pasteAction = new PasteAction();
pasteAction.setEnabled(false);
pasteAction.putValue(Action.NAME, "Paste");
pasteAction.putValue(Action.SHORT_DESCRIPTION,
"Paste to this screen from the buffer");
abilities.add(pasteAction);
//
Err.pr(WombatNote.generic, "copyPasteButton been added");
}
The
public class CopyAction extends AbstractAction
{
public void actionPerformed(ActionEvent ae)
{
execute();
}
public void execute()
{
sdzBag.copyItemValues(dt.rosterslotReferenceDetailNode);
pasteAction.setEnabled(true); // this property is listened to by
// the physical NodeController
}
}
Thus we can see that the actual mechanics of Copy and Paste are done with simple method calls on the SdzBag. The node parameter in the call to
It should be noted that nodes are context-sensitive. As an illustration of this the Copy and Paste buttons only exist on the toolbar when
Every node already has commonly used and well known abilities such as setting the current row, inserting a new DO, removing a DO, entering a query and so on. Other abilities that are unknown by the node, or completely unknown by Strandz, can also be added, but this must be done programmatically rather than via the model. Copy and paste fall into this category. The following code completes our examination of incorporating these new abilities into
List rosterSlotAbilities = new ArrayList(); ... copyPasteButton( rosterSlotAbilities ); dt.rosterslotReferenceDetailNode.setAbilities( rosterSlotAbilities);Alphabetic Worker selection
The next thing we will look at is how alphabetic Worker selection is incorporated into Wombat Rescue. This time we will start with the setup code. First of all create the
AlphabetScrollPane sp = new AlphabetScrollPane();
simplifiedForDemo = getSimplifiedForDemo();
if(simplifiedForDemo)
{
String letters[] = { "B", "C", "F", "G", "H", "M", "N", "P", "S", "W"};
sp.setLetters( letters);
}
else
{
String letters[] = { "A","B","C","D","E","G","H","J","K","L","Ma","Mu",
"N","O","P","R","Sa","Sh","St","T","V","W","Z"};
sp.setLetters( letters);
}
Then we make the
sp.getContentPanel().add( sbI.getPane( 0 ), BorderLayout.CENTER ); sbI.setPane( 0, sp );
Having made sure that the
alphabetListener = new AlphabetActionListener( sbI, dt.workerNode, dt.workerCell ); sp.getAlphabetPanel().setActionListener( alphabetListener );
The
/**
* @param txt - The letter (or sequence of) that you want to do the action for
*/
public void perform(String txt)
{
int i = 0;
for(Iterator iter = workerCell.getDataRecords().iterator(); iter.hasNext(); i++)
{
Worker vol = (Worker) iter.next();
if(vol.startsWith(txt))
{
break;
}
}
if(i != workerNode.getRowCount())
{
sbI.getStrand().SET_ROW(i);
}
}
Lastly here we make sure that the
sdzBag.getStrand().addStateChangeTrigger(
new AlphabetTrigger( sp.getAlphabetPanel()));
...
private static class AlphabetTrigger implements StateChangeTrigger
{
private AlphabetPanel panel;
AlphabetTrigger(AlphabetPanel panel)
{
this.panel = panel;
}
public void stateChangePerformed(StateChangeEvent e)
{
StateEnum current = e.getCurrentState();
StateEnum previous = e.getPreviousState();
if(current == StateEnum.ENTER_QUERY)
{
panel.setEnabled(false);
}
else if(previous == StateEnum.ENTER_QUERY)
{
panel.setEnabled(true);
}
}
}
Data Flow Triggers
Data flow triggers are used to populate application data. Here is how workers are populated:
dt.workerNode.addDataFlowTrigger( new DataFlowT1());
...
class DataFlowT1 implements DataFlowTrigger
{
public void dataFlowPerformed(DataFlowEvent evt)
{
if(evt.getID() == DataFlowEvent.PRE_QUERY)
{
dataStore.rollbackTx();
dataStore.startTx();
masterQuery();
}
else if(evt.getID() == DataFlowEvent.POST_QUERY)
{// Err.pr( "Have read data in, got " + workerExtent.size() + " Worker records");
}
}
}
private void masterQuery()
{
TimeBandMonitorI lovMonitor = WidgetUtils.getTimeBandMonitor(dataStore.getEstimatedLookupDataDuration());
TaskI task = new LookupDataTask(dataStore, this); //calls setLOVs()
lovMonitor.start(task);
lovMonitor.stop();
Collection c = null;
if(title == null)
{
Err.error("Not yet called display(), to say which title to query for");
}
else if(title == WombatDomainQueryEnum.ROSTERABLE_WORKERS.getDescription())
{
c = queriesI.executeRetCollection(WombatDomainQueryEnum.ROSTERABLE_WORKERS);
}
else if(title == WombatDomainQueryEnum.UNROSTERABLE_WORKERS.getDescription())
{
c = queriesI.executeRetCollection(WombatDomainQueryEnum.UNROSTERABLE_WORKERS);
}
else if(title == WombatDomainQueryEnum.GROUP_WORKERS.getDescription())
{
c = queriesI.executeRetCollection(WombatDomainQueryEnum.GROUP_WORKERS);
}
else
{
Err.error("Have not coded for title: <" + title + ">");
}
if(c.size() == 0 && title == WombatDomainQueryEnum.ROSTERABLE_WORKERS.getDescription())
{
Err.error("Have not found any " + title + " in " + dataStore);
}
List enterQryAttribs = dt.workerNode.getEnterQueryAttributes();
masterList = InterfUtils.refineFromMatchingValues(c, enterQryAttribs);
dt.workerCell.setData(masterList);
}
The above method is a little bit clunky at the moment because
Also from above
void setLOVs()
{
List days = queriesI.executeRetList(WombatDomainQueryEnum.ALL_DAY_IN_WEEK);
List shifts = queriesI.executeRetList(WombatDomainQueryEnum.ALL_WHICH_SHIFT);
List flexibilities = queriesI.executeRetList(WombatDomainQueryEnum.ALL_FLEXIBILITY);
List seniorities = queriesI.executeRetList(WombatDomainQueryEnum.ALL_SENIORITY);
List sexes = queriesI.executeRetList(WombatDomainQueryEnum.ALL_SEX);
List weeksInMonth = queriesI.executeRetList(WombatDomainQueryEnum.ALL_WEEK_IN_MONTH);
List intervals = queriesI.executeRetList(WombatDomainQueryEnum.ALL_INTERVAL);
List overrides = queriesI.executeRetList(WombatDomainQueryEnum.ALL_OVERRIDE);
List monthsInYear = queriesI.executeRetList(WombatDomainQueryEnum.ALL_MONTH_IN_YEAR);
Collection groups = queriesI.executeRetCollection(WombatDomainQueryEnum.WORKER_GROUPS);
List pickGroups = new ArrayList(groups);
pickGroups.add(0, queriesI.executeRetObject(WombatDomainQueryEnum.NULL_WORKER));
dt.dayInWeekLookupCell.setLOV(days);
dt.whichShiftLookupCell.setLOV(shifts);
dt.shiftPreferenceLookupCell.setLOV(shifts);
dt.flexibilityLookupCell.setLOV(flexibilities);
dt.seniorityLookupCell.setLOV(seniorities);
dt.sexLookupCell.setLOV(sexes);
dt.belongsToGroupLookupCell.setLOV(pickGroups);
dt.weekInMonthLookupCell.setLOV(weeksInMonth);
dt.intervalLookupCell.setLOV(intervals);
dt.overridesOthersLookupCell.setLOV(overrides);
dt.onlyInMonthLookupCell.setLOV(monthsInYear);
dt.notInMonthLookupCell.setLOV(monthsInYear);
}
The The detail query is simpler:
class DataFlowT2 implements DataFlowTrigger
{
public void dataFlowPerformed(DataFlowEvent evt)
{
if(evt.getID() == DataFlowEvent.PRE_QUERY)
{
detailQuery();
}
}
}
private void detailQuery()
{
detailList = queriesI.executeRetList(WombatDomainQueryEnum.ALL_ROSTER_SLOT);
dt.rosterslotReferenceDetailCell.setData(detailList);
}
The reason that a detail data flow trigger needs to be written at all is that the master-detail relationship between Worker and RosterSlot is based on a reference within RosterSlot to Worker, rather than each Worker having its own collection of RosterSlots. Record Validation
We will leave you to continue the examination of this source code. Hopefully we have covered everything that might need explantion ... |