Class: cWebList

Properties  Events  Methods    Index of Classes

The Web Framework multi-row, multi-column list control

Hierarchy

cObject
---cWebObject
------cWebBaseUIObject
---------cWebBaseDEOServer
------------cWebBaseControl
---------------cWebList
------------------cWebGrid
------------------cWebHtmlList
------------------cWebMultiSelectList
------------------cWebPromptList
------------------cWebWidgetPalette

Library: Web Application Class Library

Package: cWebList.pkg

Description

Web list controls provide the basis for creating multi-row, multi-column display. Each column in a list is represented by a column object. cWebList objects use the cWebColumn, cWebColumnButton, cWebColumnCheckbox, cWebColumnCombo, cWebColumnDate, cWebColumnImage and cWebColumnLink classes for its column objects.



Lists are ideal for displaying and browsing a list of data with support for searching, sorting and selecting of rows in the list.

A list can be used with or without a data binding. When used with a data binding, the list rows and columns are populated from the data provided by the list's server data dictionary object. When used without a data binding, you will use the class interface to read or write the control value.

If you need to support editing the list data, then you should use the cWebGrid control instead.

The cWebMultiSelectList class enhances the cWebList class with the ability to select entries from that list using either the mouse or keyboard.

If you need to show a list of data in a modal dialog, then return selected row information to the calling object, then you should use the cWebPromptList control, which has built-in support for these operations.

Topics




Object Name

The Web Framework uniquely identifies each web object via a combination of the object hierarchy (object nesting), and object name. This means that web object names must be unique within their parent (i.e. each sibling web object must have a unique name).

Object Placement

List controls must be placed within a web container (e.g. cWebPanel, cWebView or cWebModalDialog).

List controls may not be nested inside other web controls. You may not mix web panels and web controls as sibling objects.

Size and Location

Web controls are positioned by HTML flow layout (left to right, top to bottom). The relative position of the control in this flow is determined by the location of the object declaration in your code.

In addition to the flow layout, each web container is divided into a fixed number of equal-width layout columns (piColumnCount). Child controls are aligned to these layout columns to determine their horizontal position and width.

Set piColumnIndex to position a list control in a particular column of the parent container.

Set piColumnSpan to determine the width of the list control i.e., the number of layout columns occupied by the control. The actual width is determined by the number of columns and the width of the parent container.

For more information, see Positioning and Layout of Controls.

Each list control has a pre-determined minimum height according to the web application's CSS theme (psTheme). Set piHeight to set the list control to a fixed pixel height. You can also instruct a list control to occupy all available vertical height in its parent container by setting pbFillHeight to true.

The piHeight and pbFillHeight properties are mutually exclusive and should not be used together in the same object.

pbAutoColumnSizing determines whether a list can scroll horizontally.


WebContextMenu Support

cWebContextMenu support is provided through the C_WebUIContextListRow Context, then the psContextValue
property will be filled with a comma separated list of Serialized RowIds.

WebUIContext Support

cWeblist/cWebGrid supports two types of WebUIContext. See cWebContextMenu for usage information.

* C_WebUIContextListRow: Will show the menu when right-clicking in a row. psContextValue will contain the RowId, if any.
* C_WebUIContextListHead: Will show the menu when right-clicking in the cWebList header, the psCaption of the column headers. pbUseCustomHeaderMenu should be set to True for it to work as the default menu will be overwitten.

Ordering the Data

The ordering of the grid rows depends on how the data is served to the grid. This can be broken down into two categories: Data Aware Grids and Manually Loaded Grids.


Data Aware Lists

List objects support datasets served by a data dictionary object structure. The list's Server property specifies the list's connection to a DDO. By default, the list's server data dictionary is delegated to the Main_DD setting of the host view or dialog.

When the list has a data server, then each list column will support a data binding to a specific Table.Column from the data dictionary object structure. See cWebColumn for more information.

A data binding automatically provides the control's data type, tooltip and much more. These settings may also be controlled manually by setting properties.

Each time a request is processed by a view (or dialog), the list's psCurrentRowID property is set to the RowID of the list's currently selected row on the client. Use psCurrentRowID to retrieve the currently selected row.

The list object's pbDataAware property must be set to true in order for the control work with a data dictionary object structure. For more information, see "Manually Loaded Lists" below.

If pbDataAware = True and peDbGridType <> gtManual, then the grid is "data-aware" and it is populated automatically by the attached Server DDO.

When working with data-aware lists, follow these guidelines for controlling index order:

1. Use piSortColumn to control index order

2. Always set an initial piSortColumn value

3. If you need to further control index order, augment the IndexOrder function

4. Do not set these properties:


See Indexes and Ordering with Data Aware cWebLists for a more complete description of this process.

Sample

This sample demonstrates a data aware list. A web view (cWebView) object contains a DataDictionary object (DDO) and a list (cWebList) whose rows and columns are populated automatically from the data dictionary.

Object oCustomers is a cWebView
    Object oCustomer_DD is a Customer_DataDictionary
    End_Object

    Set Main_DD to oCustomer_DD
    Set Server to oCustomer_DD
    
    Procedure OnLoad
        Send Find of oCustomer_DD FIRST_RECORD 1
    End_Procedure

    Object oCustomerList is a cWebList
        Set pbFillHeight to True
                      
        Object oCustomer_Name is a cWebColumn
            Entry_Item Customer.Name
            Set psCaption to "Customer Name"
            Set piWidth to 50
        End_Object
    
        Object oCustomer_City is a cWebColumn
            Entry_Item Customer.City
            Set psCaption to "City"
            Set piWidth to 50
        End_Object
    
        Object oCustomer_State is a cWebColumnCombo
            Entry_Item Customer.State
            Set psCaption to "State"
            Set piWidth to 40
        End_Object
    
        Object oCustomer_Status is a cWebColumnCheckBox
            Entry_Item Customer.Status
            Set psCaption to "Active"
            Set piWidth to 15
        End_Object
    End_Object
End_Object

The list contains four column objects, each with a data binding to the Customer table. Two cWebColumn objects are used to display the customer name and city text values; a cWebColumnCombo object is used to display a list of combo items to display and select a state; a cWebColumnCheckbox object is used to display the customer active status in a column of checkboxes.

The OnLoad event is fired when the view is first loaded to the client. This has been augmented to 'seed' the Customer data dictionary with the first record in the table. This seeding will cause the list to fill its rows with data from the customer table starting with the record found in the OnLoad event.


Row IDs and Web Lists/Grids

When the grid or list is data-aware, then the RowID that is stored for each row is the same as the database record's RowID, i.e. the database record row ID is the value stored in the grid row ID.

If the grid or list is not data-aware, then the RowID that is stored for each row is simply a unique row id determined by the developer.

Manually Loaded Lists

Set pbDataAware to False or peDbGridType to gtManual if you want to populate a list object programmatically, i.e. without using DataDictionary objects or data-bound columns. In this case, you will need to implement the list's OnManualLoadData event to populate the rows and columns with your desired data.

The OnManualLoadData event passes back an array of tWebRow structs, where each array member represents a row of column data for the list. The order of array members will determine the order of rows. The tWebRow - aCells member is the array you will need to populate to represent the column values for each row. The 0th member of this array represents the leftmost column. The sRowId member is populated with a value that is unique for each row.

You must provide a unique RowId for each row in a manually loaded list, or numerous list features simply won't work or won't work right. When using a data-bound list, this is handled for you by the DataDictionary, with manual lists, it is the developer's responsibility to provide unique RowIds.

The OnManualLoadData event is not automatically fired. You will need to write code in your view to trigger it. Typically you would augment the list's OnLoad event to do this by signaling the client to perform a GridRefresh, for example:

Procedure OnLoad
    Forward Send OnLoad    
    Send GridRefresh
End_Procedure

Refer to the Sample section below for an example demonstrating how to manually populate a grid.

Once the grid data has been loaded and displayed on the client, you may need to make specific changes to some of its data. Use the DataSetAppendRow, DataSetInsertRowBefore, DataSetRemoveRow and DataSetUpdateRow helper methods to manipulate the data of a manually loaded grid.

When working with manually loaded lists, you may need access to the full set of list data. You can call ProcessDataSet to do so.

Sample

This sample demonstrates a list whose rows are 'manually' populated with data. We call this a non-data aware list, i.e. it is not connected to any data dictionary Server and its columns do not have any data binding.

Note that WebList Grouping is optional, so the grouping-related parameters of OnManualLoadData (aTheGroups ByRef tWebGroupHeader[][][] ByRef aTheGroupHeaders) are optional and can be omitted.

Use cWebView.pkg
Use cWebPanel.pkg
Use cWebForm.pkg 
Use cWebList.pkg
Use cWebColumn.pkg

Object oManualWebListView is a cWebView
    Set piWidth to 400
    Set piHeight to 320
    Set psCaption to "Planets List"


    // Your DDO structure will go here

    Object oWebMainPanel is a cWebPanel
        Set piColumnCount to 12
        
        // place controls here.
        // Your view will grow as controls are added
        Object oPlanetsList is a cWebList
            Set pbFillHeight to True
            Set pbDataAware to False
            
            Object oPlanetColumn is a cWebColumn
                Set psCaption to "Planet"
            End_Object
    
            Object oDistanceColumn is a cWebColumn
                Set psCaption to "Distance from Sun"
                Set peAlign to alignRight
                Set peDataType to typeNumber
                Set psMask to "*.## AU"
            End_Object
    
            Procedure OnManualLoadData tWebRow[] ByRef aTheRows String ByRef sCurrentRowID aTheGroups ByRef tWebGroupHeader[][][] ByRef aTheGroupHeaders
                Integer iNum
                
                Forward Send OnManualLoadData (&aTheRows) (&sCurrentRowID)
                
                Move 0 to iNum
                Move iNum       to aTheRows[iNum].sRowID
                Move "Planet"   to aTheRows[iNum].sCssClassName
                Move "Mercury"  to aTheRows[iNum].aCells[0].sValue
                Move (ConvertToClient(typeNumber, 0.387)) to aTheRows[iNum].aCells[1].sValue
                
                Increment iNum
                Move iNum       to aTheRows[iNum].sRowID
                Move "Planet"   to aTheRows[iNum].sCssClassName
                Move "Venus"    to aTheRows[iNum].aCells[0].sValue
                Move (ConvertToClient(typeNumber, 0.722)) to aTheRows[iNum].aCells[1].sValue
                
                Increment iNum
                Move iNum       to aTheRows[iNum].sRowID
                Move "Planet"   to aTheRows[iNum].sCssClassName
                Move "Mars"     to aTheRows[iNum].aCells[0].sValue
                Move (ConvertToClient(typeNumber, 1.52)) to aTheRows[iNum].aCells[1].sValue
                
                Increment iNum
                Move iNum       to aTheRows[iNum].sRowID
                Move "Planet"   to aTheRows[iNum].sCssClassName
                Move "Jupiter"  to aTheRows[iNum].aCells[0].sValue
                Move (ConvertToClient(typeNumber, 5.2)) to aTheRows[iNum].aCells[1].sValue
            End_Procedure
        End_Object
    
        Procedure OnLoad
            Forward Send OnLoad
            Send GridRefresh to oPlanetsList
        End_Procedure

    End_Object 

End_Object

In this example, GridRefresh is sent during the cWebView object's OnLoad event. This means that just before the view is sent to the client, GridRefresh will be sent. Since pbDataAware is False, the GridRefresh will trigger the OnManualLoadData event where the logic for populating the row data is implemented.

Within OnManualLoadData: aTheRows is an array of tWebRow structs that is populated to give the grid its data. The sRowId member is populated with a value that is unique for each row. The actual data to display in the grid is stored in the aCells array member.

Special attention is needed when populating a column whose data type (peDataType) is typeNumber, typeDate or typeDateTime, to ensure the values are sent to the client in a standard format. This is performed by applying the ConvertToClient function to each value.

Manually Loaded Grids

Set pbDataAware to False if you want to populate a grid object programmatically, i.e. without using data dictionary objects or data-bound columns. In this case you will need to implement the grid's OnManualLoadData event to populate the rows and columns with your desired data.

The OnManualLoadData event passes back an array of tWebRow structs where each array member represents a row of column data for the list. The order of array members will determine the order of rows.

The tWebRow - aValues member is the array you will need to populate to represent the column values for each row. The 0th member of this array represents a unique row identifier. You may set this to any value you wish but you must ensure that each row has a unique value. The remaining values in this member represent the values to be displayed in each column from left to right.

The OnManualLoadData event is not automatically fired. You will need to write code in your view to trigger it. Typically you would augment the grid's OnLoad event to do this by signaling the client to perform a GridRefresh, for example:

Procedure OnLoad
    Forward Send OnLoad    
    Send GridRefresh
End_Procedure

See below for information on saving a manually loaded grid.

For information about manually loading a data aware grid refer to the help section on Web Framework Lists & Grids.

WebList Grouping


To enable manual grouping mode, set the
peGrouping property to grpCustom. In this mode, the developer is responsible for determining the groups by filling structs in OnManualLoadData. This requires the list to be manually filled with data, so first peDbGridType to gtManual.

Then, inside OnManualLoadData, the developer needs to fill two additional arrays of structs with information on the groups. Together, these two will set the text in the group headers like this: ": groupvalue". Finally, fill the rows and place them under a groupheader index.

Take the following steps to implement manual grouping.

STEP 1

Set peGrouping of the weblist to grpCustom. Also set peDbGridType to gtManual.

STEP 2

Define the groupings by manually filling the tWebGroupConfig struct array. This array defines the grouping in the weblist and is also (implicitely) used in automatic grouping. For manual grouping only the member sLabel needs to be set. This is the label that will be shown at each group header. The iColumnId and bReverse members can be ignored (these are only relevant for automatic grouping).

If you are grouping the data on two levels, you'll need to provide two labels in the aTheGroups arrays. This means that you will have to define two items in the array, like this:

Move "Brand" to aTheGroups[0].sLabel
Move "Pricing category" to aTheGroups[1].sLabel


STEP 3

The second array that should be filled is the tWebGroupHeader struct array. This defines the values that will be shown in the group headers. The following struct members need to be set:

- Integer iGroupIndex: Set it to 0 for the first grouping level, 1 for the second grouping level, etc.
- Integer iParentHeaderIndex: Set it to -1 for the first grouping level, 0 for the second grouping level, etc.
- String sItem: Set it to the group value

When there should be multiple group headers above a line, then iParentHeaderIndex of tWebGroupHeader should be set.
With these two additional struct arrays the grouping is defined, but there is no data yet. This will be done in step 4.

STEP 4

The first parameter of the OnManualLoadData procedure is a tWebRow struct, which is where the weblist data should go.
The data can be manually filled like this:

Move iRow to aTheRows[0].sRowId		// iRow can start at 0 and increment for each row
Move "value_col1" to aTheRows[0].aCells[0].sValue
Move "value_col2" to aTheRows[0].aCells[1].sValue


Then, each FIRST row that should be grouped under a particular grouping header must be bound to an item from the just defined tWebGroupHeader struct array:

Move 0 to aTheRows[0].iGroupHeaderIndex // Bind to group header


The other rows that follow under the same grouping header must have iGroupHeaderIndex set to -1, like this:

Increment iRow

Move iRow to aTheRows[1].sRowId		// iRow can start at 0 and increment for each row
Move "nextrowvalue_col1" to aTheRows[1].aCells[0].sValue
Move "nextrowvalue_col2" to aTheRows[1].aCells[1].sValue
Move -1 to aTheRows[1].iGroupHeaderIndex // Bind to group header



Sample

The following code example shows how to implement simple manual grouping in a weblist. It will render a list with 5 rows divided into 2 groups, like this:

Object oCarsPerBrandList is a cWebList
    Set pbFillHeight to True
    Set peGrouping to grpCustom
    Set pbDataAware to False

    Object oCarColumn is a cWebColumn
        Set psCaption to "Car"
        Set piWidth to 50
    End_Object

    Procedure OnManualLoadData tWebRow[] ByRef aTheRows String ByRef sCurrentRowID tWebGroupConfig[] ByRef aTheGroups tWebGroupHeader[] ByRef aTheGroupHeaders
        //  Define that we have one level of grouping
        Move "Brand" to aTheGroups[0].sLabel

        //  Group Mercedes
        Move "Mercedes" to aTheGroupHeaders[0].sItem
        Move 0 to aTheGroupHeaders[0].iGroupIndex
        Move -1 to aTheGroupHeaders[0].iParentHeaderIndex

        //  Rows for group Mercedes
        Move 0 to aTheRows[0].sRowId
        Move "540K" to aTheRows[0].aCells[0].sValue
        Move 0 to aTheRows[0].iGroupHeaderIndex // Bind to group header

        Move 1 to aTheRows[1].sRowId
        Move "300d" to aTheRows[1].aCells[0].sValue
        Move -1 to aTheRows[1].iGroupHeaderIndex

        Move 2 to aTheRows[2].sRowId
        Move "230SL" to aTheRows[2].aCells[0].sValue
        Move -1 to aTheRows[2].iGroupHeaderIndex

        //  Group BMW
        Move "BMW" to aTheGroupHeaders[1].sItem
        Move 0 to aTheGroupHeaders[1].iGroupIndex
        Move -1 to aTheGroupHeaders[1].iParentHeaderIndex

        //  Rows for group BMW
        Move 3 to aTheRows[3].sRowId
        Move "Isetta" to aTheRows[3].aCells[0].sValue
        Move 1 to aTheRows[3].iGroupHeaderIndex // Bind to group header

        Move 2 to aTheRows[4].sRowId
        Move "M3 E30" to aTheRows[4].aCells[0].sValue
        Move -1 to aTheRows[4].iGroupHeaderIndex
    End_Procedure

    Procedure OnLoad
        Send GridRefresh
    End_Procedure
End_Object



Manual Grouping with Automatic Filling

--------------------------------------
Manual grouping does not require that filling of the weblist should also be done manually. The following code example shows how the rows can be filled from the database while manually creating grouping.

Procedure OnManualLoadData tWebRow[] ByRef aTheRows String ByRef sCurrentRowID tWebGroupConfig[] ByRef aTheGroups tWebGroupHeader[] ByRef aTheGroupHeaders
    Integer iRow iOrder iGroupHead
    
    Move "Order" to aTheGroups[0].sLabel
    Move -1 to iGroupHead
    
    Clear OrderDetail
    Find GT OrderDetail by 1

    While (Found)
        If (OrderDetail.Order_Number <> iOrder) Begin
            Relate OrderDetail
            
            Get LoadGridRow to aTheRows[iRow]
            Move OrderDetail.Order_Number to iOrder
            
            Increment iGroupHead
            
            Move (SFormat("%1 (%2)", OrderHeader.Order_Number, Trim(Customer.Name))) to aTheGroupHeaders[iGroupHead].sItem
            Move (SFormat("%1", FormatCurrency(OrderHeader.Order_Total, 2))) to aTheGroupHeaders[iGroupHead].sTotal
            Move -1 to aTheGroupHeaders[iGroupHead].iParentHeaderIndex
            Move 0 to aTheGroupHeaders[iGroupHead].iGroupIndex
            Move iGroupHead to aTheRows[iRow].iGroupHeaderIndex
        End
        Else Begin
            Get LoadGridRow to aTheRows[iRow]
        End
        
        Increment iRow

        Find GT OrderDetail by 1
    Loop
    
    Clear OrderDetail
    
    // Select the first row
    If (SizeOfArray(aTheRows) > 0) ;
        Move aTheRows[0].sRowId to sCurrentRowID
End_Procedure

Changing Rows

Use the following interface to programmatically manipulate the currently selected row:

Send
MoveToFirstRow to make the first row the selected row.
Send MoveToLastRow to make the last row the selected row.
Send MovePageUp to select the row one page up. The size of a page depends on the height of the list.
Send MovePageDown to select the row one page down. The size of a page depends on the height of the list.
Send MoveUpRow to select the previous row.
Send MoveDownRow to select the next row.
Send MoveToRow to select a specific row by its row index.
Send MoveToRowByID to selects a specific row by its RowId.


Configurable Column Layout

The cWebList and cWebGrid classes allow for a configurable column layout, which includes moving and hiding columns.

pbAllowColumnReordering determines whether columns can be reordered by dragging them to a different position. Dragging is done by picking up the column by its header.

pbAllowColumnHiding determines whether columns can be hidden. Hiding can be done using a context menu on the header or by dragging them outside of the grid.

pbStoreColumnLayout indicates whether the column layout is automatically stored locally on the client using local storage. When the application / view is loaded, it will check and automatically apply a stored layout.

ApplyColumnLayout applies a column layout.

ResetColumnLayout resets the column layout to the original design time layout.

OnColumnLayoutChanged is called when the user changes the layout (resizes / drags or hides a column).

pbHidden indicates whether a cWebList column is currently hidden or not.


Searching

List objects support a search capability. If the list object has the focus then simply begin typing the row value you are looking for. This pops-up a search window where you can continue typing the value that you are searching for. The search value is used to locate the closest matching row according to the current sort order of list rows.

Set pbAutoSearch to false to disable the automatic search capability.

Send Search to programmatically pop-up the search dialog to initiate a search.

OnChangeCurrentRow Event

By default, the list class does not send the OnChangeCurrentRow event to the server each time the currently selected row is changed. This is to optimize the performance of row selection in the cWebList class.

Set pbOfflineEditing to true to enable OnChangeCurrentRow events.


Drag and Drop Support

Drag Support

This control allows parts of itself to be picked up and dragged to other places in your interface. To do this, register it as a drag source in a cWebDragDropHelper.

Supported drag actions for this control are:
- C_WebDragListRow

Drop Support

This control can be used as a valid drop target by registering it as such in a cWebDragDropHelper object.

The supported drop actions for this control are:
- C_WebDropOnControl, C_WebDropListRow

File Drop Support

This allows the control to accept data dragged from elsewhere in the control (if configured that way) and can also be used to accept files when registered within a cWebDragDropFileHelper.


Appearance

The colors used by a list (or grid) control are determined by the web application's CSS theme (psTheme). You can define additional or replacement CSS styles in your web application's application.css file.

Set psCSSClass to the CSS class you would like to be applied to the overall list control.

Implement the OnDefineRowCssClass even to apply individual CSS styles to each row.

Each column class defines additional properties and events for applying special CSS styles to the column.

See Also

Styling Web Applications