In some cases users have to insert special characters, for example polish alphabet contains some extra letters or umlauts of german alphabet or symbols like a celsius sign “°C”. I have developed this functionality in form of plugin. It inserts a trigger to field which popups the panel with special symbols. The plugin is implmented in two files, the plugin and popup window.

Ext.define('app.ux.form.field.VirtualKeyboard', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.virtualkeyboard',

    requires: [
        'app.ux.form.field.VirtualKeyboardPanel'
    ],

    triggerTooltip: "Additional Symbols",
    triggerGlyph: 'xf1ab@FontAwesome',
    pickerTitle: "Symbols",
    pickerHeight: 200,
    numberOfColumns: 5,

    symbols: [],

    init: function (field) {
        this.field = field;
        var triggers = field.getTriggers();
        triggers.virtualKeyboard = {
            glyph: this.triggerGlyph,
            tooltip: this.triggerTooltip,
            handler: this.openPicker,
            scope: this
        };
        field.setTriggers(triggers);
    },

    openPicker: function () {
        this.getPicker().show();
    },

    getPicker: function () {
        if (!this.picker) {
            this.picker = this.createPicker();
        }
        return this.picker;
    },

    createPicker: function () {
        var picker,
            pickerCfg;

        pickerCfg = Ext.apply({
            xtype: 'window',
            id: this.field.id + '-symbolContainer',
            title: this.pickerTitle,
            pickerField: this,
            height: this.pickerHeight,
            layout: 'fit',
            closeAction: 'hide',
            items: [{
                xtype: 'app_ux_form_field_VirtualKeyboardPanel',
                numberOfColumns: this.numberOfColumns,
                symbols: this.symbols,
                listeners: {
                    'SymbolClick': {
                        fn: this.onSymbolClick,
                        scope: this
                    }
                }
            }]
        });

        picker = Ext.widget(pickerCfg);
        return picker;
    },

    onSymbolClick: function (symbol) {
        var fieldValue = this.field.getValue();
        fieldValue += symbol;
        this.field.setValue(fieldValue);
        this.getPicker().hide();
    },

    onEsc: function () {
        this.getPicker().hide();
    }
});

and the popup:

Ext.define('app.ux.form.field.VirtualKeyboardPanel', {
    extend: 'Ext.view.View',
    alias: 'widget.app_ux_form_field_VirtualKeyboardPanel',

    scrollable: true,
    border: false,
    symbols: [],

    cls: Ext.baseCSSPrefix + 'panel-body-default',
    itemSelector: 'div.thumb-wrap',

    initComponent: function() {
        this.tpl = this.createXTempalte();
        this.store = this.createStore();
        this.initEvents();
        this.callParent();
    },

    initEvents: function() {
        this.on('itemclick', this.onSymbolClick, this);
        this.on('blur', this.onBlur, this);
    },

    onSymbolClick: function(dataView, record, item, index, e, eOpt) {
        this.fireEvent('SymbolClick', record.get('symbol'));
        e.stopEvent();
    },

    onBlur: function(cmp, e, eOpt) {
        this.hide();
    },

    getSymbols: function() {
        var symbols = [];
        if(Ext.isArray(this.symbols)) {
            symbols = this.symbols;
        } else if(Ext.isFunction(this.symbols)) {
            symbols = this.symbols()
        }
        return symbols;
    },

    createXTempalte: function() {
        var me = this;
        var xTemplate = new Ext.XTemplate(
            '<table>',
            '<tpl for=".">',
                '<tpl if="this.showOpenTr(xindex)">',
                    '<tr>',
                '</tpl>',
                '<td style="background-color: #cccccc; text-align: center;" >',
                    '<div style="cursor: pointer;" class="thumb-wrap">',
                        '<span style="font-size: 40px;">{symbol}</span>',
                    '</div>',
                '</td>',
                '<tpl if="this.showCloseTr(xindex, xcount)">',
                    '</tr>',
                '</tpl>',
            '</tpl>',
            '</table>',
            {
                showOpenTr: function(xIndex) {
                    return xIndex === 0 && xIndex % me.numberOfColumns === 0;
                },

                showCloseTr: function(xIndex, xCount) {
                    return xIndex === xCount || xIndex % me.numberOfColumns === 0;
                }
            }
        );
        return xTemplate;
    },

    createStore: function () {
        var store = Ext.create('Ext.data.Store', {
            fields: [
                'symbol'
            ]
        });

        Ext.Array.each(this.getSymbols(), function(symbol) {
            store.add({
                'symbol': symbol
            })
        }, this);
        return store;
    }
});

The symbols can be listed in as an array or function.

plugins: {
    virtualkeyboard: {
        pickerTitle: "Polish chars",
        symbols: ['Ą', 'Ć', 'Ę', 'Ł', 'Ń', 'Ó', 'Ś', 'Ź', 'Ż'],
        numberOfColumns: 3
    }
}
plugins: {
    virtualkeyboard: {
        pickerTitle: "Chinese Сharacters",
        symbols: function() {
            var cJKUnifiedIdeographs = [];
            for(var i=19968; i<20000; i++) {
                cJKUnifiedIdeographs.push(String.fromCharCode(i));
            }
            return cJKUnifiedIdeographs;
        }
    }
}

Fiddle: