Subscribe for automatic updates: RSS icon RSS

Login icon Sign in for full access | Help icon Help
Advanced search

Pages: [1] 2
  Reply  |  Print  
Author Topic: Subdialogs in general  (Read 46169 times)
Stephen T.
Posts: 114


« on: March 04, 2014, 02:07:28 pm »

I am a bit late coming to release 2.5, so the subdialog feature is new to me.

From what I can see, I can now define 'external' modules that act as part of a main dialog and can then be used to get common elements.

Before I go to far down the wrong road, am I right in thinking that all the fields input in theory then need to be defined as Globals if I use external functions, as I can't see any way of passing and returning anything from the subdialogs.

IE in my test case, I have a claim that is made up of 'subdialogs' that cover:
claim header
insurer's/broker's references
client's name/address
client's contact methods
claim 'scope'

So I can now do:
GLOBALS
DEFINE g_claimH                        RECORD LIKE claimHeader.*,
            g_claimD                        RECORD LIKE claimDetail.*,
            g_claimrefs                     RECORD LIKE claimRefs.*,
            g_address                       RECORD LIKE address.*,
 DEFINE g_cMethArray                DYNAMIC ARRAY OF RECORD
             cMethLookUpCodesRecNo       LIKE lookUpCodes.recNo,
             cMethText                   VARCHAR(255)
             END RECORD
END GLOBALS
.....
             
DIALOG
    ATTRIBUTE(UNBUFFERED)
   
    SUBDIALOG ext_claimheader
    SUBDIALOG ext_references
    SUBDIALOG ext_nameAddress
    SUBDIALOG ext_contactmethods
    SUBDIALOG ext_claimScope

    ON ACTION .....

END DIALOG

......

So I can now re-use my address input code across all the structures that require a standard address layout - ditto for contact methods etc? We had partially gone down this route anyway, as we had the address/ contact input already as 'external' functions - but that then stopped them being bolted into an overall dialog.
Sebastien F.
Four Js
Posts: 545


« Reply #1 on: March 04, 2014, 02:42:56 pm »

Stephen,

Consider using module variables defined as PUBLIC, instead of GLOBALS:
PUBLIC module variables can be shared between modules.

What was the previous version of Genero BDL?

Take the time to read all new features that were added since the version you used:

https://4js.com/online_documentation/fjs-fgl-manual-html/?path=fjs-fgl-manual#c_fgl_nf.html

If you have any question do not hesitate!

Seb
Stephen T.
Posts: 114


« Reply #2 on: March 04, 2014, 04:14:21 pm »

Cheers Seb.
I have been using up to 2.4, but really only using the 'changes' that were called for - if that makes sense - ie only using the new featues when ther was a call to use it. Now for 2.5, I'm trying to build some test modules that use ALL the new functionality - so I have a point of reference.

Am I then also right in thinking that I can't mix 'Globals' with IMPORT FGL? As when I tried that, fields in GLOBALS 'someGlobalsFile.4gl' didn't appear to be picked up and then caused undefined reference errors.

Now, I have quickly changed the 'globals' files to be 'PUBLIC' define - and then IMPORTED those files and it seems to then work fine.

Is that the way forward now - Globals is replaced with IMPORTED 'PUBLIC' definitions?

Finally, if I go down the route of IMPORT FGL, I can get a list of the modules to be imported via the --print-imports, but that seems to have two slight draw backs:
The first is that it seems to give me the names of the missing modules (obviously), NOT the encompassing 4gl they may be in.
Secondly, using libs was easy, as the modules got picked up automatically - all that was needed was to build all the library modules into a lib and the linker did the rest. If something changed, then there was nothing to be done (ie function x is changed to call function y in a new .4gl)  - as again all that was required was to keep the lib up to date. Am I right in thinking now, that if the function x is changed, I have to identify each module that uses it and changed that 4gl to now IMPORT FGL the .4gl that includes function Y?
Sebastien F.
Four Js
Posts: 545


« Reply #3 on: March 04, 2014, 05:13:54 pm »

Stephen,

Ideally you should get rid of GLOBALS and 42x libs, using IMPORT FGL and PUBLIC/PRIVATE qualifiers. Each module should define its elements (types, constants, variables, functions). For better code reusability and readability, hide local module elements with PRIVATE qualifiers and expose what can be used by other modules with PUBLIC qualifiers. In fact IMPORT FGL is similar to the Java "import" instruction, to give you the big picture. With IMPORT FGL, you define a dependency between modules, and fglcomp can automatically compile all imported modules if needed.

I am not sure to get your problem with the x and y functions, but as long as you define properly the dependency tree (starting from the module containing MAIN), everything should be fine. Note also that we support mixing link and imports, to ease migrations.

Note that circular references are not supported yet and this can be an issue, you may need to re-orgnanize functions in different modules. And in some cases there is no solution.

For more details:

https://4js.com/online_documentation/fjs-fgl-manual-html/?path=fjs-fgl-manual#c_fgl_programs_IMPORT_FGL.html

Seb
Stephen T.
Posts: 114


« Reply #4 on: March 04, 2014, 06:02:11 pm »

OK. I think you answered the function x - function y issue - as from what you've said, each module contains its own IMPORT clauses.

Thanks again Seb.
Reuben B.
Four Js
Posts: 1126


« Reply #5 on: March 04, 2014, 11:16:56 pm »

Hi Stephen,

Your original question was discussed here https://4js.com/fjs_forum/index.php?topic=778.new&key=7a6687458834#new

... taking that example and making the suggested modification may help anyone else who has the same question.

With regards IMPORT FGL it has many other benefits.

With your existing code, you were going down the path of running --print-imports to find out what IMPORT FGL you need to add.  In time you will find that you don't need to do that because you added IMPORT FGL when you start modifying a piece of code, because by adding IMPORT FGL, you then get the benefits of code completion.  Type module-name <dot> and you get a list of public variables and/or functions 

If you go 100% down the path of IMPORT FGL, then the .42r becomes redundant, and you can go fglrun module-name.42m on any 42m that contains a MAIN. It also means if you need to change a function signature (number of parameters in/out), you can compile and deploy just the module and the modules that call it, rather than having to relink all the affected .42r's

Reuben

Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero
Stephen T.
Posts: 114


« Reply #6 on: March 05, 2014, 09:03:20 am »

Thanks Reuben.

I can see the benefits if starting from fresh - and building the IMPORT list as you go, but I was attempting to look at moving an existing muti-input dialog that had proven a problem for dialog before as two or three parts of the dialog had been split into external functions to enable them to be re-used. So it was a case of seeing how I could sew those bits back together again in a true 'DIALOG'.

Luckily, we had a convention here where all external function calls  were prefixed ext_ - so I can hopefully easily convert all the modules/functions to IMPORT and then see what I get.
Stephen T.
Posts: 114


« Reply #7 on: March 07, 2014, 11:32:11 am »

So far so good - I have converted all the external functions to work on the IMPORT FGL model, and only had one hitch with a cyclical reference. At the same time, I simply converted the 'external' global files to be 'PUBLIC' references. And that seems to hang together ok so far.

I do have a couple of questions though:

In some 'inputs', the mode ('add', 'update') was passed to the 'function' to control the fields accessible etc. I can see that I can set up a PUBLIC variable to now pass that same info into the external subdialog - BUT - at the moment it seems that I can't define PUBLIC within the subdialog itself - so have to define PUBLIC variables within the .4gl module that encompasses the subdialog. Is that correct? Also then is it envisaged that an external subdialog will always be in a .4gl module by itself?
IE say I have a module called subDialogs.4gl and within that I have a set of subdialogs - subdialog1, subdialog2...etc. Each of those subdialogs can 'receive' a passed parameter (mode) and 'return' some form of result (inputResult). At the moment I can only define PUBLIC subDialogs.mode NOT PUBLIC subDialogs.subdialog1.mode - ditto for subDialogs.inputResult.

In the same vein, if the subdialog then uses a PUBLIC variable to 'communicate' back to the main calling dialog (as in inputResult), it doesn't appear that I can have any 'procedural' statements around the SUBDIALOG call - ie I cannot have:
DIALOG
    SUBDIALOG subDialogs.subdialog1
    IF subDialogs.inputResult = 3 THEN
       ...
    END IF
    SUBDIALOG subDialogs.subdialog2


I'm not sure if either is/will be  a problem per se 'in the real world' (they appear more to be issues because of the restrictions of previously having to link multiple disparate inputs) - I'm just trying to get a feel for what I can and can't do.

 
Stephen T.
Posts: 114


« Reply #8 on: March 07, 2014, 12:50:30 pm »

I'm not sure if this is related to this topic.

In the past, we had comboboxes linked to 'standard' initialisers. So the form would have INITIALIZER=ext_init_combos for each relevant combo.

That function was 'external' - ie it did not sit inside the .4gl module that opened the form.

That function (ie ext_init_combos) has now been converted to use IMPORT FGL . From what I can see, the INITIALIZER was then never 'bound' to the module opening the form(pre IMPORT 4gl conversion and now after). I can only assume that the form 'found' the ext_init_combos function via an environment variable 'path'.

Now it seems that the ext_init_combos function does not get picked up at all (and I don't seem to see any error thrown), UNLESS I copy the contents of that function inside the module opening the form.

Is that a change based on the IMPORT FGL changes (stupidly I didn't check the system pre converting to IMPORT FGL) - or is it that simply 2.50 enforces different rules for locating the INITIALIZER function?
Stephen T.
Posts: 114


« Reply #9 on: March 08, 2014, 10:40:39 am »


A couple or so more questions:

The 'globals' were converted to PUBLIC defs - that was relatively straight forward as the vast majority of the usage was via 'global files' - so they were converted and IMPORTED. However, we do have a few locations where the full global definition files weren't used and individual globals were defined. That worked ok before (ie pre IMPORT and PUBLIC def). But that (obviously?) then didn't convert as intended. The 'globals' setter was ext_setGlobalEnv, that itself used base_globals.4gl - so a database environment variable was defined 'PUBLIC' in base_globals - ie base_globals.g_DBNAME. If then g_DBNAME was defined as a standalone 'PUBLIC' in another function - ext_open_database - then presumably that is in effect ext_open_database.g_DBNAME - and as such is a completely different variable. I can see a way around the issue (ie to split the 'base_globals' file into smaller chunks - so that all references to globals are via the same imported file. But am I using PUBLIC definitions correctly here?

This second point is/was more of an issue. The function above - ext_open_database - used the global (now public) variable g_DBNAME to open the database. That worked fine. Also, ext_open_database had 'standalone' globals that were converted to PUBLIC defs. g_DBNAME was then defined as a PUBLIC CHAR(40) and was actually set in ext_open_database by FGL_GETENV. Fine. That worked as expected. However, when the issue above was noted, the standalone PUBLIC def of g_DBNAME was replaced with the full IMPORT of base_globals. The database open then started to fail. Looking at the FGLSQLDEBUG output, the problem appears to be that g_DBNAME, if IMPORTed in an FGL, is treated as the name of the database, rather than a variable containing the name. If a local variable was set to the the PUBLIC variable and that used in the DATABASE statement, then it works fine. It just seems to object to using a PUBLIC IMPORTED def (see debug and ext_open_database extracts attached).

Thirdly, is there a way of getting DIALOG and SUBDIALOG names? I see that ui.dialog.nextField() can be used to move to fields (in conjunction with CONTINUE DIALOG). But I can't see where I can get the order of the DIALOGS/SUBDIALOGS and what fields they contain. The reason for the question is that if I want to 're-use' code, I will be using the same inputs for references, addresses, contact methods etc in multiple places. If the user leaves an 'array' by leaving a row blank say, then I want to move the user onto the first field (or last field) of the next/previous SUBDIALOG. Does that make sense? It seems that ui.dialog.nextField gives the way to code that, but I missed where I could find the fields and order of dialogs. Is that possible?
IE from the array, if the user is in the array and 'tabs' from the first field on a blank row, I want to move the user to the first input field of the next subdialog.
I think again, that I can work around this though by setting some public variables that contain the various 'next fields'.


Can I just say though that this is looking exceptionally good from my point of view. The migration of the code seems quite simple and the facilities offered by subdialogs (and the FORM re-use) look to be what's required. Good stuff. Thanks.
 

* ext_open_database.txt (1.77 KB - downloaded 1056 times.)
* SQLDEBUG.txt (1.5 KB - downloaded 1101 times.)
Sebastien F.
Four Js
Posts: 545


« Reply #10 on: March 09, 2014, 11:01:17 am »

Trying to help you on all the topics of your last 3 posts (you better open a separate thread for each subject)

Regarding passing/returning information from SUBDIALOGs, yes you must use PUBLIC module variables. No, you cannot make local declaractive DIALOG variables public (these are local to the DIALOG block, like variables defined in FUNCTIONs). You can also define functions beside the declarative dialog, to encapsulate module variables, and add some code arround. No you cannot execute procedural code inside a procedural DIALOG block (the IF subDialogs.inputResult = 3 THEN example)

Modules defining combobox initializers must have been loaded to make the initializer work. You need to call another function from that module before the OPEN WINDOW / OPEN FORM + DISPLAY FORM instructions.

Regarding the "base_globals" case, to me this looks like a candidate for a configurable utility module, where you could define PRIVATE module variables, that can be set by PUBLIC module functions (to set the dbname for ex), and execution functions (like connecting to the database). Think encapsulation and modularisation:
PRIVATE DEFINE dbname VARCHAR(50)
PUBLIC FUNCTION set_dbname(n)
  DEFINE n VARCHAR(50)
  LET dbname = n
END FUNCTION
PUBLIC FUNCTION do_connect()
  DATABASE dbname
END FUNCTION

About the second point (g_DBNAME/FGL_GETENV), sorry but I don't get it. You better provide a little sample to reproduce the problem to let us identify if the behavior described is expected or is a bug...

Regarding the "next field" case:
Here again, a little sample would be appreciated to let us understand, here some remarks...
I think that you should design your dialogs so that you don't have to add particular code to jump to a specific field in a given condition.
The field navigation should be natural and implicit.
If the "always in edit mode" for record lists (INPUT ARRAY semantics) is not mandatory, consider using DISPLAY ARRAY with modification triggers (ON APPEND, ON INSERT, ON DELETE, ON UPDATE) - It's easier to deal with a DISPLAY ARRAY as with all special cases of INPUT ARRAY.

I think you should have a look at the latest version of the documentation that is under construction.
It has updated topics that will help you to better understand dialog programming.
https://intranet.4js.com/distrib/manuals/FOURJS/FJSONLY/trunk/fjs-fgl-latest-manual-html-draft/index.html#c_fgl_prog_dialogs.html
You need a EAP login to access this doc.

Seb
Sebastien F.
Four Js
Posts: 545


« Reply #11 on: March 09, 2014, 05:24:16 pm »

The link to the new doc was wrong in previous post, here the correct link:
https://4js.com/techdocs/fjs-fgl-manual/#c_fgl_prog_dialogs.html
Seb
Reuben B.
Four Js
Posts: 1126


« Reply #12 on: March 09, 2014, 10:28:19 pm »

Stephen said ...

Quote
In the past, we had comboboxes linked to 'standard' initialisers. So the form would have INITIALIZER=ext_init_combos for each relevant combo.

That function was 'external' - ie it did not sit inside the .4gl module that opened the form.

That function (ie ext_init_combos) has now been converted to use IMPORT FGL . From what I can see, the INITIALIZER was then never 'bound' to the module opening the form(pre IMPORT 4gl conversion and now after). I can only assume that the form 'found' the ext_init_combos function via an environment variable 'path'.

Now it seems that the ext_init_combos function does not get picked up at all (and I don't seem to see any error thrown), UNLESS I copy the contents of that function inside the module opening the form.

... to understand what was happening in the past, have a look at a .42r using od -c, or strings.  The .42r simply consists of a list of function-names and modules.  So at run time, to find a function, the runner looks first in the current .42m, and if it can't find it then it reads the .42r, looking up the function-name to find the module-name, and then using FGLLDPATH to load the .42m.

Because of the way, the optimizer works, it was possible for the initializer functions or some of the functions they call not be included in the .42r, and hence the advice from me over the years would be if you had...

INITIALIZER=ext_init_combos

... was to add the following code to force your initializer function into the .42r ...

Code
  1. IF 1=0 THEN
  2.   CALL ext_init_combos(ui.ComboBox.forName("dummy"))
  3. END IF

(the optimizer function isn't clever enough to say that that line of code is never executed)


Looking at your issue, I'm surprised that at run-time, the runtime doesn't do a similar search through the IMPORT FGL references looking for the initializer function.  However if there were no other calls to that IMPORT FGL reference, it may have been optimized away, and a quick test suggests that Sebs advise to "call another function from that module" is what is required.  However that is going to be tough if all that is in that module is combobox initializer functions.  So rather than adding a call to another function from that module, I'd suggest try adding a dummy call to your initializer function.  So ...

Code
  1. IF 1=0 THEN
  2.   CALL ext_init_combos(ui.ComboBox.forName("dummy"))
  3. END IF

Reuben

Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero
Reuben B.
Four Js
Posts: 1126


« Reply #13 on: March 09, 2014, 10:54:19 pm »


Quote
Regarding the "next field" case:
Here again, a little sample would be appreciated to let us understand, here some remarks...
I think that you should design your dialogs so that you don't have to add particular code to jump to a specific field in a given condition.
The field navigation should be natural and implicit.

Seb:

... this was raised during the EAP for multi-dialogs.  Have a look at Bz14858.

We were discussing the requirement for NEXT FIELD FIRST / NEXT FIELD LAST, and whether there would be a similar requirement with multi-dialog.

Stephen:

The tabIndex value should be accessible via the AUI Tree.  Sounds like you want to find the fieldname with a higher tabIndex value that is not in the current table.

Reuben

Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero
Stephen T.
Posts: 114


« Reply #14 on: March 10, 2014, 08:59:45 am »

Reuben/Seb,
The INITIALIZER - I am just trying to get at the changes to migrate to 2.50.
In the past the INITIALIZER was three functions down -  ie:
Program Module (Main) - ext_hotkey_combos - ext_reload_combos - ext_init_combos

That then appeared to 'work' - as we never had any issues with the form combo INITIALIZER not working.

Now, after migrating to IMPORT FGL, the structure is still the same - ie the main program calls ext_hotkey_combos which call ext_reload_combos which then finally calls ext_init_combos. That then isn't getting picked up by the form.

Are you saying that is correct? And that to get the INITIALIZER to be recognised I need to add the dummy call to all program ('main') modules?
Pages: [1] 2
  Reply  |  Print  
 
Jump to:  

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines