Pages - Lists

(note: list is the CaseMaster® term form the Bootstrap table)

Lists make up a fair part of all online systems. CaseMaster® offers a number of main variations:

  • List. A simple list with columns, you can optionally click on a row
  • Filtered list. A list with a quick search option
  • Extended filtered list. A filtered list with extended search options
  • Tile lists. List or filtered list with tiles rather than rows
  • Check list. A simple list allowing you to select one or more items and action them
  • Check filtered list. A filtered list allowing you to select one or more items and action them

The following code shows the simple list <@page/data/table> qualifier, note the iterator.ofEntity() at the heart of the code, look here for more information.

inherits 'base'

function main()
    page.render(page.get('./main'))
end-function

resource main
    <@page/container
        content: <@page/content
            <@page/data/table
                iterator: iterator.ofEntity(
                    entities: <
                        <
                            name: 'contract'
                            entity: 'prm/contract'
                            orderby: 'name'
                        >
                    >,
                    rows: 2
                )
                group: 'id,name,_status'
            >
        >
    >
end-resource

The <@page/data/table> has a number of tags which are shared by all list variations. The most important tags are:

Tags Usage
tableClass Specifies one or more class names for the table
includeHeader Will a header row be included
headClass Specifies one or more class names for the table head
rowClass Specifies one or more class names for the rows
rowExclude A Boolean flag to state if a row should be exclude
rowLink Specifies a link for a row
responsive Force responsive table (see Bootstrap documentation)
compact Force compact table (see Bootstrap documentation)
bordered Force nordered table (see Bootstrap documentation)
striped Force striped table (see Bootstrap documentation)
rightAlignNumbers Enable right-aligned numerical values (defaults to true())
iterator The iterator containing the data to be listed
group Defines the tables display group
sortable Enable sorting of the table columns
export Enable exporting of the table
pagingMode Specify what sort of paging mode you want

Group

The group tag defines which columns are displayed. The simplest version would be something like this:

    group: 'description'

If the iterator has multiple entities, the list will show the description group for each of the entities.

You can optionally prefix each entry in the ad-hoc group with the name of the entity:

    group: 'client:description'

The following is an extended example:

    <@page/data/table
        iterator: iterator.ofEntity(
            entities: <
                <
                    name: 'object'
                    entity: 'om/object'
                    orderBy: 'reference'
                >
                <
                    name: 'location'
                    entity: 'crm/location'
                    orderBy: 'postcode'
                >
            >
        )
        group: 'location:_address,object:description,-object:status'
    >

The rowlink tag defines what happens when a user clicks on a table row. The following is a commonm example:

    rowLink: <@page/table/link
        url: <@url
            address: 'activityStart'
            qs: <
                activity: $bo.pk( [activity] )
            >
        >
    >

Note that the activity tag uses a $-expression (as we have seen with BO dynamic attributes). This makes sense as the expression refers to the entity of the iterator and this is only available at runtime, not when the property bag is retrieved.

The following example is from a system that makes use of the base layer:

    rowLink: <@page/table/link
        url: <@url
            address: 'objectAddSelectObjectGroup'
            qs: <
                relation: $bo.pk( [relation] )
            >
        >
    >

Export

The export tag is used to define whether and how the table data can be exported. The simplest format is as follows:

    export: true()

Or you can allow / disallow exporting based on the users' group:

    export: inGroup( 'ADMIN') 

The export tag can however have a lot more information:

    export: inGroup( 'ADMIN') 

The following shows an extended example:

    export: <@page/data/table/export
        iterator: $bo.invoke(
            'application/data/bo', 
            'list',
            name: 'entity',
            page: 1,
            limit: 9999,
            filter: qs.getUntrusted('filter'),
            attrs: true()
        )
        group: '*'
    >

See the @page/data/table/export for details.

Paging Modes

The standard paging-mode for a table is full (unless you make use of the rowExlude option in which case the system falls back to the lookAhead mode). The options are:

Option Usage
Full (Default) show current page, total number of pages, total number of rows; ability to go to previous- next page and several pages by number
Basic Only allow going to previous- and next page
None No paging features
LookAhead Allow going to previous- and next page and various previous page and one next page by number

The paging mode full requires the system to count the number of rows and will thus result in an extra query. Use basic or lookAhead if this takes up too many database resources.

    // More table stuff here
    pagingMode: pagingMode.basic
    export: true()
    // More table stuff here

Using RowExclude

You can use the rowExclude option to exclude rows based on an expression. This can be useful for advanced scenarios where you cannot exclude rows through the where clause of the iterator.

The following example excludes all rows that do not have a digit in the name:

    // More table stuff here
    rowExclude: $not( match( bo.attr( [relation], 'name' ), '[0-9]' ) )
    // More table stuff here

Note that rowExclude must be a $-expression.

Using RowClass

The rowClass tag can be used to assign a specific class to a row; often based on specific values.

The following example demonstrates how a row will have the text-muted class added when the status equals 3. Notice the $-expression.

    rowClass: $if( eq( bo.attr( [login], 'status' ), 3 ), 'text-muted', null() )

Totallers

Totallers are part of the base-layer and are used to add totals to specific table columns.

    totallers: <
        <@page/data/table/totaller
            attribute: 'linePrice'
        >
    >

See the qualifier @page/data/table/totaller for more information.

Sticky Headers

You can create 'sticky headers' (where the header always is visible and cannot be scrolled out of view)

Simply add the class sticky-top to the classes for the table header:

    // More table stuff here
    headClass: 'bg-primary text-light sticky-top'
    group: 'type.description'
    rowLink: <@page/table/ilnk
    // More table stuff here

Filtered Lists

The filtered list is probably the most common list in any CaseMaster® application.

The filtered list is an extension to the basic table and adds a filter to the top and, optional, buttons below the filter (Add currency... in the example).

function main()
    page.render( page.get( './main' ) )
end-function

resource main
    <@page/container
        content: <@page/content
            <@page/title label: <@mlText 'Currencies' > >
            <@page/data/table/qsearch   // @page/data/table/qsearch instead of @page/data/table
                iterator: iterator.ofEntity(
                    entities: <
                        <
                            name: 'currency'
                            entity: 'rd/currency'
                            orderby: 'id'
                        >
                    >,
                    rows: 100
                ),
                group: 'description'
                rowLink: <@page/table/link 
                    url: <@url 
                        address: 'edit'
                        qs: <
                            pk: $bo.pk( [currency] )
                        >
                    >
                >
                content: <@page/content
                    // This is where the button(s) below the filter are defined
                    <@page/button/new
                        label: <@mlText 'Add currency...'>
                        url: 'create'
                    >
                >
            >
        >
    >
end-resource

There are a number of important additional tags that qsearch offers over the standard table:

Tag Usage
hold Do not show the list until the user has done the first search
searchGroup Specify the attribute group to include in the quick search (default is search )
persist Save the filter value (and page and sorting) when user moves to another page
placeholder Hint inside the filter box

Extended Filtered Lists

The extended filtered list can be used to add additional search options to the quick search.

The following code example shows the extended search in action (the code example is simplified for clarity):

Note how the developer has to translate the extended filter fields into the relevant where clause.

function main()
    // Create filter
    set( 'filter', boDesc.create( 'contact' ) )
    boDesc.setAttrLocked( [filter], '*', false() )
    boDesc.setAttrIsOptional( [filter], '*', true() )
    boDesc.setTable( [filter], null() )
    boDesc.setEnhancer( [filter], 'noAudits', true() )

    // Add additional attributes to filter
    boDesc.addAttr(
        [filter],
        '_addressFilter',
        <@bo/attribute
            label: <@mlText 'Address'>
            dataType: dataType.String
            length: 100
        >
    )

    // Render the page
    page.render(page.get('./main'))
end-function

resource main
    <@page/container
        content: <@page/content
            <@page/data/table/qsearch/extended
                filter: <@page/data/table/qsearch/extended/filter
                    name: '//filter'
                    entity: [filter]
                    groups: 'company, _addressFilter'
                >
                iterator: iterator.ofEntity(
                    <
                        <@iterator/entity
                            name: 'contact'
                            entity: 'contact'
                            where: $buildString(
                                '&',
                                if( isNotNull( bo.attr( [//filter], 'company' ) ), 'company={ bo.attr( [//filter ], "company" ) }', null() ),
                                if( isNotNull( bo.attr( [//filter], '_addressFilter' ) ), '#exists[ company/address, #qsearch( search, { bo.attr( [//filter], "_addressFilter" ) } ) ]', null() )
                            )
                        >
                    >
                )
                group: 'description'
                content: <@page/content
                    // Stuff here to show buttons
                >
                rowLink: <@page/table/link
                    // Stuff here to handle row-click
                    >
                >
            >
        >
    >
end-resource

The base-layer offers a more sophisticated and easier to use extended list.

In addition to the filter options offered by the standard extended search list it also offers an option to add attributes that are automatically translated to the appropriate where clause (i.e. no need to take into consideration in the where clause) and where the user has control over how to search (e.g. equals, greater than, starts with, etc).

    <@page/data/table/qsearch/extended
        filter: <@page/data/table/qsearch/extended/filter
            entities: <@page/data/table/qsearch/extended/filter/entity
                <
                    name: '//contractExtended'
                    entity: 'contract'
                    iteratorEntityName: 'contract'
                    groups: 'extendedSearch'
                >
            >
        >
        iterator: iterator.ofEntity(
            <
                <@iterator/entity
                    name: 'contract'
                    entity: 'contract'
                    where: $if(qs.get('active'), '#is("isActive")', null())
                    load: 'description'
                >
            >,
            rows: 25
        )

In addition to the standard tags, the qualifier also supports entities which is a collection of page/data/table/qsearch/extended/filter/entity.

Tag Usage
name Name used to store the search settings as; must be a variable name in the global context
entity The entity name
iteratorEntityName The name of the entry in the iterator
groups The attribute groups (of the entity) you want to include in the extended search

Note that the developer does not have to worry about the where clause in the iterator.

Tile Lists

The tle list and tle quick search list work very similar to the standard list and quick search with the exception that a tile is shown for each record instead of a row in a table.

  • Ideal where the user is expected to select an item from a small series of distinctly different items
  • There is not table and thus no header and not option to sort the tiles
  • Paging works in exactly the same way as a standard list
    function subClaimDetails()
        page.render(page.get('./subClaimDetails'))
    end-function

    resource subClaimDetails
        <@page/container
            content: <@page/content
                page.get('./tabs') // Include tabs
                <@page/data/tiles
                    iterator: iterator.ofEntity(
                        <
                            <@iterator/entity
                                name: 'subClaim'
                                entity: 'claim/subClaim'
                                where: 'claim={ qs.get( 'claim' ) }'
                            >
                        >
                    )
                    shape: 'portrait'
                    size: 'large'
                    pagingMode: pagingMode.none
                    tile: <@page/data/claim/subClaimCard subClaim: $[~/subClaim] >
                    rowLink: <@page/table/link
                        url: <@url
                            address: 'fnol/capture:reportedDetails'
                            qs: <
                                contract: $bo.pk( [subClaim] )
                            >
                        >
                    >                    
                >
            >
        >
    end-resource

The key tag is tile which defines what each tile looks like. The easiest is to use the @page/tile qualifier:

    tile: <@page/tile
        header: $bo.attr( [~/contract], 'name' )
        headerClass: 'text-center text-white bg-primary h3'
        body: <@page/html
            $resolveTemplate(
                `
                <div class="tileImageVerticalCenterWrapper">
                    {{ bo.attr( [~/contract], '_logoSmall' ) }}
                </div>
                `
            )
        >
    >
Tag Usage
id Optional id (you may want to refer to in Javascript or .css)
name Optional name (you may want to refer to in Javascript or .css)
header Optional content of the header
headerClass Optional class of the header
body Optional content of the body
bodyClass Optional class of the body
footer Optional content of the footer
footerClass Optional class of the footer
headerActions Optional collection of drop-down actions that will be shown in hamburger menu at top

Example of headerActions with 2 actions:

    headerActions: <@page/content
        <@page/button/dropdown/link
            label: <@mlText 'Edit driver...'>
            url: <@url
                address: 'fnol/subClaimPolicyHolder:driver'
                qs: <subClaim: bo.pk([sub]), edit: true()>
            >
        >
        <@page/button/dropdown/link
            label: <@mlText 'Edit passengers...'>
            url: <@url
                address: 'fnol/subClaimPolicyHolder:passengers'
                qs: <subClaim: bo.pk([sub]), edit: true()>
            >
        >
    >

More advanced tags of the qualifier @page/data/tiles and @page/data/tiles/qsearch are:

Tag Usage
size Size of tile - tiny, small, medium (=default) or large. Ignored when ownTileStyle=true()
shape Shape of tile - square (=default), portrait or landscape. Ignored when ownTileStyle=true()
ownTileStyle Indicates that tiles have no styling by default. When set to true, size and shape are ignored

Check Lists

Note: this section only explains the check list that is part of the base-layer as this is the easiest to use.

The checklist (in list, qsearch and extended variations) combines the list with the logic of what to do with each selected row when you click submit.


    <@page/data/table/checklist

        // Nothing new here, iterator as usual
        iterator: iterator.ofEntity(
            entities: <
                <@iterator/entity
                    name: 'client'
                    entity: 'client'
                >
            >,
            rows: 50
        )

        // And the group of attributes to show
        group: 'description'

        // This is new: a lambda expression that takes one parameter which is the BO of the first entity of the iterator
        perRowAction: lambda.create(
            function( client )
                if eq( bo.attr( [client], 'title' ),1 )
                    bo.setAttr( [client], 'title', 2 )
                else
                    bo.setAttr( [client], 'title', 1 )
                end-if

                bo.persist( [client] )
            end-function
        )

        // Use mustSelectRows to indicate that the user must select rows
        mustSelectRows: true()

        // Display this message when the user has not selected any rows; when left out but mustSelectRows is set to true
        // the standard message 'No rows selected' is shown
        noRowsSelectedMessage: 'No clients are selected'

        // When you do not make use of redirectOnSuccess (see next), you can set a success message
        // The variable [//checkListRowsProcessed] will now have a value and be set to the number of affected records
        onSuccessMessage: $<@mlText '{0} Client(s) removed', parms: < 1:[//checkListRowsProcessed] > >

        busyMessage: <@mlText 'Doing something time consuming'>

        // You can also redirect on sucess, this option CAN NOT be used in combination with onSuccessMessage
        redirectOnSuccess: <@url
            address: 'done'
            qs: <
                rows: $[//checkListRowsProcessed]
            >
        >

        // You need a submit button to make use of this logic. Do not use redirect!
        // You can also use submits in which case you can have a collection of submit buttons each with their own
        // value. You can refer to [~/__submit] in the lambda function
        submit: <@page/button/submit>
    >

<End of document>