Use virtual keyboard in node-red-dashboard

Hi @mfassier ! Here's a quick modification of the vKeyboard script that should only send numbers after a "return" click. Let me know if it works well for you!

<script> 
    
// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
; (function ($, window, document, undefined) {

    // undefined is used here as the undefined global variable in ECMAScript 3 is
    // mutable (ie. it can be changed by someone else). undefined isn't really being
    // passed in so we can ensure the value of it is truly undefined. In ES5, undefined
    // can no longer be modified.

    // window and document are passed through as local variable rather than global
    // as this (slightly) quickens the resolution process and can be more efficiently
    // minified (especially when both are regularly referenced in your plugin).

    // Create the defaults once
    var pluginName = "jkeyboard",
        defaults = {
            layout: "english",
            input: $('#input'),
            customLayouts: {
                selectable: []
            },
        };


    var function_keys = {
        backspace: {
            text: 'DEL',
        },
        return: {
            text: 'Enter'
        },
        shift: {
            text: 'Shift'
        },
        space: {
            text: 'Space'
        },
        numeric_switch: {
            text: '123',
            command: function () {
                this.createKeyboard('numeric');
                this.events();
            }
        },
        layout_switch: {
            text: '<i class="fa fa-keyboard-o" aria-hidden="true"></i>',
            command: function () {
                var l = this.toggleLayout();
                this.createKeyboard(l);
                this.events();
            }
        },
        character_switch: {
            text: 'ABC',
            command: function () {
                this.createKeyboard(layout);
                this.events();
            }
        },
        symbol_switch: {
            text: '#+=',
            command: function () {
                this.createKeyboard('symbolic');
                this.events();
            }
        }
    };


    var layouts = {
        selectable: ['azeri', 'english', 'russian','french', 'emoji'],
        azeri: [
            ['q', 'ü', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'ö', 'ğ'],
            ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ı', 'ə'],
            ['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'ç', 'ş', 'backspace'],
            ['numeric_switch', 'layout_switch', 'space', 'return']
        ],
        english: [
            ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',],
            ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',],
            ['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'backspace'],
            ['numeric_switch', 'layout_switch', 'space', 'return']
        ],
        russian: [
            ['й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х'],
            ['ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э'],
            ['shift', 'я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю', 'backspace'],
            ['numeric_switch', 'layout_switch', 'space', 'return']
        ],
        french: [
            ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',],
            ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l','à','ç'],
            ['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm','é','è', 'backspace'],
            ['numeric_switch', 'layout_switch', 'space', 'return']
        ],
        emoji: [
            ['😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊',],
            ['😋', '😎', '😍', '😘', 'g', 'h', 'j', 'k', 'l','à','ç'],
            ['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm','é','è', 'backspace'],
            ['numeric_switch', 'layout_switch', 'space', 'return']
        ],            
        numeric: [
            ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
            ['-', '/', ':', ';', '(', ')', '$', '&', '@', '"'],
            ['symbol_switch', '.', ',', '?', '!', "'", 'backspace'],
            ['character_switch', 'layout_switch', 'space', 'return'],
        ],
        numbers_only: [
            ['1', '2', '3',],
            ['4', '5', '6',],
            ['7', '8', '9',],
            ['0', 'backspace', 'return'],
        ],
        symbolic: [
            ['[', ']', '{', '}', '#', '%', '^', '*', '+', '='],
            ['_', '\\', '|', '~', '<', '>'],
            ['numeric_switch', '.', ',', '?', '!', "'", 'backspace'],
            ['character_switch', 'layout_switch', 'space', 'return'],

        ]
    }

    var shift = false, capslock = false, layout = 'english', layout_id = 0;

    // The actual plugin constructor
    function Plugin(element, options) {
        this.element = element;
        // jQuery has an extend method which merges the contents of two or
        // more objects, storing the result in the first object. The first object
        // is generally empty as we don't want to alter the default options for
        // future instances of the plugin
        this.settings = $.extend({}, defaults, options);
        // Extend & Merge the cusom layouts
        layouts = $.extend(true, {}, this.settings.customLayouts, layouts);
        if (Array.isArray(this.settings.customLayouts.selectable)) {
            $.merge(layouts.selectable, this.settings.customLayouts.selectable);
        }
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    Plugin.prototype = {
        init: function () {
            layout = this.settings.layout;
            this.createKeyboard(layout);
            this.events();
        },

        setInput: function (newInputField) {
            this.settings.input = newInputField;
        },

        createKeyboard: function (layout) {
            shift = false;
            capslock = false;

            var keyboard_container = $('<ul/>').addClass('jkeyboard'),
                me = this;

            layouts[layout].forEach(function (line, index) {
                var line_container = $('<li/>').addClass('jline');
                line_container.append(me.createLine(line));
                keyboard_container.append(line_container);
            });

            $(this.element).html('').append(keyboard_container);
        },

        createLine: function (line) {
            var line_container = $('<ul/>');

            line.forEach(function (key, index) {
                var key_container = $('<li/>').addClass('jkey').data('command', key);

                if (function_keys[key]) {
                    key_container.addClass(key).html(function_keys[key].text);
                }
                else {
                    key_container.addClass('letter').html(key);
                }

                line_container.append(key_container);
            })

            return line_container;
        },

        events: function () {
            var letters = $(this.element).find('.letter'),
                shift_key = $(this.element).find('.shift'),
                space_key = $(this.element).find('.space'),
                backspace_key = $(this.element).find('.backspace'),
                return_key = $(this.element).find('.return'),

                me = this,
                fkeys = Object.keys(function_keys).map(function (k) {
                    return '.' + k;
                }).join(',');

            letters.on('click', function () {
                me.type((shift || capslock) ? $(this).text().toUpperCase() : $(this).text());
            });

            space_key.on('click', function () {
                me.type(' ');
            });

            return_key.on('click', function () {
                me.enter();
            });

            backspace_key.on('click', function () {
                me.backspace();
            });

            shift_key.on('click', function () {
                if (capslock) {
                    me.toggleShiftOff();
                    capslock = false;
                } else {
                    me.toggleShiftOn();
                }
            }).on('dblclick', function () {
                capslock = true;
            });


            $(fkeys).on('click', function () {
                var command = function_keys[$(this).data('command')].command;
                if (!command) return;

                command.call(me);
            });
        },

        type: function (key) {
            var input = this.settings.input,
                val = input.val(),
                input_node = input.get(0),
                start = input_node.selectionStart,
                end = input_node.selectionEnd;

            var max_length = $(input).attr("maxlength");
            if (start == end && end == val.length) {
                if (!max_length || val.length < max_length) {
                    input.val(val + key);
                    input.change()
                    $('#vkeyname').text(val + key)
                }
            } else {
                if (input_node.type == "text"){
                    var new_string = this.insertToString(start, end, val, key);
                    input.val(new_string);
                    start++;
                    end = start;
                    input_node.setSelectionRange(start, end);
                    input.change()
                }else if (input_node.type == "number"){
                    input.val(val + key);
                }else{
                    input.val(val + key);
                    input.change()
                }
                $('#vkeyname').text(val + key)
                
            }
            input.trigger('focus');

            if (shift && !capslock) {
                this.toggleShiftOff();
            }
        },
        
        enter: function () {
            var input = this.settings.input,
                val = input.val();
                input_node = input.get(0),
                start = input_node.selectionStart,
                end = input_node.selectionEnd;
            if (input.type == "text"){
                val = val + "\n";
                $('#vkeyname').text(val)
            }
            input.change()
            input.focus()
        },

        backspace: function () {
            var input = this.settings.input,
                val = input.val();
                input_node = input.get(0),
                start = input_node.selectionStart,
                end = input_node.selectionEnd;
            if (input.type == "text"){
                input.val(val.slice(0, start-1) + val.slice(start))
                input_node.setSelectionRange(start-1, start-1);
                //console.log(val)
                $('#vkeyname').text(val)
            }else{
                input.val(val.slice(0,-1))
                $('#vkeyname').text(val.slice(0,-1))
            }
            input.change()
            //input.focus()
        },

        toggleShiftOn: function () {
            var letters = $(this.element).find('.letter'),
                shift_key = $(this.element).find('.shift');

            letters.addClass('uppercase');
            shift_key.addClass('active')
            shift = true;
        },

        toggleShiftOff: function () {
            var letters = $(this.element).find('.letter'),
                shift_key = $(this.element).find('.shift');

            letters.removeClass('uppercase');
            shift_key.removeClass('active');
            shift = false;
        },

        toggleLayout: function () {
            layout_id = layout_id || 0;
            var plain_layouts = layouts.selectable;
            layout_id++;

            var current_id = layout_id % plain_layouts.length;
            var SelectedLayoutName = plain_layouts[current_id];
            $('#vkeyname').text('V-Keyboard ' + SelectedLayoutName )
            return plain_layouts[current_id];
        },

        insertToString: function (start, end, string, insert_string) {
            return string.substring(0, start) + insert_string + string.substring(end, string.length);
        }
    };

        /*
		// A really lightweight plugin wrapper around the constructor,
		// preventing against multiple instantiations
		$.fn[ pluginName ] = function ( options ) {
				return this.each(function() {
						if ( !$.data( this, "plugin_" + pluginName ) ) {
								$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
						}
				});
		};
        */
        var methods = {
            init: function(options) {
                if (!this.data("plugin_" + pluginName)) {
                    this.data("plugin_" + pluginName, new Plugin(this, options));
                }
            },
			setInput: function(content) {
				this.data("plugin_" + pluginName).setInput($(content));
            },
            setLayout: function(layoutname) {
                // change layout if it is not match current
                object = this.data("plugin_" + pluginName);
                if (typeof(layouts[layoutname]) !== 'undefined' && object.settings.layout != layoutname) {
                    object.settings.layout = layoutname;
                    object.createKeyboard(layoutname);
                    object.events();
                };
            },
        };

		$.fn[pluginName] = function (methodOrOptions) {
            if (methods[methodOrOptions]) {
                return methods[methodOrOptions].apply(this.first(), Array.prototype.slice.call( arguments, 1));
            } else if (typeof methodOrOptions === 'object' || ! methodOrOptions) {
                // Default to "init"
                return methods.init.apply(this.first(), arguments);
            } else {
                $.error('Method ' +  methodOrOptions + ' does not exist on jQuery.tooltip');
            }
        };

})(jQuery, window, document);
</script>
<style>
    .jkeyboard {
  display: inline-block;
}
.jkeyboard, .jkeyboard .jline, .jkeyboard .jline ul {
  display: block;
  margin: 0;
  padding: 0;
}
.jkeyboard .jline {
  text-align: center;
  margin-left: -14px;
}
.jkeyboard .jline ul li {
  font-family: arial, sans-serif;
  font-size: 20px;
  display: inline-block;
  border: 1px solid #468db3;
  -webkit-box-shadow: 0 0 3px #468db3;
  -webkit-box-shadow: inset 0 0 3px #468db3;
  margin: 5px 0 1px 6px;
  color: #000000;
  border-radius: 5px;
  width: 52px;
  height: 52px;
  box-sizing: border-box;
  text-align: center;
  line-height: 52px;
  overflow: hidden;
  cursor: pointer;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: -moz-none;
  -ms-user-select: none;
  user-select: none;
}
.jkeyboard .jline ul li.uppercase {
  text-transform: uppercase;
}
.jkeyboard .jline ul li:hover, .jkeyboard .jline ul li:active {
  background-color: #185a82;
}
.jkeyboard .jline .return {
  width: 80px;
}
.jkeyboard .jline .space {
  width: 366px;
}
.jkeyboard .jline .numeric_switch {
  width: 65px;
}
.jkeyboard .jline .layout_switch {
}
.jkeyboard .jline .shift {
  width: 60px;
}
.jkeyboard .jline .backspace {
  width: 69px;
}
</style>




<style>
body {font-family: Arial, Helvetica, sans-serif;}

.nr-dashboard-theme .nr-dashboard-template .md-button:not(:first-of-type) {
    margin-top: 0px;
}

/* The Modal (background) */
.modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    opacity:0.99;
    z-index: 100; /* Sit on top */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: rgb(0,0,0); /* Fallback color */
    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}

/* Modal Content */
.modal-content {
    position: fixed;
    background-color: #fefefe;
    margin: auto;
    padding: 0;
    bottom: 0%;
    left: 50%;
    transform: translate(-50%, 0%);
    border: 1px solid #888;
    width: fit-content;
    max-width: 100%;
    max-height: 100%;
    box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
    -webkit-animation-name: animate;
    -webkit-animation-duration: 0.4s;
    animation-name: animate;
    animation-duration: 0.4s
}

/* Add Animation */
@-webkit-keyframes animate {
    from {bottom:100%; opacity:0} 
    to {bottom:0%; opacity:1}
}

@keyframes animate {
    from {bottom:100%; opacity:0}
    to {bottom:0%; opacity:1}
}

/* The Close Button */
.close {
    color: black;
    float: right;
    font-size: 28px;
    font-weight: bold;
}

.close:hover,
.close:focus {
    color: #000;
    text-decoration: none;
    cursor: pointer;
}

.modal-header {
    padding: 2px 16px;
    background-color: aliceblue;
    color: white;
}

.modal-body {padding: 2px 16px;}

.modal-footer {
    padding: 2px 16px;
    background-color: #5cb85c;
    color: white;
}
</style>

<!-- The Modal -->
<div id="myModal" class="modal">

  <!-- Modal content -->
  <div class="modal-content">
      <div class="modal-header">
      <span class="close" onclick="closeModal()">&times;</span>
      <h2 id="vkeyname" style="background-color: aliceblue !important; color: black !important; text-align: center; min-height: 30px;">V-Keyboard</h2>
    </div>
    <div class="modal-body">
        <div id="keyboard"></div>
        <div>
        </div>
    </div>
  </div>
</div>


<script>
    // Get the modal
var modal = document.getElementById('myModal');

/*
$('input[type=text]').click(function () {
    $('#keyboard').unbind().removeData();
        $('#keyboard').jkeyboard({
            layout: "english",
            input: $('#'+$(this).attr('id'))
    });
});

$('input[type=number]').click(function () {
    $('#keyboard').unbind().removeData();
        $('#keyboard').jkeyboard({
            layout: "numbers_only",
            input: $('#'+$(this).attr('id'))
    });
});
*/

var inputTags;
var inputType;

var getinputs = function() {
    inputTags = document.getElementsByTagName("input");
    console.log(inputTags)
    for (var i = 0; i < inputTags.length; i++) {
        inputTags[i].addEventListener('click', openModal, false)
    }
}

setTimeout(function(){ getinputs(); }, 1000);

var inputTarget;

var openModal = function() {
    inputType = event.target.type
    inputTarget = event.target
    var layoutName;
    if (inputType == "number"){
        //inputTarget.type = "number" //hack because chrome doesn't allow setselection in number inputs
        //inputTarget.value = ""
        layoutName = "numbers_only"
    }else{
        layoutName = "english"
    }
    $('#vkeyname').text(event.target.value)
    $('#keyboard').unbind().removeData();
    modal.style.display = "block";
    $('#keyboard').jkeyboard({
        layout: layoutName,
        input: $('#'+$(this).attr('id'))
    });
}


// Get the <span> element that closes the modal
var span = document.getElementsByClassName("close")[0];

// When the user clicks on <span> (x), close the modal
//span.onclick = function(event) {
  //closeModal()
//}

// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
    var source = event.target;
    if (source == modal || source == span) {
        closeModal(source)
    }
};

var closeModal = function(source){
    //console.log("closing")
    modal.style.display = "none";
   
    if (inputType == "number"){
        inputTarget.type = "number" //hack because chrome doesn't allow selectionstart on number inputs
    }
}

</script>
<script>

var clickState = 1;
var btn = document.querySelector('.VK');

btn.addEventListener('click', function(){

  if (clickState == 0) {
    this.textContent = 'V-KeyBoard On';
    modal = document.getElementById('myModal');
    clickState = 1;
  } else {
    this.textContent = 'V-KeyBoard Off';
    modal = document.getElementById('empty');
    clickState = 0;
  }

});
</script>

<style>
.VK{
    position: fixed;
    top: 60px;
    right: 20px;
    height: 30px;
}
</style>

<div id="empty"></div>
<button class="VK">V-KeyBoard On</button> 
2 Likes