Hologram Logo

Dashboard Widget Example

Simple Dashboard

In this example we will explain how to create a dashboard widget. At the top of our widget we will show the current time and date. Under the date and time will be some Mac OS system information.


Create a new widget

From within the Hologram application, navigate to the developer page and click the "New Widget" button. Name your widget "Simple Dashboard", give it an ID of "simple-dash", and enter your name. Then click "create". Now open your widget files in a text editor.


Update widget.json

Let's start by defining the UI controls that will let us toggle on/off and customize the various pieces of info in our dashboard.

Open your widget.json file and replace the settings:[] object with this:

"settings": [
    {
       "label": "Date & Time",
       "name": "showDateAndTime",
       "type": "bool",
       "defaultValue": true
      },
      {
        "label": "CPU Usage",
        "name": "showCpuUsage",
        "type": "bool",
        "defaultValue": true
      },
      {
        "label": "Processes",
        "name": "showProcesses",
        "type": "bool",
        "defaultValue": true
      },
      {
        "label": "Threads",
        "name": "showThreads",
        "type": "bool",
        "defaultValue": true
      },
    {
        "name": "font",
        "label": "Font",
        "type": "font",
        "defaultValue": "Normal"
    },
    {
        "label": "Colors",
        "type": "divider"
    },
    {
        "name": "theme",
        "type": "color-theme",
        "presets": ["auto", "light", "dark", "custom"],
        "colors": [
            {
               "name": "backgroundColor",
               "autoColor": "darkmuted",
               "presets": { "auto": "darkmuted", "light": "#ffffff", "dark": "#000000" }
            },
            {
               "name": "borderColor",
               "autoColor": "darkvibrant",
               "presets": { "auto": "darkvibrant", "light": "#ffffff00", "dark": "#00000000" }
              },
            {
              "name": "textColor",
              "autoColor": "lightvibrant",
              "presets": { "auto": "lightvibrant", "light": "#000000", "dark": "#ffffff" }
            },
            {
              "name": "shadowColor",
              "autoColor": "darkmuted",
              "presets": { "auto": "#00000033", "light": "#00000033", "dark": "#00000033" }
            }
        ]
    },
    {
        "type": "divider"
    },
    {
        "name": "shadow",
        "label": "Shadow",
        "type": "shadow",
        "defaultValue": true
    }
]

Additionally, you'll want to add an aspect ratio and height and width info to your widget.json file:

{
    "name": "Simple Dash",
    "widgetId": "simple-dash",
    "version": "1.0.0",
    "author": "Your Name",
    "aspectRatio": "auto-height",

    "defaultSize": {
        "placementY": "top",
        "placementX": "right",
        "x": 20,
        "y": 15,
        "width": 20,
        "height": 70
    },

Notes:

In the above json code we added toggle controls for the date and time, CPU usage, processes, and threads.


Update styles.css

Now open your styles.css file and replace the CSS with this:

.widget-container {
    color: var(--textColor);
    border-radius: 0.4em;
    box-shadow: var(--shadow) var(--shadowColor);
    background-color: var(--backgroundColor);
    border: 1px solid var(--borderColor);
    font-size: 0.8em;
}

.widget-container-inner {
    padding: 1em;
}

.title {
    opacity: 0.5;
    font-size: 0.8em;
    text-transform: uppercase;
    margin-bottom: 0.4em;
    margin-top: 1.6em;
}

.title:first-child {
    margin-top: 0;
}

.stat {
    margin: 0.6em 0;
}

.stat:last-child {
    margin-bottom: 0;
}

Update index.js

Lastly, update your index.js file. Replace everything previously in it with the following code.

The code contains comments that explain it. We will further explain the key points below the code.

export default {
    extends: HologramWidget,
    data() {
        return {
            themeColors: {},
            currentTime: dayjs(),
            processCount: 0,
            cpuUsage: '',
            listenerIds: []
        }
    },
    mounted() {
        // Load the widget theme colors, and assign them to css variables
        this.fetchColorTheme('theme', colors => {
            this.addCssVariables(colors)
        })

        // Gather the information that we need to display
        this.updateNeededInfo()

        // Since we allow each piece of information in the widget to be
        // toggled on/off, we will use a "watcher" method to keep track
        // of what needs to be shown based on what the user has enabled.
        this.watchNeededDataSettings()
    },
    methods: {
        updateNeededInfo() {
            // Before doing anything else, we will stop any data getters
            // that are currently running. They'll get added back as needed later
            for (let id of this.listenerIds) {
                Hologram.removeListener(this, id);
            }

            // This variable holds the ID of any piece of information
            // that is active. We reset it first.
            this.listenerIds = []

            // Figure out what data to show based on what the user has enabled
            let settings = this.settings
            let neededInfo = {
                processCount: settings.showProcesses || settings.showThreads,
                cpuUsage: settings.showCpuUsage,
            }

            // Loop through the neededInfo array and get the listener IDs for those items
            for (const [key, shouldShow] of Object.entries(neededInfo)) {
                if (shouldShow) {
                    this.listenerIds.push(Hologram.get(this, key, u => this[key] = u))
                }
            }
            // Get the date/time if the widget setting is enabled
            if (settings.showDateAndTime) {
                this.currentTime = dayjs()
                // Use the hologram timer function to update it every minute
                this.listenerIds.push(Hologram.timer.everyMinute(this, () => this.currentTime = dayjs()))
            }
        },
        watchNeededDataSettings() {
            let settings = ['showDateAndTime', 'showCpuUsage', 'showProcesses', 'showThreads']
            for (const setting of settings) {
                this.$watch('settings.' + setting, function () {
                    this.updateNeededInfo()
                })
            }
        },
    },
    computed: {
        time() {
            return this.currentTime.format('LT')
        },
        date() {
            return this.currentTime.format('LL')
        },
        cpuUsed() {
            return Math.round(this.cpuUsage.system + this.cpuUsage.user)
        },
    },
    template: /*html*/
    `
        <!-- Here we are passing the shadow setting to a css variable,
        and setting the font of the widget using vue -->
        <div class="widget-container" :style="{'--shadow': this.settings.shadow.boxShadow, 'font-family': settings.font}">
            <div class="widget-container-inner">
                <div v-if="settings.showDateAndTime">
                    <div class="title">Date & Time</div>
                    <div class="stat">{{time}}</div>
                    <div class="stat">{{date}}</div>
                </div>
                <div class="title" v-if="settings.showCpuUsage || settings.showProcesses || settings.showThreads">STATS</div>
                <div class="stat" v-if="settings.showCpuUsage">CPU Usage: {{cpuUsed}}%</div>
                <div class="stat" v-if="settings.showProcesses">Processes: {{processCount.processCount}}</div>
                <div class="stat" v-if="settings.showThreads">Threads: {{processCount.threadCount}}</div>
            </div>
        </div>
    `
}

Notes:

We are using the Timer API to update the date and time every minute. To format the date and time we use the Day.js library. And to keep our code declarative we use the Computed Properties feature of Vue.js.

The above code has a methods: object containing two functions. These functions are called from within the mounted() method, which fires on page load.

watchNeededDataSettings() defines the data that needs to be retrieved and watched for changes. If we only needed to fetch one piece of data we could call it directly from within the mounted() method. But since our dashboard shows several data types it's more efficient to loop through an array containing their names.

You'll notice the use of $watch. That's a feature of Vue.js that watches an expression for changes.

updateNeededInfo() is a function that does two things. First, it removes all listeners that are not currently enabled. Since we allow the various pieces of data in our dashboard to be toggled on or off, it's ineficient to keep calling something that isn't in use. Second, the function uses the Timer API to trigger the date and time to get updated every minute.

The functions in the computed: object format the date and time, and the cpuUsage, and set {{date}}, {{time}}, and {{cpuUsed}} variables that are used in the template.