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:

  • Row of the cell that was clicked.

  • Column of the cell that was clicked.

  • Value of the cell that was click.

Manifest JSON Example

Copy
{
  "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

Copy
/// 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

Copy
// << 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

Copy
{
  "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

Copy
class EventPayload {
  data = 'defaultValue'
}

class Event {
  name = 'eventsOnly'
  payload = new EventPayload()
}

// Export for the event
export const eventsOnly = {
  model: async () => Event,
}

Implementation Example

Copy
// << 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

Copy
{
  "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

Copy
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

Copy
// << 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,
}