Ember Patterns
Forms
Move your form to it’s own component e.g:
<KbForm::Container>
{{#if @model.schedule.isRunning}}
<KbSpinner @caption="Please wait..." />
{{else}}
{{#with @model.schedule.value as |schedule|}}
<WorkflowDeleteForm
@schedule={{schedule}}
@routeTransition={{this.routeTransition}}
/>
{{/with}}
{{/if}}
</KbForm::Container>
Using ember-changeset
and validations:
import Component from "@glimmer/component"
import lookupValidator from "ember-changeset-validations"
import ScheduleValidations from "../validations/schedule"
import { action } from "@ember/object"
import { Changeset } from "ember-changeset"
import { inject as service } from "@ember/service"
import { task } from "ember-concurrency"
import { tracked } from "@glimmer/tracking"
export default class WorkflowDeleteFormComponent extends Component {
@service kbMessages
@tracked changeset = null
constructor(owner, args) {
super(owner, args)
console.log(this.args.schedule)
this.changeset = new Changeset(
this.args.schedule,
lookupValidator(ScheduleValidations),
ScheduleValidations
)
}
@action
submitForm() {
this.saveTask.perform()
}
@task *saveTask() {
let process_id = this.changeset.get("id")
this.changeset.validate()
if (this.changeset.isValid) {
try {
yield this.changeset.save()
if (this.args.routeTransition) {
this.args.routeTransition(process_id)
} else {
console.log(
"WorkflowDeleteFormComponent ('components/workflow-delete-form.js') no 'routeTransition'"
)
}
} catch (e) {
this.kbMessages.addError("Cannot delete the workflow process", e)
}
}
}
}
To allow empty dates, use the following code:
if (this.changeset.isValid) {
try {
if (this.changeset.get("startDate") == "") {
this.changeset.set("startDate", null)
}
yield this.changeset.save()
In the form, highlight changeset
and server side errors:
<KbForm::Form::Field::ErrorMessageChangeset
@errorField={{this.changeset.error.deleted_comment}}
/>
<KbForm::Form::Field::ErrorMessageServer
@errorField={{@schedule.errors.deleted_comment}}
/>
Tip
To return server side errors, check out the _deleted_comment
code
from ScheduledWorkflowUserViewSet
(work/api.py
).
08/04/2021, Not sure if this works with serializer errors or our
custom_exception_handler
(api/models.py
).
Notifications
Use the KbMessages
service:
https://gitlab.com/kb/kb-base-ember-addons/-/blob/tailwind-2-update-ember/addon/services/kb-messages.js
import { inject as service } from "@ember/service"
@service kbMessages
try {
// do something
} catch (e) {
this.kbMessages.addError("Cannot delete the workflow process", e)
}
Page
import { inject as service } from "@ember/service"
@service kbPage
export default class WorkflowRoute extends Route {
beforeModel(transition) {
this.kbPage.setTitle("Workflow")
}
Pagination (JSON API)
Example pagination
(meta
) returned from a JSON API view:
{page: 5, pages: 191, count: 3817}
Pagination
Configure KbRestPagination
as the pagination_class
for the viewset:
from api.api_utils import SoftDeleteViewSet
class IssueViewSet(SoftDeleteViewSet):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
serializer_class = IssueSerializer
In your controller:
queryParams = ["page"]
@tracked page = 1
get recordsPerPage() {
/**
* Number of records per page (``perPage``).
*
* This is a fixed value in Django and Ember code.
*/
return 20
}
@action
setPage(page) {
this.page = page
}
In your route, refresh the page
and add perPage
/ totalPagesParam
parameters:
queryParams = {
page: {
refreshModel: true,
},
sort: {
refreshModel: true,
},
company_name: {
refreshModel: true,
},
};
contactTask = task(async (params) => {
let filter = {};
if (params.company_name) {
filter = { company_name: params.company_name };
}
try {
let contact = await this.store.query('contact', {
page: {
number: params.page,
},
sort: params.sort,
filter: filter,
});
return await contact;
} catch (e) {
this.kbMessages.addError('Cannot load contact', e);
}
});
Add the KbPagination
component in your template (just before the table):
<KbBase::Content>
<KbTable::Container>
{{#if @model.tickets.isRunning}}{{else}}
<KbPagination
@model={{@model.tickets.value}}
@page={{this.page}}
@recordsPerPage={{this.recordsPerPage}}
@setPage={{this.setPage}}
/>
{{/if}}
<KbTable::Table>
Tip
If the KbPagination
is inside a SlideOver
,
then add @isSlideOver={{true}}
.
Slide Over
{{#if this.slideOverVisible}}
<KbSlideOver>
<KbSlideOver::Toggle
@title="Add Contacts"
@toggleSlideOver={{this.toggleSlideOver}}
/>
// Content of Slide-over...
</KbSlideOver>
{{/if}}
Tables / Data
Checklist
Add
reload: true
to allfindRecord
andfindAll
methods For details, see Error HandlingAdd a
try
,catch
to allstore
methods. UsekbMessages.addError
in thecatch
(see example below).Test the
store
methods with an exception on the server (500 error).Check the user is notified if a
store
method returns no data.
Route:
import { inject as service } from "@ember/service"
@service kbMessages
model(params) {
return {
processes: this.processesTask.perform(params)
}
}
@task *processesTask(params) {
try {
let processes = yield this.store.query("process", params)
return yield processes
} catch (e) {
this.kbMessages.addError("Cannot load workflow processes", e)
}
}
The components for tables can be found here Table. To add a spinner and nothing found to your table:
{{#if @model.processes.isRunning}}
<KbTable::Body::Row>
<KbTable::Body::Cell>
<KbSpinner @caption="Please wait..." />
</KbTable::Body::Cell>
<KbTable::Body::Cell />
<KbTable::Body::Cell />
<KbTable::Body::Cell />
<KbTable::Body::Cell />
<KbTable::Body::Cell />
</KbTable::Body::Row>
{{else}}
<KbTable::Body>
{{#each @model.processes.value as |process|}}
<Workflow::TableRow @process={{process}} />
{{else}}
<KbTable::Body::Row>
<KbTable::Body::Cell>
{{#if this.workflow}}
No workflow processes found ...
{{else}}
Please select a workflow...
{{/if}}
</KbTable::Body::Cell>
<KbTable::Body::Cell />
<KbTable::Body::Cell />
<KbTable::Body::Cell />
<KbTable::Body::Cell />
<KbTable::Body::Cell />
</KbTable::Body::Row>
{{/each}}
</KbTable::Body>
{{/if}}
Tip
For pagination, see Pagination (JSON API).