Modifiable Lists and Maps

Native Freemarker hashes and arrays are not designed to be modified. Titania provides a number of tools for working with modifiable lists and maps instead.

There are cases in a portal theme page or offline packager where it can be beneficial to build up temporary caches of information to be reused or aggregated elsewhere in the page/package. For instance, a package script may want to cache the URLs of various pages as they are built. Unfortunately, Freemarker hashes and sequences are ill-suited to this sort of task.

Sequences and hashes in Freemarker can be created programmatically.

<#assign arr = []/>
<#assign hash = {}/>

They can also be updated.

<#assign arr = arr + [newElement]/>
<#assign hash = hash + {key: value}/>

However, the Freemarker documentation discourages open-ended concatenation, stating:

Note that hash concatenation is not to be used for many repeated concatenations, like for adding items to a hash inside a loop. While adding together hashes is fast and is constant time (independent of the size of the hashes added), the resulting hash is a bit slower to read than the hashes added together. Thus after tens or hundreds of additions the result can be impractically slow to read.

and

Note that sequence concatenation is not to be used for many repeated concatenations, like for appending items to a sequence inside a loop. It's just for things like <#list users + admins as person>. Although concatenating sequences is fast and is constant time (it's speed is independently of the size of the concatenated sequences), the resulting sequence will be always a little bit slower to read than the original two sequences were. Thus, after tens or hundreds of repeated concatenations the result can be impractically slow to reader (sic.).

In fact, in some unusual circumstances with huge collections, exceptions can be thrown when attempting to read from a concatenated hash or sequence.

Titania Delivery provides a set of Freemarker directives or functions that enable the creation and modification of mutable collections that do not suffer from the limitations of sequences and hashes.

newSet and newList

The newSet and newList directives and functions can be used to instantiate mutable lists of values. The only difference between the two collection types are:

  • Values in sets are unique; attempts to add the same value multiple times will not modify the list.
  • Values in lists can be removed by their index via the removeIndex directive, which is not available on sets.

Instantiation

Instances of sets and lists are instantiated using the newSet or newList objects. These objects can be invoked as either directives or functions.

<#-- As a directive -->
<@newSet var="mySet"/>
<@newList var="myList"/>

<#-- As a function -->
<#assign mySet = newSet()/>
<#assign myList = newList()/>

Once instantiated, the variable can be used as a prefix for the various directives and properties of the collection.

Parameters

var or assign
The global variable name to assign the new object. This parameter cannot be passed to the functional form.
<@newSet var="mySet"/>
<@newSet assign="mySet"/>
<#-- Identical to: -->
<#assign mySet = newSet()/>
local
The local variable name to assign the new object. The current processing context must be within a <#function> or <#macro>. This parameter cannot be passed to the functional form.
<@newList local="myList"/>
<#-- Identical to: -->
<#local myList = newList()/>
from
Optional. A Freemarker sequence that will be the starting values for the object. Passed as the first argument to the functional form.
<@newSet var="mySet" from=['a', 'b', 'c']/>
<#assign mySet = newSet(['a', 'b', 'c'])/>

Directives

Sets provide sub-directives that can be used to modify the contents of the set.

add
Used to add values to the set using the value parameter.
<@mySet.add value="someValue"/>
clear
Removes all values from the collection.
<@myList.clear/>
removeValue
Removes a value from the collection using the value parameter.
<@mySet.removeValue value="someValue"/>
removeIndex (lists only)
Removes a value from a list using the index parameter.
Important: This directive is not present on sets, and attempts to invoke it on sets will cause errors.
<@myList.removeIndex index=4/>

Properties

Sets and lists have the following properties.

size
The number of elements in the collection.
${mySet.size}
asSequence
The collection as a native Freemarker sequence. This is the primary mechanism by which the data in the collection should be read.
Note: There is no meaningful processing or memory overhead when using this property. It is a reference to the underlying storage for the object, it is not a copy.
${myList.asSequence[0]}
${mySet.asSequence?join('<br>')}
<#if myList.asSequence?seq_contains('someValue')>
  <#-- Conditional code here -->
</#if>
json
The set as a JSON string.
<pre>${mySet.json?html}</pre>

newMap

The newMap directive and function can be used to instantiate mutable hash maps. Keys in mutable hashes must be strings.

Instantiation

<#-- As a directive -->
<@newMap var="myMap"/>

<#-- As a function -->
<#assign myMap = newMap()/>

Parameters

var or assign
The global variable name to assign the new object. This parameter cannot be passed to the functional form.
<@newMap var="myMap"/>
<@newMap assign="myMap"/>
local
The local variable name to assign the new object. The current processing context must be within a <#function> or <#macro>. This parameter cannot be passed to the functional form.
<@newMap local="myMap"/>
from
Optional. A Freemarker hash that will be the starting entries for the map. Passed as the first argument to the functional form.
<@newMap var="myMap" from={"index": 5, "title": "Some Title"}/>
<#assign myMap = newMap({"index": 5, "title": "Some Title"})/>

Directives

Maps provide sub-directives that can be used to modify the contents of the map.

put
Used to add entries to the map using the key and value attributes. The key must be a string. The value can be anything.
<@myMap.put key="someKey" value="someValue"/>
clear
Removes all entries from the map.
<@myMap.clear/>
remove
Removes an entry from the set using the key parameter.
<@myMap.remove key="someKey"/>

Properties

Maps have the following properties.

size
The number of elements in the map.
${myMap.size}
keys
The keys in the map as a Freemarker sequence.
<#if myMap.keys?seq_contains('foo')>
  <#-- Conditional code here -->
</#if>
asHash
The map as a native Freemarker hash. This is the primary mechanism by which the data in the map should be read.
Note: There is no significant processing or memory overhead when using this property. It is a reference to the underlying storage for the object, it is not a copy.
${myMap.asHash['someKey']!'No entry for someKey'}
<#if myMap.asHash?keys?seq_contains('someKey')>
  <#-- Conditional code here -->
</#if>
json
The map as a JSON string.
<pre>${myMap.json?html}</pre>