tableDisplays a dynamic, filterable and sortable table of data
TypeDoc (Velocity Macro)
Developed by

XWiki Development Team

Rating
Rate!
0 Votes
LicenseGNU Lesser General Public License 2.1
Bundled With

XWiki Enterprise

Compatibility

XWiki Enterprise >= 1.9M2

Description

The most common use case of the livetable macro is displaying a table of documents that contains an XWiki Object of a certain class. This is a powerful way for in-wiki applications developers to offer a mining interface on their application data (for example Contacts in a CRM/Contact manager, Bills in a Billing application, etc.).

Usage

The signature of the macro is the following :

#livetable($id $columns $columnsProperties $options)

Each parameter allows to control the output generated by the macro. Namely:

  • $id is a string identifier that allows to distinguish this table from others. This permits to have several tables on the same page. This id is used in the generated HTML as prefix for some elements' ids.
  • $columns is an array that holds the ordered list of columns to display in the table.
  • $columnsProperties is a hash of properties (options) to customize the behavior of each of the columns
  • $options is a hash with general options (as opposed to columns options) for the table.

The following sections give more details and examples about the last three parameters, $options, $columns and $columnsProperties.

Parameter $options

Example

#set($myTableOptions = {
  "className":"XWiki.XWikiUsers",
  "translationPrefix" : "xe.index.users.",
  "tagCloud" : true,
  "rowCount": 10
})

All accepted values

Option nameDescriptionDefault valueSince
classNameThe full name of the XWiki page holding the class definition for the type of data to display in the table. For example: XWiki.XWikiUsers to display pages with objects of type users. If no className is given (nor any of the resultPage or url options - see below), the table will display all pages of the wiki. This parameter can be ignored by a custom data source if either the resultPage or the url parameters are used.None 
resultPageThe full name of the page to use as a JSON data provider for the table. This option allows to use a different data source than the default one (XWiki.LiveTableResults) for specific needs not handled by LiveTableResults (for example: complex cross-classes queries, external data retrieved with a groovy script, etc.). This parameter will be ignored if the url parameter is used.None 
urlThis is similar to resultPage, except that it accepts an URL instead of the full name of the page to obtain results from. This allows for example to add extra query parameters.None 
selectedColumnThe name of the column on which to sort the live table by default. If this option is absent, the first sortable column met in the $columns array will be used.None 
defaultOrderThe default order to sort on the selected column. Accepted values are asc and descasc 
rowCountThe maximum number of rows to display in one page of the table.15 
maxPagesThe maximum number of pages links to display in the pagination UI (Not including the links to the first and last pages that will always be displayed).10 
translationPrefixA string to prefix table translation keys with (for names of columns for example) in order to have different display messages (translated strings) for different tables.None 
tagCloudDisplay a tag cloud filter and display interface to allow users to see entries matching particular tags and to see which tags match the current filter selectionfalse 
selectedTagsThe list of tags that should be selected initially in the tag cloud. You can still select different tags from the tag cloud after the live table loads.[]5.4RC1
callbackAn advanced option to pass the name of a JavaScript method as custom handler of matched rows, leaving the responsibility of the DOM construction of the row entry, and its injection in the table to this method. This option should be used when complex manipulations are needed to construct row entries, which is not possible to do using the default handler. You can see an usage example of this option in the "All Attachments" UI in the XWiki Enterprise document index.None 
extraParamsUsed to add extra parameters to the Ajax request for the resultPage which generates the JSON data. Must start with an "&" (If you add multiple settings when using this option they have to be separated with an "&"). You should also use the EscapeTool to URL-encode any special character in your parameter values (e.g. the space name since that space can contain any character). For example: 'extraParams' : "&space=$escapetool.url('ComitĂȘ')". The parameters allowed depend on the implementation of the resultPage. For the default resultsPage (i.e. XWiki.LiveTableResults) here's what's supported:
  • space: Find all the documents from a given specific space (Nested Spaces are not included). Example: "extraParams" : "&space=${doc.space}" (for all the documents in the current space).
  • parent: Find all the documents having a specific parent field (not used anymore with Nested Spaces). Example: "extraParams" : "&parent=${doc.space}.WebHome"
  • orphaned: Find all the orphaned documents, i.e. documents having an empty parent field (not used anymore with Nested Spaces). Example: "extraParams" : "&orphaned=1"
  • location (new in XWiki 8.1M2): Finds all the documents having a part of their full reference path matching the passed value. Example: "extraParams" : "&location=pa" would match a document at France.Paris.Beaubourg.WebHome. Note that it's possible to filter using the / character instead of the . one.
  • Custom xproperty. Find all the documents having objects that have a specific value for a property ("extraParams" : "&yourProperty=yourValue" - note that in this particular case, yourProperty has to be declared in the $columns array). This option can also be used to view properties of classes other than the one declared in className ("extraParams" : "&yourProperty_class=yourSpace.yourClass" - sorting and filtering available from version 6.2.1.)
None 
topFiltersHTML fragment that will be placed in a "top filter" area in the same fashion as the Tag Cloud filter. All filters elements (input, select) in this fragment will be automatically used as filters for the livetable.None2.3 RC 1
pageSizeDisplay a selection box to allow users to change the number of rows displayed per page in the tabletrue2.4 M1
pageSizeBoundsDefines the page sizes available in the selection box that allows users to change the number of rows displayed per page in the table. This should be a valid initialization of a Javascript array of 3 integer values: the minimum page size, the maximum page size, and the steps between proposed page sizes. The default value propose a selection between 10 and 100 with a step of 10, which means 10, 20, 30, ..., 90 and 100 in the proposed list of sizes.[10,100,10]2.4 M1
queryFiltersOptionally specifies a comma-separated list of already registered Query filter names to apply. Pass the empty string if you don't want any query filters to be applied. If not specified, the default query filters are applied.
Note: This is not the place where you would add "WHERE" clauses, but it`s for the actual name of existing query filters (e.g. "unique", "hidden", etc.). If extra "WHERE" clauses is what you are looking for, then you should use the extraParams option instead.
currentlanguage, hidden6.0 M1
outputOnlyHtmlIf set to true, it ensures that the output of the livetable macro is only HTML markup, without the syntax specific wrappers ({{html}}{{/html}} macro wrapper for syntax 2.0 or {pre}{/pre} wrappers for syntax 1.0). It is most useful when using the livetable macro inside filesystem velocity templates.false6.3 M2

Parameter $columns

This parameter allows to define the columns of the Live Table. There are 3 major types of columns a table can declare: document columns, which will be displaying (and filtering on) metadata on the document (such as its author, date of last modification, etc.) object properties columns, for the case the table is bound to an XWiki Class, and special columns, for columns which are not handled by the first two types, such as the list of attachments of a document, the actions that can be performed. The table below in the example summarizes all the possible values that can be passed to the $columns array.

Example

#set($myColumns = ["_avatar", "first_name", "last_name", "email", "doc.creationDate", "_actions"])

All accepted values

NameDescription
doc.nameThe name of the document (for example, WebHome, in Sandbox.WebHome).
doc.titleThe title of the document.
doc.spaceThe space of the document (for example, Sandbox, in Sandbox.WebHome).
doc.fullNameThe full name of the document (for example Sandbox.WebHome).
doc.creationDateThe date at which the document was created.
doc.creatorThe username of the user that created the document.
doc.authorThe username of the last author of the document.
doc.dateThe date at which the document has been last modified.
${propertyName}Any property of an XWiki Class the table is bound to, or from any secondary classes. (See the className and extraParams from the $option argument for more information on how to bind a table to an XWiki Class and how to view propertis from other classes).
_imagesA special column to display all images attached to the retrieved document.
_attachmentsA special column to display links to all attachments of the retrieved document.
_actionsA special column to display a list of actions that can be performed by administrators on the matched documents.
_avatarA special column to display the user avatar. Works only for a table bound to the XWiki.XWikiUsers XWiki class.

Parameter $columnsProperties

Example

#set($columnsProperties = {
  "_avatar" : { "type" : "none", "link" : "none", "html" : "true", "sortable":false },
  "first_name" : { "type" : "text" , "size" : 20, "link" : "view"},
  "last_name" : { "type" : "text", "link" : "view"},
  "email" : { "type" : "text", "html" : "true" }
})

All accepted values

  • Document and object fields options:
NameDescriptionsDefault valueSince
displayNameThe name to display as a column header for this column (wins over the translationPrefix table option) (Note: available starting with XE 2.3)None2.3
filterableShould the column present a filter on its header?true12 
sortableShould the column be available as a sort key?true 
typeFor filterable columns, the type of filter for the column. Valid values are: hidden (allows to hide a column), text, list, number, boolean since 8.2M2, and multilist since 8.2M2 (allows the user to filter on multiple values of a list).None (no type) 
match(6.3M1+) For filterable columns, specifies how the filter value should be matched against the stored value. Possible values are: exact, partial, prefix. The default live table results page is currently supporting this option for the following property types: StringProperty, LargeStringProperty and DBStringListProperty. Custom live table results pages might not provide this option.None (depends on the column data type) 
sizeThe size of the filter field. CSS might override this value to make the field 100%.None 
linkThe type of link to use for this column. Can be one of:
  • auto: link to the URL provided by the <columnName>_url row property from the JSON results; falls back on the doc_url property
  • field: same as auto but without the fallback; this can be used for instance with Database List properties in order to link the column value to the corresponding XWiki document
  • editor: link to edit the corresponding object property
  • author: link to the URL provided by the doc_author_url row property from the JSON results
  • space: link to the URL provided by the doc_space_url row property from the JSON results
  • wiki: link to the URL provided by the doc_wiki_url row property from the JSON results
Any other non empty value will create a link to the URL provided by doc_url row property. Note that you need to set the doc_viewable row property to true in order for these values to be taken into account. Leave this empty or don't specify it at all if you don't want to link the column value.
None (no link) 
htmlShould the returned value be treated as HTML and injected as is in the row ?false 
headerClassThe name of an optional CSS class to add to the column HTML table column header.None2.3 RC 1
classSpecifies the full name of the XWiki class for the type of data to display in the table. Used by the filtering options in the live table header, when the $options hash has resultPage key instead of className.None 

Examples

The user directory

#set($columns = ["_avatar", "first_name", "last_name", "email", "doc.creationDate", "_actions"])
#set($columnsProperties = {
   "_avatar" : { "type" : "none", "link" : "none", "html" : "true", "sortable":false },
   "first_name" : { "type" : "text" , "size" : 20, "link" : "view"},
   "last_name" : { "type" : "text", "link" : "view"},
   "email" : { "type" : "text", "html" : "true"}
})
#set($options = {
  "className":"XWiki.XWikiUsers",
  "translationPrefix" : "xe.userdirectory.",
  "tagCloud" : true,
  "rowCount": 10
})
#livetable("userdirectory" $columns $columnsProperties $options)

The url option can also be used to generate the user directory:

#set($options = {
  "url" : "$xwiki.getURL('XWiki.LiveTableResults', 'get', 'outputSyntax=plain&transprefix=xe.index.users.&classname=XWiki.XWikiUsers&collist=_avatar,first_name,last_name,email,doc.creationDate')",
  "translationPrefix" : "xe.userdirectory.",
  "tagCloud" : true,
  "rowCount": 10
})

Demo (Video)

Document Index

#set($collist = ['doc.name', 'doc.space', 'doc.date', 'doc.author'])
#set($colprops = {
 'doc.name' : { 'type' : 'text' , 'size' : 30, 'link' : 'view' },
 'doc.space' : { 'type' : 'text', 'link' : 'space' },
 'doc.date' : { 'type' : 'date' },
 'doc.author' : { 'type' : 'text', 'link' : 'author' }
})
#set($options = {
 'translationPrefix' : 'xe.index.',
 'rowCount' : 15,
 'description' : 'This table lists all the documents found on this wiki. The columns can be sorted and some can be filtered.'
})
#if(!$isGuest)
 #set($discard = $collist.add('_actions'))
 #set($discard = $colprops.put('_actions', { 'actions' : ['copy', 'delete', 'rename', 'rights'] }))
#end
#livetable('alldocs' $collist $colprops $options)

Filter organizations by domain

The organization class in the example below has two properties: an org_name of type String and an org_domain of type StaticList. The organization domain can have values like Software or Hardware, which will be used in the live table as filter options.

#set($columns = ["org_name", "org_domain"])
#set($columnsProperties = {
   "org_name" : { "type" : "text" , "size" : 20, "link" : "view"},
   "org_domain" : { "type" : "list", "class": "MySpace.OrganizationClass"}
})

#set($options = {
  "resultPage":"MySpace.ListOrganizationJSON",
  "translationPrefix" : "",
  "rowCount": 10
})
#livetable("organization_directory" $columns $columnsProperties $options)

The content of MySpace.ListOrganizationJSON (using syntax 2.0): 

{{include reference="XWiki.LiveTableResultsMacros" /}}

{{velocity wiki="false"}}
#gridresultwithfilter("MySpace.OrganizationClass" $request.collist.split(",") "" " and doc.name<>'OrganizationSheet' and doc.name<>'OrganizationTemplate'")
{{/velocity}}

All Attachments

The livetable rows can also be created using a Javascript callback function. For example, the Attachments tab on Document Index:

{{velocity}}
#set($ok = $xwiki.jsx.use('XWiki.AllAttachments'))
##
#set($collist = ["filename", "doc.name","doc.space", "doc.date", "doc.author", "type"])
#set($colprops = {
                   "filename"  : { "type" : "text" , "size" : 10 },
                   "doc.name"  : { "type" : "text" , "size" : 10 },
                   "doc.space" : { "type" : "text" , "size" : 10 },
                   "doc.date"  : { "type" : "date" , "size" : 10, "filterable":false},
                   "doc.author": { "type" : "text" , "size" : 10 },
                   "type" : {"sortable": false}
                 })
#set($options = { "url":"$xwiki.getURL('XWiki.AllAttachmentsResults')?xpage=plain&outputSyntax=plain",
                  "callback" : "XWiki.index.displayAttachmentEntry",
                  "translationPrefix" : "xe.index.attachments." })
#livetable('allattachments' $collist $colprops $options)

(% id="inaccessibleDocsMessage" class="hidden" %)
(((
{{info}}$msg.get("rightsmanager.documentrequireviewrights"){{/info}}
)))
{{/velocity}}

The callback function created the columns for each row using the JSON generated by the url parameter:

XWiki.index.displayAttachmentEntry = function (row, i, table) {
 var inaccessibleDocs = false;
 if(row.acclev == true) {
   var fileLink = new Element('a', {'href' : row.fileurl}).update(row.filename);
   var tr = new Element('tr').update(new Element('td').update(fileLink));
   var pageLink = new Element('a', {'href' : row.url}).update(row.page);
    tr.appendChild(new Element('td', {'class' : 'pagename'}).update(pageLink));
   var spaceLink = new Element('a', {'href' : row.spaceurl}).update(row.space);
    tr.appendChild(new Element('td', {'class' : 'spacename'}).update(spaceLink));
    tr.appendChild(new Element('td').update(row.date));
   var aa = new Element('a', {'href' : row.authorurl}).update(row.authorname);
    tr.appendChild(new Element('td').update(aa));
   var type = new Element('td').update(getMimeTypeImage(row.type));
    tr.appendChild(type);
   return tr;
  } else {
   var tr = new Element('tr');
   var page = row.fullname;
   var td1 = new Element('td').update("unavailable");
   var td2 = new Element('td', {'class' : 'pagename'}).update(page + "*");
   var td3 = new Element('td').update(" ");
   var td4 = new Element('td').update(" ");
   var td5 = new Element('td').update(" ");
   var td6 = new Element('td').update(" ");
    tr.appendChild(td1);
    tr.appendChild(td2);
    tr.appendChild(td3);
    tr.appendChild(td4);
    tr.appendChild(td5);
    tr.appendChild(td6);
    $('inaccessibleDocsMessage').removeClassName('hidden');
   return tr;
  }
}

Custom JSON

Here's a minimal working example, displaying two fields ("user" and "message"). A slightly more elaborate example can be found in the code of the Link Checker extension.

Page using the livetable:

{{velocity}}
#set($columns = [ "user"  , "message" ])
#set($columnsProperties = {
    "user" : { "type" : "text" },
    "message" : { "type" : "text"}
})

#set($options = {
    "resultPage":"Space.MyJSON"
})

#livetable("twitter" $columns $columnsProperties $options)
{{/velocity}}

Space.MyJSON Page:

{{velocity wiki="false"}}
#if("$!{request.xpage}" == 'plain')
  $response.setContentType('application/json')
#end
##==============================
## Offset = item # at which to start displaying data
##==============================
#set($offset = $util.parseInt($request.get('offset')))
## offset starts from 0 in velocity and 1 in javascript
#set($offset = $offset - 1)
#if($offset < 0)
  #set($offset = 0)
#end
##==================
## Limit = # of items to display
##==================
#set($limit = $util.parseInt($request.get('limit')))
##==================
## Tag = one parameter per tag
##==================
#if($request.tag)
  #foreach($tag in $request.getParameterValues('tag'))
    ## TODO: Add code to filter items based on the selected tags
  #end
#end
##==========
## Sort direction
##==========
#set($order = "$!request.sort")
#if($order != '')
  #set($orderDirection = "$!{request.get('dir').toLowerCase()}")
  #if("$!orderDirection" != '' && "$!orderDirection" != 'asc')
    #set($orderDirection = 'desc')
  #end
#end
## ===========
## Filter Data here...
## ===========
## TODO: Add code to filter data
## Each column can be filtered and the filter for a column can be retrieved with:
## #set($filterValue = "$!{request.get(<columnname>)}")

## ===
## JSON
## ===
{
"totalrows": 1,
"matchingtags": {},
"tags" : [],
"returnedrows":  1,
"offset": 1,
"reqNo": $util.parseInt($request.reqNo),
"rows": [{
  "doc_viewable" : true,
  "user" : "vincent",
  "message" : "my message"
}]
}
{{/velocity}}

Note that in order to support paging, filtering and sorting you'll need to handle the following URL parameters:

  • offset: value at which to start displaying data in the livetable
  • limit: number of items to display in the livetable
  • <columnFilter>: a filtering string entered for the column named columnFilter
  • sort: the name of the column on which to sort
  • dir: the sorting direction ("asc" or "desc")

For example here's the request sent if you typed the letter t in the message column:

http://localhost:8080/xwiki/bin/get/Space/MyJSON?outputSyntax=plain&transprefix=&classname=&collist=user,message&offset=1&limit=15&reqNo=4&message=t&sort=user&dir=asc

Starting with XWiki Enterprise 4.0 you can modify the default JSON before it is sent to the client side to feed the live table. Space.MyJSON page could look like this:

{{include reference="XWiki.LiveTableResultsMacros" /}}

{{velocity wiki="false"}}
#if("$!{request.xpage}" == 'plain')
  $response.setContentType('application/json')
#end
##==================
## Initialize the map that will store the JSON data.
##==================
#set($map = {})
##==================
## Build the JSON in memory using the previous map
##==================
#gridresult_buildJSON("$!request.classname" $request.collist.split(',') $map)
##==================
## Modify the JSON data from the map or add new data.
##==================
#set($discard = $map.put('new_table_property', 'some value'))
#foreach($row in $map.get('rows'))
  #if($row.get('doc_hasedit'))
    ## Add an additional constraint for the edit right (just an example).
    #set($rowDoc = $xwiki.getDocument($row.get('doc_fullName')))
    #if($rowDoc.getObject('SomeSpace.SomeClass').getProperty('foo').value != 'bar')
      #set($discard = $row.put('doc_hasedit', false))
    #end
  #end
  #set($discard = $row.put('new_row_property', 'some other value'))
#end
##==================
## Serialize the map in the JSON format.
##==================
$jsontool.serialize($map)
{{/velocity}}
Note that one could think that this example would slow down your wiki a lot as it loads a Document and an XObject to verify if an XProperty matches a value. And this is done for each Livetable request! (i.e. whenever you type a few letters for example in a filter field for example). The best practice is normally to return all the data from the store in one query so that it executes as fast as possible. In other words, the "if" should be done as much as possible in the query so that the store returns only the matching data). Now, it happens that the call to gridresult_buildJSON will have already loaded the Documents and put them in a memory cache, and thus in practice the cost will be minimal.

Responsiveness

Since 6.2.2, with the Flamingo Skin, the livetable is responsive to the screen size. So if there is not enough place to display the table properly (for example: on a smartphone), the livetable will look differently.

Livetable-Responsive.png

FAQ

How do I translate "emptyvalue" found in cells and column titles

Create a translation page that you register as a Document Resource Bundle and use the translationPrefix value as the key prefix. For example if you've used "translationPrefix" : "xwikiorg.extensions." then use:

xwikiorg.extensions.emptyvalue = No value specified
xwikiorg.extensions.[yourValue] = Some other value

If you don't want to display any text use:

xwikiorg.extensions.emptyvalue =

How do I translate the action buttons

Create a translation page that you register as a Document Resource Bundle and use the following translation keys as the key prefix for the _actions column:

  • <translationPrefix>._actions.<action>
  • platform.livetable._actions.<action>
    The translationPrefix refers to the livetable option parameter and the action refers the values of the _actions  column parameters ( rename, rights, delete, copy, etc.)

How do I filter on doc.title

It's not possible to filter on doc.title.

How is possible to add preview links next to Office documents listed in the _attachments column

Need to modify XWiki.LiveTableResultsMacros. In this page existing macro livetable_getAttachmentsList with original source code:

#macro(livetable_getAttachmentsList $itemDoc)
 #set($attachlist = '')
 #foreach($attachment in $itemDoc.attachmentList)
   #set($attachmentUrl = $itemDoc.getAttachmentURL($attachment.filename))
   #set($attachlist = "${attachlist}<a href='${attachmentUrl}'>$attachment.filename</a><br/>")
 #end
#end

This macro should be changed like this:

#macro(livetable_getAttachmentsList $itemDoc)
 #set($attachlist = '')
 #foreach($attachment in $itemDoc.attachmentList)
   #set($attachmentUrl = $itemDoc.getAttachmentURL($attachment.filename))
   #set($previewLink="")    
   #if("$!{oomanager.serverState}" == "Connected" && $services.officeviewer.isMimeTypeSupported($attachment.getMimeType().toLowerCase()))
     #set($title="$msg.get('core.viewers.attachments.officeView.title')")
     #set($href="$itemDoc.getExternalURL()?xpage=office&attachment=$attachment.filename")
     #set($img="$xwiki.getSkinFile('icons/silk/eye.png')")
     #set($previewLink="<a class='viewlink' title='$title' href='$href' target='_blank'><img src='$img' /></a>")
   #end
   #set($attachlist = "${attachlist}<a href='${attachmentUrl}'>$attachment.filename</a>$previewLink<br/>")
 #end
#end

How to debug an issue with the Livetable display

You need to check the returned JSON first and verify it's valid. To get the JSON use Firebug for Firefox or Developer Tools for Chrome and go the Network view. You'll see a request such as:

http://<server>/xwiki/bin/get/XWiki/LiveTableResults?outputSyntax=plain&transprefix=platform.index.&classname=&collist=doc.name,doc.space,doc.date,doc.author&offset=1&limit=15&reqNo=1&sort=doc.name&dir=asc

Open this URL in your browser, this will give you the JSON. Copy-paste it on the  JSONLint page for example and validate it.

If it's invalid then you need the JSON generation code.

(Add &sql=1 at previous URL to see query computed and parameters)

  1. ^ except for doc.title, because filtering on doc.title is not possible
  2. ^ Only list, text and number type columns can be filtered, date filtering it not available yet until XWIKI-10122; or you can try out the extension for a date filter.

Tested on

This extension has been tested with the following configurations.

Extension VersionXWiki FlavorNotes
1.0XWiki Enterprise 7.1.2
Created by Asiri Rathnayake on 2009/09/10 08:40
    

Get Connected