(First allow me to say that I'm just beginning to learn Node-RED concepts; I went through some beginners' guides at nodered.org, and now am trying to extend what I learned so far).
I'm trying to build a flow that starts with a simple JSON tree like
[{"position":"1", "title":"element #1"},
{"position":"2", "title":"element #2"},
{"position":"3", "title":"element #3"}]
To build that treee I use a template node, property is set to msg.payload.
The number of array elements (in theory) is dynamic. To make sure that this tree is true JSON I added a JSON node converting from String to JSON object.
Next I wish to parse that object into a dynamic html table. For this I used a JS function node that's looping through the object and embedding its elements into the according html elements like this:
var return="";
for(var i=0;i<=msg.payload.length-1;i++){
var row=msg.payload[i];
if(row){
return+="<tr>";
return+="<td>"+row.position+"</td>";
return+="<td>"+row.title+"</td>";
return+="</tr>";
}else{
return+="no object at index "+i.toString();
}
}
msg.payload=return;
return msg;
The output of the function then should be passed into a 2nd template like this:
<html>
<head></head>
<body>
<table border="1">
<tr>
<td>#</td>
<td>Title</td>
</tr>
{{ payload }}
</table>
</body>
</html>
I would have expected that the function's result is inserted into the static table of my template, and that happens indeed but not the way I hoped: somehow the html elements that got created by my function are not recognized as what they shoud be; instead I see that they are rendered as
<tr><td>1</td><
instead of
<tr><td>1</td>
etc.
Result is that the browser does not recognize those elements and prints them together with their contents outside my static table
Questions:
what do I need to do so that my 2nd template recognizes my computed string as a set of html elements?
or is this probably a concept not suitable for my purpose?
I'm assuming you are using handelbars for your templating engine. In that case use:
{{{ payload }}}
Instead of
{{ payload }}
However a more elegant approach would be this:
<html>
<head></head>
<body>
<table border="1">
<tr>
<td>#</td>
<td>Title</td>
</tr>
{{#each payload}}
<tr><td>{{this.position}}</td><td>{{this.title}}</td></tr>
{{/each}}
</table>
</body>
</html>
then just
return msg.payload
again thanks to #als9xd for pointing me into the right direction; his 2nd idea indeed sounds much more elegant but first I couldn't get it to work. After some trial-and-error and looking up documentation for the template node I finally came up with this: removed the function node from my original question and then altered the 2nd template to this code:
<html>
<head></head>
<body>
<table border="1">
<tr>
<td>#</td>
<td>Title</td>
</tr>
{{#payload}}
<tr>
<td>{{position}}</td>
<td>{{title}}</td>
</tr>
{{/payload}}
</table>
</body>
</html>
Difference to #als9xd's example is that I replaced {{#each payload}} with a simple {{#payload}}, plus omitted this when referencing the object keys.
Could this be due to different Node-RED versions?
Anyways this is starting to be much fun!
Related
If I use hx-swap-oob, then I get an error:
<script src="https://unpkg.com/htmx.org#1.3.3/dist/htmx.js"></script>
<div id="sum">?</div>
<table>
<tr>
<td><button
hx-get="https://run.mocky.io/v3/b5a6902e-fc46-479b-92e4-9b4befc7a920"
hx-target="closest tr">1</button>
</td>
<td>one</td>
</tr>
</table>
After pressing "Run Code Snippet" and then press 1:
htmx:swapError
{
"message": "e.querySelectorAll is not a function",
"filename": undefined,
"lineno": undefined,
"colno": undefined
}
The mocky http endpoint returns this:
<tr>
<td>2</td><td>two</td>
</tr>
<div id="sum" hx-swap-oob="true">MAGIC</div>
In above example I use the non minified version, so the error message is: eltOrSelector.querySelectorAll is not a function
If I use this endpoint, it does not fail: https://run.mocky.io/v3/2ab904eb-23a9-4006-b68b-f112b55841f3
But in my usecase the new html fragment should be <tr>...</tr>, not a <div>.....
JS stacktrace:
Uncaught TypeError: eltOrSelector.querySelectorAll is not a function
at findAll (htmx.js:295)
at handleOutOfBandSwaps (htmx.js:501)
at selectAndSwap (htmx.js:712)
at doSwap (htmx.js:2284)
at handleAjaxResponse (htmx.js:2358)
at XMLHttpRequest.xhr.onload (htmx.js:2163)
Update
I could narrow down the issue to this:
This fails:
makeFragment('<tr><td>a</td></tr> <div>X</div>')
Update2
hmmm, now I know why it fails:
parseFromString() of Chrome is the root-cause:
Update3
Follow-up question: Make parseFromString() parse without validation
Update4
I created an issue, hoping someone with more creativity has an idea how to solve this: https://github.com/bigskysoftware/htmx/issues/469
As per your issue, HTMX team has created a config that can be enabled to support table fragments like this. Documented example here: https://htmx.org/examples/update-other-content/#oob. As noted, it is not IE11 compatible.
htmx.config.useTemplateFragments = true;
Execute the following in a js file after htmx has been loaded.
I created a new endpoint which returns a slightly different HTML fragment:
<table>
<tr id="row1">
<td>2</td><td>two</td>
</tr>
</table>
<div id="sum" hx-swap-oob="true">MAGIC</div>
<script src="https://unpkg.com/htmx.org#1.3.3/dist/htmx.js"></script>
<div id="sum">?</div>
before table
<table>
<tr id="row1">
<td><button
hx-get="https://run.mocky.io/v3/28a8e653-491c-4186-abd4-4f48f3579273"
hx-target="#row1" hx-select="#row1">1</button>
</td>
<td>one</td>
</tr>
</table>
after table
HTMX uses parseFromString() internally, and this method validates the input. If there is non valid HTML, it might truncate elements.
makeFragment() of HTMX wraps the string which it sends to parseFromString(). HTMX looks at the first element in the response. In above question it is <tr>. Thus htmx uses <table> to wrap the whole http response.
The solution is the return a "normal" element to htmx (not <tr>).
Normal html fragments in htmx are exactly one element, so that it does not matter. If you use hx-swap-oob the server sends several elements to the client.
This is more or less a work-around. It would be great if htmx would handle the original string.
I have a basic Rails app with a nested association and I am doing a standard 'line items' type view where I am dynamically adding and removing rows from a template tag. I have a select that triggers a AJAX call to replace all the nested row selects (the context changed). The expenditure controller handles the overall form and the nested-form controller is used on other pages to handle the adding and removal of rows:
<div data-controller="expenditure nested-form">
<select data-target="expenditure.budget" data-action="change->expenditure#update_related"></select>
<table>
<thead></thead>
<tbody>
inserted rows...
<tr>
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
<template data-target="nested-form.template">
<tr data-new_record="true">
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
</template>
</tbody>
</table>
</div>
It works fine. I can add and remove rows and if I change the expenditure.budget select all the expenditure.budgetItemSelect targets get updated EXCEPT for the one inside the template. It's as if it's missing from the entire scope of the controller. I had them nested before but now have them in the same div data-controller="expenditure nested-form" to double check and it still doesn't work. Checked spelling and even tried removing the data-target="nested-form.template". No errors in the browser console. Am I missing something obvious here?
UPDATE
Hmmm... it seems that the template tag is read only and NOT part of the DOM which is why this fails.
I managed a hack where I replaced the contents of the entire template but that seems to break the controller that adds / deletes the rows 🤦♂️.
UPDATE 2
I found a workaround - If someone can improve this code I will accept this as a better answer.
It seems to be an issue with the <template> tag in HTML5.
I have a workaround but it's ugly.
<div data-controller="expenditure nested-form">
<select data-target="expenditure.budget" data-action="change->expenditure#update_related"></select>
<table>
<thead></thead>
<tbody>
inserted rows...
<tr>
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
<template id="expenditure_items_template">
<tr data-new_record="true">
<td>
<select data-target="expenditure.budgetItemSelect"></select>
</td>
</tr>
</template>
<script type="text/template" data-target="nested-form.template" id="expenditure_items_template_script">
</script>
</tbody>
</table>
</div>
Here is what I did in my controller:
// find the template
var template = document.getElementById("expenditure_items_template");
// load the template contents
var new_template = document.importNode(template.content, true);
// replace the select with my new content (off screen)
new_template.getElementById('expenditure_expenditure_items_attributes_NEW_RECORD_budget_item_id').innerHTML = select.innerHTML;
// clear the new script place holder
document.getElementById("expenditure_items_template_script").innerHTML = "";
// set the new updated template into the script tag
document.getElementById("expenditure_items_template_script").appendChild(new_template);
I basically have two templates - one <template> which holds the raw HTML and the second <script> that works with Stimulus.
I have a table with some data:
<table id="myTable">
<thead>
<tr>
<th><h1>Name</h1></th>
<th><h1>Picture</h1></th>
<th><h1>Likes</h1></th>
<th><h1>Time</h1></th>
</tr>
</thead>
<tbody>
***loop***
<tr>
<td>{placeholder1}</td>
<td><img src="{placeholder2}" alt=""></td>
<td>{placeholder3}</td>
<td>{placeholder4}</td>
</tr>
***end loop***
</tbody>
</table>
I have a js function who gets some data from server by POST request every 10 minutes. <tr></tr> block needs to be repeated several times.
HTML code become more and more complex and I need a solution with layouts and placeholders. I need a direction to search :)
All I need is:
Store <tr></tr> pattern with placeholders to insert it into my webpage. How could I achieve it with js?
How could I mark the places where I need data to be inserted?
Okay since you are using jQuery,
This may be your HTML
<table>
<tbody id="myTableBody">
<!-- Your elements will be placed here -->
</tbody>
</table>
I will assume you are using $.ajax or $.post in either of those, add a callback function property success
$.ajax({
// ... your properties,
success: function(data) {
// basic template for of your "tr"
var trTemplate = [
'<tr>',
'<td></td>',
'<td><img src="" alt=""></td>',
'<td></td>',
'<td></td>',
'</tr>'
].join('')
// get the tbody elemen
var $myBody = $('#myTableBody')
// if you want to clean up the current content of $myBody,
// if it is not the case just remove the following line
$myBody.empty()
// assuming data is an array of elements / entities
data.forEach(function(element){
var $tr = $(trTemplate)
$tr.find('td').eq(0).text(element.placeholder1)
$tr.find('img').attr('src', element.placeholder2)
$tr.find('td').eq(2).text(element.placeholder3)
$tr.find('td').eq(3).text(element.placeholder4)
$myBody.append($tr)
})
}
})
This is example of how you could do it, there are many ways to improve this for performance and so on. Please use it only as reference
If you're using jQuery then this is pretty straightforward. You need to id your elements so that you can reference them individually. Say you start with:
<tr id="tr-0" >
content...
</tr>
and then in javascript..
var id = $('#tr-0').attr('id');
var num = parseInt(id.substring(3));
num++;
$('#tr-0').after('<tr id='+num+'>content...</tr>');
obviously you need to figure how you're getting the content for each row but hopefully you can see that it wouldn't be too hard to fill each row with custom data.
Although you can use jQuery, simpler ways exist. jQuery will require you to add additional steps that aren't really necessary. If you want to use as few Javascript packages as possible, go with jQuery.
But, I highly recommend Vue.js for Laravel projects. There are instructions from Laracasts on how to set it up. But, I have created a jsfiddle with a working set of Vue.js with the v-for directive. Checkout the JSFiddle here.
If you have questions, I'll answer as much as I can.
I am trying to create an HTML table to display the contents of a mongodb collection. The collection contains data about different customer orders from a small business. Some of the data will always exist in a given document, for example the customer name and phone number. However, some of the pieces of data within each document need to vary, such as items ordered since one customer may order 1 item, while another may order 3 items. So, if I have a mongodb collection with documents that contain an arbitrary number fields in each document, how can I dynamically add them to an HTML table to display the document's contents? As an example of the type of display I am looking for, here is the hard-coded HTML for the fields which I know will remain constant.
<!DOCTYPE html>
<html>
<head>
<head>
<title>Invoice Report</title>
<style type="text/css">
body {font-family:sans-serif;color:#4f494f;}
form input {border-radius: 7.5px;}
h5 {display: inline;}
.label {text-align: right}
.ordersBook {float:left; padding-top: 10px;}
.name {width:100%;float:left; padding:3px;}
.wrapper { padding-left: 25px; padding-top: 20px}
</style>
<script type="text/javascript">
var itemRe = /item*/;
}
</script>
</head>
</head>
<body>
<div class="invoice">
<h4>Order Form:</h4>
<table border="1">
<tr>
<th>Name:</th>
<td>{{rows['name']}}</td>
</tr>
<tr>
<th>Created:</th>
<td>{{rows['created']}}</td>
</tr>
<tr>
<th>Phone:</th>
<td>{{rows['phone']}}</td>
</tr>
<tr>
<th>Email:</th>
<td>{{rows['email']}}</td>
</tr>
<tr>
<th>Item:</th>
<td>{{rows['item']}}</td>
</tr>
</div>
<tr>
<th>Quantity:</th>
<td>{{rows['qty']}}</td>
</tr>
<tr>
<th>Color:</th>
<td>{{rows['color']}}</td>
</tr>
<tr>
<th>Quote:</th>
<td>{{rows['quote']}}</td>
</tr>
</table>
</div>
</body>
</html>
It would probably be better to create the entire table dynamically, but I am not sure where the proper place to do this is
in a javascript function within the HTML file?
Or maybe in the pymongo file which maintains the info in the mongodb database?
The python code which handles sending the mongodb document to the HTML form uses a python Bottle template.
#bottle.route('/view/<_id>', method = 'GET')
def show_invoice(_id):
client = pymongo.MongoClient("mongodb://localhost")
db = client.orders
collection = db.myorders
from bson.objectid import ObjectId
result = collection.find_one({'_id': ObjectId(_id)})
return bottle.template('invoice', rows = result)
I greatly appreciate any help that someone may be able to offer! =)
Looking over the documentation for the bottle template engine, it looks like you can use 'ifs' and 'fors' to accomplish this.
For instance, if you order is stored at rows['orders'] and you don't know how many there are, in your template you can place:
%for item in rows['orders']:
<td>{{item}}</td>
%end
or say that you need to display a special warning if your customer is ordering an item that is frequently on backorder, and you've passed in another variable, 'backorder', that specifies this:
%if backorder:
<span>This item is frequently on backorder</span>
%end
I haven't tested either of these, but I've done similar things using the Django and Flask template engines. I pulled these samples from here:
http://bottlepy.org/docs/dev/tutorial.html#templates
and the 'Bottle Template to Format the Output' section here:
http://bottlepy.org/docs/dev/tutorial_app.html#using-bottle-for-a-web-based-todo-list
Hope this helps!
I'm becoming a huge fan of CoffeeKup, but I'm wondering how I can use literal HTML in such a template? For example when I just want to copy-paste some existing, non-CoffeeKup markup.
You can add arbitrary text with the text function:
text '<p>foo</p>'
For multiline strings, CoffeeScript's heredocs are a pleasure:
text '''
<table>
<tr>
<td>Foo</td>
</tr>
</table>
'''
Just make sure you have autoescape set to false (the default value).
Have you tried using this tool too for converting your markup instead of just inlining in?
https://github.com/brandonbloom/html2coffeekup
It's linked from the main github page of coffeekup. If it handles your old markup it would be a more elegant solution.