Bring Your Own (BYO) 7.24 Implementation Example
Overview
The Bring Your Own (BYO) 7.24 implementation examples page contains a list of arguments for a custom element. Use this list as a base for building your own components using our BYO Component SDK guide.
The Bring Your Own framework is intended for developers who have a strong understanding of the Unqork Designer Platform and JavaScript JavaScript is an object-oriented computer programming language. It is most-commonly used for interactive effects in the browser..
This example is for Creators Also known as Unqork Users, or Designer Users; is anyone who is inside the Unqork platform. using the 7.24 Unqork Designer Platform release. For environments using the 7.22 release, view our BYO 7.22 Implementation Examples.
Ag Grid Component
Configuration
Settings | Object Type | Description |
---|---|---|
columnDefinitions |
string componentKey |
The definitions of the columns from the key and label data. |
value |
string componentKey |
The value of the grid data. This version accepts a string A string is an object that represents a sequence of characters. Strings typically hold data represented in text form., but it can be defined as needed. |
Events
Event Name | Event Value | Description |
---|---|---|
Cell Clicked |
agGridCellClicked |
Add a custom event that emits on cell click. Event Payload: Add the details of the cell that was clicked into the Event Payload to use in logic:
|
Manifest JSON Example
{
"name": "AgGrid",
"version": "1.0.0",
"main": "agGrid.js",
"type": "custom",
"productType": "BYO",
"description": "AG Grid integration with Unqork",
"components": [
{
"name": "AG Grid",
"type": "agGrid",
"description": "AG Grid component for Unqork",
"model": {
"type": "object",
"properties": {
"columnDefinitions": {
"name": "Column Definitions",
"type": "array",
"description": "Column definitions for this grid (currently only supporting key and label)",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Unique key for the column"
},
"label": {
"type": "string",
"description": "Display label for the column"
}
}
}
},
"value": {
"name": "Grid Data (JSON)",
"type": "string",
"description": "JSON string of data for the grid"
}
},
"required": ["columnDefinitions"]
},
"events": [
{
"name": "AG Grid Cell Clicked",
"type": "agGridCellClicked",
"description": "This event is triggered when the user clicks on a grid cell.",
"stability": "STABLE"
}
]
}
]
}
agGrid.js
/// AgGrid Import (removed for to make file legible) ///
class UnqorkAgGridComponent extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.gridOptions = {
columnDefs: [],
rowData: [],
}
}
/**
* This is the only required method to integrate with the Unqork system
*
* @param {*} api The interface to the Unqork Runtime, used for both
* monitoring state and emitting events
*/
initialize(api) {
this.config = api.state.currentState()
this.api = api
if (this.config.columnDefinitions?.length) {
const defs = this._normalizeInput(this.config.columnDefinitions)
this.gridOptions.columnDefs = this._formatColumnDefinitions(defs)
}
if (this.config.value?.length) {
this.gridOptions.rowData = this._normalizeInput(this.config.value)
}
this.renderGrid(this.shadowRoot, this.gridOptions)
this.subscribeColumnDefs()
this.subscribeData()
}
/**
* Subscribe to the source for column definitions
*
* This can be made more robust by first using
* api.state.state$('columnDefsReference') to subscribe to
* any changes to that value (i.e. the target key), and then using
* resolveByKey$ to resolve the current value of that key, but
* keeping it simple for example here
*/
subscribeColumnDefs() {
this
.api
.state
.state$('columnDefinitions').subscribe((columnDefs) => {
console.log('AG Grid column definitions updated to', columnDefs)
// This _should_ be an array, but sometimes components try to set it as a
// JSON string instead, so we handle that here
const value = typeof(columnDefs) === 'string' ? JSON.parse(columnDefs) : columnDefs
this.gridOptions = {
rowData: this.gridOptions.rowData,
columnDefs: value ? this._formatColumnDefinitions(value) : [],
}
this.updateGrid(this.gridOptions)
})
}
subscribeData() {
this.api.state.state$('value').subscribe((rowData) => {
console.log('AG Grid value updated to', rowData)
// We support setting this as a JSON string or already parsed array of objects
// so handle that here
const value = typeof(rowData) === 'string' ? JSON.parse(rowData) : rowData
this.gridOptions = {
rowData: value || [],
columnDefs: this.gridOptions.columnDefs,
}
this.updateGrid(this.gridOptions)
})
}
updateGrid(gridOptions) {
console.log('Updating AG Grid', gridOptions)
this.gridApi.updateGridOptions(gridOptions)
}
async renderGrid(document, gridOptions) {
console.log('Rendering AG Grid component', {gridOptions, agGrid, windowAgGrid: window.agGrid})
document.innerHTML = this.view()
this.gridEl = document.querySelector('#myGrid')
this.gridApi = window.agGrid.createGrid(this.gridEl, gridOptions)
// Set this here so that it doesn't overridden when we update gridOptions
this.gridApi.addEventListener('cellClicked', (ev) => {
console.log('Emitting AG Grid Cell Clicked Event', ev)
this.emitCellClickedEvent(ev)
})
console.log('Created AG Grid', {
document,
gridOptions,
gridEl: this.gridEl,
gridApi: this.gridApi,
})
}
// Emit an event when a cell is clicked
// Full docs: https://www.ag-grid.com/javascript-data-grid/grid-events/#reference-selection-cellClicked
emitCellClickedEvent = (ev) => {
this.api.events.emit({
name: 'onAgGridCellClicked',
payload: ev
})
}
view() {
return `
<div id="myGrid" style="height: 500px"></div>
`
}
/**
* Input data can be either JSON string or actual data, due to
* Unqork data constraints, so ensure it's proper data before
* using
*/
_normalizeInput(val) {
return typeof(val) === 'string' ? JSON.parse(val) : val
}
/**
* We store column definitions with standard key/label properties
* But AG Grid using different property names, so we map it here
*/
_formatColumnDefinitions(defs) {
return defs.map(({key, label}) => {
return {
field: key,
headerName: label ?? key
}
})
}
}
// This definition defines the state settings (model) for this component
// It mirrors the fuller description in manifest.json and will
// eventually be used to auto-generate the manifest.json JSON schema
class AgGridDefinition {
columnDefsReference
dataReference
}
// Actual export that exposes both the view layer and the model
// and is consumed by the Unqork Runtime for both configuration and
// execution
// All of these are `async` to allow for us to load them on demand
// without impacting initial page load performance
export const agGrid = {
model: async () => AgGridDefinition,
view: async () => UnqorkAgGridComponent,
}
// This definition describes the schema of the event that is emitted
// when the user clicks on an item
class CellClicked {
name = 'agGridCellClicked'
payload = {
index,
text,
}
}
// This export is used to expose the event to the Unqork Runtime
export const agGridCellClicked = {
model: async () => CellClicked,
}
Implementation Example
// << AgGrid lib needs to be imported/insertend here >>
// << For readability purposes it was removed >>
class UnqorkAgGridComponent extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.gridOptions = {
columnDefs: [],
rowData: [],
}
}
/**
* This is the only required method to integrate with the Unqork system
*
* @param {*} api The interface to the Unqork Runtime, used for both
* monitoring state and emitting events
*/
initialize(api) {
this.config = api.state.currentState()
this.api = api
if (this.config.columnDefinitions?.length) {
const defs = this._normalizeInput(this.config.columnDefinitions)
this.gridOptions.columnDefs = this._formatColumnDefinitions(defs)
}
if (this.config.value?.length) {
this.gridOptions.rowData = this._normalizeInput(this.config.value)
}
this.renderGrid(this.shadowRoot, this.gridOptions)
this.subscribeColumnDefs()
this.subscribeData()
}
/**
* Subscribe to the source for column definitions
*
* This can be made more robust by first using
* api.state.state$('columnDefsReference') to subscribe to
* any changes to that value (i.e. the target key), and then using
* resolveByKey$ to resolve the current value of that key, but
* keeping it simple for example here
*/
subscribeColumnDefs() {
this
.api
.state
.state$('columnDefinitions').subscribe((columnDefs) => {
console.log('AG Grid column definitions updated to', columnDefs)
// This _should_ be an array, but sometimes components try to set it as a
// JSON string instead, so we handle that here
const value = typeof(columnDefs) === 'string' ? JSON.parse(columnDefs) : columnDefs
this.gridOptions = {
rowData: this.gridOptions.rowData,
columnDefs: value ? this._formatColumnDefinitions(value) : [],
}
this.updateGrid(this.gridOptions)
})
}
subscribeData() {
this.api.state.state$('value').subscribe((rowData) => {
console.log('AG Grid value updated to', rowData)
// We support setting this as a JSON string or already parsed array of objects
// so handle that here
const value = typeof(rowData) === 'string' ? JSON.parse(rowData) : rowData
this.gridOptions = {
rowData: value || [],
columnDefs: this.gridOptions.columnDefs,
}
this.updateGrid(this.gridOptions)
})
}
updateGrid(gridOptions) {
console.log('Updating AG Grid', gridOptions)
this.gridApi.updateGridOptions(gridOptions)
}
async renderGrid(document, gridOptions) {
console.log('Rendering AG Grid component', {gridOptions, agGrid, windowAgGrid: window.agGrid})
document.innerHTML = this.view()
this.gridEl = document.querySelector('#myGrid')
this.gridApi = window.agGrid.createGrid(this.gridEl, gridOptions)
// Set this here so that it doesn't overridden when we update gridOptions
this.gridApi.addEventListener('cellClicked', (ev) => {
console.log('Emitting AG Grid Cell Clicked Event', ev)
this.emitCellClickedEvent(ev)
})
console.log('Created AG Grid', {
document,
gridOptions,
gridEl: this.gridEl,
gridApi: this.gridApi,
})
}
// Emit an event when a cell is clicked
// Full docs: https://www.ag-grid.com/javascript-data-grid/grid-events/#reference-selection-cellClicked
emitCellClickedEvent = (ev) => {
this.api.events.emit({
name: 'onAgGridCellClicked',
payload: ev
})
}
view() {
return `
<div id="myGrid" style="height: 500px"></div>
`
}
/**
* Input data can be either JSON string or actual data, due to
* Unqork data constraints, so ensure it's proper data before
* using
*/
_normalizeInput(val) {
return typeof(val) === 'string' ? JSON.parse(val) : val
}
/**
* We store column definitions with standard key/label properties
* But AG Grid using different property names, so we map it here
*/
_formatColumnDefinitions(defs) {
return defs.map(({key, label}) => {
return {
field: key,
headerName: label ?? key
}
})
}
}
// This definition defines the state settings (model) for this component
// It mirrors the fuller description in manifest.json and will
// eventually be used to auto-generate the manifest.json JSON schema
class AgGridDefinition {
columnDefsReference
dataReference
}
// Actual export that exposes both the view layer and the model
// and is consumed by the Unqork Runtime for both configuration and
// execution
// All of these are `async` to allow for us to load them on demand
// without impacting initial page load performance
export const agGrid = {
model: async () => AgGridDefinition,
view: async () => UnqorkAgGridComponent,
}
// This definition describes the schema of the event that is emitted
// when the user clicks on an item
class CellClicked {
name = 'onAgGridCellClicked'
payload = {
index,
text,
}
}
// This export is used to expose the event to the Unqork Runtime
export const onAgGridCellClicked = {
model: async () => CellClicked,
}
Root Level Event
Manifest JSON Example
{
"name": "eventsOnly",
"version": "1.0.0",
"main": "eventsOnly.js",
"type": "custom",
"productType": "BYO",
"description": "Asset that contains only general events.",
"events": [
{
"name": "Events Only",
"type": "eventsOnly",
"description": "A top-level event.",
"stability": "ALPHA",
"model": {
"type": "object",
"properties": {
"name": {
"const": "eventsOnly",
"type": "string"
},
"payload": {
"type": "object",
"properties": {
"data": {
"description": "Sample property",
"type": "string"
}
}
}
}
}
}
]
}
eventsOnly.js
class EventPayload {
data = 'defaultValue'
}
class Event {
name = 'eventsOnly'
payload = new EventPayload()
}
// Export for the event
export const eventsOnly = {
model: async () => Event,
}
Implementation Example
// << AgGrid lib needs to be imported/insertend here >>
// << For readability purposes it was removed >>
class UnqorkAgGridComponent extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.gridOptions = {
columnDefs: [],
rowData: [],
}
}
/**
* This is the only required method to integrate with the Unqork system
*
* @param {*} api The interface to the Unqork Runtime, used for both
* monitoring state and emitting events
*/
initialize(api) {
this.config = api.state.currentState()
this.api = api
if (this.config.columnDefinitions?.length) {
const defs = this._normalizeInput(this.config.columnDefinitions)
this.gridOptions.columnDefs = this._formatColumnDefinitions(defs)
}
if (this.config.value?.length) {
this.gridOptions.rowData = this._normalizeInput(this.config.value)
}
this.renderGrid(this.shadowRoot, this.gridOptions)
this.subscribeColumnDefs()
this.subscribeData()
}
/**
* Subscribe to the source for column definitions
*
* This can be made more robust by first using
* api.state.state$('columnDefsReference') to subscribe to
* any changes to that value (i.e. the target key), and then using
* resolveByKey$ to resolve the current value of that key, but
* keeping it simple for example here
*/
subscribeColumnDefs() {
this
.api
.state
.state$('columnDefinitions').subscribe((columnDefs) => {
console.log('AG Grid column definitions updated to', columnDefs)
// This _should_ be an array, but sometimes components try to set it as a
// JSON string instead, so we handle that here
const value = typeof(columnDefs) === 'string' ? JSON.parse(columnDefs) : columnDefs
this.gridOptions = {
rowData: this.gridOptions.rowData,
columnDefs: value ? this._formatColumnDefinitions(value) : [],
}
this.updateGrid(this.gridOptions)
})
}
subscribeData() {
this.api.state.state$('value').subscribe((rowData) => {
console.log('AG Grid value updated to', rowData)
// We support setting this as a JSON string or already parsed array of objects
// so handle that here
const value = typeof(rowData) === 'string' ? JSON.parse(rowData) : rowData
this.gridOptions = {
rowData: value || [],
columnDefs: this.gridOptions.columnDefs,
}
this.updateGrid(this.gridOptions)
})
}
updateGrid(gridOptions) {
console.log('Updating AG Grid', gridOptions)
this.gridApi.updateGridOptions(gridOptions)
}
async renderGrid(document, gridOptions) {
console.log('Rendering AG Grid component', {gridOptions, agGrid, windowAgGrid: window.agGrid})
document.innerHTML = this.view()
this.gridEl = document.querySelector('#myGrid')
this.gridApi = window.agGrid.createGrid(this.gridEl, gridOptions)
// Set this here so that it doesn't overridden when we update gridOptions
this.gridApi.addEventListener('cellClicked', (ev) => {
console.log('Emitting AG Grid Cell Clicked Event', ev)
this.emitCellClickedEvent(ev)
})
console.log('Created AG Grid', {
document,
gridOptions,
gridEl: this.gridEl,
gridApi: this.gridApi,
})
}
// Emit an event when a cell is clicked
// Full docs: https://www.ag-grid.com/javascript-data-grid/grid-events/#reference-selection-cellClicked
emitCellClickedEvent = (ev) => {
this.api.events.emit({
name: 'onAgGridCellClicked',
payload: ev
})
}
view() {
return `
<div id="myGrid" style="height: 500px"></div>
`
}
/**
* Input data can be either JSON string or actual data, due to
* Unqork data constraints, so ensure it's proper data before
* using
*/
_normalizeInput(val) {
return typeof(val) === 'string' ? JSON.parse(val) : val
}
/**
* We store column definitions with standard key/label properties
* But AG Grid using different property names, so we map it here
*/
_formatColumnDefinitions(defs) {
return defs.map(({key, label}) => {
return {
field: key,
headerName: label ?? key
}
})
}
}
// This definition defines the state settings (model) for this component
// It mirrors the fuller description in manifest.json and will
// eventually be used to auto-generate the manifest.json JSON schema
class AgGridDefinition {
columnDefsReference
dataReference
}
// Actual export that exposes both the view layer and the model
// and is consumed by the Unqork Runtime for both configuration and
// execution
// All of these are `async` to allow for us to load them on demand
// without impacting initial page load performance
export const agGrid = {
model: async () => AgGridDefinition,
view: async () => UnqorkAgGridComponent,
}
// This definition describes the schema of the event that is emitted
// when the user clicks on an item
class CellClicked {
name = 'onAgGridCellClicked'
payload = {
index,
text,
}
}
// This export is used to expose the event to the Unqork Runtime
export const onAgGridCellClicked = {
model: async () => CellClicked,
}
Root Level Operation
Manifest JSON Example
{
"name": "operationsOnly",
"version": "1.0.0",
"main": "operationsOnly.js",
"type": "custom",
"productType": "BYO",
"description": "Asset that contains only general operations.",
"operations": [
{
"name": "Operations Only",
"type": "operationsOnly",
"description": "An operation to transform data.",
"stability": "STABLE",
"model": {
"definitions": {
"OperationOptions": {
"type": "object",
"properties": {
"input": {
"description": "Input component for the operation.",
"type": "string"
},
"output": {
"description": "Output component for the operation.",
"type": "string"
}
},
"required": ["input"]
}
},
"properties": {
"options": {
"$ref": "#/definitions/OperationOptions"
},
"type": {
"const": "operationsOnly",
"type": "string"
}
},
"required": ["options"],
"type": "object"
},
"contextModel": {
"type": "object",
"properties": {
"result": {
"type": "object",
"properties": {
"transformed": { "type": "string", "description": "Transformation result" }
}
}
}
}
}
]
}
operationsOnly.js
class OperationOptions {
input
output
}
class Operation {
type = 'operationsOnly'
options = new OperationOptions()
}
class OperationHandler {
execute(operation, api) {
const input = api.state.resolveByKey(operation.options.input)
const output = api.state.resolveByKey(operation.options.output)
const newValue = 'transformed ' + input.value
api.state.set(output.key, { value: newValue })
return {
transformed: newValue,
}
}
}
class OperationContextResult {
transformed
}
class OperationContext {
result = new OperationContextResult()
}
// Export for the operation
export const operationsOnly = {
model: async () => Operation,
handler: async () => OperationHandler,
contextModel: async () => OperationContext,
}
Implementation Example
// << AgGrid lib needs to be imported/insertend here >>
// << For readability purposes it was removed >>
class UnqorkAgGridComponent extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.gridOptions = {
columnDefs: [],
rowData: [],
}
}
/**
* This is the only required method to integrate with the Unqork system
*
* @param {*} api The interface to the Unqork Runtime, used for both
* monitoring state and emitting events
*/
initialize(api) {
this.config = api.state.currentState()
this.api = api
if (this.config.columnDefinitions?.length) {
const defs = this._normalizeInput(this.config.columnDefinitions)
this.gridOptions.columnDefs = this._formatColumnDefinitions(defs)
}
if (this.config.value?.length) {
this.gridOptions.rowData = this._normalizeInput(this.config.value)
}
this.renderGrid(this.shadowRoot, this.gridOptions)
this.subscribeColumnDefs()
this.subscribeData()
}
/**
* Subscribe to the source for column definitions
*
* This can be made more robust by first using
* api.state.state$('columnDefsReference') to subscribe to
* any changes to that value (i.e. the target key), and then using
* resolveByKey$ to resolve the current value of that key, but
* keeping it simple for example here
*/
subscribeColumnDefs() {
this
.api
.state
.state$('columnDefinitions').subscribe((columnDefs) => {
console.log('AG Grid column definitions updated to', columnDefs)
// This _should_ be an array, but sometimes components try to set it as a
// JSON string instead, so we handle that here
const value = typeof(columnDefs) === 'string' ? JSON.parse(columnDefs) : columnDefs
this.gridOptions = {
rowData: this.gridOptions.rowData,
columnDefs: value ? this._formatColumnDefinitions(value) : [],
}
this.updateGrid(this.gridOptions)
})
}
subscribeData() {
this.api.state.state$('value').subscribe((rowData) => {
console.log('AG Grid value updated to', rowData)
// We support setting this as a JSON string or already parsed array of objects
// so handle that here
const value = typeof(rowData) === 'string' ? JSON.parse(rowData) : rowData
this.gridOptions = {
rowData: value || [],
columnDefs: this.gridOptions.columnDefs,
}
this.updateGrid(this.gridOptions)
})
}
updateGrid(gridOptions) {
console.log('Updating AG Grid', gridOptions)
this.gridApi.updateGridOptions(gridOptions)
}
async renderGrid(document, gridOptions) {
console.log('Rendering AG Grid component', {gridOptions, agGrid, windowAgGrid: window.agGrid})
document.innerHTML = this.view()
this.gridEl = document.querySelector('#myGrid')
this.gridApi = window.agGrid.createGrid(this.gridEl, gridOptions)
// Set this here so that it doesn't overridden when we update gridOptions
this.gridApi.addEventListener('cellClicked', (ev) => {
console.log('Emitting AG Grid Cell Clicked Event', ev)
this.emitCellClickedEvent(ev)
})
console.log('Created AG Grid', {
document,
gridOptions,
gridEl: this.gridEl,
gridApi: this.gridApi,
})
}
// Emit an event when a cell is clicked
// Full docs: https://www.ag-grid.com/javascript-data-grid/grid-events/#reference-selection-cellClicked
emitCellClickedEvent = (ev) => {
this.api.events.emit({
name: 'onAgGridCellClicked',
payload: ev
})
}
view() {
return `
<div id="myGrid" style="height: 500px"></div>
`
}
/**
* Input data can be either JSON string or actual data, due to
* Unqork data constraints, so ensure it's proper data before
* using
*/
_normalizeInput(val) {
return typeof(val) === 'string' ? JSON.parse(val) : val
}
/**
* We store column definitions with standard key/label properties
* But AG Grid using different property names, so we map it here
*/
_formatColumnDefinitions(defs) {
return defs.map(({key, label}) => {
return {
field: key,
headerName: label ?? key
}
})
}
}
// This definition defines the state settings (model) for this component
// It mirrors the fuller description in manifest.json and will
// eventually be used to auto-generate the manifest.json JSON schema
class AgGridDefinition {
columnDefsReference
dataReference
}
// Actual export that exposes both the view layer and the model
// and is consumed by the Unqork Runtime for both configuration and
// execution
// All of these are `async` to allow for us to load them on demand
// without impacting initial page load performance
export const agGrid = {
model: async () => AgGridDefinition,
view: async () => UnqorkAgGridComponent,
}
// This definition describes the schema of the event that is emitted
// when the user clicks on an item
class CellClicked {
name = 'onAgGridCellClicked'
payload = {
index,
text,
}
}
// This export is used to expose the event to the Unqork Runtime
export const onAgGridCellClicked = {
model: async () => CellClicked,
}
Resources
Bring Your Own (BYO) Framework Landing Page
Find all the resources needed to build your own components here.
Introduction to the Bring Your Own (BYO) Framework
Create a BYO Package
Understanding the manifest.json File
Learn about the file's specification, component and event definitions.
Understanding the BYO Runtime Engine API
Discover the interface between your custom BYO components and the Unqork platform.
Custom Component SDK
Extend component functionality in the Unqork Designer Platform.
Custom Component
Understand the state, interface, and events of a custom component.
BYO 7.22 Implementation Examples
Discover examples of component implementation using the 7.22 Unqork Designer Platform.
Host External Assets Using a CDN (Content Delivery Network) Setup
Learn about setting up Express roles and managing permissions.
BYO Best Practices
Learn about best practices for implementing and using BYO assets.