Skip to content

Lime Web Components

Lime Web Components are UI components you can embed in the Web Client. Components are built using StencilJS, which uses TypeScript, JSX and reactive data-binding. A basic understanding of these concepts is required in order to follow along this documentation.

Creating a new web component

To create a new web component you need a package created with lime-project. Once a package has been created, a new web component can be created by executing the following from the package root directory:

lime-project generate web-component <my-component>

where <my-component> is the name of the component.

Starting the dev environment

Once a web component has been created, go to the <package>/frontend directory inside the package and run:

npm install

Then restart the Lime CRM Webserver and run:

npm start

This command will create a development build of your web-component and watch for any changes in the frontend folder.

Commands

Commands are a great way to help capture user intent. They let us encapsulate all information needed to perform an action into an object, and later execute that action without knowing anything about the action beeing requested or the receiver of the request.

To help with this, the platform exposes a service called commandbus.

Command bus

The command bus uses the command design pattern and combines it with a service layer. Its job is to take a command object (which describes what the user wants to do) and match it to a handler (which executes it). This can help you structure your code in a neat way.

We can for instance use it in a menu. The menu knows nothing about what actions to perform when the user selects an item in the menu. It only takes command objects as input, and sends the commands to the command bus for execution when an item is selected.

Example

First we declare and register the command and its handler on the command bus

@Command({ id: "my-special-command" })
class MySpecialCommand {
  public id: number;
  public limetype: Limetype;
}

class MySpecialHandler implements CommandHandler {
  public handle(command: MySpecialCommand) {
    // do your magic stuff here
  }
}

const handler = new MySpecialHandler();
const commandBus = platform.get(PlatformServiceName.CommandBus);
commandbus.register(MySpecialCommand, handler);

Then, if we want to invoke the command, we simply create an instance of it and send it to the commandbus

const command = new MySpecialCommand();
command.id = 1001;
command.limetype = { ... };

commandbus.handle(command);

Context

The context property on LimeWebComponent contains the context the component belongs to. This context depends on where the component lives. For most cases, it will just contain a limetype and an id, but in some cases there might not be any context information available. It's the responsibility of the webclient to pass a valid context to the web component.

Lime Elements

Lime Elements are available for Lime Web Components and should be used to get the same look and feel as the rest of the webclient.

For more information regarding Lime Elements, please refer to the Lime Elements documentation.

Lime Web Component

A root component is a component that lives directly within the webclient and is not nested within any other component. All root components have to implement the LimeWebComponent interface in order to work properly and the webclient will pass down the required properties when the web component is created.

Components that are not root components are not required to implement the LimeWebComponent interface. However, if they make use of the context or any of the platform services, these have to be passed down explicity by the consumer. Components that make use of the state decorators are required to fully implement LimeWebComponent.

Platform services

The platform property on LimeWebComponent exposes all services that the Lime CRM webclient provides for plugins to make use of.

List of services

Name Description
state Access state information from the webclient
translate Translate strings to other languages
http Make requests to custom endpoints
route Redirect to other parts of the webclient
notifications Show simple dialogs and notification messages
query Execute queries using the query objects API
commandbus Register and execute commands
dialog Open custom modal dialogs
eventDispatcher Handle application level events

Slots

There are a number of slots in the Lime CRM webclient where web components can be added. All of them are configured from the administrator page inside the webclient. Slots can either be slots where components are rendered, or inside menus where actions can be added.

Hiding components

Sometimes you want to show or hide a web component in a slot depending on a specific condition. Maybe the marketing team should see one set of components and the support team should see another set of components. In order to avoid gaps in our slots, you must ensure that the correct css rules are set when a component should be hidden. You do that by setting the css style display:none on the component's :host element. See example code below:

:host.hidden {
    display: none;
}
import { Component, h, Host, … } from '@stencil/core';

…

    public render() {
        return (
            <Host class={{ hidden: this.hidden }}>
                …
            </Host>
        );
    };

…

Deprecated slots

In previous versions of Lime CRM webclient, web components could be directly injected into the client by an addon. This way of configuring web components is now deprecated and will be removed in the future. To specify that a web component should appear in one of these deprecated slots, edit the file lwc.config.json in the web component's package.

The format should be:

[
  {
    "name": "<component-name>",
    "slot": "<slot-name>"
  }
]

where <component-name> is the tag name of the web component specified in the @Component decorator, and <slot-name> is the name of the slot.

Only root components should be listed in lwc.config.json, i.e. components that are created and injected by the webclient. Other components, e.g. components that are nested or reused between many components, do not belong in any slot and should not be listed.

List of slots

The following slots were previously defined for Lime CRM webclient. They should no longer be used.

Slot Location
dashboard On the client's dashboard deprecated
object.card.head.panels In the header of the limeobject card deprecated

Migration strategy

When migrating from the deprecated slots, use the following strategies.

  • dashboard - use the new "Start Page designer" in the administrator interface
  • object.card.head.panels - This slot has been split into two. Action-like components consisting mainly of a button opening a dialog etc, should be moved to the action menu for the card. Graphical components that displays information should be moved to the "Web components" configuration section for the card view. Both are accessed from the administrator interface of the webclient.

State

The state in the webclient stores all information related to data. It can be limetypes, loaded limeobjects, the current user etc.

Connecting to the state

To use the state, a property in a web component must be connected to it. This is done by decorating the property with a state decorator. The example below shows how to connect to the state to get the current logged in user.

export class MyComponent implements LimeWebComponent {
  @CurrentUser()
  @State()
  private user: object;

  public render() {
    return <p>Hello {this.user.fullname}!</p>;
  }
}

Filtering the result with arguments

It's also possible to send arguments into the decorator in order to make it more specific. The following example will load all loaded limeobjects into the connected property.

export class MyComponent implements LimeWebComponent {
  @Limeobjects()
  @State()
  private limeobjects: object;
}

If we only want objects of a specific limetype, we can specify that limetype when decorating the property. The following will give us all currenyly loaded persons

export class MyComponent implements LimeWebComponent {
  @Limeobjects({
    limetype: "person",
  })
  @State()
  private persons = [];
}

Using a mapping function

While this is useful, it can be made even more useful if combined with a mapping function. Let's say we have a web component that is displayed on the company card, and we want a list of all persons connected to that company. First, we can define a filtering function that filters out our persons like this:

function currentPersons(persons = []) {
  return persons.filter((person) => person.company === this.context.id);
}

This function takes a list of persons, and returns a new list which only contains persons of the company in the current context.

We can apply this filtering function by specifying it when decorating our property.

export class MyComponent implements LimeWebComponent {
  @Limeobjects({
    limetype: "person",
    map: [currentPersons],
  })
  @State()
  private persons = [];
}

This will map the original result (a list of all persons) to a new result (a filtered list of persons) that is stored in the connected property.

There are also two specific decorators that can be used to get the limetype and limeobject of the current context. Under the hood, these are just implemented with the map functionality used above.

import {
  SelectCurrentLimeObject,
  SelectCurrentLimeType,
  LimeObject,
  LimeType,
} from "@limetech/lime-web-components";

export class MyComponent implements LimeWebComponent {
  @SelectCurrentLimeObject()
  @State()
  private limeobject: LimeObject;

  @SelectCurrentLimeType()
  @State()
  private limetype: LimeType;
}

Available state decorators

A list of available state decorators is available in the API Documentation. All decorators are prefixed with Select, e.g. SelectConfig

Running Web component tests

To run your web components tests run

npm run test

in your frontend folder

Linting Web component

To lint your web components run

npm run lint

To try to auto fix any linting issues, simply run

npm run lint:fix