Skip to main content

Developer Guide: Adding Quick Edit to a Widget

Overview

This guide explains how to add quick editing capabilities to a new or existing widget in the Wallboard system. Quick editing allows users to override widget values at the message (sub-channel) level through a simplified interface.

Prerequisites

  • Understanding of the widget system architecture
  • Familiarity with AngularJS and TypeScript
  • Knowledge of the widget service structure

Step-by-Step Implementation

Note: The quick edit configuration is automatically processed during content save operations by contentSaverService. The generateQuickEditableWidgetsBlock() method is called before saving to ensure all quick edit metadata is up-to-date.

Step 1: Initialize Quick Editables in the Widget

In your widget's initialization code, add the initQuickEditable function and call it during editor initialization:

TypeScript Widget Example (e.g., Text Widget)

// In your widget service class
private initQuickEditable(widget: YourWidgetType) {
if (!widget.quickEditables) {
widget.quickEditables = {
// Define which properties can be quick-edited
// Property name: default enabled state
text: false // For text widget
};
}
}

// Call during editor initialization
initEditorBehaviour(scope: ng.IScope & { tagObject: YourWidgetType }) {
this.initQuickEditable(scope.tagObject);
// ... other initialization code
}

JavaScript Widget Example (e.g., Image Widget)

function initQuickEditable(tagObject) {
if (!tagObject.quickEditables) {
tagObject.quickEditables = {
src: false // For image widget
};
}
}

// In initEditorWatchers function
widgetObject.initEditorWatchers = function(scope, element, attrs, id) {
initQuickEditable(scope.tagObject);
// ... other watchers
};

Step 2: Create the Quick Edit UI Block

Define a UI block that allows users to toggle quick editing for each property:

Simple Property Example (Text, Image Source)

const quickEditBlock = {
getBlock: (widget) => {
return {
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'text', // or 'src' for image
label: 'Quick editable',
tooltip: 'Enables overriding value from the quick editor',
onChange: () => {
editorStatus.contentJsonChanged();
}
};
}
};

Multiple Properties Example (Shape Widget)

const quickEditBlock = {
getBlock: (widget) => {
return [
{
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'shape.fillColor',
label: 'Fill color',
tooltip: 'Enables overriding fill color from the quick editor',
onChange: () => {
editorStatus.contentJsonChanged();
}
},
{
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'shape.strokeStyle',
label: 'Stroke color',
tooltip: 'Enables overriding stroke color from the quick editor',
onChange: () => {
editorStatus.contentJsonChanged();
}
}
];
}
};

Step 3: Add Quick Edit Block to Widget Tabs

Include the quick edit block in your widget's tab configuration:

getTabDetails(tabName: WidgetTabName, widget: YourWidgetType): WidgetTabDetails {
const allTabDetails: Record<WidgetTabName, WidgetTabDetails> = {
[WidgetTabName.general]: {
tabName: 'general',
tagObject: widget,
sections: [
// ... other sections
{
label: 'Quick edit',
blockSchemes: [this.quickEditBlock],
hiddenRuleBuilder: new EditorSectionHideRuleBuilder()
.setWidgetType(WidgetType.YourWidget)
.setSection(UsualSectionName.quickEdit)
}
]
},
// ... other tabs
};
return allTabDetails[tabName];
}

Step 4: Register Widget Type in QuickEditableInputMapper

Update the QuickEditableInputMapper service to handle your widget type:

Add to Widget Type Mapping

// In quick-editable-input-mapper.service.ts

private getQuickEditGroupForWidget(widgetType: WidgetType): QuickEditAttributeGroupType {
switch (widgetType) {
// ... existing cases
case WidgetType.YourWidget:
return QuickEditAttributeGroupType.YOUR_GROUP;
default:
throw new Error(`Unsupported widget type: ${widgetType}`);
}
}

private getQuickEditAttributeTypeForWidget(
widgetType: WidgetType,
editedProperty: string
): QuickEditAttributeType {
const widgetAttributeMap: Partial<Record<WidgetType, Record<string, QuickEditAttributeType>>> = {
// ... existing mappings
[WidgetType.YourWidget]: {
yourProperty: QuickEditAttributeType.YOUR_ATTRIBUTE_TYPE,
}
};
// ... rest of the function
}

Add Property Processing

private getWidgetQuickEditSettings(
quickEditObject: QuickEditObject,
editedProperty: string
): QuickEditToggleParams {
// ... existing code
switch (editedProperty) {
// ... existing cases
case 'yourProperty':
return this.createQuickEditParams(
isEnabled,
groupType,
attributeType,
QuickEditOverrideRule.MESSAGE,
QuickEditAttributeGroupValueType.SINGLE_VALUED,
QuickEditAttributeValueType.STRING
);
default:
return null;
}
}

Step 5: Update Type Definitions

Add necessary enums and types if your widget requires new attribute types:

// In widgetTypeDefinitions.ts

// Add to QuickEditAttributeGroupType enum if needed
export enum QuickEditAttributeGroupType {
// ... existing values
YOUR_GROUP = 'YOUR_GROUP'
}

// Add to QuickEditAttributeType enum
export enum QuickEditAttributeType {
// ... existing values
YOUR_ATTRIBUTE_TYPE = 'YOUR_ATTRIBUTE_TYPE'
}

Step 6: Handle Property Mapping in Processor

Update the QuickEditableProcessor to handle your widget's properties:

// In quick-editable-widget-processor.service.ts

private getPropertyPathFromType(type: QuickEditAttributeType): string {
switch (type) {
// ... existing cases
case QuickEditAttributeType.YOUR_ATTRIBUTE_TYPE:
return 'yourProperty'; // or 'nested.property.path'
default:
return '';
}
}

Complete Examples

Example 1: Simple Text Widget

// text.ts
@WidgetService('TextWidgetService')
export class TextWidgetService {

private quickEditBlock = {
getBlock: (widget: TextWidget) => {
return {
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'text',
label: 'Quick editable',
tooltip: 'Enables overriding value from the quick editor',
onChange: () => {
this.editorStatus.contentJsonChanged();
}
};
}
};

private initQuickEditable(widget: TextWidget) {
if (!widget.quickEditables) {
widget.quickEditables = { text: false };
}
}

initEditorBehaviour(scope: ng.IScope & { tagObject: TextWidget }) {
this.initQuickEditable(scope.tagObject);
}

getTabDetails(tabName: WidgetTabName, widget: TextWidget): WidgetTabDetails {
// Include quickEditBlock in sections
}
}

Example 2: Media Widget with Caching

// video.js
const quickEditBlock = {
getBlock: (widget) => {
return {
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'src',
label: 'Quick editable',
tooltip: 'Enables overriding video source from the quick editor',
onChange: () => {
editorStatus.contentJsonChanged();
}
};
}
};

function initQuickEditable(tagObject) {
if (!tagObject.quickEditables) {
tagObject.quickEditables = { src: false };
}
}

// Video sources are automatically cached by the processor

Example 3: Multi-Property Widget

// shape.js
const quickEditBlock = {
getBlock: (widget) => {
return [
{
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'shape.fillColor',
label: 'Fill color',
tooltip: 'Enables overriding fill color from the quick editor',
onChange: () => {
editorStatus.contentJsonChanged();
}
},
{
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'shape.gradColor',
label: 'Gradient color',
tooltip: 'Enables overriding gradient color from the quick editor',
onChange: () => {
editorStatus.contentJsonChanged();
},
ngHideFunction: () => !widget.shape.gradientEnabled
},
{
type: 'slidebox',
value: widget.quickEditables,
propertyName: 'shape.strokeStyle',
label: 'Stroke color',
tooltip: 'Enables overriding stroke color from the quick editor',
onChange: () => {
editorStatus.contentJsonChanged();
}
}
];
}
};

Best Practices

1. Property Naming

  • Use dot notation for nested properties: 'shape.fillColor'
  • Keep property names consistent with widget data structure

2. Default States

  • Initialize quickEditables with false by default
  • Only enable quick editing when explicitly requested by user

3. Change Tracking

  • Always call editorStatus.contentJsonChanged() in onChange handlers
  • This ensures changes are saved properly

4. UI Considerations

  • Provide clear labels and helpful tooltips
  • Use ngHideFunction to conditionally show options
  • Group related quick-editable properties together

5. Type Safety

  • Define proper TypeScript interfaces for your widget type
  • Use enums for attribute types and groups
  • Validate property paths at compile time when possible

Summary

Adding quick edit support to a widget involves:

  1. Initializing the quickEditables object
  2. Creating UI controls for toggling quick edit
  3. Registering widget type and properties in the mapper
  4. Updating type definitions
  5. Handling property mapping in the processor
  6. Testing the implementation thoroughly