Last modified by Vincent Massol on 2023/10/10

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc start="2"/}}
3 {{/box}}
4
5 This tutorial guides you through the creation of a new [[XClass property type>>Documentation.DevGuide.DataModel.WebHome||anchor="HXWikiClasses2CObjects2CandProperties"]], which is a way to extend the [[class editor>>Documentation.UserGuide.Features.PageEditing||anchor="HClasseseditingmode"]].
6
7 You should start by reading the [[XWiki Data Model>>Documentation.DevGuide.DataModel.WebHome]] to understand XWiki Classes, Objects and Properties, and then the [[Writing XWiki components>>Documentation.DevGuide.Tutorials.WritingComponents.WebHome]] tutorial because new property types are implemented as components.
8
9 == Create a new property type ==
10
11 As an example, we will create an "External Image" property type which can be used to store URLs to external images. Let's start by creating the actual property type which must extend ##PropertyClass##. Unfortunately this means that your Maven project will have to depend on XWiki's old core.
12
13 {{code language="java"}}
14 public class ExternalImageClass extends PropertyClass
15 {
16 /**
17 * Default constructor.
18 */
19 public ExternalImageClass()
20 {
21 // Specify the default name and pretty name of this XClass property. They can be overwritten from the class
22 // editor when adding a property of this type to an XClass.
23 super("externalImage", "External Image", null);
24 }
25
26 @Override
27 public BaseProperty fromString(String value)
28 {
29 BaseProperty property = newProperty();
30 // The stored value can be different than the value set by the user. You can do the needed transformations here.
31 // In our case the value is an image URL so we keep it as it is. The reverse transformation, from the stored
32 // value to the user friendly value, can be done in the property displayer.
33 property.setValue(value);
34 return property;
35 }
36
37 @Override
38 public BaseProperty newProperty()
39 {
40 // The value of this XClass property is stored as a String. You have to use raw types here like StringProperty
41 // because they are mapped to the database. Adding a new raw type implies modifying the Hibernate mapping and is
42 // not the subject of this tutorial.
43 BaseProperty property = new StringProperty();
44 property.setName(getName());
45 return property;
46 }
47
48 @Override
49 public <T extends EntityReference> void mergeProperty(BaseProperty<T> currentProperty,
50 BaseProperty<T> previousProperty, BaseProperty<T> newProperty, MergeConfiguration configuration,
51 XWikiContext context, MergeResult mergeResult)
52 {
53 if (!Objects.equals(previousProperty, newProperty)) {
54 if (Objects.equals(previousProperty, currentProperty)) {
55 currentProperty.setValue(newProperty.getValue());
56 } else {
57 // Found conflict
58 mergeResult.getLog().error("Collision found on property [{}] current has been modified", getName());
59 }
60 }
61 }
62 }
63 {{/code}}
64
65 Notice that we have used a ##StringProperty## to store the value of our XClass property. The available raw property types that can be used for storing the value in the database are:
66
67 * ##DateProperty##
68 * ##DBStringListProperty##
69 * ##DoubleProperty##
70 * ##FloatProperty##
71 * ##IntegerProperty##
72 * ##LargeStringProperty##
73 * ##LongProperty##
74 * ##StringListProperty##
75 * ##StringProperty##
76
77 Extending this list is not possible without modifying the Hibernate mapping and is not the subject of this tutorial. You can create high level XClass property types but in the end their values will be stored as one of these raw types.
78
79 Also as you can see we have overwritten the default 3-way-merge implementation for this kind of properties to make sure the URL is not merged character by character but instead compared as a whole.
80
81 Next let's create a provider for our "External Image" property type. This is going to be used whenever a property of type "External Image" is added to an XClass (e.g. from the class editor).
82
83 {{code language="java"}}
84 @Component
85 // Note that the component hint matches the name of the property class without the "Class" suffix. The reason is that
86 // the component hint must match the value returned by the #getClassType() method of your property class, which by
87 // default strips the "Class" suffix from the Java class name of your property class. If you want to use a different
88 // hint that doesn't follow this naming convention you need to override #getClassType().
89 @Named("ExternalImage")
90 @Singleton
91 public class ExternalImageClassProvider implements PropertyClassProvider
92 {
93 @Override
94 public PropertyClassInterface getInstance()
95 {
96 return new ExternalImageClass();
97 }
98
99 @Override
100 public PropertyMetaClassInterface getDefinition()
101 {
102 PropertyMetaClass definition = new PropertyMetaClass();
103 // This text will appear in the drop down list of property types to choose from in the class editor.
104 definition.setPrettyName("External Image");
105 definition.setName(getClass().getAnnotation(Named.class).value());
106
107 // Add a meta property that will allows us to specify a CSS class name for the image HTML element.
108 // NOTE: We define meta properties using XClass property types. This means for instance that you can define meta
109 // properties of External Image type or whatever XClass property type you create.
110 StringClass styleName = new StringClass();
111 styleName.setName("styleName");
112 styleName.setPrettyName("Style Name");
113 definition.safeput(styleName.getName(), styleName);
114
115 // The alternative text is required for a valid image HTML element so we add a meta property for it.
116 StringClass placeholder = new StringClass();
117 placeholder.setName("placeholder");
118 placeholder.setPrettyName("Alternative Text");
119 definition.safeput(placeholder.getName(), placeholder);
120
121 // Add more meta properties here.
122
123 return definition;
124 }
125 }
126 {{/code}}
127
128 The provider acts like a factory for our property type but it also defines the list of meta properties. Each XClass property type has a list of meta properties that control how the property is displayed, how it's value is parsed, and so on. The values of these meta properties are shared by all instances of an XClass. So for instance, if you create a XClass with an "External Image" property and set, from the class editor, the "styleName" meta property to "icon" then all objects of that XClass will use that value.
129
130 The final step is to add the provider component to ##components.txt##.
131
132 {{code language="none"}}
133 org.xwiki.example.internal.ExternalImageClassProvider
134 {{/code}}
135
136 Now you can build your Maven project and copy the generated jar to the ##WEB-INF/lib## folder of your XWiki instance. Restart the server and you're done.
137
138 == Use the new property type ==
139
140 Let's create a class that has a property of type "External Image". You should see "External Image" listed in the drop down list box in the [[class editor>>Documentation.UserGuide.Features.PageEditing||anchor="HClasseseditingmode"]].
141
142 {{image reference="addProperty.png"/}}
143
144 After you add the property to the class you can set all its meta properties. You'll notice that each property has a list of standard meta properties, like name or pretty name, and some specific meta properties, like style name and alternative text in our case.
145
146 {{image reference="editProperty.png"/}}
147
148 Save the class and let's add an object (instance) of this class to a wiki page. For this you have to edit the wiki page in [[object mode>>Documentation.UserGuide.Features.PageEditing||anchor="HObjectseditingmode"]].
149
150 {{image reference="editObject.png"/}}
151
152 Save the page and let's create a [[sheet>>extensions:Extension.Sheet Module]] for our class.
153
154 {{code language="none"}}
155 {{velocity}}
156 ; $doc.displayPrettyName('screenshot', false, false)
157 : $doc.display('screenshot')
158 {{/velocity}}
159 {{/code}}
160
161 Now if you look at the wiki page that has the object you'll see that it doesn't look too good. This is because the "External Image" property type uses a default displayer that only displays the value (the image URL).
162
163 {{image reference="viewModeNoDisplayer.png"/}}
164
165 == Write a displayer for the new property type ==
166
167 We can improve the display of our "External Image" properties by creating a custom displayer. One way to achieve this is by creating a Velocity template ##displayer_externalimage.vm## under the ##/templates/## folder.
168
169 {{code language="none"}}
170 #if ($type == 'edit' || $type == 'search')
171 #set ($id = $escapetool.xml("${prefix}${name}"))
172 <input type="text" id="$!id" name="$!id" value="$!escapetool.xml($value)" />
173 #elseif ($type == 'view' || $type == 'rendered')
174 <img src="$escapetool.xml($value)" alt="$escapetool.xml($field.getProperty('placeholder').value)"
175 class="$escapetool.xml($field.getProperty('styleName').value)" />
176 #elseif ($type == 'hidden')
177 #set ($id = $escapetool.xml("${prefix}${name}"))
178 <input type="hidden" id="$!id" name="$!id" value="$!escapetool.xml($value)" />
179 #else
180 ## In order for the custom displayer to be taken into account, the result of its evaluation with an unknown display
181 ## mode must not be empty. Let's output something.
182 Unknown display mode.
183 #end
184 {{/code}}
185
186 You can read more about [[custom displayers>>xwiki:ReleaseNotes.ReleaseNotesXWikiEnterprise42M2||anchor="HDefaultcustomdisplayersforEasiercustomizationofthewayobjectfieldsaredisplayed"]]. The wiki page should look better in view mode now.
187
188 {{image reference="viewMode.png"/}}
189
190 In edit mode it will look the same but you can extend the displayer to provide image preview for instance.
191
192 {{image reference="editMode.png"/}}

Get Connected