Coherent GT  1.10.0
A modern user interface library for games
Binding for C++

Coherent GT allows easy communication between your application and the UI via several mechanisms.

Events

C++ triggering JavaScript

First the JavaScript code has to attach a handler for the event using engine.on:

engine.on('PlayerScoreChanged', function (new_score) {
var scoreDisplay = document.getElementById('scoreDisplay');
scoreDisplay.innerHTML = new_score;
});

Then we trigger the event in C++, using the Coherent::UIGT::View::TrigerEvent method:

// set player score to 10000
view->TriggerEvent('PlayerScoreChanged', 10000);

There are also global functions in the Coherent::UIGT namespace which can be used such as:

Coherent::UIGT::TriggerEvent(view, 'ScoreChanged', 10000);

Every TriggerEvent call starts with a C++ macro:

COHERENTGT_TRIGGER_EVENT_SCOPE(TriggerEvent_N_Params, RuntimeName);

By default this macro does nothing and is compiled out. You can, however, add custom code there by defining COHERENTGT_TRIGGER_EVENT_SCOPE before it's used in View.h. For example, you can add some profiling code such as:

#define COHERENTGT_TRIGGER_EVENT_SCOPE(X, RuntimeName) \
struct GeneratedClass_##X { \
GeneratedClass_##X(const char* name) \
: EventName(name) { \
/* Start profiling */ \
} \
~GeneratedClass_##X() { \
/* End profiling */ \
} \
const char* EventName; \
} __instance(RuntimeName);
Warning
The first argument of the macro is a compile time token and can be used for creating new tokens during compilation. The second argument is a runtime string literal and is valid within the trigger event scope. If you try using it as a compile-time token you'll always get "name".

Note that in order for the binding to work you need to include the coherent.js library in the head section of your HTML page.

See also
For more information about engine, see JavaScript.

JavaScript triggering C++

Registering C++ functions for events triggered by JavaScript should happend the in the handler of Coherent::UIGT::ViewListener::OnReadyForBindings event.

Event Handlers

Event handlers are registered with the Coherent::UI::View::RegisterForEvent method. They cannot return any value to JavaScript, but may trigger an event for JavaScript. There may be more than one C++ handler for a given event. There also may be both C++ and JavaScript handlers for the same event.

Warning
There is no guarantee for the order of execution of C++ event handlers.
class Game
{
public:
void Quit()
{
}
} g_Game;
class GameViewListener : public Coherent::UI::ViewListener
{
public:
virtual void OnReadyForBindings()
{
m_View->RegisterForEvent("OnQuitClicked",
Coherent::UI::MakeHandler(&g_Game, &Game::Quit));
}
};

Triggering the event from JavaScript looks like:

engine.on('OnQuitClicked', function () {
ShowMessage('Bye');
});
// using jQuery to simplify the sample
$('#QuitButton').click(function () {
// this will execute both Game::Quit in C++
// and ShowMessage('Bye') in JavaScript
engine.trigger('OnQuitClicked');
});

Coherent::UIGT::MakeHandler uses template argument deduction to guess the signature of the handler. This requires several specializations of Coherent::UIGT::FunctorTraits and overloads of Coherent::UIGT::EventHandler::InvokeStub. To extend the number of arguments for event handlers supported by Coherent GT, you have to add additional specializations and overloads.

Call Handlers

Call handlers are registered with the Coherent::UI::View::BindCall method. There may be only one handler for a given call and the handler may return a result.

class Game
{
public:
std::string GetPlayerName()
{
return m_PlayerName;
}
} g_Game;
class GameViewListener : public Coherent::UI::ViewListener
{
public:
virtual void OnReadyForBindings()
{
m_View->BindCall("getPlayerName", Coherent::UI::MakeHandler(&g_Game,
&Game::GetPlayerName));
}
};

To get the player name in the view is:

// call 'Game::GetPlayerName' with callback for the result
engine.call('getPlayerName').then(function (name) {
var playerName = document.getElementById('playerName');
playerName.innerHTML = name;
});

Exposing C++ Types

To be able to use your C++ types as arguments or results for event and call handlers, the C++ type must be exposed to Coherent GT. To expose a C++ type to Coherent GT use the following pattern:

class Player
{
public:
std::string Name;
double GetScore() const;
void SetScore(double);
};
// called by Coherent GT when an instance of Player goes through
// the C++ / JavaScript boundary
void CoherentBind(Coherent::UIGT::Binder* binder, Player* player)
{
if (auto type = binder->RegisterType("Player", player))
{
type.Property("name", &Player::Name)
.Property("score", &Player::GetScore, &Player::SetScore)
;
}
}
Note
When exposing custom properties with the ".Property" syntax, a string literal must be provided. The string literal should "live" at least until the OnBindingsReleased View callback is called.

The CoherentBind overload must be visible in all places where a Player instance is exposed to JavaScript, i.e. in any of the following cases:

  • a Player instance is used as an argument to TriggerEvent
  • a C++ handler takes a Player as an argument
  • a C++ handler returns a Player to JavaScript

In case the CoherentBind overload for the Player class is not visible, you will get the following compilation error:

include\coherent\uigt\binding\binding.h(57): error C2338: Coherent::UIGT::Overload_CoherentBind_For_Your_Type<T*>
1>          include\coherent\uigt\binding\binding.h(191) : see reference to function template instantiation 'void CoherentBind<T>(Coherent::UIGT::Binder *,T *)' being compiled
1>          with
1>          [
1>              T=Player
1>          ]

Going down the template instantiation stack, you can find where you are using Player without the CoherentBind overload visible.

You can define the CoherentBind overload for Player in any C++ source (.cpp) file, but you'll need to have a declaration for it, that is included everywhere Player is exposed to Coherent GT.

Depending on the structure of your project, you may consider the following patterns:

  • If Player is dedicated only for the UI - then add the declaration of CoherentBind for Player in the Player header. This way the overload will be visible everywhere Player is visible.
  • If Player is generic game type - add a PlayerBinding.h or a MySubsystemBindings.h header with the declaration of CoherentBind for Player (or for all the types in the particular game subsystem). After that make sure to include the header where Player is used with Coherent GT.
Note
If you are using namespaces, then the CoherentBind overload for Player has to be either in the namespace of Player or in the Coherent::UIGT namespace. This way it will be found using argument dependent lookup.
Warning
You have to call Coherent::UIGT::View::TriggerEvent with a valid instance of your class. Coherent GT uses this instance to cache the exposed properties and in certain cases might need to use this instance. For example in the case of virtual getters or setters, or virtual inheritance, the instance pointer might need to be adjusted before calling a getter or a setter for a property and getting this adjustment is using the virtual table pointer.

STL and container types

Coherent GT has built-in support for most STL containers and std:: classes.

C++ Type JavaScript Type Header
std::string String <Coherent\UIGT\Binding\String.h>
std::vector Array <Coherent\UIGT\Binding\Vector.h>
std::map Object <Coherent\UIGT\Binding\Map.h>
std::pair Object <Coherent\UIGT\Binding\Pair.h>
C style array Array <Coherent\UIGT\Binding\Array.h>

Support for additional containers can be added in a similar way.

C++ and JavaScript Communication

Registering type Player triggering events in both directions with instances of Player as argument and as a result of a call handler.

class Game
{
public:
void Start()
{
m_View->TriggerEvent("StartWithPlayer", m_Player);
}
private:
Player m_Player;
} g_Game;

Then in JavaScript, we receive an object with the specified properties. The value of each property is the same as in the moment of triggering the event. The JavaScript callback may store a reference to the object, but its properties WILL NOT be synchronised with the actual g_Game.m_Player in the game.

engine.on('StartWithPlayer', function (player) {
var playerName = document.getElementById('playerName');
playerName.innerHTML = player.name;
var scoreDisplay = document.getElementById('scoreDisplay');
scoreDisplay.innerHTML = player.score;
});

If you want to call C++ handler with an instance of Player created in JavaScript there is one important detail - the object must have a property __Type with value Player (the same name of the type we gave to Coherent::UIGT::Binder::RegisterType in CoherentBind for Player. Otherwise Coherent GT cannot treat the object as an instance of Player.

$('#doCreatePlayer').click(function () {
var player = {
__Type: 'Player', // set the type name
name: $('#playerName').val(),
score: 0
};
engine.call('CreatePlayer', player).then(function (success) {
if (success) {
ShowMessage('Welcome ' + player.name);
} else {
ShowMessage('Sorry, try another name');
}
});
});
bool CreatePlayer(const Player& player)
{
if (player.Name.find(' ') == std::string::npos)
{
// create the player
return true;
}
else
{
// sorry, no spaces in player name
return false;
}
}

For some calls it's possible that there is no meaningful value to return. For example -

Item Player::GetItem(int slot)
{
if (HasItemAt(slot))
{
return GetItemAt(slot);
}
else
{
// notify the view that there is not item at this slot
m_View->SetScriptError(Coherent::UI::SCE_NoResult, "no item at slot");
return Item();
}
}
engine.call('GetItem', slot).then(function (item) {
ShowMessage('Item at slot ' + slot + ' costs ' + item.Price);
},
// called when there is no item at this slot
function (errorType, message) {
console.log('could not get item at slot ' + slot);
}
});

Exporting C++ objects by reference

Coherent GT supports exporting C++ objects by reference. This avoids copying the C++ values to the JavaScript heap and makes the communication between the game and the UI faster. However the user is responsible for keeping the exposed C++ object pointers valid while they are exposed to JavaScript.

Warning
Destroying or moving a C++ object exposed by reference to another location, without notifying the JavaScript universe will cause undefined behaviour when the variable is used by JavaScript.

The API to expose a C++ object by reference is the same as normal values. The difference is that to expose a reference the object has to be wrapped in a Coherent::UIGT::ByRef holder.

Once an object is exposed by reference to JavaScript, any subsequent TriggerEvent calls using ByRef with the same object will result in the same JavaScript variable sent to JavaScript. This allows for 1-to-1 mapping between the C++ and JavaScript objects.

When an exposed object is about to be destroyed (or moved) its reference must be removed from JavaScript to avoid using invalid pointers (use-after-free). To notify JavaScript that a referenced object is no longer available call the Coherent::UIGT::View::RemoveExposedInstance method with the address of the object being destroyed. It will remove the reference from the JavaScript values and make any attempt to access the properties of the object throw an exception in JavaScript. Also if exposed array by ref you need to call Coherent::UIGT::View::RemoveExposedArray instead.

Note
When a complex object is exposed by references, all of its subobjects are also exposed by reference. This means that the JavaScript should avoid keeping references to them.
Warning
When running Coherent GT in asynchronous mode, the objects expored by reference are accessed on the UI thread. Also the Coherent::UIGT::View::RemoveExposedInstance method is asynchronous, so the JavaScript code should have stopped using the C++ object, before it can be safely removed.

Here is a example usage of exposing objects by reference.

struct Nameplate
{
float X;
float Y;
float Health;
};
// the same API for exposing the Nameplate struct
//
void CoherentBind(Coherent::UIGT::Binder* binder, Nameplate* nameplate)
{
if (auto type = binder->RegisterType("nameplate", nameplate))
{
type.Property("x", &Nameplate::X)
.Property("y", &Nameplate::Y)
.Property("health", &Nameplate::Health)
;
}
}
class NameplateManager
{
public:
void CreateNameplates(unsigned count)
{
// Create and fill nameplates
// ...
for (auto i = 0; i < count; ++i)
{
// Send a reference to the nameplate to JavaScript
m_View->TriggerEvent('nameplates.create',
Coherent::UIGT::ByRef(&m_Nameplates[i]));
}
}
void DestroyNameplates()
{
for (auto& nameplate: m_Nameplates)
{
// Notify the JavaScript universe that this reference is no
// longer valid. All accessses to the variable will throw
// exception in JavaScript.
m_View->RemoveExposedInstance(&nameplate);
}
}
// The game requires to modify a nameplate, so we store it in the
// modified list in order to update the UI later
Nameplate& ModifyNameplate(unsigned id)
{
auto& nameplate = m_Nameplates[id];
m_Modified.push_back(&nameplate);
return nameplate;
}
// Update the modified nameplates since the last update
// Again expose the nameplates by reference. This way the JavaScript
// will receive the very same JavaScript object as the one created.
void Update()
{
for (auto modified : m_Modified)
{
m_View->TriggerEvent('nameplates.update',
cohml::ByRef(modified));
}
m_Modified.clear();
}
private:
// Beware that the vector may grow and relocate the nameplates.
// If this happens all the exposed references must be destroyed and
// recreated.
std::vector<Nameplate> m_Nameplates;
std::vector<Nameplate*> m_Modified;
};

To take advantage of this C++ classes the JavaScript side can do:

// this will point the exposed by C++ nameplate object
function updateNameplate() {
this.position.left = this.X + 'vw'; // will take the current X member
this.position.top = this.Y + 'vh'; // will take the current Y member
// update the health with the current value, without fractional part
this.health.textContent = this.Health.toFixed();
}
function createNameplate(nameplate) {
// create the DOM elements for the nameplate
var plate = createNameplateDOM();
// extend the C++ nameplate with additional JavaScript values - the DOM
// elements that has to be updated when the nameplate is changed
nameplate.position = plate.style;
nameplate.health = plate.childNodes.item(1);
// extend the C++ nameplate with JavaScript function for the update
nameplate.update = updateNameplate();
nameplate.update();
document.body.appendChild(plate);
}
engine.on('nameplates.create', createNameplate);
engine.on('nameplates.update', function (nameplate) {
// This the same JavaScript value, so it already has an update method
// and references to the DOM elements
nameplate.update();
}

Calling C++ methods from JavaScript

When a C++ object is exposed by reference to JavaScript, its methods also might be called from JavaScript.

class Game
{
void Initialize(Coherent::UIGT::View* view)
{
// this game object will be accessing as the global "game" variable
// in JavaScript
view->ExposeAsGlobal("game", this);
}
~Game()
{
// make sure JavaScript won't crash us
m_View->RemoveExposedInstance(this);
}
bool LoadSave(const char* name);
Quit();
};
void CoherentBind(Coherent::UIGT::Binder* binder, Game* game)
{
if (auto type = binder->RegisterType("Game", game))
{
type.Method("loadSave", &Game::LoadSave)
.Method("quit", &Game::Quit)
;
}
}

After this JavaScript can use the game variable as any other object.

quitButton.addEventListener('click', function () { game.quit(); }, false);
// ...
function LoadGame(name) {
if (!game.loadSave(name)) { // calls the C++ method
ShowError();
}
}

Disabling default binding behaviour

Coherent GT provides a default version of CoherentBind which will error out if you don't provide a template specialization of CoherentBind for your type. In some cases you might want to implement the default by yourself (e.g. binding all types via the reflection system of your engine). To do that you need to #define COHERENTGT_DISABLE_DEFAULT_BINDING_TEMPLATES which will disable the default implementation.

Data binding

Starting with Coherent GT version 1.9 a new feature is introduced - data binding. It allows clients to bind an object, exposed by reference, directly to the HTML DOM using either mark-up or simply creating your own node through JavaScript. The C++ object that is exposed is referred to as a "model", which serves as the backing store of the data. The interface is updated when you mark some models and/or properties as dirty and signal that the changes should be pulled from the models and applied to the visual interface, using View::SynchronizeModels. Models and properties can be marked as dirty through the ModelHandle and PropertyHandle structures.

Creating a named model

You can create a named model using the View::CreateModel API. It takes a string argument with the model name and the pointer to the model instance.

struct Player
{
int LeftPos;
};
Player g_Player{ 123 };
void CoherentBind(Coherent::UIGT::Binder* binder, Player* player)
{
if (auto type = binder->RegisterType("Player", player))
{
type.Property("leftPos", &Player::LeftPos)
}
}
void RegisterMyModel()
{
g_PlayerModelH = m_View->CreateModel("g_TestPlayer", &g_Player);
}

The example above would define a Player structure which exposes the LeftPos C++ property as leftPos in JavaScript. The View::CreateModel invocation would then register a model named "g_TestPlayer" which corresponds to the g_Player C++ instance. You can refer to that property using a data bind value {{g_TestPlayer.leftPos}}.

HTML/JS data binding syntax

The syntax of the data binding attributes is simply a JavaScript expression, which can also contain references to model properties that are encapsulated in double curly braces like so:

1 <div data-bind-style-left="{{myModel.leftPos}}"></div>

myModel is a named model which has a leftPos property in JavaScript. The above example can be

Expressions that are solely referring to a model's property are the fastest performance-wise since no additional JavaScript is executed and the bound value is directly assigned within the SDK.

You can also construct complex expressions such as `data-bind-style-left='{{myModel.LeftPos}}.toFixed()'or data-bind-style-left='Math.pow({{myModel.LeftPos}}, 2)'`.

Warning
Complex expressions require JavaScript evaluation and are more expensive than bindings to a single value without processing.

A node can also be attached to a model using the new engine.attachToModel(DOMNode, exposedModel) JavaScript API (provided from coherent.js). When using this API, clients can refer to the bound model as "this". Here's an example:

1 engine.on('Ready', function () {
2  var display = document.createElement('div'),
3  mana = document.createElement('span');
4 
5  mana.setAttribute('data-bind-value', '{{this.Mana}}');
6  mana.setAttribute('data-bind-class-toggle', 'red:{{this.Mana}} < 50');
7 
8  display.appendChild(mana);
9 
10  engine.attachToModel(display, g_TestPlayer);
11 
12  document.body.appendChild(display);
13 }

Attaching a dynamic model internally traverses all children of the attached DOM node and takes into account their data-bind-* attributes as well.

Note
Dynamically setting an attribute on an already attached DOM node will have no effect. Data-bind values currently only affect the node before attachment.
Warning
Static DOM nodes (written in the markup, and not dynamically created) record the names of the models that affect their appearance when the node is attached to DOM tree (basically after the HTML is parsed). If you register a model with a name that is used in one of those nodes, the binding will be picked up from the record. If you try to unregister the named mode and then register it again, the binding won't work any more, since there is no new record about that binding (it's only updated on DOM node attach event).

Supported data-bind-* attributes

  • data-bind-value takes a string and assigns the node's textContent.
  • data-bind-style-left takes a string or number and assigns the node's style left property. In case a number is passed, pixel units are assumed.
  • data-bind-style-top takes a string or number and assigns the node's style top property. In case a number is passed, pixel units are assumed.
  • data-bind-style-opacity takes a floating point number between 0 and 1 and assigns the node's style opacity property.
  • data-bind-style-width takes a string or number and assigns the node's style width property. In case a number is passed, pixel units are assumed.
  • data-bind-style-height takes a string or number and assigns the node's style height property. In case a number is passed, pixel units are assumed.
  • data-bind-style-color takes a string and applies it as the node's style color property.
  • data-bind-style-background-color takes a string and applies it as the node's style background-color property.
  • data-bind-class-toggle takes a string in the format "class-name:bool_condition". The "class-name" is the name of some class that is defined in CSS and "bool_condition" is data bind value expression that evaluates to a boolean. The specified class is added or removed depending on the boolean condition.

Example with class toggling:

1 <head>
2  <style>
3  .red {
4  background-color: red;
5  }
6  </style>
7 </head>
8 <body>
9  <div data-bind-class-toggle="red:{{this.Health}} < 50">Something red</div>

The "red" class will be added or removed depending on the value of the "Health" property. Whenever it's below 50, the "red" class will fill the background in red color.

Creating a "nameless" model

The engine.attachToModel(DOMNode, exposedModel) API will also work even if exposedModel isn't already registered by the C++ API. In that case, the SDK will create a nameless model for the attachToModel call. You can obtain a model handle in the C++ code by simply creating one your own, using a View object and model instance.

The usual practice is to send the object first from C++ to JavaScript using ByRef bindings:

view->TriggerEvent("attachNamelessModel", Coherent::UIGT::ByRef(player));

Then, in the HTML/JS you handle the event like so:

1 <script type="text/html" id="player-template">
2  <div id="attach-player" data-test-class="gm" data-bind-class-toggle="gm:{{this.gameMaster}}">
3  <label>Name: <span id="attach-name" data-test="name" data-bind-value="{{this.name}}">CoolName</span></label>
4  <label>Health: <span id="attach-health" data-test="health" data-bind-value="{{this.health}}"></span></label>
5  <div id="attach-health-class" data-test-class="good-shape" data-bind-class-toggle="good-shape:{{this.health}} >= 100">
6  <div id="attach-health-bar" data-style="width" data-bind-style-width="{{this.health}} / 1000"></div>
7  </div>
8  </div>
9 </script>
function instantiateTemplate(selector) {
var template = document.querySelector(selector),
div = document.createElement('div');
div.innerHTML = template.textContent;
return div;
}
engine.on('attachNamelessModel', function (model) {
var instance = instantiateTemplate('#player-template');
engine.attachToModel(instance, model);
// Do something with the DOM node "instance" here
}

Unregistering a model

Clients can unregister models from binding using the View::UnregisterModel API. This removes the model by instance pointer.

You can also remove a DOM node that has data-bindings, but you need be careful with that since the removal of such node is not a trivial operation and involves walking through all models that are affected from that change.

Updating a model

Models can be updated through the View interface or PropertyHandle structure.

The View interface allows for updating all properties of a certain model, using the View::UpdateWholeModel API.

More fine-grained control over updating can be obtained through a PropertyHandle. In order to get a PropertyHandle, a ModelHandle is needed first.

A ModelHandle is returned upon invoking the View::CreateModel API, or when created manually with a View object and model instance pointer. In case of the latter, you need to ensure the model is already registered, either through the View::CraeteModel API, or a nameless model was created through JavaScript.

A whole model can be updated through the ModelHandle, too, using the ModelHandle::SetNeedsUpdate API.

// Option 1 (through View::CreateModel)
ModelHandle handle = m_View->CreateModel("player", m_Player.get();
// Option 2 (manually)
ModelHandle handle(m_Player.get(), m_View):

The ModelHandle allows clients to obtain a PropertyHandle by property name using the ModelHandle::GetPropertyHandle method. The returned property handle can mark single properties (or specific array indices) as dirty through the PropertyHandle::SetNeedsUpdate or PropertyHandle::SetNeedsUpdateArrayIndex APIs.

class MyViewListener : public Coherent::UIGT::ViewListener
{
Player* m_Player;
...
virtual void OnBindingModelCreated(
const char* name,
ModelHandle& handle) COHERENT_OVERRIDE
{
if (strcmp(name, "ExpectedModelName") == 0)
{
PropertyHandle hpHandle = handle.GetPropertyHandle("health");
player->Health = 42;
hpHandle.SetNeedsUpdate();
m_View->SynchronizeModels();
}
}
...
};
Note
When using the View::CreateModel API it's important to get your property handles in the ViewListener::OnBindingModelCreated callback since when using async mode, the model won't be created until some time later after the View::CreateModel call. This is not required when using sync mode, but it's still good practice.
Using the fine-grained API for updating single properties of models is more verbose, but will have better performance in case the models have a lot of properties, whose values have not changed.

All changes marked through the ModelHandle/PropertyHandles are simply marked as needing update and changes are not propagated immediately. Clients can choose an appropriate moment in time to do the update through the View::SynchronizeModels API, which does the actual update.

Structural data-binding

The data binding implements attributes that allow for DOM structural changes. These attributes are:

  • data-bind-if: Displays a DOM element based on a condition. The expressions in the attribute value should evaluate to a boolean value.
  • data-bind-switch: Displays a single child DOM element marked with data-bind-switch-when="constant" attribute, when the evaluated switch attribute value equals the switch-when constant. Optionally, a data-bind-switch-default attribute can be specified, which displays the associated DOM node when the switch condition doesn't equal any of the switch-case constants.
  • data-bind-for: Repeats the DOM node for each element in the collection. The syntax is data-bind-for="{{iter:myModel.arrayProperty}}", where myModel.arrayProperty is an array property of myModel, and iter is a variable used for iteration, which is available in child DOM node evaluators.
Warning
The data-bind-for attribute currently supports iterating over arrays bound by-ref only! Any other type of collection won't work.

Following are a few examples for the structural constructs.

struct Player
{
std::string Name = "";
float Health = 100;
std::string Team = "red";
std::vector<std::string> Pets = { "Sabretooth tiger", "Eagle", "Pony" };
float GetHealth() const { return Health; }
void SetHealth(float health) { Health = health; }
};
void CoherentBind(Coherent::UIGT::Binder* binder, Player* p)
{
if (auto type = binder->RegisterType("Player", p))
{
type.Property("name", &Player::Name)
.Property("health", &Player::GetHealth, &Player::SetHealth)
.Property("pets", &Player::Pets)
.Property("team", &Player::Team);
;
}
}
1 < !-- displays a warning message if the player's health is low -->
2 <div data-bind-if="{{g_Player.health}} < 40" id="playerHPLowWarn">
3  Player needs a medic!
4 </div>
5 
6 < !-- displays the team of the player, nothing if it's something other than
7 "red", "blue" or "green" -->
8 <div data-bind-switch="{{g_Player.team}}">
9  <div data-bind-switch-when="red" class="teamDesc">Team red</div>
10  <div data-bind-switch-when="green" class="teamDesc">Team green</div>
11  <div data-bind-switch-when="blue" class="teamDesc">Team blue</div>
12 </div>
13 
14 < !-- displays the team of the player, "No team" if it's something other than
15 "red", "blue" or "green" -->
16 <div data-bind-switch="{{g_Player.team}}">
17  <div data-bind-switch-when="red" class="teamDesc">Team red</div>
18  <div data-bind-switch-when="green" class="teamDesc">Team green</div>
19  <div data-bind-switch-when="blue" class="teamDesc">Team blue</div>
20  <div data-bind-switch-default class="teamDesc">No team</div>
21 </div>
22 
23 < !-- enumerates all pets of the player -->
24 <div data-bind-for="iter:{{g_Player.pets}}" class="petsList">
25  <div class="petsItem" data-bind-value="{{iter}}"></div>
26 </div>
27 
28 < !-- Structural evaluators can be nested -->
29 < !-- enumerates all pets of the player if her health is above 50 and the team
30 is either "red" or "blue" -->
31 <div data-bind-if="{{g_Player.health}} > 50" id="ifTest1">
32  <div data-bind-if="{{g_Player.team}} == 'red' || {{g_Player.team}} == 'blue'">
33  <div data-bind-for="iter:{{g_Player.pets}}" class="petsList">
34  <div class="petsItem" data-bind-value="{{iter}}"></div>
35  </div>
36  </div>
37 </div>
Warning
Structural data-binding evaluators generate helper nodes that serve as "anchoring" points in the DOM tree. Modifying those nodes manually will result in undefined behaviour!