小组件开发文档
本主题提供小组件开发指南,该指南通过示例说明主要的小组件定义类。
快速入门指南
有关运行的小组件项目,请参阅
小组件开发和部署快速入门指南。
自定义小组件实施
Codebeamer 小组件框架旨在简化新小组件的开发过程,同时为小组件开发人员提供了更多的自由度。
以下类定义了系统中的小组件:
• Widget 接口。
• WidgetInformation 接口。
• 适用于小组件内容和编辑器内容的 Renderer 接口。(强烈建议)。
• WidgetFactory 接口。
实施示例使用 Spring 依赖注入机制。
为了使小组件框架能够选取自定义小组件,必须使用 @Component 注释,为 Widget、WidgetInformation 和 WidgetFactory 实施添加注释。
以下各部分介绍如何通过注释代码示例来实现这些接口。
小组件信息接口
WidgetInformation 接口类表示小组件目录中的小组件。该接口类直接影响小组件浏览器弹窗中的显示内容。
package com.intland.codebeamer.dashboard.component.widgets.wiki;
import org.springframework.stereotype.Component;
import com.intland.codebeamer.dashboard.component.common.interfaces.WidgetInformation;
import com.intland.codebeamer.dashboard.component.widgets.common.WidgetCategory;
// Annotation required to make it available in the system.
@Component
public class WikiMarkupWidgetInformation implements WidgetInformation {
// Defines the category, which affects on which tab in Widget popup it appears.
//Possible values: ALL, CHART, PROJECT, AGILE, TEST, PERSONAL_CONTENT, OTHER
@Override
public String getCategory() {
return WidgetCategory.OTHER.getName();
}
// Custom icon for the Widget.
@Override
public String getImagePreviewUrl() {
return "/images/newskin/dashboard/thumbnails/icon_wiki_markup.png";
}
// Knowledgebase url on codebeamer.com, reserved for built-in widgets.
@Override
public String getKnowledgeBaseUrl() {
return "";
}
// Vendor information.
@Override
public String getVendor() {
return "Intland";
}
// Key in ApplicationResources.properties, which defines the short name of the Widget.
@Override
public String getName() {
return "dashboard.wiki.markup.widget.name";
}
// Key in ApplicationProperties.properties, which contains the description for the Widget.
@Override
public String getShortDescription() {
return "dashboard.wiki.markup.widget.short.description";
}
// Reserved for future use.
@Override
public WikiMarkupWidgetFactory getFactory() {
return null;
}
// Type name of the related Widget class.
@Override
public String getType() {
return WikiMarkupWidget.class.getCanonicalName();
}
}
小组件接口
此接口的实施是任何小组件的核心所在。
此接口表示框架持久层中的小组件,其中包含用于呈现内容和编辑器的逻辑。
|
|
建议不要直接实现,而是从 AbstractWidget 类进行继承,因为该类已具有共同特征的实施。
|
接口:
package com.intland.codebeamer.dashboard.component.common.interfaces;
import com.intland.codebeamer.dashboard.component.common.RenderingContext;
public interface LayoutComponent {
// Unique identifier of the component
String getId();
// Renders the component as HTML fragment (not a standalone HTML page)
String renderToHtml(RenderingContext renderingContext);
}
package com.intland.codebeamer.dashboard.component.common.interfaces;
import java.util.Map;
import com.intland.codebeamer.dashboard.component.common.RenderingContext;
import com.intland.codebeamer.dashboard.component.serializer.JacksonSerializableObject;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute;
public interface Widget extends LayoutComponent, JacksonSerializableObject {
// Renders the editor for the Widget as HTML fragment (not a standalone HTML page)
String renderEditorToHtml(RenderingContext renderingContext);
// Attributes of the widget. These are typed values, which might represent just a simple text or more complex entities like a list of projects.
Map<String, WidgetAttribute> getAttributes();
// Setter for the title of the Widget.
void setTitle(String title);
// Reserved for future use.
void setFullWidth(boolean isFullWidth);
// Reserved for future use.
boolean isFullWidth();
// Sets the color of the Widget header.
void setColor(String color);
// Reserved for future use.
void setHeaderHidden(boolean headerHidden);
// Returns a key from ApplicationResources.properties, which defines the default title for the Widget.
String getDefaultTitleKey();
}
AbstractWidget 类:
package com.intland.codebeamer.dashboard.component.widgets.common;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.intland.codebeamer.dashboard.component.common.interfaces.Widget;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute;
// Important to have these annotations to ensure that the persistence layer saves the Widget properly
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic = true)
public abstract class AbstractWidget implements Widget {
protected static final String NULL_MESSAGE_FORMAT = "The value %s must not be null.";
// Identifier of the Widget
protected final String id;
// CURRENT attributes of the widget
protected final Map<String, WidgetAttribute> attributes;
// Title of the widget
protected String title;
// Reserved for future use
protected boolean isFullWidth;
// Color of the header
protected String color;
// Reserved for future use.
protected boolean headerHidden;
public AbstractWidget(String id, Map<String, WidgetAttribute> attributes) {
Validate.notBlank(id, NULL_MESSAGE_FORMAT, "id");
Validate.notNull(attributes, NULL_MESSAGE_FORMAT, "attributes");
this.id = id;
this.attributes = attributes;
}
// Getters and setter omitted.
}
以下示例显示如何从头开始实现小组件:
|
|
建议扩展 AbstractRendererWidget 类,因为此类已实现此模式,且其中还包含重要的错误处理代码。
|
package com.intland.codebeamer.dashboard.component.widgets.wiki;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.intland.codebeamer.dashboard.component.common.RenderingContext;
import com.intland.codebeamer.dashboard.component.common.interfaces.Renderer;
import com.intland.codebeamer.dashboard.component.widgets.common.AbstractWidget;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.TextAttribute;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic=true)
public class WikiMarkupWidget extends AbstractWidget {
public static final String MARKUP_ATTRIBUTE_NAME = "markup";
public static final WidgetAttribute<String> DEFAULT_MARKUP_ATTRIBUTE = new TextAttribute("", true, false);
// Preferred method to define default attributes
public static Map<String, WidgetAttribute> getDescriptor() {
final Map<String, WidgetAttribute> result = new HashMap<String, WidgetAttribute>();
result.put(MARKUP_ATTRIBUTE_NAME, DEFAULT_MARKUP_ATTRIBUTE);
return result;
}
// Renderer instance, which can create the content of the Widget
private final Renderer<WikiMarkupWidget> htmlRenderer;
// Renderer instance, which can create the editor form for the Widget
private final Renderer<WikiMarkupWidget> editorRenderer;
// Annotated constructor used by the persistence layer. Do not forget to add these annotations!
public WikiMarkupWidget(@JsonProperty("id") final String id, @JsonProperty("attributes") final Map<String, WidgetAttribute> attributes,
@JacksonInject("wikiMarkupWidgetHtmlRenderer") final Renderer<WikiMarkupWidget> htmlRenderer,
@JacksonInject("wikiMarkupWidgetEditorRenderer") final Renderer<WikiMarkupWidget> editorRenderer) {
super(id, attributes);
Validate.notNull(htmlRenderer, NULL_MESSAGE_FORMAT, "htmlRenderer");
Validate.notNull(editorRenderer, NULL_MESSAGE_FORMAT, "editorRenderer");
this.htmlRenderer = htmlRenderer;
this.editorRenderer = editorRenderer;
}
@Override
public String getTypeName() {
return WikiMarkupWidget.class.getCanonicalName();
}
// Recommended way to render the content of the Widget.
@Override
public String renderToHtml(final RenderingContext renderingContext) {
return htmlRenderer.render(renderingContext, this);
}
// Recommended way to render the editor for of the Widget.
@Override
public String renderEditorToHtml(final RenderingContext renderingContext) {
return editorRenderer.render(renderingContext, this);
}
@Override
public String getDefaultTitleKey() {
return "dashboard.wiki.markup.widget.name";
}
}
小组件属性子类型作为支持类
此类属类的子类型表示可用作小组件参数的不同数据类型。
所有内置子类型都有其专属的呈现器,通过将呈现器与其他支持类相结合,可以自动为各个小组件生成编辑器表单。
类:
package com.intland.codebeamer.dashboard.component.widgets.common.attribute;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
// Important annotations, which fine the persistence mechanism.
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="typeName")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic=true)
public abstract class WidgetAttribute<T> {
protected static final String NULL_MESSAGE_FORMAT = "The value %s must not be null.";
// CURRRENT value of the attribute.
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.WRAPPER_OBJECT, property="valueTypeName")
T value;
// Shows whether this attribute has to be filled on the editor form.
protected final boolean required;
// Shows whether this attribute has to be validated using additional validation logic.
protected final boolean validationRequired;
public WidgetAttribute(final T value, final boolean required, final boolean validationRequired) {
Validate.notNull(value, NULL_MESSAGE_FORMAT, "value");
this.value = value;
this.required = required;
this.validationRequired = validationRequired;
}
// Getters and setters omited.
// Important method, all attributes has to provide a way to parse values from a String representation.
public abstract WidgetAttribute<T> convert(final String newValue);
// If the attribute is affected by export / import mechanism (like using project ids), then the identifiers will be mapped by calling this method during the import.
public void replaceIdsAfterImport(Map<Integer, Map<Integer, Integer>> ids) {
}
protected void setValue(T value) {
this.value = value;
}
}
简单子类型:
|
类名称
|
值类型
|
|
BooleanAttribute
|
布尔值
|
|
StringAttribute
|
字符串
|
|
TextAttribute
|
字符串,但具有富文本编辑器
|
|
IntegerAttribute
|
整数
|
|
IntegerListAttribute
|
整数列表
|
|
StringListAttribute
|
字符串列表
|
|
FixedChoiceWidgetAttribute
|
可以作为类型参数与枚举一起使用,其中包含来自该枚举的单个值。
|
|
MultiChoiceWidgetAttribute
|
与 FixedChoiceWidgetAttribute 类似,但可以包含多个值。
|
表示 Codebeamer 实体的子类型:
|
类名称
|
值类型
|
|
ProjectListAttribute
|
项目标识符列表
|
|
ProjectAttribute
|
单一项目标识符
|
|
ReleaseListAttribute
|
版本标识符列表
|
|
ReleaseAttribute
|
单一版本标识符
|
|
TeamListAttribute
|
团队标识符列表
|
|
TeamAttribute
|
单一团队标识符
|
|
TrackerListAttribute
|
跟踪器标识符列表
|
|
TrackerAttribute
|
单一跟踪器标识符
|
|
TrackerTypeListAttribute
|
TrackerType 标识符列表
|
|
TypedTrackerListAttribute
|
类似于 TrackerListAttribute,但仅允许使用给定的跟踪器类型
|
|
UserListAttribute
|
用户标识符列表
|
|
UserAttribute
|
单一用户标识符
|
呈现器接口和实施
通过 Renderer 实例提供内容,这是小组件框架中的首选方法。
如上一代码示例所示,通过此接口,可以编写简洁且易于理解的小组件类,具体方法是将呈现小组件内容和编辑器表单的潜在复杂任务移到独立类中。
类:
import com.intland.codebeamer.dashboard.component.common.RenderingContext;
public interface Renderer<T> {
String render(RenderingContext renderingContext, T object);
}
render 方法会根据当前 RenderingContext 和给定对象创建字符串值。
根据设计,此接口不会强制执行任何技术解决方案来创建对象的字符串表示。开发人员可根据各自的首选项或规范,使用基于模板或编程的呈现方法。
HTML 内容呈现器有一个限制条件:自定义代码必须封装在 DIV 元素中,其中包含小组件的标识符,如以下 Velocity 模板所示:
<div id="${id}">
${html}
</div>
以下代码所示为一个示例实施:
package com.intland.codebeamer.dashboard.component.widgets.wiki;
// Imports removed
@Component
@Qualifier("wikiMarkupWidgetHtmlRenderer")
public class WikiMarkupWidgetHtmlRenderer implements Renderer<WikiMarkupWidget> {
private static final String TEMPLATE = "wikimarkup-widget.vm";
@Autowired
private WikiPageManager wikiPageManager;
@Autowired
private WikiMarkupProcessor wikiMarkupProcessor;
@Override
public String render(final RenderingContext renderingContext, final WikiMarkupWidget object) {
// Initialization and argument checks (omitted)
// Use the arguments to retrieve the information required to render the wiki markup to html.
final String id = object.getId();
final Map<String, WidgetAttribute> attributes = object.getAttributes();
final WidgetAttribute wikiMarkupAttribute = attributes.get(WikiMarkupWidget.MARKUP_ATTRIBUTE_NAME);
final String markup = wikiMarkupAttribute.getValue() != null ? (String) wikiMarkupAttribute.getValue() : "";
// Transforming Wiki markup to HTML (omitted)
// Add data to Velocity context
velocityContext.put("id", id);
velocityContext.put("html", html);
final TemplateRenderer templateRenderer = TemplateRenderer.getInstance();
// Render Velocity template
return templateRenderer.renderTemplateOnPath(TEMPLATE, velocityContext, new Parameters(renderingContext.getRequest().getLocale(), false));
}
// Helper methods (omitted)
}
使用支持类可以简化编辑器表单内容的呈现。这些类根据小组件和标准呈现器的属性来呈现编辑器,从而为各个属性创建相应的表单元素。
所有内置 WidgetAttribute 类型都有其特定的呈现器。
此实施基于 Velocity 模板。
|
|
在此类型的呈现器实施中,必须使用 Velocity 模板。
|
package com.intland.codebeamer.dashboard.component.widgets.wiki;
import org.apache.commons.lang3.Validate;
import org.apache.velocity.VelocityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.intland.codebeamer.dashboard.component.common.RenderingContext;
import com.intland.codebeamer.dashboard.component.common.interfaces.Renderer;
import com.intland.codebeamer.dashboard.component.widgets.common.ModelPopulator;
import com.intland.codebeamer.utils.TemplateRenderer;
import com.intland.codebeamer.utils.TemplateRenderer.Parameters;
@Component
@Qualifier("wikiMarkupWidgetEditorRenderer")
public class WikiMarkupWidgetEditorRenderer implements Renderer<WikiMarkupWidget> {
private static final String NULL_MESSAGE_FORMAT = "The value %s must not be null.";
private static final String EDITOR_TEMPLATE = "wikimarkup-widget-editor.vm";
// This is the required helper class, which will generate the editor form elements
private final ModelPopulator<VelocityContext> modelPopulator;
private final TemplateRenderer templateRenderer;
@Autowired
public WikiMarkupWidgetEditorRenderer(final ModelPopulator<VelocityContext> modelPopulator, final TemplateRenderer templateRenderer) {
Validate.notNull(modelPopulator, NULL_MESSAGE_FORMAT, "modelPopulator");
Validate.notNull(templateRenderer, NULL_MESSAGE_FORMAT, "templateRenderer");
this.modelPopulator = modelPopulator;
this.templateRenderer = templateRenderer;
}
@Override
public String render(final RenderingContext renderingContext, final WikiMarkupWidget wikiMarkupWidget) {
Validate.notNull(renderingContext, NULL_MESSAGE_FORMAT, "renderingContext");
Validate.notNull(wikiMarkupWidget, NULL_MESSAGE_FORMAT, "wikiMarkupWidget");
// Create the Velocity context
final VelocityContext velocityContext = new VelocityContext();
// Let the populator create the right HTML fragments for each attribute
modelPopulator.populateAttributes(renderingContext, wikiMarkupWidget, WikiMarkupWidget.getDescriptor(), velocityContext);
// Return the rendererd template
return templateRenderer.renderTemplateOnPath(EDITOR_TEMPLATE, velocityContext, new Parameters(renderingContext.getRequest().getLocale(), false));
}
}
ModelPopulator 将各个表单元素添加到 VelocityContext (其名称由小组件的描述符定义,在本例中,其名称为 WikiMarkupWidget.getDescriptor()) 中。模板如下所示:
${markup}
<br/>
虽然生成了表单元素,但仍可通过此模板添加专属内容。
呈现上下文实施
RenderingContext 实例中包含数据,可用于呈现 HTML 内容。
// Imports omitted
public class RenderingContext {
// The current user, who is viewing or editing the dashboard.
private final UserDto user;
// The current request instance.
private final HttpServletRequest request;
// WikiPageDto representing the actual Dashboard.
private final WikiPageDto dashboard;
// Utility class, which can be used to translate keys from ApplicationResources.properties to text
private final LocalizedTextRetriever localizedTextRetriever;
// Constructor, getters omitted
}
小组件工厂接口
通过实现 WidgetFactory 接口,可以在从持久层加载仪表板以及在创建或编辑小组件时,创建新的小组件实例。
package com.intland.codebeamer.dashboard.component.serializer;
import com.fasterxml.jackson.databind.InjectableValues;
public interface JacksonObjectDeserializeContext<T> {
// Class of the Widget
Class<T> getType();
// Full class name of the Widget
String getTypeName();
// All the properties, which must be injected through dependency injection into Widgets. (See the Jackson annotation on the example Widget constructor.)
InjectableValues getInjectableValues();
}
package com.intland.codebeamer.dashboard.component.common.interfaces;
import com.intland.codebeamer.dashboard.component.serializer.JacksonObjectDeserializeContext;
// Marker interface
public interface LayoutComponentFactory<T extends LayoutComponent> extends JacksonObjectDeserializeContext<T> {
}
package com.intland.codebeamer.dashboard.component.common.interfaces;
import java.util.Map;
public interface WidgetFactory<T extends Widget> extends LayoutComponentFactory<T> {
// Create an instance with the given attributes
T createInstance(String id, Map<String, String> attributes);
// Create an instance with the given attributes, run validations
T createInstance(String id, Map<String, String> attributes, boolean validate);
// Create an instance with default attribute values
T createInstance();
}
以下代码所示为一个示例实施:
package com.intland.codebeamer.dashboard.component.widgets.wiki;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.InjectableValues;
import com.intland.codebeamer.dashboard.component.common.interfaces.Renderer;
import com.intland.codebeamer.dashboard.component.common.interfaces.WidgetFactory;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttributeMapper;
@Component
public class WikiMarkupWidgetFactory implements WidgetFactory<WikiMarkupWidget> {
private static final String NULL_MESSAGE_FORMAT = "The value %s must not be null.";
// The HMTL renderer
private final Renderer<WikiMarkupWidget> htmlRenderer;
// The editor form renderer
private final Renderer<WikiMarkupWidget> editorRenderer;
private final WidgetAttributeMapper widgetAttributeMapper;
// Qualifiers are important here!
@Autowired
public WikiMarkupWidgetFactory(@Qualifier("wikiMarkupWidgetHtmlRenderer") final Renderer<WikiMarkupWidget> htmlRenderer,
@Qualifier("wikiMarkupWidgetEditorRenderer") final Renderer<WikiMarkupWidget> editorRenderer,
final WidgetAttributeMapper widgetAttributeMapper) {
Validate.notNull(htmlRenderer, NULL_MESSAGE_FORMAT, "htmlRenderer");
Validate.notNull(editorRenderer, NULL_MESSAGE_FORMAT, "editorRenderer");
Validate.notNull(widgetAttributeMapper, NULL_MESSAGE_FORMAT, "widgetAttributeMapper");
this.htmlRenderer = htmlRenderer;
this.editorRenderer = editorRenderer;
this.widgetAttributeMapper = widgetAttributeMapper;
}
@Override
public WikiMarkupWidget createInstance(String id, Map<String, String> attributes) {
return createInstance(id, attributes, true);
}
@Override
public WikiMarkupWidget createInstance(String id, Map<String, String> attributes, boolean validate) {
Validate.notBlank(id, NULL_MESSAGE_FORMAT, "id");
Validate.notNull(attributes, NULL_MESSAGE_FORMAT, "attributes");
final Map<String, WidgetAttribute> widgetAttributes = widgetAttributeMapper.map(attributes, WikiMarkupWidget.getDescriptor(), validate);
return new WikiMarkupWidget(id, widgetAttributes, htmlRenderer, editorRenderer);
}
// The default attributes of the Widget are accessed via a static method
@Override
public WikiMarkupWidget createInstance() {
return new WikiMarkupWidget(UUID.randomUUID().toString(), WikiMarkupWidget.getDescriptor(), htmlRenderer, editorRenderer);
}
@Override
public Class<WikiMarkupWidget> getType() {
return WikiMarkupWidget.class;
}
@Override
public String getTypeName() {
return WikiMarkupWidget.class.getCanonicalName();
}
// Add all the constructor attributes of the Widget class, which needs to be injected via dependency injection. Make sure that the name of the value here matches the Jackson annotation on the Widget itself! ("wikiMarkupWidgetHtmlRenderer" for example)
@Override
public InjectableValues getInjectableValues() {
final InjectableValues inject = new InjectableValues.Std()
.addValue("wikiMarkupWidgetHtmlRenderer", htmlRenderer)
.addValue("wikiMarkupWidgetEditorRenderer",editorRenderer);
return inject;
}
}
将现有 Wiki 插件封装为小组件
可以将先前写入的 Wiki 插件复用为小组件。将针对基于插件的小组件和独立小组件创建相同的类,但这两种插件之间存在细微的差别。
如果 Wiki 插件旨在用于 HTML 表示,则需要将小组件的实际参数作为自变量传递至该插件。还需要调用 execute 方法来生成 HTML。
该框架提供的呈现器实施有助于简化此过程。AbstractDefaultWidgetHtmlRenderer 类可以定义几个重要的方法,如下面的代码所示:
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.intland.codebeamer.dashboard.component.common.interfaces.Widget;
import com.intland.codebeamer.dashboard.component.widgets.common.DefaultWidgetHtmlRenderer;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute;
import com.intland.codebeamer.dashboard.component.widgets.provider.WidgetAttributeValueProvider;
import com.intland.codebeamer.wiki.plugins.base.AbstractCodeBeamerWikiPlugin;
public abstract class AbstractDefaultWidgetHtmlRenderer<U extends Widget, Z extends AbstractCodeBeamerWikiPlugin> extends DefaultWidgetHtmlRenderer<U, Z>{
@Autowired
public AbstractDefaultWidgetHtmlRenderer(ObjectFactory<Z> factory) {
super(factory);
}
protected Map<String, String> createPluginParameters(Map<String, WidgetAttribute> attributes) {
final Map<String, String> parameters = new HashMap<String, String>();
// Generates the parameters of the plugin using the WidgetAttributeValueProviders, code omitted
createAdditionalParameters(attributes, parameters);
return parameters;
}
// You can add any parameter here, which is not generated by a WidgetAttributeValueProvider instance
protected void createAdditionalParameters(final Map<String, WidgetAttribute> attributes, final Map<String, String> parameters) {
}
// Returns the WidgetAttributes defined in the Widget itself
protected abstract WidgetAttributeWrapper[] getWidgetAttributes();
// A list of WidgetAttributeValueProvider instances. These can map a WidgetAttribute to a Wiki plugin parameter.
protected Map<WidgetAttributeWrapper, WidgetAttributeValueProvider> getValueProviders() {
return Collections.emptyMap();
}
}
WidgetAttributeValueProvider 实例将根据 Wiki 插件的接口,将 WidgetAttributes 转换为字符串参数,如下面的代码所示:
import java.util.Map;
import com.intland.codebeamer.dashboard.component.widgets.common.WidgetAttributeWrapper;
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute;
public interface WidgetAttributeValueProvider {
String obtainValue(WidgetAttributeWrapper attribute, Map<String, WidgetAttribute> attributes);
}
以下代码显示了一个整数转换示例:
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute;
public abstract class AbstractIntegerValueProvider extends AbstractWidgetAttributeValueProvider<WidgetAttribute<Integer>> {
@Override
protected String convertWidgetAttributeToString(final WidgetAttribute<Integer> widgetAttribute) {
return widgetAttribute.getValue().toString();
}
}
AbstractWidgetAttributeValueProvider 类包含 obtainValue 方法的默认实施,因此子类只需定义以下内容:
• convertWidgetAttributeToString。
• getWidgetAttributeKey 方法。
getWidgetAttributeKey 应返回 Wiki 插件参数的名称,例如以下示例类:
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Qualifier("widthIntegerValueProvider")
@Component
public class WidthIntegerValueProvider extends AbstractIntegerValueProvider {
public static final String WIDTH = "width";
@Override
public String getWidgetAttributeKey() {
return WIDTH;
}
}
以下代码显示了一个在自定义呈现器中封装插件呈现功能的示例:
package com.intland.codebeamer.dashboard.component.widgets.project;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.intland.codebeamer.dashboard.component.widgets.common.AbstractDefaultWidgetHtmlRenderer;
import com.intland.codebeamer.dashboard.component.widgets.common.WidgetAttributeWrapper;
import com.intland.codebeamer.dashboard.component.widgets.provider.WidgetAttributeValueProvider;
import com.intland.codebeamer.wiki.plugins.releaseganttchart.ReleaseGanttChartPlugin;
@Component
@Qualifier("releaseGanttChartWidgetHtmlRenderer")
public class ReleaseGanttChartWidgetHtmlRenderer extends AbstractDefaultWidgetHtmlRenderer<ReleaseGanttChartWidget, ReleaseGanttChartPlugin> {
private static final String NULL_MESSAGE_FORMAT = "The value %s must not be null.";
private final WidgetAttributeValueProvider releaseListAttributeValueProvider;
private final WidgetAttributeValueProvider heightAttributeValueProvider;
@Autowired
public ReleaseGanttChartWidgetHtmlRenderer(ObjectFactory<ReleaseGanttChartPlugin> objectFactory,
@Qualifier("ganttReleaseListAttributeValueProvider") final WidgetAttributeValueProvider releaseListAttributeValueProvider,
@Qualifier("ganttHeightAttributeProvider") final WidgetAttributeValueProvider heightAttributeValueProvider) {
super(objectFactory);
Validate.notNull(releaseListAttributeValueProvider, NULL_MESSAGE_FORMAT, "releaseListAttributeValueProvider");
Validate.notNull(heightAttributeValueProvider, NULL_MESSAGE_FORMAT, "heightAttributeValueProvider");
this.releaseListAttributeValueProvider = releaseListAttributeValueProvider;
this.heightAttributeValueProvider = heightAttributeValueProvider;
}
@Override
protected WidgetAttributeWrapper[] getWidgetAttributes() {
return ReleaseGanttChartWidget.Attribute.values();
}
@Override
protected Map<WidgetAttributeWrapper, WidgetAttributeValueProvider> getValueProviders() {
final Map<WidgetAttributeWrapper, WidgetAttributeValueProvider> valueProviders =
new HashMap<WidgetAttributeWrapper, WidgetAttributeValueProvider>();
valueProviders.put(ReleaseGanttChartWidget.Attribute.RELEASE_IDS, releaseListAttributeValueProvider);
valueProviders.put(ReleaseGanttChartWidget.Attribute.HEIGHT, heightAttributeValueProvider);
return valueProviders;
}
}
排除故障
如果向仪表板中添加了多个小组件,则某些小组件可能无法加载。
为了避免此类问题,受影响的小组件必须将 data-deferred="true" 添加到由小组件呈现的 HTML 元素中。
完成所有操作后,受影响的小组件必须触发 renderfinished 自定义事件,才能通知应用程序这一情况。
示例:
$(CSS_SELECTOR).trigger("renderfinished")
。