This article relates to the game "Dota 2". Click here for more information.
This article relates to the SDK/Workshop Tools for "Dota 2 Workshop Tools". Click here for more information.
This article's documentation is for Source 2. Click here for more information.

Javascript

From Valve Developer Community
Jump to navigation Jump to search

Panorama uses Javascript as a scripting language, allowing custom UI to dynamically respond to user input and game events. Javascript code executing on clients can also communicate with the custom game server code (implemented in Lua.)

Javascript API

The Panorama JS API is mostly a superset of the previous Scaleform ActionScript API.

It is documented here: Dota 2 Workshop_Tools/Panorama/Javascript/API Updated 22.04.2023 -> Without Panel's method Dota_2_Workshop_Tools/Panorama/Javascript/API2

Workflow

Hooking Up Scripts

Your Panorama XML can refer to JS files in a 'scripts' block, or include inline Javascript in a 'script' block (only suitable for very small amounts of code.)

<root>
	<scripts>
		<!--
			This is a reference to a script file located here:
			content/dota_addons/ADDON_NAME/scripts/custom_game/my_script_name.js
		-->
		<include src="file://{resources}/scripts/custom_game/my_script_name.js" />
	</scripts>
	<script>
		// This is an inline Javascript (XML experts: Panorama automatically wraps your script block inside a CDATA section.)
		$.Msg( "Hello, world!" );
	</script>
	<Panel>
		<!-- (Panel hierarchy goes here.) -->
	</Panel>
</root>

(The script above will be auto-compiled by the tools to this location: game/dota_addons/ADDON_NAME/scripts/custom_game/my_script_name.vjs_c or can be manually compiled using resourcecompiler.)

Reload Behavior

When a panel is reloaded, the associated javascript will usually also re-execute. This normally results in the desired behavior, but sometimes leads to confusion. When iterating on a complicated UI, it's usually worth the trouble to ensure that your script is robust to being reloaded. Callbacks registered with the system for things like game events will automatically be ignored when they go stale - it's safe to re-register. However, if you're dynamically creating panels, consider checking to see if they're already there for a previous reload. A bit of careful checking can go a long way to improving your iteration speed.

Panels

Creating Panels

A powerful way to reuse parts of your UI is to dynamically create panels. For example, the multiteam scoreboard creates a new child panel for each team, and in turn each team panel creates a new child panel for each player. This way the UI can dynamically adapt to any number of teams and players, with a single copy of XML/CSS/JS for each concept.

var parentPanel = $.GetContextPanel(); // the root panel of the current XML context
var newChildPanel = $.CreatePanel( "Panel", parentPanel, "ChildPanelID" );
newChildPanel.BLoadLayout( "file://{resources}/layout/custom_game/new_panel.xml", false, false );

Accessing CSS Properties From Javascript

Many CSS properties can be accessed from javascript via MyPanel.style.property. Due to syntax differences between the two languages their names are not identical to their CSS equivalent (this is the same on the web) but are easily deducible: Hyphen-delimited words are converted to camelCase equivalent. For example background-color in CSS is style.backgroundColor in Javascript.

$ (Dollar Sign)

Several important global routines are accessible through the $ global object


JQuery-Like Selection

As with JQuery, you can find a panel in the current context using $( "#PanelID" ). Note that this currently has significant limitations: it can only match a single panel by ID. If there is no matching panel it will return null instead of a empty selector, which can result in unexpected JS errors. (And failure of the rest of the script to execute.)

$( "#MyLabel" ).text = "hello";


Msg

For simple logging, use $.Msg() - it supports all Javascript types and any number of arguments. (It will print all the arguments consecutively on a single line.)

// This will print: Hello {"who":"world"}!
$.Msg( "Hello ", { "who": "world" }, "!" );


GetContextPanel

$.GetContextPanel() returns the root "context panel" that the script is running for. (Very similar to 'this' or 'self' in other languages.) This is the root panel of the XML that loaded the script.

<root>
	<script>
		$.GetContextPanel().SetHasClass( "context_panel", true ); // after this it will have both "context_panel" and "root_panel" classes
	</script>
	<Panel class="root_panel">
		<Label text="Hi" />
	</Panel>
</root>

Game APIs

Game Events

Javascript can register functions to get called when game events are fired. This works for both engine events and new Custom Game Events:

    function OnFoo( data ) { $.Msg( "foo_event: ", data ); }
    var handle = GameEvents.Subscribe( "foo_event_name", OnFoo );
    GameEvents.Unsubscribe( handle );

Custom Nettables

Custom nettables are a way to communicate persistent state from the server to clients. See: Dota 2 Workshop Tools/Custom Nettables

Best Practices and Javascript Gotchas

"use strict"

Our example Javascript code employs the "use strict" feature to increase safety, robustness, and sanity.

That Weird Anonymous Function Thing (IIFE)

In our example Javascript, you may see use of the Immediately-invoked Function Expression (IIFE) pattern.

(function () {
	$.Msg( "The panel associated with this javascript just got loaded or reloaded!" );
})();

Triple Equals

If you're familiar with C-family languages, you may be surprised to learn that Javascript has two equality operators: '==' and '==='

You can learn more about this here or elsewhere on the web.

Variable Capture

If you're coming to Javascript from another language, you should know that nested functions capture their enclosing scope by reference, which can lead to unexpected results:

var closures_bad = [];
var closures_good = [];
function create()
{
	for (var i = 0; i < 5; i++)
	{
		closures_bad[i] = function()
		{
			$.Msg( "i = ", i );
		};
		closures_good[i] = (function( j ){return function()
		{
			$.Msg( "i = ", j );
		}}(i));
	}
}

function run()
{
	// prints "5 5 5 5 5" because loop variable 'i' is captured by reference above
	for (var k = 0; k < 5; k++)
	{
		closures_bad[k]();
	}
	// prints "0 1 2 3 4" because each value of loop 'i' is captured by a different function parameter 'j'
	for (var k = 0; k < 5; k++)
	{
		closures_good[k]();
	}
}

create();
run();

Misc

Panorama Javascript execution is handled by the Google V8 javascript engine.
Learn JavaScript to make best use of this tool.