Dynamically instantiate QuillJS editor - javascript

(I'm going to preface this with the fact that I'm a new javascript developer, and I'm sure I have gaps in my knowledge about how javascript/angular/quill all work together on the page.)
I'm wanting to know if this is possible. Instead of instantiating the editor in the script tag on the page, I want to instantiate the editor for the div when it gets clicked. I'm using an Angular controller for my page, and inside the on click event I set up for the div, I tried a few things:
editor = new Quill(myDiv, {
modules: { toolbar: '#toolbar' },
theme: 'snow'
});
But that didn't work, so I thought maybe I had to explicitly pass the id of the div:
editor = new Quill('#editor', {
modules: { toolbar: '#toolbar' },
theme: 'snow'
});
This didn't work, and didn't focus inside the div and allow me to edit. So I thought maybe the problem was that I was hijacking the click event with angular, and maybe I need to switch the focus to the div after instantiating the editor. So I created a focus directive (just copy/pasted from another SO article) which worked fine when I tested on an input.
app.directive('focusOn', function () {
return function (scope, elem, attr) {
scope.$on(attr.focusOn, function (e) {
elem[0].focus();
});
};
then in the on click function in the angular controller:
$scope.$broadcast('focussec123');
if (editor == null) {
editor = new Quill('#editor', {
modules: { toolbar: '#toolbar' },
theme: 'snow'
});
}
That worked to select the text inside the div, but it didn't show the toolbar and so I suspected it didn't really work. I'm sure I'm misunderstanding some interactions and I'm fully aware I lack a lot of necessary knowledge about JS. My bottom line is I want to know:
Is it possible to dynamically instantiate the editor only for the current section, and to instantiate the editor again for another section when it gets clicked, etc.
If so, how?
Thanks in advance.

yes you can create Quill instances dynamically by clicking on a <div>.
It's exactly what we do.
That's how (roughly):
export class TextbocComponent ... {
private quill: Quill;
private target: HTMLElement;
private Quill = require("quill/dist/quill");
private onParagraphClicked(event: MouseEvent): void {
const options = {
theme: "bubble"
};
if (!this.quill) {
this.target = <HTMLElement>event.currentTarget;
this.quill = new this.Quill($(target).get(0), options);
$(target).children(".ql-editor").on("click", function(e) {
e.preventDefault();
});
}
this.quill.focus();
event.stopPropagation();
event.preventDefault();
}
}

For those who aren't using Angular:
$(document).on('click', '#editor', function() {
if (!$(this).hasClass('ql-container')) {
var quill = new Quill($('#editor').get(0), {
theme: 'snow'
});
quill.focus()
}
});

Its much easier:
var quills = [];
counter = 0;
$( ".init_quill_class" ).each(function() { // add this class to desired div
quills[counter] = new Quill($(".init_quill_class")[counter], {});
//quills[counter].enable(false); // if u only want to show elems
counter++;
});

Related

How to wrap a container div around a Ckeditor table onOk

When a user enters a table on the Ckeditor, I want to wrap a div around it with a class but I can't find a way to get this table HTML element. What is the best way to go about it?
I've tried creating a plugin to extend the table dialog onOk function (see code). This gives me all the properties from the table dialog but I don't want to have to create the whole table element again with all the properties as I don't want to re-write the existing table plugin.
I just need to get the code this plugin adds and wrap it in a div.
I thought about doing it in my projects javascript, when page loads, get all tables and wrap it in a div. However, this doesn't seem like the best way to do it at all. I thought there must be a way via ckeditor?
CKEDITOR.plugins.add( 'responsivetables', {
// The plugin initialization logic
init: function(editor) {
vsAddResponsiveTables(editor);
}
});
function vsAddResponsiveTables(editor){
CKEDITOR.on( 'dialogDefinition', function( ev ) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if ( dialogName == 'table') {
addTableHandler(dialogDefinition, editor);
}
});
}
function addTableHandler(dialogDefinition, editor){
dialogDefinition.onOk = function (a) {
// get table element and wrap in div?
}
}
I found the answer so for anyone else that needs it, this is what I did:
I used the insertElement event instead of when dialog was closed, only doing what I need if its a table that's being added.
// Register the plugin within the editor.
CKEDITOR.plugins.add( 'responsivetables', {
// The plugin initialization logic goes inside this method.
init: function(editor) {
vsAddResponsiveTables(editor);
}
});
function vsAddResponsiveTables(editor){
// React to the insertElement event.
editor.on('insertElement', function(event) {
if (event.data.getName() != 'table') {
return;
}
// Create a new div element to use as a wrapper.
var div = new CKEDITOR.dom.element('div').addClass('table-scroll');
// Append the original element to the new wrapper.
event.data.appendTo(div);
// Replace the original element with the wrapper.
event.data = div;
}, null, null, 1);
}
To the previous answer by 'gemmalouise' need to add one more line of code
CKEDITOR.editorConfig = function( config ) {
config.extraPlugins = 'responsivetables';
}
Otherwise it will not work (I cannot indicate this in the comment, because lack of 50 reputation).
And more compact code of this fuctional:
CKEDITOR.plugins.add('responsivetables', {
init: function (editor) {
editor.on('insertElement', function (event) {
if (event.data.getName() === 'table') {
var div = new CKEDITOR.dom.element('div').addClass('table-scroll'); // Create a new div element to use as a wrapper.
div.append(event.data); // Append the original element to the new wrapper.
event.data = div; // Replace the original element with the wrapper.
}
}, null, null, 1);
}
});

Unable to catch CKEditor change event

I looked through many threads at SO, but could not find an answer that solves my problem. So, I define a CKEditor instance like so:
var editor = $('#test-editor');
editor.ckeditor(function() {}, {
customConfig: '../../assets/js/custom/ckeditor_config.js',
allowedContent: true
});
However I do not know how can I catch change event. This is what I tried:
var t = editor.ckeditor(function() {
this.on('change', function () {
console.log("test 1");
});
}, {
customConfig: '../../assets/js/custom/ckeditor_config.js',
allowedContent: true
});
editor.on('change', function() {
console.log("test 2");
});
t.on('change', function() {
console.log("test 3");
});
All these three attempts ended in failure. I should add, that I do not want to loop through all editors on a page, I just want to address one particular editor rendered at component with #test-editor. How can I do that?
The jQuery ckeditor() method returns a jQuery object, which exposes only 5 events of CKEditor in its event handling. In order to use other events, you need to use the CKEditor.editor object by accessing the editor property of the jQuery object.
So, you need to use something like this:
var myeditor = $('#test-editor').ckeditor({
customConfig: '../../assets/js/custom/ckeditor_config.js',
allowedContent: true
});
myeditor.editor.on('change', function(evt) {
console.log('change detected');
});

Ckeditor initial disabled widget button state

I'm currently developing a Ckeditor 4 widget, but I run into the following issue. I'd like my widget button initially disabled untill an AJAX call is done and has a particular result.
The widget code:
editor.widgets.add('smartobject', {
dialog: 'smartobject',
pathName: lang.pathName,
upcast: function(element) {
return element.hasClass('smartObject');
},
init: function() {
this.setData('editorHtml', this.element.getOuterHtml());
},
data: function() {
var editorHtml = this.data.editorHtml;
var newElement = new CKEDITOR.dom.element.createFromHtml(editorHtml);
newElement.replace(this.element);
this.element = newElement;
}
});
The button is added as follows:
editor.ui.addButton && editor.ui.addButton('CreateSmartobject', {
label: lang.toolbar,
command: 'smartobject',
toolbar: 'insert,5',
icon: 'smartobject'
});
With this code it seems I can't configure the default disabled state.
So I searched in the docs, and thought I had the fix.
The following code addition seemed to work:
editor.$smartobjectPluginPreloadAvailableSmartobjectsPromise.done(function(availableSmartobjects) {
if (availableSmartobjects && availableSmartobjects.length > 0) {
editor.getCommand('smartobject').enable();
}
});
editor.addCommand('smartobject', new CKEDITOR.dialogCommand('smartobject', {
startDisabled: 1
}));
After adding this code the button is initially disabled, and enabled after the AJAX call is completed. So far so good. After a while I tried to add a new 'smartobject', but after completing the dialog config, the widgets 'data' function is not called. When editing an already existing smartobject by doubleclicking the element in the editor, still works..
I've probably mixed up different 'code styles' for adding a button, but I can't find the fix I need for my use case..
Any ideas how to fix this?
It seemed my idea was not possible through the ckeditor widget API and I combined some API logic which was not meant to be combined..
For now I simply fixed it by initially hiding the widgets button through CSS and adding a class to the button after the AJAX call succeeded:
.cke_button__createsmartobject {
display: none !important;
}
.cke_button__createsmartobject.showButton {
display: block !important;
}
And the JS logic:
editor.ui.addButton && editor.ui.addButton('CreateSmartobject', {
label: lang.toolbar,
command: 'smartobject',
toolbar: 'insert,5',
icon: 'smartobject'
});
// Enable the button if smartobjects are allowed for the itemtype of this editor.
editor.$smartobjectPluginPreloadAvailableSmartobjectsPromise.done(function(availableSmartobjects) {
if (availableSmartobjects && availableSmartobjects.length > 0) {
jQuery('.cke_button__createsmartobject').addClass('showButton');
}
});
It's not the solution I'm most proud of, but it works for now.

Leaflet and Mapbox: Buttons not working when added via Javascript

Anyone know why that, when clicked, the buttons do not add or remove overlays from the map? Full PLNKR here
The HTML
<div id="toggleButtons" style="display: none">
<button id="add">Add Overlays</button>
<button id="remove">Remove Overlays</button>
</div>
The Javascript
L.Control.GroupedLayers.include({
addOverlays: function () {
for (var i in this._layers) {
if (this._layers[i].overlay) {
if (!this._map.hasLayer(this._layers[i].layer)) {
this._map.addLayer(this._layers[i].layer);
}
}
}
},
removeOverlays: function () {
for (var i in this._layers) {
if (this._layers[i].overlay) {
if (this._map.hasLayer(this._layers[i].layer)) {
this._map.removeLayer(this._layers[i].layer);
}
}
}
}
});
var control = new L.Control.GroupedLayers(ExampleData.Basemaps, {
'Landmarks': {
'Cities': ExampleData.LayerGroups.cities,
'Restaurants': ExampleData.LayerGroups.restaurants
},
'Random': {
'Dogs': ExampleData.LayerGroups.dogs,
'Cats': ExampleData.LayerGroups.cats
}
}).addTo(map);
L.DomEvent.addListener(L.DomUtil.get('add'), 'click', function () {
control.addOverlays();
});
L.DomEvent.addListener(L.DomUtil.get('remove'), 'click', function () {
control.removeOverlays();
});
And then I added the mapbox legendControl.addLegend method (from the mapbox API documentation)
map.legendControl.addLegend(document.getElementById('toggleButtons').innerHTML);
Although the buttons are shown in the map, their click properties are not working. Any clues? Thanks!
You're not 'adding' the buttons with javascript, you're making a copy of them and placing the copy into the legendControl. The actual buttons with the eventhandlers are still present in the DOM but hidden because you've added display: none as inline style. What you want to do is select the buttons and remove them from the body:
var buttons = document.getElementById('toggleButtons');
document.body.removeChild(buttons);
Then you can add them to the legend and attach the eventhandlers:
var legendControl = L.mapbox.legendControl().addTo(map);
legendControl.addLegend(buttons.innerHTML);
L.DomEvent.addListener(L.DomUtil.get('add'), 'click', function () {
control.addOverlays();
});
L.DomEvent.addListener(L.DomUtil.get('remove'), 'click', function () {
control.removeOverlays();
});
Working example on Plunker: http://plnkr.co/edit/7pDkrZbS7Re1YshKZSLs?p=preview
PS. I'm quite baffled as to why you would abuse mapbox's legend control class to add two buttons. If you need a custom control you can just create one using leaflet's L.Control class. It spares you from loading the legend control class which you're not using, thus bloat.
EDIT: As promised in the comments below an example of rolling this solution into your own custom control. I'll explain to more throughout the comments in the code but the general idea is take the basic L.Control interface and adding the functionality and DOM generation to it:
// Create a new custom control class extended from L.Control
L.Control.Toggle = L.Control.extend({
// Have some default options, you can also change/set
// these when intializing the control
options: {
position: 'topright',
addText: 'Add',
removeText: 'Remove'
},
initialize: function (control, options) {
// Add the options to the instance
L.setOptions(this, options);
// Add a reference to the layers in the layer control
// which is added to the constructor upon intialization
this._layers = control._layers;
},
onAdd: function (map) {
// Create the container
var container = L.DomUtil.create('div', 'control-overlaystoggle'),
// Create add button with classname, append to container
addButton = L.DomUtil.create('button', 'control-overlaystoggle-add', container),
// Create remove button with classname, append to container
removeButton = L.DomUtil.create('button', 'control-overlays-toggleremove', container);
// Add texts from options to the buttons
addButton.textContent = this.options.addText;
removeButton.textContent = this.options.removeText;
// Listen for click events on button, delegate to methods below
L.DomEvent.addListener(addButton, 'click', this.addOverlays, this);
L.DomEvent.addListener(removeButton, 'click', this.removeOverlays, this);
// Make sure clicks don't bubble up to the map
L.DomEvent.disableClickPropagation(container);
// Return the container
return container;
},
// Methods to add/remove extracted from the groupedLayerControl
addOverlays: function () {
for (var i in this._layers) {
if (this._layers[i].overlay) {
if (!this._map.hasLayer(this._layers[i].layer)) {
this._map.addLayer(this._layers[i].layer);
}
}
}
},
removeOverlays: function () {
for (var i in this._layers) {
if (this._layers[i].overlay) {
if (this._map.hasLayer(this._layers[i].layer)) {
this._map.removeLayer(this._layers[i].layer);
}
}
}
}
});
Now you can use your new control as follows:
// Create a new instance of your layer control and add it to the map
var layerControl = new L.Control.GroupedLayers(baselayers, overlays).addTo(map);
// Create a new instance of your toggle control
// set the layercontrol and options as parameters
// and add it to the map
var toggleControl = new L.Control.Toggle(layerControl, {
position: 'bottomleft',
addText: 'Add overlays',
removeText: 'Remove overlays'
}).addTo(map);
I know, this is quick and dirty but it should give you a decent idea of what you can do with the L.Control class in general.
Here's a working example on Plunker: http://plnkr.co/edit/7pDkrZbS7Re1YshKZSLs?p=preview
And here's the reference for L.Control: http://leafletjs.com/reference.html#control
You need to follow delegation strategy here..
document.querySelector('body').addEventListener('click', function(event) {
if (event.target.id.toLowerCase() === 'add') {
control.addOverlays();
}
if (event.target.id.toLowerCase() === 'remove') {
control.removeOverlays();
}
});

ckeditor - javascriptspellcheck plugin

I am trying to integrate javascriptspellchecker into a web page with ckEditor (note I am using ckeditor version 3.6). I would like to replace the default spellcheck and SCAYT (spell check as you type) plugins with new custom plugins that use javascriptspellcheck.
I have created a plugin following the example from the javascriptspellchecker website but it doesn't work properly. The javascriptspellchecker taked the id of the textarea and runs a spellcheck on it's value (or attaches event handlers to spellcheck after input when chosing SCAYT). The problem is, when I alter the text in a ckEditor instance, the hidden textbox doesn't seem to be updated in the background. This means the plugin I have written only checks the original value of the textarea, and the SCAYT doesn't work.
My plugin so far:-
(function () {
//Section 1 : Code to execute when the toolbar button is pressed
var a = {
exec: function (editor) {
$Spelling.SpellCheckInWindow($(editor.element).attr('id'))
}
},
//Section 2 : Create the button and add the functionality to it
b = 'javascriptspellcheck';
CKEDITOR.plugins.add(b, {
init: function (editor) {
editor.addCommand(b, a);
editor.ui.addButton("JavaScriptSpellCheck", {
label: 'Check Spelling',
icon: this.path + "images/spell.png",
command: b
});
}
});
})();
Does anyone know if it is possible to make a working plugin? Is there a way to force the editor to update the hidden textarea, or is there another DOM element I can pass to the spellchecker?
Update:
In case it is useful, the SCAYT version of my plugin uses the following execute function
exec: function (editor) {
$Spelling.SpellCheckAsYouType($(editor.element).attr('id'))
}
Update 2:
I found a soltion for the normal spell check, I can call editor.UpdateElement() before running the spell check and it works! I'm not sure why though, when I inspect the original textarea with firebug the value doesn't seem to have changed.
New Spellcheck plugin
(function () {
//Section 1 : Code to execute when the toolbar button is pressed
var a = {
exec: function (editor) {
editor.updateElement();
$Spelling.SpellCheckInWindow($(editor.element).attr('id'));
}
},
//Section 2 : Create the button and add the functionality to it
b = 'javascriptspellcheck';
CKEDITOR.plugins.add(b, {
init: function (editor) {
editor.addCommand(b, a);
editor.ui.addButton("JavaScriptSpellCheck", {
label: 'Check Spelling',
icon: this.path + "images/spell.png",
command: b
});
}
});
})();
I still can't get SCAYT to work though. I found a ckeditor plugin to catch change events, and tried to call the updateElement() funciton again on every change. This doesn't work though, can anyone help?
My SCAYT plugin using the ckeditor onchange plugin:
exec: function (editor) {
editor.on('change', function (e) { this.updateElement(); });
$Spelling.SpellCheckAsYouType($(editor.element).attr('id'));
}
After contacting support for JavaScriptSpellcheck, they replied saying "SCAYT will not work with any editor as it risks injecting junk HTML into your forms". So the SCAYT plugin for CK Editor is not possible. As in my question update, the code for a working Spell Check in Window plugin for CK Editor (v3.6) is below:
(function () {
//Section 1 : Code to execute when the toolbar button is pressed
var a = {
exec: function (editor) {
editor.updateElement();
$Spelling.SpellCheckInWindow($(editor.element).attr('id'));
}
},
//Section 2 : Create the button and add the functionality to it
b = 'javascriptspellcheck';
CKEDITOR.plugins.add(b, {
init: function (editor) {
editor.addCommand(b, a);
editor.ui.addButton("JavaScriptSpellCheck", {
label: 'Check Spelling',
icon: this.path + "images/spell.png",
command: b
});
}
});
})();

Categories

Resources