Tech News Back Issues Issue: 112802

Introduction to the Omnis Web Client: Part 5, Uploading, previewing and saving image files on the server using web client.

By Dr Caroline Wilkins
iB2B Systems Ltd

In the previous two 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 Paged Pane component that enables a user to enter records into a table from a remote form.

If you want to skip the stages in the previous newsletter, you can download the zip file containing RescuePet.lbs and RescuePet.df1. The numbering in the this issue follows on directly from the last issue.

In this newsletter, we will add a JPEG field to the remote form and extend the interface to include a facility to upload and preview image files from the client machine, using the remote form.

19. Tidying up

19.1 Earlier in the tutorial, we put numbered label components on each page of the remote form so we could check that the Sidebar was controlling the pages of the Paged Pane. Now we are going to tidy up a bit. Change the text of the label component on page 2 from '2' to 'Add New Dog'. If you change the fieldstyle property (Appearance tab) to ' ', you will then have the freedom to change the font and fontsize properties to whatever you prefer.
19.2. To get rid of the white background to the text label, change the backpattern property to 'X'. This will make the background transparent.
19.3 Select the labels for the data entry fields (use the shift button for multiple element selection) and set the background to transparent in the same way.
19.4 Resize and reposition the label elements to taste.

20. JPG field

20.1 Drag a JPG component from the Component Store onto the remote form (page 2 of the Paged Pane). Resize to taste.
20.2 Right click on the form and select Class Methods. We are not going to write any code, we just need to set up a variable to hold the image data. Create an instance variable called iImage of type Picture. Close the method editor.
20.3 Set the dataname property of the JPG field you have created to iImage.
20.4 Set the name property to 'Picture' We now have a field to hold the image file once we have uploaded it.

21. Form file component

Now we are going to add the component that enables users to upload files from their client machine, the form file component. The form file component can be used to write to the client machine or read from it. In this example, we will just be reading files.

21.1 Drag a form file component from the Component Store and position under the JPEG field created previously.
21.2 Set the icon id property as desired. I suggest k16x16, 1613 (IDE1 selection).
21.3 Set the text property to 'Upload Image File' and format the font using the properties on the Text tab. Close the remote form to save your progress. Re-open and test (Ctrl-T). You should find that you can click on the 'Upload Image File' button that you have just created and select a file for upload from your machine. Don't bother selecting a file just yet. You will notice that you can select a file of any format. In practice, we would often want to restrict the file types.
21.4 Go to the Form File tab of the properties for the form file component and set the typelist property to: 'JPEG Files (*.jpg),.JPG,JPEG' Note, each entry in the list has the following format: Description, Windows Extension, Macintosh file type
21.5 Close the remote form. Add whatever icon page is necessary for the icon you have chosen for the form file component. In the example above, that would be IDE1.
21.6 Test the remote form (Ctrl-T) and confirm that you are only able to select JPG files from your client machine now.

22. Displaying the image data

22.1 Confirm that the action property for the form file component is set to kFFRead.
22.2 Go to the events property for the form file component and check the evFileRead event to enable it.
22.3 Double click on the form file component and paste the code snippet below in as its field method. The code is designed to intercept attempts to upload files greater than 100 KB. This is a useful technique that will save your less technical users from lengthy and pointless uploads when large files are not desired. This is particularly applicable if you intend to resize the image file when it arrives at the server (as we do for the real RescuePet system).

On evFileRead ;; a file has been read
; display the filename, creator and type
  Calculate ivFileName as pFileName
  Calculate ivFileCreator as pFileCreator
  Calculate ivFileType as pFileType
  Calculate iImage as pFileData

  Calculate iImageSize as binlength(iImage)
  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}
    Calculate ivEventCode as pEventCode
    Do $cinst.$objs.Picture.$redraw()
  End If

22.4 Place a breakpoint in the method then test the remote form in your browser and try and upload a jpg file using the form file button you have built. You should find that iImage holds the picture data and the picture is displayed in the JPEG component on the form. At this stage, the data is all on the client. Next we will write it to the server.
22.5 In order to make sure that the event code for the form file component executes on the client rather than passing the image across the Internet to the Omnis Server, set the method above to 'Execute on Client' by right clicking on the method name in the left window of the method editor and selecting the appropriate option. If you don't do this, then the precautions you have taken to avoid uploading images greater than a certain size, in this case 100 KB, will be pointless.

23. Writing file to server

23.1 This bit is too easy. Just add the following couple of extra lines to the field method code behind the 'Enter' button:

Calculate lTempPath as con('C:\InetPub\wwwroot\rescuepet\images\',iDogRow.Id,'.jpg')
WriteBinFile (lTempPath,iImage)

The entire method behind the 'Enter' button should now look like:

On evClick
  Do iDogRow.$definefromsqlclass('Dog')
  Calculate iDogRow.Id as iId
  Calculate iDogRow.Name as iName
  Calculate iDogRow.Age as iAge
  Calculate iDogRow.Gender as iGender
  Calculate iDogRow.Size as iSize
  Calculate iDogRow.ShelterId as iShelterId Do iDogRow.$insert()

  Calculate lTempPath as con('C:\InetPub\wwwroot\rescuepet\images\',iDogRow.Id,'.jpg')
  WriteBinFile (lTempPath,iImage)

Obviously, you should choose your own location to store the image files. In this example, we store them to a path on the webserver where they can be shared with other web resources. This means we can upload images via web client and immediately display them on an html web page if needed.
23.2 Verify that your file has been written to your machine in the location specified and that the numbering of the file name matches the corresponding record that should have been written in the same process to the Dog table of the data file.

If all has gone to plan, your library should look something like the one you can download here: (Libraries & datafiles may need to be converted in Omnis.)



Mouse Event Examples

By David Swain
Polymath Business Systems

Last time we examined how tracking of mouse events and "right" mouse events can be enabled, what the event messages are in each of these families and some additional functions that help us further identify exactly what is happening with the mouse when one of these events occurs. Now it's time to have a little fun with this information!

evMouseEnter and evMouseLeave

Let's begin with the simplest subgroup: the mouse movement messages. These are triggered when the mouse crosses a boundary of a field with its mouseevents property set to kTrue (or this property could be turned on at the library level).

There is one use I make of this facility in nearly every application — and since I use this so universally, I even turn on the mouseevents property at the library level. In Omnis 7 I became very fond of the Help message command, which was used to put descriptive text in the status bar of a window as the mouse passed over a field. This facility was not put into Omnis Studio. Instead, we were given tooltips for window objects (known as "windowtips"). Now this is a personal thing, but I have this love/hate relationship with windowtips. On the one hand, I like being able to give the user information about the object the mouse is pointing at, but I can't change the font, font size or color of the things (they're yellow), and worst of all I can't control their position. It seems like they always pop up and cover some other important item on the window. So years ago I developed a way to convert window tooltips into help messages that appear in the status bar of the window instead of as tooltips. evMouseEnter and evMouseLeave are key players in this technique.

Since I want to use this throughout my application, I chose to centralize the processing involved by creating a "helper" Object class named "Status object", which I instantiate as a Task variable of object type named "status".

There are two methods in this Object class used for the help message chore. The first is named "$genmsg" (for "general message"). It has a single parameter named "msgString" ("message string") of Character type. It has only one line of code:

Do $topwind.$statusbar.$panes.1.$text.$assign(msgString)

This takes the string sent as a parameter and assigns it as the text of the first pane in the status bar of whatever window is currently on top of the window stack. Since only the top window receives "mouse enter" and "mouse leave" events, this is appropriate.

The other method in my object is named "$clear". It has no parameters and contains this one line of code:

Do $topwind.$statusbar.$panes.1.$text.$assign('')

This method simply removes any text from the first pane of the status bar of the top window. Now we have to determine how and when to invoke these methods.

Obviously, we want to invoke "$genmsg" when the mouse enters a field and "$clear" when the mouse leaves that field. It only takes a couple of lines of method commands to do this, but this is a lot of code to create if we decide to put it into the "$event" method of each field! So let's put it into the "$control" method of each window instead and save ourselves some work. As long as we don't trap these events at the field level, they will find their way up to the window's "$control" method even if a field is buried many levels deep in containers. Here is the code for the window's "$control method:

On evMouseEnter
  Do status.$genmsg($cobj.$tooltip)
On evMouseLeave
  Do status.$clear

Remember that "$cobj" refers to the object the directly received the event that is currently being processed for all levels of event handling. So "$cobj.$tooltip" contains the tooltip string for the object that the mouse just entered. Now turn off the showwindowtips property in the Omnis preferences (not the preferences at the library level) and see the difference!

If you created this code but it doesn't seem to work, there are three things to check:

  1. Is the object instantiated in the task within which the window is instantiated?
  2. Is there content in the tooltip property for the objects on the window(s) being tested?
  3. Is the mouseevents property value in the library preferences set to "kTrue"?

If all of these are so, you should have no problems.

evMouseDown and evMouseUp

This may or may not have practical use for you, but it illustrates a use of these two events. let's suppose that we want our users to be able to draw lines on a window instance. They would move the mouse to the beginning position for a line, press the (left) mouse button down, move the mouse to the end position for the line and then release the mouse button. If we can capture the position information for the "mouse down" and " mouse up" events, we should be able to add a line background object to the window using the "$add" method for the group of background objects for the current window ("$cinst.$bobjs"). The syntax for this method is:

$add(type,top,left,height,width,invisible,disabled) or $add(object) adds a new field or object to the window and returns an item reference to it

The type for a line is "kLine". The invisible and disabled parameters default to "kFalse", so we can ignore them in our experiment. That leaves us with the problem of determining values for top, left, height and width. With line objects in Omnis Studio, we can actually think of these "beginning y position", "beginning x position", "distance to ending y position" and "distance to ending x position" since a line object can have negative values for height and width.

To begin our project, let's create a new Window class and name it "sketchpad". Open the Method Editor for this Window class and create four instance variables of Long integer type named "x1", "x2", "y1" and "y2". Now we just need to work out how to populate these variables and how to cast the parameters for the "$add" method.

In the last article, we learned about the mouseover() function. We can determine the current "x" or "y" coordinate of the mouse by supplying the "kMHorz" or "kMVert" constants as the parameter for this function. These return the absolute position of the mouse within the current window instance. That is, containers are ignored. Also remember that the origin of our window coordinate system is the upper left corner, that "x" increases to the right and that "y" increases downward.

If we capture values for "x1" and "y1" for the "evMouseDown" event and "x2" and "y2" for the "evMouseUp" event, we will have our beginning and ending position coordinates. The height is simply the difference between the beginning and ending "y" values ("ending" minus "beginning" if you remember from geometry class) and the width is the difference between the beginning and ending "x" values. Since we have already turned on mouse events for our library, we only have to put the following code in the $event method of our window class and the test the result:

On evMouseDown
  Calculate x1 as mouseover(kMHorz)
  Calculate y1 as mouseover(kMVert)
On evMouseUp
  Calculate x2 as mouseover(kMHorz)
  Calculate y2 as mouseover(kMVert)
  Do $cinst.$bobjs.$add(kLine,y1,x1,y2-y1,x2-x1)

We do this in the $event method so that only "mouse down" and "mouse up" events that happen directly to the window (and not to field objects on the window) are involved. Notice that these events can occur to the window directly. If we wanted to "cover our bets" and insure that this would all work even if there are field objects in the way, we could put this exact code in the windows $control method as well.

If we wanted to be really clean in our coding, we could also include four method lines after the method line that creates the line object that resets the values of our four instance variables to zero. But since we only use these values in the "evMouseUp" portion of this method and we can't have an "evMouseUp" without first having an "evMouseDown", the code above will work just fine. The user can draw a line from any point on the window instance in any direction and a line will appear when they lift the (left) mouse button.

So far, so good. But what if we wanted to draw rectangles instead of lines? Rectangles can't have negative values for height or width like lines can, so we have to make some modifications to that last method line. For rectangles (and all other drawn objects except lines) we need to specify the upper left coordinates and teh absolute height and width. This means we have to use the minimum "x" and "y" coordinates for left and top and the absolute value of the distance between the beginning and ending coordinate along each axis. The result for a rectangle would then be:

Do $cinst.$bobjs.$add(kRect,min(y1,y2),min(x1,x2),abs(y2-y1),abs(x2-x1))

If we wanted to then perform other operations on our new object (like giving it something other than the default color), we should create another instance variable named "newObjRef" of Item reference type and set that as the Return parameter of the Do command. This gives us a notational reference to the new object and we can then apply any other property values we need.

evRMouseDown and evRMouseUp

The "right" mouse button is used in Omnis Studio and many other programs as the "context" button, but we can occasionally use it for other special purposes as well. Let's suppose we need to allow users of a specific window to resize the fields on that window. We could use the "right" mouse events to let them toggle the $selected runtime property of the fields they wish to resize. (Didn't know we could do that? Just watch!)

First, we need to enable "right" mouse events for the proper scope. We have a number of choices. We could turn it on for the entire library, but maybe we don't need it quite so broadly available. We could use notation to turn it on for the library reversibly (in a Reversible block) in the $construct method of our window, but maybe we only want this to apply to Entry fields. We could turn it on for each Entry field as we create the Window class at design time, but that seems like a lot of work. Why not turn it on for all Entry fields on the window instance as it constructs? We can do this with the following command line in the windows $construct method:

Do $cinst.$objs.$sendall($ref.$rmouseevents.$assign(kTrue),$ref.$objtype=kEntry)

To manage this for all affected fields in one place within the window, we can use the windows $control method for detecting and reacting to this event type. The event would only be passed up to $control from fields that can recognize the event, so we don't have to worry about the user changing the size of other field types.

We will also need another instance variable (actually, this could be a local variable if this were the only thing we used it for) of Item reference type that we will name "objRef". We can then use the mouseover() function, but this time with the "kMItemref" constant as its parameter value, to determine which object received the "right" mouse event and to populate "objRef". Once we have this information, we can easily toggle the value of the $selected property of the field that received the event. Here is the code we would put in the windows $control method:

On evRMouseUp
  Set reference objRef to mouseover(kMItemref)
  Calculate objRef.$selected as not(objRef.$selected)

Give it a try!

For my last example, let's consider how we might modify the use of a context menu by pointing it to an object other than the one that receives the context-click. Perhaps we have a number of radio buttons in a group box and want some action of the context menu to apply to the container even if one of the items inside the container is context-clicked.

We will need a few items to accomplish this. We need an instance variable in the window of Item reference type to point to the intended object. We need an accessor method for the window class so the context menu can query the window to determine to which object is should direct its action. The menu needs to be programmed to perform its actions on that object rather than on the object that received the context-click. And finally, we need a means of setting the value of the Item reference instance variable — and that's where the "right" mouse events come into play.

Create a new Window class named "contextTest" and place on it a group box with four radio buttons within the group box. Create an instance variable for the window named "objRef" and give it the Item reference data type. Create a Class method named "$getObjRef" and put the following line of code in it:

Quit method objRef

Since this is a public method (name begins with "$"), it can be addressed from our context menu. In the "$event" method of the group box, put the following two command lines:

On evRMouseDown
  Set reference objRef to $cobj

This puts a reference to the group box into "objRef" at the beginning of a context-click on the group box itself.

In the "$event" methods of each of the radio buttons, put the following lines of code:

On evRMouseDown
  Set reference objRef to $cobj.$container

This also puts a reference to the group box into "objRef" at the beginning of a context-click on any of the radio buttons

Now create a new Menu class named "contextMenu" (or something equally clever!). Create four lines labeled "Plain", "Chiseled", "Inset" and "Embossed". Give the Menu class an instance variable named "winObjRef" of Item reference type. In the "$construct" method of the class, put the following command line:

Do $cwind.$getObjRef Returns winObjRef

In each of the menu lines, put the following command line, but use the proper 3D effect constant (kFlat, kChisel, kInset or kEmbossed) to match the line title:

Calculate winObjRef.$effect as kFlat

Now go back to the window and set "contextMenu" as the contextmenu property value for each of the fields (including the group box). Open a test instance of the window and check it out!


Next time we will examine keystroke events. We'll have at least this much fun with those!

© 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.