此教程将指导你创建一个新的类属性类型,通过这种方式来扩展类编辑器。
此教程适用于XWiki 4.3M2以上版本。不要在旧版本尝试!
你应该先阅读XWiki数据模型来了解XWiki的类,对象,属性,然后编写一个XWiki组件,因为新的属性类型是通过组件来实现。
创建一个新的属性类型
作为一个例子,我们将创建一个可用于存储外部图像URL的"External Image"属性类型。让我们通过扩展PropertyClass来创建属性类型。不幸的是,这意味着你的Maven项目将不得不依赖XWiki的old core代码。
public class ExternalImageClass extends PropertyClass
{
/**
* Default constructor.
*/
public ExternalImageClass()
{
// Specify the default name and pretty name of this XClass property. They can be overwritten from the class
// editor when adding a property of this type to an XClass.
super("externalImage", "External Image", null);
}
@Override
public BaseProperty fromString(String value)
{
BaseProperty property = newProperty();
// The stored value can be different than the value set by the user. You can do the needed transformations here.
// In our case the value is an image URL so we keep it as it is. The reverse transformation, from the stored
// value to the user friendly value, can be done in the property displayer.
property.setValue(value);
return property;
}
@Override
public BaseProperty newProperty()
{
// The value of this XClass property is stored as a String. You have to use raw types here like StringProperty
// because they are mapped to the database. Adding a new raw type implies modifying the Hibernate mapping and is
// not the subject of this tutorial.
BaseProperty property = new StringProperty();
property.setName(getName());
return property;
}
@Override
public <T extends EntityReference> void mergeProperty(BaseProperty<T> currentProperty,
BaseProperty<T> previousProperty, BaseProperty<T> newProperty, MergeConfiguration configuration,
XWikiContext context, MergeResult mergeResult)
{
if (!Objects.equals(previousProperty, newProperty)) {
if (Objects.equals(previousProperty, currentProperty)) {
currentProperty.setValue(newProperty.getValue());
} else {
// Found conflict
mergeResult.getLog().error("Collision found on property [{}] current has been modified", getName());
}
}
}
}
注意,我们使用了一个StringProperty来存储我们的xclass属性的值。可用原始属性类型:
- DateProperty
- DBStringListProperty
- DoubleProperty
- FloatProperty
- IntegerProperty
- LargeStringProperty
- LongProperty
- StringListProperty
- StringProperty
扩展这个列表是需要通过修改Hibernate映射,但不是这个教程的主题。你可以创建高级的类属性类型,但最终它们的值将被存储为这些原始类型之一。
此外,正如你所看到的我们为这种属性覆盖默认的mergeProperty实现,以确保URL不会合并逐个字符,而是作为整体。
下一步,我们为"External Image"属性类型创建一个provider。这用于将"External Image"属性类型添加到一个XClass(例如类编辑器)。
@Component
// Note that the component hint matches the name of the property class without the "Class" suffix. The reason is that
// the component hint must match the value returned by the #getClassType() method of your property class, which by
// default strips the "Class" suffix from the Java class name of your property class. If you want to use a different
// hint that doesn't follow this naming convention you need to override #getClassType().
@Named("ExternalImage")
@Singleton
public class ExternalImageClassProvider implements PropertyClassProvider
{
@Override
public PropertyClassInterface getInstance()
{
return new ExternalImageClass();
}
@Override
public PropertyMetaClassInterface getDefinition()
{
PropertyMetaClass definition = new PropertyMetaClass();
// This text will appear in the drop down list of property types to choose from in the class editor.
definition.setPrettyName("External Image");
definition.setName(getClass().getAnnotation(Named.class).value());
// Add a meta property that will allows us to specify a CSS class name for the image HTML element.
// NOTE: We define meta properties using XClass property types. This means for instance that you can define meta
// properties of External Image type or whatever XClass property type you create.
StringClass styleName = new StringClass();
styleName.setName("styleName");
styleName.setPrettyName("Style Name");
definition.safeput(styleName.getName(), styleName);
// The alternative text is required for a valid image HTML element so we add a meta property for it.
StringClass placeholder = new StringClass();
placeholder.setName("placeholder");
placeholder.setPrettyName("Alternative Text");
definition.safeput(placeholder.getName(), placeholder);
// Add more meta properties here.
return definition;
}
}
provider就像一个我们属性类型工厂,但是它还定义了元属性列表。每一个类属性拥有元属性列表,这些控制属性如何展示和值如何解析等。这些meta属性的值是由一个类的所有实例共享。例如,如果你创建一个拥有"External Image"属性的类并且设置值,从类编辑,"styleName"元属性的值,所有类的对象将使用这值。
最后一步是在components.txt添加provider组件。
org.xwiki.example.internal.ExternalImageClassProvider
现在你可以编译你的Maven项目,把生成的jar复制到WEB-INF/lib文件夹下。重新启动服务器,就完成了。
使用新的属性类型
让我们创建一个类拥有"External Image"属性。你将在类编辑器的下拉列表框中看到"External Image"。
当你添加新属性到类之后,你可以设置它的所有的元属性。你会注意到,每个属性都有一个标准的元属性列表,比如name或者pretty name,还有一些特定的元属性,比如在我们的例子中的style name和alternative text。
保存这个类,然后在一个wiki页面上添加这个类对象(实例)。为此,你必须在对象模式来编辑wiki页面。
保存此页面,然后为我们的类创建一个sheet。
{{velocity}}
; $doc.displayPrettyName('screenshot', false, false)
: $doc.display('screenshot')
{{/velocity}}
现在,如果你看看这个wiki页面,你会发现它看起来不太好看。这是因为"External Image"属性类型使用一个只显示值(图片URL)的默认的displayer。
为新的属性类型编写一个displayer
我们可以通过创建一个自定义displayer来改善我们的"External Image"属性显示。实现方法之一是通过在/templates/文件夹下创建一个Velocity模板displayer_externalimage.vm。
#if ($type == 'edit')
#set($id = $escapetool.xml("${prefix}${name}"))
<input type="text" id="$!id" name="$!id" value="$!escapetool.xml($value)" />
#elseif ($type == 'view')
<img src="$escapetool.xml($value)" alt="$escapetool.xml($field.getProperty('placeholder').value)"
class="$escapetool.xml($field.getProperty('styleName').value)" />
#else
## In order for the custom displayer to be taken into account, the result of its evaluation with an unknown display
## mode must not be empty. Let's output something.
Unknown display mode.
#end
你可以阅读更多有关自定义displayer。现在,wiki页面应该在视图模式下好看多了。
在编辑模式下它看起来一样的,但是你可以扩展displayer提供图像预览。