Quantcast

MS Access - Browser Style Form Navigation

(No Ratings Yet)
Loading ... Loading ...

If you're new here, you may want to subscribe to my RSS feed. So that you can read the latest updates about Web2.0 tools, Making Money Online, Tips in SEO, Ajax and many more. Thanks for visiting ProgramimiCOM!

My last couple of articles have featured a demo app affectionately named Something Not Entirely Unlike Access. The application employs a variety of methods to obfuscate the “Accessian” features. In our last article, we discussed how to automatically resize subforms, the way some browser frames work. This month, we’ll tackle navigation.

Once again, the download is the same as last two month’s. The screen shot below displays the main form in design view, and you’ll notice the Internet Exploreresque Forward and Back buttons in the upper left corner. Programming the logic behind their navigation is this month’s project.

The Easy Piece

Let’s get the easy piece out of the way. The form has at least three buttons: one to navigate back, one to navigate forward, and a Home or Start Page button. The images were easy to create using MSPaint and a screen shot of Internet Explorer. Once you have an image you like, just assign it to the [Picture] property of the button.

As shown in the screen shot, I’ve used the technique whereby I assign a public function to the OnClick event. (A public sub should work in newer versions of Access, but I believe even Access 2000 would not recognize the assignment unless it was a Function, not a Sub.) So by clicking the back button, a function on frmMain called MainNavBack() is executed. The code for these functions, MainNavBack() and MainNavForward(), is shown below.

Each of these functions calls another function on frmMain named LoadMainSubform(). This process was described in our first article, which introduced the Something Not Entirely Unlike Access application. Accordingly, we won’t go into detail on what this function does, but suffice it to say that, given the name of a form, it loads and resizes that form into the single subform object on frmMain, effectively changing pages.

This month’s trick will be logging page visits and then determining the correct page to load when users click Back or Forward. To do this, we create and implement our own navigation class, named clsNavigation. We’ll show the code for the class a little later, but first let’s show how it’s used. Implementation in the form requires these steps:

  1. Declare a module level object of type clsNavigation using the NEW modifier.
  2. Call the object’s Load method in the form’s Open event.
  3. For each new page, call the object’s AddNavPage method to log the visit.
  4. Create a function to call the object’s NavPrevPage method to find the previous page.
  5. Create a function to call the object’s NavNextPage method to find the next page.

That’s all we have to do on the form. Our work is done here. The real logic exists in the class, clsNavigation. So long as you have the above five steps implemented correctly in the form, everything else depends on the class module, which you may freely import into your application from the download code. You’ll need to tweak it a little to get it to work, but it’s all there, and more.

dl_browsernav_image001.jpg

    ‘ First, create a module level navigation object of
    ‘ type clsNavigation.  (this class does not yet exist
    ‘ … we will be building it shortly.)
    Private m_objNav As New clsNavigation

    Private Sub Form_Open(Cancel As Integer)
        On Error GoTo Err_Handler

        ‘ Create class to manage navigation controls
        m_objNav.Load

        ‘ do other Form Open stuff here …
    End Function

    Public Function MainNavBack() As Boolean
        On Error GoTo Err_Handler
        Dim strPage As String

        strPage = m_objNav.NavPrevPage
        Call LoadMainSubform(strPage, False)
    Exit_Here:
        Exit Function
    Err_Handler:
        MsgBox Err.Description, vbCritical
        Resume Next
    End Function

    Public Function MainNavForward() As Boolean
        On Error GoTo Err_Handler
        Dim strPage As String

        strPage = m_objNav.NavNextPage
        Call LoadMainSubform(strPage, False)
    Exit_Here:
        Exit Function
    Err_Handler:
        MsgBox Err.Description, vbCritical
        Resume Next
    End Function

    Public Sub LoadMainSubform(ByVal sFormName As String, _
                               ByVal fLogNav As Boolean    )
        On Error GoTo Err_Handler

        ‘ Update the navigation object with the new page,
        ‘ unless the LogNav flag is set to False. This
        ‘ give you flexibility to skip logging for some pages.
        If fLogNav = True Then m_objNav.AddNavPage sFormName

        ‘ continue with process of loading form …
    End Function

Creating and Loading The Navigation Class

Technically, the Navigation class is created above in the declaration. When the module level variable, m_objNav, is declared with the New modifier, the class is instantiated and ready to use. Because it is a module level variable, it persists as long as frmMain is open.

To build the class, you need to select Class Module from the Insert menu option. A Class module is different from a Standard module, so be sure that all the code that follows goes into a Class Module. When the above-mentioned variable, m_objNav, is declared, an instance of the class is created. (Notice that the icon for a Class module is different from the icon for a Standard module.)

dl_browsernav_image002.jpg

While an instance of the class may exist in m_objNav, it can’t really do anything until the Load method is called. Loading the class does little more than create an in-memory ADO recordset, into which we will load our data. The code looks like this …

Option Compare Database
Option Explicit

Private c_rstNav As ADODB.Recordset
Private c_intCurrItem As Integer
Private c_intMaxItem As Integer
Private c_fMovedBack As Boolean

    Public Sub Load()
        On Error GoTo Err_Handler

        ‘ Instantiate the private level ADO recordset object, add
        ‘ as many fields as you’d like, and load the first record.
        Set c_rstNav = New ADODB.Recordset
        With c_rstNav
            .Fields.Append “ItemID”, adInteger
            .Fields.Append “Value”, adVarChar, 64
            .Fields.Append “CustomerID”, adVarChar, 5
            .Fields.Append “EmployeeID”, adInteger
            .Fields.Append “ProductID”, adInteger
            .Fields.Append “OrderID”, adInteger
            .Fields.Append “URL”, adVarChar, 512
            .Open

            ‘ Note that I have added various IDs to the recordset.
            ‘ These will be used to load the correct record.
            ‘ I always include functions to set and get these IDs.
            ‘
            ‘ This could be recoded to be more flexible, but since
            ‘ the demo app uses this format, I’ll let it be for now.

            .AddNew
            !ItemID = 0
            !Value = “frmStartPage”
            !CustomerID = GetCustomerID()
            !EmployeeID = GetEmployeeID()
            !ProductID = GetProductID()
            !OrderID = GetOrderID()
            .Update
        End With

    Exit_Here:
        Exit Sub
    Err_Handler:
        MsgBox Err.Description, vbCritical
        Resume Next
    End Sub

The class is now loaded. The private ADO recordset, c_rstNav, exists and contains a single, initial record. This recordset will persist so long as the class persists. The class persists as long as the form is open. So, we’ve created a little in-memory log book of the pages visited by our user.

Adding a Page

The next action we need to code is the adding of a page. Of course, we need to log the page’s name, but we’re also going to need some additional information. For example, if we just navigated to frmCustomer, we’re also going to need to know WHICH customer, that is, what was the CustomerID at the moment the form was loaded?

Once again, having considered my previous articles would be of benefit, but in a nutshell, here’s how I load forms: When the user double-clicks on, for example, the row of the Customers List Subform, the current row’s CustomerID is saved by means of the SetCustomerID() public function. When the form is loaded, it uses the GetCustomerID() method to determine which record to load. So, at the time the page is “navigated to,” the CustomerID is known and can be persisted in our navigation object. The same is true for EmployeeID, ProductID, OrderID or URL, if the form is loading a web page.

Now, I should be ashamed of myself for this clumsy and non-extensible code. It would have been much better to have only two columns: KeyFieldName and KeyFieldValue. These could be reused more efficiently. When the frmCustomer is loaded, the [KeyFieldName] would be set to the text “CustomerID” and the [KeyFieldValue] to its value. When frmEmployee is loaded, [KeyFieldName] would be “EmployeeID” and [KeyFieldValue] would contain the current EmployeeID. This would have been smart, but as it turns out, that’s not how the demo code works, so I won’t bother tweaking it now, but as you can see from the code below, it’s pretty simple to modify this navigation recordset. Add and/or remove fields as you wish. Play with it … it’s fun.

The AddNavPage() method has some tricks to it. First, the argument, sValue, must exist. This part of the code could be smarter too, by checking to see that a form actually exists by the name passed in sValue.

In order to behave as Internet Explorer does, the FlushForward method must be called each time a page is added. It basically resets the recordset to make the current page the last record. It’s like with IE (or any other browser) when you navigate back three or four pages, and then go to a new page. The browser dumps any knowledge of those pages and starts off on a new path, with the current page becoming the last in the string of pages.

Next the c_rstNav recordset object is manipulated to locate our starting point, the last ItemID and value in its set. The strLastValue variable is used to avoid adding rows for the same page multiple times. (The Refresh or Requery action of the form may trigger this method and we don’t need to log those events.) I’ve also added a condition to allow for logging of multiple pages so long as the form is frmIE, which is a browser control.

Once the record is successfully saved, we increment the class variables c_intMaxItem and c_intCurrItem, which will be used later when locating the requested navigation page. At this point, our page is logged in the recordset, and the class properties are set.

Public Sub AddNavPage(ByVal sValue As String)
        On Error GoTo Err_Handler

        Dim intLastItem As Integer
        Dim strLastValue As String

        If Trim(sValue) = “” Then
            Exit Sub
        Else
            Call FlushForward

            With c_rstNav
                If Not .EOF Then .MoveLast Else .MovePrevious
                If Not .BOF Then
                    intLastItem = !ItemID
                    strLastValue = !Value
                End If

                ‘ Always log IE browser pages.
                If sValue <> strLastValue Or sValue = “frmIE” Then
                    .AddNew
                    !ItemID = intLastItem + 1
                    !CustomerID = GetCustomerID()
                    !Value = sValue
                    !EmployeeID = GetEmployeeID()
                    !ProductID = GetProductID()
                    !OrderID = GetOrderID()
                    !URL = GetURL()

                    On Error Resume Next
                    .Update
                    If Err.Number = 0 Then
                        c_intMaxItem = c_intMaxItem + 1
                        c_intCurrItem = c_intMaxItem
                    End If
                End If
            End With
        End If

    Exit_Here:
        Exit Sub
    Err_Handler:
        MsgBox Err.Description, vbCritical
        Resume Next
    End Sub

Retrieving A Page

The code for NavNextPage is shown below, but NavPrevPage is nearly identical. All this code needs to do is locate the previous or next record in the recordset and extract the form name and associated IDs. This is where that class variable, c_intCurrItem, comes into play. The Find method is used with the c_rstNav recordset to locate c_intCurrItem. Once found, the values of the row are read and the form name is returned by the function.

    Public Function NavNextPage() As String
        On Error GoTo Err_Handler

        Dim strOut As String

        c_intCurrItem = c_intCurrItem + 1
        If c_intCurrItem > c_intMaxItem Then
            c_intCurrItem = c_intMaxItem
            strOut = “-1″
        End If

        With c_rstNav
            If Not .BOF Then .MoveFirst
            .Find “[ItemID]=” & c_intCurrItem
            If Not .EOF Then
                strOut = !Value
                SetCustomerID Nz(!CustomerID, “”)
                SetEmployeeID Nz(!EmployeeID, 0)
                SetProductID Nz(!ProductID, 0)
                SetOrderID Nz(!OrderID, 0)
                SetURL Nz(!URL, “http://www.amazecreations.com/datafast/”)
            End If
        End With

    Exit_Here:
        NavNextPage = strOut
        Exit Function
    Err_Handler:
        MsgBox Err.Description, vbCritical
        Resume Next
    End Function

Final Thoughts

In reviewing the code, I can’t help but think that I’ve over-complicated the process and over-simplified the explanation. That having been said, I still think this code may be of benefit to readers. It can be improved upon, it’s true and it will take some concentration to read and understand all that is taking place, but that’s what programming is about. It’s not perfect, but it’s a start.

by Danny Lesandrini

Auto-Resize Access Subforms In MS Access

(No Ratings Yet)
Loading ... Loading ...

In the last article we introduced the application we’re calling Something Not Entirely Unlike Access, which simulates some aspects of a web browser in Microsoft Access. This article will focus on the process of resizing subforms on the main form, and the download is the same as last article’s. The screen shot below displays four subforms:  two wide ones on the left, and two narrower ones on the right. In this example, all four have the same height, but as you’ll see, that too is adjustable.

sneua_th.jpg

Resize Subform Objects Code

Jumping right into the code, you’ll notice that every form includes a public function named ResizeControls() which accepts two arguments: lObjWidth and lObjHeight. This function is called by the PARENT form, and the values passed are determined by Form properties called InsideWidth and InsideHeight. You will need to adjust the Height by subtracting the Form Header and Footer space, as that is part of the inside Height. It looks something like this …

Public Const cGap As Long = 100

lngObjWidth = Me.InsideWidth - (cGap * 2)

lngHeadFoot = Me.Section(acHeader).Height + Me.Section(acFooter).Height
lngObjHeight = Me.InsideHeight - (lngHeadFoot + (cGap * 2))

The code (both above and below) references a constant named cGap. This is a global constant which is set once and used everywhere. It allows me to tweak the appearance, giving more or less space between objects with a single edit. (This public constant must be placed in a standard module or the main form module, so it is always available.)

The parent form, after loading the requested subform in the subform object, invokes the ResizeControls method, passing the appropriate width and height. If that subform has subforms, it simply repeats this process, determining the allotted space for each subform and invoking that subform’s ResizeControls property. While this process isn’t trivial, once you get used to it, writing the code becomes routine. Most of the important stuff happens on the ResizeControls() function. (See my comments inline with the code.)

Public Function ResizeControls
	(ByVal lObjWidth As Long, ByVal lObjHeight As Long) As Long
    On Error GoTo Err_Handler

    Dim lngWidthLeft As Long
    Dim lngWidthRight As Long
    Dim lngHeightLeft As Long
    Dim lngHeight As Long
    Dim lngHorOffset As Long
    Dim lngVerOffset As Long

    ‘ The following two public function calls
	‘ perform some standard formatting.
    ‘ The first one sets the forms colors, such as control font color,
    ‘ section back colors and the like.  The download includes this code,
    ‘ which is relatively generic.  The argument passed is the form
    ‘ itself (Me), to which the modifications are being made.
    Call SetFormColors(Me)

    ‘The code for setting the header controls is more involved,
    ‘ requiring some resizing.  Accordingly, this function is explained
    ‘ below.
    g_lngResult = SetHeaderCtls(Me, lObjWidth)

    ‘ This first step is a little tricky.  I wanted to account for
    ‘ scrollbars, but not every form has it’s Horizontal and/or vertical
    ‘ scroll bars set. So I created a function, GetScrollbarOffset(),
    ‘ which would determine how much space should be allotted.  The code
    ‘ for that is in the download file.

    ‘ Determine the control widths.
    ‘ In this example, I’m allotting 70% to the left side controls,
    ‘ and 30% to the right side controls.  I’m also allowing for the
    ‘ space of 2 gaps.
    lngHorOffset = GetScrollbarOffset(Me, “V”) + (cGap * 2)
    lngWidthLeft = (lObjWidth - lngHorOffset) * 0.7
    lngWidthRight = (lObjWidth - lngHorOffset) * 0.3

    ‘ Determine the controls heights.
    ‘ This is similar to the process above, except we must account
    ‘ for the header section.
    lngVerOffset = GetScrollbarOffset(Me, “H”) + (cGap * 2)
	+ Me.Section(acHeader).Height
    lngHeight = (lObjHeight - lngVerOffset) / 2

    ‘ This next section does the real work.  You must know the names of
    ‘ all your subform objects and you must set the LEFT, TOP, WIDTH and
    ‘ HEIGHT properties of each.  Finally, you need to call the
    ‘ ResizeControls() method of each of these subforms, so that they can resize
    ‘ their subforms … if they have any.  (For consistency, and simplicity, I
    ‘ make sure every form and subform has this public function, even if it doesn’t
    ‘ actually do anything.  That way it never fails when this call is made.

    ‘ NOTE:  The positioning is simple math.  You’ll have to work out the details
    ‘        for your application in a way that’s pleasing to you.  The following
    ‘        provides a working template of how it might be accomplished.

    ‘ Position objects and call resize functions
    Me!objEmployee.Left = cGap
    Me!objEmployee.Top = cGap
    Me!objEmployee.Width = lngWidthLeft
    Me!objEmployee.Height = lngHeight
    g_lngResult = Me!objEmployee.Form.ResizeControls(lngWidthLeft, lngHeight)

    Me!objCustomer.Left = cGap
    Me!objCustomer.Top = Me!objEmployee.Top + (lngHeight) + cGap
    Me!objCustomer.Width = lngWidthLeft
    Me!objCustomer.Height = lngHeight
    g_lngResult = Me!objCustomer.Form.ResizeControls(lngWidthLeft, lngHeight)

    Me!objProduct.Left = cGap + lngWidthLeft + cGap
    Me!objProduct.Top = cGap
    Me!objProduct.Width = lngWidthRight
    Me!objProduct.Height = lngHeight
    g_lngResult = Me!objProduct.Form.ResizeControls(lngWidthRight, lngHeight)

    Me!objOrders.Left = cGap + lngWidthLeft + cGap
    Me!objOrders.Top = Me!objProduct.Top + (lngHeight) + cGap
    Me!objOrders.Width = lngWidthRight
    Me!objOrders.Height = lngHeight
    g_lngResult = Me!objOrders.Form.ResizeControls(lngWidthRight, lngHeight)

Exit_Here:
    Exit Function
Err_Handler:
    MsgBox Err.Description, vbCritical
    Resume Next
End Function

Resize Header Controls Code

As you poke around in the sample application, you’ll notice that every form has an array of header controls: lblCaption and lblDescription and sometimes hyperlink labels named New, Edit and Delete. Again, for consistency, I try to include these labels on every form, even if they are not used. (You can set the properties of an invisible label, but you’ll get an error if you try to reference a non-existent control.)

Below is the code that is called from every ResizeControls() function. It takes three arguments: The calling form (by reference), a width and an optional comma-delimited string list of control names that should be formatted as hyperlinks. See inline comments for an explanation of the code.

Public Function SetHeaderCtls(ByRef frm As Access.Form,
                                   ByVal lWidth As Long,
                                   Optional ByVal sHyperLinks As String) As Boolean
    On Error GoTo Err_Handler

    Dim lngScroll As Long
    Dim strForm As String
    Dim strControls() As String
    Dim iCtl As Integer
    Dim ctl As Control
    Dim lngStartLblPos As Long
    Dim fLblCaption As Boolean
    Dim fLblDescr As Boolean
    Dim strCaption As String
    Dim strDescr As String
    Dim strCriteria As String

    ‘ Grab the form’s name … that will be required later.
    strForm = frm.Name

    ‘ If the form has a scrollbar, then deduct that from the width passed.
    lngScroll = GetScrollbarOffset(frm, “V”)
    lWidth = lWidth - lngScroll

    ‘ //////////////////////////////////////////////////////////////////////////////
    ‘ The sHyperLinks parameter is optional.  If missing, set it to empty string
    If IsMissing(sHyperLinks) Then sHyperLinks = “”

    ‘ //////////////////////////////////////////////////////////////////////////////
    ‘ When sHyperLinks exists, process the list of hyperlink labels.
    If Trim(sHyperLinks) <> “” Then
        strControls = Split(sHyperLinks, “,”)

        ‘ Place control at the left, shifted right by one “Gap” width.
        lngStartLblPos = cGap

        ‘ Loop through all the hyperlinks, positioning them with gaps.
        For iCtl = 0 To UBound(strControls())
            Set ctl = frm.Controls(strControls(iCtl))
            ctl.Top = 50
            ctl.Left = lngStartLblPos
            ctl.Height = 210
            lngStartLblPos = lngStartLblPos + (ctl.Width + cGap)
            ctl.HyperlinkAddress = ” ”
        Next
    End If

    ‘ Determine if the form has controls named lblCaption and lblDescription and
    ‘ set the flags appropriately.  This method may be extended to handle other
    ‘ common controls that appear on multiple forms.
    ‘
    ‘ First, assume the controls are missing or don’t exist.
    fLblCaption = False
    fLblDescr = False

    ‘ If found, then set the flag to True.
    For Each ctl In frm.Controls
        If ctl.Name = “lblCaption” Then fLblCaption = True
        If ctl.Name = “lblDescription” Then fLblDescr = True
    Next

    ‘ //////////////////////////////////////////////////////////////////////////////
    ‘ Set the text for the caption and description labels based on the form name.
    ‘   (Captions and Descriptions are saved in a table named FormLookup.)
    strCriteria = “[FormName]=’” & strForm & “‘”
    strCaption = Nz(DLookup(”[CaptionText]”, “FormLookup”, strCriteria))
    strDescr = Nz(DLookup(”[DescriptionText]”, “FormLookup”, strCriteria))
    If strCaption = “” Then strCaption = ParseFormName(strForm)
    If strDescr = “” Then strDescr = “No description found for [” & strCaption & “]”

    ‘ //////////////////////////////////////////////////////////////////////////////
    ‘ Set properties for lblCaption … if it exists.
    ‘   (Note that constants are used for all color values.  This allows for quick
    ‘    and easy formatting changes by editing the list of constants.)
    If fLblCaption Then
        With frm.Controls(”lblCaption”)
            ‘ If the label is set to NOT VISIBLE, then might as well skip formatting.
            If .Visible = True Then
                .Caption = ” ” & strCaption
                ‘.Top = 0
                ‘.Left = 0
                .Width = lWidth
                .ForeColor = cCaptionForeColor
                .BackColor = cCaptionBackColor
                .BackStyle = cNormal
                .FontName = “Tahoma”
                .FontBold = True
            End If
        End With
    End If

    ‘ Set properties for lblDescription … if it exists.
    If fLblDescr Then
        With frm.Controls(”lblDescription”)
            ‘ If the label is set to NOT VISIBLE, then might as well skip formatting.
            If .Visible Then
                .Caption = ”   ” & strDescr
                .Left = 0
                .Width = lWidth
                .ForeColor = cDescripForeColor
                .BackColor = cDescripBackColor
                .BackStyle = cNormal
                .FontName = “Tahoma”
                .FontBold = True
            End If
        End With
    End If

Exit_Here:
    Exit Function
Err_Handler:
    MsgBox Err.Description, vbCritical
    Resume Next
End Function

Fun and Frustration

This object resize code works pretty well and I’m pleased with the applications where I’ve implemented it. That doesn’t mean, however, that it is without frustration. Getting things to line up and display where desired will take some tweaking. If you set one property incorrectly, the whole page will look screwy. Those who attempt to implement this will undoubtedly want to write me for assistance and I’ll be happy to help, but ultimately you are going to have to use trial and error to get your pages to display the way you want. Please check and double check the TOP, LEFT, WIDTH and HEIGHT properties before assuming the code is broken. Remember, it works in the demo, so if you have difficulty, the solution is in your implementation code.

by Danny Lesandrini 

Something Not Entirely Unlike Access (Part 1)

(No Ratings Yet)
Loading ... Loading ...

“We want you to build an application in MS Access, but we don’t want it to look like Access. In fact, we want it to look like a web page … like a browser application.”

Can that be done? Judge for yourself. Below is the screen shot and here is the download with the working code for the application I’m calling Something Not Entirely Unlike Access. This application demonstrates the following browser-ish features:

  • Absence of Access menus
  • Obfuscation of Access icon and startup screen
  • Single form (page) interface
  • Automatic resizing of subforms and their controls
  • Forward and Back buttons to navigate “pages”
  • Start Page button to return users to their “home” page
  • Built in WWW web browser page
  • Hyperlinks to load new forms/subforms
  • Reports display as Snapshots outside of application

If any of these topics interest you, download the demo application and give it a whirl. It wasn’t built with the intention of being ascetically pleasing, so please don’t send me feedback about how ugly it is. It’s all about the code … and there’s plenty of that in this app. More than I can realistically cover in a single article, but I’ll break out pieces for future articles as the spirit moves me. In the mean time, you can get it all, provided you’re not afraid to dig into the code.

dl_unlike_access_image001.jpg

Hide Access Stuff

The first thing you want to do is to get rid of the things that make Access look like Access. Menus are the first to go, and here’s the code that gets rid of them. I put this code on my startup form and the first thing I do is to set the form Visible property to FALSE and turn off the screen painting (DoCmd.Echo False), so that I work my magic without the user seeing the flashing. I also maximize my startup form, but that has more to do with other aspects of this application.

    Me.Visible = False
    DoCmd.Echo False
    DoCmd.Maximize

    DoCmd.ShowToolbar “Web”, acToolbarNo
    DoCmd.ShowToolbar “Menu Bar”, acToolbarNo
    DoCmd.ShowToolbar “Form View”, acToolbarNo

Menus
To turn off the menus, execute the DoCmd.ShowToolbar command with the name of the target toolbar or menu, and the parameter acToolbarNo. (acToolbarYes, as you might imagine, shows the menu.) The help file says that ShowToolbar works only on toolbars, but that isn’t my experience. To prove this to yourself, double-click on the Welcome label. I added code that executed the acToolbarYes option to show the “Menu Bar” and it indeed toggles the main menu bar visible.

Icon and Title
The next Access Stuff to remove is the application icon, application title and title bar text. It’s been so long that I’ve been using this code, I can’t say for sure who to give credit to, but more likely than not, it was from the Access Web developer’s reference site. If not, I apologize to whomever contributed this fun and useful piece of code.

The function does the equivalent of setting startup properties in VBA code. If, for example, no application icon has been set, the property doesn’t really yet exist. An error will be thrown when you try to set it for the first time. Same for the Application Title property. This function will create the property if it’s missing and set them to some predefined values.

The advantage of this is that when you assign an icon to the application, the pretty Microsoft Access key icon goes away. Also, if you don’t supply an application title, it defaults to Microsoft Access. So, if you’re seeking to hide all things Access, you’ll need to supply a title.

Public Function SetAppProperties() As Boolean
    On Error GoTo Err_Handler

        Dim strFile As String
        Dim strTitle As String
        Dim dbs As DAO.Database
        Dim prp As DAO.Property

        Const cAPP_ICON = “AppIcon”
        Const cAPP_TITLE = “AppTitle”

        Set dbs = CurrentDb

        strFile = CurrentProject.Path & “dbj.ico”
        strTitle = “My Web App”

        On Error Resume Next
        dbs.Properties(cAPP_ICON) = strFile
        If Err.Number = 3270 Then
            Err.Clear
            Set prp = dbs.CreateProperty(cAPP_ICON, dbText, strFile)
            dbs.Properties.Append prp
        End If

        dbs.Properties(cAPP_TITLE) = strTitle
        If Err.Number = 3270 Then
            Err.Clear
            Set prp = dbs.CreateProperty(cAPP_TITLE, dbText, strTitle)
            dbs.Properties.Append prp
        End If

    Exit_Here:
        Set dbs = Nothing
        Application.RefreshTitleBar
    Exit Function

    Err_Handler:
        Select Case Err
            Case 3270   ‘Property not found
            Case Else
                MsgBox Err.Description, vbCritical
        End Select
        Resume Exit_Here
    End Function

Status Bar
The last piece of visual housekeeping to take care of is to hide the Status Bar … that horizontal information warehouse at the bottom of the page. Now, you could keep this option turned on, if you like, because even Internet Explorer has a status bar that you can opt to see, but I found that removing it helped to obfuscate the Accessian features and to enhance the appearance that the application was not a Microsoft Access program. Here’s how you set the Status Bar option in code:

Application.SetOption “Show Status Bar”, False

This is a convenient bit of syntax to keep handy. I use it to set all kind of options in code, especially Error Handling. If you set some public constants, you can conveniently toggle the error handling based on who is logged in. For users, I set it to Break In Class Mode or Break on Unhandled, but when I’m testing, I want it to Break On All. This code sets that option:

 Public Const cBreakOnAll       As Long = 0
    Public Const cBreakInClass     As Long = 1
    Public Const cBreakUnhandled   As Long = 2
    SetOption “Error Trapping”, cBreakOnAll  ‘ cBreakUnhandled

Managing “Pages”

This feature is really more involved than can be easily described in one article, so I’ll just give you the 30,000 foot overview. To look like a Web Browser, the app needs to stick to one main client form that is “loaded” with pages. There needs to be a device to remember navigation between pages and the pieces need to resize (in most cases) so that they take up the entire screen real estate.

Single Form with Subform
The first of those objectives is realized by creating only one form that the users ever really “open” as a form. I’ve called mine frmMain, and it’s the startup form. All other forms are named sfrmXXX or sfrmYYY to identify them as subforms. (Except for a popup form like frmAbout or frmHelp.) This main form contains a single subform named objSubform. Each time a request for data is made, whether that be for products, employees or orders, the appropriate form is loaded into this subform and a command is sent to the subform to resize itself according to the available space.

The code for loading a subform is embedded on frmMain, which is always open. For example, clicking on a customer in the list (see screen shot above) executes these lines of code. First, a function is called to “set” the CustomerID and the next line asks the main form to load the main subform with a form named “frmShowCustomer”. That form performs a “get” of the CustomerID when it loads, effectively filtering the results to the selected customer.

SetCustomerID Nz(Me!CustomerID, “”)
    g_lngResult = Forms!frmMain.LoadMainSubform(”frmShowCustomer”, True)

Navigation
The code to maintain the list of forms visited and to reload them is not trivial, but it’s encapsulated in a special class named clsNavigation. This will most definitely be the source of a future article, but in brief it does the following:

  • The Load method creates an in-memory ADO recordset
  • AddNavPage method inserts a new nav record into the recordset
  • GetNavPage returns a specific “page”
  • NavPrevPage and NavNextPage step you through the pages

What web browser would be complete without a Home or Start Page button. While this could be handled through the navigation control, I’ve opted to manage that feature with a form called frmStartPage. This is the form that is always loaded first, and to reload it is a simple modification of the call above, substituting “frmStartPage” for “frmShowCustomer”. By the way, the second option in that method, which is a Boolean True or False, determines whether or not the navigation control is asked to log the visit.

Strictly Web Features

Add a Web Browser
The last set of enhancements have to do with adding features that are strictly webish. There exists a custom control in Access called the Microsoft Web Browser control. The code to load a web page is so simple you’re going to laugh. After locating it in the toolbox by clicking on the More Tools icon and navigating to and clicking it, rename the control to objWebBrowser and add this line of code to any event that suits your needs:


Me!objWebBrowser.Navigate “http://www.google.com”

That’s all there is to adding web surfing to your apps.

Hyperlinks
The Hyperlinks are created by setting the IsHyperlink property of the control to Yes. (It’s in the list of properties under the Format tab for any bound control.) Next, you lock the control for editing add code to the Click method to cause the app to do something, like “jump” to the record that was clicked. Access takes care of formatting the text to look like a hyperlink and even gives you the pointing finger icon that you get on a web page. Pretty cool, and very simple to do.

Reports
If it’s not Access, then it can’t display an Access report, right? Right. So, we use an alternate process for displaying reports. Assuming the report is in the variable sReport and it’s being output to a file named sFile, the following lines of code will create and open a Microsoft Snapshot Report in a separate window:

DoCmd.OutputTo acOutputReport, sReport, acFormatSNP, sFile, False
        Application.FollowHyperlink strFile

As it turns out, this is a really clean solution. Sure, files are created on the disk, but disk space is cheap these days, and it gives the added benefit of archiving snapshots of the reports over time. Something that itself could be managed from your app, providing links to previously created reports. It doesn’t get cooler than this.

Satisfied?

Well, I apologize if the above description lacks detail. There just isn’t time or space to go into detail about all the features of this Something Not Entirely Unlike Access application. The download will be a great starting point for you, if any of these features sound like something you would want to add to your applications. Also, watch this space for future articles, where I’ll describe in detail the inner workings of the more complex processes.

by Danny Lesandrini

Good Answer… Perhaps TOO Good…

(No Ratings Yet)
Loading ... Loading ...

Peter B. was an out-of-work PHP developer looking for contract work in early 2005. A recruiter he’d worked with in the past emailed him some information regarding a possible position. Reading the job description, Peter thought he’d be a good fit, so he submitted his resume and got a response via email a few days later.

The hiring manager described their typical process; Peter would have to answer a screening question to determine his skill level, and if his answer was satisfactory, they’d schedule a face-to-face interview. With a little trepidation, Peter said he was ready for the question. He was concerned that it could be about a complex topic that he wasn’t very familiar with. A few hours later, an email arrived with the subject “SCREENING QUESTION,” flagged with high importance.

His mouse hovering over the email, he expected to open it and have to answer “on a PB349 microprocessor, if memory address 0xa9f00c contains a MOV instruction to memory address 0×8ad9da, what is the magnetic force dispensed by a 64KB memory module for the next 600 instructions? You have thirty seconds.”

Peter took a deep breath and clicked on the email. Here is the exact question he was asked: “Describe what concatenation is, how it applies to PHP, and how you’ve used it in the past.”

Peter was surprised. It was a question that anyone with any basic exposure to any modern programming language could answer. It would’ve been harder to describe what multiplication was and how he’d used it in the past.

Still, he wanted to show that he understood the concept, so he played along. He typed up a detailed response.

Concatenation is the process to sequentially join multiple pieces of data, usually literal strings with non-string-literal data (most commonly, variables or other literals). The concatenation operator varies from language to language. Javascript, for example, overloads the plus-sign (+) as it is both the concatenation operator as well as the arithmetic addition operator. PHP uses the period (.) as the concatenation operator.

String concatenation is often used in PHP to build a string of HTML for output to the client (browser). This is common in prodecural-based PHP code. However, I should note that oftentimes, that using concatenation for HTML generation is inefficient or can be better served by some other design pattern - particulary if the developer is using concatention during an echo operation (in this case, comma-separating the tokens is faster).

Another common use for string concatenation is the generation of dynamic SQL queries. For example, if I had a CMS that was to pull all articles written by a certain user, the code might look something like this

<?php
$sql = “SELECT article_id, article_body FROM Articles WHERE author_id = ” . $User->getID() . ” ORDER BY article_date DESC”;
?>

As you can see, the above code combines three tokens to generate a complete SQL query.

  1. A SQL fragment
  2. The user’s ID as pulled from a custom User object
  3. A SQL fragment

SQL queries are rarely generated without some sort of dynamic data to alter their structure, so this is a very common task that I’ve used in just about every web application I’ve written. Some other simple examples include cookie generation, error message generation, email headers, and dynamic URL construction.

I hope this sufficiently explains concatenation in general, how it relates to PHP, and my experience with using this basic operator.

Peter sent the email and got a phone call a few days later.

Peter: Hello?
Lisa: Hi, Peter? This is Lisa from Concatcorp.
Peter: Oh, hi! Good to hear from you! I hope you have news about the job.
Lisa: Well, yes, but…
Peter: Yes?
Lisa: We’re going to offer the position to another candidate.
Peter: I see… may I ask why? I thought I did a pretty thorough job answering the screening question…
Lisa: Well, that’s just it. The problem is that they think your answer was too good. They think you plagiarized it. I’m sorry.

It was then that Peter realized he was probably better off without that job.

Making a CelebrityCollector with Apache Tapestry: the For Component

(No Ratings Yet)
Loading ... Loading ...

We are going to start a new project today, named CelebrityCollector. At first it will be very simple, but in the following articles we’ll be adding more and more functionality to it. Of course, the purpose at this stage of study is not to build a real-world application but to meet different Tapestry components and to learn various important concepts.Creating the project

Create a new project as explained in the article “Creating Your First Tapestry Project” and name it CelebrityCollector. Don’t forget to add the Tapestry41 library and configure the Tapestry servlet properly.

Add to the project three empty pages: Home, CelebritiesList and Details (see the “Introducing Simple Components in Apache Tapestry” article for an explanation on how to add empty pages; this operation will be simplified significantly when we’ll adopt the NBTapestry module in one of the upcoming articles). Let the package containing page classes be com.devshed.tapestry.celebrities.

Let’s envision what we are going to have in the project so far. The Home page will only have one link (already familiar to you as the PageLink component). Upon clicking this link the user will see the CelebritiesList page.

The CelebritiesList page will display a table with a number of celebrities’ names in it. Each surname will be a link, and if the user clicks on it, the Details page with information on the selected celebrity will open.

As usual, let’s start from mock ups. Here is the Home page mock up and its HTML code:

image0011.gif

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>

 

<html>

  <head>

    <title>Celebrity Collector: Home</title>

  </head>

  <body>

      <h1>Celebrity Collector</h1>

 

      <p><a href=”">List Celebrities</a></p>

 

  </body>

</html>

The CelebritiesList page mock up and its HTML code are shown below. The designer who worked on the mock up didn’t know which celebrities we are going to list, so he put some arbitrary names into the table, just to see how everything might look in the working application.

image0021.gif

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>

 

<html>

  <head>

    <title>Celebrity Collector: List</title>

  </head>

  <body>

 

  <h2>Celebrities in Collection:</h2>

 

  <table width=”300″ cellpadding=”5″ border=”1″>

      <tr>

          <th>Last Name</th>

          <th>First Name</th>

      </tr>

      <tr>

          <td>

              <a href=”">Smith</a>

          </td>

          <td>John</td>

      </tr>

      <tr>

          <td>

              <a href=”">Smithson</a>

          </td>

          <td>Jane</td>

      </tr>

      <tr>

          <td>

              <a href=”">Swedenborg</a>

          </td>

          <td>Emmanuel</td>

      </tr>

  </table>

 

  </body>

</html>

Finally, here is some content for the Details page mock up:

 image0031.gif

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>

 

<html>

  <head>

    <title>Celebrity Collector: John Smith</title>

  </head>

  <body>

 

  <h2>John Smith</h2>

 

  <table>

      <tr>

          <td><b>Birthday:</b></td>

          <td>01/01/1950</td>

      </tr>

      <tr>

          <td><b>Occupation:</b></td>

          <td>Actor</td>

      </tr>

  </table>

  <p><a href=”">Back to the list</a></p>

 

  </body>

</html>

Now, before going into the specifics of Tapestry development, we need to think about which Java classes we might need in order to manipulate information in our application – store it, transfer, etc. In other words, we need to design an object model. This is not Tapestry specific work but something we would do in any Java Web application.

Creating an object model

First of all, we need some class to represent a celebrity; it would be natural to name it Celebrity. For the first iteration of the project, this class will have only five properties:

  • A numeric ID (an int);

  • First name (a String);

  • Last name (a String);

  • Date of birth (a Date);

  • Occupation (a String).

We need to have this numeric ID because, well, theoretically speaking it is not against the laws of nature to have two celebrities named John Smith born on the same day who became famous in the same occupation. But even if that happened, we would have this ID as a way to distinguish between them. In a real life application, this could be a primary key in a database.

Create a new class named Celebrity (right-click the earlier created com.devshed.tapestry.celebrities package, New -> Java Class…) and add to it five private members:

private int id;

private String firstName;

private String lastName;

private Date dateOfBirth;

private String occupation;

Again, make use of this handy shortcut: after typing “Date” press Ctrl-Space and select java.util.Date class.

Then allow NetBeans to create getter and setter methods: right-click somewhere in your code and choose Refactor -> Encapsulate Fields… Make sure that everything is selected as the picture shows:

image004.jpg

Press Next, and NetBeans will create an overview of the changes it is going to make and show it in the Refactoring view below the code editor. Just press Do Refactoring there, and you will see getters and setters added to the Celebrity class.

Now, we need to have a source of data that will give us a number of Celebrity objects. In a real life application, this would be a database, but for now let’s leave everything as simple as possible and use a mock data source – a simple Java class that creates a List of Celebrity objects and provides methods to retrieve either the whole list or just one specified object. Later, we might replace the mock data source with a real one.

Let’s add a DataSource class to the existing Java package. There is nothing Tapestry-specific or terribly clever in this class, so you can simply copy the code:

package com.devshed.tapestry.celebrities;

 

import java.util.ArrayList;

import java.util.Calendar;

import java.util.Iterator;

import java.util.List;

 

public class DataSource {

 

    List list = new ArrayList();

 

    /** Creates a new instance of DataSource */

    public DataSource() {

 

        Calendar calendar = Calendar.getInstance();

 

 

        Celebrity fonda = new Celebrity();

        fonda.setFirstName(”Jane”);

        fonda.setLastName(”Fonda”);

        fonda.setOccupation(”Actress”);

        calendar.set(1937, 11, 21);

        fonda.setDateOfBirth(calendar.getTime());

        fonda.setId(1);

 

        list.add(fonda);

 

        Celebrity gallo = new Celebrity();

        gallo.setFirstName(”Ernest”);

        gallo.setLastName(”Gallo”);

        gallo.setOccupation(”Wine-maker”);

        calendar.set(1909, 2, 18); // Months are 0-based (2 is March)

        gallo.setDateOfBirth(calendar.getTime());

        gallo.setId(2);

 

        list.add(gallo);

 

        Celebrity gates = new Celebrity();

        gates.setFirstName(”Bill”);

        gates.setLastName(”Gates”);

        gates.setOccupation(”Programmer ;)”);

        calendar.set(1955, 9, 28);

        gates.setDateOfBirth(calendar.getTime());

        gates.setId(3);

 

        list.add(gates);

 

        Celebrity jolie = new Celebrity();

        jolie.setFirstName(”Angelina”);

        jolie.setLastName(”Jolie”);

        jolie.setOccupation(”Actress”);

        calendar.set(1975, 5, 4);

        jolie.setDateOfBirth(calendar.getTime());

        jolie.setId(4);

 

        list.add(jolie);

 

    }

 

    public List getCelebrities() {

        return list;

    }

 

    public Celebrity getCelebrityById(int id) {

 

        for (Iterator i = list.iterator(); i.hasNext();) {

            Celebrity celebrity = (Celebrity)i.next();

            if (celebrity.getId() == id) return celebrity;

        }

 

        return null;

    }

}

This class simply creates a List of a few celebrities in its constructor using some hard-coded data. We can obtain the whole lot by using the getCelebrities() method, or retrieve a single celebrity by specifying his or her ID and using the getCelebrityById() method.

Now that we have all supporting code ready, we can begin converting page mock ups into Tapestry templates.

Creating pages

Making Home.html a Tapestry template is easy. All we need to do is convert the existing HTML link into an already familiar PageLink component, and it is okay to do this implicitly:

<p><a href=”" jwcid=”@PageLink” page=”CelebritiesList”>

    List Celebrities</a></p>

You might want to run the project at this point and see if clicking on the link produces the desired result. It should.

As the Home page is so simple and its page specification remains empty, we don’t actually need any Home page class for it. In fact, you could simply remove the class attribute from the page specification:

<?xml version=”1.0″?>

<!DOCTYPE page-specification PUBLIC

    “-//Apache Software Foundation//Tapestry Specification 4.0//EN”

    “http://tapestry.apache.org/dtd/Tapestry_4_0.dtd”>

<page-specification>

</page-specification>

In this case Tapestry will use its BasePage class as a Home page class. However with time, as the CelebrityCollector application becomes more complex, we might decide to add something to the Home page. So I suggest leaving in place both the Home.page specification and the Home.java class; it doesn’t matter that they are empty so far.

Converting Details.html is also straightforward. All we need is a number of Insert components and another PageLink. Let’s suppose for now that we have a celebrity property already available in the Details class. We shall add it in a few minutes. Here is the Details page template:

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>

 

<html>

  <head>

      <title>Celebrity Collector: <span jwcid=”fullName”>

          John Smith</span></title>

  </head>

  <body>

 

  <h2><span jwcid=”fullName2″>John Smith</span></h2>

 

  <table>

      <tr>

          <td><b>Birthday:</b></td>

          <td><span jwcid=”birthday”>01/01/1950</span></td>

      </tr>

      <tr>

          <td><b>Occupation:</b></td>

          <td><span jwcid=”occupation”>Actor</span></td>

      </tr>

  </table>

  <p><a href=”" jwcid=”@PageLink” page=”CelebritiesList”>

       Back to the list</a></p>

 

  </body>

</html>

And here is the corresponding Details page specification (note that all Insert components are configured in the specification while PageLink is defined implicitly):

<?xml version=”1.0″?>

<!DOCTYPE page-specification PUBLIC

    “-//Apache Software Foundation//Tapestry Specification 4.0//EN”

    “http://tapestry.apache.org/dtd/Tapestry_4_0.dtd”>

<page-specification class=”com.devshed.tapestry.celebrities.Details”>

 

    <component id=”fullName” type=”Insert”>

        <binding name=”value”

          value=”celebrity.firstName + ‘ ‘ + celebrity.lastName”/>

    </component>

 

    <component id=”fullName2″ copy-of=”fullName”/>

 

    <component id=”birthday” type=”Insert”>

        <binding name=”value” value=”celebrity.dateOfBirth”/>

    </component>

 

    <component id=”occupation” type=”Insert”>

        <binding name=”value” value=”celebrity.occupation”/>

    </component>

 

</page-specification>

Before discussing how components were configured, let’s add a celebrity property to the Details page by providing abstract public getter and setter methods:

public abstract class Details extends BasePage {

 

    public abstract Celebrity getCelebrity();

    public abstract void setCelebrity(Celebrity c);

 

}

At some point, we are going to use the setter to assign some Celebrity object to this property, but for now we don’t care when and where that will be done. Let’s concentrate on how different Insert components were configured in the page specification or, most importantly, on their bindings.

The component named occupation is the simplest one. It is bound to the OGNL expression

celebrity.occupation

Which means that to obtain a value to display, Tapestry will first invoke the getCelebrity() method of the Details class. We have provided this method, and although we made it abstract, Tapestry will take care of creating a concrete implementation of it at runtime. So we’ll get a Celebrity object as a return value from this method.

Then, according to the rules of OGNL, Tapestry will try to invoke the getOccupation() method on the Celebrity object. Sure enough, we have provided such method, and it returns a String. That same string will be displayed by the component then.

The birthday component is different in only one respect: its binding returns a Date. To display it, Tapestry will invoke the Date’s toString() method which will produce… well, some result. In fact, we can have complete control of how the value is displayed by an Insert component by using its format binding, but let me delay the explanation until the next article.

In two cases we want to display the full name of the celebrity: first, in the title element of the resulting HTML page (this is the piece of information displayed by a browser’s title bar), and second, in the header of the page. The fullName component does the job in the title element. Have a look at its binding expression:

celebrity.firstName + ‘ ‘ + celebrity.lastName

According to this OGNL expression, Tapestry will obtain the first and the last names of the celebrity by invoking appropriate methods of the Details and Celebrity classes and then glue both names together leaving a blank space between them. So you see, OGNL can be quite useful.

In order to insert the full name into the page header, we use the fullName2 component. As we need exactly the same result, we could use exactly the same binding – but there is a more rational way to achieve the desired result. We can simply state that fullName2 component is a copy of the fullName component, and this is exactly what we are doing here.

Finally, we are coming to the CelebritiesList page, and here we are going to need the new component named For. Let’s see what it can do for us.

The For component

The For component does three crucially important things:

  1. It takes a bunch of objects and iterates through them one by one. Technically, this bunch can be an Array or any representatives of the Collection interface, such as an ArrayList or a HashSet, to name a few.

  2. Each time it picks an object from the bunch it makes that object available for our code.

  3. It displays any markup and any components surrounded by itself as many times as there are objects in the bunch.

Let’s begin from configuring a For component in the List page specification:

<component id=”eachCelebrity” type=”For”>

   <binding name=”source” value=”celebrities”/>

</component>

We’ve specified a For component and given it some name (eachCelebrity). We have also provided its required and most important binding, source. This is where the bunch of objects will come from. Let’s define the getCelebrities() method in the CelebritiesList page then:

public abstract class CelebritiesList extends BasePage {

 

    public List getCelebrities() {

        return new DataSource().getCelebrities();

    }

 

}

We can place a For component in HTML markup in the same way as we did this before, using a div or a span. However, in this specific case it is more reasonable to convert a table row (tr element) into a For component. Let’s see how we can do this:

<table width=”300″ cellpadding=”5″ border=”1″>

    <tr>

        <th>Last Name</th>

        <th>First Name</th>

    </tr>

    <tr jwcid=”eachCelebrity”>

        <td>

            <a href=”">Smith</a>

        </td>

        <td>John</td>

    </tr>

    <tr jwcid=”$remove$”>

        <td>

            <a href=”">Smithson</a>

        </td>

        <td>Jane</td>

    </tr>

    <tr jwcid=”$remove$”>

        <td>

            <a href=”">Swedenborg</a>

        </td>

        <td>Emmanuel</td>

    </tr>

</table>

We have marked one of the table rows as an “eachCelebrity” component which we have just defined in the page specification. It should be repeated as many times as there are Celebrity objects in the bunch provided to this component. But now we have two table rows which we don’t want to see at runtime; they were written by the designer for preview purposes only.

Quite conveniently, Tapestry has a special thing which looks like a component ID but in fact just marks an HTML element for removal at runtime: jwcid=”$remove$”. So we have marked the two remaining table rows for removal.

Run the application, and this is what you should see:

image0051.jpg

It works! The rows for Jane Smithson and Emmanuel Swedenborg were removed, as required, and the row for John Smith was repeated four times – exactly the number of celebrities in our mock data source. Now we need to display the real celebrities instead of John Smith.

First of all, we shall ask the For component to expose to our code the Celebrity object it is currently iterating through. We use another binding for this purpose:

<component id=”eachCelebrity” type=”For”>

    <binding name=”source” value=”celebrities”/>

    <binding name=”value” value=”currentCelebrity”/>

</component>

Each time our For component picks a Celebrity object from the List provided to it, it will pass this object to the page class by invoking the page class’ setCurrentCelebrity() method. We don’t have such a method at the moment and we don’t need to write it by hand. All we need to do is tell Tapestry to create a currentCelebrity property.

Normally, we would simply provide an abstract getter method, and Tapestry would do the rest. This time however we are not going to access the new property from our Java code. As you will see in a moment, it will be used only by components on the page. So it makes sense to specify the property in the page specification:

<page-specification
class=”com.devshed.tapestry.celebrities.CelebritiesList”>

 

    <property name=”currentCelebrity”/>

 

    <component id=”eachCelebrity” type=”For”>

        <binding name=”source” value=”celebrities”/>

        <binding name=”value” value=”currentCelebrity”/>

    </component>

 

</page-specification>

Finally, we are going to use a couple of Insert components to display the first and last name of the current celebrity in the corresponding table row:

 <tr jwcid=”eachCelebrity”>

      <td>

          <a href=”">

              <span jwcid=”lastName”>Smith</span>

          </a>

      </td>

      <td>

          <span jwcid=”firstName”>John</span>

      </td>

  </tr>

Since we decided to use declared components here, we need to define them in the page specification:

<component id=”firstName” type=”Insert”>

    <binding name=”value” value=”currentCelebrity.firstName”/>

</component>

<component id=”lastName” type=”Insert”>

    <binding name=”value” value=”currentCelebrity.lastName”/>

</component>

Run the application, click on the “List Celebrities” link, and you should see the correct result:

image0062.jpg

However, if you click on a link in the table, nothing will happen, while we would like to see the Details page for the selected celebrity. To implement this functionality, we need a DirectLink component, and this is exactly what we are going to deal with in the next article.

by Alexander Kolesnikov 

Apache Tapestry and Listener Methods, Conditional Components and PageLink

(No Ratings Yet)
Loading ... Loading ...

Today we continue to explore some of the most fundamental concepts of Tapestry while building one of the simplest Java Web applications. In the previous part of this tutorial we became familiar with properties of Tapestry pages and different ways to configure them. Now we’ll look into the details of writing listener methods.


A downloadable zip file is available for this article.

The simplest approach

We have already used one listener method in our GuessTheWord application. Let’s remind ourselves how we did that.

In the Home.html template we created a form:

<form action=”" jwcid=”secretWordForm”>

… some content …

</form>

Then in the Home.page specification we told Tapestry what to do when the form is submitted:

<component id=”secretWordForm” type=”Form”>

<binding name=”listener” value=”listener:onWordSubmit”/>

</component>

Which basically means that we want the onWordSubmit() listener method of the Home class to be invoked. And here is the method itself:

public String onWordSubmit() {

return “Secret”;

}

Right now, the listener method is very simple. It does not perform any processing and simply returns a String. Tapestry understands this as “Show the page with this name, please!” and displays the Secret page.

But the listener method could be even simpler than this, for example:

public void onWordSubmit() {

// Do something

}

This version returns nothing – so the same page will be redisplayed. But if we put some code inside of the method, it will be executed, of course.

More often however we want some other page to be shown, and not only that, but we want to somehow prepare the next page before showing it.

 Passing information to the next page

In our GuessTheWord project we need to somehow pass the secret word submitted by the user to the Secret page, where the word will be checked, and depending on the result, an appropriate part of the page will be shown.

The Home page class has a property to store the submitted word. Before passing the word to the Secret page, we need to configure a similar property in that page too.

As you already know, to have a property configured automatically by Tapestry, all we need to provide is an abstract getter method. So let’s add to the Secret class this line of code:

public abstract String getTheWord();

However, as you will see soon, we are going to need a setter method too, as we are going to set the value for this property from the Home page. Nothing can be easier; let’s just add an abstract setter too, so that the Secret class will contain the following code:

public abstract class Secret extends BasePage {

 

   public abstract String getTheWord();

   public abstract void setTheWord();

 

}

Now we can return to our listener method in the Home class. First of all, we need to somehow obtain in it a reference to the Secret page; then we are going to use this reference to pass the value of the secret word. But how can we get a reference to a different page class? IRequestCycle will help us.

To every user, Tapestry assigns a kind of majordomo – an object that carries the burden of the application’s housekeeping. All that we need to know about this object as Tapestry developers is that it implements the IRequestCycle interface with a number of useful methods in it. In particular, it can give us a reference to any page of our application.

To tell Tapestry that we are going to need help from the majordomo, we simply pass an implementation of IRequestCycle as a parameter of our listener method:

… onWordSubmit(IRequestCycle cycle) {

   …

}

And then we use the getPage() method to obtain a reference to the desired page:

Secret nextPage = (Secret) cycle.getPage(“Secret”);

The getPage() method has no idea of the type our Secret page class, so it returns a generic Tapestry page (technically, an implementation of IPage interface, and this interface is always implemented by any Tapestry page). But we know that the page named “Secret” has a class named Secret for its page class, so we can safely cast the returned result to what we expect it to be.

The next step is to pass the value of the secret word to the Secret page:

nextPage.setTheWord(getTheWord());

Or you can write it like this for clarity:

nextPage.setTheWord(this.getTheWord());

Finally, we need to ask Tapestry to show the next page. The simplest way to do this is to return the reference to the page we have just used to set the value. Technically, we return an IPage which simply means “some Tapestry page.” Here is what our completed listener method will look like:

public IPage onWordSubmit(IRequestCycle cycle) {

 

   Secret nextPage = (Secret)cycle.getPage(”Secret”);

   nextPage.setTheWord(getTheWord());

 

   return nextPage;

 

}