Skip to main content

Drumee Frontend SDK — General Principles

Source: https://drumee.github.io/api-reference/frontend-sdk/


1. Core Philosophy

Drumee uses a Skeleton-based UI model — all UI is expressed as component trees built with the Skeletons API. Never write raw HTML. A widget feeds a skeleton into itself; the framework handles DOM creation.

The entire application is built on Backbone.js/Marionette. Every widget is a view that can host children (parts), receive events, and call backend services.


2. Widget Lifecycle

onDomRefresh()

Fires once after the widget's DOM element is mounted but before children render. Use it as the initialization entry point.

onDomRefresh() {
this.feed(require("./skeleton").default(this));
}

For async data loading, await before feeding:

async onDomRefresh() {
await this.loadEnv();
this.feed(require("./skeleton").default(this));
}

3. Rendering Content into Parts

feed(c) — replace all children

Primary rendering method. Completely replaces current children with new content.

part.feed(Skeletons.Note("Hello"));
part.feed([SkeletonA, SkeletonB]);
part.feed((ui) => Skeletons.Box.Y({ kids: [...] }));
part.feed(null); // no-op

append(c, index?) — add at end or position

Inserts without replacing existing children.

part.append(Skeletons.Note("New item"));
part.append(Skeletons.Note("At top"), 0);
this.ensurePart("my-list").then((list) => list.append(item));

prepend(c) — add at beginning

Equivalent to append(c, 0).

list.prepend(Skeletons.Note("Pinned item"));

Rule: Use ensurePart("name") before calling append/prepend on a named part to ensure it exists.


4. Event System

Two independent event channels handle all widget communication.

4.1 Interaction events: service + uiHandleronUiEvent

service names the action; uiHandler (always an array) routes it to the widget.

// Skeleton
Skeletons.Button.Svg({ ico: "save", service: "save-doc", uiHandler: [ui] });

// Widget
async onUiEvent(cmd, args = {}) {
const service = args.service || cmd.get(_a.service);
switch (service) {
case "save-doc":
const data = cmd.mget("my_param"); // extra skeleton props
await this.postService(SERVICE.my_module.save, { data });
break;
}
}

Rules:

  • service without uiHandler → event never fires
  • Multiple handlers allowed: uiHandler: [ui, parentUi]
  • Extra skeleton props are accessible via cmd.mget("prop_name")

4.2 Lifecycle events: sys_pn + partHandleronPartReady

sys_pn names a part; partHandler routes the mount callback.

// Skeleton
Skeletons.Box.Y({ sys_pn: "content", partHandler: ui });

// Widget
onPartReady(child, pn) {
switch (pn) {
case "content":
child.feed(require("./skeleton/my-page").default(this));
break;
case "video":
child.el.volume = 0.5; // direct DOM access
break;
default:
super.onPartReady(child, pn); // always delegate unhandled cases
}
}

Rules:

  • sys_pn without partHandleronPartReady never fires (but ensurePart still works)
  • Always call super.onPartReady(child, pn) in the default case
  • Dynamic names work: `item-action:${item.id}` for per-item access

5. Backend Communication

fetchService(service, payload) — GET

Read data from the server. Automatically injects socket_id, device_id, and auth headers. Strips UI-only fields before sending.

const data = await this.fetchService(SERVICE.my_module.get_data, { uid });
// or object form:
const data = await this.fetchService({ service: SERVICE.my_module.get_data, uid });

postService(service, payload) — POST

Mutate data on the server. Same automatic injections as fetchService.

await this.postService(SERVICE.my_module.save, { uid, ...payload });

Best practices for both:

  • Guard with required values before calling
  • Wrap in try/catch and re-throw errors
  • Cache fetchService results to avoid redundant requests
  • Use optimistic UI updates for postService calls

6. Media File System (MFS)

MFS widgets extend DrumeeMFS (not LetcBox). They represent filesystem nodes: files, folders, hubs.

Initialization

// Called after model data is available
this.model.set(data);
this.initData(); // sets isMfs, isHub, isFolder, isHubOrFolder flags; computes sizes/dates
this.initURL();

// unselect() must be implemented by subclasses
unselect(mode) {
this.setState(0);
this.el.removeAttribute("data-selected");
}

Node identity

MethodReturnsDescription
getCurrentNid()numberContainer nid: folder→own id, hub→0, file→parent id
getHostName()stringVirtual hostname (e.g. files.drumee.com)
getHostId()stringHub ID owning the node
isRegularFile()booleanNot hub/folder and not symlink
isSymLink()booleanSymlink flag AND not a hub

URL generation

MethodAsyncUse case
url(format?)NoDisplay URL (thumbnail or original)
directUrl()NoRaw file href; null for non-regular files
srcUrl()NoFull URL for src= attributes
viewerLink(format?, e?)YesShareable desk viewer URL
pluginUrl()NoPlugin endpoint URL

File download

this.download({}); // auto-detects file vs hub/folder
this.download_zip({ zipid, zipname, progress }); // pre-built zip
await this.fetchFile({ url, download: "file.pdf", progress: widget }); // stream
this.abortDownload(); // cancel in-flight

Metadata

// metadata() — auto-called by initData(); flattens md5Hash, dataType onto model
metadata() {
const md = super.metadata();
const { customField } = md;
this.mset({ customField });
return md;
}

this.fullname(); // → "filename.ext"
this.copyPropertiesFrom(srcNode); // clone filesystem properties

Permissions

Based on a bitmask in the node's privilege field.

if (this.canUpload()) { /* show upload button */ }
if (this.canRemove()) { /* show delete option */ }
if (this.canShare()) { /* show share button */ }
if (this.canManageAccess()) { /* show access panel */ }
if (this.isMediaOwner()) { /* owner-only actions */ }
MethodPermission bitGuard
isMediaOwner()owner
canAdmin()admin
canOrganize()modifyBlocks non-hub symlinks
canUpload()writeBlocks non-hub symlinks
canShare()downloadArea must be dmz or share
canManageAccess()adminArea must be private
canRemove()modifyBlocks locked nodes
canDownload()download

7. Skeleton Component Reference

Layout — Skeletons.Box

VariantDirectionUse case
Box.YcolumnVertical stacks
Box.XrowToolbars, horizontal rows
Box.GgridGrid layouts
Box.ZabsoluteOverlays

Never add display: flex or flex-direction in SCSS for Box elements — the framework sets these.

Key props: kids, kidsOpt (shared options for all children), populate (generate from data array), sys_pn, uiHandler, partHandler.

Skeletons.Box.X({
className: `${pfx}__toolbar`,
kids: [
Skeletons.Button.Svg({ ico: "save", service: "save", uiHandler: [ui] }),
Skeletons.Button.Svg({ ico: "close", service: "close", uiHandler: [ui] }),
],
});

Buttons — Skeletons.Button

VariantRendersUse case
Button.SvgIcon onlyToolbars, compact actions
Button.LabelIcon + textMenus, nav, checkboxes
Button.IconIcon (custom size)Non-standard dimensions

All require ico (kebab-case SVG name). Common props: service, uiHandler, state, radio, tooltips, haptic, dataset.

Skeletons.Button.Svg({ ico: "arrow-right", service: "next", uiHandler: [ui] });
Skeletons.Button.Label({ ico: "user", label: LOCALE.PROFILE, href: "#/profile" });
Skeletons.Button.Icon({ ico: "logo" }, { width: 48, height: 48, padding: 8 });

Text — Skeletons.Note

Lightest primitive. For inline text, labels, clickable notes.

Skeletons.Note("Hello World");
Skeletons.Note("Loading...", "spinner-class"); // shorthand: (content, className)
Skeletons.Note({ className: `${pfx}__label`, content: LOCALE.MY_TEXT, service: "click", uiHandler: [ui] });

Inputs — Skeletons.Entry / Skeletons.EntryBox

Entry — basic single-line input. EntryBox — extends Entry with validators, inline error display, prefix icon, and password toggle.

// Entry
Skeletons.Entry({
placeholder: LOCALE.EMAIL, require: "email",
mode: "commit", service: "submit", uiHandler: [ui],
});

// EntryBox — for forms with validation feedback
Skeletons.EntryBox({
placeholder: LOCALE.EMAIL, require: "email",
shower: 0, validators: myValidators, showError: true,
formItem: "email", interactive: 1, preselect: 1,
mode: "commit", service: "submit",
uiHandler: [ui], errorHandler: ui,
});

Lists — Skeletons.List

VariantUse case
List.SmartAPI-driven, with spinner + empty state
List.ScrollStatic scrollable container
List.TableTabular data
Skeletons.List.Smart({
className: `${pfx}__list`, sys_pn: "items",
api: ui.getItems.bind(ui),
itemsOpt: { kind: "my_item_widget", uiHandler: [ui] },
spinner: true, spinnerWait: 500,
evArgs: Skeletons.Note(LOCALE.NO_ITEMS, "no-content"),
vendorOpt: Preset.List.Orange_e,
});

Other components

ComponentPurpose
Skeletons.TextareaMulti-line input
Skeletons.MessengerChat input (auto-clear, auto-focus, upload)
Skeletons.RichTextRich text editor/viewer
Skeletons.AvatarUser avatar (image or color-generated)
Skeletons.UserProfileDrumee user profile with online indicator
Skeletons.ProfileGeneric entity profile
Skeletons.Image.SmartPhotos/covers with low/high res
Skeletons.Image.SvgNon-interactive SVG icon
Skeletons.ProgressUpload/download progress bar
Skeletons.FileSelectorNative file picker
Skeletons.Wrapper.X/YDialog/overlay container
Skeletons.ElementGeneric DOM element (img, video, source)

8. Socket / HTTP Utilities

xhRequest(url, opt?)

Low-level GET via XHR. Service paths (e.g., "media.get_node_attr") are auto-prefixed with the bootstrap svc URL.

xhRequest(fileUrl, { responseType: "text" }).then((text) => this.load(text));
xhRequest("my_module.get_data", { uid }).then((data) => { this._data = data; });

uploadFile(file, params)

Binary upload via XHR. Returns the XHR instance (call .abort() to cancel). Metadata is encoded in the x-param-xia-data header.

const xhr = this.uploadFile(file, { hub_id: this.getHostId(), nid: this.getCurrentNid() });
// Hooks auto-bound on widget: onUploadProgress, onUploadError, onLoad, onAbort, onUploadEnd

Drag-and-drop uploads

sendTo(targetWidget, dropEvent, params, token); // public entry point
// → internally wraps files in pseudo_media → calls target.insertMedia()

9. CSS Naming Convention

Widget class name → fig object:

class __chat_hub → fig.family = "chat-hub", fig.group = "chat", fig.name = "hub"

Use fig.family as the BEM root in skeletons:

const pfx = ui.fig.family; // e.g. "chat-hub"

Skeletons.Box.Y({
className: `${pfx}__container`,
kids: [
Skeletons.Note({ className: `${pfx}__title`, content: ui.mget(_a.name) }),
],
});
LevelSuffix convention
Outer box (contains only boxes)__container, __wrapper
Middle box (mixed children)__section, __group
Inner box (contains only leaves)__content, __inner, __row, __line

10. Key Rules at a Glance

RuleReason
Never write HTML — use SkeletonsFramework manages DOM
Never hardcode text — use LOCALE.*i18n
uiHandler always as array: [ui]Supports multiple handlers
Always super.onPartReady() in defaultDelegates to parent class
Extend LetcBox for standard widgetsExtend DrumeeMFS only for filesystem nodes
Never add flex/flex-direction in SCSS for Box elementsFramework sets these
Use ensurePart() before append/prependGuarantees part exists
Use RADIO_BROADCAST for app-wide eventsStandard event bus

11. Example — Scaffolding a New Widget

1. Create and initialize the project

mkdir my-drumee-widget
npm init my-drumee-widget
cd my-drumee-widget

2. Install the dev tools

npm i @drumee/ui-dev-tools

3. Scaffold the widget

npm run add-widget -- --fig=widget.example --dest src/widget/example

--fig sets the widget's fig identity (group=widget, family=widget-example), which becomes the BEM root for all CSS class names. --dest is the output directory where the scaffold files are generated.