Wiki source code of Live Data Extension Points

Last modified by Eleni Cojocariu on 2026/09/11 15:51

Show last authors
1 Live Data extends on both sides of its JSON configuration: on the server by contributing a [[Component>>doc:extensions:Extension.Component Module]], in the browser by registering a component on the widget. A configuration naming the new hint is enough to reach it.
2
3 |=Extension point|=Kind|=Looked up under|=Contributes
4 |##LiveDataSource##|Java Component|The value of the ##source## Macro parameter|A new origin for the entries and the property descriptors
5 |##LiveTableNewRowNamingStrategy##|Java Component|The value of the ##newRowNamingStrategy## source parameter|A way of naming the pages created for new entries by the Live Table source
6 |##registerPanel##|JavaScript|N/A|A new panel in the Live Data menu
7 |##componentStore.register##|JavaScript|A kind and a name|A new displayer, filter or layout
8
9 == Adding a Source ==
10
11 A source is what puts a set of entries within reach of page authors: register a Component under a hint, and that hint becomes a value they can pass as the ##source## Macro parameter. The Component itself is small, and hands the real work to two stores.
12
13 {{code language="java"}}
14 @Role
15 public interface LiveDataSource
16 {
17 LiveDataEntryStore getEntries();
18
19 LiveDataPropertyDescriptorStore getProperties();
20 }
21 {{/code}}
22
23 === Reading and Writing the Entries ===
24
25 ##LiveDataEntryStore## is where the data comes from. Only the two read methods have to be written; every other method has a default implementation, and the two that make the Live Data writable throw ##UnsupportedOperationException## by default. A store that keeps those defaults gives a Live Data the reader can browse but not change. Each method also throws ##LiveDataException##.
26
27 |=Method|=Returns|=Write it
28 |##get(LiveDataQuery)##|The entries matching the query, and how many there are in total|Always
29 |##get(Object entryId)##|One entry, by its id|Always
30 |##get(Object entryId, String property)##|One value of one entry|Only to avoid fetching the whole entry, which is what the default does
31 |##save(Map)##|The id of the entry that was stored|To let the reader create entries, and to let them [[edit values in place>>doc:documentation.xs.user.livedata.edit-value-place.WebHome]], which the default ##update## goes through
32 |##update(Object, String, Object)##|The value the property had before|Only to replace the default, which reads the entry, sets the property and saves it
33 |##remove(Object entryId)##|The entry that was removed|To let the reader delete entries
34
35 === Declaring the Properties ===
36
37 ##LiveDataPropertyDescriptorStore## decides what the widget knows about each property: its type, whether it can be sorted, filtered or edited, and which displayer and filter it uses. What this store returns becomes the ##meta.propertyDescriptors## of the [[Live Data Configuration>>doc:documentation.xs.dev.livedata.configuration.WebHome]], so it is what a reader sees in the [[Properties, Sort and Filter panels>>doc:documentation.xs.user.livedata.panels.WebHome]].
38
39 |=Method|=Returns|=Write it
40 |##get()##|Every property the entries may have|Always
41 |##get(String propertyId)##|One descriptor|Only to replace the default, which picks it out of the list above
42 |##save(LiveDataPropertyDescriptor)##|Whether the descriptor was stored|To let properties be redefined at runtime
43 |##remove(String propertyId)##|The descriptor that was removed|To let properties be dropped at runtime
44
45 == Naming the Pages Created for New Entries ==
46
47 {{version since="18.7.0RC1"}}
48 For the live table source, new row creation requires a page naming strategy. For that, there is a specific interface:
49
50 {{code language="java"}}
51 /**
52 * Strategy to generate the document reference of a new livetable entry.
53 *
54 * @since 18.7.0RC1
55 */
56 @Role
57 @Unstable
58 public interface LiveTableNewRowNamingStrategy
59 {
60 /**
61 * Generates a document reference for a new livetable entry.
62 *
63 * @param parameters the livedata source parameters
64 * @return the generated document reference
65 * @throws LiveDataException if the reference cannot be generated
66 * @throws XWikiException if there is a wiki-level error
67 */
68 DocumentReference generate(Map<String, Object> parameters) throws LiveDataException, XWikiException;
69
70 /**
71 * Checks if the current user is allowed to create a new entry based on the provided source parameters.
72 *
73 * @param parameters the live data source parameters
74 * @return whether the current user is allowed to create a new entry with this strategy
75 */
76 boolean isCreationAllowed(Map<String, Object> parameters);
77 }
78 {{/code}}
79
80 New strategies can be added by creating new named components implementing this interface, and naming them in the ##newRowNamingStrategy## source parameter of a Live Data.
81 {{/version}}
82
83 == Adding a Panel ==
84
85 A panel is registered on an instance with ##registerPanel##, from a listener of the ##xwiki:livedata:instanceCreated## event. The reader then toggles it from the Live Data menu, like the built-in panels. This registers a "Hello World" panel:
86
87 {{code language="js"}}
88 document.addEventListener('xwiki:livedata:instanceCreated', function(e) {
89 const panel = {
90 id: 'myExtension',
91 name: 'My Extension',
92 title: 'Hello World',
93 icon: 'camera',
94 container: document.createElement('div'),
95 component: 'LiveDataAdvancedPanelExtension',
96 order: 4000
97 };
98
99 panel.container.textContent = 'Hello World!';
100
101 e.detail.livedata.registerPanel(panel);
102 });
103 {{/code}}
104
105 |=Property|=What it holds
106 |##id##|A name unique among all the panels of the instance
107 |##name##|The text displayed in the Live Data menu
108 |##title##|The text displayed in the title of the panel
109 |##icon##|The icon displayed both in the menu and in the panel title
110 |##order##|The display order of the panel. The built-in Properties, Sort and Filter panels are at 1000, 2000 and 3000
111 |##container##|A DOM node, attached to the panel body while the panel is open and detached when it is collapsed
112 |##component##|The Vue component rendering the panel. Use ##LiveDataAdvancedPanelExtension##, the only supported one
113
114 Every property must be set when the panel is registered. All of them except ##order## can be changed afterwards, and the change is reflected in the interface, which is how a counter in the name or the title is kept up to date.
115
116 == Registering a Displayer, a Filter or a Layout ==
117
118 Vue 3 no longer allows loading a component globally with ##Vue.component##. Live Data provides a component store instead, on which displayers, filters and layouts are registered by name.
119
120 ##componentStore.register(string, string, () => Promise<VueComponent>)##
121
122 * **kind: string**: any of ##filter##, ##layout##, or ##displayer##, defines the type of loaded component
123 * **name: string**: the name of the registered component
124 * **loader: () => Promise<VueComponent>**: a function returning a ##VueComponent## wrapped in a promise. This allows for the lazy loading of components, only when effectively loaded (see ##load## below)
125
126 ##componentStore.load(string, string): Promise<VueComponent>##
127
128 * **kind: string**: the kind of component to load
129 * **name: string**: the name of the component to load
130 * **returns**: a Promise resolving to the component. When nothing is registered under that kind and name yet, the Promise stays pending and is resolved as soon as a matching component is registered
131
132 The store is imported from the ##@xwiki/platform-livedata-componentstore## module, mapped in the [[importmap>>doc:documentation.xs.dev.livedata.webjar.WebHome]] of the Live Data webjar:
133
134 {{code language="javascript"}}
135 import { componentStore } from "@xwiki/platform-livedata-componentstore";
136
137 // Dynamically register a new "toggle" displayer component.
138 componentStore.register("displayer", "toggle", async () => {
139 return (await import("./components/DisplayerToggle.vue")).default;
140 });
141 {{/code}}

Get Connected