Tech News Back Issues Issue: 012203

Introduction to the Omnis Web Client: Part 8, Updating table data and changing images

By Dr Caroline Wilkins
iB2B Systems Ltd

http://www.artificia.co.uk
caroline@ib2bsystems.com

In the previous newsletters, we created an Omnis library, an Omnis database, programmed a startup task that will open a session to that database and created a schema in the library to represent a table in the database. We built a web client application that consists of a Sidebar controlling the pagenumber of a PagedPane component that enables a user to enter records into a table from a remote form. We added a facility to upload JPEG images to the web server using the remote form. We then rationalized the number of instance variables by using the iRow.Field technique and cut out some unnecessary code.

Last time, we added a feature on the remote form application which displays data from a table in a Heading List and responds to user clicks of the Heading List by displaying row data in single line edit boxes.

In this newsletter, we will develop this further to perform updates of table data, and the viewing and changing of associated image files. If you want to skip the stages in the previous newsletters, you can download the zip file RPView.zip containing RescuePet.lbs and RescuePet.df1 (libraries & datafiles may need to be converted in Omnis).

The numbering in the this issue follows on directly from the last issue. Make sure that you either put your library and datafile in c:\RescuePet\ directory or change the path to the datafile in the Startup_Task $construct method:

Set hostname {C:\RescuePet\RescuePet.df1}

34. Update button

34.1 Add a new button to page 3 of your paged pane.
34.2 Set the text property to 'Update'
34.3 Enable the evClick event in the events property for the button.
34.4 Add code so you have the following behind the Update button:

On evClick
  Do iViewDogRow.$update(iViewDogRowOld) Returns lStatus

This will make Omnis locate the table record with data contained in iViewDogRowOld and replace that row with the data contained in iViewDogRow. Recall that you previously added code behind the headed list that assigned the selected row of the List into the iViewDogRow and iViewDogRowOld row variables. (The iViewDogRowOld variable was put there as a point of reference for the update method we are adding now.) The user will have been able to edit the contents of iViewDogRow since we have assigned datanames of the form iViewDogRow.Field to the fields on the form.

It is in this update method that we see one of the major benefits of using the iRow.Field approach to dataname allocation. If we had stuck to the idea of using separate instance variables for each field on the form, we would have to issue a series of Calculate commands in order to push the user data into the row we are trying to update. This is much more efficient.

35. Refresh table data

35.1 If you test the application at this stage, you will find that the database is actually updated with any changes you make to a particular record, but it doesn't show on the remote form until you click on the 'Dog Database' icon on the Sidebar. This is obviously not very satisfactory.

Looking at the code behind the Sidebar, you will see that the following code is executed when user selects icon 3:

Do $tables.T_Dog.$sqlclassname.$assign('Dog') Returns #F
Do iViewDogList.$definefromsqlclass('Dog')
Do iViewDogList.$select()
Do iViewDogList.$fetch(1000) ;; iViewDogList
Do $cinst.$objs.ViewDogList.$redraw()

We are going to need to call on this routine in order to perform a refresh of the headed list data. It would not be very good design to just copy and paste (although it would work). Instead, we should create a new Class method called refreshDogList.

35.2 Right click on Class methods in your method editor and select 'Insert New Method'. Name it 'refreshDogList'
35.3 Copy the code above into this new method.

35.4 Edit the code behind the update button you added in the previous section of this tutorial so that you have the following behind the Update button:

On evClick
  Do iViewDogRow.$update(iViewDogRowOld) Returns lStatus
  Do method refreshDogList

35.5 Save changes to remote form by closing it in design mode. Then open and test! The headed list should be refreshed with the updates you have made to data.

36. Tidying up

36.1 Return to the code behind the Sidebar and replace the block you have just copied into refreshDogList with the line:

Do method refreshDogList

This means that both the Sidebar and the Update button are calling on the same method to refresh the headed list. If you need to make any changes later on, it will just have to be done in one place. This sort of approach to organizing the code that you need in multiple places will streamline your application considerably and make it much easier to navigate, debug and enhance. Its value will become more obvious as your application grows in complexity.

36.2 Now that you have pushed the code that requires database interaction into its own method, you should now be able to set the method behind the Sidebar to client side execution. Right click on it and select "Execute on Web Client". The $event method should then turn pink.

The advantage of setting the event method of the Sidebar to execute on the client is that it will prevent the client from consulting the Omnis server whenever a user clicks the Sidebar. It isn't necessary for the client to call the server for many Sidebar clicks, as not all will require database interaction, so you can make the web client application appear to run a lot faster by cutting out unnecessary trips to the server in this way. If the user does click on the 3rd icon, then the call to the server will happen, but otherwise it will not. This sort of detail will make the difference between a web client application that is a joy to use and one that will drive your users to web rage!

37. View Image for row

37.1 Add a Picture component to page 3, the Dog Database, page of the paged pane.
37.2 Set the name property to 'ViewPicture'
37.3 In the method editor, create a new instance variable of Picture type called iViewImage
37.4 Set the new picture component dataname to iViewImage
37.5 Add a button to the form and set its text property to 'View Dog Picture'
37.6 Enable the evClick event for the button
37.7 Add the code below so the code behind the button looks like:

On evClick
  Calculate lTempPath as con('C:\InetPub\wwwroot\rescuepet\images\',iViewDogRow.Id,'.jpg')
  Test if file exists {[lTempPath]}
  If flag false
    Quit method
  End If
  ReadBinFile (lTempPath,iViewImage)
  Do $cinst.$redraw()

37.8 Save your remote form by closing it. Reopen and test the form. You should now find that if you click the 'View Dog Picture' button after selecting a row from the list, the corresponding JPG image is displayed. If this is not happening, first check that you do have images uploaded to the 'C:\InetPub\wwwroot\rescuepet\images' directory. If you need images to test with, please feel free to visit http://www.rescuepet.org.uk and download some! If necessary, place a breakpoint behind the 'View Dog Picture' button and step through the method. Establish whether the method is locating a valid image file and loading it into the iViewImage variable.

38. Change Image for row

38.1 Go to page 2 of your paged pane. Select the formfile component that you are using there to upload image files. Ctrl-C to copy it. Go to page 3 and paste it in. You may of course, put a fresh new formfile component on page 3, but this will be faster.
38.2 Change iImage to iViewImage on lines 6 and 8
38.3 Change Picture to ViewPicture on the 2nd to last line
38.4 The code behind the formfile component should look like:

On evFileRead ;; a file has been read on the server
  ; display the filename, creator and type
  Calculate ivFileName as pFileName
  Calculate ivFileCreator as pFileCreator
  Calculate ivFileType as pFileType
  Calculate iViewImage as pFileData
  Calculate iImageSize as binlength(iViewImage)
  Calculate iImageSize as iImageSize/1000
  If iImageSize>100 ;; i.e. 100 KB
    OK message File too big {The file you have selected is [iImageSize] KB. Please resize so that it is less than 100 KB before uploading}
  Else
    Calculate ivEventCode as pEventCode
    Do $cinst.$objs.ViewPicture.$redraw()
  End If

38.5 If you test now, you should find you are able to switch the image over for one from your hard disk. We need to be able to actually save this though, so....
38.6 Add a button
38.7 Enable evClick event for button
38.8 Set text to 'Save Dog Picture'
38.9 Add code so that code behind this button looks like:

On evClick
  Calculate lTempPath as con('C:\InetPub\wwwroot\rescuepet\images\',iViewDogRow.Id,'.jpg')
  WriteBinFile (lTempPath,iViewImage)

You should now be able to change the image associated with a particular dog record in the database.

You will notice that we have separated the display of row data from the display of images. We could have put the code behind the 'View Dog Picture' in the headed list evClick event. This would make a nicer interface, but would mean that the user would be downloading image files with each click on the headed list. The approach we have taken minimizes the transfer of image data. In the next newsletter, we will look at ways of improving the user interface so that images do not get accidentally.

If all has gone to plan, you should have a library that looks something like RPUpdate.zip (libraries & datafiles may need to be converted in Omnis) and a new feature on the remote form application which performs updates of table data and the viewing and changing of associated image files.

 


 

Item Reference and Field Reference Parameters

By David Swain
Polymath Business Systems
www.polymath-bus-sys.com
dataguru@polymath-bus-sys.com

A few weeks ago we received a request from a reader for an explanation of when we should use a Field reference parameter as opposed to an Item reference parameter in a method. Actually, these two are usually quite different, but it's easy to see how this might be confusing. First, a little background:

Parameter Metaphysics

A successful parameter pass is a partnership arrangement between the calling (sending) and called (receiving) parameters. The receiving parameter is a variable and it determines how the parameter pass is to take place based on its data type. So it is "in the driver's seat", so to speak, in determining how the parameter offered by the calling method is to be used in the called method. But the calling method has an equal responsibility to present the parameter being passed in the proper form expected by the receiving parameter.

There are two ways we can pass a parameter to a method: by value and by reference. In fact, this is how the parameter is received, so the calling method has no control over this aspect of the pass. We might better say that parameters are either "received by value" or "received by reference". The use of the word "reference" is a likely source of some of the problem in understanding here because we have a type of value in Omnis Studio that is called Item "reference".

The sending parameter still has some responsibility in the process, though. In setting up the sending parameter, we must understand how the receiving parameter intends to receive what the sending parameter intends to send and then we must "package" the sending parameter appropriately. Perhaps we should think in terms of a parameter being sent as expressed in the calling method and received as defined in the receiving method. We would then focus on how we define the parameter variable in the receiving method and how we express the parameter in the sending method.

"Normal" Data Types Including Item Reference

Parameter variables have the same data type options as other variables plus one special "data type" reserved for parameters alone. A parameter defined using a "normal" data type (one available to any other variable) will receive the parameter passed to it by value. That is, the act of passing the parameter works in essentially the same way as the Calculate command acts on its target variable (for most data types). The expression in the sending parameter is evaluated and the result is put into the receiving parameter variable.

Omnis Studio is very flexible in what it allows to be passed in this way. It does not have strict data typing and so can set a value of any data type based on a value of any other data type without generating an error — although the resulting value might be "empty" in some cases and this may cause errors further down the trail. To avoid unintentional and potentially hazardous "empty" values being received, we must take care in the way we express our sending parameters.

An Item reference parameter variable is just like any other "normal" parameter. The value it receives must be a valid notational reference to an item or it will be "empty" (or at least unuseable). While the passing of a non-valid notational reference will not generate an error, any attempt to use that parameter variable in the called method as the target of a Calculate command will result in an "Attempt to assign to an unset item reference" error. More on why the concern about the Calculate command a bit later...

Field Reference "Data Type"

If the parameter variable in the called method is given the special Field reference data type, the parameter in the calling location must be a variable. In the broadest sense of the term, this can include a notational path to a property of some object, but only value-holding notational items can be used in this manner. So notation strings that point to objects or object groups will not trigger an error, but cannot be operated upon through the Field reference parameter variable.

Only a variable can be passed to a Field reference parameter variable. If we try to use an expression as the sending parameter, an "Attempt to assign a calculation to a field reference parameter" error will be generated when the receiving parameter variable is instantiated. This includes attempts to send the name of a variable as a string (using the nam() function or enclosing the variable name in quotes).

When a variable is passed by reference, the parameter variable in the called method acts as an alias or an avatar for the variable passed from the calling method. That is, any action performed on the parameter variable is actually performed on the original variable from the calling method. Why would we do this? Tradition has it that when passing a parameter by reference, no significant RAM allocation is required by the parameter variable — even if it represents a variable with a very large value (like a long list). The parameter variable is simply a conduit to the real variable containing the value. I have not delved deeply enough into this to prove to myself conclusively that this is so, but switching to passing large lists to reports by reference rather than by value has cleared up more than one "out of memory" error in the past, so I suspect that this is true.

Another effect of passing parameters by reference is that it allows us to bypass the rules of "scope" and directly affect the value of an "out-of-scope" variable. For this reason, Object Orientation true believers sometimes find passing variables by reference to be distasteful because it "violates" certain OO rules regarding access to items in another object (but most of them do it anyway for practical reasons). Passing by reference pre-dates object oriented thinking, but is no less viable because it is "old". The feature is a part of Omnis Studio and it offers us the ability to affect the values of as many variables as we need in a calling method from a called method.

On to the comparison...

References to Variables

Now to confuse what I just explained above by being very precise in describing a special case! We can achieve what appears to be the same effect as "passing by reference" by passing a reference to a variable to an Item reference parameter variable. (Yes, I checked that sentence for syntactical correctness a few times...) Let me demonstrate this:

Suppose we have a Character variable named "variable" (scope does not matter here, except that this variable must be "in scope" for the calling method) and we have a parameter variable of Field reference type named "pVariable" in a method named "$subroutine". The method "$subroutine" contains this one line:

Calculate pVariable as 'Howdy'

If we execute the following command line from a calling method, variable will be populated with the string 'Howdy':

Do <notationToContainerOf Subroutine>.$subroutine(variable)

If we now change the data type of "pVariable" to Item reference, we only have to change our calling command line to the following to achieve the same result:

Do <notationToContainerOf Subroutine>.$subroutine(variable.$ref)

Rather than acting like the Calculate command, passing a parameter value to an Item reference parameter variable acts more like the Set reference command. We must make sure that the value being passed is a valid notational reference. We can then use the parameter variable in "$subroutine" in exactly the same way as when it was a Field reference parameter. The Calculate command does not affect the value of an Item reference variable, but affects the item to which that value resolves (which must be a "value-carrying" item like a variable or a property).

So what is the advantage of one technique over another? Not much. In either case the parameter variable "pVariable" in "$subroutine" is just standing in for the actual variable and scope is being bypassed. The biggest difference is in how Omnis Studio handles the parameter value on the calling side. The direct use of "variable" as the sent parameter requires only a 2 byte token (trust me on this for now...) while "variable.$ref" requires 10 bytes and a little more interpretation time.

I will post an example library on my web site at http://www.omnistraining.com/demolibs.html that contains this example.

Curious about how I can speak with certainty about the number of bytes in a token? Make sure you read the next article to see how we can explore this aspect of Omnis Studio!

 
© 2002-2003 Copyright of the text and images herein remains with the respective author. No part of this newsletter may be reproduced, transmitted, stored in a retrieval system or translated into any language in any form by any means without the written permission of the author or Omnis Software.
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7™ is a trademark of Omnis Software Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.