Server side events

Use server side events to stream data onto the application templates. This allows us to create dynamic operations in the templates to load specific parts without reloading the entire page.

Any page type can be streamed, but the pages that are streamed should be created specifically for that purpose.

Read about more advanced methods to manipulate the streams

Defining the loader

For the feature to work we need to load the erp pkg script package. We can use the tools’ helper .Tools.LoadSSE that will create the required tag with a dynamic source automatically.

Without this package the sse feature will not work.

<!DOCTYPE html>
<html>
    <head>
        {{ .Tools.LoadSSE }}
    </head>
    <body>
    </body>
</html>

Define the stream target

Any html node can be the target.

The following parameters/attributes are required

  1. id - the node has to have a unique id
  2. data-sse-src - the source of the stream attribute, for this we can again use the tools’ helper .Tools.GetSSESrc with input parameter of the page that we want to stream

Optional parameters/attributes

  1. data-sse-behaviour - the method that is used to handle the stream
    • load - the default value, stream will end once it responds with full data
    • replace - stream will constantly run, and it will replace all the contents of the container node with the events data
    • push - stream will constantly run, and it will push new content at the end of the nodes content

Contents of th steam can be either simple any type the page template is defined for.

Load behaviour

Using the load behaviour we can create a loader type, the data inside the container will be loaded immediately and once the event returns the data then the stream is automatically closed

<!DOCTYPE html>
<html>
    <head>
        {{ .Tools.LoadSSE }}
    </head>
    <body>
        <!-- data-sse-behaviour="load" is the default behaviour and can be skipped if load is used -->
        <div id="t1"
             data-sse-src="{{ .Tools.GetSSESrc "da-sse-data-page"}}">
    
            <!-- contents will be removed once data is 'finished' so loader elements can be added here  -->
            <div class="loader"></div>
        
        </div>
    </body>
</html>

Replace behaviour

In this behaviour mode the data of the element will always get replaced when new data is being returned in the stream. The stream will be open until it returns an error or the window is closed.

Useful to propagate notifications or states.

<!DOCTYPE html>
<html>
    <head>
        {{ .Tools.LoadSSE }}
    </head>
    <body>
        <!-- data-sse-behaviour="replace" is important here to keep the stream open -->
        <div class="my-notification-container" id="t1"
             data-sse-src="{{ .Tools.GetSSESrc "da-sse-notifications-page"}}"
             data-sse-behaviour="replace">
        </div>
    </body>
</html>

Push behaviour

Perhaps the least useful behaviour. Data from the stream is added at the end of the nodes content without replacing the existing data. The stream will be open until it returns an error or the window is closed.

Possibly useful for cases where the data is some sort of log.

<!DOCTYPE html>
<html>
    <head>
        {{ .Tools.LoadSSE }}
    </head>
    <body>
        <!-- data-sse-behaviour="push" is important here to keep the stream open -->
        <div class="my-log-container" id="t1"
             data-sse-src="{{ .Tools.GetSSESrc "da-sse-log-page"}}"
             data-sse-behaviour="push">
        </div>
    </body>
</html>

Prepend behaviour

Data from the stream is added at the front of the nodes content without replacing the existing data. The stream will be open until it returns an error or the window is closed.

<!DOCTYPE html>
<html>
    <head>
        {{ .Tools.LoadSSE }}
    </head>
    <body>
        <!-- data-sse-behaviour="push" is important here to keep the stream open -->
        <div class="my-log-container" id="t1"
             data-sse-src="{{ .Tools.GetSSESrc "da-sse-log-page"}}"
             data-sse-behaviour="prepend">
        </div>
    </body>
</html>

Preload behaviours

The following extra behaviours can be used to first load data and then make it wait for the action to load more data.

Useful for cases where we want to render the content right away in its current state, but we do not want it to re-render all the time.

  • loadAndReplace
  • loadAndPush
  • loadAndPrepend

Multiple loaders

A page can define multiple loaders, but it’s limited to 6 streams per browser (total of all tabs).

<!DOCTYPE html>
<html>
    <head>
        {{ .Tools.LoadSSE }}
        <link rel="stylesheet" href="{{ .Tools.StaticLink "da-sse-styles-css" }}">
    </head>
    <body>
        <div class="column full mt-2">
            <div class="row">
                <span class="alert" id="notifications"
                    data-sse-src="{{ .Tools.GetSSESrc "da-sse-notifications-page"}}"
                    data-sse-behaviour="replace"></span>
            </div>

            <div class="column full justify-center mt-2" id="t1"
                data-sse-src="{{ .Tools.GetSSESrc "da-sse-data-page"}}">
                
                <div class="row justify-center">
                    <div class="column">
                        <div class="loader"></div>
                        <p class="text-center">Loading</p>
                    </div>
                </div>

            </div>
        </div>
    </body>
</html>

Subsections of Server side events

Actions and custom data

From version 1.241+

Normally the SSE feature will continue to stream new data, this streams the same contents in a loop, and in most cases is not needed. To improve this we can use special custom actions on streams to only flush data when there is an actual change.

Define the custom action to the SSE block

To make the server side events flush data based on actions we need to add the action name to the sse stream. The name (my-action) is custom that you can define yourself.

<div class="row">
    <span class="alert" id="notifications"
        data-sse-src="{{ .Tools.GetSSESrc "da-sse-notifications-page"}}"
        data-sse-behaviour="push"
        data-sse-action="my-action">
    </span>
</div>

When this is loaded the stream will not flush any data until that action has been detected.

Send the action

We can use two different methods to send the actions. After the server receives the event it will flush the stream.

Form based actions

With form based actions all possible inputs are visible in the template html code.

<form method="post">
    <input type="hidden" name="AutomatApi.StreamActionEvent.Name" value="my-action">
    <button type="submit">Trigger action</button>
</form>

Template driven action

With this the data is hidden from the html code. Suitable for actions that contain data that we do not want the end user to see or edit.

We still use a form but what is being sent is hidden.

    <form method="post">
        <input type="hidden" name="Send" id="send" value="1">
        <button type="submit" class="form-button">Trigger action</button>
    </form>

    {{ if .Data.Parameters.Send }}
        {{ .Tools.StreamActionEvent "my-action" }}
    {{ end }}

Passing data with the action

We can also pass data with the action trigger. We can then read the data on the possible streamed page and change the rendered content based on the input.

The data should be in json format but the structure is custom.

With forms

With the regular forms data manipulation is limited. Also note that everything sent like this will be visible in the html code and can be adjusted by the user.

<form method="post">
    <input type="hidden" name="AutomatApi.StreamActionEvent.Name" value="my-action">
    <input type="hidden" name="AutomatApi.StreamActionEvent.Data" value=`{"key": "value"}`>
    <button type="submit">Trigger action</button>
</form>

Template action

All template sent data will be hidden, and we can safely add data from session or other values that we do not want the user to edit or see.

    <form method="post">
        <div class="form-input">
            <label for="name">Name</label>
            <input name="Name" id="name" value="{{ .Data.Parameters.Name }}">
        </div>

        <div class="form-input">
            <label for="send">Action</label>
            <input name="Send" id="send" value="{{ .Data.Parameters.Send }}">
        </div>

        <button type="submit" class="form-button">Submit</button>
    </form>

    {{ if .Data.Parameters.Send }}

        {{ $data := printf 
            `{"type": "%s", "name": "%s"}` 
            .Data.Parameters.Send 
            .Data.Parameters.Name
        }}

        {{ .Tools.StreamActionEvent "my-action" $data }}

    {{ end }}

Reading action data

We can read the data that is being passed with the actions and use it to change the data that we render.

We assume the data is:

{
  "name": "Some user",
  "content": "some text" 
}

The get method here is the same as elsewhere with dynamics.

<h4> {{ .Data.Stream.Get "name" }} </h4>
<p>
<!-- Render server time when content is 'clock' and the content itself when not -->
{{ if eq (.Data.Stream.Get "content").String "clock" }}
    {{ dtCurrent }}
{{ else }}
    {{ .Data.Stream.Get "content" }}
{{ end }}