TinyMCE Dynamic Buttons/Methods - javascript

I'm attempting to create a Wordpress plugin which allows you to easily add a dropdown menu with items in for inserting shortcodes. To do this, I need to 'hook' into TinyMCE, registering a custom plugin. My plan is to allow users to setup the shortcode menu items using a simple PHP array.
The following class is instantiated which registers a new button and the plugin script:
<?php
namespace PaperMoon\ShortcodeButtons;
defined('ABSPATH') or die('Access Denied');
class TinyMce {
public function __construct()
{
$this->add_actions();
$this->add_filters();
}
private function add_actions()
{
add_action('admin_head', [$this, 'print_config']);
}
public function print_config()
{
$shortcodes_config = include PMSB_PLUGIN_PATH . 'lib/shortcodes-config.php'; ?>
<script type='text/javascript' id="test">
var pmsb = {
'config': <?php echo json_encode($shortcodes_config); ?>
};
</script> <?php
}
private function add_filters()
{
add_filter('mce_buttons', [$this, 'register_buttons']);
add_filter('mce_external_plugins', [$this, 'register_plugins']);
}
public function register_buttons($buttons)
{
array_push($buttons, 'shortcodebuttons');
return $buttons;
}
public function register_plugins($plugin_array)
{
$plugin_array['shortcodebuttons'] = PMSB_PLUGIN_URL . 'assets/js/tinymce.shortcodebuttons.js';
return $plugin_array;
}
}
A user would create a PHP array like so (which is included in the above class and output as a javascript variable in the header):
<?php
return [
[
'title' => 'Test Shortcode',
'slug' => 'text_shortcode',
'fields' => [
'type' => 'text',
'name' => 'textboxName',
'label' => 'Text',
'value' => '30'
]
],
[
'title' => 'Test Shortcode2',
'slug' => 'text_shortcode2',
'fields' => [
'type' => 'text',
'name' => 'textboxName2',
'label' => 'Text2',
'value' => '30'
]
]
];
Finally, here's the actual plugin script:
"use strict";
(function() {
tinymce.PluginManager.add('shortcodebuttons', function(editor, url) {
var menu = [];
var open_dialog = function(i)
{
console.log(pmsb.config[i]);
editor.windowManager.open({
title: pmsb.config[i].title,
body: pmsb.config[i].fields,
onsubmit: function(e) {
editor.insertContent('[' + pmsb.config[i].slug + ' textbox="' + e.data.textboxName + '" multiline="' + e.data.multilineName + '" listbox="' + e.data.listboxName + '"]');
}
});
}
for(let i = 0; i <= pmsb.config.length - 1; i++) {
menu[i] = {
text: pmsb.config[i].title,
onclick: function() {
open_dialog(i)
}
}
}
console.log(menu);
editor.addButton('shortcodebuttons', {
text: 'Shortcodes',
icon: false,
type: 'menubutton',
menu: menu
});
});
})();
The button registers fine, the menu items also register fine but when it comes to click on to open up a modal window, I get this error:
Uncaught Error: Could not find control by type: text
I think it's something to do with the fact that the 'fields' is coming from a function which, for some reason, TinyMCE doesn't like - even though it's built correctly.
I had thought of doing this a different way - by creating a PHP file which outputs the correct JS but if I do that, I can't register it as a TinyMCE plugin when using the mce_external_plugins action hook.
I found another question which ran in to the same problem with no answer.
I've really hit a wall with this one, any help would be hugely appreciated!

Well, the typical thing happened - as soon as I post the question, I stumble upon the answer. Maybe I need to get myself a rubber desk duck?
Anyway, the clue is in the error message. Despite having looked at quite a number of tutorials which all suggest using 'text' as the control type, it's actually 'textbox'. I'm guessing this is a more recent change in TinyMCE and the tutorials I was looking at were a bit older.
I searched for yet another tutorial and found the answer staring me right in the face.

Related

WordPress Gutenberg Blocks Get Author Info

I feel like what I want to accomplish is simple: create a custom WordPress Gutenberg Block that auto-populates with the author name and image and is not editable. Then render this block on the frontend post.
I've gone at this from a lot of different angles, and I think what I need is a Dynamic Block
https://developer.wordpress.org/block-editor/tutorials/block-tutorial/creating-dynamic-blocks/
First issue is the wp.data.select("core/editor").getCurrentPostId() doesn't load the post ID. But then I'll be honest, I don't know if the return would even have the author info I want anyway. Any guidance is most appreciated.
const post_id = wp.data.select("core/editor").getCurrentPostId();
var el = wp.element.createElement;
wp.blocks.registerBlockType('my-plugin/author-block', {
title: 'Author Footer',
icon: 'admin-users',
category: 'my-blocks',
attributes: {
// Nothing stored
},
edit: wp.data.withSelect( function(select){ // Get server stuff to pass to our block
return {
posts: select('core').getEntityRecords('postType', 'post', {per_page: 1, post_id})
};
}) (function(props) { // How our block renders in the editor
if ( ! props.posts ) {
return 'Loading...';
}
if ( props.posts.length === 0 ) {
return 'No posts';
}
var post = props.posts[0];
// Do stuff with the post like maybe get author name and image???
// ... Return elements to editor viewer
}), // End edit()
save: function(props) { // Save the block for rendering in frontend post
// ... Return saved elements
} // End save()
});
Finally figured something out so thought I'd share.
edit: function(props) { // How our block renders in the editor in edit mode
var postID = wp.data.select("core/editor").getCurrentPostId();
wp.apiFetch( { path: '/wp/v2/posts/'+postID } ).then( post => {
var authorID = post.author
wp.apiFetch( { path: '/wp/v2/users/'+authorID } ).then( author => {
authorName = author.name;
authorImage = author.avatar_urls['96'];
jQuery('.author-bottom').html('<img src="'+authorImage+'"><div><h6>Written by</h6><h6 class="author-name">'+authorName+'</h6></div>');
});
});
return el(
'div',
{
className: 'author-bottom'
},
el(
'img',
{
src: null
}
),
el(
'div',
null,
el(
'h6',
null,
'Written by'
),
el(
'h6',
{
className: 'author-name'
},
'Loading...'
)
)
); // End return
}, // End edit()
As soon as the block loads, it initiates a couple apiFetch calls (one to get the author ID, then another to get the author's name and image).
While that's happening, the author block loads with the temporary text "Loading..."
Once the apiFetch is complete I used jQuery to update the author block.
There is no save function because it's a dynamic block. I registered the block in my plugin php file like so:
function my_author_block_dynamic_render($attributes, $content) {
return '
<div class="author-bottom">
<img src="'.get_avatar_url(get_the_author_email(), ['size' => '200']).'">
<div>
<h6>Written by</h6>
<h6 class="author-name">'.get_the_author().'</h6>
</div>
</div>';
}
function my_dynamic_blocks() {
wp_register_script(
'my-blocks-script',
plugins_url( 'my-block.js', __FILE__ ),
array( 'wp-blocks', 'wp-element' ));
register_block_type( 'my-blocks/author-block', array(
'editor_script' => 'my-blocks-script',
'render_callback' => 'my_author_block_dynamic_render'));
}
add_action( 'init', 'my_dynamic_blocks' );
I couldn't find a good tutorial on creating a custom author block, so hopefully this example helps some poor soul down the road!

How to apply show less and show more on cells of a reactive table in meteor

document_table_Settings : function ()
{
return{
rowsPerPage: 5,
showNavigation: 'auto',
showColumnToggles: false,
fields: [
{key: 'para',label: 'Para',sortable: false},
{key: 'desc', label: 'Description',sortable: false},
{
key: 'rowId', label: 'Delete',sortable: false, fn: function (rowId, object) {
var html = "<button name='Del' id=" + rowId + " class='btn btn-danger'>Delete</button>"
return new Spacebars.SafeString(html);
}
},
{
key: 'rowId', label: 'Edit',sortable: false, fn: function (rowId, object) {
var html = "<button name='edit' id=" + rowId + " class='btn btn-warning'>Edit</button>"
return new Spacebars.SafeString(html);
}
}
]
};
}
I want to show description entries having show more and show less feature .As the description is long enough. so after 100 character it shows button to toggle.
If I understand you correctly, you are trying to only show the first 100 characters of the 'Description' column in the Reactive Table and then add some mechanism so that the user can click or rollover to see the entire 'Description' text.
There are a few ways to achieve this and I have provided two options below (in order of simplicity).
For a low tech rollover option, truncate the text to only show the first 100 characters, add an ellipsis (...) to the end of your text, then use the title property in a span element to show the full text on rollover.
First you will need to define a 'truncate' Template helper (I would make this a global helper so that you can use anywhere in your app).
Template.registerHelper('truncate', function(strValue, length) {
var len = DEFAULT_TRUNCATE_LENGTH;
var truncatedString = strValue;
if (length && length instanceof Number) {
len = length;
}
if (strValue.length > len) {
truncatedString = strValue.substr(1, len) + "...";
}
return truncatedString;
});
Then create a new Template for the column.
<template name="field_description">
<span title="{{data.description}}">{{truncate data.description}}</span>
</template>
And finally, change your Reactive Table configuration to use a Template.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
For a slightly more complicated option, you can take a similar approach but add a clickable link that would show more or less detail. To get it to work you have to define a few Reactive Vars, define an event handler, and change your 'Description' Template accordingly. Here is a rough solution that should work.
Change your template like so.
<template name="field_description">
<span>{{truncatedDescription}}
{{#if showLink}}
{{linkState}}
{{/if}}
</span>
</template>
Then add the necessary logic to your field_description template (including an event handler).
import { Template } from 'meteor/templating';
import './field-description.html';
Template.field_descirption.onCreated(function() {
const MAX_LENGTH = 100;
this.description = new ReactiveVar(Template.currentData().description);
this.showMore = new ReactiveVar(true);
if (this.description.get().length > MAX_LENGTH) {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
}
this.showLink = () => {
return Template.currentData().description.length > MAX_LENGTH;
};
this.toggleTruncate = () => {
if (this.showMore.get()) {
this.description.set(Template.currentData().description);
this.showMore.set(false);
} else {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
this.showMore.set(true);
}
};
});
Template.field_descirption.helpers({
truncatedDescription: function() {
return Template.instance().description.get();
},
showLink: function() {
return Template.instance().showLink();
},
linkState: function() {
if (Template.instance().showMore.get()) {
return 'show more';
} else {
return 'show less';
}
};
});
Template.field_descirption.events({
'click .js-more-less'(event, instance) {
instance.toggleTruncate();
},
});
Lastly, make sure your Reactive Table config is still setup to use a Template for the field.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
Note that the second option makes use of Meteor's Reactivity to solve the problem. Let me know if you need additional explanation on how the 2nd solution works.
That should do it!

Add Custom Button in User JavaScript for MediaWiki

I want to create a button like the one that inserts your signature. How to do this?
After doing some research I figured out that I can insert custom buttons with the User:MYUSERNAME/common.js page.
I saw several examples. But the wiki references and informations are often splittet across multiple pages and out dated. So I try here if I am lucky and find someone who tried similar things.
Who can help me with this:
var customizeToolbar = function() {
$('#wpTextbox1').wikiEditor('addToToolbar', {
section: 'advanced',
group: 'format',
tools: {
"comment": {
label: 'Comment',
type: 'button',
icon: '//upload.wikimedia.org/wikipedia/commons/3/37/Btn_toolbar_commentaire.png',
action: {
type: 'encapsulate',
options: {
pre: "<!-- ",
post: " -->"
}
}
}
}
});
};
When I do it like this, nothing happens because customizeTooblar most likely gets never called. When I remove it, it says that wikiEditor is not defined.
I already enabled $wgAllowUserJs = true; in LocalSettings.php.
I saw this question: Creating custom edit buttons for MediaWiki Is this still the way we should do this kind of things? This is possibly not a dublicate question because I am already asking about my particular issue here.
The problem was that the initiation code was missing.
This code should directly add a smiley label to your advanced toolbar:
var customizeToolbar = function() {
/* Your code goes here */
$( '#wpTextbox1' ).wikiEditor( 'addToToolbar', {
'section': 'advanced',
'group': 'insert',
'tools': {
'SimpleComment': {
label: 'Comment',
type: 'button',
icon: '//upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Gnome-face-smile.svg/22px-Gnome-face-smile.svg.png',
action: {
type: 'encapsulate',
options: {
pre: "preText",
post: "postText"
}
}
}
}
} );
};
/* Check if view is in edit mode and that the required modules are available. Then, customize the toolbar … */
if ( $.inArray( mw.config.get( 'wgAction' ), [ 'edit', 'submit' ] ) !== -1 ) {
mw.loader.using( 'user.options', function () {
// This can be the string "0" if the user disabled the preference ([[phab:T54542#555387]])
if ( mw.user.options.get( 'usebetatoolbar' ) == 1 ) {
$.when(
mw.loader.using( 'ext.wikiEditor.toolbar' ), $.ready
).then( customizeToolbar );
}
} );
}
// Add the customizations to LiquidThreads' edit toolbar, if available
mw.hook( 'ext.lqt.textareaCreated' ).add( customizeToolbar );
Can be added to wiki/User:YOUR_USRNAME/common.js
In LocalSettings.php this option must be enabled $wgAllowUserJs = true;

Get selected row in Gridview yii2 selectioncolumn undefined error

i am getting error while selecting the rows and sending keys to controller.
In firebug on the button click event it displays error something like this
Uncaught TypeError: Cannot read property 'selectionColumn' of undefined
Here is my view code. its simple gridview
<?= Button::widget([
'label' => 'Message',
'options' => ['class' => 'btn-danger','id' => 'message'],
]);
?>
</p>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'showOnEmpty'=>true,
'columns' => [
['class' => 'yii\grid\CheckboxColumn'],
[
'attribute' => 'event_id',
'label' => 'Event Title',
'value' => 'event.title'
],
[
'attribute' => 'fullName',
'label' => 'Name',
'value' => 'users.fullname',
],
],
]);
?>
And here is the script i am using for onclick event
$script = '
jQuery(document).ready(function() {
btnCheck = $("#message");
btnCheck.click(function() {
var keys = $("#w1").yiiGridView("getSelectedRows");
alert(keys);
$.ajax({
type: "POST",
url: "'.\yii\helpers\Url::to(['/checkin/message']).'",
dataType: "json",
data: {keylist: keys}
});
});
});';
$this->registerJs($script, \yii\web\View::POS_END);
Which doesnt seems to work somehow on the button onclick event only instead of that if i use this script it works fine
$this->registerJs('
$(document).ready(function(){
$("#w1 input[type=checkbox]").click(function(){
var keys = $("#w1").yiiGridView("getSelectedRows");
$.ajax({
type:"POST",
url: "../message", // your controller action
dataType: "json",
data: {keylist: keys},
success: alert(keys)
});
});
});
Which is almost same but in the second script the ajax request is sent every time i click on the checkbox.
I want users to select the rows first and do action on the button click event
Hope you understand
I know its very common error but i tried different options and none of it seems to work
thank you.......
When you see an error message, the first thing you should check the line it is occurring at. There you will find the most important information. In our case, this is the line of code where the problem was:
var keys=$("#w1").yiiGridView("getSelectedRows");
Here, your code searched for an element having the id of w1 and called yiiGridView for that. Somewhere inside the function a selectioncolumn was referred, but the element whose member with that name was referred was not initialized properly.

How to customize default data-confirm dialog box in Yii2 Gridview

I want to change the browser's default confirm dialog (data-confirm) box which comes on click of delete button.
I want to replace this with custom dialog box.
Following is my Gridview Code:
<?=
GridView::widget([
'dataProvider' => $dataProvider,
//'filterModel' => $searchModel,
'columns' => [
//['class' => 'yii\grid\SerialColumn'],
'account',
'code',
[
'class' => 'yii\grid\ActionColumn',
'header' => 'Action',
'template' => ' {update} {delete}',
'buttons' => [
'update' => function ($url, $model) {
return Html::a('<span class="btn-xs btn-primary">Edit</span>', $url, [
'title' => Yii::t('app', 'Edit'),
]);
},
'delete' => function ($url, $model) {
return Html::a('<span class="btn-xs btn-danger">Delete</span>', $url, [
'title' => Yii::t('app', 'Delete'),
//'data-confirm'=>'Are you sure you want to delete this item?',
'data-method'=>'POST'
]);
}
]
],
],
]);
?>
My JQuery code:
confirm: function (message, title, okAction) {
$("<div></div>").dialog({
// Remove the closing 'X' from the dialog
open: function (event, ui) {
$(".ui-dialog-titlebar-close").hide();
},
buttons: {
"Ok": function () {
$(this).dialog("ok");
okAction();
},
"Cancel": function () {
$(this).dialog("close");
}
},
close: function (event, ui) {
$(this).remove();
},
resizable: false,
title: title,
modal: true
}).text(message);
}
});
$(document).ready(function () {
$(".delete-row").click(function () {
$.confirm("Are you sure you want to delete this item?", "Production Control WF");
});
});
However the confirm dialog box appearing after implementation of this code but simultaneously its redirecting as well without clicking on any button.
Any help would be appreciated.
My answer contains two parts:
Replacing the default confirm-dialog
Using SweetAlert as the replacement
In the first part I explain the procedure to replace the default confirmation-dialog. This is the official and proper way to replace the Yii2-confirm-dialog globally.
In the second (optional) part I show how to use SweetAlert in Yii2 to replace the dialog.
Replacing the default confirm-dialog
Its pretty easy actually since the yii.js was overhauled since the launch of Yii2. You have to create a JS-file which you deploy to your web folder. This is done as follows:
1. Create folder for JS-File
In your web folder create a folder named js (or whatever name you wish)
2. Create the actual JS-File
In the folder from step 1 create a JS-file named for example yii_overrides.js
In this file you override the yii.confirm variable with your own handler-method.
My yii_overrides.js looks like this:
/**
* Override the default yii confirm dialog. This function is
* called by yii when a confirmation is requested.
*
* #param string message the message to display
* #param string ok callback triggered when confirmation is true
* #param string cancelCallback callback triggered when cancelled
*/
yii.confirm = function (message, okCallback, cancelCallback) {
swal({
title: message,
type: 'warning',
showCancelButton: true,
closeOnConfirm: true,
allowOutsideClick: true
}, okCallback);
};
The swal function calls the SweetAlert-Projects beautiful alert-box. Feel free to use whatever confirmation-style or -extension you want. SweetAlert looks awesome though...
3. Add JS-File to your Yii2-asset-bundle
Open assets\AppAsset.php and add your JS-File to assure it will be added to your HTML-header. Your file should look something like this now:
class AppAsset extends AssetBundle
{
public $basePath = '#webroot';
public $baseUrl = '#web';
public $css = [
'css/site.css',
];
public $js = [
//HERE!
'js/yii_overrides.js',
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
//additional import of third party alert project
'app\assets\SweetAlertAsset',
];
}
Also make sure to include the asset of your custom alert-library if necessary. You can see this on the last line of the $depends-variable in the code above.
Adding SweetAlert
If you want to use SweetAlert as well, here is how you do it. There is a yii2-extension available providing you with an asset-bundle, but it is not current and uses old filenames. It therefore doesn't work. But its VERY easy to do it yourself.
1. Add dependency to SweetAlert
In your composer.json add
"bower-asset/sweetalert": "1.1.*"
to the required section and trigger composer update. You now have the necessary files.
2. Create SweetAlertAsset
Create a file named SweetAlertAsset.php next to your AppAsset in the assets-folder of your project. This is the content:
<?php
namespace app\assets;
class SweetAlertAsset extends \yii\web\AssetBundle
{
public $sourcePath = '#bower/sweetalert/dist';
public $css = [
'sweetalert.css',
];
public $js = [
'sweetalert.min.js'
];
}
Reference it within AppAsset as seen further above.
3. Done
Easy, wasn't it!?
Based on Sweet Alert 2.0 updates
i've modify the answer by PLM57
SweetAlert has changed callback functions to promises.
Here is the example override code for promise implementation:
/**
* Override the default yii confirm dialog. This function is
* called by yii when a confirmation is requested.
*
* #param string message the message to display
* #param string ok callback triggered when confirmation is true
* #param string cancelCallback callback triggered when cancelled
*/
yii.confirm = function (message, okCallback, cancelCallback) {
swal({
title: message,
icon: 'warning',
buttons: true
}).then((action) => {
if(action){
okCallback()
}
});
}
For more info on update from 1.x to 2.x, refer to this
Short and simple way.
[
'class' => 'yii\grid\ActionColumn',
'template' => '{view} {update} {delete}',
'buttons' => [
'delete' => function ($url, $model, $key) {
return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [
'data-method' => 'post',
'data-pjax' => 0,
'data-confirm' => 'Your message text'
]);
},
]
i think you only need to change $url to #
return Html::a('<span class="btn-xs btn-danger">Delete</span>', "#", [
'title' => Yii::t('app', 'Delete'),
//'data-confirm'=>'Are you sure you want to delete this item?',
'data-method'=>'POST'
]);

Categories