Qwicap attempts to impose as few requirements as possible on client code, but there is one situation where it must impose a design pattern: Sending a web page to the user, and processing the form data set sent back from that page. It usually goes something like this:
while (true) { try { Q.prompt(ThisWebPage); // "Q" is the current Qwicap instance.
rejectInput
" if the built-in validation features aren't
sufficient for your purposes).
break
" statement, or
equivalent logic, to exit the enclosing "while
" loop.
} catch (QwicapAbandonmentException e) { e.rethrowIfThisPageWasAbandoned(); } }
An implementation of the prompt pattern is shown in the Qwicap Introduction.
There is room for variation within this pattern, but its essentials must be retained
in order for Qwicap to work correctly. Those essentials are: (1) Prompting,
retrieving/validating/rejecting input, and branching to subsidiary web pages, must
occur within a "try
" block that catches the "QwicapAbandonmentException
"
and invokes that exception's "rethrowIfThisPageWasAbandoned
" method.
(2) That "try
" block must be placed within a loop that does not exit
until you have no further need for the web page it handles. (3) Handle only one
web page within each such loop.
If you want to get started with Qwicap as quickly as possible, that's all you need to know; use that pattern and your web application will work pretty much as you'd expect. If you want to understand the purpose of the pattern, continue reading.
Abandonment Issues
Conventional applications enjoy the simplicity of rigidly controlling their user's movement within them. (Whether users enjoy this is another matter.) Web applications don't have this luxury; they can define a user's options as rigidly as they wish, but the user can always abandon the current page by using their browser's "back" button and then ambush the application with inputs from past pages. Web applications may choose to detect and ignore such input, which can simplify matters, but this approach has been known to annoy users who are accustomed to their "back" buttons. Alternately, web applications can be designed to keep no state by embedding all of the information they need in every form they generate, and then starting their work more-or-less from scratch with every hit, but that creates its own set of problems.
Qwicap attempts to provide the application developer with the simplicity of being unaware of such inputs, and the convenience of maintaining rich state information, while letting users enjoy their "back" buttons. It can't satisfy all of the desires of either developer or user, but it achieves a useful middle-ground: Web application developers get the luxury of having the effects of the "back" button handled transparently to their code, in exchange for implementing the "prompt" pattern. Users get to hit their "back" buttons, in exchange for only being able to go back to the most recent version of pages that are still in-play (in other words, pages whose implementations are still in the call chain; inputs from other pages are discarded).
An Example
Below is a very contrived fragment of an example application that prompts for various inputs, and allows the user to navigate among pages within the application. Four pages are provided. The first three, A, B and C, each prompt for a mathematical constant (they don't do anything with the input; it's merely to prevent the pages from seeming completely pointless). The remaining page, Z, displays the correct value of those constants. All of the pages allow the user to go to page Z, but pages A, B and C are arranged in an ordered progression: C can only be reached from B, and B can only be reached from A.
The pages A, B and C are each composed of two forms: one allows the user to enter a mathematical constant, and the other provides the navigation buttons, e.g. "Goto Page B", "Go Back".
(This example assumes a reference to the current Qwicap instance is present in an instance variable named "Q".)
Example Implementation | Example Page |
---|---|
A00 public void methodA() { A01 MutableMarkup DocA = Q.getDocument("a.html").getMutable(); A02 A03 while (true) { A04 try { A05 Q.prompt(DocA); A06 if (Q.has("gotoB")) A07 methodB(); A08 else if (Q.has("gotoZ")) A09 methodZ(); A10 else if (Q.has("quit")) A11 break; A12 else { A13 getDouble("pi", 3.13, 3.15, false, 0.0); A14 A15 .... // Do something useful with the input A16 } A17 } catch (QwicapAbandonmentException e) { A18 e.rethrowIfThisPageWasAbandoned(); A19 } A20 } A21 A22 Q.goodbye(Q.getDocument("goodbye.html").getMutable()); A23 } |
Page A |
B00 public void methodB() { B01 MutableMarkup DocB = Q.getDocument("b.html").getMutable(); B02 B03 while (true) { B04 try { B05 Q.prompt(DocB); B06 if (Q.has("gotoC")) B07 methodC(); B08 else if (Q.has("gotoZ")) B09 methodZ(); B10 else if (Q.has("goback")) B11 return; B12 else { B13 getDouble("e", 2.70, 2.72, false, 0.0); B14 B15 .... // Do something useful with the input B16 } B17 } catch (QwicapAbandonmentException e) { B18 e.rethrowIfThisPageWasAbandoned(); B19 } B20 } B21 } |
Page B |
C00 public void methodC() { C01 MutableMarkup DocC = Q.getDocument("c.html").getMutable(); C02 C03 while (true) { C04 try { C05 Q.prompt(DocC); C06 if (Q.has("goback")) C07 return; C08 else if (Q.has("gotoZ")) C09 methodZ(); C10 else { C10 Q.getDouble("phi", 1.60, 1.62, false, 0.0); C11 C12 .... // Do something useful with the input C13 } C14 } catch (QwicapAbandonmentException e) { C15 e.rethrowIfThisPageWasAbandoned(); C16 } C17 } C18 } |
Page C |
Z00 public void methodZ() { Z01 MutableMarkup DocZ = Q.getDocument("z.html").getMutable(); Z02 Z03 while (true) { Z04 try { Z05 Q.prompt(DocZ); Z06 if (Q.has("goback")) Z07 return; Z08 } catch (QwicapAbandonmentException e) { Z09 e.rethrowIfThisPageWasAbandoned(); Z10 } Z11 } Z12 } |
Page Z π = 3.1415....
|
The Call Chain
Consider the case where a user goes from page A, to B, to C. When page C is displayed (at line C05), the call chain would look like this:
methodC:C05
methodB:B07
methodA:A07
Because the methods supporting pages A and B are still in the call chain (this was
previously referred to as being "in-play"), they remain valid targets for user
interaction. In other words, if the user hits their browser's "back" button and
returns to either page, then interacts with any of the forms on one of those pages,
Qwicap will accept the form input as valid, and will automatically return the
program's point of execution to the method that is supposed to handle that input.
The QwicapAbandonmentException
and the Prompt pattern provide the
mechanism for accomplishing this.
To illustrate the workings of that process using the call chain above, suppose the user has clicked their browser's "back" button twice, and is now looking at page A ("a.html"). Suppose then that the user enters a value for π and clicks on page A's "Submit π" button. The application is still at line C05, where a form data set that supplies a value for π is neither expected, nor wanted. So, Qwicap must:
- Identify the page-of-origin1 ("a.html") of the form data set.
- Determine whether the method that managed the page (the method that passed the
page to
Qwicap.prompt
) is still present in the call chain. - If the method that managed the page is still present in the call chain, Qwicap must force your program to resume execution of that method, so that that method can process the form data set. Conversely, if the method is not in the call chain anymore, Qwicap must discard the unwanted form data set, and retransmit the current page ("c.html") to the user.
In this case, Qwicap would find that (1) it can identify the page-of-origin ("a.html"),
and (2) the method that manages the page (methodA
) is still present in the
call chain. Therefore, Qwicap sets out to accomplish objective no. 3: Forcing your
program to resume execution of that method (methodA
). It begins that
process by throwing a QwicapPageAbandonedException
,
one of several subclasses of QwicapAbandonmentException
.
The QwicapPageAbandonedException
causes the prompt
call
at line C05 to terminate, and execution jumps to the catch
on line C14.
That exception handler calls QwicapAbandonmentException
's method
rethrowIfThisPageWasAbandoned
. That rethrow method examines the
current call chain and determines that the program is executing methodC
,
which is not where it wants your application to be. So, the method re-throws the
exception which causes methodC
to exit and execution to resume in method
B at the QwicapAbandonmentException
handler on line B17.
The exception handler at B17 then calls the rethrowIfThisPageWasAbandoned
method. That method again determines that execution hasn't returned to the method that
manages page A, and therefore re-throws the QwicapAbandonmentException
for the second time. That causes methodB
to exit and execution resumes in
method A at its QwicapAbandonmentException
handler on line A18.
The exception handler at A18 then calls the rethrowIfThisPageWasAbandoned
method. That method determines that execution has, at long last, returned to the
method that does manage page A, and therefore it does nothing. That causes the
exception handler to fall-through to the bottom of the enclosing loop (line A20), and
for the loop to bring us back to the prompt
invocation on line A05.
Qwicap recognizes it already has a fresh form data set from this page, and therefore
the prompt
method does nothing. That leaves methodA
exactly
where it needs to be to begin processing the form data set that was original received
by the prompt
method invocation in methodC
.
Fresh and Stale Pages
[Obviously, this documentation isn't finished.]
MutableMarkup
object, i.e. the same mutable, in-memory copy of a
web page. So don't do that.