Refresh ui-node

Hello,

I am developing a node that lets the user modify an incoming msg.payload via the UI (html table).
By clicking the "save" button - this payload is being sent to the output. This works great so far and the user can see all the changes in the UI.

But here's the problem:
Refreshing the page causes the table to restore the msg.payload that came in before it was modified. How can I update the internals of my node while saving?

BTW: Connecting the (my-node) output to a function node and the function node to the (my-node) input solves the problem - but that's not pretty.

Thanks!

What do you do with the new value?

Put a change event handler on each cell of your table, sending the new value every time it is updated. No need to wait and send the whole row of data using a Save button.

The better examples I've seen even use css tricks to provide instant feedback, like setting a background color to green on success, or red on failure.

@shrickus the save button is a 'feature'. The user has to finish before sending the complete data. The whole html/css part already works perfectly, I do show changes with colors and values inside the table data. The table shows changes live and even after clicking send the table contains the modified data (the outgoing payload is fine as well).

But hitting the F5 key will reset the table to the start state, that's the issue here.

But, from an interface perspective refresh not saving changes makes sense. After all, actions might have been taken but the safe button hasn’t been pressed. Thus should from that perspective refresh result in showing the unchanged data from before the editing (aka refresh resulting in undoing the changes), or should refresh result in showing changed data that isn’t saved yet, and if so what makes pressing the save button special?

Is your kind of user intuitively going to expect refresh means changed even without saving, as it is something that differs from most implementations out there in the real world. Like I understand why you want this to happen, but have you thought about wanting it for the right reasons and does it match the logical progression of your interface design.

@afelix ok.. so maybe I wasn't very specific about the actual issue.

Current status:

  1. Changing the data -> not saving the data -> F5 = old values
    ------- This should be like this and is ok right now
  2. Changing the data -> not saving the data -> leaving the page -> return later = old values
    ------- This should be like this and is ok right now
  3. Changing the data -> saving the data (new payload is being sent to the output) -> F5 = old values
    ------- This should not be like this and is the only problem here

This is about same problem as in this thread API addwidget()
Conclusion is - On redeploy/refresh the API resend last incoming payload.

As you can't change this behavior, you should respect it and build your widget to follow the underlying logic.

2 Likes

If storeFrontEndInputAsState is true then the state should be updated with the new value, so a refresh should then have the latest value.

1 Like

@dceejay I have tried this setting already and I wasn't sure if I need to add something else as well.

Changing storeFrontEndInputAsState from false to true, clears the table completely after "saving" and I have to manually inject a new payload to get it back. I am using "<tr ng-repeat="n in msg.payload[0] track by $index" ....".

@hotNipi Well, I think resending the last incoming payload is not bad at all and I respect the underlying logic.

I just thought that there is a possibility to programmatically send a msg to the input inside the myUiNode.js.
Especially because it is quite easy to fix by adding a function node:
123

Hey guys,
@Steve-Mcl and myself have a very annoying issue with our contextmenu node.
Summarized:

  1. you click on a clickable shape in our new SVG node
  2. an output message is being send (incl. X/Y coordinates) to our contextmenu node
  3. the contextmenu node displays a menu at the location (specified in the input message)
  4. you click somewhere else in the dashboard, so the context menu disappears
  5. you deploy something.
  6. now suddenly the last input message is received again, so we display the contextmenu AGAIN....

Does anybody have a tip to avoid the last input message being repeated?
We have played with the storeFrontEndInputAsState value but that doesn't help :woozy_face:

Bart, did we try setting this parameter to true?

Re reading that comment makes me think we should give it a try?

I think I have tried this about 350 times :yum:
But will try it again for last time tomorrow ...

May be something like this may work:
Add time (when click was made) to the msg.payload
At the contextmenu ignore incoming messages where time is older than (choose acceptable time)

1 Like

Morning @hotNipi,
I didn't have time yet to test your proposal, but I'm wondering if we simply could do it like this:

$scope.$watch('msg', function(msg) {
   // Ignore undefined messages.
   if (!msg) {
      return;
   }

   // Avoid messages being handled twice.  Otherwise messages will be replayed after a deploy, causing animations to be started automatically
   if (msg.alreadyHandled) {
      return;
   }

   msg.alreadyHandled = true; 

   ...
}

Or do you really think (based on your experience) we need to work with a timestamp? Is this perhaps because we need to allow the same message to be handled multiple times during a (short) time period, e.g. because AngularJs calls the watch multiple times anyway?
Thanks !!
Bart

I think the unwanted replaymessage should be filtered in beforeEmit.
If you change message properties the node-red-dashboard/ ui.js does not know about this change unless you send the message back to the server side. Then you can take advantage of storeFrontEndInputAsState parameter. Haven't tested but just reading the code it should work like this.

from ui.js :

// This is the handler for messages coming back from the UI
    var handler = function (msg) {
        if (msg.id !== opt.node.id) { return; }  // ignore if not us
        if (settings.readOnly === true) {
            msg.value = currentValues[msg.id];
        } // don't accept input if we are in read only mode
        else {
            var converted = opt.convertBack(msg.value);
            if (opt.storeFrontEndInputAsState === true) {
                currentValues[msg.id] = converted;
                replayMessages[msg.id] = msg;

Should do some test on this. I'm not sure ..

1 Like

So
I think the unwanted replaymessage should be filtered in beforeEmit <-- wrong. socket emits what is stored. beforeEmit is in use only with new incoming messages.

I did this and it nearly does the trick

$scope.$watch('msg', function (msg) {							
	if (!msg) {								
		return;
	}	
	if(msg.handled && msg.handled == true){
		console.log('scope already hanlded',msg)
		return
	}
        console.log('scope fresh msg',msg)
	var m = { }
	m.payload = 'ignore me'
	m.topic = msg.topic
	m.handled = true
	$scope.send(m)
					
	// do your stuff with msg
																
});

What is wrong about this is that you'll get always that second message back to scope

normal run:
image

after deploy:
image

I think proper way is to introduce flag for ui-node's indicating that socket should not send replaymessage.

Is this something we can do ourselves in our nodes, or do we need to call @dceejay. Alias 'the master' (of disaster :wink:)

It is decision for master to even accept the proposal or deny. By looking the code it is a bit more than just "introduce a flag" thing. Needs some deeper analyse and testing but I'm a bit too busy to fit it right away into my near schedule.

EDIT: overall there may be other ways also to get it and we just not smart enough to find it :slight_smile:

1 Like

Possible workaround:

As you can not ignore first message always (it may be correct message) make your own first message

$scope.init = function(config){
	$scope.acceptIncomingMessages = false
	//do your regular init phase stuff

	//send init message
	var initmessage = {payload:"init-"+$scope.unique}
	$scope.send(initmessage)													
}

$scope.$watch("msg", function (msg) {							
	if (!msg) {								
		return;
	}					
	
	if(msg.payload == "init-"+$scope.unique){
		console.log("scope msg - init",msg)
		$scope.acceptIncomingMessages = true
		return
	}							
							
	if($scope.acceptIncomingMessages == false){
		console.log("scope msg - no way ",msg)
		return
	}
	console.log("scope msg - passed",msg)
	//continue here with all good messages passed 
}

Replay message will be sent before your first message and will be ignored.