Members
(static, constant) SLOW_LOAD_SERVER_S
- Default Value
- 30
(static, constant) SLOW_LOAD_STILL_S
When a slow first page's loading text changes, in seconds since the load started. From a server that keeps up, the first page arrives in about 3.4 s even on a 512 MB Stick (measured 2026-09-24, 8,643-movie library), so a load that is merely normal never shows any text.
- Default Value
- 8
Methods
(static) SetUpOptions() → {void}
- Type:
- void
(static) buildGridQuery(nameStartsWith, searchTerm) → {object}
The query for a fresh load of the current view (see gridQuery.build).
| Name | Type | Description |
|---|---|---|
nameStartsWith | string | The alpha filter (" " and "" are both sent as-is) |
searchTerm | string | The search term; a presenter may override it |
LoadItemsTask2 input fields
- Type:
- object
(static) configureBackdrop() → {void}
- Type:
- void
(static) configureGrid() → {void}
- Type:
- void
(static) deliverFilters(filters, errorMsg) → {void}
Hand the filter result back to the presenter, which owns m.apiFilters + the toast. This is needed because Roku promise callbacks run at component scope, not on the presenter class instance.
| Name | Type | Description |
|---|---|---|
filters | object | |
errorMsg | string |
- Type:
- void
(static) focusAfterLoad() → {void}
A load's first result arrived. It moves focus only when the user is not somewhere it must not be taken from: another screen (the view is suspended), or the options dialog, where moving it loses their place and their next OK is dropped or commits the wrong entry. On the alpha menu it stays there.
- Type:
- void
(static) focusContent() → {void}
Focus the content area as it currently is: Try again when the load failed, otherwise whichever of the genre list and the grid is showing.
- Type:
- void
(static) focusRetryButton() → {void}
- Type:
- void
(static) getCollectionType() → {string}
- Type:
- string
(static) getDefaultView() → {string}
- Type:
- string
(static) getItemFlatIndex(itemId) → {integer}
Returns the flat index of an item within the grid's content root. Called by GridItem via callFunc for O(1) index lookup (avoids O(N) child scan).
| Name | Type | Description |
|---|---|---|
itemId | string | the Jellyfin item ID to look up |
flat index, or -1 if not found
- Type:
- integer
(static) getItemFocused() → {dynamic}
- Type:
- dynamic
(static) gridRow() → {integer}
The row the user is at: where the grid is scrolling to, or where focus is when it is still.
- Type:
- integer
(static) hideTVGuide() → {void}
Hide TV Guide and return to grid
- Type:
- void
(static) init() → {void}
- Type:
- void
(static) loadFilters(params) → {void}
Render-thread filter load, collapsed from GetFiltersTask (Phase-4 pure-fetch Task collapse; see docs/dev/promises.md). Invoked by the presenter via callFunc so the body runs in component-m context, where fetchAsync + auto-abandon work. The presenter is a class, and the auto-abandon plugin only injects into components with onDestroy (which BaseGridView has). The presenter still owns m.apiFilters + the toast; this only fetches and hands the result back via deliverFilters().
| Name | Type | Description |
|---|---|---|
params | object |
- Type:
- void
(static) loadInitialItems() → {void}
- Type:
- void
(static) loadLanguageFilters(filters, errorMsg) → {void}
Second leg of the filter load, on 12.0+ servers only: the audio/subtitle language lists, which live on a DIFFERENT endpoint from the rest.
The two are disjoint and neither is a superset: /Items/Filters carries OfficialRatings and Years, /Items/Filters2 carries the language lists. So a client that wants both has to ask twice. Sequential rather than parallel because a promise callback here cannot capture locals, so the first leg's result has to be parked on m anyway; the extra round trip happens once per library load, off the critical path (the grid's own items load independently).
The language leg is strictly OPTIONAL: any failure delivers the first leg's result unchanged and shows no toast. Losing two filter categories is not worth interrupting someone who was not necessarily going to open the panel, and the categories are offered only when their lists arrive non-empty, so the panel is correct either way.
| Name | Type | Description |
|---|---|---|
filters | object | |
errorMsg | string |
- Type:
- void
(static) loadLibraryFromRoute(route) → {void}
Configure the library view from its route. Both in-app navigation and a deep link / cast supply the rich library node as route.context.item (the resolver passes the node it already validated), so there is no fetch here. A missing context node is therefore an unexpected route shape, not a deferred feature: warn rather than silently render nothing.
| Name | Type | Description |
|---|---|---|
route | object |
- Type:
- void
(static) loadMoreData() → {void}
- Type:
- void
(static) loadMoreIfShort() → {void}
Asks for the next page when the loaded rows left below the focus would run out before a page could arrive (gridPaging, "keep a runway"). Asked whenever that can change: focus moved, a page arrived, the view came back.
- Type:
- void
(static) logGridSession() → {void}
- Type:
- void
(static) newGridSession() → {void}
One line per grid query, for tuning gridPaging's starting values: how often the user waited at the end of the loaded rows and for how long (stalls), against how much was loaded and how far down they actually went. A stall counts from reaching the last loaded row with a page in flight, before the indicator's delay. appendMs / appendMaxMs are the render thread's side of a page (adding its items to the grid), summed over the session and at its worst page, because a long add is a scroll hitch that no task-side number shows.
- Type:
- void
(static) offeredViews() → {object}
The views the presenter is currently listing for this library, [] when there is no presenter yet.
Derived from getOptions() rather than from a second list the presenters would also have to maintain: there is one list, so nothing can disagree with it. "Currently" matters: a presenter's views depend on the server version and on the parent item (a genre folder offers fewer than its library), so this is asked per load, not cached. The cost is one extra options build per library load, the same work SetUpOptions already does once: a handful of translate() calls on the render thread.
- Type:
- object
(static) onAlphaActiveChanged() → {void}
- Type:
- void
(static) onAlphaSelectedChanged() → {void}
- Type:
- void
(static) onDestroy() → {void}
onDestroy: Full teardown releasing all resources before component removal Called automatically via JRScreen.beforeViewClose when sgRouter permanently closes this view.
- Type:
- void
(static) onFailureNarrationDue() → {void}
- Type:
- void
(static) onGenreItemFocused() → {void}
- Type:
- void
(static) onGenreItemSelected() → {void}
- Type:
- void
(static) onGenreSkeletonsReady(msg) → {void}
Draw the Genres view's rows before their artwork exists.
The task knows every genre's name as soon as the genre LIST query returns, which is one request rather than one-per-genre, so the list can be on screen long before the samples land. Each row gets its title and a single skeleton placeholder cell: a RowList does not lay out a childless row at all, and that one cell is also what gives the row its slot height.
genreSkeletons carries ids and titles, not nodes. Building the rows here rather than shipping them from the task keeps the payload of that cross-thread write small: the rendezvous is priced by what it carries. Same split of work as HomeRows.createSkeletonRows().
Nothing here is authoritative: content still arrives later with the real rows and replaces these wholesale. If it never arrives, the skeletons are what the failure looks like, which beats an empty screen.
| Name | Type | Description |
|---|---|---|
msg | dynamic |
- Type:
- void
(static) onGridLoadFailed(isFirstPage) → {void}
A run that got no usable answer from the server (its task logged why). That says nothing about what the library holds, so it is never shown as empty.
A later page: what is on screen stays, and so does the total, so scrolling near the end asks again (onItemFocused), and so does coming back to the grid (onScreenShown). When the user is told is gridPaging's rule. The first page: the failure state replaces the grid, with a Try again the user chooses to press. Retrying on our own would re-send a query the server may still be working on.
| Name | Type | Description |
|---|---|---|
isFirstPage | boolean |
- Type:
- void
(static) onGridScrolled() → {void}
The grid's scroll position, reported on every rendered frame while it scrolls. itemFocused is not enough to keep a runway: during a run of key presses the grid keeps scrolling and reports focus only where it stops (measured 2026-09-25 on a Stick 4K: 15 reports over 66 rows), by which time the user can be at the last loaded row. Acts only when the row entering the focus position changes, so a frame that moves no row costs one comparison.
- Type:
- void
(static) onItemDataLoaded(msg) → {void}
| Name | Type | Description |
|---|---|---|
msg | dynamic |
- Type:
- void
(static) onItemFocused() → {void}
- Type:
- void
(static) onItemSelected() → {void}
- Type:
- void
(static) onKeyEvent(key, press) → {boolean}
| Name | Type | Description |
|---|---|---|
key | string | |
press | boolean |
- Type:
- boolean
(static) onLibrarySelection(msg) → {void}
A grid / genre / channel item was selected: navigate to its route (rich node as context). Non-navigable selections fall through to playback.
| Name | Type | Description |
|---|---|---|
msg | dynamic |
- Type:
- void
(static) onLoadingMoreDue() → {void}
- Type:
- void
(static) onPresenterLogoLoaded(event) → {void}
Bridge callback for presenter logo loading - forwards to presenter.onLogoLoaded() This is needed because Roku observers look for functions at component scope, not on class instances. Presenters should observe with "onPresenterLogoLoaded".
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onPresenterTypeChanged() → {void}
Called when presenterType field changes - creates the appropriate presenter
- Type:
- void
(static) onQuickPlayLaunch(msg) → {void}
Forward a non-navigable selection to the playback launcher.
| Name | Type | Description |
|---|---|---|
msg | dynamic |
- Type:
- void
(static) onRetryButtonReady() → {void}
- Type:
- void
(static) onScreenHidden() → {void}
- Type:
- void
(static) onScreenShown() → {void}
- Type:
- void
(static) onSlowLoadDue() → {void}
- Type:
- void
(static) onTVGuideFocusedChannel() → {void}
Handle channel focus from TV Guide
- Type:
- void
(static) onTVGuideWatchChannel() → {void}
Handle channel selection from TV Guide
- Type:
- void
(static) onVoiceFilter() → {void}
- Type:
- void
(static) optionsClosed() → {void}
- Type:
- void
(static) prepareDataLoad(disableRemoteopt) → {void}
Prepare for data loading by releasing any run in flight, showing spinner, and hiding empty text
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
disableRemote | boolean | <optional> | false | Whether to disable remote input during loading |
- Type:
- void
(static) releaseGridRun() → {void}
Stops the grid's run in flight, if any, and detaches both fields a run is observed on.
- Type:
- void
(static) reloadGridItems() → {void}
Full reset + reload of the grid. Used by optionsClosed (sort/filter/view change) and by the resume-time content-freshness check in onScreenShown (a delete elsewhere invalidated our cached content).
- Type:
- void
(static) restoreGridFocus() → {void}
Focus on return to this view (onScreenShown and restoreScreenFocus). The saved lastFocus is kept when it is outside the content area (the alpha menu, the TV guide). Inside it, what it names may no longer be what the view shows (a load can fail or finish while suspended), so the content area is focused as it is now.
- Type:
- void
(static) restoreScreenFocus() → {void}
Focus hook (overrides JRScreen.restoreScreenFocus). The router's handleFocus runs it right after onScreenShown, and the base restores lastFocus again, undoing a restore that followed what changed while this view was suspended. Same trap and same fix as Home.restoreHomeFocus.
- Type:
- void
(static) retryGridLoad() → {void}
Runs the failed query again, from its first page.
- Type:
- void
(static) revealGenreList(rowsRoot) → {void}
Swaps the genre RowList to a new set of rows and reveals it, preserving the user's position. One owner for both delivery stages (the skeleton rows and the real rows), so the opacity/focus/spinner sequence cannot drift between them.
rowsRoot is a DETACHED ContentNode root: building rows off-tree and assigning content once is a single change notification instead of one per row (the same shape onAlphaSelectedChanged already uses).
Focus/scroll: the view is interactive from the moment skeletons draw, so the user may have scrolled before the real rows land. Skeleton and real rows are 1:1 in server order, so the previous [row, item] position is index-stable: restore it rather than letting the content swap snap the list back to the top. The item index is clamped: a skeleton row holds one cell, the filled row up to 7.
| Name | Type | Description |
|---|---|---|
rowsRoot | object |
- Type:
- void
(static) setGridQuery(query) → {void}
Makes query the grid's current query. A new query is a new result set, so the previous query's total no longer applies: until this query's first page lands, nothing may page against it. (Swapping the grid's content reports focus on item 0, and onItemFocused would otherwise page against the old total.)
| Name | Type | Description |
|---|---|---|
query | object |
- Type:
- void
(static) setupNodes() → {void}
- Type:
- void
(static) showTVGuide() → {void}
Show TV Guide (EPG) for LiveTV
- Type:
- void
(static) startGridRun(startIndex, observeSkeletons) → {void}
Runs m.gridQuery from startIndex on a NEW LoadItemsTask2 node, releasing the run in flight. Never a relaunch of the previous node: see replaceTask.
| Name | Type | Description |
|---|---|---|
startIndex | integer | The first item to fetch |
observeSkeletons | boolean | Whether this run may deliver genreSkeletons |
- Type:
- void
(static) startSlowLoadText() → {void}
A first page can take the server most of a minute (timeouts.GRID_PAGE_MS). The spinner says nothing for SLOW_LOAD_STILL_S, then that it is still loading, then from SLOW_LOAD_SERVER_S that the wait is the server's: all we know then, since nothing has arrived. The cause, if the page fails, goes to the log (decision grid-load-failure-retry-policy).
- Type:
- void
(static) stopGridSpinner() → {void}
Hides the loading spinner, and stops its text from moving on (startSlowLoadText).
- Type:
- void
(static) trackStall(isWaiting) → {void}
| Name | Type | Description |
|---|---|---|
isWaiting | boolean |
- Type:
- void
(static) updateBackdropForFocusedItem() → {void}
- Type:
- void
(static) updateLoadingMore() → {void}
Shows "Loading more" while the user waits at the last loaded row for the next page, after a short delay so a fast page never flashes it. Asked wherever the answer can change.
- Type:
- void
(static) updateTitle() → {void}
- Type:
- void