Omnis Technical Note TNGI0022 December 2007

Re-usable Context Menus

For Omnis Studio 4
By Andreas Pfeiffer

A basic principle in programming is called KISS: "Keep it simple and short!" This sounds easy in principle, but harder to achieve. Often a problem is created by employing the wrong approach, so if you start with the idea of keeping your code short, in standardised, re-usable bits then you stand more chance of success. 

A good example of creating standard re-usable objects could be context menus. Of course there are ways in Omnis to make such menus that are dynamic and universal to use, but unless you design them in a standardised way your code can quickly become very complicated. You should always imagine that another developer may have to maintain your code at a later date, so again you should keep it simple and add comments to your code. If you build a menu dynamically, it may be difficult for another programmer to identify how such a menu will look at runtime and, more importantly, how it will behave and which methods are executed. A second reason to keep things simple is for usability. If, for example, certain menu lines regularly change or are removed the end-user may be confused or irritated. It may be more important for context menus to have a certain continuity, so when a certain option is not required it is disabled rather than being removed. 

So how can we make context menus that can be re-used in many places but still write a minimum of code? 

First think carefully about where you have to program a certain function. You may tend to put functionality into a menu, but the object you're right-clicking on, such as a list, probably contains all the logic or "knowledge", plus the current task, to perform the required action. Therefore, rather than embedding the logic in the menu, it would be easier and more elegant to create a public method in the list itself which can be triggered from the context menu. 

As an example, you can create a method in the headed list box called $deleteSelectedLines - this can be added directly below the $event method of the list object. This method will delete just the selected lines of the list "ivDataList" and redraw the list object. This is quite simple as we are in the window and not in the context menu: 

Code for the $deleteSelectedLines method: 

Do ivList.$remove(kListDeleteSelected)
Do $crecipient.$redraw()

Note that $crecipient is used and not $cobj, because $cobj would refer to the object which includes the event - in this case not the list box which is the recipient and therefore can be referenced with $crecipient. 

Now you only need one line of code, for the call, to be included in the $event() method of the appropriate line in your context menu. You can use $ctarget to refer to the Headed List Box: 

Do $ctarget.$deleteSelectedLines()

So we have been successful in keeping our method very short and simple.

To deactivate lines in the context menu

It would be a good idea to deactivate any menu lines that are not required, that is, you can set the $enabled property for a line to kFalse to disable the menu line. In the $construct method of the context menu you could simply check in the Headed List Box whether there is such a method.

$construct() method of the context menu:

Do $cinst.$objs.delete.$enabled.
  $assign($ctarget.$methods.$findname('$deleteSelectedLines'))

You can use the $findname method to find the method called "$deleteSelectedLines" and therefore whether or not it exists in the target object. If it is found the method gives a reference back which, in this case, is interpreted as kTrue. If there is no method with this name behind the object the list line will be disabled (and grayed out). 

However you may want to include a user authorisation, or it might not be appropriate - as with the method $deleteSelectedRecords - to activate the menu line if perhaps no lines in the list are selected. Therefore it is more logical to leave this task to the Headed List Box and to place an additional public method behind the List Box. This way you can effectively control whether the line in the context menu should be activated or not.

Code for $isDeleteSelectedLines() method behind the list object: 

Quit method totc($cinst.ivDataList,#LSEL)

In this case kTrue is returned if the number of selected lines is greater than zero, that is, if at least one line is selected. 

In the $construct method of the context menu, the appropriate menu line for this method is now: 

Do $cinst.$objs.delete.$enabled.
  $assign($ctarget.$isDeleteSelectedLines())

Of course you could have used the method $anyLinesSelected() alternatively, but this would prevent you from the option to add additional access protection in the $is.. method. 

Specialising by inheritance

With this approach it should be easy for you to create a multi-functional context menu for the list handling. But there may be list objects for which you want to use additional lines in the context menu which are special to these objects. For this purpose, you can use the menu so far described and differentiate between the different cases. You can add additional list lines and process them as described above to call the methods behind the list object or to deactivate the lines. 

Summary

The inspection of the objects as such and the well directed messaging allow us to write short and powerful methods which are easy to maintain and become re-usable for a variety of purposes. Therefore, every Omnis developer should be "alarmed" when they realise they have written a method with more than 20 lines of code - very often (but not always), you can find a better solution for this by employing shorter, easier to understand methods in the right places.