Handlers

Route handlers, router initialization, and virtual collection wiring for the file browser.

FileBrowserRouters

Return type for init_router — contains both the browser and virtual collection routers.


source

FileBrowserRouters


def FileBrowserRouters(
    browser:APIRouter, collection:APIRouter, urls:VirtualCollectionUrls, render:Callable,
    render_selection_oobs:Callable=_no_selection_oobs, update_selection_oobs:Callable=_no_update_selection_oobs,
    current_path:Callable=_no_current_path, sync_items:Callable=_no_sync_items
)->None:

Return value from init_router — both routers, URL bundle, render, and OOB helpers.

Selection Handlers

Selection Toggle Handler

Targeted OOB Helpers

Router Initialization

The init_router function creates both the browser-specific router and the virtual collection router, managing internal VC state and the items list as closure state.


source

init_router


def init_router(
    config:FileBrowserConfig, # Browser configuration
    provider:FileSystemProvider, # File system provider
    state_getter:Callable, # Function to get current state
    state_setter:Callable, # Function to save state
    route_prefix:str='/browser', # Route prefix for browser routes
    vc_route_prefix:str='', # Route prefix for VC routes (auto: {route_prefix}/vc)
    callbacks:Optional=None, # Optional callbacks
    home_path:Optional=None, # Home directory (defaults to provider)
)->FileBrowserRouters: # Browser + collection routers and URL bundle

Initialize file browser and virtual collection routers.

import tempfile
from pathlib import Path
from fasthtml.common import to_xml
from cjm_fasthtml_file_browser.providers.local import LocalFileSystemProvider

# Create test fixtures
with tempfile.TemporaryDirectory() as tmpdir:
    (Path(tmpdir) / "file1.txt").write_text("content1")
    (Path(tmpdir) / "file2.py").write_text("print('hi')")
    (Path(tmpdir) / "subdir").mkdir()

    config = FileBrowserConfig()
    provider = LocalFileSystemProvider()

    _state = BrowserState(current_path=tmpdir)
    def get_state(): return _state
    def set_state(s):
        global _state
        _state = s

    result = init_router(
        config=config,
        provider=provider,
        state_getter=get_state,
        state_setter=set_state,
        route_prefix="/browser",
    )

    # Verify return type
    assert isinstance(result, FileBrowserRouters)
    assert result.browser.prefix == "/browser"
    assert result.collection is not None
    assert result.urls.nav_up != ""
    assert result.urls.nav_down != ""
    assert result.urls.focus_row != ""
    assert result.urls.activate != ""
    assert result.urls.sort != ""

    # Verify render callable produces HTML
    assert callable(result.render)
    html = to_xml(result.render())
    assert "file-browser" in html
    assert "file1.txt" in html or "subdir" in html

    # Verify render_selection_oobs callable exists
    assert callable(result.render_selection_oobs)

print("Router initialization tests passed!")
Router initialization tests passed!
# Test render_selection_oobs — targeted checkbox OOB updates
with tempfile.TemporaryDirectory() as tmpdir:
    for i in range(5):
        (Path(tmpdir) / f"file{i}.txt").write_text(f"content{i}")

    config = FileBrowserConfig(selection_mode=SelectionMode.MULTIPLE, vc_prefix="test")
    provider = LocalFileSystemProvider()
    _state = BrowserState(current_path=tmpdir)
    def get_state(): return _state
    def set_state(s):
        global _state
        _state = s

    routers = init_router(
        config=config, provider=provider,
        state_getter=get_state, state_setter=set_state,
        route_prefix="/browser",
    )

    # Select file0 and file2 in browser state
    _state.selection.selected_paths = [str(Path(tmpdir) / "file0.txt"), str(Path(tmpdir) / "file2.txt")]

    # Get OOBs for paths in current directory
    oobs = routers.render_selection_oobs([str(Path(tmpdir) / "file0.txt"), str(Path(tmpdir) / "file2.txt")])
    assert len(oobs) > 0, "Should return OOB elements for visible items"
    for oob in oobs:
        oob_html = to_xml(oob)
        assert 'hx-swap-oob="outerHTML"' in oob_html
        assert 'col-select' in oob_html

    # Paths not in current directory return empty
    oobs_empty = routers.render_selection_oobs(["/nonexistent/path.txt"])
    assert oobs_empty == (), f"Expected empty tuple, got {len(oobs_empty)} elements"

    print("render_selection_oobs tests passed!")
# Test update_selection_oobs — sync + render in one call
with tempfile.TemporaryDirectory() as tmpdir:
    for i in range(5):
        (Path(tmpdir) / f"file{i}.txt").write_text(f"content{i}")

    config = FileBrowserConfig(selection_mode=SelectionMode.MULTIPLE, vc_prefix="upd")
    provider = LocalFileSystemProvider()
    _state = BrowserState(current_path=tmpdir)
    def get_state(): return _state
    def set_state(s):
        global _state
        _state = s

    routers = init_router(
        config=config, provider=provider,
        state_getter=get_state, state_setter=set_state,
        route_prefix="/browser",
    )

    assert callable(routers.update_selection_oobs)

    # Initially no selection
    assert _state.selection.selected_paths == []

    # update_selection_oobs syncs selection AND returns OOBs
    file0 = str(Path(tmpdir) / "file0.txt")
    file2 = str(Path(tmpdir) / "file2.txt")
    oobs = routers.update_selection_oobs([file0, file2], [file0, file2])

    # Browser state should now have the selection synced
    assert file0 in _state.selection.selected_paths
    assert file2 in _state.selection.selected_paths

    # OOBs should be returned for visible items
    assert len(oobs) > 0
    for oob in oobs:
        oob_html = to_xml(oob)
        assert 'hx-swap-oob="outerHTML"' in oob_html
        assert 'col-select' in oob_html

    # Deselect file0 — pass new full selection, changed path is file0
    oobs2 = routers.update_selection_oobs([file2], [file0])
    assert file0 not in _state.selection.selected_paths
    assert file2 in _state.selection.selected_paths

    print("update_selection_oobs tests passed!")
# Test current_path — returns browsed directory path
with tempfile.TemporaryDirectory() as tmpdir:
    config = FileBrowserConfig()
    provider = LocalFileSystemProvider()
    _state = BrowserState(current_path=tmpdir)
    def get_state(): return _state
    def set_state(s):
        global _state
        _state = s

    routers = init_router(
        config=config, provider=provider,
        state_getter=get_state, state_setter=set_state,
        route_prefix="/browser",
    )

    assert callable(routers.current_path)
    assert routers.current_path() == tmpdir

    # Simulate navigation by updating state
    _state.current_path = "/some/other/path"
    assert routers.current_path() == "/some/other/path"

    print("current_path tests passed!")