Additional Blogs by Members
cancel
Showing results forΒ 
Search instead forΒ 
Did you mean:Β 
Former Member
0 Kudos

Once I re-read topics of my WebDynpro-related posts, and found that they roughly can be divided in 3 categories:

  • solving problems with ICMIModel (problems with OVS, RFC, dynamic UI programming, etc);
  • solving problems with calculated attributes;
  • use both as golden hammer that solves any problem

Today's post falls into second category, so it is just yet another (and hopefully useful) application of calculated attributes. Also we will need deprecated albeit unavoidable in certain scenarios IFrame control. By the way, I'm writting about TimedTrigger second time in a row πŸ™‚

Hurry up!

It is common for quiz-like applications to limit time user can think over the question. Not a big deal, you might say, just place TimedTrigger on WebDynpro view, set delay property to necessary value and you are done. Not that simple!

See, WebDynpro server-side rendering causes client-server requests when either user invokes certain action (button or link click) or WebDynpro internally "decides" that it is necessary to query server, for example, to fetch next page of rows when user scrolling Table UI control.

Hmmm... And what is the problem? Well, rendering engine for WebDynpro HTML client (probably others clients as well) sends complete page back. Re-read this: there is no "delta" updates currently, the whole page is sent back. So WebDynpro renders all controls and sends HTML back to client. The new content replace old one in "flicker-free" manner and user even doesn't notice full page replace in most cases.

Still do not understand the point? Then it's time for "Aha!" moment. How do you think, what value will TimedTrigger.delay property has after such round-trip? Yes, the one defined on server-side, i.e. initial one.

Count Down Timer in Webdynpro. Say, you defined that question must be answered within 5 minutes, and set delay of TimedTrigger to 5 * 60 = 300. User sees the question and thinks a bit. 40 seconds passed, 260 seconds are remaining. Next she tries to select row in table, the client-server roundtrip is performed and she has yet another 300 seconds at her disposal. 40 + 300 = 340. Another action, another 300 seconds added to available time. It's not a fair game!

No one can stop time

So the solution is obvious: we need a mechanism that reduces delay from some initial value for any server-side action invocation, be it either custom action handler or internal WebDynpro post-back. Here is an algorithm:

  1. First, we need to save timestamp on server when question is initially shown to user. The best place is either wdDoInit of view controller or handler of inbound plug. As far as we do not plan to show this value on UI, we use internal controller variable startTime(long) rather then context attribute.
  2. Next, we have to bind TimedTrigger.delay property to context attribute Delay of type integer. We must use binding because we plan to alter value. And, once again, never ever use wdDoModifyView for these purposes - bindable properties of UI controls must only be updated via context!
  3. Finally, we must update Delay attribute to reflect remaining time. It would be silly to update it manually in every action handler. Even if we do this task, we still are unable to alter this attribute when user scroll Table content, for example. So attribute Delay should be calculated read-only attribute with dynamically defined value. Time, dynamically... some tautology is here πŸ™‚

The formula to recalculate delay dynamically is short but probably not obvious:

int delay = Math.max( 1, (int)( (startTime - System.currentTimeMillis()) / 1000L + element.getTimeout() ) );

 
...where startTime is a timestamp saved in [1], element.getTimeout() is configurable value for total time user may spend on question.

As far as Java returns timestamp in milliseconds, and TimedTrigger uses second as lowest resolution we have to adjust time spent on question (startTime - System.currentTimeMillis) correspondingly. Note, that this will be negative value, that is later taken from total time (element.getTimeout()).

It's probably not obvious why it is necessary to use Math.max(1, calculatedTimeLeft). First of all, negative values for TimedTrigger.delay cause an error. Second, zero value disables TimedTrigger at all!

But the real reason is here. At the time Delay attribute getter is invoked, we know that WebDynpro renders client response. And we may not interrupt this process in any way. So even time is over according to calculations, we must allow WebDynpro to sent response back to client. But with minimal allowed delay value: one. So TimedTrigger will fire almost immediately and application forwards user to another page (with explanation that she was unable to answer question in time).

The Ticking Thing

So far so good. But application still misses yet another feature common for time-limited quiz applications, namely visual timer that instantly informs user about time left. Unfortunately, WebDynpro currently have no such control. And emulating it with yet another TimedTrigger is impossible in this case, read the documentation for TimedTrigger control to understand better why.

Instead, we must resort to well-known IFrame evil. In this case, the evil does not hurt much, and even make our application to look like time-bomb πŸ™‚

We use classic count-down HTML + JavaScript code:

     

 
The only problem here is that we must pass additional parameter to HTML page - the time left. Probably it is possible to put HTML in mimes folder, then just add this parameter to URL and parse query string in JavaScript. But I don't know how WebDynpro binary cache will work if I add parameter to static page. And, frankly, I have no enough time for experiments πŸ˜‰

Instead, the HTML template is created as string constant within view controller, then placeholder is substituted with actual value and WDWebResource API is used to dynamically put resulted HTML into binary cache:

final int delay = element.getDelay(); final String html = COUNTDOWN_HTML.replaceAll("#delay#", String.valueOf(delay) ); try { final IWDCachedWebResource resource = WDWebResource.getWebResource( html.getBytes("UTF-8"), WDWebResourceType.HTML ); resource.setResourceName("HTML_inline.html"); resource.setAttachement( false ); resource.setReadOnce( false ); return resource.getAbsoluteURL(); } catch (final Exception ex) { return "about:blank"; }

 
You can find more details about this technique in my Display formatted text using WebDynpro for Java

Results

Here is a screenshot of main application view at design time:

image
 
image

 
The most important things here are TimedTrigger control, IFrame with countdown JavaScript and submit Button that let user submit an answer (sure, if and only if she does it before time is over). The rest of controls stay here just to verify that actions does not reset timer. You may select rows in table, scroll table, click on links and see that time is counting down continuously.

And this screen-shot is taken from application running within Internet Explorer browser:

image

 
It must be noted that the solution is not perfect. The actual time spent on question may exceed defined one in one-two seconds (remember this Math(1, calculatedTimeLeft) thing?). But I guess it's not so critical for quizzes.

As usual, you may download source code here ("Save as...", rename to *.zip). The application is created using NW04s, but it should be easy to back-port it to NW04.

1 Comment