SettingsUpdated event only fires once per session

I'm seeing a weird issue where the SettingsUpdated event only fires once per session. This means that if a user changes their settings in my plugin & saves, then changes them again & saves, the changes in the second save will not take effect. If the page is refreshed the settings will reflect the settings from the first save. Then the user can make the 2nd set of changes and save again for them to take effect.

Has anyone else seen this? Any idea what to look into?

OctoPrint version: 1.7.2

Screen recording: L9AcKnt4BX - YouTube

Haven't seen that one before personally. Is there anything in the logs indicating something going wrong during the initial save process? Context is king when it comes to assisting with these types of questions, so if you have a link to your plugin code it could also help spot any irregularities.

I was just looking at octoprint.log (that's the file you see streamed on the left in the screen recording). Is there another I should look at?

sorry, I didn't actually watch the video, I see now what I think you may be running into. When binding a list of dictionaries on the knockout side, if you don't bind just quite right then changes to the list are only seen if the length of the list changes, not if properties of the dictionary are changed. So if you add another item to your list I bet it saves. There are a couple of ways to combat this.

One way to combat this is by using a pop-up editor rather than relying on inline editing of the dictionaries. I've done this in several of my plugins that involve lists of data, most notably Tasmota and TPLinkSmartplug.

Simpler example would be what I did with event manager in core octoprint. You basically need to make your individual items observable as you create them in the add function. Basically creating a secondary view model like here.

I see. I apologize - I'm still relatively unsure when it comes to DOM/web development - especially non-vanilla things like jinja/knockout. I'm referencing the js you linked. I already tried using self.name = ko.observable(customEvent.name); in the self.CustomEvent function, but that just gave me some text like "[object Object]" in the octoprint settings window.

Below is what I've got for relevant jinja2 & js - maybe you can spot something obvious I'm doing wrong? This is so close to working! It's just this updating of the setting issue that I've got left

<!-- ko foreach: customEvents -->
<tr data-bind="with: new $root.CustomEvent($data)">
    <td><input type="text" data-bind="value: name"></input></td>
    <td><input type="text" data-bind="value: message"></input></td>
    <td style="max-width: 37px;">
        <button class="btn btn-danger" title="Remove" data-bind="click: remove">
            <i class="fa fa-minus"></i>
        </button>
    </td>
</tr>
<!-- /ko -->
<tr style="justify-content: flex-end;"><td style="overflow: visible; flex-grow: 0; padding: 0; border: 0;">
    <button class="btn btn-primary" title="Add new" data-bind="click: $root.newCustomEvent">
        <i class="fa fa-plus"></i>
    </button>
self.newCustomEvent = () =>
    root.selectedHook().customEvents.push({
        name: ko.observable(''),
        message: ko.observable('')
    })
    
self.CustomEvent = function(customEvent){
    let self = this;    
    self.name = customEvent.name;
    self.message = customEvent.message;
    self.remove = () => root.selectedHook().customEvents.remove(customEvent);
}

Thanks for all your help!

sorry, I'm not super familiar with the => style of js programming and again, it's hard to tell without full context, but I would have something like this.

<!-- ko foreach: customEvents -->
<tr>
    <td><input type="text" data-bind="value: name"></input></td>
    <td><input type="text" data-bind="value: message"></input></td>
    <td style="max-width: 37px;">
        <button class="btn btn-danger" title="Remove" data-bind="click: $root.remove">
            <i class="fa fa-minus"></i>
        </button>
    </td>
</tr>
<!-- /ko -->
<tr style="justify-content: flex-end;"><td style="overflow: visible; flex-grow: 0; padding: 0; border: 0;">
    <button class="btn btn-primary" title="Add new" data-bind="click: newCustomEvent">
        <i class="fa fa-plus"></i>
    </button>
self.newCustomEvent = function() {
    self.settingsViewModel.plugins.pluginidentifier.customEvents.push({
        name: ko.observable(''),
        message: ko.observable('')
    });
}

self.remove = function(data) {
    self.settingsViewModel.plugins.pluginidentifier.customEvents.remove(data);
}