Skip to content

New SDK Preview

This section is a preview of the new SDK. The new SDK is still in development, and we are actively working on improving it. We value your feedback, so if you have any questions or suggestions, please reach out to us. If you are not using the new SDK, please refer to another documentation section.

Balancy UI Builder Documentation

Table of Contents

  1. Introduction
  2. UI Builder
  3. JavaScript API Reference
  4. Data Attributes System
  5. Events System
  6. Global Variables
  7. Common Patterns
  8. Battle Pass Integration

Introduction

The Balancy UI Builder allows developers to create custom user interfaces using HTML, CSS, and JavaScript. The system automatically injects a powerful JavaScript API that provides seamless integration with the Balancy backend, automatic element management, and cross-platform communication.

UI Builder

[This section will be populated with UI builder interface documentation, screenshots, and tutorials]

UI Builder is available for editing specific type of Assets called Views. To open UI Builder, go to Assets page and select View tab. The page will show Views you currently have in your project and Create button to create a new one. In the context menu of a View there is Open action, as it is shown here:

UI Builder opening

In general, UI Builder will look like this:

UI Builder main layout

It has main working area with the View's layout, tool panel on the right with basic elements, navigation panel with all the content of the View, and the tool panel on the top with additional controls.

What is View?

View is a specific type of asset combining HTML, CSS, and JavaScript code. Views are user interfaces that can be used to visualise game events and offers of any kind. Views are being opened in Webview provided and controlled by Balancy SDK.

Tools Panel

Located in the right side, it provides a collection of basic elements to use as building blocks for Views, allows to set up element's style, and to manage additional settings for elements, such as dynamic data or information to connect them to JavaScript.

All three Tabs are shown here:

UI Builder main layout

Basic blocks are:

  • Containers - many combinations, needed to build adaptive layouts
  • Images - static art assets, connected to Balancy resurces. Regular or 9-sliced
  • Texts - static, localized, and dynamic texts
  • Buttons - always 9-sliced images with an option to assign actions
  • Links - URLs to external resources
  • Video - for embedding videos

For selected element tool panel will show styling params to control position, layout constraints, size, typography, and additional effects.

For such elements like images and texts there are additional parameters on Settings tab, such as image source, text source, button action, string formatting, 9-slice image mapping, etc.

Hierarchy view

Located on the left side, navigation panel shows a tree view with all elements used in current View, providing the way to rearrange layout and select elements for editing. Elements can be moved between containers via simple drag-n-drop.

Top Panel

On the top panel there are following controls:

UI Builder main layout

In order from left to right:

  • Device screen size selector. Allows to select different screen aspect ratios typical for some popular devices.
  • Landscape toggle. Allows to switch the layout between portrait and landscape orientations.
  • Play UI button. Allows to quickly test changes by overriding the View currently loaded in Demo App (see below).
  • Save button. Nothing special, just saves changes.
  • View Components toggle. Allows to show/hide real boundaries of components, even invisible ones to check layouts.
  • View Code button. Allows to check HTML and CSS code generated during the editting. Read-only.
  • Global Script Editor button. Opens code editor for the global JavaScript code of the View. See below.
  • Add Fonts button. Opens the list of custom fonts, added to support proper visuals of the View. Works with fonts uploaded on Assets/Fonts tab.

Global Script

Global Script is the way to embed JavaScript code into View. It may be used for creating animations and for working with dynamic content of the View, that depends on the individual player progress. Examples: particle animations, moving elements, playing sounds, actual progress through Season Pass, ect.

Here is an example of clock animation for the timer of Season Pass View, provided by Balancy:

UI Builder main layout

See the full documentation on JS API below in this document.

Previewing in Demo App

It is possible to use built-in Demo App to check recent changes of the View in real time, without having to deploy changes in the regular way.

UI Builder main layout

To do so:

  • Have Game Event properly set up on the Dashboard, so the Demo App will be able to activate it for you based on Condition. You will need your View to be assigned to their respective entities - simple offer, group offer or game event itself.
  • While being inside UI Builder, open Demo App by clicking >> button on the top panel near the User Account icon.
  • Save changes made for the View by Save button.
  • Press Play UI button. It will load updated View into the Demo App bypassing Deploy process. It will show notification in case of success.
  • Open the entity of your View in Demo App UI.
  • When the View is opened this way, it will show Overridden View: v1 text at the bottom of it, so it will be clear that the content is actually taken directly from UI Builder, and not from the latest Deploy. Version number in vX will increase automatically after every use of Play UI button during current working session.

Using 9-slice Images

To use 9-slice images in your Views, you need to set them up as such in Assets/Images section first:

UI Builder main layout

Then you will add Image 9-slice or Button 9-slice element to your View. It will open assets selector to choose your image from the list:

UI Builder main layout

After that, specific parameters of your 9-slice will be applied to the element automatically.

UI Builder main layout

You can change the image later by pressing Select Image button in the Settings tab of Tool Panel to open assets selector again.

Using Localized Texts

To use Localized Text in your Views, you need to have it created in Localization section first. Then you will add Text element to your View and on the Settings tab of Tool Panel you will press Select Local String button.

UI Builder main layout

It will open Localized String selector:

UI Builder main layout

This way your Views will always be localized the same way as any other of your game configs.

Using Dynamic Texts

To use text elements that display dynamic information such as times, prices, titles, descriptions that would be taken from the Offers/Events assiciated with your View, set the type of Text element to Dynamic. After that, follow API documentation below, depending on what type of information you need to display.

Example of using dynamic text for offer duration timer:

UI Builder main layout

JavaScript API Reference

The Balancy framework automatically injects the balancy object into your JavaScript environment, providing access to backend data, user profiles, and platform-specific functionality.


balancy.findDomElement(dataId, parentElement?)

Finds an element with the specified data-id attribute.

Parameters:

  • dataId (string): The data-id value to search for
  • parentElement (HTMLElement, optional): Parent element to search within (defaults to document)

Returns: HTMLElement | null

Example:

const headerElement = balancy.findDomElement('window-header');
const button = balancy.findDomElement('claim-button', headerElement);

balancy.setImage(element, sprite)

Sets an image on an HTML element using a Balancy sprite object.

Parameters:

  • element (HTMLElement): Target element (IMG tag or element with background)
  • sprite (object): Sprite object with id property

Example:

const iconElement = document.getElementById('reward-icon');
balancy.setImage(iconElement, reward.item.unnyIcon);


balancy.getSystemProfileValue(path)

Retrieves a value from the system profile (UnnyProfile).

Parameters:

  • path (string): Dot-separated path to the desired value

Returns: Promise<any>

Example:

const playerLevel = await balancy.getSystemProfileValue('GeneralInfo.Level');
//playerLevel = 5

const generalInfo = await balancy.getSystemProfileValue('GeneralInfo');
//generalInfo example (Object)
// {
//     "level": 0,
//     "country": "CY",
//     "session": 68,
//     "customId": "Custom456",
//     "deviceId": "30438f52-d81e-4065-8607-298c98626ecc",
//     "platform": "Unknown",
//     "playTime": 18295,
//     "isNewUser": false,
//     "profileId": "528c0854-54d8-11f0-b905-1fec53a055ba",
//     "appVersion": "1.0.0",
//     "currentDay": 20297,
//     "deviceName": "",
//     "deviceType": 0,
//     "platformId": -1,
//     "deviceModel": "",
//     "dontDisturb": false,
//     "offlineTime": 1,
//     "tutorialStep": 0,
//     "engineVersion": "React_1.0",
//     "lastLoginTime": 1753712746,
//     "trafficSource": "",
//     "firstLoginTime": 1753186128,
//     "installVersion": "1.0.0",
//     "systemLanguage": "",
//     "operatingSystem": "",
//     "trafficCampaign": "",
//     "gameLocalization": "en",
//     "systemMemorySize": 0,
//     "timeSinceInstall": 526622,
//     "balancyPlatformId": 7,
//     "timeSincePurchase": 338642,
//     "operatingSystemFamily": 0
// }

balancy.getProfileValue(profile, path)

Retrieves a value from a specific profile.

Parameters:

  • profile (string): Profile name
  • path (string): Dot-separated path to the desired value

Returns: Promise

Example:

const value = await balancy.getProfileValue('PlayerData', 'Achievements.Completed');

balancy.getDocumentValue(id, depth)

Retrieves a document from Balancy with specified depth.

Parameters:

  • id (string): Document ID
  • depth (number): Depth of nested objects to fetch

Returns: Promise

Example:

const config = await balancy.getDocumentValue('245', 3);
console.info(config);


balancy.getLocalizedText(key)

Gets localized text for the specified key.

Parameters:

  • key (string): Localization key

Returns: Promise

Example:

const welcomeText = await balancy.getLocalizedText('welcome_message');
document.getElementById('title').textContent = welcomeText;

balancy.getImageUrl(id)

Gets the URL for an image asset.

Parameters:

  • id (string): Image asset ID

Returns: Promise

Example:

const imageUrl = await balancy.getImageUrl('reward-icon-123');
document.getElementById('icon').src = imageUrl;


balancy.closeView(source)

Closes the current view.

Parameters:

  • source (string): Reason for closing (for debugging)

Example:

balancy.closeView('Purchase completed');

balancy.sendIsReady()

While loading a page, Balancy automatically injects sprites and fonts, once everything is ready it shoots balancy-ready event. Right after this event Balance View notifies the SDK that it's ready and is being shown to the user. If you want to postpone and load any additional data, you can delay the ready signal:

Example:

function main() {
    balancy.delayIsReady();

    //some async logic...., then
    balancy.sendIsReady();
}
window.addEventListener('balancy-ready', main, { once: true });


balancy.getBattlePassConfig()

Gets the configuration for the current battle pass.

Returns: Promise<object> - Battle pass configuration

Example:

const config = await balancy.getBattlePassConfig();
console.log('Scores required:', config.scores);
console.log('Reward lines:', config.rewards);

Response Structure:

{
    unnyId: "995",
    name: "LIVEOPS TEMPLATES/KEY_SEASON_PASS_NAME",
    scores: [10, 20, 30, 40, 50, 60], // XP required for each level
    rewards: [
      {
        unnyId: "1007",
        name: "Free Track",
        rewards: [
          {
            unnyId: "1002",
            count: 1,
            item: {
              unnyId: "826",
              name: "Gems",
              unnyIcon: { id: "819", type: 1 }
            }
          }
          // ... more rewards
        ]
      }
      // ... more reward lines
    ]
}

balancy.getBattlePassProgress()

Gets the current player's battle pass progress.

Returns: Promise<object> - Battle pass progress

Example:

const progress = await balancy.getBattlePassProgress();
console.log('Current level:', progress.level);
console.log('Current XP:', progress.scores);

Response Structure:

{
  level: 1,           // Current level (0-based)
  scores: 15,         // Current XP/scores
  finished: false,    // Whether battle pass is completed
  progressInfo: [
    {
      progress: [1, 0, 0, 0, 0, 0], // Claim status for each level (0=locked, 1=available, 2=claimed)
      available: true,               // Whether this reward line is available
      unnyIdReward: "1007"          // Reward line ID
    }
    // ... more reward lines
  ]
}

balancy.claimBattlePassReward(lineId, index)

Claims a battle pass reward.

Parameters:

  • lineId (string): Reward line ID
  • index (number): Level index to claim

Returns: Promise<boolean> - Success status

Example:

const success = await balancy.claimBattlePassReward('1007', 0);
if (success) {
  console.log('Reward claimed successfully!');
}


balancy.canBuyGroupOffer(index)

Checks if a group offer can be purchased.

Parameters:

  • index (number): Offer index

Returns: Promise<boolean>

Example:

const canBuy = await balancy.canBuyGroupOffer(0);
document.getElementById('buy-button').disabled = !canBuy;


balancy.formatTime(seconds)

Formats time duration into a human-readable string.

Parameters:

  • seconds (number): Duration in seconds

Returns: string

Example:

const timeLeft = balancy.getTimeLeft();
const formatted = balancy.formatTime(timeLeft);
// Returns: "2d 5h" or "01:23:45"

balancy.getTimeLeft()

Gets remaining time based on balancySettings.

Returns: number - Seconds remaining

Example:

const timeLeft = balancy.getTimeLeft();
document.getElementById('timer').textContent = balancy.formatTime(timeLeft);

balancy.formatDataTemplate(formatStr, data)

Formats a template string with data values.

Parameters:

  • formatStr (string): Template string with {path} placeholders
  • data (object): Data object

Returns: string

Example:

const template = "You have {currency.coins} coins and {level} level";
const data = { currency: { coins: 100 }, level: 5 };
const result = balancy.formatDataTemplate(template, data);
// Returns: "You have 100 coins and 5 level"


Constants and Enums

RequestAction

const RequestAction = {
  None: 0,
  GetProfile: 1,
  SetProfile: 2,
  GetDocument: 3,
  GetLocalization: 10,
  GetImageUrl: 11,
  GetInfo: 12,
  CanBuyGroupOffer: 13,
  GetBattlePassProgress: 20,
  BuyOffer: 101,
  BuyGroupOffer: 102,
  BuyShopSlot: 103,
  BattlePassClaim: 104,
  CloseWindow: 200,
  BalancyIsReady: 201,
  CustomMessage: 1000
};

InfoType

const InfoType = {
  None: 0,
  OfferPrice: 1,
  OfferGroupPrice: 2,
  TimeLeft: 3,
  CustomPrice: 9,
  Custom: 10
};

BattlePassStatus

const BattlePassStatus = {
  NotAvailable: 0,
  Available: 1,
  Claimed: 2
};

Data Attributes System

Balancy automatically processes HTML elements with special data attributes, providing declarative functionality without JavaScript code.

Button Actions

Add data-button-action to make any element interactive:

<!-- Buy offer button -->
<button data-button-action="101" data-button-params='{"productId": "coin_pack_1"}'>
  Buy Coins
</button>

<!-- Buy group offer -->
<button data-button-action="102" data-index="0">
  Buy Special Pack
</button>

<!-- Claim battle pass reward -->
<button data-button-action="104" data-index="2">
  Claim Reward
</button>

Available Attributes:

  • data-button-action: Action ID from RequestAction enum
  • data-button-params: JSON string with additional parameters
  • data-index: Index parameter for certain actions

Button Events:

  • balancyButtonResponse: Fired when action completes
  • balancyButtonError: Fired on error

Example Event Handling:

button.addEventListener('balancyButtonResponse', (event) => {
  const { success, result, actionId } = event.detail;
  if (success) {
    console.log('Action completed:', result);
  }
});

Dynamic Text Updates

Elements with data-text-type="dynamic" automatically update with backend data:

<!-- Show offer price -->
<span data-text-type="dynamic" 
      data-info-type="1" 
      data-text-format="{price} USD">
</span>

<!-- Show time remaining -->
<span data-text-type="dynamic" 
      data-info-type="3" 
      data-text-format="Time left: {time}">
</span>

<!-- Custom data -->
<span data-text-type="dynamic" 
      data-info-type="10" 
      data-custom="player_stats"
      data-text-format="Level {level} - {xp} XP">
</span>

Available Attributes:

  • data-text-type="dynamic": Marks element for dynamic updates
  • data-info-type: Type of information to fetch (InfoType enum)
  • data-text-format: Template string for formatting
  • data-custom: Custom parameter for InfoType.Custom
  • data-product-id: Product ID for price queries
  • data-index: Index for group offers

Automatic Localization

Elements with data-text-type="localized" automatically get localized:

<h1 data-text-type="localized" data-localization-key="welcome_title">
  <!-- Will be replaced with localized text -->
</h1>

<button data-text-type="localized" data-localization-key="buy_button">
  Buy Now
</button>

Automatic Image Loading

Elements with data-image-id automatically load images:

<!-- For IMG elements -->
<img data-image-id="icon_123" alt="Reward Icon">

<!-- For background images -->
<div data-image-id="background_456" class="hero-section"></div>

Custom Font Injection

Elements with font data attributes automatically load custom fonts:

<div data-font-id="font_789" data-font-name="CustomFont" class="styled-text">
  This text uses a custom font
</div>

Element Identification

Use data-id for element identification:

<div data-id="player-info">
  <span data-id="player-name">Player Name</span>
  <span data-id="player-level">Level 1</span>
</div>

<script>
const playerInfo = balancy.findDomElement('player-info');
const nameElement = balancy.findDomElement('player-name', playerInfo);
</script>

Events System

Lifecycle Events

The framework dispatches these events during initialization:

  • balancy-buttons-complete: All buttons are processed
  • balancy-localization-complete: All text is localized
  • balancy-text-complete: All dynamic text is updated
  • balancy-images-complete: All images are loaded
  • balancy-fonts-complete: All fonts are loaded
  • balancy-ready: Everything is ready (dispatched after all above)

Example:

document.addEventListener('balancy-ready', () => {
  console.log('Balancy UI is fully initialized');
  // Your custom initialization code here
});

window.addEventListener('balancy-ready', main, { once: true });

Button Interaction Events

balancyButtonResponse Event:

button.addEventListener('balancyButtonResponse', (event) => {
  const { actionId, result, success, senderId } = event.detail;
  if (success) {
    console.log(`Action ${actionId} succeeded:`, result);
  } else {
    console.log(`Action ${actionId} failed:`, event.detail.error);
  }
});

balancyButtonError Event:

button.addEventListener('balancyButtonError', (event) => {
  const { actionId, error, paramsAttr } = event.detail;
  console.error(`Button error: ${error}`);
});

Backend Notifications

The framework automatically handles notifications from the backend:

// This is handled automatically, but you can override:
balancy.notificationReceived = function(data) {
  console.log('Notification received:', data);

  switch (data.type) {
    case 101: // OnOfferDeactivated
      if (window.balancyViewOwner.instanceId === data.id) {
        balancy.closeView('Offer expired');
      }
      break;
    case 122: // OnOfferGroupWasPurchased
      // Refresh UI state
      updateOfferAvailability();
      break;
  }
};

Global Variables

window.balancyViewOwner

Contains context information about the current view:

{
  instanceId: "offer_123",      // Current offer/event instance ID
  unnyIdGameEvent: "663",       // Game event ID (for battle pass, etc.)
  productId: "coin_pack_small"  // Product ID for purchases
}

window.balancySettings

Contains view settings and timing information:

{
  launchTime: 1751328000,  // Unix timestamp when view was launched
  secondsLeft: 86400       // Seconds remaining for time-limited content
}

Custom Function Overrides

You can override certain behaviors:

// Custom time formatting
window.customFormatTime = function(seconds) {
  if (seconds < 60) return `${seconds}s`;
  if (seconds < 3600) return `${Math.floor(seconds/60)}m`;
  return `${Math.floor(seconds/3600)}h`;
};

Common Patterns

Loading and Displaying Data

async function loadPlayerInfo() {
  try {
    const level = await balancy.getSystemProfileValue('GeneralInfo.Level');
    const coins = await balancy.getProfileValue('Profile', 'Currency.Coins');
    const playerName = await balancy.getProfileValue('Profile', 'GeneralInfo.Name');

    document.getElementById('level').textContent = level;
    document.getElementById('coins').textContent = coins;
    document.getElementById('name').textContent = playerName;
  } catch (error) {
    console.error('Failed to load player info:', error);
  }
}

Creating Interactive Buttons

function createOfferButton(offer) {
  const button = document.createElement('button');
  button.setAttribute('data-button-action', '101'); // BuyOffer
  button.setAttribute('data-button-params', JSON.stringify({
    productId: offer.productId,
    currency: 'USD'
  }));

  button.addEventListener('balancyButtonResponse', (event) => {
    if (event.detail.success) {
      showPurchaseSuccess(offer);
    } else {
      showPurchaseError(event.detail.error);
    }
  });

  return button;
}

Timer Implementation

function startTimer() {
  function updateTimer() {
    const timeLeft = balancy.getTimeLeft();
    const formatted = balancy.formatTime(timeLeft);
    document.getElementById('timer').textContent = formatted;

    if (timeLeft <= 0) {
      balancy.closeView('Time expired');
      return;
    }
  }

  updateTimer();
  setInterval(updateTimer, 1000);
}

Handling Notifications

// Override notification handler for custom behavior
const originalNotificationHandler = balancy.notificationReceived;
balancy.notificationReceived = function(data) {
  // Call original handler
  originalNotificationHandler.call(this, data);

  // Add custom logic
  if (data.type === 122) { // OnOfferGroupWasPurchased
    showCelebrationAnimation();
    refreshOfferList();
  }
};

Working with Images and Localization

async function setupRewardDisplay(reward) {
  const container = document.getElementById('reward-container');

  // Create image element
  const img = document.createElement('img');
  balancy.setImage(img, reward.item.unnyIcon);

  // Create title with localization
  const title = document.createElement('h3');
  const localizedName = await balancy.getLocalizedText(reward.item.name);
  title.textContent = localizedName;

  // Create count display
  const count = document.createElement('span');
  count.textContent = `x${reward.count}`;

  container.appendChild(img);
  container.appendChild(title);
  container.appendChild(count);
}

Battle Pass Integration

The Battle Pass is a complete example of advanced Balancy integration. Here's a simplified version showing key concepts:

Basic Setup

async function initializeBattlePass() {
  // Delay automatic ready signal
  balancy.delayIsReady();

  try {
    // Load configuration and progress
    const config = await balancy.getBattlePassConfig();
    const progress = await balancy.getBattlePassProgress();

    // Setup UI
    setupHeader(progress, config.scores);
    setupRewardTrack(config.rewards, progress);
    setupClaimButtons(config.rewards, progress);

    // Notify ready
    balancy.sendIsReady();
  } catch (error) {
    console.error('Failed to initialize battle pass:', error);
  }
}

window.addEventListener('balancy-ready', initializeBattlePass, { once: true });

Progress Display

function updateProgressDisplay(progress, scores) {
  const currentLevel = progress.level;
  const currentScores = progress.scores;
  const maxScores = scores[currentLevel] || scores[scores.length - 1];
  const progressPercent = (currentScores / maxScores) * 100;

  document.getElementById('level').textContent = currentLevel + 1;
  document.getElementById('progress').style.width = `${progressPercent}%`;
  document.getElementById('scores').textContent = `${currentScores} / ${maxScores}`;
}

Reward Claiming

function setupClaimButton(lineId, levelIndex, reward, status) {
  const button = document.getElementById(`claim-${lineId}-${levelIndex}`);

  if (status === 1) { // Available
    button.style.display = 'block';
    button.onclick = async () => {
      const success = await balancy.claimBattlePassReward(lineId, levelIndex);
      if (success) {
        // Show reward animation
        showRewardAnimation(button, reward);
        // Update button state
        setupClaimButton(lineId, levelIndex, reward, 2); // Claimed
      }
    };
  } else {
    button.style.display = 'none';
  }
}

Animation System

function showRewardAnimation(sourceElement, reward) {
  const flyingDiv = document.createElement('div');
  flyingDiv.className = 'flying-reward';

  // Position at source element
  const rect = sourceElement.getBoundingClientRect();
  flyingDiv.style.left = rect.left + 'px';
  flyingDiv.style.top = rect.top + 'px';

  // Add reward icon
  const icon = document.createElement('img');
  balancy.setImage(icon, reward.item.unnyIcon);
  flyingDiv.appendChild(icon);

  // Add to page and animate
  document.body.appendChild(flyingDiv);

  // Animate upward and fade out
  flyingDiv.animate([
    { transform: 'translateY(0px)', opacity: 1 },
    { transform: 'translateY(-100px)', opacity: 0 }
  ], {
    duration: 2000,
    easing: 'ease-out'
  }).addEventListener('finish', () => {
    flyingDiv.remove();
  });
}

This documentation provides a comprehensive guide to the Balancy UI Builder's JavaScript capabilities. The system is designed to be both powerful for complex implementations and simple for basic use cases through its declarative data attribute system.