|
| 1 | +## Browser UI — BlimpKit gotchas |
| 2 | + |
| 3 | +The IDE shell and most editor perspectives render through **BlimpKit**, a thin AngularJS-on-Fundamental-Styles component library that lives in `components/ui/platform-core/src/main/resources/META-INF/dirigible/platform-core/ui/blimpkit/` (Angular module name **`blimpKit`** — camelCase, declared in `blimpkit.js`). The runnable artifact is the bundled `/webjars/blimpkit__blimpkit/dist/blimpkit.min.js` (~158 KB, currently webjar 2.1.6). Findings below are the ones that have already burned someone — read once, save hours later. |
| 4 | + |
| 5 | +- **`<bk-checkbox>` is invisible without `<bk-checkbox-label>`.** `bk-checkbox` compiles to a bare `<input type="checkbox" class="fd-checkbox">`. Fundamental-Styles' `.fd-checkbox` rule hides the native input (`opacity:0; position:absolute`) on the assumption that a sibling `<bk-checkbox-label>` will draw the visible square via its `.fd-checkbox__checkmark` ::before pseudo. A lone `<bk-checkbox>` is therefore a working click target with zero visible chrome — easy to ship and never catch in code review. Pair it: `<bk-checkbox id="x" ng-model="…">` followed by `<bk-checkbox-label for="x" empty="true">…</bk-checkbox-label>` (the `empty="true"` attribute drops the inner text container so the label provides just the checkmark — use it when the surrounding markup already labels the row). |
| 6 | +- **`<bk-dialog>` has an isolate scope.** You can't put `ng-controller="…PopupCtrl"` on the dialog element itself — Angular throws "Multiple directives [bkDialog, ngController] asking for new/isolated scope on: <bk-dialog>". Wrap with a thin `<div ng-controller="…">` and put `<bk-dialog visible="…">` inside. |
| 7 | +- **`<bk-select>` doesn't support `ng-options`.** Use `<bk-option ng-repeat>` instead — text via the `text` attribute, model value via `value`. Example: `<bk-option ng-repeat="opt in items" text="{{opt.name}}" value="opt.id">`. When the select sits in a parent with `overflow:hidden` (a dialog, a sidebar), add `dropdown-fixed="true"` so the menu floats via `position:fixed` instead of being clipped. |
| 8 | +- **`<bk-option>`'s `text` and `value` bind differently** — `text: '@'` is **interpolation** (use `text="{{ expr }}"` or a literal), `value: '<'` is a **one-way expression** (use `value="expr"`, never `value="{{ expr }}"`). Mixing them up is the canonical bug for this directive: |
| 9 | + - `value="{{s}}"` makes Angular try to parse `{{s}}` as a JS expression, the directive's link silently fails, and the dropdown shows raw `{{ text }}` from the unlinked template (one ghost item per ng-repeat iteration, not six). Fix: `value="s"`. |
| 10 | + - `value="user"` evaluates `$scope.user`, not the string `"user"` — every option ends up with the same `undefined` value and selection becomes a no-op. For string literals, quote inside: `value="'user'"`. For the empty default option, `value="''"`, not `value=""` (which is the undefined-expression). |
| 11 | + - Numeric literals (`value="2"`) and loop variables (`value="s"`) are already expressions — leave them unquoted. Numbers stay numbers, so `selectedValue === '2'` will fail; either store as numbers on the model or coerce in the controller (the refresh-interval dropdowns in `view-jvm-monitoring` / `view-jvm-threads` `parseInt` the model on read). |
| 12 | +- **Perspective SVG icons inherit `fill` from CSS — don't hard-code `fill` on the path.** `blimpkit.css` styles `.fd-list__navigation-item i.bk-icon--svg svg` with `fill: var(--fdVerticalNav_Icon_Color, #303030)` (and `var(--sapSelectedColor)` on the active state). The CSS only takes effect on `<path>` elements with **no own `fill`** — adding `fill="#000000"` (the default when you paste an SVG from a web icon set) locks the icon to black and breaks dark-theme adaptability. Strip the fill attribute (jobs.svg / operations.svg pattern) or set `fill="currentColor"` (database.svg pattern). The container svg's other niceties (`width="512"` / `height="512"` / `stroke-width=".99999"`) don't affect rendering through this CSS but are the established style. |
| 13 | +- **`<bk-input>` / `<bk-textarea>` / `<bk-button>` use `replace:true`.** The attributes you write on the directive element (ng-model, ng-blur, ng-keypress, ng-disabled, custom directives like `auto-focus` / `select-text`) end up on the underlying native `<input>` / `<textarea>` / `<button>`, so existing controller code keeps working unchanged after migrating native form controls to `bk-*`. ng-model binds against the parent scope — the isolate scope `bk-input` declares only owns `compact` / `state` / `glyph`. |
| 14 | +- **Don't put `ng-class` on a `replace:true` directive element that already has its own `ng-class`.** `bk-table-header-cell`, `bk-table-cell`, and most layout-y BlimpKit directives template as `<th ng-class="getClasses()" …>` — Angular's attribute merge **string-concatenates** duplicate `ng-class` values, producing nonsense like `ng-class="{ sorted: sort.key === 'id' } getClasses()"`. The page then throws `$parse:syntax` at compile time and the row never renders. (`class` merges cleanly — only `ng-class` is broken — so `class="no-sort"` on a `<th bk-table-header-cell>` works fine.) The fix: push the conditional class onto a child element instead of the directive root: `<th bk-table-header-cell ng-click="…"><span class="sort-caret" ng-class="{ active: sort.key === 'id' }">{{ caret() }}</span></th>`. Same applies for anything else with a `replace:true` + `ng-class` template (audit `components/ui/platform-core/.../blimpkit/*.js` for the pattern before adding `ng-class` to a `bk-*` directive). |
| 15 | +- **The `blimpKit` module's `.config()` block disables three `$compileProvider` flags.** `cssClassDirectivesEnabled(false)`, `commentDirectivesEnabled(false)`, and `debugInfoEnabled(false)` are flipped at module-load when debug info was on — saves per-element scope-tracking overhead in production. The last flag breaks Selenide-style debugging that calls `angular.element(node).scope()`: Angular stops attaching scope refs to DOM nodes, so the lookup returns `undefined`. If your app or its integration tests rely on that, re-enable the flags in a `.config(['$compileProvider', …])` block of your own — module config blocks run in dependency order, so `blimpKit`'s flips happen first and your override sticks. |
| 16 | +- **SAP-icons + the "72" body font live in platform-core's `fonts.css`.** Every BlimpKit-using page needs `<link rel="stylesheet" href="/services/web/platform-core/ui/styles/fonts.css">`. Without it `.sap-icon--*` glyphs render as tofu squares because the `@font-face { font-family: "SAP-icons"; … }` declaration is missing. The IDE shell loads this automatically via the `platform-links` injection mechanism (see below); standalone iframes (editor-bpm, embedded views) have to add the link tag explicitly. Other `@font-face` rules in the same file declare the body font: `"72"` (Regular / Light / Bold), `"72-Light"`, `"72-Bold"`, `"72Mono-Regular"`, `"72Mono-Bold"`, plus `"BusinessSuiteInAppSymbols"` and `"SAP-icons-TNT"`. |
| 17 | +- **`<meta name="platform-links" category="…">` auto-injects scripts + stylesheets.** Looking at any non-iframe perspective HTML you'll see a single `<meta name="platform-links" category="ng-view,ng-perspective">`-style tag in the `<head>`. `HtmlPlatformLinksInjector` (in `components/engine/engine-web/.../HtmlPlatformLinksInjector.java`) reads it at request time, walks the `category` list, and replaces the meta tag with the bundle of `<link>` and `<script>` tags registered for those categories. Categories are defined in `components/engine/engine-web/src/main/resources/platform-links.json` — `ng-view` is the heavyweight bundle (jQuery, AngularJS, all the platform hubs, BlimpKit, Fundamental-Styles, fonts.css), `ng-perspective` adds split + layout, `ng-editor` adds workspace + repository hubs, etc. Adding new shared platform code → add it to this JSON, not to every perspective HTML. |
| 18 | +- **`<bk-dialog>` toggles visibility via the `visible` binding, not a `.modal('show')` plugin.** `<bk-dialog visible="modal.visible">` watches the expression and adds `fd-dialog--active` when true. No backdrop element is added (the dialog's own `.fd-dialog--active` overlay handles z-index + dimming). To dismiss programmatically: flip the bound flag (`scope.modal.visible = false`) inside an `$apply`; let the directive's digest cycle remove the `--active` class; then `$timeout` ~300ms later before tearing down the scope so the close animation completes. |
| 19 | +- **Test selectors after a BlimpKit migration.** Native `<input class="form-control">` → `<input class="fd-input fd-input--compact">`. `<div class="modal in">` (Bootstrap-3 visible) → `<section class="fd-dialog fd-dialog--active">`. `body.modal-open` and `.modal-backdrop` are NOT set by `<bk-dialog>` — drop assertions on those, the active overlay handles its own dimming. When fixing Selenide tests that look at `.modal-header .close`, switch to `.fd-dialog__header .fd-button` (or scope to the dialog with `section.fd-dialog--active button.fd-button`). |
| 20 | +- **A `<split>` splitter needs the `platformSplit` module in the app's dependency list — loading the script is not enough.** The `<split>`/`<split-pane>` resizable-pane directives are defined in Angular module `platformSplit` (`platform-core/ui/platform/split.js`). The script + `split.css` are already bundled by the `ng-perspective` (and `ng-split`) `platform-links` categories, so a perspective that declares `ng-perspective` does NOT also need `ng-split`. But every app must still list `'platformSplit'` in its `angular.module('app', [...])` deps, or the directives never register: `<split>`/`<split-pane>` stay inert unknown elements and the layout collapses (one pane fills everything, the others vanish — with no console error). Working examples: `editor-csvim`, `perspective-settings`, `resources-inbox`. Layout: `.bk-split` is `height:100%`, so under a persistent `<bk-toolbar>` in a `bk-vbox` body wrap the split in `<div class="bk-stretch">` (see `resources-documents`); wrap each pane's content in `<div class="bk-vbox bk-fill-parent">`; `split-pane size` values should sum to 100; the gutter replaces any manual `bk-border--*`. |
| 21 | + |
0 commit comments