Skip to content

Router Tabs Layout Integration

This guide explains how the current Cabloy Basic layouts turn ModelTabs into visible router shell behavior in Zova.

Use this page after Router View Hosts Guide and Router Tabs Mechanism when your next question is no longer “how does the tabs model work?” but “how do the real layout controllers and render beans turn that model into the Admin or Web shell?”.

Read this page together with:

TIP

Router ecosystem docs path

  1. Page Route Guide — learn the public route-record and layout surface
  2. Zova Router Under the Hood — learn how the core router runtime cooperates
  3. Router View Hosts Guide — learn how routed pages are actually hosted
  4. Router Tabs Mechanism — learn how the tabs host turns route visits into state
  5. Router Tabs Layout Integration — learn how the current Basic layouts turn that state into visible shell behavior
  6. Router Tabs Route Meta Cookbook — learn how route metadata intentionally drives the model

You are here: step 5. Previous pages: Router View Hosts Guide and Router Tabs Mechanism. Next recommended page: Router Tabs Route Meta Cookbook.

Why this page exists

The current router-tabs docs already explain:

  • the business meaning of router tabs
  • the shared ModelTabs mechanism
  • the Admin vs Web visual comparison
  • the route-meta inputs that drive the model

What contributors and AI workflows still often want next is the layout-integration bridge:

  • where ModelTabs is created inside a real layout controller
  • how the layout chooses tabsOptions
  • how the render bean turns tabs into a visible shell
  • which parts are shared model semantics and which parts are layout presentation

This page is that bridge.

If your next question is specifically about how page code feeds task-level title, dirty state, or form scene into the visible shell, continue with Page Meta Guide.

The shortest accurate mental model

For the current public Cabloy Basic source, the shortest accurate model is:

  1. the layout controller loads menus and creates a ModelTabs selector bean
  2. the layout passes layout-specific tabsOptions such as scene, max, maxItems, and cache
  3. the shared ZRouterViewTabs host keeps route visits and keep-alive behavior synchronized with ModelTabs
  4. the layout render bean reads $$modelTabs.tabs, tabCurrent, and related state to produce the visible shell
  5. Admin and Web reuse the same underlying tabs model but project it into different top-level UI structures

That is why layout integration is not merely cosmetic. It is the point where shared router-tabs state becomes the concrete shell experience.

The source files that matter most

Admin layout integration

  • zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx
  • zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx
  • zova/src/suite/a-home/modules/home-layoutadmin/src/config/config.ts

Web layout integration

  • zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx
  • zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx
  • zova/src/suite/a-home/modules/home-layoutweb/src/config/config.ts

Shared host/model layer used by both

  • zova/src/suite-vendor/a-zova/modules/a-routertabs/src/component/routerViewTabs/controller.tsx
  • zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts

Step-by-step layout integration path

1. The layout controller, not the render bean, creates ModelTabs

The real integration starts in the layout controller.

In both Admin and Web, the controller is where:

  • menus are loaded
  • tabs config is read from the layout scope config
  • a selector-scoped ModelTabs bean is created
  • later menu changes refresh or reset visible tab info

This is the first important source-level fact about layout integration:

  • the render bean does not own tabs state
  • the layout controller owns the decision to create and configure the tabs model

2. Admin layout integration

Representative controller source:

text
zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx

Zova-native role

The Admin layout controller creates a workbench-oriented tabs model whose top level is anchored to menu-backed business workspaces.

Source-confirmed runtime behavior

Inside _initTabs() the Admin layout:

  • reads this.scope.config.tabs
  • builds tabsOptions
  • uses this.bean._getBeanSelector('a-routertabs.model.tabs', true, configTabs.scene, tabsOptions)
  • keeps the selected model on this.$$modelTabs
  • watches menu data and calls updateAllTabInfos() when menus change

The important Admin-specific tabsOptions behavior is:

  • getInitialTabs() returns [{ tabKey: '/', affix: true }] once menus are available
  • getTabInfo(tabKey) resolves title and icon from the menu model by link

That means the Admin layout does two important things for the shared tabs model:

  1. it guarantees a fixed affixed top-level home workspace
  2. it derives visible workspace labels from menu-backed business information rather than from temporary page state

Why this matters

This is why Admin level-1 tabs feel stable and business-oriented.

The model itself is generic, but the layout controller injects business-facing tab identity and the initial affixed workspace rule.

3. Admin render integration

Representative render source:

text
zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx

Zova-native role

The Admin render bean turns ModelTabs into an explicit two-level workbench shell.

Source-confirmed runtime behavior

The render bean exposes three main surfaces:

  • renderTabs() for level-1 workspace tabs
  • renderTabItems() for level-2 task items
  • _renderRouterViewTabs() for the actual routed host outlet

Important details:

  • level-1 rows iterate over $$modelTabs.tabs
  • clicking a level-1 tab calls $$modelTabs.activeTab(tabKey)
  • non-affix level-1 tabs expose deleteTab(tabKey)
  • level-2 rows read $$modelTabs.tabCurrent.items
  • the anchor item is intentionally skipped when componentKey === tabKey
  • clicking a level-2 item calls activeTabItem(tabKey, componentKey)
  • deleting a level-2 item calls deleteTabItem(...)
  • page-level icon state is derived from pageMeta such as pageDirty and formMeta.formScene
  • the actual routed content still enters through <ZRouterViewTabs>

For the authoring-side write path that produces those pageMeta values, see Page Meta Guide.

This is the second important source-level fact about layout integration:

  • the layout render bean does not replace the router host
  • it wraps the shared host with a visible shell that reads the same model state

What the Admin shell makes visible

Admin makes these model semantics explicit:

  • level-1 workspace grouping
  • level-2 task items
  • dirty indicators
  • create/edit task indicators
  • close actions for both outer and inner levels

That is why Admin is the clearest business-meaning specimen for router tabs.

4. Web layout integration

Representative controller source:

text
zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx

Zova-native role

The Web layout controller creates a tabs model that is still shared in semantics, but whose initial top-level state is derived from the menu tree rather than from one affixed home workspace.

Source-confirmed runtime behavior

Inside _initTabs() the Web layout:

  • reads this.scope.config.tabs
  • builds tabsOptions
  • creates the same selector-scoped ModelTabs bean
  • watches menu data and calls resetAllTabInfos() when menus change

The important Web-specific tabsOptions behavior is:

  • getInitialTabs() returns one item per top-level menu tree entry
  • each initial tab uses item.folder ? item.name : item.link as tabKey
  • info is stored directly from the menu tree item

That means the Web layout treats top-level workspace entries more like shell navigation nodes than like one pre-affixed home workspace plus later-opened work areas.

Why this matters

This is why the Web shell can feel menu-driven at the top level while still relying on the same ModelTabs state underneath.

The model stays shared, but the controller chooses a different initialization strategy.

5. Web render integration

Representative render source:

text
zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx

Zova-native role

The Web render bean projects the same tabs model into a horizontal menu-like shell.

Source-confirmed runtime behavior

The render bean:

  • iterates over $$modelTabs.tabs
  • renders top-level entries through _renderMenuItem(...)
  • supports folder, separator, and leaf menu shapes
  • uses tabKey === $$modelTabs.tabKeyCurrent for top-level active state
  • lets top-level shell clicks call $$modelTabs.activeTab(tabKey)
  • lets nested leaf links navigate through RouterLink
  • still renders the actual routed content through <ZRouterViewTabs>

This is the third important source-level fact about layout integration:

  • Web does not abandon the tabs model
  • it simply renders the top-level workspace state through a menu-oriented shell instead of an explicit lifted tab row

6. Config is part of layout integration, not an afterthought

Representative config sources:

  • zova/src/suite/a-home/modules/home-layoutadmin/src/config/config.ts
  • zova/src/suite/a-home/modules/home-layoutweb/src/config/config.ts

Current Basic source values

Admin:

  • scene: ''
  • max: 6
  • maxItems: 3
  • cache: true
  • tabItem.maxWidth: '130px'

Web:

  • scene: 'web'
  • max: 6
  • maxItems: 6
  • cache: false

Why this matters

These config differences directly shape shell behavior:

  • scene chooses the selector-space identity of the tabs model
  • max and maxItems change pruning density
  • cache changes whether shell state is restored through cached model state
  • Admin-only tabItem.maxWidth shapes inner task-row presentation

A practical reading takeaway is:

  • layout integration includes controller logic, render logic, and config together

7. What is shared vs what is layout-specific

Shared semantics

These remain in the shared model/host layer:

  • route-to-workspace mapping
  • tabKey and componentKey meaning
  • keep-alive inclusion
  • activation and pruning behavior
  • page-meta storage on tab items
  • routed content entering through ZRouterViewTabs

Layout-specific behavior

These belong mainly to the layout integration layer:

  • how top-level entries are initialized from menu state
  • whether there is an affixed home workspace
  • whether the second level is shown explicitly
  • whether the top level looks like tabs or a horizontal menu
  • how close buttons, item width, and icon density are presented

This is the key boundary to preserve:

  • change shared semantics in the host/model layer only when the framework contract should change
  • change visual shell behavior in the layout layer when only presentation should change

8. A practical reading order for source readers

If your question is specifically about how ModelTabs becomes a visible shell, use this order:

  1. zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts
  2. zova/src/suite-vendor/a-zova/modules/a-routertabs/src/component/routerViewTabs/controller.tsx
  3. zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx
  4. zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx
  5. zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx
  6. zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx
  7. the two layout config files

Use Admin first when you want the clearest expression of the workbench model. Use Web next when you want to confirm that the same model can back a different shell style.

Common mistakes to avoid

Mistake 1: treating the layout render bean as the tabs state owner

It is not.

The render bean projects state visually. The controller and shared model own the state.

Mistake 2: assuming Admin-specific tab rows define the whole router-tabs contract

They do not.

Admin is one visible projection of a broader routed-host and tabs-model contract.

Mistake 3: assuming Web is not using router tabs because it looks like a menu

It is still using ModelTabs and ZRouterViewTabs.

Only the shell projection differs.

Mistake 4: changing layout presentation when the real change belongs in ModelTabs

If the behavior should be shared across shells, change the shared model/host layer first.

If only the visible shell should differ, change the layout layer first.

Edition note

This guide is source-confirmed against the current public Cabloy Basic layouts.

That means the integration story here is accurate for the current Basic Admin and Web shells, but should not be overgeneralized to every future edition or downstream layout without verification.

Final takeaway

If Router Tabs Mechanism explains how the shared tabs model works, this page explains how the real Basic layouts turn that shared model into visible shell behavior.

That is the missing bridge between framework-level tabs semantics and the actual layout code contributors edit.

Released under the MIT License.