Last modified by Marius Dumitru Florea on 2017/11/27

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>>platform:DevGuide.DataModel||anchor="HXWikiClasses2CObjects2CandProperties"]], which is a way to extend the [[class editor>>platform:Features.PageEditing||anchor="HClasseseditingmode"]].
6
7 {{warning}}
8 This tutorial applies to XWiki version 4.3M2 and above. Don't try on older versions!
9 {{/warning}}
10
11 You should start by reading the [[XWiki Data Model>>platform:DevGuide.DataModel]] to understand XWiki Classes, Objects and Properties, and then the [[Writing XWiki components>>platform:DevGuide.WritingComponents]] tutorial because new property types are implemented as components.
12
13 == Create a new property type ==
14
15 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.
16
17 {{code language="java"}}
18 public class ExternalImageClass extends PropertyClass
19 {
20 /**
21 * Default constructor.
22 */
23 public ExternalImageClass()
24 {
25 // Specify the default name and pretty name of this XClass property. They can be overwritten from the class
26 // editor when adding a property of this type to an XClass.
27 super("externalImage", "External Image", null);
28 }
29
30 @Override
31 public BaseProperty fromString(String value)
32 {
33 BaseProperty property = newProperty();
34 // The stored value can be different than the value set by the user. You can do the needed transformations here.
35 // In our case the value is an image URL so we keep it as it is. The reverse transformation, from the stored
36 // value to the user friendly value, can be done in the property displayer.
37 property.setValue(value);
38 return property;
39 }
40
41 @Override
42 public BaseProperty newProperty()
43 {
44 // The value of this XClass property is stored as a String. You have to use raw types here like StringProperty
45 // because they are mapped to the database. Adding a new raw type implies modifying the Hibernate mapping and is
46 // not the subject of this tutorial.
47 BaseProperty property = new StringProperty();
48 property.setName(getName());
49 return property;
50 }
51
52 @Override
53 public <T extends EntityReference> void mergeProperty(BaseProperty<T> currentProperty,
54 BaseProperty<T> previousProperty, BaseProperty<T> newProperty, MergeConfiguration configuration,
55 XWikiContext context, MergeResult mergeResult)
56 {
57 if (!Objects.equals(previousProperty, newProperty)) {
58 if (Objects.equals(previousProperty, currentProperty)) {
59 currentProperty.setValue(newProperty.getValue());
60 } else {
61 // Found conflict
62 mergeResult.getLog().error("Collision found on property [{}] current has been modified", getName());
63 }
64 }
65 }
66 }
67 {{/code}}
68
69 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:
70
71 * ##DateProperty##
72 * ##DBStringListProperty##
73 * ##DoubleProperty##
74 * ##FloatProperty##
75 * ##IntegerProperty##
76 * ##LargeStringProperty##
77 * ##LongProperty##
78 * ##StringListProperty##
79 * ##StringProperty##
80
81 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.
82
83 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.
84
85 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).
86
87 {{code language="java"}}
88 @Component
89 // Note that the component hint matches the name of the property class without the "Class" suffix. The reason is that
90 // the component hint must match the value returned by the #getClassType() method of your property class, which by
91 // default strips the "Class" suffix from the Java class name of your property class. If you want to use a different
92 // hint that doesn't follow this naming convention you need to override #getClassType().
93 @Named("ExternalImage")
94 @Singleton
95 public class ExternalImageClassProvider implements PropertyClassProvider
96 {
97 @Override
98 public PropertyClassInterface getInstance()
99 {
100 return new ExternalImageClass();
101 }
102
103 @Override
104 public PropertyMetaClassInterface getDefinition()
105 {
106 PropertyMetaClass definition = new PropertyMetaClass();
107 // This text will appear in the drop down list of property types to choose from in the class editor.
108 definition.setPrettyName("External Image");
109 definition.setName(getClass().getAnnotation(Named.class).value());
110
111 // Add a meta property that will allows us to specify a CSS class name for the image HTML element.
112 // NOTE: We define meta properties using XClass property types. This means for instance that you can define meta
113 // properties of External Image type or whatever XClass property type you create.
114 StringClass styleName = new StringClass();
115 styleName.setName("styleName");
116 styleName.setPrettyName("Style Name");
117 definition.safeput(styleName.getName(), styleName);
118
119 // The alternative text is required for a valid image HTML element so we add a meta property for it.
120 StringClass placeholder = new StringClass();
121 placeholder.setName("placeholder");
122 placeholder.setPrettyName("Alternative Text");
123 definition.safeput(placeholder.getName(), placeholder);
124
125 // Add more meta properties here.
126
127 return definition;
128 }
129 }
130 {{/code}}
131
132 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.
133
134 The final step is to add the provider component to ##components.txt##.
135
136 {{code language="none"}}
137 org.xwiki.example.internal.ExternalImageClassProvider
138 {{/code}}
139
140 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.
141
142 == Use the new property type ==
143
144 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>>platform:Features.PageEditing||anchor="HClasseseditingmode"]].
145
146 {{image reference="addProperty.png"/}}
147
148 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.
149
150 {{image reference="editProperty.png"/}}
151
152 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>>platform:Features.PageEditing||anchor="HObjectseditingmode"]].
153
154 {{image reference="editObject.png"/}}
155
156 Save the page and let's create a [[sheet>>extensions:Extension.Sheet Module]] for our class.
157
158 {{code language="none"}}
159 {{velocity}}
160 ; $doc.displayPrettyName('screenshot', false, false)
161 : $doc.display('screenshot')
162 {{/velocity}}
163 {{/code}}
164
165 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).
166
167 {{image reference="viewModeNoDisplayer.png"/}}
168
169 == Write a displayer for the new property type ==
170
171 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.
172
173 {{code language="none"}}
174 #if ($type == 'edit' || $type == 'search')
175 #set ($id = $escapetool.xml("${prefix}${name}"))
176 <input type="text" id="$!id" name="$!id" value="$!escapetool.xml($value)" />
177 #elseif ($type == 'view' || $type == 'rendered')
178 <img src="$escapetool.xml($value)" alt="$escapetool.xml($field.getProperty('placeholder').value)"
179 class="$escapetool.xml($field.getProperty('styleName').value)" />
180 #elseif ($type == 'hidden')
181 #set ($id = $escapetool.xml("${prefix}${name}"))
182 <input type="hidden" id="$!id" name="$!id" value="$!escapetool.xml($value)" />
183 #else
184 ## In order for the custom displayer to be taken into account, the result of its evaluation with an unknown display
185 ## mode must not be empty. Let's output something.
186 Unknown display mode.
187 #end
188 {{/code}}
189
190 You can read more about [[custom displayers>>xwiki:ReleaseNotes.ReleaseNotesXWikiEnterprise42M2||anchor="HDefaultcustomdisplayersforEasiercustomizationofthewayobjectfieldsaredisplayed"]]. The wiki page should look better in view mode now.
191
192 {{image reference="viewMode.png"/}}
193
194 In edit mode it will look the same but you can extend the displayer to provide image preview for instance.
195
196 {{image reference="editMode.png"/}}

Get Connected