UNPKG

node-red-contrib-file-template

Version:

A Node-RED node that loads HTML template content from the filesystem with automatic file watching, reloading, and built-in file editor

412 lines (358 loc) 18.3 kB
<!-- Node-RED File Template Configuration --> <script type="text/javascript"> RED.nodes.registerType('file-template', { category: 'function', color: '#E7E7AE', defaults: { name: {value: ""}, filename: {value: "", required: true}, format: {value: "handlebars"}, syntax: {value: "mustache"}, template: {value: ""}, field: {value: "payload", required: true}, fieldType: {value: "msg"}, output: {value: "str"}, loadOnBoot: {value: false}, bootData: {value: "{}"}, bootDelay: {value: 1000}, fileContent: {value: ""} }, inputs: 1, outputs: 1, icon: "file.png", label: function() { if (this.name) { return this.name; } if (this.filename) { return "file-template: " + this.filename.split('/').pop(); } return "file-template"; }, labelStyle: function() { return this.name ? "node_label_italic" : ""; }, oneditprepare: function() { var node = this; // Initialize field type selector with proper typedInput configuration $("#node-input-field").typedInput({ default: 'msg', typeField: $("#node-input-fieldType"), types: ['msg','flow','global'] }); // File picker button $("#node-input-filename-picker").click(function() { // Simple file picker - in real implementation you might want a proper file browser var filename = prompt("Enter template file path:", node.filename || "Html/TodoList.html"); if (filename) { $("#node-input-filename").val(filename); } }); // NEW: Load file content functionality function updateFileStatus(message, isError = false) { $("#file-content-status").text(message); $("#file-content-status").css('color', isError ? '#d32f2f' : '#666'); } function updateFileInfo(info) { $("#file-content-info").text(info); } // Load file content button $("#node-input-load-file-btn").click(function() { var filename = $("#node-input-filename").val().trim(); if (!filename) { updateFileStatus("Please specify a template file first", true); return; } updateFileStatus("Loading file..."); // Make AJAX request to load file content $.ajax({ url: 'file-template/load-file', type: 'POST', data: JSON.stringify({ filename: filename }), contentType: 'application/json', success: function(data) { if (data.error) { updateFileStatus("Error: " + data.error, true); } else { $("#node-input-fileContent").val(data.content); updateFileStatus("File loaded successfully"); updateFileInfo(`${data.size} chars, modified: ${new Date(data.mtime).toLocaleString()}`); } }, error: function(xhr, status, error) { updateFileStatus("Failed to load file: " + error, true); } }); }); // Save file content button $("#node-input-save-file-btn").click(function() { var filename = $("#node-input-filename").val().trim(); var content = $("#node-input-fileContent").val(); if (!filename) { updateFileStatus("Please specify a template file first", true); return; } if (!content) { updateFileStatus("No content to save", true); return; } if (!confirm("Are you sure you want to save the changes to the file?\n\nThis will overwrite: " + filename)) { return; } updateFileStatus("Saving file..."); // Make AJAX request to save file content $.ajax({ url: 'file-template/save-file', type: 'POST', data: JSON.stringify({ filename: filename, content: content }), contentType: 'application/json', success: function(data) { if (data.error) { updateFileStatus("Error: " + data.error, true); } else { updateFileStatus("File saved successfully"); updateFileInfo(`${data.size} chars saved`); } }, error: function(xhr, status, error) { updateFileStatus("Failed to save file: " + error, true); } }); }); // Auto-load file content when filename changes $("#node-input-filename").on('blur', function() { var filename = $(this).val().trim(); if (filename && filename !== node.filename) { // Auto-load if file content is empty if (!$("#node-input-fileContent").val().trim()) { $("#node-input-load-file-btn").click(); } } }); // Initialize file content if already stored if (node.fileContent) { $("#node-input-fileContent").val(node.fileContent); updateFileStatus("Content loaded from configuration"); } // Template format change handler $("#node-input-format").change(function() { var format = $(this).val(); if (format === "handlebars" || format === "mustache") { $("#template-syntax-row").show(); } else { $("#template-syntax-row").hide(); } }); // Initialize visibility $("#node-input-format").trigger('change'); // Load on boot change handler $("#node-input-loadOnBoot").change(function() { if ($(this).is(':checked')) { $("#boot-settings-row").show(); $("#boot-data-row").show(); $("#boot-delay-row").show(); } else { $("#boot-settings-row").hide(); $("#boot-data-row").hide(); $("#boot-delay-row").hide(); } }); // Initialize boot settings visibility $("#node-input-loadOnBoot").trigger('change'); }, oneditsave: function() { // Save file content to node configuration this.fileContent = $("#node-input-fileContent").val(); // Validate filename var filename = $("#node-input-filename").val().trim(); if (!filename) { return false; } } }); </script> <!-- Node Configuration Dialog --> <script type="text/html" data-template-name="file-template"> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <input type="text" id="node-input-name" placeholder="Node name"> </div> <div class="form-row"> <label for="node-input-filename"><i class="fa fa-file-o"></i> Template File</label> <input type="text" id="node-input-filename" placeholder="path/to/template.html" style="width: calc(100% - 30px);"> <button type="button" id="node-input-filename-picker" class="editor-button" style="width: 25px; margin-left: 5px;"> <i class="fa fa-folder-open"></i> </button> </div> <!-- NEW: File Content Editor --> <div class="form-row"> <label for="node-input-fileContent" style="width: 100%;"><i class="fa fa-edit"></i> Template Content <button type="button" id="node-input-load-file-btn" class="editor-button" style="float: right; margin-top: -3px;"> <i class="fa fa-refresh"></i> Load File </button> <button type="button" id="node-input-save-file-btn" class="editor-button" style="float: right; margin-right: 5px; margin-top: -3px;"> <i class="fa fa-save"></i> Save to File </button> </label> <textarea id="node-input-fileContent" rows="12" placeholder="File content will appear here..." style="font-family: monospace; font-size: 12px; width: 100%; resize: vertical;"></textarea> <div style="margin-top: 5px; color: #666; font-size: 11px;"> <span id="file-content-status">Select a file and click "Load File" to edit content</span> <span style="float: right;" id="file-content-info"></span> </div> </div> <div class="form-row"> <label for="node-input-format"><i class="fa fa-code"></i> Template Format</label> <select id="node-input-format"> <option value="handlebars">Handlebars/Mustache</option> <option value="plain">Plain HTML</option> </select> </div> <div class="form-row" id="template-syntax-row"> <label for="node-input-syntax"><i class="fa fa-cogs"></i> Template Syntax</label> <select id="node-input-syntax"> <option value="mustache">Mustache {{variable}}</option> <option value="handlebars">Handlebars {{#each}} {{/each}}</option> </select> </div> <div class="form-row"> <label for="node-input-field"><i class="fa fa-edit"></i> Template Data</label> <input type="text" id="node-input-field" placeholder="payload"> <input type="hidden" id="node-input-fieldType"> </div> <div class="form-row"> <label for="node-input-output"><i class="fa fa-sign-out"></i> Output</label> <select id="node-input-output"> <option value="str">Set msg.template to string</option> <option value="parsed">Set msg.template to parsed object</option> </select> </div> <div class="form-row"> <label for="node-input-template"><i class="fa fa-code"></i> Fallback Template</label> <textarea id="node-input-template" rows="4" placeholder="Optional inline template as fallback"></textarea> </div> <div class="form-row"> <label for="node-input-loadOnBoot"><i class="fa fa-power-off"></i> Load on Boot</label> <input type="checkbox" id="node-input-loadOnBoot" style="width: auto; margin: 0;"> <span style="margin-left: 10px;">Automatically process template when Node-RED starts</span> </div> <div class="form-row" id="boot-settings-row" style="display: none;"> <label style="width: 100%; margin-bottom: 5px;"><i class="fa fa-cog"></i> <strong>Boot Settings</strong></label> </div> <div class="form-row" id="boot-delay-row" style="display: none;"> <label for="node-input-bootDelay"><i class="fa fa-clock-o"></i> Boot Delay (ms)</label> <input type="number" id="node-input-bootDelay" placeholder="1000" min="0" max="30000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">Delay before processing template on startup</span> </div> <div class="form-row" id="boot-data-row" style="display: none;"> <label for="node-input-bootData"><i class="fa fa-database"></i> Boot Data (JSON)</label> <textarea id="node-input-bootData" rows="3" placeholder='{"title": "Dashboard", "user": "Admin"}' style="font-family: monospace;"></textarea> <span style="display: block; margin-top: 5px; color: #999; font-size: 12px;"> JSON data for template variable substitution on boot (e.g., values for {{variable}} placeholders). Use {} for static templates without variables. </span> </div> <div class="form-tips"> <p><b>File Template Node</b></p> <p>Loads HTML template content from the filesystem with automatic file watching and reloading.</p> <ul> <li><strong>Template File:</strong> Path to your HTML template file (relative to Node-RED working directory)</li> <li><strong>Template Content:</strong> Edit file content directly in the editor with load/save functionality</li> <li><strong>Template Data:</strong> The message property containing data for template substitution</li> <li><strong>File Watching:</strong> Automatically reloads when the template file changes</li> <li><strong>Mustache Syntax:</strong> Use {{variable}} for simple substitution, {{#each array}} for iteration</li> <li><strong>Load on Boot:</strong> Automatically processes template when Node-RED starts up</li> </ul> </div> </script> <!-- Node Help Text --> <script type="text/html" data-help-name="file-template"> <p>A Node-RED node that loads HTML template content from the filesystem with automatic file watching and reloading.</p> <h3>Features</h3> <ul> <li><strong>File Editing:</strong> Load, edit, and save template files directly in the Node-RED editor</li> <li><strong>Live Preview:</strong> See file content and modifications before deployment</li> <li><strong>Auto-reload:</strong> Changes to files are automatically detected and reloaded</li> <li><strong>Template Processing:</strong> Supports Handlebars/Mustache syntax for dynamic content</li> <li><strong>Boot Processing:</strong> Can automatically process templates when Node-RED starts</li> </ul> <h3>File Editor</h3> <p>The Template Content editor allows you to:</p> <ul> <li><strong>Load File:</strong> Click "Load File" to populate the editor with the current file content</li> <li><strong>Edit Content:</strong> Modify the template directly in the monospace editor</li> <li><strong>Save Changes:</strong> Click "Save to File" to write your changes back to the filesystem</li> <li><strong>Auto-load:</strong> Content is automatically loaded when you specify a filename</li> </ul> <h3>Inputs</h3> <dl class="message-properties"> <dt>payload <span class="property-type">object</span></dt> <dd>The data object to use for template variable substitution.</dd> <dt class="optional">topic <span class="property-type">string</span></dt> <dd>Optional message topic that gets passed through.</dd> </dl> <h3>Outputs</h3> <dl class="message-properties"> <dt>template <span class="property-type">string</span></dt> <dd>The processed template content with variables substituted.</dd> <dt>_fileTemplate <span class="property-type">object</span></dt> <dd>Metadata about the template file including filename, last modified time, and content length.</dd> </dl> <h3>Configuration</h3> <dl> <dt>Template File</dt> <dd>Path to the HTML template file, relative to Node-RED working directory.</dd> <dt>Template Format</dt> <dd>Choose between Handlebars/Mustache for variable substitution or Plain HTML for static content.</dd> <dt>Template Data</dt> <dd>The message property (default: payload) containing the data for template variable substitution.</dd> <dt>Output</dt> <dd>Whether to output the result as a string or attempt to parse it as JSON.</dd> <dt>Fallback Template</dt> <dd>Optional inline template content to use if the file cannot be loaded.</dd> <dt>Load on Boot</dt> <dd>When enabled, automatically processes the template when Node-RED starts up.</dd> <dt>Boot Delay</dt> <dd>Delay in milliseconds before processing template on startup (default: 1000ms).</dd> <dt>Boot Data</dt> <dd>JSON data for template variable substitution on boot (e.g., values for {{variable}} placeholders). Use {} for static templates without variables.</dd> </dl> <h3>Features</h3> <ul> <li><strong>File Watching:</strong> Automatically detects changes to the template file and reloads content</li> <li><strong>Template Processing:</strong> Supports Mustache-style variable substitution ({{variable}})</li> <li><strong>Error Handling:</strong> Graceful handling of missing files with fallback options</li> <li><strong>Status Updates:</strong> Visual status indicators showing file load status and errors</li> <li><strong>Flexible Data Source:</strong> Can pull template data from msg, flow, or global context</li> <li><strong>Load on Boot:</strong> Automatically processes template when Node-RED starts, eliminating the need for manual trigger flows</li> </ul> <h3>Template Syntax Examples</h3> <p>Simple variable substitution:</p> <pre> &lt;h1&gt;{{title}}&lt;/h1&gt; &lt;p&gt;Hello {{user.name}}&lt;/p&gt; </pre> <p>With input data:</p> <pre> { "title": "My Dashboard", "user": { "name": "John Doe" } } </pre> <p>Results in:</p> <pre> &lt;h1&gt;My Dashboard&lt;/h1&gt; &lt;p&gt;Hello John Doe&lt;/p&gt; </pre> <h3>Status Indicators</h3> <ul> <li><span style="color: green;"></span> Green: Template file loaded successfully</li> <li><span style="color: yellow;"></span> Yellow: Using fallback inline template</li> <li><span style="color: red;"></span> Red: Error loading file or processing template</li> <li><span style="color: grey;"></span> Grey: No file specified or node inactive</li> </ul> </script>