Handling Updates

Here we will define what happens when the CasparCG Server calls the update command on our graphic.

In the last section we called out how the update function looked different than the other play out commands.

const _graphic = (function() {
    (function() {
        // Runs after the rest of the parent function parses
        window['update'] = (raw) => update(raw);
        window['play'] = play;
        window['next'] = next;
        window['stop'] = stop;
        window['remove'] = remove;
    })();
    // Other function definitions
}

We do this because the update function is the only function that is passed data from the CasparCG server. We use an arrow function to capture the incoming string and pass it to our own update function. An example explains it best.

// CasparCG Command 
// CG 1-1 UPDATE 0 "Hello from Caspar"

const _graphic = (function() {
    // Other function definitions
    
    function update(raw) {
        console.log(raw) // Output: Hello from Caspar
    }
    
    // Other play out command function definitions
}

Template Data Setup

Now that we have some data, let's put it to work! We will be using JSON data so we can easily convert it to a JS object and utilize it in our script. In your web browser's developer console, enter the following JS command to get the example JSON data stringified and escaped.

JSON.stringify({
	style: {primaryColor: "lightblue", textColor: "black", position: "right"},
	data: {title: "Title One", subtitle: "Subtitle One"}
}).replace(/"/g, '\\"');

// {\"style\":{\"primaryColor\":\"lightblue\",\"textColor\":\"black\"},\"data\":{\"title\":\"Title One\",\"subtitle\":\"Subtitle One\"}}

Feel free to change the colors or titles to different values. The colors an be a color keyword, HEX, RGB, or RGBA value.

You may also add multiple names by changing data to an array of objects.

data: [
    {title: "Title One", subtitle: "Subtitle One"},
    {title: "Title Two", subtitle: "Subtitle Two"}
]

We can now call our update function either from CasparCG server or the browser's developer console.

// CasparCG Server
CG 1-1 ADD 0 http://localhost:8080/lower-third.1.html 0 "{\"style\":{\"primaryColor\":\"lightblue\",\"textColor\":\"black\"},\"data\":{\"title\":\"Title One\",\"subtitle\":\"Subtitle One\"}}"

// Broswer Developer Console
update("{\"style\":{\"primaryColor\":\"lightblue\",\"textColor\":\"black\"},\"data\":{\"title\":\"Title One\",\"subtitle\":\"Subtitle One\"}}")

When developing with the browser, you can save the update command in your browser's snippets. Then run the snippet when you refresh the page.

Template Data w/ the Development Widget

If you are using the HTML Widget setup in the Development Setup section, then now is a good time to add our data into the widget. This will allow the widget to inject our template data when we run the update command.

Via Web Browser

If you downloaded a Chromium browser, then you can use your mouse to interact with the widget and add the data normally. Click the Data button on the widget and then in the popup window, click the {} icon on the top right. From there paste in the following JSON.

{
    "data": [
        {"title": "Title One", "subtitle": "Subtitle One"},
        {"title": "Title Two", "subtitle": "Subtitle Two"}
    ], "style": {
        "primaryColor": "lightblue",
        "textColor": "black",
        "position": "center"
    }
}

In the text field at the bottom of the popup window, enter the name Load and click the save icon on the bottom right. The name will appear on the right hand side under "Data Sets". Click on the {} icon again and remove all the text from the text area. Then, paste in the following JSON snippet.

{
    "data": {"title": "Title Three", "subtitle": "Subtitle Three"}
}

Change the name at the bottom from Load to Add New Title and click save icon again. You should be able to toggle between both data sets in the Data Sets list. Ensure that the Load data set is first in the list so the widget can run the update command with the correct data set.

When loading the graphic for the first time, click the Update button with the Load option selected. To add a new name to the graphic ( Once the graphic is complete ), click the update function with the Add New Title option selected.

Via CasparCG Server

If you are developing directly in the CasparCG server, then you will need to have remote debugging enabled for your HTML templates. Somewhere in your server's casparcg.config file there should be the following lines to enable the remote debugging feature.

<html>
    <remote-debugging-port>8081</remote-debugging-port>
</html>

We can now start the server and go to http://localhost:8081/ and when a template is added via the CG ADD command, a link will appear for the template's debugging session. If you do not see a link after running the CG ADD command, refresh the debug page.

If you are using a modern version of Chrome to debug CasparCG server then you may get this error, TypeError: document.registerElement is not a function. To fix this, download and use a version of Chromium listed in the CasparCG Server Setup section. Future version of CasparCG will not have this issue.

Once you have navigated to the remote development console, we can add the template's data to the widget. The downside to developing from within the server is none of Chromium's local storage is saved when the server stops. This mean, every time you begin developing, you would need to enter the following command to load both sets of data.

_widget.setTemplateData("{\"data\":{\"title\":\"Title Three\",\"subtitle\":\"Subtitle Three\"}}", 'Add New Title');

_widget.setTemplateData("{\"style\":{\"primaryColor\":\"lightblue\",\"textColor\":\"black\"},\"data\":[{\"title\":\"Title One\",\"subtitle\":\"Subtitle One\"},{\"title\":\"Title Two\",\"subtitle\":\"Subtitle Two\"}]}", 'Load');

The widget now has the two pieces of JSON data required by our graphic. To switch between each data set, we can run another function.

_widget.getDataOptions(); // Returns all the data sets
_widget.selectTemplateData('Add New Title'); // Selects a data set

Replace Add New Title with the name of the data set you want to use. Finally, there is one additional CasparCG server trade off we have to address.

Every time the CG ADD command is ran, a new debug session will be started for that template. This means you will need to go back to the original debug page, http://localhost:8081/ , and select the new debug session link. To avoid this, we will refresh the page from the console then re-enable our widget.

location.reload(); // Reloads the HTML page

// After the page has reloaded

// If using NRK version, re-enable the widget
_widget.enableWidget();

// Select the correct data and update the template
_widget.selectTemplateData('Load');
_widget.executePlayOutCommand('update');

The graphic's update function will run with the data passed to it as if the CasparCG server was running it.

Updates in the JavaScript File

After all of that setup, we are finally ready to begin scripting. Inside the update function, let's begin by parsing the string passed by the window's update function.

function update(raw) {
    let parsed;
    // Try and parse incoming string as JSON
    try {
        parsed = JSON.parse(raw);
        if(!Object.keys(parsed).length) 
            throw new Error('Empty objects are invalid');
        if(!parsed.style) {
            if(!parsed.data) 
                throw new Error('Invalid data object');
        }
    } catch(e) { // Parse Failed
        handleError(e);
        return;
    }
    Array.isArray(parsed.data) // Save the text data
        ? data = data.concat(parsed.data)
        : data.push(parsed.data);
    style = parsed.style; // Save the style data
}

If the parsing fails or the object does not have the properties we are looking for, we will throw an error. If there was valid data, we will add it to our graphic's data and style proprieties.

Next, let's write two functions. One to handle our text data and the other to handle our style data. We will begin with the text data because it is simpler.

function update(raw) {
    let parsed;
    /* Parsing JSON Try Catch Block */
    /* Save data and style */
    
    // Is graphic being loaded?
    if(state === 0) {
        try {
            applyData();
        } catch (error) {
            handleError(error);
            return;
        }
    }
}

First we check and see if the graphic is still being loaded with the state property. If it is, we will call the applyData function inside of a try catch block. We can then write the applyData function.

const _graphic = (function() {
    /* Other Graphic Properties */
    let data;
    let style;
    
    // Other function definitions
    
    function applyData() {
        const graphic = document.querySelector('.lt-style-one .graphic');
        const title = graphic.querySelector('h1');
        const subtitle = graphic.querySelector('p');

        title.textContent = data[activeStep].title;
        subtitle.textContent = data[activeStep].subtitle;
    }
    
    function update() { /* Update function code */}
}

The applyData functions is pretty straight forwards. We setup a variable for each text element and then set it's text content to the current title and subtitle. Later on we will increment the activeStep property to get the next title in the list.

Next we can add in our function to setup the graphic's styles. Let's start by adding a function call to applyStyles.

function update(raw) {
    let parsed;
    /* Parsing JSON Try Catch Block */
    if(this.state === 0) {
        try {
            applyData();
            applyStyle();
        } catch (error) {
            handleError(e);
            return;
        }
    }
}

We can then begin writing the applyStyle function.

const _graphic = (function() {
    /* Other Graphic Properties */
    let data;
    let style;
    
    // Other function definitions
    function applyData() { }
    
    function applyStyle() {
        const container = document.querySelector('.lt-style-one');
        const graphic = container.querySelector('.graphic');
        const [pathLeft, pathRight] = graphic.querySelectorAll('svg path');
        const title = graphic.querySelector('h1');
        const subtitle = graphic.querySelector('.subtitle');

        // Set the elements CSS styles
        pathLeft.style.stroke = style.primaryColor;
        pathRight.style.stroke = style.primaryColor;
        title.style.color = style.textColor;
        subtitle.style.color = style.textColor;
        subtitle.style.backgroundColor = style.primaryColor;

        // Position the graphic on screen
        switch(style.position) {
            case 'left':
                container.style.marginRight = 'auto';
                break;
            case 'center':
                container.style.margin = '4vh auto';
                break;
            default:
                container.style.marginLeft = 'auto';
                break;
        }
    }
    
    function update() { /* Update function code */}
}

The concept of the applyStyles function is similar to the applyData function. Get a reference to all the elements needed to be modified and then update the necessary properties. We use [pathLeft, pathRight] when defining the svg paths. This is a shorthand way of defining elements from within an array in the variable declaration. It is a form of deconstruction and a full breakdown on how it works can be found on MDN's website.

We also check the position property to the style object. This is optional but, when used will move the graphic to either the left, center, or right section of the screen.

Wrapping Up

We need to increment the graphic's state property. This will matter when we begin animating.

function update(raw) {
    let parsed;
    /* Parsing JSON Try Catch Block */
    if(this.state === 0) {
        try {
            applyData();
            applyStyle();
            state = 1; // Graphic has been loaded
        } catch (error) {
            handleError(e);
            return;
        }
    }
}

Our graphic can now take in data and setup the initial state of the graphic. The last thing we need to do is set an opacity property on the .lt-style-one element. This will ensure we can not see the graphic before it is loaded. We could set this value with SCSS but, on a slow network the CSS file may not load before the first paint which would display the graphic for a moment.

<body>
    <main class="lt-style-one">
        <div class="graphic" style="opacity: 0;">
            <!-- SVG and text element definitions -->
        </div>
    </main>
    <script src="../js/lower.1.js"></script>
</body>

We will use JS to reveal the graphic when the CasparCG server executes CG PLAY command.

Resources

Last updated