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

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;

Related

Error returning value when editing message

I've been thinking about how to do this for days and if you could help me.
I expose you, I have followed the CKEditor 5 tutorial to the point of including the mentions, this is where my problem begins.
Following the tutorial we come to the part of the output of the mention, this as they do in the tutorial I have transformed it from <span> to <a> together with its class, its URL and its data. Well the editor shows it fine until you want to edit the post.
That is, imagine this message:
Hello world I am the first code of #undercover
Well when I include it in the database everything is correct, but when we return that same message to the editor it becomes:
Hello world I am the first code of #undercover
Investigating and as my Javascript is quite low I have been trying things.
The conversion. I've tried but there is something I can't understand and it's like passing the values ​​to the function. Let me explain, when I pass that <a> that I save in the database, if I transform it into a <span> and then insert it if it tries to make the change to mention but the class attribute and the href attribute are "orphaned".
Well, I have 3 ideas and I can't do any of them at some point I get stuck, so I ask you for help.
My idea is to return the text I have in the database and the editor reads it fine.
Idea 1: Put the text in Javascript and identify and exchange the mentions that are in the database by the function of the mentions command, this is really complicated for me because it is very abstract, even so I am still looking for how to do it.
Idea 2: Save the value in the database in another way, this has been a last idea, how to search and put the of the mention but with the custom values. Even if {mention: {id: #undercover}} were saved in the database, I wouldn't care as long as it was later transformed correctly in the editor.
Idea 3: The use of conversions, I have managed to understand this and it has cost me that its function is to identify the mention within the editor and exchange it for the data you want. In this idea I can't understand how to pass the values ​​other than manually, that is, how to pass the class and href attributes.
Here I leave you the section of the code, I hope you can give me a hand and thank you very much.
function MentionCustomization( editor ) {
// The upcast converter will convert <a class="mention" href="" data-user-id="">
// elements to the model 'mention' attribute.
editor.conversion.for( 'upcast' ).elementToAttribute( {
view: {
name: 'a',
key: 'data-mention',
classes: 'mention',
attributes: {
href: true,
'data-user-id': true,
}
},
model: {
key: 'mention',
value: viewItem => {
// The mention feature expects that the mention attribute value
// in the model is a plain object with a set of additional attributes.
// In order to create a proper object, use the toMentionAttribute helper method:
const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewItem, {
// Add any other properties that you need.
link: viewItem.getAttribute( 'href' ),
userId: viewItem.getAttribute( 'data-user-id' )
} );
return mentionAttribute;
}
},
converterPriority: 'high'
} );
// Downcast the model 'mention' text attribute to a view <a> element.
editor.conversion.for( 'downcast' ).attributeToElement( {
model: 'mention',
view: ( modelAttributeValue, { writer } ) => {
// Do not convert empty attributes (lack of value means no mention).
if ( !modelAttributeValue ) {
return;
}
return writer.createAttributeElement( 'a', {
class: 'group-color-'+modelAttributeValue.group,
'data-mention': modelAttributeValue.id,
// 'data-user-id': modelAttributeValue.userId,
'href': '/member/profile/'+modelAttributeValue.user_id,
}, {
// Make mention attribute to be wrapped by other attribute elements.
priority: 20,
// Prevent merging mentions together.
id: modelAttributeValue.uid,
} );
},
converterPriority: 'high'
} );
}
$.ajax({
type: "POST",
dataType: "json",
url: "/members/list_json",
success: function(info){
ClassicEditor
.create( document.querySelector( '#comment' ), {
extraPlugins: [ MentionCustomization ],
updateSourceElementOnDestroy: true,
language: 'es',
toolbar: [ 'bold', 'italic', '|' , 'link', '|', 'bulletedList'],
mention: {
feeds: [
{
marker: '#',
feed: getFeedItems,
minimumCharacters: 2,
itemRenderer: customItemRenderer,
}
]
}
} )
.then( editor => {
window.editor = editor;
/*
*/
} )
.catch( err => {
console.error( err.stack );
} );
let list_members = [];
for(let i = 0; i < info.length; i++){
var member = info[i];
list_members.push(member);
}
function getFeedItems( queryText ) {
return new Promise( resolve => {
setTimeout( () => {
const itemsToDisplay = list_members
.filter( isItemMatching )
.slice( 0, 10 );
resolve( itemsToDisplay );
}, 100 );
} );
function isItemMatching( item ) {
const searchString = queryText.toLowerCase();
return (
item.username.toLowerCase().includes( searchString )
);
}
}
},
});
function customItemRenderer( item ) {
const itemElement = document.createElement( 'span' );
const avatar = document.createElement( 'img' );
const userNameElement = document.createElement( 'span' );
itemElement.classList.add( 'mention__item');
avatar.src = `${ item.avatar }`;
avatar.classList.add('image-fluid', 'img-thumbnail', 'rounded-circle');
userNameElement.classList.add( 'mention__item__user-name' );
userNameElement.style.cssText = 'color: '+ item.group_color +';';
userNameElement.textContent = item.id;
itemElement.appendChild( avatar );
itemElement.appendChild( userNameElement );
return itemElement;
}

Recreating the columns block - building a custom block

As the title, I am looking to recreate the basic functionality of the WP columns block. The reason for this is-
WP adds a number of controls that I do not want the user to have (variations, width slider)
The allowed blocks, a column, lets the user add any sort of content they like. I am looking to have control over this with an allowed block list
I have created the following edit function:
( function( wp ) {
var registerBlockType = wp.blocks.registerBlockType;
var el = wp.element.createElement;
var __ = wp.i18n.__;
const { RadioControl, PanelBody, RangeControl } = wp.components;
const { useBlockProps, InspectorControls, InnerBlocks } = wp.blockEditor;
const allowedBlocks = [ 'core/paragraph', 'core/button' ];
registerBlockType( 'wpboiler-core/columns', {
apiVersion: 2,
title: __(
'Columns',
'columns'
),
description: __(
'A block for displaying content in columns',
'columns'
),
category: 'design',
icon: 'schedule',
supports: {
html: false,
},
attributes: {
columnselect: {
type: 'number',
default: 2,
},
},
edit: function(props) {
const { attributes, setAttributes } = props;
const { columnselect } = attributes;
const onChangeColumnRange = value => setAttributes({ columnselect: value });
let columnsContainer = [];
for(var n = 1; n <= columnselect; n++) {
columnsContainer.push(
el(
'div',
null,
el(
InnerBlocks, {
allowedBlocks: allowedBlocks,
}
)
)
);
};
return el(
'section',
useBlockProps(attributes),
// INSPECTOR CONTROL BEGIN
el(
InspectorControls,
null,
el(
PanelBody,
{
title: "Columns",
},
el(
RangeControl, {
min: 2,
max: 4,
value: columnselect,
onChange: onChangeColumnRange,
}
),
),
),
// INSPECTOR CONTROL END
el(
'div',
{ className: 'columns__container' },
columnsContainer
),
);
},
save: function() {
return null;
},
} );
}(
window.wp
) );
The issue I am coming up against is multiple InnerBlocks. The function creates a list of InnerBlock areas. However, editing one area changes them all.
I believe one method to get around this would be to create a custom column block which contains an InnerBlock component, and render that in the for loop instead of InnerBlock. So something along the lines of...
for(var n = 1; n <= columnselect; n++) {
columnsContainer.push(
{RENDER_COLUMN_BLOCK}
);
};
But if I did this, would I still come up with the same issue? That editing one of the columns would in fact edit them all? Does each instance need to have an ID to know which is being edited?
And I am also struggling to find out how I render another custom component inside a block when not using a build-step (es5).
Any help on this task would be appreciated.
Update
A 'solution' I have created after coming across the following post is as follows, where it basically turns off the block appender once the desired column count is reached. Each new instance creates a custom block called column which has its own set of allowed blocks.
// COLUMNS index.js
( function( wp ) {
var registerBlockType = wp.blocks.registerBlockType;
var el = wp.element.createElement;
var __ = wp.i18n.__;
const { useSelect } = wp.data;
const { useBlockProps, InnerBlocks } = wp.blockEditor;
const allowedBlocks = [ 'wpboiler-core/column' ];
registerBlockType( 'wpboiler/columns', {
apiVersion: 2,
title: __(
'Columns',
'columns'
),
description: __(
'Displays content in columns',
'columns'
),
category: 'design',
icon: 'schedule',
supports: {
html: false,
},
edit: function(props) {
const { attributes, clientId } = props;
const innerBlockCount = useSelect((select) => select('core/block-editor').getBlock(clientId).innerBlocks);
return el(
'section',
useBlockProps(attributes),
__( 'Add columns by pressing the + icon. Maximum 4 columns', 'columns' ),
el(
'div',
{ className: 'columns__container' },
innerBlockCount.length > 3 ?
el(
InnerBlocks, {
allowedBlocks: allowedBlocks,
renderAppender: false
}
)
:
el(
InnerBlocks, {
allowedBlocks: allowedBlocks,
}
),
)
);
},
save: function() {
return el(
'section',
{ className: 'columns' },
el(
'div',
{ className: 'columns__container' },
el(
InnerBlocks.Content, {},
),
),
);
},
} );
}(
window.wp
) );
// COLUMN - INDIVIDUAL index.js
( function( wp ) {
var registerBlockType = wp.blocks.registerBlockType;
var el = wp.element.createElement;
var __ = wp.i18n.__;
const { useBlockProps, InnerBlocks } = wp.blockEditor;
const allowedBlocks = [ 'core/heading', 'core/paragraph', 'core/button', 'core/list' ];
registerBlockType( 'wpboiler/column', {
apiVersion: 2,
title: __(
'Column',
'column'
),
description: __(
'Displays an individual column',
'column'
),
category: 'widgets',
icon: 'schedule',
supports: {
html: false,
},
parent: [ 'wpboiler-core/columns' ],
edit: function() {
return el(
'div',
useBlockProps(),
el(
'div',
{ className: 'column' },
el(
InnerBlocks,
{
allowedBlocks: allowedBlocks,
},
),
),
);
},
save: function() {
return el(
'div',
{ className: 'column' },
el(
InnerBlocks.Content, {},
),
);
},
} );
}(
window.wp
) );
However, doing it this way removes the need for the range control/columns select. But it does act in a similar (although not identical) way to the native columns block.
Again, other suggestions are welcomed.
You are correct in that you cannot have multiple <InnerBlocks> within a single block. Your best option is, as you suggest, to use two blocks: a columns wrapper block with a single <InnerBlocks> component that can only contain column blocks. Each column block can then have its own <InnerBlocks> component.
You will not need to loop over anything, since the <InnerBlocks> component will take care of rendering all the column blocks for you. Essentially you will have a columns block that outputs:
<InnerBlocks
allowedBlocks={ ['my/columns'] }
orientation="horizontal"
/>
Then your column block will just output:
<InnerBlocks/>
This is exactly how the WordPress core columns block works. I have also successfully used this myself to replace the built-in columns block.
Finally, although it seems like a lot of extra work to add a build step, I highly encourage it. It makes the code significantly easier to read and work with.

Cannot get instance of CKEditor

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();

How do i limit my own element to contain only plain text?

How do I create a block element, which can only contain plain text? (No bold, italic and so on).
I have registered my element as:
model.schema.register(mtHeaderLine, {
// Changing inheritAllFrom to '$block' creates an editable element
// but then it can contain bold text.
inheritAllFrom: '$text',
allowIn: mtHeaderDiv,
isBlock: true
});
And then I downcast with:
editor.conversion.for('downcast').add(downcastElementToElement( { model: mtHeaderLine, view: 'div' }
But that creates an element which I can't edit.
I also tried to downcast with:
view: (modelElement, viewWriter ) => {
const viewElement=viewWriter.createEditableElement('div',{ 'class': (mtHeaderLine) ,isghost: isGhost });
return viewElement;
}
But that did not give me an editable element either.
You need to use Schema#addAttributeCheck(). That method allows you to register a callback which will disallow all attributes on $text which is in some context (e.g. a child of a model <plaintext> element):
function MyPlugin( editor ) {
editor.model.schema.register( 'plaintext', {
inheritAllFrom: '$block'
} );
editor.model.schema.addAttributeCheck( ( ctx, attrName ) => {
if ( ctx.endsWith( 'plaintext $text' ) ) {
return false;
}
} );
editor.conversion.elementToElement( {
model: 'plaintext',
view: 'div'
} );
}
ClassicEditor
.create( document.getElementById( 'editor' ), {
plugins: [ ...ClassicEditor.builtinPlugins, MyPlugin ]
} )
.then( editor => {
window.editor = editor;
} )
.catch( error => {
console.error( error );
} );
DEMO: https://jsfiddle.net/rvas7pLn/1/

Toggle the State of a CKEditor plugin button

What is the correct way to toggle the state of a ckeditor plugin menu button based on the selection?
For example, in a link/unlink plugin, I would only want to enable unlink if the cursor is in a link.
editor.addCommand("unlink", {
exec: function (editor) {
//do something here
},
refresh: function (editor, path) {
// never seems to get fired. Is this even the right hook?
}
});
editor.ui.addButton("Unlink", {
label: "Unlink",
command: "unlink"
});
Thanks for the help!
There is CKEDITOR.commandDefinition#contextSensitive property that makes it possible to control the state of a command in a particular context.
For example, the actual implementation of Unlink button looks like:
CKEDITOR.unlinkCommand.prototype = {
exec: function( editor ) {
...
},
refresh: function( editor, path ) {
var element = path.lastElement && path.lastElement.getAscendant( 'a', true );
if ( element && element.getName() == 'a' && element.getAttribute( 'href' ) && element.getChildCount() )
this.setState( CKEDITOR.TRISTATE_OFF );
else
this.setState( CKEDITOR.TRISTATE_DISABLED );
},
contextSensitive: 1,
...
};

Categories

Resources