Services - Shared Utilities
Services provide shared functionality across the UI: event handling, popups, translations, storage, and navigation.
Services provide shared functionality across the UI: event handling, popups, translations, storage, and navigation.
Events Service
Provides auto-cleanup event handling tied to component lifecycle.
useEvents Composable
import { useEvents } from '@/services/events'
// Creates event handler that auto-cleans on unmount
const events = useEvents()
// Subscribe to events
events.on('MyEvent', (data) => {
console.log('Received:', data)
})
// One-time listener
events.once('InitComplete', () => {
console.log('Initialization done')
})
// Manual unsubscribe (rarely needed)
events.off('MyEvent', specificHandler)
// Emit events (UI → UI)
events.emit('CustomEvent', { data: 'value' })useStreams Composable
import { useStreams } from '@/services/events'
// Subscribe to streams (auto-cleanup)
useStreams(['electrics', 'sensors'], (streams) => {
if (streams.electrics) {
speed.value = streams.electrics.wheelspeed
}
})
// With manual control
const streamControl = useStreams(['electrics'], handler)
streamControl.off() // Manually unsubscribe
streamControl.on() // Re-subscribePopup Service
Display modal dialogs, confirmations, and prompts.
Import
import {
openMessage,
openConfirmation,
openPrompt,
openProgress,
addPopup,
popupsView
} from '@/services/popup'Simple Message
import { openMessage } from '@/services/popup'
// Display message with OK button
await openMessage('Title', 'This is a message')Confirmation Dialog
import { openConfirmation } from '@/services/popup'
import { $translate } from '@/services'
import { ACCENTS } from '@/common/components/base'
const confirmed = await openConfirmation(
$translate.instant('ui.common.confirm'),
'Are you sure you want to proceed?',
[
{ label: 'Yes', value: true, extras: { default: true } },
{ label: 'No', value: false, extras: { cancel: true, accent: ACCENTS.secondary } }
]
)
if (confirmed) {
// User clicked Yes
}Text Prompt
import { openPrompt } from '@/services/popup'
const name = await openPrompt(
'Enter your name:', // Message
'Name Input', // Title
{
defaultValue: '',
maxLength: 50,
validate: (text) => text.length > 0,
errorMessage: 'Name is required',
disableWhenInvalid: true
}
)
if (name) {
console.log('User entered:', name)
}Progress Dialog
import { openProgress } from '@/services/popup'
const popup = openProgress(
'Downloading...', // Message
'Download Progress', // Title
{
indeterminate: false,
min: 0,
max: 100,
initialValue: 0
}
)
// Update progress
popup.progress.update(50, 'Halfway there...')
// Complete
popup.progress.done(100)Custom Popup
import { addPopup, PopupTypes } from '@/services/popup'
import MyCustomComponent from './MyCustomComponent.vue'
const popup = addPopup(
MyCustomComponent,
{ customProp: 'value' },
PopupTypes.normal
)
// Close from outside
popup.return(result)
// Or from inside component
// props.__popup.return(result)Translation Service
Internationalization support.
Basic Usage
import { $translate } from '@/services'
// Simple translation
const text = $translate.instant('ui.common.okay')
// In templates
<span>{{ $t('ui.common.okay') }}</span>Context Translation
For strings with parameters:
import { $translate } from '@/services'
// Object with context
$translate.contextTranslate({
txt: 'ui.career.moneyFormat',
context: { amount: 1500 }
})
// Returns: "$1,500" (based on translation file)
// In templates
<span>{{ $ctx_t({ txt: 'ui.career.moneyFormat', context: { amount: 1500 } }) }}</span>Multi-Context Translation
import { $translate } from '@/services'
// Array of translation objects
$translate.multiContextTranslate([
{ txt: 'ui.career.vehicle' },
{ txt: 'ui.common.colon' },
{ txt: 'ui.vehicles.etk800' }
])Storage Service
localStorage wrapper with reactivity.
Basic Usage
import Storage from '@/services/storage'
const storage = new Storage('my-app')
// Get/Set
storage.set('name', 'John')
const name = storage.get('name') // 'John'
const age = storage.get('age', 25) // 25 (default)
// Check/Delete
storage.has('name') // true
storage.del('name')Reactive Values
import Storage from '@/services/storage'
const storage = new Storage('my-app', {
theme: 'dark',
volume: 0.8,
lastPlayed: () => Date.now() // Function defaults are evaluated when needed
})
// Reactive object
storage.values.theme // 'dark'
storage.values.volume = 0.5 // Saves to localStorage
// Use in Vue
<template>
<span>{{ storage.values.theme }}</span>
</template>Navigator Service
Page and menu navigation.
Basic Navigation
import { useRouter } from 'vue-router'
const router = useRouter()
// Navigate to route
router.push('/career/vehicleInventory')
// Named route with params
router.push({
name: 'vehiclePurchase',
params: { vehicleInfo: 'abc123' }
})
// Go back
router.back()Game State Navigation
// For Angular/legacy states
const bngVue = window.bngVue
bngVue.gotoAngularState('menu.mainmenu')
bngVue.gotoGameState('garagemode')
bngVue.gotoGameState('menu.vehicles', {
params: { mode: 'garageMode' }
})From Lua
-- Navigate to UI state
guihooks.trigger('ChangeState', {state = 'career/vehicleInventory'})
-- Navigate back
guihooks.trigger('UINavigation', 'back', 1)UI Apps Service
Control HUD app visibility and layout.
import { useUIApps } from '@/services/uiApps'
const appsAPI = useUIApps()
// Set app layout
appsAPI.setLayout('blank') // Hide all
appsAPI.setLayout('tasklist') // Show only tasklist
// Set visibility
appsAPI.setVisible(true)
appsAPI.setVisible(false)
// Get current layout
const layout = appsAPI.currentLayoutControls Service
Input device detection.
import useControls from '@/services/controls'
import { storeToRefs } from 'pinia'
const controls = useControls()
const { showIfController } = storeToRefs(controls)
// In template
<BngBinding v-if="showIfController" ui-event="back" controller />Info Bar Service
Bottom information bar.
import { useInfoBar } from '@/services/infoBar'
const infoBar = useInfoBar()
// Control visibility
infoBar.visible = true
infoBar.showSysInfo = true
// Add action hints
infoBar.clearHints()
infoBar.addHints([
{ action: 'confirm', label: 'ui.common.select' },
{ action: 'back', label: 'ui.common.back' }
])Top Bar Service
Top navigation bar.
import { useTopBar } from '@/services/topBar'
const topBar = useTopBar()
topBar.show()
topBar.hide()Screen Cover Service
Loading screen control.
import { loadingScreen, startLoading } from '@/services/screenCover'
// Show loading screen
startLoading()
// Check state
if (loadingScreen.shown) {
// Loading screen is visible
}Content Services
Content parsing utilities.
import { $content } from '@/services'
// BBCode parsing
$content.bbcode // BBCode parse utilities
// Markdown parsing
$content.markdown // Markdown utilitiesNote:
$contentonly exportsbbcodeandmarkdownmodules. There are novehicles,levels, orpartsutilities here.
Common Patterns
Service in Component
<template>
<div>
<BngButton @click="confirmDelete">
{{ $t('ui.common.delete') }}
</BngButton>
</div>
</template>
<script setup>
import { openConfirmation } from '@/services/popup'
import { $translate } from '@/services'
import { useEvents } from '@/services/events'
import { ACCENTS } from '@/common/components/base'
const events = useEvents()
async function confirmDelete() {
const confirmed = await openConfirmation(
$translate.instant('ui.common.confirm'),
$translate.instant('ui.myModule.deleteWarning'),
[
{ label: $translate.instant('ui.common.delete'), value: true },
{ label: $translate.instant('ui.common.cancel'), value: false, extras: { accent: ACCENTS.secondary } }
]
)
if (confirmed) {
events.emit('ItemDeleted')
}
}
</script>Persistent Settings
import Storage from '@/services/storage'
const settings = new Storage('myApp-settings', {
volume: 0.8,
showHints: true,
lastVehicle: null
})
// Read
const vol = settings.values.volume
// Write (auto-saves)
settings.values.volume = 0.5Event-Driven Updates
import { ref, onMounted } from 'vue'
import { useEvents } from '@/services/events'
import { lua } from '@/bridge'
const data = ref(null)
const events = useEvents()
onMounted(() => {
// Request data
lua.myExtension.requestData()
// Listen for response
events.on('DataReceived', (received) => {
data.value = received
})
})Service Imports Summary
// From @/services
import { $translate, useLibStore, $content } from '@/services'
// From @/services/events
import { useEvents, useStreams } from '@/services/events'
// From @/services/popup
import { openMessage, openConfirmation, openPrompt, openProgress, addPopup } from '@/services/popup'
// From @/services/storage
import Storage from '@/services/storage'
// From @/services/uiApps
import { useUIApps } from '@/services/uiApps'
// From @/services/infoBar
import { useInfoBar } from '@/services/infoBar'
// From @/services/topBar
import { useTopBar } from '@/services/topBar'See Also
- Bridge API - Low-level event/stream handling
- Components - Bng* UI components
- Lua Integration - Full round-trip patterns
Lua ↔ UI Integration - Full Round-Trip Patterns
Complete patterns for bidirectional communication between Lua game engine and Vue UI.
Vue Modules - Full-Page Screens
Vue Modules are full-page screens like the career menu, garage, vehicle config, and bigmap. They use Vue Router for navigation and can contain complex multi-view layouts.