Property Bags, Qualifiers and Resources

reviewed: 15 February 2025

Property bags are best compared to JSON. A property bag is a single item that can hold multiple values.

The Empty Property Bag

The simplest property bag is the empty property bag and is created as follows:

    set( 'bag', <> )

A property bag starts with a < and ends with a >.

Property Bag Values

The following is an example of a property bag with 3 values (1, 16 and 22):

    set( 'pb', < 1 16 22 > )

Value may be separated by comma's but this is not necessary.

    set( 'pb', < 1, 16, 22 > )

A property bag can contain values of different types:

    set( 
        'pb', 
        < 
            'Joe Blogg'
            #12 Nov 1971#
            3 
        > 
    )

Also note how newlines and tabs do not affect a property bag.

A property bag can also contain property bags:

    set( 
        'pb', 
        < 
            'Joe Blogg'
            #12 Nov 1971#
            <
                'Deben Down'
                '12a'
                'NG182AF'
                'Mansfield'
            >
        > 
    )

Values in a property bag can also be tagged:

    set( 
        'pb', 
        < 
            name: 'Joe Blogg'
            dateOfBirth: #12 Nov 1971#
            address: <
                Street: 'Deben Down'
                houseNumber: '12a'
                town: 'Mansfield'
                postcode: 'NG182AF'
            >
        > 
    )

Property Bag Paths

You can use specific functions to get values from a property bag. The following example retrieves the postcode from the property bag created and assigned to the pb variable above:

    pb.get( [pb], 'address/postcode' )

The string address/postcode is known as a property bag path.

The following two paths would return the same value:

    pb.get( [pb], '3/postcode' )
    pb.get( [pb], '3/4' )

The address is the 3rd entry in the property bag and the postcode is the 4th value in the address sub-property bag.

Accessing a Property Bag as Simple Datatype

The following code snippet is again based on the pb variable that was created earlier.

    response.write( [pb] )

    // Displays Joe Blogg

What happens is that the response.write() function expects one parameter and assumes the parameter is a string. We have seen earlier that CaseMaster is lenient and will try to be helpful and will convert variables to the most suitable datatype.

In this example, the variable pb is a property bag. When a property bag is converted to a string, CaseMaster will simply take its first value and use that (note that 'name' is a tag and 'Joe Blogg' is the first value).

This is a very important behaviour of property bags that we will see in action in many places.

Property Bags as Arrays

Property bags are often used as arrays of entries of similar type. We have already seen that entries can be addressed by their tag name (where available) or by their positional number (starting at 1).

Tag names can also be numbers.

    set( 'pb', < 1:1, 2:16, 3:22 > )

But beware when the numbers stop representing their position:

    set( 'pb', < 1:1, 4:16, 8:22 > )

There are various property bag functions to help managing property bag arrays.

Property Bag Functions

The pb function handler contains all the functions relevant to property bags. There is however one function in the default handler that is related to property bags and that is to test whether a variable is a property bag:

    set(
        'pb',
        <
            1
            2
            3
        >
    )

    response.writeLine( isPB( [pb] ) )

    set( 'pb', 'Hello world' )

    response.writeLine( isPB( [pb] ) )

    // Displays True False 

We have already seen the pb.get() function in action. Other important property bag functions are:

General property bag functions:

pb.dump()

pb.parse()

pb.set()

pb.tryGet()

pb.has()

pb.keys()

pb.merge()

Property bag array functions:

Examples are based on the following property bag:

    set(
        'books',
        <
            <
                isbn: '9781593279509'
                title: 'Eloquent JavaScript, Third Edition'
                subtitle: 'A Modern Introduction to Programming'
                author: 'Marijn Haverbeke'
            >
            <
                isbn: '9781491943533'
                title: 'Practical Modern JavaScript'
                subtitle: 'Dive into ES6 and the Future of JavaScript'
                author: 'Nicolás Bevacqua'
            >
            <
                isbn: '9781593277574'
                title: 'Understanding ECMAScript 6'
                subtitle: 'The Definitive Guide for JavaScript Developers'
                author: 'Nicholas C. Zakas'
            >
        >
    )

pb.count()

Return the number of entries in the property bag.

    response.write( pb.count( [books] ) )

    // Displays 3

pb.slice()

Return a property bag containing the items between a start and (optional) end position.

    response.write( pb.slice( [books], 1, 2 ) )

    // Displays 9781491943533 (the first value of the resulting property bag)

    response.write( pb.count( [books] ) )

    // Displays 2

pb.push()

Add an entry to the end of a property bag.

    pb.push( 
        [books],
        <
            isbn: '9781449365035'
            title: 'Speaking JavaScript'
            subtitle: 'An In-Depth Guide for Programmers'
            author: 'Axel Rauschmayer'
        >
    )

    response.write( pb.count( [books] ) )

    // Displays 4

pb.pop()

Remove the last entry of a property bag:

    pb.pop( [books] )
    pb.pop( [books] )

    response.write( pb.count( [books] ) )

    // Displays 2

pb.merge()

Merge another property bag in place in a property bag and return the updated property bag.

    pb.count(
        pb.merge(
            [books],
            <
                isbn: '9781449365035'
                title: 'Speaking JavaScript'
                subtitle: 'An In-Depth Guide for Programmers'
                author: 'Axel Rauschmayer'
            >
        )
    )

    // Displays 3 now (after the pb.push and pb.pop examples)

pb.sort()

The pb.sort() function makes use of Lambda functions and will be discussed here.

pb.filter()

The pb.filter() function makes use of Lambda functions and will be discussed here.

Property Bags as JSON

The function json.pb2json() can convert a property bag to JSON and json.json2pb() can convert JSON to a property bag.

A property bag will best convert to JSON when you provide some hints:

    set(
        'books',
        <@json
            array: <@json/array
                <
                    isbn: "9781593279509"
                    title: "Eloquent JavaScript, Third Edition"
                    subtitle: "A Modern Introduction to Programming"
                    author: "Marijn Haverbeke"
                >
                <
                    isbn: "9781491943533"
                    title: "Practical Modern JavaScript"
                    subtitle: "Dive into ES6 and the Future of JavaScript"
                    author: "Nicolás Bevacqua"
                >
                <
                    isbn: "9781593277574"
                    title: "Understanding ECMAScript 6"
                    subtitle: "The Definitive Guide for JavaScript Developers"
                    author: "Nicholas C. Zakas"
                >
            >
        >
    )

    response.write( json.dump( json.pb2json( [books] ), true() ) )

    // Displays:
    //
    // {
    //     "array": [
    //         {
    //             "isbn": "9781593279509",
    //             "title": "Eloquent JavaScript, Third Edition",
    //             "subtitle": "A Modern Introduction to Programming",
    //             "author": "Marijn Haverbeke"
    //         },
    //         {
    //             "isbn": "9781491943533",
    //             "title": "Practical Modern JavaScript",
    //             "subtitle": "Dive into ES6 and the Future of JavaScript",
    //             "author": "Nicolás Bevacqua"
    //         },
    //         {
    //             "isbn": "9781593277574",
    //             "title": "Understanding ECMAScript 6",
    //             "subtitle": "The Definitive Guide for JavaScript Developers",
    //             "author": "Nicholas C. Zakas"
    //         }
    //     ]
    // }        

Key are the @json and @json/array additions, both examples of qualifiers which are discussed in the next paragraph.

Note that a property bag that is to be converted to JSON must have a non-array property bag as the outer structure; i.e. a 'stand-alone' array is not valid JSON (although routinely consumed and produced by web services).

Qualifiers

Qualifiers is a qualified property bags. This means that a type is associated with the property bag. We have already seen this in action in the previous paragraph with the @json qualifier.

    <@json
        name: 'Joe Blogg'
        dateOfBirth: #12 Nov 1971#
        address: <
            Street: 'Deben Down'
            houseNumber: '12a'
            town: 'Mansfield'
            postcode: 'NG182AF'
        >
    >

Behind each qualifier is a .cms file in the qualifier folder that implements the qualifier. Most qualifiers are designed for use in web development but can be used in many other scenarios; for example to instruct how a property bag is converted to JSON (as seen in the previous chapter).

The use and purpose of qualifiers is best explained when we are looking at pages (see here). I appreciate that qualifiers are somewhat abstract after reading this but promise that they make perfect sense once you see their power in action.

Resources

Resources are, like functions and conditions separate sections with a name unique in a .cms file.

Resources, unlike functions and conditions, do not have parameters.

The only content of a resource (obviously with exception of white-space and comments) is a property bag (qualified or not).

    resource email
        <
            subject: 'Lorem ipsum'
            body: 
            `
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
                Vivamus ac leo eu justo posuere mattis. 
                Integer laoreet vestibulum leo, vel auctor diam commodo nec. 
                Curabitur elit nibh, laoreet et ornare id, finibus in ante. 
                Fusce at sagittis sapien. In hac habitasse platea dictumst. 
                Etiam turpis nibh, suscipit vitae felis ac, blandit suscipit est. 
            `
        >
    end-resource

A resource can have public, static or internal as access modifier; static only relevant for resources in BO scripts and internal only relevant for resources in page scripts.

Resources cannot contain any logic, they can only contain one property bag. You can get a resource by its' name.

    set( 'pb', page.get( './email' ) )

The idea behind resources is that it can help developers separating function from definition. This has a number of benefits:

  • Property bags in resources are reusable (as they can be get from different places)
  • Separating function from definition helps to make code easier to understand and maintain

As with qualifiers, the purpose and power of resources will become clear we we look at pages.

<End of document>