UI Tabular Table Sorting

So, to clarify, changing to temp.unshift(msg.payload[0]); produces an undefined output; with temp.unshift(msg.payload) I was getting the output I showed. Could you confirm what I need in the function and what exactly is the change in tabulator? Do I go back to context.set (I change it to flow.set)

Oh, my fault then. I thought that was just what you wanted it to look like. If the array was working without referencing position 0, then use that. Keep in the function whatever gives the desired output for now. You can always optimize it later. But if you're getting the array out of the function like you want, use that.

You'll want to change your (scope) function that receives the msg from Node-Red. Make sure it uses the setData() function for the table instead of any other function. That will make it so the table fills itself with the incoming data and nothing else.

Not yet. Get your data to display first, then change it. The problem with flow.set() is that any part of the program can change the variable. You don't want that. You want only the function it belongs in to change it. But like I say, make your data display first, then go back and optimize.

Sorry, still unable to get this figured out on my own. This function generates the output arrays, which do not display. Changing to msg.payload[0] causes output to go undefined. Only way to display is changing msg.payload[0] in the tabulator function.

var temp = context.get("scanArray");
temp.unshift(msg.payload);
if(temp.length == 11){ //Whatever is one greater than the length of table you want
    temp.pop();
}
context.set("scanArray", temp);
msg.payload = temp;
return msg;

Ok. What's your template code?

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/luxon@3.0.4/build/global/luxon.min.js"></script>
<div id="screener"></div>
<script>
    var screenerTable = new Tabulator("#screener", {
     	height:900, 
     	//textSize : 10,
     	layout:"fitColumns",
     	columns:[ //Define Table Columns
    	 	{title:"", field:"note", width:78},
    	 	{title:"Coin", field:"currency", hozAlign:"center", width:70},
    	 	{title:"Exchange", field:"exchange", hozAlign:"center", width:80},
    	 	{title:"UP", field:"percent", hozAlign:"center", width:50,
    	 	 formatter: function(cell, formatterParams){var value = cell.getValue(); if(value == null) {return ""}; if (value !== null) {if(value >= 0){cell.getElement().style.color ='#609f70'} else {cell.getElement().style.color ='#ff4a68'} return value +'%';}}    
    	 	},
     	 	{title:"W", field:"win", hozAlign:"center", width:22	 	    
    	 	},
    	 	{title:"24Hr", field:"change", hozAlign:"center", width:65, formatterParams:{precision:0},
             formatter: function(cell, formatterParams){var value = cell.getValue(); if(value == null) {return ""}; if (value !== null) {if(value >= 0){cell.getElement().style.color ='#609f70'} else {cell.getElement().style.color ='#ff4a68'} return value +'%';}}	    
    	 	},
    	 	{title:"B", field:"b_coin", hozAlign:"center", width:20, formatter:function(cell, formatterParams, onRendered) {var value = cell.getValue(); if(value == null) {return ""}; return "<span style='color:#2962ff; font-weight:bold;'>" + cell.getValue() + "</span>";}},  
    	 	{title:"G", field:"g_coin", hozAlign:"center", width:20},
    	 	{title:"Volume", field:"volume", hozAlign:"right", width:95,formatter:"money", formatterParams:{thousand:",", precision:0}},
    	 	{title:"Price", field:"price", hozAlign:"right", width:70, formatter:"money", formatterParams:{thousand:",", precision:3}},
    	 	{title:"Time", field:"time", hozAlign:"center", width:90}
     	],
    });
    
    screenerTable.on("rowClick", function(e, row){ 
    	var fetchOptions = {
      	    method:'POST',
            headers:{'Content-Type':'application/json'},
            body:JSON.stringify(row.getData())
        };
        fetch("https://red.interscope.link/table-out", fetchOptions)
            .catch(function(error){alert(error)});
    });
    
    (function(scope){
        scope.$watch('msg', function(msg){
            if(msg && msg.payload.flag) {
                screenerTable.addData([msg.payload], true);
            }
        });
    })(scope);
</script>



Change this:

To this:

    (function(scope){
        scope.$watch('msg', function(msg){
            if(msg && msg.payload.flag) {
                screenerTable.setData(msg.payload, true);
            }
        });
    })(scope)

You were receiving an array and placing it into another array by bracketing [] your msg.payload. You also were adding the data to the table, which would have caused huge chunks to be added to the table instead of just redoing the table with the new data. Keep your function the same as that should work. The only thing that should break it now is if you're passing in an array from your data output to your function. At that point, you'll need to reference position 0 of that array. But from what you showed, that shouldn't be an issue.

Nothing is getting rendered, not even blank rows. Still nothing shown getting saved in context tab.

Output seems ok:

[{"currency":"GALA","exchange":"Gate.io","percent":null,"win":null,"change":"-1.01","b_coin":"✔","g_coin":"✓","price":"0.029493","volume":"158806076.58777","time":"2:01:24 PM","symbol":"GATEIO:GALAUSDT","note":"➦","flag":true},{"currency":"GALA","exchange":"Gate.io","percent":null,"win":null,"change":"-1.06","b_coin":"✔","g_coin":"✓","price":"0.029479","volume":"158809360.74504","time":"2:01:14 PM","symbol":"GATEIO:GALAUSDT","note":"➦","flag":true}]

I tested changing to flow.set just to see what was getting saved and it in fact showing scanArray being saved with output data. This is the function

var temp = flow.get("scanArray");
temp.unshift(msg.payload);
if(temp.length == 20){ //Whatever is one greater than the length of table you want
    temp.pop();
}
flow.set("scanArray", temp);
msg.payload = temp;
return msg;

Ok. So the function works and data is being pushed out of it the way we're expecting. That means there's something wrong with the template code, most likely a syntax error. Try this first:

    (function(scope){
        scope.$watch('msg', function(msg){
            if(msg && msg.payload.flag) {
                screenerTable.setData(msg.payload);
            }
        });
    })(scope)

I took the ,true out of the setData command. It's the only thing I could see that was wrong with the code. If that doesn't fix it, check your developers console in your browser (F12) and see what it's saying for errors.

The problem was the " if (msg && msg.payload.flag) " - I removed it and data started coming in - I assume it's not seeing the flag variable to check against - needs to be adjusted somehow.

Should I leave flow.set (rather than context.set) ?

So I see the table behavior is different now. On browser refresh the table still clears out, but when the next row gets added all of the previous come back.

P.S. the table data also disappears on re-deploying the flow, which I guess makes sense it's saved in memory only - perhaps it should be saved in local file system?

On the other hand, I see an issue with some of the fields not getting updated (getting carried through from the previous row) - I changed flow.set back context.set but that didn't help

If that's the case, make sure that whatever is setting msg.payload.flag is setting it as true or false.

msg.payload.flag = true;
//or
msg.payload.flag = false;

That will ensure you are having it set to something you can work with.

Good programming practice is to use the most local context required. In this case, context is more local than flow, so if context works, use it. Otherwise stick with flow.

Yes. This is a product of how it is stored. There are ways of having the table be able to pull the data anytime it's rebuilt (i.e. page refresh), but those become more complex and adding them on top of something not working can cause more issues. How you describe it functioning is exactly what I expected to hear.

This is a simple change, but it goes back to the last comment. The complexities come in because now you're adding file nodes and something like HTTP nodes. The implementation is easy. But if something already isn't working, it can make understanding them that much more complicated.

What fields aren't being updated? It may be data type issue or something of how it's handled in the table.

I think it's set properly since it was working until we made this change (?) The data now has a different structure - shouldn't this be adjusted accordingly?

This is where the flag is

{"currency":"MATIC","exchange":"Gate.io","time":"5:07:34 PM","note":"screener","flag":true}
  • What fields aren't being updated?

The PRICE and VOLUME fields are getting updated "intermittently" - sometimes appear correct, sometimes there is an issue every other row (where the values are just carried from the previous row). I believe this started happening couple of days ago (so probably not related to the table ). Those values come in via an API call and I think the issue occurs when the calls are made right after the other (without enough delay) - I don't think it's an API issue (never been the case), but likely the flow.set price/volume variables don't get updated in time before getting to the table.

It's probably not a data structure issue. It's probably when and how it's being read. The function storing it isn't reacting to the value. But whatever is choosing whether to send the payload on to the table or not should only be sending messages flagged true for the storing function to store and pass along. You may not even need the flag, but that depends on your program structure. Which brings me to another item. You can remove the column definition that uses the flag variable entirely. You're using that property data for message control, not data display. If a property item isn't defined in a column definition list, it will be ignored. So remove this line from your column definition:

{title:"", field:"note", width:78},

Flow.set or context.set won't affect the data that comes through. Those actions happen in microseconds. The computer system will sit and wait for a very long time in computer terms before the next API message is received. You may be getting updates too quickly, causing the effect that the server data is pulled twice before it is updated (i.e. the server updates every 5 seconds and the program is pulling every 3). I don't know and that's just a guess. But your new function is handling everything in a few clock cycles of the processor, so it's not going to inhibit your data. Not only that, but it's reacting to the data coming in. If you're concerned, monitor your API data coming in and see if that is changing as you expect.

I am actually showing content in the note field, but "flag" I was sending just for control purposes (I thought it needed to be sent in the msg in order to check. I can certainly remove flag.

This is how it's handled in the function that's outputting the data. If the data set is coming with a note = "screener" (in this case, it's data the set from a row-click, which I don't want re-display in the table) then "flag" is set to false.


// SCREENER

if (note === "screener") {flag = false} else {flag = true}

msgScreener.payload = {"currency": currency, "exchange": exchange,  "percent": percent, "win": win, "change": change, "b_coin": b_coin, "g_coin": g_coin, "price": price, "volume": volume, "time": msg.time, "symbol":symbol, "note":note, "flag":flag};


Yup, this sounds exactly right ...

My fault. I didn't keep track of the property names very closely. In any case, it's an extra thing you apparently don't need.

At this point, it sounds like everything may be working as expected. If you do decide to implement file storage and the ability for your table to pull it, just DM me and I'll walk you through it.

Well, almost everything :slight_smile: The flag checking if(msg && msg.payload.flag) is not solved. It was commented out, when I bring it back nothing gets through - as you said, it's not seeing/reacting to the value. By the way, I did remove flag from the payload and don't understand how it can know what the value is.

If the only messages that are going to the table are already sorted, all you need to do is this:

if(msg){

Then you're good to go. If instead you still need to filter out messages going to the table, put the flag check in your function and put all the function code inside of it:

if(msg.payload.flag){
    delete msg.payload.flag;
    //All the rest of the function code goes in here.
}

That will filter it pre-table still and not change anything else with the function.

Hi! I am implementing another table where rows get generated from an incoming array. Not sure now similar it is to the one we just did because in this case the rows don't get added sequentially - here the array comes in already containing all of the records sets, and that number (of rows) may vary each time - could be 5, could be 0, etc.

Here is an example with 2 data sets. The first task is to extract the desired fields, such as payload[0].profit.percent, payload[0].position.price.value, payload[0].data.created_at, etc. What is the best way to do this?

I found this jsonata example $append(payload.profit.{"usd":**.usd,"percent":**.percent}, payload.position.price.{"value":**.value}), which works but the different paths lead to output with multiple objects (for each record). Perhaps a template should be set up?

[{"id":21277788,"version":2,"account":{"id":31430770,"type":"paper_trading","name":"Paper Account","market":"Paper trading account","link":"/accounts/31430770"},"pair":"USDT_ETH","instant":false,"status":{"type":"waiting_targets","basic_type":"waiting_targets","title":"Waiting Targets"},"leverage":{"enabled":false},"position":{"type":"buy","editable":false,"units":{"value":"0.02","editable":false},"price":{"value":"1243.85","value_without_commission":"1242.61","editable":false},"total":{"value":"24.8770522"},"order_type":"market","status":{"type":"finished","basic_type":"finished","title":"Finished"}},"take_profit":{"enabled":true,"steps":[{"id":88864270,"order_type":"market","editable":true,"units":{"value":"0.02"},"price":{"value":"1306.04","type":"last","percent":null},"volume":"100.0","total":"26.1208","trailing":{"enabled":true,"percent":"-2.0"},"status":{"type":"to_process","basic_type":"to_process","title":"Pending"},"data":{"cancelable":true,"panic_sell_available":true},"position":1}]},"stop_loss":{"enabled":true,"breakeven":false,"order_type":"market","editable":true,"price":{"value":null},"conditional":{"price":{"value":"1206.53","type":"last","percent":null},"trailing":{"enabled":true,"percent":null}},"timeout":{"enabled":true,"value":2},"status":{"type":"trailing_activated","basic_type":"trailing_activated","title":"Trailing Activated"}},"reduce_funds":{"steps":[]},"market_close":{},"note":"","note_raw":null,"skip_enter_step":false,"data":{"editable":true,"current_price":{"day_change_percent":"-0.138","bid":"1242.5","ask":"1242.51","last":"1242.5","quote_volume":"847233893.982426"},"target_price_type":"price","base_order_finished":true,"missing_funds_to_close":0,"liquidation_price":null,"average_enter_price":"1243.85","average_close_price":null,"average_enter_price_without_commission":"1242.61","average_close_price_without_commission":null,"panic_sell_available":true,"add_funds_available":true,"reduce_funds_available":true,"force_start_available":false,"force_process_available":true,"cancel_available":true,"finished":false,"base_position_step_finished":true,"entered_amount":"0.02","entered_total":"24.8770522","closed_amount":"0.0","closed_total":"0.0","created_at":"2022-11-15T18:58:45.507Z","updated_at":"2022-11-15T18:58:45.507Z","type":"smart_trade"},"profit":{"volume":"-0.0519022","usd":"-0.0519022","percent":"-0.21","roe":null},"margin":{"amount":null,"total":null},"is_position_not_filled":false},{"id":21277785,"version":2,"account":{"id":31430770,"type":"paper_trading","name":"Paper Account","market":"Paper trading account","link":"/accounts/31430770"},"pair":"USDT_SOL","instant":false,"status":{"type":"waiting_targets","basic_type":"waiting_targets","title":"Waiting Targets"},"leverage":{"enabled":false},"position":{"type":"buy","editable":false,"units":{"value":"1.41","editable":false},"price":{"value":"14.16","value_without_commission":"14.15","editable":false},"total":{"value":"19.9714515"},"order_type":"market","status":{"type":"finished","basic_type":"finished","title":"Finished"}},"take_profit":{"enabled":true,"steps":[{"id":88864262,"order_type":"market","editable":true,"units":{"value":"1.41"},"price":{"value":"14.92","type":"last","percent":null},"volume":"100.0","total":"21.0372","trailing":{"enabled":true,"percent":"-2.0"},"status":{"type":"to_process","basic_type":"to_process","title":"Pending"},"data":{"cancelable":true,"panic_sell_available":true},"position":1}]},"stop_loss":{"enabled":true,"breakeven":false,"order_type":"market","editable":true,"price":{"value":null},"conditional":{"price":{"value":"13.78","type":"last","percent":null},"trailing":{"enabled":true,"percent":null}},"timeout":{"enabled":true,"value":2},"status":{"type":"trailing_activated","basic_type":"trailing_activated","title":"Trailing Activated"}},"reduce_funds":{"steps":[]},"market_close":{},"note":"","note_raw":null,"skip_enter_step":false,"data":{"editable":true,"current_price":{"day_change_percent":"0.929","bid":"14.12","ask":"14.13","last":"14.12","quote_volume":"117341581.0138"},"target_price_type":"price","base_order_finished":true,"missing_funds_to_close":0,"liquidation_price":null,"average_enter_price":"14.16","average_close_price":null,"average_enter_price_without_commission":"14.15","average_close_price_without_commission":null,"panic_sell_available":true,"add_funds_available":true,"reduce_funds_available":true,"force_start_available":false,"force_process_available":true,"cancel_available":true,"finished":false,"base_position_step_finished":true,"entered_amount":"1.41","entered_total":"19.9714515","closed_amount":"0.0","closed_total":"0.0","created_at":"2022-11-15T18:58:30.977Z","updated_at":"2022-11-15T18:58:30.977Z","type":"smart_trade"},"profit":{"volume":"-0.0821607","usd":"-0.0821607","percent":"-0.41","roe":null},"margin":{"amount":null,"total":null},"is_position_not_filled":false}]