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.parse()
The pb.parse() function takes a string and tries to parse it assuming the string content follows the property baf syntax. This is for example useful for property bags that are stored in a database.
// This return null as pb.get() will only work on property bags and not on strings
pb.get( '< firstName: "Bertus" surName: "Dispa" >', 'firstName' )
// Doing a pb.parse() on the string turns the string into a valid property bag
pb.get( pb.parse( '< firstName: "Bertus" surName: "Dispa" >' ), 'firstName' )
pb.dump()
The pb.dump() function is the opposite of pb.parse() and returns a property bag as a string. An optional 2nd parameter can be used to return a formatted string for increased readability.
pb.dump( < firstName: "Bertus" surName: "Dispa" >, formatted:true() )
pb.set()
The function pb.set() is used to insert / set an element in a property bag. You would typically use pb.set() to programatically massage or construct property bags.
script(
set( 'pb', <> ),
pb.set( [pb], 'firstName', 'Bertus' ),
pb.set( [pb], 'surName', 'Dispa' ),
pb.set( [pb], 'address/street', 'Coronation Street' ),
pb.set( [pb], 'address/houseNumber', '12' ),
pb.set( [pb], 'address/postCode', 'NG123AB' ),
pb.dump( [pb] )
// Results in <firstName: "Bertus", surName: "Dispa", address: <street: "Coronation Street", houseNumber: "12", postCode: "NG123AB">>
script(
set( 'pb', <> ),
pb.set( [pb], formatString( '{0}/title', add( pb.count( [pb] ), 1 ) ), 'Atlas Shrugged' ),
pb.set( [pb], formatString( '{0}/title', add( pb.count( [pb] ), 1 ) ), 'Waterloo' ),
pb.set( [pb], formatString( '{0}/title', add( pb.count( [pb] ), 1 ) ), 'A brief history' ),
pb.dump( [pb] )
)
// Results in <1: <title: "Atlas Shrugged">, 2: <title: "Waterloo">, 3: <title: "A brief history">>
)
Note: see also pb.push() for dealing with arrays in property bags.
pb.has()
We have already seen pb.get() in action. This function will always return a value, even if it is null(). You can use the pb.has() function to check whether a property bag has a certain key.
script(
set( 'pb', < firstName: "Bertus" surName: "Dispa"> ),
pb.has( [pb], 'firstName' )
)
// Results in True
script(
set( 'pb', < firstName: "Bertus" surName: "Dispa"> ),
pb.has( [pb], 'dob' )
)
// Results in False
script(
set( 'pb', < firstName: "Bertus" surName: "Dispa"> ),
pb.has( [pb], 1 )
)
// Results in True
script(
set( 'pb', < firstName: "Bertus" surName: "Dispa"> ),
pb.has( [pb], 16 )
)
// Results in False
pb.tryGet()
The function pb.tryGet() is a combination of pb.has() and pb.get().
The function pb.tryGet() takes 3 parameters:
| --- | --- | | Parameter | Use | | Property bag | Handle to a property bag (or a property bag literal) | | Path | Path to search for | | Variable name | Variable name to store value as (when found) |
The function returns true() when the property bag contains a tag for the path and false() otherwise.
set( 'pb', < firstName: "Bertus" surName: "Dispa" > )
if pb.tryGet( [pb], 'firstName', 'firstName' )
return [firstName]
else
return '?'
end-if
pb.keys()
The pb.keys() function returns a property bag with the keys of property bag entries. Note that internally, each property bag tag has a key, even if one was not assigned explicitly.
script(
set( 'pb', <> ),
pb.set( [pb], formatString( '{0}/title', add( pb.count( [pb] ), 1 ) ), 'Atlas Shrugged' ),
pb.set( [pb], formatString( '{0}/title', add( pb.count( [pb] ), 1 ) ), 'Waterloo' ),
pb.set( [pb], formatString( '{0}/title', add( pb.count( [pb] ), 1 ) ), 'A brief history' ),
pb.dump( pb.keys( [pb] ) )
)
// Results in <"1", "2", "3">
script(
set( 'pb', <> ),
pb.push( [pb], 'Atlas Shrugged' ),
pb.push( [pb], 'Waterloo' ),
pb.push( [pb], 'A brief history' ),
pb.dump( pb.keys( [pb] ) )
)
// Results in something like <"3564c76a-ae07-4c3d-8bb0-2fe97b53e3d6", "594d3992-4a62-465e-a1cf-5e1d29b2f3e4", "2648ed81-3e87-49a9-8772-aa84e2fe3190">
script(
set( 'pb', < "Bertus", "Dispa"> ),
pb.dump( pb.keys( [pb] ) )
)
// Results in something like <"a203c8c4-733f-4b11-b14f-905e10e1b679", "77b0169d-6483-437a-a29f-a20a8fa626aa">
pb.merge()
The pb.merge() function is designed to merge two property bags.
script(
set( 'pb1', < firstName: "Bertus" surName: "Dispa" > ),
set( 'pb2', < street: "Coronation Street" houseNumber: "12" postCode: "NG123AB" > ),
pb.merge( [pb1], [pb2] )
)
// Results in something like <firstName: "Bertus", surName: "Dispa", street: "Coronation Street", houseNumber: "12", postCode: "NG123AB">
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 (discussed here).
pb.dump(
pb.sort(
[books],
function ( a, b )
if lt( pb.get( [a], 'title' ), pb.get( [b], 'title' ) )
return -1
else-if eq( pb.get( [a], 'title' ), pb.get( [b], 'title' ) )
return 1
else
return 0
end-if
end-function
)
)
pb.filter()
The pb.filter() function makes use of Lambda functions (discussed here).
pb.dump(
pb.filter(
[books],
function ( book )
return match( pb.get( [book], 'title' ), 'eloquent' )
end-function
)
)
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 statements, 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>