diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-dicts.js b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-dicts.js
new file mode 100644
index 0000000000..4029869c91
--- /dev/null
+++ b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/blocks-dicts.js
@@ -0,0 +1,174 @@
+import Blockly from 'blockly'
+
+export default function (f7) {
+ Blockly.Blocks['dicts_create_with'] = {
+ /**
+ * Block for creating a list with any number of elements of any type.
+ * @this {Blockly.Block}
+ */
+ init: function () {
+ this.setStyle('list_blocks')
+ this.itemCount_ = 3
+ this.updateShape_()
+ this.setOutput(true, 'Dictionary')
+ this.setMutator(new Blockly.Mutator(['dicts_create_with_item']))
+ this.setTooltip('Create a key/value dictionary')
+ },
+ /**
+ * Create XML to represent list inputs.
+ * @return {!Element} XML storage element.
+ * @this {Blockly.Block}
+ */
+ mutationToDom: function () {
+ let container = Blockly.utils.xml.createElement('mutation')
+ container.setAttribute('items', this.itemCount_)
+ return container
+ },
+ /**
+ * Parse XML to restore the list inputs.
+ * @param {!Element} xmlElement XML storage element.
+ * @this {Blockly.Block}
+ */
+ domToMutation: function (xmlElement) {
+ this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10)
+ this.updateShape_()
+ },
+ /**
+ * Populate the mutator's dialog with this block's components.
+ * @param {!Blockly.Workspace} workspace Mutator's workspace.
+ * @return {!Blockly.Block} Root block in mutator.
+ * @this {Blockly.Block}
+ */
+ decompose: function (workspace) {
+ let containerBlock = workspace.newBlock('dicts_create_with_container')
+ containerBlock.initSvg()
+ let connection = containerBlock.getInput('STACK').connection
+ for (let i = 0; i < this.itemCount_; i++) {
+ let itemBlock = workspace.newBlock('dicts_create_with_item')
+ itemBlock.initSvg()
+ connection.connect(itemBlock.previousConnection)
+ connection = itemBlock.nextConnection
+ }
+ return containerBlock
+ },
+ /**
+ * Reconfigure this block based on the mutator dialog's components.
+ * @param {!Blockly.Block} containerBlock Root block in mutator.
+ * @this {Blockly.Block}
+ */
+ compose: function (containerBlock) {
+ let itemBlock = containerBlock.getInputTargetBlock('STACK')
+ // Count number of inputs.
+ let connections = []
+ while (itemBlock && !itemBlock.isInsertionMarker()) {
+ connections.push(itemBlock.valueConnection_)
+ itemBlock = itemBlock.nextConnection &&
+ itemBlock.nextConnection.targetBlock()
+ }
+ // Disconnect any children that don't belong.
+ for (let i = 0; i < this.itemCount_; i++) {
+ let input = this.getInput('ADD' + i)
+ if (!input) continue
+ let connection = input.connection.targetConnection
+ if (connection && connections.indexOf(connection) === -1) {
+ connection.disconnect()
+ }
+ }
+ this.itemCount_ = connections.length
+ this.updateShape_()
+ // Reconnect any child blocks.
+ for (let i = 0; i < this.itemCount_; i++) {
+ Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i)
+ }
+ },
+ /**
+ * Store pointers to any connected child blocks.
+ * @param {!Blockly.Block} containerBlock Root block in mutator.
+ * @this {Blockly.Block}
+ */
+ saveConnections: function (containerBlock) {
+ let itemBlock = containerBlock.getInputTargetBlock('STACK')
+ let i = 0
+ while (itemBlock) {
+ let input = this.getInput('ADD' + i)
+ itemBlock.valueConnection_ = input && input.connection.targetConnection
+ i++
+ itemBlock = itemBlock.nextConnection &&
+ itemBlock.nextConnection.targetBlock()
+ }
+ },
+ /**
+ * Modify this block to have the correct number of inputs.
+ * @private
+ * @this {Blockly.Block}
+ */
+ updateShape_: function () {
+ if (this.itemCount_ && this.getInput('EMPTY')) {
+ this.removeInput('EMPTY')
+ } else if (!this.itemCount_ && !this.getInput('EMPTY')) {
+ this.appendDummyInput('EMPTY')
+ .appendField('create empty dictionary')
+ }
+ // Add new inputs.
+ let i
+ for (i = 0; i < this.itemCount_; i++) {
+ if (!this.getInput('ADD' + i)) {
+ let input = this.appendValueInput('ADD' + i)
+ .setAlign(Blockly.ALIGN_RIGHT)
+ if (i === 0) {
+ input.appendField('dictionary of')
+ }
+ input.appendField(new Blockly.FieldTextInput('key' + i), 'KEY' + i)
+ }
+ }
+ // Remove deleted inputs.
+ while (this.getInput('ADD' + i)) {
+ this.removeInput('ADD' + i)
+ i++
+ }
+ }
+ }
+
+ Blockly.Blocks['dicts_create_with_container'] = {
+ /**
+ * Mutator block for list container.
+ * @this {Blockly.Block}
+ */
+ init: function () {
+ this.setStyle('list_blocks')
+ this.appendDummyInput()
+ .appendField('key/values')
+ this.appendStatementInput('STACK')
+ this.setTooltip('Initialize a Dictionary')
+ this.contextMenu = false
+ }
+ }
+
+ Blockly.Blocks['dicts_create_with_item'] = {
+ /**
+ * Mutator block for adding items.
+ * @this {Blockly.Block}
+ */
+ init: function () {
+ this.setStyle('list_blocks')
+ this.appendDummyInput()
+ .appendField('key')
+ this.setPreviousStatement(true)
+ this.setNextStatement(true)
+ this.setTooltip('add a key/value to the dictionary')
+ this.contextMenu = false
+ }
+ }
+
+ Blockly.JavaScript['dicts_create_with'] = function (block) {
+ // Create an object with any number of elements of any type.
+ let elements = new Array(block.itemCount_)
+ for (let i = 0; i < block.itemCount_; i++) {
+ elements[i] = '\'' + block.getFieldValue('KEY' + i) + '\': '
+ elements[i] += Blockly.JavaScript.valueToCode(block, 'ADD' + i,
+ Blockly.JavaScript.ORDER_NONE) || 'null'
+ }
+ let code = '{' + elements.join(', ') + '}'
+ return [code, Blockly.JavaScript.ORDER_ATOMIC]
+ }
+}
diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/blockly/index.js b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/index.js
index 87b3af5fe5..6e1b698cd2 100644
--- a/bundles/org.openhab.ui/web/src/assets/definitions/blockly/index.js
+++ b/bundles/org.openhab.ui/web/src/assets/definitions/blockly/index.js
@@ -1,3 +1,4 @@
+import defineDictionaryBlocks from './blocks-dicts'
import defineItemBlocks from './blocks-items'
import defineThingsBlocks from './blocks-things'
import defineAudioBlocks from './blocks-audio'
@@ -8,6 +9,7 @@ import defineTimerBlocks from './blocks-timers'
import defineValueStorageBlocks from './blocks-valuestorage'
export default function (f7, data) {
+ defineDictionaryBlocks(f7)
defineItemBlocks(f7)
defineThingsBlocks(f7)
defineAudioBlocks(f7, data.sinks, data.voices)
diff --git a/bundles/org.openhab.ui/web/src/pages/settings/rules/script/blockly-editor.vue b/bundles/org.openhab.ui/web/src/pages/settings/rules/script/blockly-editor.vue
index 3d07c7135e..29b7b6effc 100644
--- a/bundles/org.openhab.ui/web/src/pages/settings/rules/script/blockly-editor.vue
+++ b/bundles/org.openhab.ui/web/src/pages/settings/rules/script/blockly-editor.vue
@@ -261,6 +261,8 @@
+
+