v2.1.0

Slider Manager Class

Provides methods for working with sliders in the Bitrix24 application. It allows opening and closing sliders, as well as managing their content.

Methods

openSliderAppPage

async openSliderAppPage(params: any = {}): Promise<any>

The openSliderAppPage method allows you to open the current app in a new slider, passing arbitrary parameters.

How it works:

  • Data Passing: You call the method and pass an object with parameters (e.g., place, action, or id).
  • Processing in the Application: On the application side, it's most convenient to intercept these parameters through middleware. This allows you to decide which route to display to the user before the page is rendered.
  • Seamless Navigation: The user sees a new slider with the desired content, while the redirection logic is hidden within the application.

Slider settings are passed via bx24_-prefixed keys. In particular, bx24_title sets the slider title; when the portal opens the slider it also reflects that title to the browser tab (document.title). So, unlike parent.setTitle (which only updates the in-layout #pagetitle), this path does change the browser tab title:

await $b24.slider.openSliderAppPage({ bx24_title: 'My title' })
frame-slider-app-page-open.ts
import type { B24Frame } from '@bitrix24/b24jssdk'
import { LoggerFactory } from '@bitrix24/b24jssdk'


const devMode = typeof import.meta !== 'undefined' && (import.meta?.dev || import.meta.env?.DEV)
const $logger = LoggerFactory.createForBrowser('Example:B24FrameSliderOpenPath', devMode)
const $b24 = useB24().get() as B24Frame

async function openAppPage() {
  const response = await $b24.slider.openSliderAppPage(
    {
      // The 'place' parameter will be available in placement
      // It should be processed in middleware to redirect to the desired route.
      place: 'app.place',
      bx24_width: 650,
      bx24_title: 'Page title in the browser'
    }
  )

  $logger.debug('response', { response })
}

await openAppPage()

What the portal does with your parameters

Three things worth knowing before you design around this call, because each has sent someone down a wrong path:

  • The portal re-opens your own registered handler URL — not a portal path. openPath is the one that opens a portal path, and pointing it at an application page yields a 404 on the portal side. That failure looks like "sliders cannot host application pages", which is not the case.
  • Everything in the argument object is forwarded to the new frame as PLACEMENT_OPTIONS. So place is an ordinary call parameter, not a registered placement — you do not need placement.bind for it, and you may name it whatever you like.
  • bx24_width has a practical lower bound. Below your own mobile breakpoint a desktop slider silently renders your mobile layout.

Routing to the right screen once the frame opens is your application's job. The SDK's part ends when the parameter is delivered.

Routing to the right screen in Nuxt

The pattern below is the one to copy. It looks longer than a plain return navigateTo(target) because two of its lines exist to survive a case that returns no error when it fails.

A redirect returned from route middleware does not survive the first navigation when the entry route is prerendered. The portal opens your frame with a query string (DOMAIN, PROTOCOL, LANG, APP_SID); for a prerendered page opened with a query, Nuxt hydrates on the bare path and then restores the original URL on app:suspense:resolve by assigning router.currentRoute.value directly — bypassing navigation guards, and overwriting whatever your middleware redirected to. The slider stays on the page the portal opened, and nothing reports an error.

This is not limited to nuxt generate. nuxt build prerenders any route listed in nitro.prerender.routes, and with crawlLinks: true anything reachable by a link — so an app that looks like an ordinary SSR deployment can still ship a prerendered slider entry point. Whether the file is served by a static web server or by Nitro itself makes no difference; what matters is only whether the page carries a prerendered payload.

// @check-ignore: Nuxt auto-imports (defineNuxtRouteMiddleware, navigateTo, onNuxtReady) plus two app-level helpers — none resolvable in the docs typecheck context

export default defineNuxtRouteMiddleware(async (to) => {
  if (import.meta.server) return

  const { $initializeB24Frame } = useNuxtApp()
  const $b24 = await $initializeB24Frame()

  const target = routeForPlace($b24.placement.options?.place)
  if (!target || isSamePath(to.path, target)) return

  // Take the query from the ADDRESS BAR, not from `to.query`: a prerendered page
  // hydrates on the bare path, so `to` carries no query at all and the target
  // screen would lose `place` along with the portal's own parameters.
  const dest = { path: target, query: Object.fromEntries(new URLSearchParams(location.search)) }

  const nuxtApp = useNuxtApp()

  // Live navigation inside an already running app — the ordinary way works.
  if (!nuxtApp.isHydrating) return navigateTo(dest, { replace: true })

  // First navigation of a freshly opened slider frame: navigate once the app is
  // ready, when Nuxt has already restored the initial URL.
  onNuxtReady(() => {
    void nuxtApp.runWithContext(async () => {
      const router = useRouter()
      // The user may have navigated away while we waited for an idle moment.
      if (!isSamePath(router.currentRoute.value.path, to.path)) return
      // `router.replace`, not `navigateTo`: the latter, when it catches another
      // navigation in progress, RETURNS a route object instead of navigating —
      // and here nobody would apply it.
      const failure = await router.replace(dest)
      if (failure) console.warn('slider redirect failed', failure.type)
    })
  })
})

replace: true matters: the page the portal opened is not a step the user took, and "back" inside the slider should not return to it.

isSamePath has to tolerate a trailing slash. A static server serves /app/ while the router resolves /app, and a strict !== cancels the redirect every single time.

Carrying the whole query forward also carries APP_SID, the portal's session identifier for this frame. That is deliberate — the target screen needs the portal's parameters — but it means the value lands in a router history entry and in route.fullPath. If you send fullPath, the current URL or a navigation breadcrumb to an analytics or error-reporting service, strip it there. Copy only the keys you need if your app does not require the rest.

Where the navigation is issued decides the outcome. Measured on Nuxt 4.5 with a statically generated app, frame opened at /app?place=app-options:

Navigation issued fromResult
return navigateTo(target) in middlewareURL unchanged, no error reported
router.replace / navigateTo in app:mountedNavigationFailure: aborted (type 8)
navigateTo from onNuxtReadyworks

onNuxtReady hooks the same app:suspense:resolve, but subscribes later and defers to requestIdleCallback, so it runs after the restore. There is no other supported way out: redirectCode is read on the server only, abortNavigation cannot redirect, and external: true means a full reload of the application.

The slider opens, but shows the main page

One symptom, several unrelated causes, and no error raised by any of them. Check in this order:

  1. Did the parameter arrive? Log the key names of $b24.placement.options and the value of place alone — not the whole object. It can carry the portal's query parameters, APP_SID among them, and this repo's own rule is that a credential-shaped value never reaches a logger (see AGENTS.md and the local/no-credential-in-logger lint rule). Log which frame you are in alongside it (window.location.pathname, $b24.placement.placement): both the main window and the slider frame run your code, and in the main window place is legitimately absent — without a frame identifier the main window's perfectly normal "no place" line reads as a slider failure.
  2. Was it read correctly? PLACEMENT_OPTIONS can arrive as a JSON string; options?.place on a string is undefined — see placement options.
  3. Did your routing actually run? Log the resulting URL after navigation, not the decision to navigate. A decision that is correct and a navigation that silently did not happen look identical in a log that only records the former. On a prerendered Nuxt route, see the warning above.
  4. Did the query survive your redirect? If your app also reads place from the frame URL, a redirect that drops the query leaves the target screen unable to tell it is in a slider — and "Cancel" then opens a second copy of the app instead of collapsing the slider.

Steps 2 and 3 reproduce without a portal: serve your built app locally and open /<your-page>?place=<your-place>, reading place from the query as a fallback. The routing half of the problem has nothing to do with Bitrix24.

closeSliderAppPage

async closeSliderAppPage(): Promise<void>

Closes the slider with the application.

frame-slider-app-page-close.ts
import type { B24Frame } from '@bitrix24/b24jssdk'

const $b24 = useB24().get() as B24Frame

async function closePage() {
  return $b24.slider.closeSliderAppPage()
}

closePage()

openPath

async openPath(url: URL, width: number = 1640): Promise<StatusClose>

Opens the specified path inside the portal in a slider.

Handles errors related to mobile device usage and can open the URL in a new tab if the slider is not supported.

Returns StatusClose

ParameterTypeDescription
urlURLURL to be opened.
widthnumberSlider width, a number in the range from 1640 to 900.

This example demonstrates calling the Bitrix24 slider and handling its closing result.

Key points:

  • Completion handling: After closing the slider, the SDK returns a status. This allows subsequent operations to be initiated, such as reinitializing application data.
In some cases, Bitrix24 opens the interface not in the slider, but in a new browser tab.
  • Limitation: It is virtually impossible to detect the closing of an individual tab using standard JS tools.
  • Solution: The isOpenAtNewWindow parameter in the jsSdk response allows you to determine whether a tab was opened instead of the slider and correctly configure the application's logic.
frame-slider-open-path.ts
import type { B24Frame } from '@bitrix24/b24jssdk'
import { LoggerFactory } from '@bitrix24/b24jssdk'


const devMode = typeof import.meta !== 'undefined' && (import.meta?.dev || import.meta.env?.DEV)
const $logger = LoggerFactory.createForBrowser('Example:B24FrameSliderOpenPath', devMode)
const $b24 = useB24().get() as B24Frame

async function makeOpenSliderEditCurrency(currencyCode: string) {
  // Open the slider with the specified width
  const url = $b24.slider.getUrl(`/crm/configs/currency/edit/${currencyCode}/`)
  const response = await $b24.slider.openPath(
    $b24.slider.getUrl(`/crm/configs/currency/edit/${currencyCode}/`),
    950
  )

  $logger.debug('response', { url, response })

  // Check that it was a slider (not a new tab) and it was closed
  if (!response.isOpenAtNewWindow && response.isClose) {
    $logger.notice(`The slider is closed. Reinitializing the application...`)
    // Data update logic
  }
}

await makeOpenSliderEditCurrency('USD')

getUrl

getUrl(path: string = '/'): URL

Returns a URL relative to the domain name and path.

$b24 = await initializeB24Frame()
// ...
const url = $b24.slider.getUrl('/settings/configs/userfield_list.php')

getTargetOrigin

getTargetOrigin(): string

Returns the Bitrix24 address (e.g., https://your_domain.bitrix24.com).