Skip to content

Commit

Permalink
Merge pull request #168 from flowforge/data-table
Browse files Browse the repository at this point in the history
Widget: Data Table
  • Loading branch information
joepavitt authored Sep 6, 2023
2 parents a644638 + 1f500e0 commit 9b59c47
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export default {
{ text: 'ui-form', link: '/nodes/widgets/ui-form' },
{ text: 'ui-slider', link: '/nodes/widgets/ui-slider' },
{ text: 'ui-switch', link: '/nodes/widgets/ui-switch' },
{ text: 'ui-table', link: '/nodes/widgets/ui-table' },
{ text: 'ui-template', link: '/nodes/widgets/ui-template' },
{ text: 'ui-text', link: '/nodes/widgets/ui-text' },
{ text: 'ui-text-input', link: '/nodes/widgets/ui-text-input' },
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/node-examples/ui-table.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions docs/nodes/widgets/ui-table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
props:
Group: Defines which group of the UI Dashboard this widget will render in.
Size: Controls the width of the button with respect to the parent group. Maximum value is the width of the group.
Label: The text shown within the button.
Max Rows: Defines the maximum number of data-rows to render in the table. Excess rows will be available through pagination control.
Auto Columns: If checked, then the columns are calculated automatically based on the contents of received messages.
Columns: If "Auto Columns" is false, then these columns are used when rendering the table instead.
---

<script setup>
</script>

# Data Table `ui-table`

Renders a set of data in a tabular format. Expects an input (`mag.payload`) in the format of:

```json
[{
"colA": "A",
"colB": "Hello",
"colC": 3
}, {
"colA": "B",
"colB": "World",
"colC": 5
}]
```

The table will be rendered with colums `colA`, `colB` and `colC`, unless "Columns" are explicitely defined on the node, with "Auto Columns" toggled off.

## Properties

<PropsTable/>

## Examples

![Example of a Data Table](../../assets/images/node-examples/ui-table.png "Example of a Data Table"){data-zoomable}
*Example of a rendered data table in a Dashboard.*

![Example of a Paginated Table](../../assets/images/node-examples/ui-table-pagination.png "Example of a Paginated Table"){data-zoomable}
*Example of a paginated table which has 10 rows of data, but with "Max Rows" set to 5.*
2 changes: 1 addition & 1 deletion nodes/widgets/ui_chart.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
align: 'right',
paletteLabel: 'chart',
label: function () {
return this.name || (~this.label.indexOf('{' + '{') ? null : this.label) || this.mode + ' input'
return this.name || (~this.label.indexOf('{' + '{') ? null : this.label) || this.mode + ' chart'
},
labelStyle: function () { return this.name ? 'node_label_italic' : '' },
oneditprepare: function () {
Expand Down
149 changes: 149 additions & 0 deletions nodes/widgets/ui_table.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<script type="text/javascript">
(function () {
RED.nodes.registerType('ui-table', {
category: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.label.category'),
color: 'rgb(119, 198, 204)',
defaults: {
group: { type: 'ui-group', required: true },
name: { value: '' },
label: { value: 'text' },
order: { value: 0 },
width: {
value: 0,
validate: function (v) {
const width = v || 0
const currentGroup = $('#node-input-group').val() || this.group
const groupNode = RED.nodes.node(currentGroup)
const valid = !groupNode || +width <= +groupNode.width
$('#node-input-size').toggleClass('input-error', !valid)
return valid
}
},
height: { value: 0 },
maxrows: { value: 0, validate: RED.validators.number() },
autocols: { value: true },
columns: {
// value: [{ key: '', label: '' }]
value: null
}
},
inputs: 1,
outputs: 0,
outputLabels: function () { return this.mode },
icon: 'font-awesome/fa-table',
paletteLabel: 'table',
label: function () {
return this.name || 'table'
},
oneditprepare: function () {
$('#node-input-size').elementSizer({
width: '#node-input-width',
height: '#node-input-height',
group: '#node-input-group'
})
const autocols = $('#node-input-autocols')

$(autocols).change(() => {
const val = autocols.is(':checked')
const customColsDiv = $('#node-input-columns-container')
if (val) {
customColsDiv.hide()
} else {
customColsDiv.show()
}
})

// Columns Editor
function generateColumn (i, col) {
const container = $('<li/>', { style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);' })
const row = $('<div/>').appendTo(container)
$('<div/>', { style: 'padding-top:5px; padding-left:175px;' }).appendTo(container)
$('<div/>', { style: 'padding-top:5px; padding-left:120px;' }).appendTo(container)

$('<i style="color: var(--red-ui-form-text-color, #eee); cursor:move; margin-left:3px;" class="node-input-column-handle fa fa-bars"></i>').appendTo(row)

$('<input/>', { class: 'node-input-column-key', type: 'text', style: 'margin-left:7px; width:calc(50% - 32px);', placeholder: 'Key', value: col.key }).appendTo(row)
$('<input/>', { class: 'node-input-column-label', type: 'text', style: 'margin-left:7px; width:calc(50% - 32px);', placeholder: 'Label', value: col.label }).appendTo(row)

const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row)
const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan)
$('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton)

deleteButton.click(function () {
container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' })
container.fadeOut(300, function () {
$(this).remove()
})
})

$('#node-input-column-container').append(container)
}

$('#node-input-add-column').click(function () {
generateColumn($('#node-input-column-container').children().length + 1, {})
$('#node-input-column-container-div').scrollTop($('#node-input-column-column-div').get(0).scrollHeight)
})

for (let i = 0; i < this.columns?.length; i++) {
const col = this.columns[i]
generateColumn(i + 1, col)
}

$('#node-input-column-container').sortable({
axis: 'y',
handle: '.node-input-column-handle',
cursor: 'move'
})
},
oneditsave: function () {
const columns = $('#node-input-column-container').children()
const node = this
node.columns = []
columns.each(function (i) {
const column = $(this)
const o = {
label: column.find('.node-input-column-label').val(),
key: column.find('.node-input-column-key').val()
}
node.columns.push(o)
})
}
})
})()
</script>

<script type="text/html" data-template-name="ui-table">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name">
</div>
<div class="form-row">
<label for="node-input-group"><i class="fa fa-table"></i> Group</label>
<input type="text" id="node-input-group">
</div>
<div class="form-row">
<label><i class="fa fa-object-group"></i> Size</label>
<input type="hidden" id="node-input-width">
<input type="hidden" id="node-input-height">
<button class="editor-button" id="node-input-size"></button>
</div>
<div class="form-row">
<label for="node-input-maxrows"><i class="fa fa-tag"></i> Max Rows</label>
<input type="number" id="node-input-maxrows">
</div>
<div class="form-row" style="display:flex;">
<label for="node-input-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> Columns</label>
<div class="form-row node-input-column-container-row" style="margin-bottom: 0px; width:calc(70% + 15px);">
<div>
<input type="checkbox" checked id="node-input-autocols" style="display: inline-block; width: auto; margin: 0px 0px 0px 4px;">
<label style="width:auto" for="node-input-autocols">Auto Calculate Columns</label>
</div>
<div id="node-input-columns-container">
<div id="node-input-column-container-div" style="box-sizing:border-box; border-radius:5px; height:257px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:scroll; display:inline-block; width: 100%;">
<ol id="node-input-column-container" style="list-style-type:none; margin:0;"></ol>
</div>
<a href="#" class="editor-button editor-button-small" id="node-input-add-column" style="margin-top:4px;"><i class="fa fa-plus"></i> <span>column</span></a>
</div>
</div>
</div>
</script>
16 changes: 16 additions & 0 deletions nodes/widgets/ui_table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = function (RED) {
function TableNode (config) {
const node = this

// create node in Node-RED
RED.nodes.createNode(this, config)

// which group are we rendering this widget
const group = RED.nodes.getNode(config.group)

// inform the dashboard UI that we are adding this node
group.register(node, config)
}

RED.nodes.registerType('ui-table', TableNode)
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"ui-slider": "nodes/widgets/ui_slider.js",
"ui-switch": "nodes/widgets/ui_switch.js",
"ui-text": "nodes/widgets/ui_text.js",
"ui-table": "nodes/widgets/ui_table.js",
"ui-chart": "nodes/widgets/ui_chart.js",
"ui-markdown": "nodes/widgets/ui_markdown.js",
"ui-template": "nodes/widgets/ui_template.js"
Expand Down
13 changes: 13 additions & 0 deletions ui/src/stylesheets/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,17 @@ main {
}
.active .v-switch__thumb {
opacity: 1.0;
}

/* Data Table */

.v-table {
background: rgb(var(--v-theme-group-background));
}

.v-table, .v-table .v-table__wrapper > table > thead > tr > th {
color: rgb(var(--v-theme-on-group-background));
}
.v-table .v-table__wrapper > table > thead > tr > th {
font-weight: 600;
}
2 changes: 2 additions & 0 deletions ui/src/widgets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import UITextInput from './ui-text-input/UITextInput.vue'
/* eslint-disable import/no-unresolved */
import UIButton from './ui-button/UIButton'
import UIDropdown from './ui-dropdown/UIDropdown'
import UITable from './ui-table/UITable'
import UIForm from './ui-form/UIForm'
import UIChart from './ui-chart/UIChart'
import UIRadioGroup from './ui-radio-group/UIRadioGroup'
Expand All @@ -29,6 +30,7 @@ import UITextInput from './ui-text-input/UITextInput'
export default {
'ui-button': UIButton,
'ui-dropdown': UIDropdown,
'ui-table': UITable,
'ui-form': UIForm,
'ui-chart': UIChart,
'ui-radio-group': UIRadioGroup,
Expand Down
Loading

0 comments on commit 9b59c47

Please sign in to comment.