Learning plugin development - problem with creating Knockout functions

Hi, I want to learn plugin development, and I am following the knockout tutorial to create a button that can change text to upper case: Knockout tutorial - Introduction lesson 5 of 5

I have to variables:
some_text and max_diff

I want to set both variables in setting view, then display them in tab view.
I also created a button to change the text value to all cap.

So far, I can only display the value of max_diff in the tab view, but it doesn't work for "some_text".
What did I do wrong?

I created a test plugin, with following structure:


Content of "_ _ init _ _.py"

# coding=utf-8
from __future__ import absolute_import

### (Don't forget to remove me)
# This is a basic skeleton for your plugin's __init__.py. You probably want to adjust the class name of your plugin
# as well as the plugin mixins it's subclassing from. This is really just a basic skeleton to get you started,
# defining your plugin as a template plugin, settings and asset plugin. Feel free to add or remove mixins
# as necessary.
# Take a look at the documentation on what other plugin mixins are available.

import octoprint.plugin
import logging

class TestPlugin(octoprint.plugin.StartupPlugin,

    def __init__(self):
        self._logger = logging.getLogger("octoprint.plugins.Test")
        self._logger.info("this is Test init func")

    def on_after_startup(self):
        self._logger.info("### Test Plugin ###")
        self._logger.info("### Test Plugin max_diff: %s" % self._settings.get(["max_diff"]))

    ##~~ SettingsPlugin mixin

    def get_settings_defaults(self):
        return dict(
            # put your plugin's default settings here/
            max_diff=1,  # some variable for playing around
            some_text="abcdefg", # some text variable for playing around

    def get_template_configs(self):
        return [
            dict(type="settings", custom_bindings=False),
            dict(type="tab", custom_bindings=False)

    ##~~ AssetPlugin mixin

    def get_assets(self):
        # Define your plugin's asset files to automatically include in the
        # core UI here.
        return dict(

    def gcode_processor(self, comm, line, *args, **kwargs):
        if "MACHINE_TYPE" not in line:
            return line

        from octoprint.util.comm import parse_firmware_line

        # Create a dict with all the keys/values returned by the M115 request
        printer_data = parse_firmware_line(line)

        logging.getLogger("octoprint.plugin." + __name__).info(
            "Machine type detected: {machine}.".format(machine=printer_data["firmware_name"]))

        return line

# If you want your plugin to be registered within OctoPrint under a different name than what you defined in setup.py
# ("OctoPrint-PluginSkeleton"), you may define that here. Same goes for the other metadata derived from setup.py that
# can be overwritten via __plugin_xyz__ control properties. See the documentation for that.
__plugin_name__ = "Test"

# Starting with OctoPrint 1.4.0 OctoPrint will also support to run under Python 3 in addition to the deprecated
# Python 2. New plugins should make sure to run under both versions for now. Uncomment one of the following
# compatibility flags according to what Python versions your plugin supports!
# __plugin_pythoncompat__ = ">=2.7,<3" # only python 2
# __plugin_pythoncompat__ = ">=3,<4" # only python 3
__plugin_pythoncompat__ = ">=2.7,<4"  # python 2 and 3

def __plugin_load__():
    global __plugin_implementation__
    __plugin_implementation__ = TestPlugin()

    global __plugin_hooks__
    __plugin_hooks__ = {
        "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information,
        # "octoprint.comm.protocol.scripts": __plugin_implementation__.message_on_connect,
        "octoprint.comm.protocol.gcode.received": __plugin_implementation__.gcode_processor,

Content of "TestPlugin.js"

 * View model for OctoPrint-TestPlugin
 * Author: tester
 * License: AGPLv3
$(function() {
    function TestPluginViewModel(parameters) {
        var self = this;

        // assign the injected parameters, e.g.:
        // self.loginStateViewModel = parameters[0];
        // self.settingsViewModel = parameters[1];

        // TODO: Implement your plugin's view model here.
        self.settings = parameters[0];

        self.currentMaxDiff = ko.observable();
        self.newMaxDiff = ko.observable();
        self.someText = ko.observable();

        self.showMaxDiff = function() {

        self.capitalizeSomeText = function() {
            var currentText = self.someText();        // Read the current value
            self.someText(currentText.toUpperCase()); // Write back a modified value

         self.onBeforeBinding = function() {

    /* view model class, parameters for constructor, container to bind to
     * Please see http://docs.octoprint.org/en/master/plugins/viewmodels.html#registering-custom-viewmodels for more details
     * and a full list of the available options.
        // This is the constructor to call for instantiating the plugin
        construct: TestPluginViewModel,
        // ViewModels your plugin depends on, e.g. loginStateViewModel, settingsViewModel, ...
        //dependencies: [ /* "loginStateViewModel", "settingsViewModel" */ ],
        // Elements to bind to, e.g. #settings_plugin_TestPlugin, #tab_plugin_TestPlugin, ...
        //elements: [ /* ... */ ]
        // instantiation via the parameters argument
        dependencies: ["settingsViewModel"],

        // Finally, this is the list of selectors for all elements we want this view model to be bound to.
        elements: ["#tab_plugin_TestPlugin"]

Content of "TestPlugin_settings.jinja2"

<form class="form-horizontal">
    <div class="control-group">
        <!-- <label class="control-label">{{ _('Maximum Difference') }}</label> -->
        <div class="controls">
            <!-- <label for="inputMaxDiff"></label> -->
            <!-- class="input-block-levels" -->
            <p> Enter Max Diff: <input type="text" data-bind="value: settings.plugins.TestPlugin.max_diff"></p>
            <p> Enter any text: <input type="text" data-bind="value: settings.plugins.TestPlugin.some_text"></p>

Content of "TestPlug_tab.jinja2"

<div class="input-append">
    <input type="text" class="input-block-level" data-bind="value: max_diff">


    <p> newMaxDiff: <strong data-bind="text: newMaxDiff"></strong></p>
    <p> currentMaxDiff: <strong data-bind="text: currentMaxDiff"></strong></p>
    <p> max_diff: <strong data-bind="text: max_diff"></strong></p>
    <p> settings.plugins.TestPlugin.max_diff: <strong data-bind="text: settings.plugins.TestPlugin.max_diff"></strong></p>
    <p> someText: <strong data-bind="text: someText"></strong></p>
    <button class="btn btn-primary" data-bind="click: showMaxDiff">Refresh</button>
    <button class="btn btn-primary" data-bind="click: capitalizeSomeText">Go Cap!</button>

custom_bindings=False needs to be custom_bindings=True if you are calling functions within your own viewmodel.

1 Like

after I made custom_bindings=True, either variable worked.

1 Like

If that worked, don't forget to mark the solution.

what I meant was, after I made custom_bindings=True, both variables stopped displaying on the tab view.

if you use custom_bindings=True you also need to change your references in the jinja template. So the above would become.


correction, your parameter is not getting assigned to settingsViewModel so should be


Hi jneilliii

I ended up trying to start afresh with the basics:
I created a new project, let's call it sensor_plotter, again, following the tutorial I have created:

  • _ _ init _ _.py
  • sensor_plotter.js
  • sensor_plotter_settings.jinja2
  • sensor_plotter_navbar.jinja2
  • sensor_plotter_tab.jinja2

General description:
In __ init __.py, there are two variables created by get_settings_defaults() : url, and some_var
The get_template_config sets settings, navbar, and tab to custom_bindings=True

In sensor_plotter.js, self.some_settings = parameters[0];

and in settings, tab, and navbar, I called the variables by some_settings.settings.plugins..sensor_plotter.url

The results:
in setting, I am able to assign a new value to url or some_var with input: <input type="text" data-bind="value: some_settings.settings.plugins.sensor_plotter.url">
<input type="text" data-bind="value: some_settings.settings.plugins.sensor_plotter.some_var">

in tab, I am able to bind data with some_settings.settings.plugins..sensor_plotter.url and some_settings.settings.plugins..sensor_plotter.some_var, and each tie I update with input from setting, the tab will update accordingly.

in navbar, however it will not bind data with <a href="#" data-bind="attr: {href: some_settings.settings.plugins.sensor_plotter.url}">Sensor Plotter navbar

What went wrong?

Actual code content:

_ init _.py

# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import octoprint.plugin
import logging
class SensorPlotterPlugin(octoprint.plugin.StartupPlugin,

    def get_settings_defaults(self):
        return dict(url="https://google.com",
                    some_var="this is some variable")

    def get_template_configs(self):
        return [
            dict(type="settings", custom_bindings=True),
            dict(type="navbar", custom_bindings=True),
            dict(type="tab", custom_bindings=True),

    def get_assets(self):
        return dict(

__plugin_name__ = "Sensor Plotter"
__plugin_pythoncompat__ = ">=2.7,<4"
__plugin_implementation__ = SensorPlotterPlugin()


$(function() {
    function sensor_plotterViewModel(parameters) {
        var self = this;
        self.some_settings= parameters[0];

        construct: sensor_plotterViewModel,
        dependencies: ["settingsViewModel"],
        elements: ["#tab_plugin_sensor_plotter", "#settings_plugin_sensor_plotter", "#navbar_plugin_sensor_plotter"]


        <label>{{ _('URL') }}</label>
            <input type="text"  data-bind="value: some_settings.settings.plugins.sensor_plotter.url">
            <input type="text"  data-bind="value: some_settings.settings.plugins.sensor_plotter.some_var">


<p>URL: <strong data-bind="text: some_settings.settings.plugins.sensor_plotter.url"></strong></p>
<p>URL: <strong data-bind="text: some_settings.settings.plugins.sensor_plotter.some_var"></strong></p>
<a href="#" data-bind="attr: {href: some_settings.settings.plugins.sensor_plotter.url}">Sensor Plotter tab</a>


<a href="#" data-bind="attr: {href: some_settings.plugins.sensor_plotter.url}">Sensor Plotter navbar</a>

ah, i figured it out.

  • In the javascript, I need to specific self.pluginName = "sensor_plotter";

  • in the javascript, the parameters index need to match the dependences self.some_settings = parameters[0];, and dependencies: ["settingsViewModel"],. Index 0 matches index 0;

  • in the javascript, the elements need to be included, and the plugin names need to match: elements: ["#tab_plugin_sensor_plotter", "#settings_plugin_sensor_plotter", "#navbar_plugin_sensor_plotter"]

  • in jinja2, the variables are referenced by variables declared in settings' parameter (point #2 above). e.g. some_settings.settings.plugins.sensor_plotter.url