diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js
index 4dc14dbe..5f3c85f9 100644
--- a/src/octoprint/static/js/app/main.js
+++ b/src/octoprint/static/js/app/main.js
@@ -498,36 +498,32 @@ $(function() {
//~~ view model binding
settingsViewModel.requestData(function() {
- ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
+ var viewModelsToInit = additionalViewModels.concat([
+ [settingsViewModel, document.getElementById("settings_dialog")],
+ [connectionViewModel, document.getElementById("connection_wrapper")],
+ [printerStateViewModel, document.getElementById("state_wrapper")],
+ [gcodeFilesViewModel, document.getElementById("files_wrapper")],
+ [temperatureViewModel, document.getElementById("temp")],
+ [controlViewModel, document.getElementById("control")],
+ [gcodeViewModel, document.getElementById("gcode")],
+ [terminalViewModel, document.getElementById("term")],
+ [navigationViewModel, document.getElementById("navbar")],
+ [appearanceViewModel, document.getElementsByTagName("head")[0]],
+ [printerStateViewModel, document.getElementById("drop_overlay")],
+ [logViewModel, document.getElementById("logs")],
+ [timelapseViewModel, document.getElementById("timelapse")],
+ [slicingViewModel, document.getElementById("slicing_configuration_dialog")]
+ ]);
- ko.applyBindings(connectionViewModel, document.getElementById("connection_wrapper"));
- ko.applyBindings(printerStateViewModel, document.getElementById("state_wrapper"));
- ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_wrapper"));
- ko.applyBindings(temperatureViewModel, document.getElementById("temp"));
- ko.applyBindings(controlViewModel, document.getElementById("control"));
- ko.applyBindings(terminalViewModel, document.getElementById("term"));
- var gcode = document.getElementById("gcode");
- if (gcode) {
- gcodeViewModel.initialize();
- ko.applyBindings(gcodeViewModel, gcode);
- }
- //ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
- ko.applyBindings(navigationViewModel, document.getElementById("navbar"));
- ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]);
- ko.applyBindings(printerStateViewModel, document.getElementById("drop_overlay"));
- ko.applyBindings(logViewModel, document.getElementById("logs"));
+ // apply bindings
+ var rerenderControls = false;
+ _.each(viewModelsToInit, function(viewModelData) {
+ if (!Array.isArray(viewModelData) || viewModelData.length != 2) {
+ return;
+ }
- var timelapseElement = document.getElementById("timelapse");
- if (timelapseElement) {
- ko.applyBindings(timelapseViewModel, timelapseElement);
- }
-
- ko.applyBindings(slicingViewModel, document.getElementById("slicing_configuration_dialog"));
-
- // apply bindings and signal startup
- _.each(additionalViewModels, function(additionalViewModel) {
- var viewModel = additionalViewModel[0];
- var targets = additionalViewModel[1];
+ var viewModel = viewModelData[0];
+ var targets = viewModelData[1];
if (targets === undefined) {
return;
@@ -542,17 +538,30 @@ $(function() {
}
_.each(targets, function(target) {
- try {
- ko.applyBindings(viewModel, target);
- } catch (exc) {
- console.log("Could not apply bindings for additional view model " + viewModel + ": " + exc.message);
+ if (target) {
+ try {
+ ko.applyBindings(viewModel, target);
+ } catch (exc) {
+ console.log("Could not apply bindings for additional view model " + viewModel + ": " + exc.message);
+ }
+ } else {
+ console.log("Could not apply binding for view model " + viewModel + ", target does not exist");
}
});
if (viewModel.hasOwnProperty("onAfterBinding")) {
viewModel.onAfterBinding();
}
+
+ if (viewModel.hasOwnProperty("getAdditionalControls")) {
+ controlViewModel.additionalControls = controlViewModel.additionalControls.concat(viewModel.getAdditionalControls());
+ rerenderControls = true
+ }
});
+
+ if (rerenderControls) {
+ controlViewModel.rerenderControls();
+ }
});
//~~ UI stuff
diff --git a/src/octoprint/static/js/app/viewmodels/control.js b/src/octoprint/static/js/app/viewmodels/control.js
index c12767cb..a66e5d3b 100644
--- a/src/octoprint/static/js/app/viewmodels/control.js
+++ b/src/octoprint/static/js/app/viewmodels/control.js
@@ -26,6 +26,9 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
self.feedbackControlLookup = {};
+ self.controlsFromServer = [];
+ self.additionalControls = [];
+
self.keycontrolActive = ko.observable(false);
self.keycontrolHelpActive = ko.observable(false);
self.keycontrolPossible = ko.computed(function() {
@@ -84,6 +87,11 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
}
};
+ self.rerenderControls = function() {
+ var allControls = self.controlsFromServer.concat(self.additionalControls);
+ self.controls(self._processControls(allControls))
+ };
+
self.requestData = function() {
$.ajax({
url: API_BASEURL + "printer/command/custom",
@@ -96,7 +104,8 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
};
self._fromResponse = function(response) {
- self.controls(self._processControls(response.controls));
+ self.controlsFromServer = response.controls;
+ self.rerenderControls();
};
self._processControls = function(controls) {
@@ -117,9 +126,49 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
} else if (control.type == "section") {
control.children = self._processControls(control.children);
}
+
+ var js;
+ if (control.hasOwnProperty("javascript")) {
+ js = control.javascript;
+
+ // if js is a function everything's fine already, but if it's a string we need to eval that first
+ if (!_.isFunction(js)) {
+ control.javascript = function(data) {
+ eval(js);
+ };
+ }
+ }
+
+ if (control.hasOwnProperty("enabled")) {
+ js = control.enabled;
+
+ // if js is a function everything's fine already, but if it's a string we need to eval that first
+ if (!_.isFunction(js)) {
+ control.enabled = function(data) {
+ return eval(js);
+ }
+ }
+ }
+
return control;
};
+ self.isCustomEnabled = function(data) {
+ if (data.hasOwnProperty("enabled")) {
+ return data.enabled(data);
+ } else {
+ return self.isOperational() && self.loginState.isUser();
+ }
+ };
+
+ self.clickCustom = function(data) {
+ if (data.hasOwnProperty("javascript")) {
+ data.javascript(data);
+ } else {
+ self.sendCustomCommand(data);
+ }
+ };
+
self.sendJogCommand = function(axis, multiplier, distance) {
if (typeof distance === "undefined")
distance = $('#jog_distance button.active').data('distance');
@@ -185,17 +234,17 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
self.sendSelectToolCommand = function(data) {
if (!data || !data.key()) return;
- var data = {
+ var payload = {
command: "select",
tool: data.key()
- }
+ };
$.ajax({
url: API_BASEURL + "printer/tool",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
- data: JSON.stringify(data)
+ data: JSON.stringify(payload)
});
};
@@ -211,7 +260,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data)
})
- }
+ };
var data = undefined;
if (command.type == "command" || command.type == "parametric_command" || command.type == "feedback_command") {
// single command
diff --git a/src/octoprint/templates/tabs/control.jinja2 b/src/octoprint/templates/tabs/control.jinja2
index 93c54ef6..1c0d78a1 100644
--- a/src/octoprint/templates/tabs/control.jinja2
+++ b/src/octoprint/templates/tabs/control.jinja2
@@ -105,12 +105,12 @@