View and screen interactions

What you will learn

In this lesson, you will improve your screen to have 2 buttons and create interaction between screen and views. You will make your screen react to input and create states for your buttons.

This should be the final result :

Final result

Something particular when making a TV application and using a remote control is that you should always have a focus somewhere. User will be able to navigate with constraint directions (even when using a gamepad) and predictable ones. Add a second button to see how to interact between them.

In your CatalogScreen, add a second button as a child, and place it next to the first one :

export default $AppScreen.declare("CatalogScreen", {

    children: /** @lends CatalogScreen.prototype */ {
        button1: {class: $ButtonView},
        button2: {class: $ButtonView}
    },

    style: {
        button2: {
            x: ({parent}) => parent.button1.x + parent.button1.width + 20
        }
    }
});

Change connectData method to set different text to each button :

methods: {
    connectData: function () {
        this.button1.setText("Home");
        this.button2.setText("VOD");
    }
}

Focus management

Now you have 2 buttons, first one is focused and the other one is not. Focus is automatically given to the first child of a Screen. And if you want to check, you can use debug tool available in your console. Just enter in Chrome console :

> wtvDebug.currentScreen.focusedChild

Here you have complete access to the instance. To check wich one it is, you can get its id. “Id” is the name you gave in the screen’s children declaration.

> wtvDebug.currentScreen.focusedChild.id
> 'button1'

State management

As focus / unfocused state is mandatory in remote controlled application, it is part of Dana. States will allow you to define a different bunch of style properties when a state is applied to a View. What is written in style is considered as the default state. You are going to define a different style for default and focused states.

Edit the ButtonView as follows :

export default $View.declare("ButtonView", {
    style: {
        text: {
            color: $Theme.COLOR_BLACK
        },
        rectangle: {
            opacity: 0
        }
    },
    states: {
        focused: {
            "rectangle.opacity": 1,
            "text.color": $Theme.COLOR_WHITE
        }
    }
});

The keyword states allows you to declare the different set of states where you want to apply different style property. To apply style property to a children, use its id and the property name {childId}.{propertyName}. Here you have changed the color of the text and the opacity of the rectangle. When focused, a blue rectangle with a white text will be visible, and by default a button will only be composed of a black text.

Caution

focused state is managed by Dana and you do not need to do anything to add or remove it. You can of course create your one states and manage them by yourself.

Use the browser console to execute javascript code and focus the second button.

wtvDebug.currentScreen.button2.focus()

Second button became focused and its style is updated. First button lost its focused state and its style is also updated to roll back to default values.

Transitions

Caution

CSS renderer has no transitions implementation because it targets low performance devices. Edit your profile to template-lightning for this exercice.

By using the keyword transitions in a View, you can define a transition to be applied when a style property change. Using the same kind of notation {childId}.{propertyName}, you will be able to define a transition duration using the keyword duration.

export default $View.declare("ButtonView", {
    transitions: {
        "rectangle.opacity": {duration: 250}
    }
});

To see it, you can use focus again :

wtvDebug.currentScreen.button2.focus()

Caution

Transitions are applied when style property value is updated, no matter the way. It is working when using transitions, but also if you update value by yourself. For example, try to change rectangle’s opacity of button1 by entering wtvDebug.currentScreen.button1.rectangle.opacity = 1

Input management

As explained, TV applications are used with remote controller. In CatalogScreen, create two functions for : right and left.

methods keyword is used to store functions of an instance. Implementation of those two functions are basics :

  • right method : if current focus is on button1, focus button2. Otherwise, do nothing
  • left method : if current focus is on button2, focus button1. Otherwise, do nothing
export default $AppScreen.declare("CatalogScreen", {
    methods: /** @lends CatalogScreen.prototype */ {
        right: function () {
            if (this.focusedChild.id === "button1") {
                this.button2.focus();
            }
        },

        left: function () {
            if (this.focusedChild.id === "button2") {
                this.button1.focus();
            }
        }
    }
});

To be able to simulate remote behaviour, on browser you can use navigation key (up / down / right / left). Use right and left keys to switch focus between your two buttons.

As those inputs are going to be used in every TV applications, a default implementation has been done in AppScreen To ease and speed up development . CatalogScreen inherits from AppScreen, so you benefits of those implementations. Let’s review this file.

export default $Screen.declare("AppScreen", {
    inputs: ["keypress.UP", "keypress.DOWN", "keypress.LEFT", "keypress.RIGHT", "keypress.OK", "keypress.BACK"],
    methods: /** @lends  AppScreen.prototype */ {
        onKeypressLEFT: function (key) {
            return this.left(key);
        },

        onKeypressRIGHT: function (key) {
            return this.right(key);
        },
        /**
         * @override
         */
        left: function (key) {
            return this.focusedChild.hasNavigation ? this.focusedChild.left(key) : false;
        },
        /**
         * @override
         */

        right: function (key) {
            return this.focusedChild.hasNavigation ? this.focusedChild.right(key) : false;
        }
    }

});

inputs keyword is used to list input key events that screen is going to listen to. Key event name is split in two parts :

  • key event : 3 values possible keydown, keypress, keyup
  • key name : comprehensible name of the key used

Once inputs are registered, you need to declare the relative function called when input is used. Naming should follow the convention on{keyEvent}{keyName}. First letter of keyEvent and keyName should be put in uppercase. As a result you have :

  • keypress.LEFT will call onKeypressLEFT
  • keypress.DOWN will call onKeypressDOWN

In AppScreen, onKeypressLEFT calls left method. In CatalogScreen, you have overriden left method. That’s why, when you press your keyboard’s left key, focus is changing.

In the begining of the lesson, you have changed you profile to start on CatalogScreen. Revert to splash to be able to use routing between those 2 screens. In app.config.json, inside the object default, edit back firstRoute:

"firstRoute": {
    "id": "splash"
}

Now open SplashScreen. In beforeShow method, a timer is started. Once resolved, route to your new screen.

export default $AppScreen.declare("SplashScreen", {
    methods: /** @lends SplashScreen.prototype */ {
        beforeShow: function (context) {
            this.timerManager.setTimeout(() => {
                this.route("catalog");
            }, this.splashDuration, this.statics.HIDE_SPLASH_SCREEN_TIMER_ID);
        }
    }
});

Note

Do not forget to relaunch your npm start. Now, you can see both of your screens displayed one after the other.

Summary

To summarize, during this lesson you have :

  • controlled navigation between views inside a screen
  • used states and transitions
  • routed to a screen from another one

To go further on those concepts, you can browse the following documentation pages :