Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

VFP menus and XML
Rafael Copquin, September 1, 2006
The title sounds quizzical, doesn’t it? What has the Visual FoxPro menu system in common with XML? Not much. However, it is possible to use XML as a means to generate separate menus for every user. It consists of a series of case statements that would define pads, popups or menu bars, according to t...
Summary
The title sounds quizzical, doesn’t it? What has the Visual FoxPro menu system in common with XML? Not much. However, it is possible to use XML as a means to generate separate menus for every user. It consists of a series of case statements that would define pads, popups or menu bars, according to the status of certain boolean properties. If the properties were set to true, the corresponding lines of code would be “allowed” or “disallowed” if they were set to false.
Description
The title sounds quizzical, doesn’t it? What has the Visual FoxPro menu system in common with XML? Not much. However, it is possible to use XML as a means to generate separate menus for every user.

The idea occurred to me when I inherited a system written originally in VFP 6, containing a very cumbersome system of allocating permits and functions to the different users of the network. It consists of a series of case statements that would define pads, popups or menu bars, according to the status of certain boolean properties. If the properties were set to true, the corresponding lines of code would be “allowed” or “disallowed” if they were set to false. The more users the Company had, the more involved the process of authorizing menu items. As in all organizations, some people are allowed to see the full menu (generally managers), while others can only see a limited portion of it, usually the one that relates to their job duties.

So, faced with the prospect of maintaining that system and consistent with my generally lazy nature, I decided to change it and use a more manageable approach.

Outline of the process

A table containing the user names and passwords, plus a memo field to save the menus for each user in the form of an XML file, is basically all that is needed.

When a user logs in, and after verifying his identity and rights, the XML data contained in the memo field is retrieved and converted into a cursor. A scan through this cursor builds the menu, which is then attached to a top-level form.

Simple, huh? Yes, in theory, but, how do you get the XML data in the users table in the first place?

How it is done

As in many things Fox, there are different approaches to do this. But to me, the easiest way is to use the VFP menu builder and create the full menu with all possible pads, popups, subpopups and bars, as if it were to be used in the usual way.

However, instead of marking in the General Options the checkbox for ‘top-level form’, we leave it unmarked. Another thing we would have to care for is to name every pad by clicking on the Options button and writing a suitable name in the pad name textbox. We then generate the MPR. In this manner, the VFP menu builder will create a file with very few comments and using very few random names, which, when you analyze this mpr file, are obscure and difficult to understand.

What I mean is, if your pads are something like this:

Pads
Invoicing
Cust.accounts
Stocks
Prices
Tables
Utilities
Exit

by naming the pads in the Options sections, we force the builder to use our names, and not the random group of letters and numbers which the menubuilder obtains by using the sys(2015) function. So, in the above example, we should use names such as Invoices, custaccs,stocks,prices,tables, etc.

The menu generator uses the default name of _MSYSMENU to name the main menu bar, because the menu will supposedly replace the VFP standard menu which is shown in the VFP screen. But we are going to use a Top Level Form to display our menu and it would be advisable to use a more meaningful name such as MAINMENU or whatever you like. So we change all references to _MSYSMENU to MainMenu.

We get rid of all comment lines and two additional lines, the builder places at the very top:

SET SYSMENU TO
SET SYSMENU AUTOMATIC
We don’t need them, so we eliminate them.

Lastly, our code will probably contain things like this:

ON SELECTION BAR 2 OF ACCOUNTING ;
	DO _1vu0zdd89 ;
	IN LOCFILE("MYPROJECTS\MAINMENU" ,"MPX;MPR|FXP;PRG" ,"WHERE is MAINMENU?")
We must replace that line by someting like this:
ON SELECTION BAR 2 OF ACCOUNTING DO menuprocs with ‘accounting’
Menuprocs would be a prg containing a series of case statements that would call the necessary routines, such as for example:
Do case
      Case ....
      Case ....
      Case cOption = ‘accounting’
               Public oAccounting
               Do form accounting name oAccounting linked

Etc
Why do we do all of the above? The answer is that we want to generate a cursor that would contain every line of the menu file (MPR) in a single record. Therefore, we cannot allow line breaks or comment lines.

Don’t worry about spaces between lines, later on in this article you will see how we ignore empty lines.

In the end, we will have a ‘clean’ MPR file which we would turn into a cursor which will subsequently be converted to XML and finally saved in the users table.

Convert the MPR file into a cursor

Use in Select("auxilmenu")
    
create cursor auxilmenu ( mprmenu M )
Select auxilmenu
append blank
append memo mprmenu from mainmenu.mpr
After we save the mpr file into the memo field, we will proceed to read it line by line and turn it into a cursor.
Local i, cLine, nValue

nValue = 0

Use in Select("mastermenu")

Create Cursor mastermenu(  menuid I,;
			   theprompt C(100),;
			   allowed L;
			)
nValue = 0
Set Memowidth to 2000
Select auxilmenu
For i = 1 to Memlines(mprmenu)
    cLine = Alltrim(Mline(mprmenu,i))
    If Len(cLine) = 0         && this is where we get rid of empty lines
       Loop
    Else
       nValue = nValue + 1 
       Insert into mastermenu(menuid,theprompt,allowed) ;
       values(nValue,cLine,.t.)
    endif   
EndFor
We now have turned the menu into a cursor. And converting into XML is simple enough:
oXML = Createobject("XMLAdapter")
oXML.AddTableSchema("mastermenu")
oXML.ToXML("cXML")
Now, let us save this mastermenu into the users table:
Select theusers
Locate for Upper(Alltrim(username)) == 'MASTERMENU'
If Found()
   Select theusers
   replace themenu with cXML
Else
    Insert into theusers(username,themenu) values ‘MASTERMENU’,cXML)
EndIf
That was easy, wasn’t it? We now have the mastermenu, converted into a cursor, safely contained within the users table under the user name ‘MASTERMENU’

What do we do now with this cursor?

It is now possible to use this cursor and assign different functions to all users. The process consists of calling the mastermenu XML file from its “safe deposit” in the memo field, converting it back to a cursor, mark the “allowed” field with .f. for every function disallowed, convert it back to XML and save it in the memo field, this time under the user’s name.

Let us do it.

Assign the modified mastermenu to the user

Local cXML

Use in Select("mastermenu")

Select theusers
Locate for Upper(Alltrim(username))=='MASTERMENU'
If found()
   cXML = theusers.themenu
   XMLToCursor(cXML,'mastermenu',4)
   Update mastermenu set allowed = .t.  && in the master menu everything is allowed
   Go top in mastermenu
endif
We then show this cursor ('mastermenu') in a grid. For the particular user chosen (let’s call him George) we will mark as not authorized those bars, popups or even pads that he should not use. We do that by unclicking on the "allowed" field. If you see the code above, you will notice that all functions are allowed (update mastermenu set allowed = .t.), because in the mastermenu, every function is available.

In this manner, some functions will be allowed and others will not (this is the special case of the user George). We now convert this specific menu to XML, and save it in the George record memo field.

oXML = Createobject("XMLAdapter")
oXML.AddTableSchema("mastermenu")
oXML.ToXML("cXML")

Select theusers
Locate for upper(alltrim(username)) == ‘GEORGE’
If found()
   replace themenu with cXML
EndIf
Now that we have a specialized menu for the user George, we call it a day and go home.

How is the user’s menu generated?

The following day we have to tackle with the problem of generating this specialized menu every time George logs in.

So, after George logs in (use a login form for that), we pull the XML from his record in the "theusers" table, convert it into a cursor and iterate through all the records, building the menu.

We are going to use this menu in a top level form, and for the purpose of this example the TLF will be called "Main", called from the application starting program with:

Do form main name oMain linked
Read events
The following code should be in a method in the Top Level Form:
Select theusers
Locate for upper(alltrim(username)) == "GEORGE"
If found()
   cXML = theusers.themenu
   XMLToCursor(cXML,'mastermenu',4)
Endif
(in real life the word George above would be in a property, passed to the Main form from the login form)
Release Menus mainmenu extended

define Menu mainmenu bar in window (thisform.Name) 

Select mastermenu
Scan for allowed = .t.
     cOption = theprompt
     &cOption
EndScan

Activate Menu mainmenu nowait
That’s it! End of story.

Conclusion

I tried to simplify the process of assigning functions to users, only showing those parts of the menu that concern them, hiding from them the confidential or not necessary parts.

There is a lot more to be done with this approach, such as providing for modifications to the main menu and passing them to all users in the users table. Another permutation would be to give some users full modification rights and others just read only rights for the same routine. I think that all of this can be accomplished by adding specific fields to the menu cursor. After all, when a cursor is shown in a grid, marking or unmarking records is very easy.

Rafael Copquin, Estudio Copquin
Public Accountant and Certified Internal Auditor; Fox programmer since 1987, starting with FPD and upgrading to VFP. Vast experience in conversions from FPD to VFP, specialized in business and accounting systems. Member of the Microsoft Users Group of Argentina's Board of Directors as Treasurer.
More articles from this author
Rafael Copquin, April 1, 2006
This class is especially designed to build cursor adapters by simply passing a few parameters, such as table name, cursor name, cursor adapter object name, updatable or not, empty or not, complex or simple select statement.
Rafael Copquin, April 1, 2002
Those of us who have ever programmed FoxPro DOS applications, are marveled and at the same time overwhelmed with the possibilities of enhancement in the appearance, the performance and the different ways of doing the things that were difficult, or outright impossible to do in DOS, th...
Rafael Copquin, June 1, 2002
In the April 2002 issue, I showed the way to use a grid as a picklist, and in order to make the example clear and readily understandable, I did not worry about code reusability. The example simply showed how to program methods in the form containing the picklist grid in order to enable th...
Rafael Copquin, October 1, 2002
Background Early in my development experience, back in the days of Fox Dos, I had to develop a better way to make invoices than the one I had been using. What I had been doing was using the SCROLL statement to make the screen move upwards every time a new item was added. Whenever the user w...
Rafael Copquin, March 1, 2006
In October 2002 the UTMag published the first article on the subject of making invoices with grids. Ever since, I have received numerous emails from readers asking me how to save the invoice thus created in the server tables. This article explains how to use cursor adapters to save the data.
Rafael Copquin, July 1, 2006
When we are examining data from a table, using generally a grid control, even if we did it with a browse window, we are in reality viewing a small part of a, perhaps, enormous quantity of data. We are literally viewing only the amount of records that fit in the grid or the browse window. If we wa...
Rafael Copquin, August 1, 2002
Background Many VFP applications deal with accounting data in ways that, in my opinion, are not very efficient, from the program standpoint, or very clear to the user. An example of this is an account statement. Now, an account statement can be any accounting statement that portrays the tran...
Rafael Copquin, June 5, 2013
When Microsoft discontinued development of Visual FoxPro, many would-be writers on VFP issues stopped sending articles to the UniversalThread and as a result, no more articles on VFP were published, with a few exceptions. Over the years, I kept receiving requests from many readers for a continu...
Rafael Copquin, March 1, 2007
Accounting and computer systems should not be tied to any one language in particular. You can design a computer system to be written in Fortran, Pascal, Cobol, VFP or Visual Basic.NET. However, the love of my life as far as computer languages are concerned is Visual FoxPro and that is why the rest o...