Skip to main content

Person Cards

Hub GUI ships the pieces for showing people consistently: round avatars (photo, or initials when there is none), a binding that fills an avatar and a name label, and a floating person card that opens on hover over any avatar or mention — photo, name, role, team, company, e-mail, phone and a link to the full profile.

PieceImport (@io-oidis-hub-gui/Io/Oidis/Hub/Gui/…)Purpose
IPersonCardInterfaces/IPersonCard.jsData of one card. Empty fields are hidden.
PersonDirectoryUtils/PersonDirectory.jsSession cache + batched resolver, avatar sources and markup.
PersonHoverCardUtils/PersonHoverCard.jsThe hover card (Bootstrap popover) over every [data-person] element.
MemberBindingBindings/MemberBinding.jsFills an Image (+ optional name/e-mail Label) from a card.
CanvasHelper.CreateInitialsAvatar / InitialsOfUtils/CanvasHelper.jsInitials PNG from a display name.

The server decides what a card reveals​

CORE does not fetch people itself. The application registers a resolver that asks its own server for several references at once; the server returns only the fields the current viewer may see (a guest gets a limited card, an employee the contact details, an administrator a link into internal records). An empty field simply does not render.

import { IPersonCard } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/Interfaces/IPersonCard.js";
import { PersonDirectory } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/Utils/PersonDirectory.js";
import { PersonHoverCard } from "@io-oidis-hub-gui/Io/Oidis/Hub/Gui/Utils/PersonHoverCard.js";

// once, after the user context is loaded
PersonDirectory.setResolver(async ($refs : string[]) : Promise<IPersonCard[]> =>
await MyConnector.getClient().getPersonCards($refs));
PersonHoverCard.Install({linkText: "Otevřít kartu", guestText: "Host"});
  • A reference is any string the resolver understands — an e-mail, a user id, or an application scheme (Labs uses user:<id> and person:<id>). Each returned card carries the ref it answers; references missing in the result resolve to null.
  • References requested in the same tick go out in one resolver call, and every reference is resolved at most once per session. setResolver() and Clear() drop the cache (e.g. after a sign-in as another user).
  • Peek(ref) returns an already resolved card synchronously; Get(ref) returns a promise.

Avatars in views rendered as HTML text​

Most list and board views build HTML strings, so avatars there are markup, not controls:

html += PersonDirectory.AvatarHtml(ref, name, 24);   // <img class="PersonAvatar" data-person="…" …>

The markup uses the photo when the card is already cached, otherwise the initials. To get photos on the first render either:

  • preload the people of the view before rendering — await Promise.all(refs.map((r) => PersonDirectory.Get(r))) — one request, then AvatarHtml finds them in the cache; or
  • hydrate after inserting the markup — PersonDirectory.Hydrate(rootElement) swaps the initials for photos once the cards arrive.

Preloading is the reliable option for components that render their content later than your code runs (e.g. KanbanBoard draws cards after ON_COMPLETE).

Any other element becomes a card anchor by carrying the attribute: <span class="Mention" data-person="user:42">@Jan</span>. The attribute survives DOMPurify (data attributes are allowed by default).

Avatars in searchable dropdowns​

DropDownSearch / DropDownMultiselect match the search against the visible text of an item (textContent), so an item may carry avatar markup (AvatarHtml(...) + " " + escapedName) without the data URL or the data-person id producing false hits. Labs uses this in the board assignee filter (TrackerFilterMultiselect.PersonRefs). The multiselect toggle joins the selected item texts, so the avatars show there too.

MemberBinding (component avatars)​

For OOP views with an Image user control:

const card : IPersonCard = await new MemberBinding().Bind({avatar: this.avatar, name: this.name, email: this.email}, emailOrId);

It sets the initials as Fallback, the photo as Source, the name (or " " for an unknown person) and the e-mail, and marks the image with data-person so the hover card opens over it once PersonHoverCard is installed. Without Install() nothing changes in behaviour — the attribute is inert.

Hover card behaviour​

  • Opens after showDelay (450 ms) of hovering a [data-person] element, immediately on a tap on touch screens ((hover: none)).
  • Stays open while the pointer is on the element or on the card; closes hideDelay (250 ms) after leaving both, on Esc, or on a click elsewhere.
  • Rendered as a Bootstrap popover with customClass: "PersonCard" in <body>; the content is escaped and sanitized again by Bootstrap. Style it through .popover.PersonCard (PersonHoverCard.scss).
  • Implemented on WindowManager.getEvents().setOnMove/setOnClick/setOnKeyDown — window-level setOnMouseOver never fires (see Events System).
Bootstrap tooltip on the same element

A popover on an element that already has a Bootstrap tooltip restores the native title attribute when it is disposed, which would show the browser tooltip next to the Bootstrap one. PersonHoverCard removes the title again after dispose() and hides the tooltip while the card is open — keep that in mind when building similar popovers.

Labs reference implementation​

Labs (io-oidis-labs) is the first full consumer:

  • Server: Utils/PersonCardManager.getPersonCards($refs) (ONLY_FOR_AUTHORIZED_USERS, re-checks the caller) — staff and employees get e-mail, phone, team and role; a guest gets name, photo and company of the Oidis people and only the guests of its own projects; administrators get links to the HR detail or the subject contact.
  • Client: Utils/PersonCards registers the resolver in MainPageController.afterLoad, the tracker board preloads its persons, issue comments and history show avatars, mentions in Markdown carry data-person.

Zbyšek registers a resolver over its getMemberProfile (Utils/MemberCards) and uses the CORE MemberBinding.