SDK Reference
useSdk()¶
From any component of a feature page, at any depth:
import React, { useEffect, useState } from 'react'; import { Button } from 'antd'; import { useSdk } from '@clarive/sdk'; const Greeting = ({ idProject }) => { const { api, _, user, can, url, navigate } = useSdk(); const [data, setData] = useState(null); useEffect(() => { let alive = true; api.get('greeting', { id_project: idProject }) .then(res => alive && setData(res)) .catch(err => alive && console.error(err.status, err.message)); return () => { alive = false; }; }, [idProject]); return ( <div> <p>{_('User: %1 (%2)', user.realname || user.username, user.username)}</p> <a href={url('/hello-data/detail', { idProject })}>{_('Detail')}</a> {can('action.hello_react.admin') && ( <Button onClick={() => navigate('/code/hello-admin', { idProject })}>Admin</Button> )} </div> ); };
useSdk() only works inside something the core painted for the feature: a page registered with
addRoute, or a component registered with addComponent and painted in a core screen. Anywhere
else it throws useSdk() must be used inside a feature page or component. Each feature gets its
own sdk: api points to its backend and _ to its translations.
useSdk and version can also be imported from @clarive/sdk.
The sdk object¶
| Member | Description |
|---|---|
version |
SDK version of the core, e.g. '1.0.0'. |
feature |
Feature data (id, name, entry, css, sdk, i18n). |
addRoute(path, render, { permission }) |
Register a page. See Building the Frontend. |
addSideMenuItem(sectionUrl, item, { contexts }) |
Add an entry to an existing side menu section. |
addTopMenuItem(item) |
Add an entry to the top menu. |
addSideMenuSection(section) |
Add an own side menu section. |
addComponent(name, Component) |
Offer a component to the core screens that accept one (see below). Its full name is <id>/<name>. React.lazy components are accepted. |
api.get(path, data) |
GET /feature/<id>/api/<path>, data as query string. |
api.post(path, data) |
POST, data as a form (it arrives in $c->req->params). |
api.postJSON(path, data, { timeout }) |
POST with a JSON body; timeout in ms. |
pluginApi(id) |
The same against a JS plugin: pluginApi('foo').post('getItems', data) sends POST /plugin/foo with path=getItems. |
_(msgid, ...args) |
Translation with %1..%n placeholders. |
language |
Session language ('es', 'en'...). |
user |
Frozen copy of the user: mid, username, realname, isRoot, language, dateFormat, timeFormat. |
can(permission) |
Does the user have this permission? Only for showing or hiding UI. |
navigate(path, query) |
Navigate to an application url. |
url(path, query) |
Url for an <a href>: url('/hello-data/detail', { id: 3 }) gives '#/hello-data/detail?id=3'. |
subscribe(events, callback, { filter }) |
Listen to server events (see below). Returns the function that stops listening. |
openTopic(mid) |
Open a topic in its tab. |
download(url, params) |
Download a file from a GET url of the application with params. The browser saves it and the page stays. |
What is not there, on purpose: the core DataStore, mobx, the router, Cla.* or the core
request library. The sdk is the contract: plain, stable values. When a feature needs something
more, a new method is added to the sdk (a minor version), instead of opening access to the core internals.
user is taken at startup and does not change while the application is open. Impersonating another
user reloads the page, and then user is the impersonated user.
Backend calls¶
- Paths are relative:
'greeting','items/list'. No leading/, no.or.., and no query string in the path (parameters go indata). An invalid path rejects the promise with aTypeError. - They return native promises. Underneath they use the core transport, so the session, re-login after a 401, the impersonated user and the loading indicator work as in the core.
- An error is an
Errorwith{ status, body, url }.messageis themsg(ormessage) of the Clarive error JSON;bodyis the parsed JSON, or the text if it was not JSON.
api.post('save', { name }) .then(res => ...) .catch(err => { if (err.status === 403) ... // no permission console.error(err.message, err.body); });
Server events¶
subscribe listens to the same event stream the core uses (event.topic.modify,
event.revision.update, the events fired by your backend...):
const { subscribe } = useSdk(); useEffect( () => subscribe('event.revision', (eventKey, events) => refresh(), { filter: { mid: revision.mid } }), [revision.mid] );
eventsis an event key or a list of them. A key also matches its sub keys:'event.revision'getsevent.revision.update.filterkeeps only the events whose data match, e.g.{ mid: topicMid }(a value or a list of values, compared with===).callback(eventKey, events)gets the matching events. An exception in the callback is logged in the console and does not stop the subscription.- It returns the function that stops listening. Return it from
useEffectso it is called when the component unmounts.
Components in core screens¶
Besides its own pages, a feature can offer components that the core paints inside its screens.
The core never names a feature: the component name comes from backend data. Today, from the
repository CI: its viewer_components method (see Backend).
// src/index.jsx const RevisionViewer = React.lazy(() => import('./RevisionViewer.jsx')); export default function register(sdk) { sdk.addComponent('RevisionViewer', RevisionViewer); // -> 'hello-react/RevisionViewer' }
Places that accept components (viewer_components key, and the props the component gets):
| Key | Where | Props |
|---|---|---|
revision |
Body of a revision in a topic. The header (name, repository, remove) is still painted by the core. | revision (revision data), topic { mid, title }, canEdit, isPrintView, refresh() (reloads the topic) |
select |
Selector used to add revisions from a topic. | repository { mid, name, icon, collection, contentUrl }, idProject, topic { mid, title }, field { id, name }, onAddRevision(), onClickTopic({ mid }), onBack() |
browse |
Repository page (Code, Repositories), route /code/repositories/component/<name>. |
repository, idProject, onBack() |
The props are plain values and callbacks, never core stores. Inside the component, useSdk()
gives the feature's sdk (its api, _, subscribe...).
// src/RevisionViewer.jsx import React from 'react'; import { Button, Tag } from 'antd'; import { useSdk } from '@clarive/sdk'; export default function RevisionViewer({ revision, topic, canEdit, refresh }) { const { _, openTopic } = useSdk(); return ( <div> <p>{_('Revision %1 of topic %2', revision.name, topic.title)}</p> <Tag>{canEdit ? _('can edit') : _('read only')}</Tag> <Button onClick={() => openTopic(topic.mid)}>{_('Open in a tab')}</Button> <Button onClick={refresh}>{_('Reload topic')}</Button> </div> ); }
If the component is not available (the feature is not installed, failed, timed out or does not add
it), the core uses its usual view (the view for the repository viewer_type, the generic
selector or a notice) and logs [features] component <name> not available (...) in the console.
While the feature is still loading, a skeleton is shown. If the component throws while rendering, only
that slot shows "The feature X failed to render"; the rest of the screen keeps working.