Balancy UI Builder Documentation¶
Table of Contents¶
- Introduction
- UI Builder
- JavaScript API Reference
- Data Attributes System
- Events System
- Global Variables
- Common Patterns
- 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¶
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:
In general, UI Builder will look like this:
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:
Basic blocks are:
Containers
- many combinations, needed to build adaptive layoutsImages
- static art assets, connected to Balancy resurces. Regular or 9-slicedTexts
- static, localized, and dynamic textsButtons
- always 9-sliced images with an option to assign actions
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:
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.
- Copy / Paste buttons. Allows to copy-paste elements selected in navigation, even between different Views.
- 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.
Script Editor has Find function (Ctrl-F / Cmd-F) and Replace function (Ctrl-H / Cmd-H).
Here is an example of clock animation for the timer of Season Pass View, provided by Balancy:
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.
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:
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:
After that, specific parameters of your 9-slice will be applied to the element automatically.
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.
It will open Localized String selector:
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:
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 forparentElement
(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 namepath
(string): Dot-separated path to the desired value
Returns: Promise<any>
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 IDdepth
(number): Depth of nested objects to fetch
Returns: Promise<object>
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<string>
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('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 IDindex
(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. This powerful method supports nested object access and automatic document resolution using unnyId references.
Parameters:
formatStr
(string): Template string with {path} placeholdersdata
(object): Data object
Returns: Promise
Example - Basic Usage:
const template = "You have {currency.coins} coins and {level} level";
const data = { currency: { coins: 100 }, level: 5 };
const result = await balancy.formatDataTemplate(template, data);
// Returns: "You have 100 coins and 5 level"
Example - UnnyId Resolution:
// If data.gameOffer is null but data.unnyIdGameOffer exists,
// the method will automatically fetch the document
const template = "{gameOffer.name}";
const data = {
gameOffer: null,
unnyIdGameOffer: "1188"
};
const result = await balancy.formatDataTemplate(template, data);
// Returns: "DEFAULT/offer_name_key"
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,
OwnerLocalizedKey: 4,
OwnerCustom: 5,
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 enumdata-button-params
: JSON string with additional parametersdata-index
: Index parameter for certain actions
Button Events:
balancyButtonResponse
: Fired when action completesbalancyButtonError
: 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>
<!-- Owner custom data formatting -->
<span data-text-type="dynamic"
data-info-type="5"
data-text-format="{discount}% OFF - ${priceUSD}">
</span>
<!-- Owner localized key -->
<span data-text-type="dynamic"
data-info-type="4"
data-text-format="{gameOffer.name}">
</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 updatesdata-info-type
: Type of information to fetch (InfoType enum)data-text-format
: Template string for formattingdata-custom
: Custom parameter for InfoType.Customdata-product-id
: Product ID for price queriesdata-index
: Index for group offers
InfoType Values:
1
(OfferPrice): Display offer price2
(OfferGroupPrice): Display group offer price3
(TimeLeft): Display time remaining (automatically formatted)4
(OwnerLocalizedKey): Format template using owner data, then use result as localization key5
(OwnerCustom): Format template using owner data directly9
(CustomPrice): Display custom price information10
(Custom): Display custom data with specified parameter
Advanced InfoTypes: OwnerLocalizedKey and OwnerCustom¶
These new InfoTypes work with the current view's owner data (window.balancyViewOwner
) and support automatic document resolution:
InfoType.OwnerLocalizedKey (4): Formats the template using owner data, then uses the result as a localization key.
<!-- If owner has gameOffer.name = "OFFER_SUPER_PACK_NAME" -->
<span data-text-type="dynamic"
data-info-type="4"
data-text-format="{gameOffer.name}">
</span>
<!-- Will display the localized text for key "OFFER_SUPER_PACK_NAME" -->
InfoType.OwnerCustom (5): Formats the template using owner data directly for display.
<!-- Direct formatting with owner data (avoid using localized keys like gameOffer.name) -->
<span data-text-type="dynamic"
data-info-type="5"
data-text-format="{discount}% OFF - {priceUSD} USD">
</span>
<!-- Will display: "25% OFF - 4.99 USD" -->
Owner Data Structure Example:
window.balancyViewOwner = {
instanceId: "f09ab140-3752-4593-98e3-48a31046",
gameOffer: null, // Will be resolved automatically
unnyIdGameOffer: "1188", // Used for document resolution
discount: 25,
priceUSD: 4.99,
status: 1
// ... other fields
};
Automatic Document Resolution: Both InfoTypes automatically resolve null references using unnyId fields:
- If
{gameOffer.name}
is accessed butgameOffer
is null - The system looks for
unnyIdGameOffer
in the same object - Fetches the document using
balancy.getDocumentValue()
- Continues with the remaining path (
name
in this case)
This works recursively for nested paths like {gameOffer.storeItem.name}
.
Important Usage Guidelines:
- Use InfoType.OwnerLocalizedKey (4) when you need to display localized text. Fields like
gameOffer.name
contain localization keys, not display text. - Use InfoType.OwnerCustom (5) for non-localized data like numbers, counts, prices, and other raw values.
- Avoid mixing localization keys with raw data in InfoType.OwnerCustom - if you need
{gameOffer.name}
as display text, use InfoType.OwnerLocalizedKey instead.
Correct Pattern:
<!-- For localized title -->
<h1 data-text-type="dynamic" data-info-type="4" data-text-format="{gameOffer.name}"></h1>
<!-- For price and discount -->
<span data-text-type="dynamic" data-info-type="5" data-text-format="{discount}% OFF"></span>
<span data-text-type="dynamic" data-info-type="5" data-text-format="${priceUSD}"></span>
Incorrect Pattern:
<!-- DON'T do this - gameOffer.name is a localization key, not display text -->
<span data-text-type="dynamic" data-info-type="5" data-text-format="{gameOffer.name} - {discount}% OFF"></span>
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 processedbalancy-localization-complete
: All text is localizedbalancy-text-complete
: All dynamic text is updatedbalancy-images-complete
: All images are loadedbalancy-fonts-complete
: All fonts are loadedbalancy-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();
});
}
Advanced Usage Examples¶
Event Management in Practice¶
// Example: Managing event state and progress
async function initializeEvent() {
// Load event configuration and custom data
const eventConfig = await balancy.getDocumentValue(window.balancyViewOwner.unnyIdGameEvent, 3);
const customEventInfo = await balancy.getCustomEventInfo();
const tasks = await balancy.getTasks();
// Initialize based on current state
if (!customEventInfo.started) {
await startNewEvent(eventConfig);
} else {
continueEvent(customEventInfo, tasks);
}
}
async function startNewEvent(eventConfig) {
// Save custom event data
const customData = {
started: true,
startTime: Math.floor(Date.now() / 1000),
progress: {}
};
await balancy.setCustomEventInfo(customData);
// Activate event tasks
const activeTasks = await balancy.activateTasks([eventConfig.task.unnyId]);
console.log('Event started with tasks:', activeTasks);
}
async function completeEventTask(taskId) {
const success = await balancy.claimTaskReward(taskId);
if (success) {
// Award inventory items
await balancy.addInventoryItems('reward_coins', 100);
console.log('Task completed and reward claimed');
}
}
Group Offers and Purchasing¶
// Example: Chain deals implementation
async function setupChainOffers() {
const offerId = window.balancyViewOwner.unnyIdGameOfferGroup;
const offerGroup = await balancy.getDocumentValue(offerId, 3);
const purchasedCount = window.balancyViewOwner.purchasedItems.length;
// Check which offers are available
for (let i = 0; i < offerGroup.storeItems.length; i++) {
const canPurchase = await balancy.canBuyGroupOffer(i);
updateOfferButton(i, canPurchase);
}
}
async function purchaseOffer(index) {
try {
const result = await balancy.buyGroupOffer(index);
console.log('Purchase successful:', result);
// Update UI after purchase
await setupChainOffers();
} catch (error) {
console.error('Purchase failed:', error);
}
}
// Get product pricing information
async function displayOfferPrice(productId) {
const productInfo = await balancy.getProductInfo(productId);
document.getElementById('price').textContent = productInfo.LocalizedPriceString;
}
Inventory and Rewards Management¶
// Example: Managing player inventory
async function checkPlayerInventory() {
const coinCount = await balancy.getInventoryItemsCount('coins');
const gemCount = await balancy.getInventoryItemsCount('gems');
console.log(`Player has ${coinCount} coins and ${gemCount} gems`);
}
async function awardEventReward(itemId, amount) {
await balancy.addInventoryItems(itemId, amount);
// Show reward notification
showRewardNotification(itemId, amount);
}
async function spendCurrency(itemId, amount) {
const currentCount = await balancy.getInventoryItemsCount(itemId);
if (currentCount >= amount) {
await balancy.removeInventoryItems(itemId, amount);
return true;
}
return false;
}
Working with Item Display Formatting¶
// Example: Properly format item counts for display
function displayReward(reward) {
const format = balancy.formatItemsCount(reward, true);
const countElement = document.getElementById('reward-count');
countElement.textContent = format.text;
// Apply appropriate styling
if (format.isCurrency) {
countElement.classList.add('currency-style');
} else if (format.isDuration) {
countElement.classList.add('duration-style');
}
}
// Format large numbers for display
function displayPlayerStats(xp, coins) {
document.getElementById('xp').textContent = balancy.shortenCount(xp);
document.getElementById('coins').textContent = balancy.shortenCount(coins);
}
// Template string formatting
function showMessage(template, ...values) {
const message = balancy.formatString(template, ...values);
document.getElementById('message').textContent = message;
}
// Usage: showMessage('You earned {0} coins and {1} XP!', 500, 150);
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.