How to create a pure 4GL wizard

Posted by Patrick Tingen on 22-Oct-2018 12:34

I am looking for a way to create a wizard-like program in pure 4GL. In the past I have created things like this by defining a window, drawing multiple frames on it and showing/hiding them at the right time. This works ok if you have a few frames but if the frames are larger or when you have more than just a few frames, it becomes cumbersome very fast. 

Alternative way would be to use includes, but I only use includes for definitions and stopped using includes for real code somewhere at the end of the previous century.

Yet another method would be to use a smartfolder with ADM2, but ADM2 seems a bit - well - old.

Even another way would be to use a tabstrip control, but that feels not good as well.

So what remains? I have been playing a bit with separate windows, setting the attribute 'Suppress Window' in the windows that form the elements of the wizard. This makes sure that the frame of the program you start is created inside the current window. So it behaves a bit the way I want it, but not entirely; program flow is held in the program you start. This can be overcome by starting the elements of the wizard as persistent programs, but then you end up having multiple programs active in the same window and if you close the window, you need to click the close-window cross (upper right of the window itself) as many times as there are elements. Not ideal. 

Is there a better way to do this?

All Replies

Posted by Peter Judge on 22-Oct-2018 13:00

Since you can dynamically create widgets in ABL easily enough, would you consider storing the UI metadata somewhere? XML, JSON, db tables are all options. There’s code that shows doing this in Dynamics - github.com/.../rydynvcroi.i  ; this is included in github.com/.../rydynviewp.p .
 
If you want a wizard-style UI, then you can should have one window per ‘function’ (with read/write data). Once you have those pieces you can stich them together in multiple windows or in a single tabbed container or even a treeview potentially.  I’d think that you always want some form of controller for the whole wizard flow – and that can then deal with hiding/view/kill/launch of the child frames (windows, tabs, etc). The controller can be a simple .P – it doesn’t have to be visual itself.
 
The challenge is scope, I think. How complex and/or generic and/or extensible do you need this to be? Also, do you need a Windows/desktop UI? Can you do it via Web or API?
 
 
 

Posted by onnodehaan on 22-Oct-2018 13:02

Hi Patrick,

Our wizard actually works very good. We have been using it for over 10 years.

We have a generic "wizard"-dialog. That dialog can load a list of .W-files (smart frames) one after the other and scales accordingly. Every "wizard" has an API, that manages temp-tables and sets the order of the frames that ask for data. Validations and the final write-to-database are also performed inside the wizard-specific API.

To an enduser it's exactly the same as a normal wizard in other Windows programs.

Posted by Patrick Tingen on 23-Oct-2018 01:51

After my post I dived in again and explored the option with suppress-window. Actually, in my first attempts I was trying to overcomplicate things so it could be done simpler. And more simpler is more better :)

I have now created a window to serve as the host for the frames that make up the steps of the wizard. In my test I used 3 steps, but it is plain easy to extend it. The window is wizard.w, the individual steps wizardsub1.w to 3.w. In the window, I start the underlying .w files persistently. These are set up to suppress-window = yes and have an output parameter that gets filled with the frame handle. This handle is saved in a temp-table in the window and is used to hide/view/move the frame around.

I added a counter to the temp-table and two extra buttons for 'next' and 'previous' that use this counter to walk through the screens. A simple FOR-EACH will hide or view the steps. To overcome the problem of having to click the close button multiple times, I let the windows accept a handle to the parent procedure (the main window). A close event is then applied to the parent instead of to the child, so if you close the parent window, it closes at once.

It works quite well. Actually, I should have used a dialog instead of a window, but that's a minor thing. Next thing is how to handle updates to the database. Since the steps are individual programs, they need to have a common source for the data if they need to react on each others state. A database will do, but it means that changes by the user cannot be cancelled; if you do something in step 1, it must be recorded in the database otherwise step 2 will not know of it. But it implies that every step you do as a user is immediately recorded in the database. But you might want to undo what you did in the wizard, so I need a better solution. One may be a dataset that is controlled in the window and shared with the children.

If anyone is interested, I can throw it on GitHub

Posted by ske on 23-Oct-2018 02:20

What do you mean you can't undo something just because it was immediately recorded to the database? If you put the whole wizard in one big transaction, you can undo everything that was updated in the database at any time until the transaction ends. That's what UNDO of transactions does.

Until the transaction is undone, the updates are there in the database, and can be found. (Just make sure that you really have assigned values to any indexes that you want use to find hem.)

Posted by Patrick Tingen on 23-Oct-2018 02:36

Yes, but having a transaction open while having UI at the same time doesn't sound right. If I wanted to create a wizard that did something with the order table, it would mean that I needed a lock on the order table, which does not give a good user experience.

Posted by onnodehaan on 23-Oct-2018 02:52

Hi Patrick,

Sounds a lot like we set up our wizard (see previous comment).

In order to keep the locking to a minimum, we let every "wizardsub.w"-frame populate a temp-table. Every time a user presses Next, the temp-table is send to a wizard-specific API, using dynamic code.

After pressing Finish (we rename the label of the Next button in the last step), this wizard-specific API start the write-out to the database.

Because our wizard-framework is set up dynamically, we can load in a list of .w's and a API dynamically, making the framework usable to every possible wizard we want to make

Posted by Patrick Tingen on 23-Oct-2018 02:59

Hi Onno,

Thats more or less along the lines I am experimenting now. The main program holds a temp-table that is sent to the subwindows when they get focus and fetched when they lose it. This works, but is not dynamic. Is it possible for you to share some code?

Posted by bronco on 23-Oct-2018 03:04

I would use a data structure (be it a dataset of a temptable) per wizard page. Every page (.w) should have a Save procedure which re-validates if persisting the dataset/tt is still possible. At the push of the finish button a transaction should be started. Then sequentially call all the Save procedures and then either everything is persisted to the database or the transaction is undone. Easy :-)

Posted by bronco on 23-Oct-2018 03:05

Which is basically the same as Onno suggested I see now...

Posted by Rutger Olthuis on 23-Oct-2018 08:01

I'd use an xml or json in memory to store the data while working through the wizard.

Posted by Peter Judge on 23-Oct-2018 08:06

If it’s all in ABL, you can use a ProDataSet with TRACKING-CHANGES on. That allows you to revert changes manually if needed using the REJECT-CHANGES() method.
 

This thread is closed