Cannot get instance of CKEditor - javascript

I have several fields which need to be initialized with CKEditor, for this I have created an helper class that contains the initEditor method.
The method below should return the initialized editor but it doesn't:
window.CKEditorHelper = window.CKEditorHelper || {};
(function (exports) {
exports.initEditor = function (input, myEditor) {
ClassicEditor
.create(document.querySelector(input), {
language: {
ui: 'en'
content: 'en'
}
})
.then(editor => {
myEditor = editor;
});
};
})(window.CKEditorHelper);
this is called in the following way:
let editor = null;
CKEditorHelper.initEditor('#description', editor);
so when I click on a button:
$('#save').on('click', function(){
console.log(editor.getData());
});
I get:
Cannot read property 'getData' of null
what I did wrong?

There are some issues on your code
let editor = null;
the let keyword only define a variable within function scope, when you use editor on another scope (your click handle event), it could be undefined
Another line
myEditor = editor;
This just simple made the reference to your original editor object will gone
Here is my solution to fix it
Change the way you init an editor like bellow
window.editorInstance = {editor: null};
CKEditorHelper.initEditor('#description', editorInstance);
Change your CKEditorHelper to
window.CKEditorHelper = window.CKEditorHelper || {};
(function (exports) {
exports.initEditor = function (input, myEditorInstance) {
ClassicEditor
.create(document.querySelector(input), {
language: {
ui: 'en'
content: 'en'
}
})
.then(editor => {
myEditorInstance.editor = editor;
});
};
})(window.CKEditorHelper);
And when you want to use your editor
console.log(editorInstance.editor.getData());

You can give this in javascript
$(document).ready(function () {
CKEDITOR.replace('tmpcontent', { height: '100px' })
})
take the value by using following
$('#save').on('click', function(){
var textareaValue = CKEDITOR.instances.tmpcontent.getData();
});
<label class="control-label">Message</label>
<textarea name="tmpcontent" id="tmpcontent" class="form-control"></textarea>
//OR in latest version
var myEditor;
ClassicEditor
.create( document.querySelector( '#description' ) )
.then( editor => {
console.log( 'Editor was initialized', editor );
myEditor = editor;
} )
.catch( err => {
console.error( err.stack );
} );
and then get data using
myEditor.getData();

Related

CK Editor Laravel Livewire

Is there anyway for Laravel Livewire to make my CKEditor the same behavior as a wire:model.lazy? currently I have a script tag that listens for any changes. Which causes for every type a request towards the component..
<script>
ClassicEditor
.create(document.querySelector('#body'))
.then(editor => {
editor.model.document.on('change:data', () => {
#this.set('body', editor.getData());
})
})
.catch(error => {
console.error(error);
});
</script>
The behavior I want is either a button or everytime I lose focus on the CKEditor the $body will be updated.
Just listen to the submit button and update the value on click:
let editor;
ClassicEditor.create(document.getElementById('post'), {
// configs
})
.then(newEditor => {
editor = newEditor;
})
.catch(error => {});
document.querySelector('button[type="submit"]').addEventListener('click', function () {
#this.set('post', editor.getData());
});
For me and anybody else who have the same issue
The main issue here is on.change this piece of code on( 'change:data'... will make the editor send post request on every single key press.
Solving the issue.
<script>
let body_changed = false;
ClassicEditor
.create(document.getElementById('body'), {})
.then(editor => {
window.body = editor;
detectTextChanges(editor);
detectFocusOut(editor);
})
function detectFocusOut(editor) {
editor.ui.focusTracker.on('change:isFocused', (evt, name, isFocused) => {
if (!isFocused && body_changed) {
body_changed = false;
#this.set('body', editor.getData());
}
})
}
function detectTextChanges(editor) {
editor.model.document.on('change:data', () => {
body_changed = true;
});
}
</script>
Hope this will help me and others in future :)

Angular 8, how to reference a component property inside a function of a javascript library

I am using Semantic UI in angular and I have the following code:
componentProperty: boolean = false;
ngOnInit() {
(<any>$('.ui.dropdown')).dropdown();
(<any>$('.ui.input')).popup({
on: 'focus',
onShow: (e) => {
return this.componentProperty;
}
});
}
the function popup is defined by semantic UI. Semantic UI relies on Jquery.
Here componentProperty is undefined.
This about scope. Try:
ngOnInit() {
const me = this;
(<any>$('.ui.dropdown')).dropdown();
(<any>$('.ui.input')).popup({
on: 'focus',
onShow: (e) => {
return me.componentProperty;
}
});
}
Have you tryed to define the onShow function in the components scope?
ngOnInit() {
const onShow = () => this.componentProperty;
(<any>$('.ui.input')).popup({
on: 'focus',
onShow
});
}
And if that still won't work:
const doOnShow = () => this.componentProperty;
(<any>$('.ui.input')).popup({
on: 'focus',
onShow: doOnShow.bind(this)
});

How will I access the components in the script inside a template?

I would like reuse my html components that contains some javascript code, so for simplify I bring one simple example:
index.html:
<!DOCTYPE html>
<html>
<head></head>
<body>
<my-component></my-component>
<script src="index.js"></script>
</body>
</html>
my-component.html:
<template>
<div id="something"></div>
<script>
// It doesn't work, this here is "window"
document.getElementById("something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, {mode: "no-cors"})
.then(res => res.text())
.then(html => {
const parser = new DOMParser()
const document = parser.parseFromString(html, 'text/html')
const head = document.head
const template = head.querySelector('template')
return template
})
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'})
shadow.appendChild(document.importNode(template.content, true))
}
}
return customElements.define(name, UnityComponent)
}
function loadComponent (name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template))
}
return {loadComponent}
}())
makeComponent.loadComponent("my-component", "my-component.html")
I can with this code, but it copy all variables of the script to window:
<template>
<div id="something"></div>
<style onload="templFunc.apply(this.getRootNode())"></style>
<script>
function templFunc() {
// It works
let text = "Something"
this.querySelector('#something').innerHTML = text
// but...
console.log(window.text) // => "Something"
}
</script>
</template>
It doesn't make a sense, if the script is inside the template at least should can access the elements inside the template, else the template is almost not util for the javascript, so, I can't understand the intention of use script inside the template or how to reuse the web components that use javascript, Is it wrong do this?
So, How to I access the components in the script inside a template without copy all script variables to window?
As you found out <script> inside a <template> runs in Global scope
If you use Angular, note Angular bluntly removes all <script> content from Templates.
One workaround is to add an HTML element that triggers code within Element scope.
<img src onerror="[CODE]"> is the most likely candidate:
This then can call a Global function, or run this.getRootNode().host immediatly.
<template id=scriptContainer>
<script>
console.log("script runs in Global scope!!");
function GlobalFunction(scope, marker) {
scope = scope.getRootNode().host || scope;
console.log('run', marker, 'scope:', scope);
scope.elementMethod && scope.elementMethod();
}
</script>
<img src onerror="(()=>{
this.onerror = null;// prevent endless loop if function generates an error
GlobalFunction(this,'fromIMGonerror');
})()">
</template>
<my-element id=ONE></my-element>
<my-element id=TWO></my-element>
<script>
console.log('START SCRIPT');
customElements.define('my-element',
class extends HTMLElement {
connectedCallback() {
console.log('connectedCallback', this.id);
this.attachShadow({ mode: 'open' })
.append(scriptContainer.content.cloneNode(true));
}
});
</script>
More detailed playground, including injecting SCRIPTs, at: https://jsfiddle.net/WebComponents/q0k8ts6b/
Here is the solution,
my-component:
<template>
<div id="something"></div>
<script>
makeComponent.getComponent("my-component", "something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
this.setAttribute("id", name);
shadow.appendChild(document.importNode(template.content, true));
}
}
return customElements.define(name, UnityComponent);
}
function getComponent(host, query) {
return document.getElementById(host).shadowRoot.querySelector(query);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { getComponent, loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However I think that this is not the better way, maybe I need use the events here, and pass the shadow scope to a listener that is called in the script tag in the template, but I don't know how to pass the scope to the event yet.
Up:
With events:
my-component:
<template>
<div id="something"></div>
<script>
document.addEventListener("custom-event", (e) => {
console.log(e.detail.target.shadowRoot.getElementById("date-year"));
})
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(document.importNode(template.content, true));
const event = new CustomEvent("custom-event", {'detail': {
target: this
}});
document.dispatchEvent(event);
}
}
return customElements.define(name, UnityComponent);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However, I prefer the first solution even. But if you need of nested components the first doesn't work, you need of the second.

What is el() function? ReferenceError: "el is not defined"

I am writing a block in wordpress gutenberg but wordpress showing el( is not defined?
my edit: function is
edit: function( props ) {
function onChange( event ) {
props.setAttributes( { author: event.target.value } );
}
return el( 'input', {
value: props.attributes.author,
onChange: onChange,
} );
},
how to include el support in my plugin ?
You should define el first which contains the block element.
var el = element.createElement;

Polymer this and Cytoscape this

So I have a problem and it's most likely because I still don't get JavaScript... Cytoscape has their own 'this' and Polymer has their 'this'
<div id="labelFld">{{node.label}}</div>
<div id="measureFld">{{node.measure}}</div>
<div id="timesignatureFld">{{node.time_signature}}</div>
<div id="voiceFld">{{node.voice}}</div>
<div id="beatFld">{{node.beat}}</div>
<div id="meventFld">{{node.event}}</div>
var cy;
cytoscape({
ready : function () {
Polymer: ({
...
properties : {
node : {
type : Object,
notify : true,
readOnly : true
}
},
...
// Fires when the local DOM has been fully prepared
ready : function () {
var self_node = this.node; // <- 'this' via Polymer
try {
cy = cytoscape({
container : this.$.rhythmgraph,
ready : function (e) {}
});
} catch (err) {
console.log(err);
}
// Assign handler to Cytoscape click event for node elements
cy.on('click', 'node', {
"nodedata" : self_node // <- Seems to pass by value, not reference
}, function (e) {
self_node = this.data(); // <- 'this' via Cytoscape
console.log(self_node);
// e.data.nodedata = this.data();
});
},
But in order to update my <div>{{node.label}}</div> I have to be able to do this.node.label = "N42" // Polymer but I can't do it in the cy.on('click','node', ... ) because I need this.data() // Cytoscape inside there.
Scope is really killing me on this.
EDIT
In the end, I created an Observer to watch and update:
self_node = this.selectedNode;
var poly = this;
Object.observe(self_node, function(changes) {
changes.forEach(function(change) {
if(change.type == "update") {
poly.selectedNode = {
"id": change.object.id,
... }
};
poly.notifyPath('selectedNode.id', change.object.id);
}
});}.bind(poly));
I find this a common gotcha among JS dev beginners. You'll have to bind the function to its proper this reference.
cy.on('click', 'node', {
"nodedata" : rhynode
}, function (e) {
e.data.nodedata = this.data();
console.log(e.data.nodedata);
}.bind(this)); // here
With ES2015, arrow functions would bind automatically to the proper this:
cy.on('click', 'node', {
"nodedata" : rhynode
}, (e) => {
e.data.nodedata = this.data();
console.log(e.data.nodedata);
});

Categories

Resources