123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- <script type="text/javascript">
- (async function () {
- // convert to i18 text
- function c_(x) {
- return RED._('@flowfuse/node-red-dashboard/ui-template:ui-template.' + x)
- }
- // TODO: move this to util.js for reuse & unit testing
- function hasProperty(obj, prop) {
- return Object.prototype.hasOwnProperty.call(obj, prop)
- }
-
- const template = []
-
- template.push('<template>')
- template.push(' <div>')
- template.push(' <h2>Counter</h2>')
- template.push(' <p>Current Count: {{ count }}</p>')
- template.push(' <p class="my-class">Formatted Count: {{ formattedCount }}</p>')
- template.push(' <v-btn @click="increase()">Increment</v-btn>')
- template.push(' </div>')
- template.push('</template>')
- template.push('')
- template.push('<script>')
- template.push(' export default {')
- template.push(' data() {')
- template.push(' // define variables available component-wide')
- template.push(' // (in <template> and component functions)')
- template.push(' return {')
- template.push(' count: 0')
- template.push(' }')
- template.push(' },')
- template.push(' watch: {')
- template.push(' // watch for any changes of "count"')
- template.push(' count: function () {')
- template.push(' if (this.count % 5 === 0) {')
- template.push(' this.send({payload: \'Multiple of 5\'})')
- template.push(' }')
- template.push(' }')
- template.push(' },')
- template.push(' computed: {')
- template.push(' // automatically compute this variable')
- template.push(' // whenever VueJS deems appropriate')
- template.push(' formattedCount: function () {')
- template.push(' return this.count + \'Apples\'')
- template.push(' }')
- template.push(' },')
- template.push(' methods: {')
- template.push(' // expose a method to our <template> and Vue Application')
- template.push(' increase: function () {')
- template.push(' this.count++')
- template.push(' }')
- template.push(' },')
- template.push(' mounted() {')
- template.push(' // code here when the component is first loaded')
- template.push(' },')
- template.push(' unmounted() {')
- template.push(' // code here when the component is removed from the Dashboard')
- template.push(' // i.e. when the user navigates away from the page')
- template.push(' }')
- template.push(' }')
- // eslint complaingin about unterminated string literal - need to do this oddity to get around it
- template.push('</' + 'script>')
- template.push('<style>')
- template.push(' /* define any styles here - supports raw CSS */')
- template.push(' .my-class {')
- template.push(' color: red;')
- template.push(' }')
- template.push('</style>')
-
- const defaultTemplate = template.join('\n')
-
- RED.nodes.registerType('ui-template', {
- category: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.label.category'),
- color: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.colors.dark'),
- defaults: {
- group: {
- type: 'ui-group',
- validate: function () {
- const tempScope = ($('#node-input-templateScope').val() !== undefined) ? $('#node-input-templateScope').val() : this.templateScope
- const groupVal = ($('#node-input-group').val() !== undefined) ? $('#node-input-group').val() : this.group
-
- if (tempScope !== 'local') {
- return true
- }
-
- if (tempScope === 'local') {
- if (!!groupVal && groupVal !== '_ADD_') {
- return true
- }
- }
- return false
- }
- }, // for when template is scoped to 'local' (default)
- page: {
- type: 'ui-page',
- validate: function () {
- const tempScope = ($('#node-input-templateScope').val() !== undefined) ? $('#node-input-templateScope').val() : this.templateScope
- const pageVal = ($('#node-input-page').val() !== undefined) ? $('#node-input-page').val() : this.page
-
- if (tempScope !== 'widget:page' && tempScope !== 'page:style') {
- return true
- }
- if (tempScope === 'widget:page' || tempScope === 'page:style') {
- if (!!pageVal && pageVal !== '_ADD_') {
- return true
- }
- }
- return false
- }
- }, // for when template is scoped to 'page'
- ui: {
- type: 'ui-base',
- validate: function () {
- const tempScope = ($('#node-input-templateScope').val() !== undefined) ? $('#node-input-templateScope').val() : this.templateScope
- const uiVal = ($('#node-input-ui').val() !== undefined) ? $('#node-input-ui').val() : this.ui
-
- if (tempScope !== 'widget:ui' && tempScope !== 'site:style') {
- return true
- }
- if (tempScope === 'widget:ui' || tempScope === 'site:style') {
- if (!!uiVal && uiVal !== '_ADD_') {
- return true
- }
- }
- return false
- }
- }, // for when template is scoped to 'site'
- name: { value: '' },
- order: { value: 0 },
- width: {
- value: 0,
- validate: function (v) {
- let valid = true
- if (this.templateScope !== 'global') {
- const width = v || 0
- const currentGroup = $('#node-input-group').val() || this.group
- const groupNode = RED.nodes.node(currentGroup)
- valid = !groupNode || +width <= +groupNode.width
- $('#node-input-size').toggleClass('input-error', !valid)
- }
- return valid
- }
- },
- height: { value: 0 },
- head: { value: '' },
- format: { value: defaultTemplate },
- storeOutMessages: { value: true },
- passthru: { value: true },
- resendOnRefresh: { value: true },
- templateScope: { value: 'local' },
- className: { value: '' }
- },
- inputs: 1,
- outputs: 1,
- icon: 'font-awesome/fa-code',
- paletteLabel: 'template',
- label: function () {
- if (this.name) { return this.name }
- const knownLabels = {
- local: 'template', // widget:group - kept as local for backward compatability
- 'widget:page': 'template',
- 'widget:ui': 'template',
- 'site:style': 'site style',
- 'page:style': 'page style'
- }
- return knownLabels[this.templateScope] || 'template'
- },
- labelStyle: function () { return this.name ? 'node_label_italic' : '' },
- oneditprepare: function () {
- if (RED.editor.__debug === true) {
- console.log('ui-template: oneditprepare') // useful for locating this code in the browser debugger
- }
- if (hasProperty(RED.editor, 'editText') && typeof RED.editor.editText === 'function') {
- $('#node-template-expand-editor').show()
- } else {
- $('#node-template-expand-editor').hide()
- }
- const that = this
- const $templateScope = $('#node-input-templateScope')
-
- // if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
- // as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
- if (RED.nodes.subflow(this.z)) {
- // change inputs from hidden to text & display them
- $('#node-input-width').attr('type', 'text')
- $('#node-input-height').attr('type', 'text')
- $('div.form-row.nr-db-ui-element-sizer-row').hide()
- $('div.form-row.nr-db-ui-manual-size-row').show()
- } else {
- // not in a subflow, use the elementSizer
- $('div.form-row.nr-db-ui-element-sizer-row').show()
- $('div.form-row.nr-db-ui-manual-size-row').hide()
- $('#node-input-size').elementSizer({
- width: '#node-input-width',
- height: '#node-input-height',
- group: '#node-input-group'
- })
- }
-
- if (typeof this.storeOutMessages === 'undefined') {
- this.storeOutMessages = true
- $('#node-input-storeOutMessages').prop('checked', true)
- }
-
- if (typeof this.passthru === 'undefined') {
- this.passthru = true
- $('#node-input-passthru').prop('checked', true)
- }
-
- if (typeof this.templateScope === 'undefined') {
- this.templateScope = 'local'
- $templateScope.val(this.templateScope)
- }
-
- $templateScope.on('change', function () {
- $('#template-row-group, #template-row-page, #template-row-ui, #template-row-class').hide()
-
- switch ($templateScope.val()) {
- case 'site:style':
- $('#template-row-ui').show()
- that.editor.getSession().setMode('ace/mode/css')
- break
- case 'page:style':
- $('#template-row-page').show()
- that.editor.getSession().setMode('ace/mode/css')
- break
- case 'site:script':
- $('#template-row-ui').show()
- that.editor.getSession().setMode('ace/mode/javascript')
- break
- case 'page:script':
- $('#template-row-page').show()
- that.editor.getSession().setMode('ace/mode/javascript')
- break
- case 'widget:ui':
- $('#template-row-ui').show()
- that.editor.getSession().setMode('ace/mode/html')
- break
- case 'widget:page':
- $('#template-row-page').show()
- that.editor.getSession().setMode('ace/mode/html')
- break
- default:
- $('#template-row-group, #template-row-class').show()
- that.editor.getSession().setMode('ace/mode/html')
- break
- }
- resize.call(that)
- })
-
- this.editor = RED.editor.createEditor({
- id: 'node-input-format-editor',
- mode: 'ace/mode/html',
- value: $('#node-input-format').val()
- })
-
- RED.library.create({
- url: 'uitemplates', // where to get the data from
- type: 'ui-template', // the type of object the library is for
- editor: this.editor, // the field name the main text body goes to
- mode: 'ace/mode/html',
- fields: ['name']
- })
-
- this.editor.focus()
-
- RED.popover.tooltip($('#node-template-expand-editor'), c_('label.expand'))
-
- $('#node-template-expand-editor').on('click', function (e) {
- e.preventDefault()
- const value = that.editor.getValue()
- RED.editor.editText({
- mode: $templateScope.val() === 'global' ? 'css' : 'html',
- value,
- width: 'Infinity',
- cursor: that.editor.getCursorPosition(),
- complete: function (v, cursor) {
- that.editor.setValue(v, -1)
- that.editor.gotoLine(cursor.row + 1, cursor.column, false)
- setTimeout(function () { that.editor.focus() }, 300)
- }
- })
- })
-
- // use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip
- $('.ui-node-popover-title').tooltip({
- show: {
- effect: 'slideDown',
- delay: 150
- }
- })
-
- $templateScope.trigger('change') // trigger the change event to hide/show the group/dashboard row
- },
- oneditsave: function () {
- const annot = this.editor.getSession().getAnnotations()
- this.noerr = 0
- $('#node-input-noerr').val(0)
- for (let k = 0; k < annot.length; k++) {
- if (annot[k].type === 'error') {
- $('#node-input-noerr').val(annot.length)
- this.noerr = annot.length
- }
- }
- $('#node-input-format').val(this.editor.getValue())
- this.editor.destroy()
- delete this.editor
-
- // ensure we only have one of group/page/dashboard defined
- const scope = $('#node-input-templateScope').val()
- if (scope === 'local') {
- $('#node-input-ui').val('_ADD_')
- $('#node-input-page').val('_ADD_')
- } else if (scope === 'widget:page') {
- $('#node-input-ui').val('_ADD_')
- $('#node-input-group').val('_ADD_')
- } else if (scope === 'widget:ui') {
- $('#node-input-page').val('_ADD_')
- $('#node-input-group').val('_ADD_')
- } else if (scope === 'site:style') {
- $('#node-input-page').val('_ADD_')
- $('#node-input-group').val('_ADD_')
- } else if (scope === 'page:style') {
- $('#node-input-ui').val('_ADD_')
- $('#node-input-group').val('_ADD_')
- }
- },
- oneditcancel: function () {
- this.editor.destroy()
- delete this.editor
- },
- oneditresize: resize.bind(this)
- })
-
- function resize(size) {
- const rows = $('#dialog-form>div:not(.node-text-editor-row)')
- let fixRowsHeight = 0
- for (let i = 0; i < rows.size(); i++) {
- fixRowsHeight += $(rows[i]).height()
- }
- const dialogHeight = $('#dialog-form').height()
- const height = dialogHeight - fixRowsHeight
- $('.node-text-editor').css('height', height + 'px')
- if (RED.editor.__debug === true) {
- console.log('dialogHeight', dialogHeight, 'fixRowsHeight', fixRowsHeight, 'height', height, 'editorRow')
- }
- this.editor && this.editor.resize()
- }
- })()
- </script>
-
- <script type="text/html" data-template-name="ui-template">
- <div class="form-row">
- <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
- <div style="display:inline-block; width:calc(100% - 105px)">
- <input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
- </div>
- </div>
- <div class="form-row">
- <label for="node-input-temlplateScope"><i class="fa fa-dot-circle-o"></i> <span data-i18n="ui-template.label.scope"></span></label>
- <select style="width:76%" id="node-input-templateScope">
- <option value="local" data-i18n="ui-template.label.widget-group"></option>
- <option value="widget:page" data-i18n="ui-template.label.widget-page"></option>
- <option value="widget:ui" data-i18n="ui-template.label.widget-ui"></option>
- <option value="site:style" data-i18n="ui-template.label.site-style"></option>
- <option value="page:style" data-i18n="ui-template.label.page-style"></option>
- <!-- FUTURE? <option value="site:script" data-i18n="ui-template.label.site-script"></option>
- <option value="page:script" data-i18n="ui-template.label.page-script"></option> -->
- </select>
- </div>
- <div id="template-row-ui" class="form-row">
- <label for="node-input-ui"><i class="fa fa-bookmark"></i> <span data-i18n="ui-template.label.ui"></label>
- <input type="text" id="node-input-ui">
- </div>
- <div id="template-row-page" class="form-row">
- <label for="node-input-page"><i class="fa fa-bookmark"></i> <span data-i18n="ui-template.label.page"></label>
- <input type="text" id="node-input-page">
- </div>
- <div id="template-row-group" class="form-row">
- <label for="node-input-group"><i class="fa fa-table"></i> <span data-i18n="ui-template.label.group"></label>
- <input type="text" id="node-input-group">
- </div>
- <div class="form-row nr-db-ui-element-sizer-row">
- <label><i class="fa fa-object-group"></i> <span data-i18n="ui-template.label.size">Size</label>
- <button class="editor-button" id="node-input-size"></button>
- </div>
- <div class="form-row nr-db-ui-manual-size-row">
- <label><i class="fa fa-arrows-h"></i> <span data-i18n="ui-template.label.width">Width</label>
- <input type="hidden" id="node-input-width">
- </div>
- <div class="form-row nr-db-ui-manual-size-row">
- <label><i class="fa fa-arrows-v"></i> <span data-i18n="ui-template.label.height">Height</label>
- <input type="hidden" id="node-input-height">
- </div>
- <!--<div class="form-row" id="template-row-size">
- <label><i class="fa fa-object-group"></i> <span data-i18n="ui-template.label.size"></span></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-className"><i class="fa fa-code"></i> <span data-i18n="ui-template.label.className"></label>
- <div style="display: inline;">
- <input style="width: 70%;" type="text" id="node-input-className" data-i18n="[placeholder]ui-template.label.classNamePlaceholder" style="flex-grow: 1;">
- <a
- data-html="true"
- title="Dynamic Property: Send msg.class to append new classes to this widget. NOTE: classes set at runtime will be applied in addition to any class(es) set in the nodes class field."
- class="red-ui-button ui-node-popover-title"
- style="margin-left: 4px; cursor: help; font-size: 0.625rem; border-radius: 50%; width: 24px; height: 24px; display: inline-flex; justify-content: center; align-items: center;">
- <i style="font-family: ui-serif;">fx</i>
- </a>
- </div>
- </div>
- <div class="form-row" style="margin-bottom:0px;">
- <label for="node-input-format" style="width:110px;"><i class="fa fa-copy"></i> <span data-i18n="ui-template.label.template"></span></label>
- <input type="hidden" id="node-input-format">
- <button id="node-template-expand-editor" class="red-ui-button red-ui-button-small" style="float:right"><i class="fa fa-expand"></i></button>
- </div>
- <div class="form-row node-text-editor-row" style="display: block;">
- <div style="height:250px; min-height:100px" class="node-text-editor" id="node-input-format-editor" ></div>
- </div>
- <div id="template-pass-store">
- <div class="form-row" style="margin-bottom:0px;">
- <input type="checkbox" id="node-input-passthru" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
- <label for="node-input-passthru" style="width:70%;"> <span data-i18n="ui-template.label.pass-through"></span></label>
- </div>
- </div>
- <!--<div class="form-row" style="margin-bottom:0px;">
- <input type="checkbox" id="node-input-storeOutMessages" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
- <label for="node-input-storeOutMessages" style="width:70%;"> <span data-i18n="ui-template.label.store-state"></span></label>
- </div>
- <div class="form-row" style="margin-bottom:0px;">
- <input type="checkbox" id="node-input-resendOnRefresh" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
- <label for="node-input-resendOnRefresh" style="width:70%;"> <span data-i18n="ui-template.label.resend"></span></label>
- </div>
- </div> -->
- </script>
|