Skip to content

Example Web Component

create todos component

In this web component the user can create todos for multiple companies at once.

The code is broken down to the following steps:

  • Imports interfaces from the frontend/src/components/servercommand.service.interface.ts
  • Retrieves the ServerCommandsService from the platform
  • Creates one ServerCommand object per company and adds parameters, using the current user to set the coworker relation.
  • Sends the request using the postCommands function
  • Checks whether the response is successful or not

Important

If a failure occurs when creating any of the todos objects in the backend, then everything will be rolled back. Compare this to using the limeobject API where each todo would require a separate POST request and the component would need much more code to do the rollback. The only viable option to do this is to write a custom endpoint. Using server commands and the builtin upsert-limeobject command provides an alternative to creating custom endpoints every time a client needs to save more than one limeobject in one single unit of work.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// frontend/src/components/create-todos/create-todos.tsx
import {
  LimeWebComponent,
  LimeWebComponentContext,
  LimeWebComponentPlatform,
  NotificationService,
  PlatformServiceName,
  Query,
  QueryService,
  QueryResponse,
  Operator,
} from "@limetech/lime-web-components-interfaces";
import { CurrentUser } from "@limetech/lime-web-components-decorators";
import { Component, h, Prop, State } from "@stencil/core";
import { ListItem } from "@limetech/lime-elements";
import {
  ServerCommand,
  ServerCommandService,
  ServerCommandServiceName,
} from "../servercommand.service.interface";

@Component({
  tag: "create-todos",
  shadow: true,
  styleUrl: "create-todos.scss",
})
export class CreateTodos implements LimeWebComponent {
  /**
   * @inherit
   */
  @Prop()
  public platform: LimeWebComponentPlatform;

  /**
   * @inherit
   */
  @Prop()
  public context: LimeWebComponentContext;

  @State()
  private companies: any[] = [];

  @State()
  private note: string;

  @State()
  private subject: string;

  @State()
  private saving: boolean;

  @CurrentUser()
  private currentUser: any;

  get queryService(): QueryService {
    return this.platform.get(PlatformServiceName.Query);
  }

  get notificationService(): NotificationService {
    return this.platform.get(PlatformServiceName.Notification);
  }

  get serverCommandService(): ServerCommandService {
    return this.platform.get(ServerCommandServiceName);
  }

  public render() {
    return [
      <header>Create todos</header>,
      <section>
        <limel-picker
          label="Companies"
          value={this.companies}
          multiple={true}
          searcher={this.searchCompanies}
          onChange={this.companiesChangedHandler}
          disabled={this.saving}
          required={true}
        />
        <limel-input-field
          type="text"
          label="Subject"
          value={this.subject}
          onChange={this.subjectChangedHandler}
          disabled={this.saving}
        />
        <limel-input-field
          type="textarea"
          label="Note"
          value={this.note}
          onChange={this.noteChangedHandler}
          disabled={this.saving}
        />
        <limel-button
          label="Create"
          onClick={this.createTodosHandler}
          loading={this.saving}
          disabled={this.saving}
        />
      </section>,
    ];
  }

  private searchCompanies = async (text: string): Promise<ListItem[]> => {
    if (!text) {
      return [];
    }

    const query: Query = {
      limetype: "company",
      limit: 10,
      orderBy: [{ name: "ASC" }],
      filter: {
        op: Operator.LIKE,
        key: "name",
        exp: text,
      },
      responseFormat: {
        object: {
          _id: null,
          name: null,
        },
      },
    };

    const result: QueryResponse = await this.queryService.execute(query);

    // eslint-disable-next-line no-underscore-dangle
    return result.objects.map((r) => ({ text: r.name, value: r._id }));
  };

  private noteChangedHandler = (event: CustomEvent<string>) => {
    this.note = event.detail;
  };

  private subjectChangedHandler = (event: CustomEvent<string>) => {
    this.subject = event.detail;
  };

  private companiesChangedHandler = (
    event: CustomEvent<Array<ListItem<number>>>
  ) => {
    this.companies = [...event.detail];
  };

  private companyToCommand = (companyItem: ListItem<number>): ServerCommand => {
    return {
      name: "upsert-limeobject",
      parameters: {
        limetype: "todo",
        subject: this.subject,
        note: this.note,
        company: companyItem.value,
        coworker: this.currentUser.coworker.id,
      },
    };
  };

  private createTodosHandler = async () => {
    const commands = this.companies.map(this.companyToCommand);

    const response = await this.serverCommandService.postCommands(commands);
    if (response.successful) {
      this.notificationService.notify(`${commands.length} todos created`);
      this.note = "";
      this.subject = "";
      this.companies = [];
    } else {
      const errors = response.commands.map((r) => r.error.message);
      this.notificationService.alert(
        "Error saving data",
        `Failed to create the todos: ${errors.join(", ")}`
      );
    }
  };
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// frontend/src/components/create-todos/create-todos.spec.tsx
import { newSpecPage, SpecPage } from "@stencil/core/testing";
import { CreateTodos } from "./create-todos";
import {
  createComponent,
  destroyComponent,
} from "@limetech/lime-web-components-testing";
import {
  ServerCommandService,
  ServerCommandServiceName,
} from "../servercommand.service.interface";

describe("create-todos", () => {
  let page: SpecPage;
  let serverCommandService: Partial<ServerCommandService>;
  let component: HTMLCreateTodos;

  beforeEach(async () => {
    page = await newSpecPage({
      components: [CreateTodos],
    });
    component = createComponent(CreateTodos, page, {
      tag: "create-todos",
      state: {
        currentUser: {
          coworker: { id: 1234 },
        },
        companies: [
          { text: "Company A", value: 1001 },
          { text: "Company B", value: 1002 },
        ],
        note: "Description of the todo",
        subject: "Test todo",
      },
    });
    serverCommandService = { postCommands: jest.fn() };
    component.platform.register(ServerCommandServiceName, serverCommandService);
    await page.waitForChanges();
  });

  it("renders", () => {
    expect(page.root).toEqualLightHtml("<create-todos></create-todos>");
  });

  it("calls postCommands on click", () => {
    const button = component.shadowRoot.querySelector("limel-button");
    expect(button).toBeDefined();
    expect(serverCommandService.postCommands).toHaveBeenCalledTimes(0);
    button.click();
    expect(serverCommandService.postCommands).toHaveBeenCalledTimes(1);
    expect(serverCommandService.postCommands).toHaveBeenCalledWith([
      {
        name: "upsert-limeobject",
        parameters: {
          company: 1001,
          coworker: 1234,
          limetype: "todo",
          note: "Description of the todo",
          subject: "Test todo",
        },
      },
      {
        name: "upsert-limeobject",
        parameters: {
          company: 1002,
          coworker: 1234,
          limetype: "todo",
          note: "Description of the todo",
          subject: "Test todo",
        },
      },
    ]);
  });

  afterEach(() => {
    destroyComponent(component);
  });
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// frontend/src/components/create-todos/create-todos.scss
header {
    background-color: rgb(var(--contrast-300));
    border-radius: 0.75rem 0.75rem 0rem 0rem;
    padding: 1rem;
}

section {
    background-color: rgb(var(--contrast-100));
    display: grid;
    row-gap: 0.5rem;
    border-radius: 0rem 0rem 0.75rem 0.75rem;
    padding: 1rem;
}

limel-input-field[type="textarea"] {
    min-height: 8rem;
}

The following JSON payloads are examples from API requests when using the component.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
  "commands": [
    {
      "name": "upsert-limeobject",
      "parameters": {
        "limetype": "todo",
        "subject": "Test todo",
        "note": "Description for the todo!",
        "company": 1007,
        "coworker": 1010
      }
    },
    {
      "name": "upsert-limeobject",
      "parameters": {
        "limetype": "todo",
        "subject": "Test todo",
        "note": "Description for the todo!",
        "company": 1008,
        "coworker": 1010
      }
    },
    {
      "name": "upsert-limeobject",
      "parameters": {
        "limetype": "todo",
        "subject": "Test todo",
        "note": "Description for the todo!",
        "company": 1009,
        "coworker": 1010
      }
    }
  ]
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
  "successful": true,
  "id": "",
  "commands": [
    {
      "command": {
        "name": "upsert-limeobject",
        "parameters": {
          "limetype": "todo",
          "subject": "Test todo",
          "note": "Description for the todo!",
          "company": 1007,
          "coworker": 1010
        }
      },
      "successful": true,
      "data": { "limetype": "todo", "id": 1052 }
    },
    {
      "command": {
        "name": "upsert-limeobject",
        "parameters": {
          "limetype": "todo",
          "subject": "Test todo",
          "note": "Description for the todo!",
          "company": 1008,
          "coworker": 1010
        }
      },
      "successful": true,
      "data": { "limetype": "todo", "id": 1053 }
    },
    {
      "command": {
        "name": "upsert-limeobject",
        "parameters": {
          "limetype": "todo",
          "subject": "Test todo",
          "note": "Description for the todo!",
          "company": 1009,
          "coworker": 1010
        }
      },
      "successful": true,
      "data": { "limetype": "todo", "id": 1054 }
    }
  ]
}
Back to top