Quill: Quick way to add toolbars

Created on 15 Feb 2016  路  17Comments  路  Source: quilljs/quill

Although Quill is very customisable with the toolbars, it would be cool if you could just supply an array of toolbar items to activate:

{
   toolbars: ['bold', 'italic', '|', 'styles', '|', 'link']
}

I realise this would be limited in customising specific styling (icons, etc), but if general markup is generated then it can still be pretty customisable, like the icons in css etc. For instance, to get Quill up and running (converted from another popular wysiwyg editor) this is what I had to do:

/**
 * Bind html editors.
 *
 * @param  {jQuery} $elements
 * @return {void}
 */
bindHtmlEditors: function($elements) {
    var template = [
        '<span class="ql-format-group">',
            '<select title="Font" class="ql-font">',
                '<option value="sans-serif" selected="">Sans Serif</option>',
                '<option value="serif">Serif</option>',
                '<option value="monospace">Monospace</option>',
            '</select>',
            '<select title="Size" class="ql-size">',
                '<option value="10px">Small</option>',
                '<option value="13px" selected="">Normal</option>',
                '<option value="18px">Large</option>',
                '<option value="32px">Huge</option>',
            '</select>',
        '</span>',
        '<span class="ql-format-group">',
            '<span title="Bold" class="ql-format-button ql-bold"></span>',
            '<span class="ql-format-separator"></span>',
            '<span title="Italic" class="ql-format-button ql-italic"></span>',
            '<span class="ql-format-separator"></span>',
            '<span title="Underline" class="ql-format-button ql-underline"></span>',
            '<span class="ql-format-separator"></span>',
            '<span title="Strikethrough" class="ql-format-button ql-strike"></span>',
        '</span>',
        '<span class="ql-format-group">',
            '<select title="Text Color" class="ql-color">',
                '<option value="rgb(0, 0, 0)" label="rgb(0, 0, 0)" selected=""></option>',
                '<option value="rgb(230, 0, 0)" label="rgb(230, 0, 0)"></option>',
                '<option value="rgb(255, 153, 0)" label="rgb(255, 153, 0)"></option>',
                '<option value="rgb(255, 255, 0)" label="rgb(255, 255, 0)"></option>',
                '<option value="rgb(0, 138, 0)" label="rgb(0, 138, 0)"></option>',
                '<option value="rgb(0, 102, 204)" label="rgb(0, 102, 204)"></option>',
                '<option value="rgb(153, 51, 255)" label="rgb(153, 51, 255)"></option>',
                '<option value="rgb(255, 255, 255)" label="rgb(255, 255, 255)"></option>',
                '<option value="rgb(250, 204, 204)" label="rgb(250, 204, 204)"></option>',
                '<option value="rgb(255, 235, 204)" label="rgb(255, 235, 204)"></option>',
                '<option value="rgb(255, 255, 204)" label="rgb(255, 255, 204)"></option>',
                '<option value="rgb(204, 232, 204)" label="rgb(204, 232, 204)"></option>',
                '<option value="rgb(204, 224, 245)" label="rgb(204, 224, 245)"></option>',
                '<option value="rgb(235, 214, 255)" label="rgb(235, 214, 255)"></option>',
                '<option value="rgb(187, 187, 187)" label="rgb(187, 187, 187)"></option>',
                '<option value="rgb(240, 102, 102)" label="rgb(240, 102, 102)"></option>',
                '<option value="rgb(255, 194, 102)" label="rgb(255, 194, 102)"></option>',
                '<option value="rgb(255, 255, 102)" label="rgb(255, 255, 102)"></option>',
                '<option value="rgb(102, 185, 102)" label="rgb(102, 185, 102)"></option>',
                '<option value="rgb(102, 163, 224)" label="rgb(102, 163, 224)"></option>',
                '<option value="rgb(194, 133, 255)" label="rgb(194, 133, 255)"></option>',
                '<option value="rgb(136, 136, 136)" label="rgb(136, 136, 136)"></option>',
                '<option value="rgb(161, 0, 0)" label="rgb(161, 0, 0)"></option>',
                '<option value="rgb(178, 107, 0)" label="rgb(178, 107, 0)"></option>',
                '<option value="rgb(178, 178, 0)" label="rgb(178, 178, 0)"></option>',
                '<option value="rgb(0, 97, 0)" label="rgb(0, 97, 0)"></option>',
                '<option value="rgb(0, 71, 178)" label="rgb(0, 71, 178)"></option>',
                '<option value="rgb(107, 36, 178)" label="rgb(107, 36, 178)"></option>',
                '<option value="rgb(68, 68, 68)" label="rgb(68, 68, 68)"></option>',
                '<option value="rgb(92, 0, 0)" label="rgb(92, 0, 0)"></option>',
                '<option value="rgb(102, 61, 0)" label="rgb(102, 61, 0)"></option>',
                '<option value="rgb(102, 102, 0)" label="rgb(102, 102, 0)"></option>',
                '<option value="rgb(0, 55, 0)" label="rgb(0, 55, 0)"></option>',
                '<option value="rgb(0, 41, 102)" label="rgb(0, 41, 102)"></option>',
                '<option value="rgb(61, 20, 102)" label="rgb(61, 20, 102)"></option>',
            '</select>',
        '</span>',
        '<span class="ql-format-group">',
            '<span title="List" class="ql-format-button ql-list"></span>',
            '<span class="ql-format-separator"></span>',
            '<span title="Bullet" class="ql-format-button ql-bullet"></span>',
            '<span class="ql-format-separator"></span>',
            '<select title="Text Alignment" class="ql-align">',
                '<option value="left" label="Left" selected=""></option>',
                '<option value="center" label="Center"></option>',
                '<option value="right" label="Right"></option>',
                '<option value="justify" label="Justify"></option>',
            '</select>',
        '</span>',
        '<span class="ql-format-group">',
            '<span title="Link" class="ql-format-button ql-link"></span>',
        '</span>'
    ].join('');

    $elements.each(function() {
        var $textarea = $(this);
        var $wrapper = $textarea.wrap('<div class="quill-wrapper">');
        var $editor = $('<div/>').html($(this).val());
        $textarea.after($editor).hide();
        var editor = new Quill($editor[0], {
            theme: 'snow'
        });

        $(this).closest('form').submit(function() {
            $textarea.val(editor.getHTML());
        });
        var $toolbar = $('<div>').html(template);
        $editor.before($toolbar);
        editor.addModule('toolbar', { container: $toolbar[0] });
    });
},

This could probably be cleaned up a little but just as an example, it it would be nicer to get going 'quicker' with quill from the get go if some sensible boilerplate can be generated.

feature

Most helpful comment

I know they don't work that way ;)

I'm suggesting maybe the toolbars options should work as I have illustrated above. It would support all existing functionality well, whereas your current options object is either one way or the other, there are compromises with either - but there is no obvious need for it. Just a thought anyway.

All 17 comments

Thanks for the Issue. The general idea of having a simpler toolbar configuration has been on the radar. Specifics of the interface is still under consideration but here's one idea:

// At the simplest level it can simply be an array of formats:
['bold', 'italic', 'underline', 'strike']

// Controls can also be grouped by nesting an array, limited to one level of nesting
// CSS will control what these groups do, ex show separators between buttons
[['bold', 'italic'], ['link', 'image']]

// For buttons with custom values, instead of a string you can have an object
[{ 'size': '32px' }]

// For dropdowns, the value of the object is an array of values, false is default/selected
// CSS content can be used to control visual labels
[
  'bold', 
  { size: ['10px', false, '16px', '32px' ]}
]

// Themes will be allowed to specify a default set of values for dropdowns
// So the config on quilljs.com can be as simple as
[
  ['font', 'size'], 
  ['bold', 'italic', 'underline', 'strke'],
  ['color', 'background'],
  [{ list: 'ordered' }, { list: 'bullet'}, 'align' ],
  ['link', 'image']
]

This has somewhat of a strong reliance on CSS, especially its use in showing labels. This is okay when used with a theme because the snow theme just removes labels and mostly uses icons instead but an un-themed editor under this proposal does not allow labels to be shown when configured with Javascript instead of HTML.

That sounds awesome, seems like you've had a good thought about this. I would suggest to keep the structure as simple and syntax-free as possible though, as I would much rather have something like this where you instantiate a copy of an object and configure it specifically.

var editor = new Quill('#test', {

    toolbars: [
        ['bold', 'italic', 'underline', 'strike'],
        [Quill.toolbar.sizes(['10px', false, '16px'])],
        [
            Quill.toolbar.list('align'),
            Quill.toolbar.list('bullet')
        ]
    ]

});

So basically there are 3 options;

  1. String, which is simply an alias for doing Quill.toolbar.<item>()
  2. An array, which is just a way of grouping toolbar items.
  3. An instantiated toolbar object

The benefit of doing it this way is it's far more explicit, less error-prone to typos, easier to parse/process internally and is future proof for more complex customisation. For instance you could add support for changing the template for each individual toolbar:

var bold = Quill.toolbar.bold().setTemplate(
    '<span class="%action_class%">Bold</span>'
);

Or maybe even supply a custom template renderer by supplying closure:

var bold = Quill.toolbar.bold().setTemplate(function() {
    return '<span>Bold</span>;
});

Or change the label:

Quill.toolbar.bold().setLabel('Blah')

This has been added in the 1.0 beta release

Ok, but how to I add toolbar controls, and handlers, and a custom container etc.?

The following doesn't work (1.0.0-rc2) :/

toolbars: {
    container: '#toolbar',
    controls:['bold', 'italic', 'underline', 'strike']
}

There's no need to guess at the configuration. Documentation is available.

I did read that, but I can't see any way to have both a container specified, and a 'controls' array - i even looked in the source, which does refer to container, controls, handlers in the toolbar module, but the options don't seem to work that way unless I'm missing something.

The options don't work that way--controls is an internal data structure.

I know they don't work that way ;)

I'm suggesting maybe the toolbars options should work as I have illustrated above. It would support all existing functionality well, whereas your current options object is either one way or the other, there are compromises with either - but there is no obvious need for it. Just a thought anyway.

Your initial inquiry sounded like a question, not a proposal or suggestion. In either case this seems resolved.

How can what @jonburger suggested be achieved? (specifying a container and further options)

It's one or the other. Either you have a custom container you want Quill to attach handlers to, or Quill creates the container for you and adds the appropriate handlers.

I'm not even sure how both would make sense. Would the additional controls go before or after your the elements already in the container? Does it have to be empty? Once you add this will people naturally ask for control over where to place the additional controls? You can have a library endlessly configurable and at some point you have to ask if it's worth the complexity, and favor code over configuration.

I certainly see that point of view, @jhchen 馃憤

I think the problem (at least the one I'm having at any rate), is that I'm not entirely clear on how to have full control over the toolbar (with custom buttons et. al.) through code (which I vastly prefer to adding markup).

For instance:

const opts = [
  [ 'bold', 'italic'],
  [{ 'save': 'save' }],
  [ { 'header': [ 1, 2 ] }],
  [ { 'align': [false, 'center'] }]
];

This code adds ql-formats <span>-tags for bold, and _italic_ (which can be styled using :before pseudo elements), a custom button ("save"), and two <select>-tags for headings and alignment. However, there is currently no way (that I can see anyway) to style the <option>-tags for the dropdown.

The documentation states that "CSS is used to control the visual labels for dropdown options.", but as far as I know there's no way to prepend/append pseudo elements to select tags, at least according to the w3 docs.

Btw I just want to add that I really enjoy using the library, thank you very much for your time and effort. 馃槃

If you use either Bubble or Snow theme, they hide the native <select> and add their own dropdown implementation for the very reason you mention that native <select> elements are not very configurable.

Thank you very much for the response, @jhchen. Does this mean that in order to show labels for <select> elements I need to either:

1: Use a predefined theme (like Bubble or Snow)
2: Create my own theme

Is that correct?

I'm not 100% on what you mean by labels, but you can either:

  1. Use Quill's dropdowns through Bubble/Snow
  2. Bring your own, which can be a vanilla <select>

Ah, sorry. I mean to have dropdown values with textually populated options (like for the Snow and Bubble themes). Like this:

movie

To re-iterate, from what I understand by what you're saying, to accomplish this I need to either:

1: Use a predefined theme
2: Create my own theme
3: Use a <select> tag through markup (I'd prefer through code).

Is that right?

@nicohvi I found this in another ticket, it can work for simple dropdowns. My trouble with it is that I can't have a label for the options different from their values, so I still had to use html for that.

/deep/ .ql-(YOUR_CONTROL_HERE).ql-picker .ql-picker-item:before {
  content: attr(data-label); // Make option labels appear
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

GildedHonour picture GildedHonour  路  3Comments

lustoykov picture lustoykov  路  3Comments

Softvision-MariusComan picture Softvision-MariusComan  路  3Comments

ouhman picture ouhman  路  3Comments

visore picture visore  路  3Comments