@swimlane/swimlane-element

SwimlaneElement is a simple base class for creating Swimlane platform widgets using lit-html templating.

Usage no npm install needed!

<script type="module">
  import swimlaneSwimlaneElement from 'https://cdn.skypack.dev/@swimlane/swimlane-element';
</script>

README

SwimlaneElement

SwimlaneElement is a simple base class for creating Swimlane platform widgets using lit-html templating.

Building

Run npm run build to build SwimlaneElement. The build artifacts will be stored in the build/ directory.

Development server

Run npm run start to serve the demo.

Release

  • Checkout master (git checkout master)
  • Pull master (git pull)
  • Refresh node modules (npm ci)
  • Examine log to determine next version (X.Y.Z)
  • Run git checkout -b release/X.Y.Z
  • Update version in package.json.
  • Update changelog in CHANGELOG.md
  • Run git commit -am "(release): X.Y.Z"
  • Run git tag X.Y.Z
  • Run git push origin HEAD --tags
  • Run npm run publish
  • Submit PR

Overview

LitElement (https://lit-element.polymer-project.org/) is a webcomponent base class that uses lit-html (https://lit-html.polymer-project.org/) templating library. LitElement (https://lit-element.polymer-project.org/) uses lit-html to render into the element's Shadow DOM and adds API to react to changes in properties.

SwimlaneElement extends from LitElement to add API for interacting with records or reports in the Swimlane platform.

Getting Started

/**
 * Import `SwimlaneElement` class.
 * The `css` and `html` methods are also imported from `swimlane-element`.
 */
import { SwimlaneElement, css, html } from '@swimlane/swimlane-element@1';

/**
 * The `recordFrameTemplate` is a template function see templates below.
 */
import { recordFrameTemplate } from '@swimlane/swimlane-element@1/templates.js';

/**
 * A Swimlane widget is implemented as an anonymous class which extends the `SwimlaneElement` class.
 */
export default class extends SwimlaneElement {
  /**
   * The `styles()` getter is a static class getter, in other words, it is a property defined on the class constructor.
   * This style getter method does not have access to any properties stored in the objects.
   * This getter defines styles common to all instances of this widget.
   * Defining this in your widget is optional.
   */
  static get styles() {
    return [super.styles, css`
      .frame::after {
        content: "Record Output";
      }
    `];
  }

  /**
   * The `render` method is an instance method and, therefore, has access to properties  stored in the object (i.e. `record` and `report`, see properties below.
   * The render method is scheduled by `LitElement` Lifecycle events and must return a `lit-html` template.
   * Defining this method in your widget is required.
   */
  render() {
    return recordFrameTemplate(html`
      <pre>${JSON.stringify(this.record, undefined, 2)}</pre>
    `);
  }
}

Properties

LitElement efficiently manages declared properties and attributes. By default SwimlaneElement declares record or report properties as well as the 'contextData' property. Documentation on how LitElement handles properties and attributes can be found here.

  • this.record - Available to record widgets, an object containing key-value pairs for each field on the record.
  • this.report - Available to report widgets, an object containing data, rawData, and query.
  • this.contextData - Available to both record and report widgets, an object containing data such as application, currentUser, origin, and token.

Templates

To define a template for a SwimlaneElement component, write a render function that returns a lit-html template. More details on writing render methods can be found in the documentation for LitElement and lit-html. The swimlane-element library includes predefined templates designed for the Swimlane platform. These templates can be imported from @swimlane/swimlane-element@1/templates.js.

  • recordFrameTemplate - A base template for rendering a frame on the record page. This function takes as arguments a template to render inside the frame and an optional rows height value. Without a defined rows value the frame will auto size to the content.
  • reportFrameTemplate - A base template for rendering a frame on the dashboards. This function takes as arguments a template to render inside the frame and an optional rows height value. Without a defined rows value the frame will auto size to the content.

Styles

As with LitElement there are "three main places in which you can define styles for your host element" ref. The two most common methods for SwimlaneElements will be to Define styles in a static styles property or Define styles in a style element.

Events

Documentation on how to handle events in LitElement can be found here. SwimlaneElement adds the following methods for emitting Swimlane widget events:

  • this.updateRecordValue(key, value) : Available to record widgets. Updates record data by field key.
  • this.addComment(key, value) : Available to record widgets. Adds a new comment by field key.
  • this.triggerIntegration(taskId) : Available to record widgets. Triggers an integration.
  • this.triggerSave() : Available to record widgets. Triggers record save on the record page.

Lifecycle

LitElement watches properties and attributes for changes and asynchronously renders the template to the element's shadow DOM. Documentation of LitElement lifecycle methods and properties can be found here. In addition to the HtmlElement and LitElement lifecycle methods, SwimlaneElement adds the resizedCallback method.

  • resizedCallback - This method (called whenever the element is resized) but default simply calls requestUpdate.

Examples

Basic

// SwimlaneElement and html are the basic required imports
import { SwimlaneElement, html } from '@swimlane/swimlane-element@1';

// Create and export a class definition for your widget that extends the SwimlaneElement base class
export default class extends SwimlaneElement {
  // The render callback renders your element's template.
  // It should always return the same template given the same properties and should not perform
  // any side effects such as setting properties or manipulating the DOM.
  render() {
    // Return the template using the html template tag.
    return html`
      <div>Hello world!</div>
    `;
  }
}

Record data

import { SwimlaneElement, html } from '@swimlane/swimlane-element@1';

export default class extends SwimlaneElement {
  // The render callback is called each time the record data changes.
  // lit-html is optimized for handling frequent updates and updating the DOM efficiently
  render() {
    return html`
      <div>
        Current count: ${this.record['count']}
      </div>
    `;
  }
}

Handling events

import { SwimlaneElement, html } from '@swimlane/swimlane-element@1';

export default class extends SwimlaneElement {
  render() {
    return html`
      <div>
        Current count: [${this.record['count']}]
        <!-- Use @[eventname] syntax to register inline event handlers -->
        <button @click=${() => this.updateRecordValue('count', this.record['count'] + 1)}>+</button>
        <!-- You can also pass a function reference directly. -->
        <button @click=${this._onDecrement}>-</button>
      </div>
    `;
  }

  _onDecrement() {
    this.updateRecordValue('count', this.record['count'] - 1)
  }
}

Styles in a static styles property

import { SwimlaneElement, html } from '@swimlane/swimlane-element@1';

export default class extends SwimlaneElement {
  /**
   * Styles should be added as a static getter. They are evaluated once, and then added
   * in the element's shadow dom.
   */
  static get styles() {
    return css`
      :host {
        display: block;
      }
      .message {
        color: blue;
      }
    `;
  }

  render() {
    return html`
      <div class="message">
        Current count: ${this.record['count']}
      </div>
    `;
  }
}

Swimlane styles

import { SwimlaneElement, html } from '@swimlane/swimlane-element@1';
import { recordFrameTemplate } from '@swimlane/swimlane-element@1/templates.js';

export default class extends SwimlaneElement {
  /**
   * The styles getter may return a array.
   * Add `super.styles` to get swimlane default styles.
   * Overide the `.frame::after` style to set a label for the frame.
   */
  static get styles() {
    return [super.styles, css`
      :host {
        display: block;
      }
      .message {
        color: blue;
      }
      .frame::after {
        content: "Current Count";
      }
    `];
  }

  /**
   * Wrap the template in `recordFrameTemplate` function to add a frame.
   * The seoncd argument (optional) is the row count height.
   */
  render() {
    return recordFrameTemplate(html`
      <div class="message">
        ${this.record['count']}
      </div>
    `, 2);
  }
}

Styles in a style element.

import { SwimlaneElement, html } from '@swimlane/swimlane-element@1';

export default class extends SwimlaneElement {
  render() {
    const color = this.record['count'] > 10 ? 'red' : 'blue';
    return html`
      <-- styles defined here are dynamic, and will update when properties update.
      <style>
        .message {
          color: ${color};
        }
      </style>
      <div class="message">
        Current count: ${this.record['count']}
      </div>
    `;
  }
}

Full Demo

import { SwimlaneElement, css, html } from '@swimlane/swimlane-element@1';
import { recordFrameTemplate } from '@swimlane/swimlane-element@1/templates.js';

export default class extends SwimlaneElement {
  static get styles() {
    return [super.styles, css`
      :host {
        text-align: center;
      }
      .frame::after {
        content: "Current Count";
      }
      button {
        font-size: xx-large;
        margin-top: 10px;
        height: 60px;
        width: 60px;
        border-radius: 5px;
        background-color: #ffbb47;
        color: #07080b;
        bordeR: 0;
      }
      .message {
        font-size: xx-large;
      }
    `];
  }

  render() {
    const color = this.record['count'] > 10 ? '#FF4514' : '#1483FF';
    return recordFrameTemplate(html`
      <style>
        .message {
          color: ${color};
        }
      </style>
      <div>
        <div class="message">
          ${this.record['count']}
        </div>
        <button @click=${this._onIncrement}>+</button>
        <button @click=${this._onDecrement}>-</button>
      </div>
    `, 2);
  }

  _onIncrement() {
    this.updateRecordValue('count', this.record['count'] + 1)
  }

  _onDecrement() {
    this.updateRecordValue('count', this.record['count'] - 1)
  }
}

Credits

SwimlaneElement is a Swimlane open-source project; we believe in giving back to the open-source community by sharing some of the projects we build for our application. Swimlane is an automated cyber security operations and incident response platform that enables cyber security teams to leverage threat intelligence, speed up incident response and automate security operations.