Would like to pull part of an existing form and add it to current form

Started by Candy M., January 23, 2009, 09:16:20 PM

Previous topic - Next topic

Candy M.

I'll see if I can make sense with this question.

I would like to be able to take a Page of an existing
form and add it to the current form (a different form) in
the AUI tree and make use of it.

For example, I may have a customer master form
called customer and have a folder tab on
that form and the first page of the folder tab is
what I'm wanting to re-use.  Also, I have
a screen record that is associated with the fields
of that page.

Then, I may have several reports that have a CONSTRUCT
statement that uses that first page of the customer master maintenance.
Currently we are creating another .per, but duplicating
the section from the customer master.  Well, if you ever add
a field to customer master, then you would make changes
to the other report forms that use that customer as well which is time-consuming.

What I want to do is to extract that Page section from
that customer XML screen and add it to the report form
maybe as a second page of the report form.  Theoretically
I think I could do that, but what I would not have and
which I do not know how to add to the AUI tree is the
screen record for that page of the customer master.  I don't know
if that is possible. 

Does this make sense?  Has anyone else wanted to do that?

Thanks for any insight.

Reuben B.

Candy,

You are not the first person to have a similar issue.

You can use the pre-processor to achieve what you want.  Create a serier of .inc files corresponding to the layout/attributes/instruction section of the customer bit of the .per

Code (per) Select
-- customer_layout.inc
GRID
{
Customer Code    [cu01      ] Customer Name [cu02                       ]
Customer Address [cu03                      ]
                 [cu04                      ]
                 [cu05                      ]
                 [cu06                      ]
}
END


Code (per) Select
-- customer_attributes.inc
cu01 = formonly.customer_code;
cu02 = formonly.customer_name;
cu03 = formonly.customer_address1;
cu04 = formonly.customer_address2;
cu05 = formonly.customer_address3;
cu06 = formonly.customer_address4;


Code (per) Select
-- customer_maintenance.per
LAYOUT
&include "customer_layout.inc"
END
ATTRIBUTES
&include "customer_attributes.inc"



Code (per) Select
-- customer_product_sales_report.per
LAYOUT (TEXT="Customer Sales by Product Report")
FOLDER
PAGE (TEXT="Customer")
&include "customer_layout.inc"
END
PAGE (TEXT="Product")
GRID
{
Product   [sa01      ]
}
END
END
END
END
ATTRIBUTES
&include "customer_attributes.inc"
sa01 = formonly.product_code;



and to re-use the same CONSTRUCT for the customer in a number of 4gls

Code (genero) Select
--customer_construct.inc
CONSTRUCT BY NAME customer_sql ON customer_code, customer_name, customer_address1,  customer_address2, customer_address3,  customer_address4
   # add zooms here
END CONSTRUCT   



Code (genero) Select
--customer_product_sales_report.4gl
  DIALOG
      &include "customer_construct.inc"
      CONSTRUCT BY NAME product_sql ON product_code
      END CONSTRUCT
      ON ACTION accept
         EXIT DIALOG
      ON ACTION cancel
         EXIT DIALOG
   END DIALOG


Hope that helps,

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

Candy M.

Thanks Reuben,
       That will help.
       But I still would like flexibility of doing in DOM tree.

Candy

Candy M.

Reuben,
     I tried that but there are some things I don't understand with the
preprocessor.  Say my file is "customer.per" and I have an error in
"customer_layout.inc".  When I issue the command fglform customer.per
it doesn't place errors in customer.err.  They are placed in a file with
the text on next line and .err.  Say if next line is GROUP, it creates
an  error file group.err with no errors in it.  I finally issued the
command fglform -M customer so I could see the errors.  If someone
could explain what is going on, I'd appreciate it.

Candy

Reuben B.

Quote from: Candy McCall on January 30, 2009, 02:02:56 AM
Thanks Reuben,
       That will help.
       But I still would like flexibility of doing in DOM tree.

Candy

There is nothing stopping you using the DOM/XML classes in Genero to create a .42f at run-time and then loading it with OPEN WINDOW WITH FORM.  I did something very similar with the .4tb, .4tm, .4ad so that I had a consistent File/Edit/Help pattern in topmenu and a similarly consistent toolbar. 

The problem is, as alluded to in the CONSTRUCT thread, is that you can't modify the matching 4GL dialog statement at run-time.  So whilst at run-time you can create your report form files (.42f) to include what is in the customer maintenance form as one folder page, what you can't do at run-time is create/modify the input/construct to match.
Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero

Reuben B.

Quote from: Candy McCall on January 30, 2009, 04:20:59 AM
Reuben,
     I tried that but there are some things I don't understand with the
preprocessor.  Say my file is "customer.per" and I have an error in
"customer_layout.inc".  When I issue the command fglform customer.per
it doesn't place errors in customer.err.  They are placed in a file with
the text on next line and .err.  Say if next line is GROUP, it creates
an  error file group.err with no errors in it.  I finally issued the
command fglform -M customer so I could see the errors.  If someone
could explain what is going on, I'd appreciate it.

Candy

That doesn't look right, mind you it has been a while since I used an error file,  I tend to use Studio these days and when I do get to a command line I tend to use -M .  A similar thing happens with fglcomp.  I'll raise a support call.


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

Reuben B.

Quote from: Reuben Barclay on February 02, 2009, 03:46:26 AM
Quote from: Candy McCall on January 30, 2009, 04:20:59 AM
Reuben,
     I tried that but there are some things I don't understand with the
preprocessor.  Say my file is "customer.per" and I have an error in
"customer_layout.inc".  When I issue the command fglform customer.per
it doesn't place errors in customer.err.  They are placed in a file with
the text on next line and .err.  Say if next line is GROUP, it creates
an  error file group.err with no errors in it.  I finally issued the
command fglform -M customer so I could see the errors.  If someone
could explain what is going on, I'd appreciate it.

Candy

That doesn't look right, mind you it has been a while since I used an error file,  I tend to use Studio these days and when I do get to a command line I tend to use -M .  A similar thing happens with fglcomp.  I'll raise a support call.


Hang on, I don't get the same behaviour you are experiencing.  Perhaps create a simple example and send in a support call stating version numbers etc.  I would expect the behaviour to be such that tools such as vim -quickfix can edit the appropriate file.
Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero

Candy M.

Reuben,
    I e-mailed 3 different test cases to Support on Friday.  One of the cases was a segmentation fault.
Candy

Candy M.

I'm using a screen record in the CONSTRUCT so maybe if
I don't use that, I could maybe accomplish what I want.

Candy

Candy M.

If I attempt to do this, I see a <RecordView > tag in the form and I'm thinking I would
need to add that to the DOM tree in order for it to communicate with the CONSTRUCT statement.

Candy

Bernard M.

Hi Candy,

I guess you need to include a file in the LAYOUT section to generate the wrong behavior.
This has been filled as new bug #Bz.11931.

Regards,
Bernard

Reuben B.

Here is another way of achieving this.  This takes 2 .42f form files, reads them both, and inserts one of them as a folder page into the other form file.   I've hard-coded it a bit and there is a number of changes I'd make before putting it in production but hopefully gives you an idea of the potential.


Code (genero) Select

-- customer2.4gl
MAIN
   DEFER INTERRUPT
   OPTIONS INPUT WRAP
   OPTIONS FIELD ORDER FORM
   
   MENU ""
      COMMAND "Maintenance"
         CALL do_maintenance()
      COMMAND "Sales Report by Customer and Product"
         CALL do_report1()
      ON ACTION close
         EXIT MENU
   END MENU
END MAIN

FUNCTION do_maintenance()
DEFINE customer_code CHAR(10)
DEFINE customer_name CHAR(30)
DEFINE customer_address1 CHAR(30)
DEFINE customer_address2 CHAR(30)
DEFINE customer_address3 CHAR(30)
DEFINE customer_address4 CHAR(30)

   OPEN WINDOW maint WITH FORM "customer2_maint"
   INPUT BY NAME customer_code, customer_name, customer_address1,  customer_address2, customer_address3,  customer_address4
      ON ACTION accept
         EXIT INPUT
      ON ACTION cancel
         EXIT INPUT
   END INPUT
   CLOSE WINDOW maint
END FUNCTION

FUNCTION do_report1()
DEFINE customer_sql, product_sql STRING

   OPEN WINDOW report1 WITH FORM build_report_formfile("customer2_report1", "customer2_maint")
   DIALOG
      CONSTRUCT BY NAME customer_sql ON customer_code, customer_name, customer_address1,  customer_address2, customer_address3,  customer_address4
      END CONSTRUCT 
      CONSTRUCT BY NAME product_sql ON product_code
      END CONSTRUCT
      ON ACTION accept
         EXIT DIALOG
      ON ACTION cancel
         EXIT DIALOG
   END DIALOG
   DISPLAY SFMT("%1 AND %2", customer_sql, product_sql)
   CLOSE WINDOW report1
END FUNCTION



# include file2 as a folderpage in file1
FUNCTION build_report_formfile(file1, file2)
DEFINE file1, file2 STRING
DEFINE dom1, dom2 om.domdocument
DEFINE root1, root2, folder1, page1, form2, grid1, recordview1, recordview2, link1, link2 om.DomNode
DEFINE folder_list1, form_list2, recordview_list1, recordview_list2 om.nodelist
DEFINE filename STRING
DEFINE i INTEGER

   LET dom1 = om.DomDocument.createfromxmlfile(SFMT("%1.42f", file1))
   LET dom2 = om.DomDocument.createfromxmlfile(SFMT("%1.42f", file2))
   LET root1 = dom1.getdocumentelement()
   LET root2 = dom2.getdocumentelement()
   
   LET folder_list1 = root1.selectbytagname("Folder")
   IF folder_list1.getlength() > 0 THEN
      LET folder1 = folder_list1.item(1)
   ELSE
      -- Handle errors better
      DISPLAY "1"
      EXIT PROGRAM 1
   END IF
   LET page1 = folder1.createchild("Page")
   CALL page1.setAttribute("text", "Customer")
   LET grid1 = page1.createChild("Grid")
   
   LET form_list2 = root2.selectbytagname("Form")
   IF form_list2.getlength() > 0 THEN
      LET form2 = form_list2.item(1)
   ELSE
      -- Handle errors better
      DISPLAY "2"
      EXIT PROGRAM 2
   END IF
   
   LET recordview_list1 = root1.selectByTagName("RecordView")
   IF recordview_list1.getlength() != 1 THEN
      -- Handle errors better
      DISPLAY "3"
      EXIT PROGRAM 3
   ELSE
      LET recordview1 = recordview_list1.item(1)
   END IF
   
   LET recordview_list2 = root2.selectByTagName("RecordView")
   IF recordview_list2.getlength() != 1 THEN
      -- Handle errors better
      DISPLAY "4"
      EXIT PROGRAM 4
   ELSE
      LET recordview2 = recordview_list2.item(1)
   END IF
   
   -- Copy each link
   FOR i = 1 TO recordview2.getchildcount()
      LET link1 = recordview1.createchild("Link")
      LET link2 = recordview2.getchildbyindex(i)
      CALL copy_node(link2, link1,1)
   END FOR
   
   -- Copy the form layout contents
   CALL copy_node(form2.getChildByIndex(1), grid1,1)
   
   LET filename = SFMT("my_generated_formfile.42f",FGL_GETPID())   
   CALL root1.writeXml(filename)
   
   RETURN filename
END FUNCTION

-- copy attributes and children of node n1 to node n2
-- displacement is to add them at the end of the existing fields defined in the form
FUNCTION copy_node(n1, n2, displacement)
DEFINE n1, n2, c1, c2 om.DomNode
DEFINE displacement INTEGER
DEFINE i INTEGER

   FOR i = 1 TO n1.getattributescount()
      CASE n1.getattributename(i)
         WHEN "fieldId"
            CALL n2.setAttribute(n1.getattributename(i), (n1.getattributevalue(i)+displacement) USING "<<&")
         WHEN "fieldIdRef"
            CALL n2.setAttribute(n1.getattributename(i), (n1.getattributevalue(i)+displacement) USING "<<&")
         WHEN "tabIndex"
            CALL n2.setAttribute(n1.getattributename(i), (n1.getattributevalue(i)+displacement) USING "<<&")
         OTHERWISE
            CALL n2.setAttribute(n1.getattributename(i), n1.getattributevalue(i))
      END CASE
   END FOR
   FOR i = 1 TO n1.getchildcount()
      LET c1 = n1.getchildbyindex(i)
      LET c2 = n2.createChild(c1.getTagName())
      CALL copy_node(c1, c2, displacement)
   END FOR
END FUNCTION


Code (per) Select

-- customer2_maint.per
LAYOUT
GRID
{
Customer Code    [cu01      ] Customer Name [cu02                       ]
Customer Address [cu03                      ]
                 [cu04                      ]
                 [cu05                      ]
                 [cu06                      ]
}
END
END
ATTRIBUTES
cu01 = formonly.customer_code;
cu02 = formonly.customer_name;
cu03 = formonly.customer_address1;
cu04 = formonly.customer_address2;
cu05 = formonly.customer_address3;
cu06 = formonly.customer_address4;


Code (per) Select

-- customer2_report1.per
LAYOUT (TEXT="Customer Sales by Product Report")
FOLDER
PAGE (TEXT="Product")
GRID
{
Product   [sa01      ]
}
END
END
END
END
ATTRIBUTES
sa01 = formonly.product_code;

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

Candy M.

Thanks, Reuben.  I will look at that.

One of the things I did try today is that in the report form, on the second folder tab,
I had basically an empty page.

I opened the report form, then read in another form which contained the page
I wanted to extract.  I used the replaceChild() method and the program complained
that I was inserting a node where it didn't belong or something like that. So I didn't get
very far. 

Thanks for this idea.  I will look at it.

Candy

Rene S.

Hi,
especially since the introduction of folders many real-live per files tends to be overcrowded. The problem with those overcrowded forms becomes more critical since having the DIALOG statement. That's why the request of modularizing forms sounds very important.

The proposed solutions - using the preprocessor, merging two forms at runtime via dom - have one important limitation: all form fields (and of course all other objects defined in the different sources) will appear at runtime within the same scope.

There is another suggestion. Imagine a new form-item in the per file is a placeholder for a 4gl-window. This would instruct the runtime to render the new window into this rectangle. No new 4gl instruction is required. The 4gl-programmer can use the CURRENT WINDOW  statement to select the "sub" window of his choice. This solution would help to split rich forms into logically units fitting the program logic.

This code fragment could define placeholders for two 4gl-windows:
Code (per) Select

GRID
{
<W window1            >

<W window2            >

}
END


After displaying this form, a OPEN WINDOW window1 WITH FORM "foo" would merge at runtime the form "foo" into the "master" form. A TOOLBAR  or a TOPMENU defined in the "foo" form could replace temporarily  (or be merged into) the TOOLBAR or TOPMENU defined in the "master" form if the WINDOW window1 is the current window.

From my perspective this solution is easy to understand and very transparent. BTW, this would also help when migrating from the text-ui to the graphical-ui: If a programmer expects to open two or more windows at a given position, he can use this solution to reach the target with (hopefully) less amount of work.

Rene


Bryce S.

Hi,

It's good to see this topic raised again - when I started using Genero it was one of the first things I wanted to be able to do (Search for thread: "Is it possible to open a group from one form inside another form?" « Reply #6 on: February 28, 2008, 08:53:12 PM ») so as I could reuse the form elements from common modules in the main program window when those modules got called, instead of popping up a new window.  I ended up cloning the module's form sections (at the group level under the layout section with matching attributes  section) into the main program form to have it look good for a couple of programs, but this is not very good for maintenance so of limited use.

Reuben indicates it can be done with the pre-processor, but I can't really get my head around this and it seems complicated. So maybe Fourjs can come up with a friendly method to achieve this in the code? It seems like such an obvious thing to want to do once you get into the way Genero can display forms and hide/unhide groups, etc...

Regards,
  Bryce Stenberg.