I'm building a Grapesjs plugin and have added a 'jscript' trait to a button component, which appears as a codemirror textarea. The idea is for users to be able to edit some javascript code associated with a button. I can't seem to intercept the codemirror area's change event, at least, not the proper codemirror specific version.
Happily, when I edit the codemirror area and change focus, a regular 'change' event triggers the Grapejs onEvent handler within my plugin's editor.TraitManager.addType('jcodemirror-editor', {} - good. I can then store the contents of the codemirror area into the trait.
onEvent({ elInput, component, event }) {
let code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
},
However if we paste or backspace or delete etc. in the codemirror area then the regular 'change' event is never issued!
So I'm trying to intercept the deeper codemirror specific 'change' event which is usually intercepted via cm.on("change", function (cm, changeObj) {} and which is triggered more reliably (unfortunately also on each keystroke). How do I wire this codemirror specific event to trigger the usual onEvent({ elInput, component, event }) {} code?
I have a workaround in place in my https://jsfiddle.net/tcab/1rh7mn5b/ but would like to know the proper way to do this.
My Plugin:
function customScriptPlugin(editor) {
const codemirrorEnabled = true // otherwise trait editor is just a plain textarea
const script = function (props) {
this.onclick = function () {
eval(props.jscript)
}
};
editor.DomComponents.addType("customScript", {
isComponent: el => el.tagName == 'BUTTON' && el.hasAttribute && el.hasAttribute("data-scriptable"),
model: {
defaults: {
traits: [
{
// type: 'text',
type: 'jcodemirror-editor', // defined below
name: 'jscript',
changeProp: true,
}
],
script,
jscript: `let res = 1 + 3; console.log('result is', res);`,
'script-props': ['jscript'],
},
},
});
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
// This is the 'more accurate' codemirror 'change' event
// which is triggered key by key. We need it cos if we paste
// or backspace or delete etc. in codemirror then the
// regular 'change' event is never issued! But how do we get
// this event to trigger the proper, usual 'onEvent' below?
// Currently cheating and doing the onEvent work here with
// this special handler.
myCodeMirror.on("change", function (cm, changeObj) { // HACK
const component = editor.getSelected()
const code_to_run = myCodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
console.log('onEvent hack - (myCodeMirror change event) updating jscript trait to be:', code_to_run)
})
}
return el;
},
// UI textarea & codemirror 'change' events trigger this function,
// so that we can update the component 'jscript' trait property.
onEvent({ elInput, component, event }) {
let code_to_run
if (codemirrorEnabled)
code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
else
code_to_run = elInput.querySelector('textarea').value
console.log('onEvent - updating jscript trait to be:', code_to_run)
component.getTrait('jscript').set('value', code_to_run);
}, // onEvent
// Updates the trait area UI based on what is in the component.
onUpdate({ elInput, component }) {
console.log('onUpdate - component trait jscript -> UI', component.get('jscript'))
if (codemirrorEnabled) {
const cm = elInput.querySelector(".CodeMirror").CodeMirror
cm.setValue(component.get('jscript'))
// codemirror content doesn't appear till you click on it - fix with this trick
setTimeout(function () {
cm.refresh();
}, 1);
}
else {
const textareaEl = elInput.querySelector('textarea');
textareaEl.value = component.get('jscript')
// actually is this even needed as things still update automatically without it?
// textareaEl.dispatchEvent(new CustomEvent('change'));
}
}, // onUpdate
}) // addType
editor.BlockManager.add(
'btnRegular',
{
category: 'Basic',
label: 'Regular Button',
attributes: { class: "fa fa-square-o" },
content: '<button type="button">Click Me</button>',
});
editor.BlockManager.add(
'btnScriptable',
{
category: 'Scriptable',
label: 'Scriptable Button',
attributes: { class: "fa fa-rocket" },
content: '<button type="button" data-scriptable="true">Run Script</button>',
});
}
const editor = grapesjs.init({
container: '#gjs',
fromElement: 1,
height: '100%',
storageManager: { type: 0 },
plugins: ['gjs-blocks-basic', 'customScriptPlugin']
});
According to official Grapesjs documentation on traits integrating external ui components you can trigger the onEvent event manually by calling this.onChange(ev).
So within createInput I continued to intercept the more reliable myCodeMirror.on("change", ... event and within that handler triggered the onEvent manually viz:
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const self = this // SOLUTION part 1
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
myCodeMirror.on("change", function (cm, changeObj) {
self.onChange(changeObj) // SOLUTION part 2
})
}
return el;
},
Related
I have a quill editor with a quill-better-table module. I want it to be uneditable at certain times, so I set it to readOnly. This works for buttons and text, but the table is still editable. The context menu (operationMenu) is also available.
Is there any way to make the better-table uneditable?
const quill = new Quill('#editor-wrapper', {
theme: 'snow',
readOnly: this.readOnly || false,
modules: {
table: false, // disable table module
'better-table': {
operationMenu: {
items: {
unmergeCells: {
text: 'Another unmerge cells name'
}
}
},
toolbar: {
container: [
['tableCreate'], // custom button for create table
],
handlers: {
'tableCreate': () => this.addCreateTableBtnEvent()
}
},
}
}
})
addCreateTableBtnEvent: function () {
const table = quill.getModule('better-table');
table.insertTable(2, 2);
}
Maybe it's not an elegant solution and the modification must be taken into account if I upgrade quill-better-table.js in the future, but so far it works.
I edited the quill-better-table.js and put in checks whether the Quill editor is editable or not.
If it's not editable than there is no content menu or column tool on the quill-better-table.
You can skip any function of the quill-better-table in this way.
...
menuInitial(_ref)
{
let {
table,
left,
top
} = _ref;
var editable = this.quill.root.getAttribute("contenteditable")
if ( editable === 'true' )
{
this.domNode = document.createElement('div');
this.domNode.classList.add('qlbt-operation-menu');
..
Actually, I want to show the attributes of clicked/selected element in the dialog, first, it does properly but when I close the dialog and open for any other element its show the prev attributes list because let listAttrElements = getListOfItems(elAttributes);
this line won't run again until reloading the page.
I just want to update the list whenever the modal opens for a single element, close open again for another element then values should be as per another element.
// Our dialog definition.
CKEDITOR.dialog.add( 'abbrDialog', function( editor ) {
let selection = editor.getSelection();
let element = selection.getStartElement();
let elAttributes = Object.keys(element.getAttributes());
let listAttrElements = getListOfItems(elAttributes);
return {
// Basic properties of the dialog window: title, minimum size.
title: 'Attributes',
minWidth: 400,
minHeight: 200,
// Dialog window content definition.
contents: [
{
// Definition of the Basic Settings dialog tab (page).
id: 'basic',
// label: 'Basic Settings',
// The tab content.
elements: listAttrElements
}
],
// Invoked when the dialog is loaded.
onShow: function() {
console.log('here in on show');
},
// This method is invoked once a user clicks the OK button, confirming the dialog.
onOk: function() {
CKEDITOR.dialog.getCurrent().reset();
},
onLoad: () => {
console.log('loaded')
}
};
});
function getListOfItems (attrObj) {
return attrObj.map ((el, index) => { return {
// Text input field for the abbreviation text.
type: 'checkbox',
id: index,
label: el,
// Called by the main setupContent method call on dialog initialization.
setup: function( element ) {
console.log('new setuop require')
this.setValue( element.getText() );
},
// Called by the main commitContent method call on dialog confirmation.
// set value onchange
commit: function( element ) {
element.setText( this.getValue() );
}
}});
}
Thanks in advance for the help!
https://github.com/ckeditor/ckeditor4/issues/4906#issuecomment-927666316
This is how I solved it:
editor.on('dialogHide', function (evt) {
if (evt.data.getName()=== 'abbrDialog') {
editor._.storedDialogs['abbrDialog'] = false
}
});
I am using angular-silkgrid with angular 7. I am using inline editing feature. I am using single select editor for inline edit. Currently I want to achieve this functionality:
As soon as user click on the editable field , the select drop down will be visible.On select any option from select dropdown the inline mode should exist and column value should be updated.
currently I need to click on other field to exit from inline mode(I want to achieve this on select option select)
editor: {
// display checkmark icon when True
enableRenderHtml: true,
// tslint:disable-next-line:max-line-length
collection: [{
value: 1,
label: 'Sucessful'
}, {
value: 2,
label: 'Unsucessful'
}],
model: Editors.singleSelect, // CustomSelectEditor
elementOptions: {
autoAdjustDropPosition: false,
onClick: (event, rr) => {
// here i want to write some code which can trigger to grid to start update
}
}
}
Thanks All for the answers. I have solved my issue as below:
editor: {
enableRenderHtml: true,
collection: [{ value: CCLStaus.Sucessfull, label: 'Sucessful' }, { value: CCLStaus.UnSucessfull, label: 'Unsucessful' }],
model: Editors.singleSelect,// CustomSelectEditor
elementOptions: {
onClick: (event) => {
const updateItem = this.angularSilkGrid.gridService.getDataItemByRowIndex(this.rowInEditMode);
updateItem.status = +event.value;
this.angularSilkGrid.gridService.updateItem(updateItem, { highlightRow: false });
this.angularSilkGrid.gridService.renderGrid();
}
}
}
Generally,
grid.getEditorLock().commitCurrentEdit()
will commit and close the editor.
Also, any of
grid.navigateRight()
grid.navigateLeft()
grid.navigateDown()
grid.navigateUp()
grid.navigateNext()
grid.navigatePrev()
will commit and exit gracefully. In the select2 editor, you'll notice:
this.init = function () {
...
// Set focus back to select2 element on closing.
$input.on('select2:close', function (e) {
if ((e.params.originalSelect2Event && e.params.originalSelect2Event.data)
|| e.params.key === 9) {
// item was selected with ENTER or no selection with TAB
args.grid.navigateNext();
} else {
// closed with no selection
setTimeout(function () {
$input.select2('focus');
}, 100);
}
});
};
this.destroy = function () {
$input.select2('destroy');
$input.remove();
};
, noting that args.grid.navigateNext() will commit and close the editor, including calling the destroy() method at the appropriate time.
From the Angular-Slickgrid Editor Example there's a checkbox in the example to auto commit and that is a setting to you need to enable in your Grid Options
this.gridOptions = {
autoEdit: true,
autoCommitEdit: true,
}
The lib will internally call grid.getEditorLock().commitCurrentEdit() like Ben wrote in his answer, in Angular-Slickgrid you can just set the autoCommitEdit flag that I added.
I am aware with the tweaks required for enabling focus inside the tinymce pop up when in bootstrap modal.
But currently I am working with a vuetify dialog. Which does'nt seem to focus the pop up fields of tinymce.
I have gone through this question but it does not work in context to vuetify
TinyMCE 4 links plugin modal in not editable
Below is my code I have removed some methods just for clean up and have kept basic things that I have tried in mounted event & editor init.
<no-ssr placeholder="Loading Editor..">
<tinymce
id="content"
:toolbar2="toolbar2"
:toolbar1="type=='BASIC'?'':toolbar1"
:plugins="plugins"
:other_options="other_options"
v-model="content"
#input="handleInput"
v-on:editorInit="initCallBack"
/>
</no-ssr>
</template>
<script>
//https://dyonir.github.io/vue-tinymce-editor/?en_US
export default {
props: {
value: { type: String },
type: { type: String }
},
data() {
return {
content: this.value,
plugins: this.getPlugins(),
toolbar2: "",
toolbar1: this.getToolbar1(),
other_options: {
menubar: this.getMenubar(),
height: "320",
file_browser_callback: this.browseFile,
auto_focus: '#content'
}
};
},
mounted(event) {
// window.tinyMCE.activeEditor.focus();
// window.tinymce.editors["content"]
console.log(this.$el, event);
let list=document.getElementsByClassName("mce-textbox");
for (let index = 0; index < list.length; ++index) {
list[index].setAttribute("tabindex", "-1");
}
// if ((event.target).closest(".mce-window").length) {
// e.stopImmediatePropagation();
// }
// this.$refs.refToElement ..$el.focus())
// this.el.addEventListener('focusin', e => e.stopPropagation());
},
methods: {
handleInput(e) {
this.$emit("input", this.content);
},
initCallBack(e) {
window.tinymce.editors["content"].setContent(this.value);
window.tinymce.editors["content"].getBody().focus();
// console.log(this.$refs);
// const focusable = this.$refs.content.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
// focusable.length && focusable[0].focus()
document.getElementById("content").addEventListener("onfocusin", console.log("fucssed"));
// tinymce.activeEditor.fire("focus");
this.$el.querySelector(".mce-tinymce").addEventListener('focusin', e =>{ e.stopImmediatePropagation();console.log('event',e)});
const element = this.$el.querySelector(".mce-tinymce");
let _this=this;
if (element)
this.$nextTick(() => {
element.focus();
console.log("FOCUSED",element,_this);
// element.stopImmediatePropagation();
});
// window.tinyMCE.activeEditor.focus();
// console.log(this.$el,e);
// if ((e).closest(".mce-window").length) {
// e.stopImmediatePropagation();
// }
}
};
</script>```
I am using the component : https://github.com/dyonir/vue-tinymce-editor
But fields of the pop are not getting focussed/edited.
From vuetify 2.0 onwards there is a new prop 'retain-focus' which you can set to false in order to fix the above issue.
<v-dialog :retain-focus="false">
Tab focus will return to the first child of the dialog by default. Disable this when using external tools that require focus such as TinyMCE or vue-clipboard.
Edit:
Here is the link to retain-focus prop implementation GitHub:
https://github.com/vuetifyjs/vuetify/issues/6892
Modify the z-index of v-dialog:
Current:
z-index: 202
Modified:
<style>
.v-dialog__content {z-index: 203 !important;}
</style>
Do not forget the !important to give priority to style.
To prevent the tab-event to be fired in my Angular 5+ component, I overrode the standard quill implementation for the tab-event with a binding as mentioned in the documentation
const bindings = {
// This will overwrite the default binding also named 'tab'
tab: {
key: 9,
handler: function(range) {
// Handle tab
},
},
};
As we don't want to deal with nested ordered or unordered lists, I expected the tab-event not to be triggered on empty list entries.
Does anyone knows how to modify a custom handler to prevent this functionality?
You must define the 'modules' property on this way:
var quill = new Quill('#editor', {
modules: {
keyboard: {
bindings: {
indent: {
key: 'Tab',
handler() {
return false;
},
},
outdent: {
key: 'Tab',
shiftKey: true,
handler() {
return false;
},
},
}
}
}
});
Well, there are other default handlers for tab like indent, outdent and outdent backspace. You can check this file https://github.com/quilljs/quill/blob/develop/modules/keyboard.js#L184
So you have to overwrite more :)