HTML in createNotice with Gutenberg shows [object Object] - wordpress

I’m playing around with creating a custom notice in Gutenberg, and based on whether the data validates when the post is saved, the notice may include some HTML links.
I ended up only seeing the raw HTML output, so after some searching on Google I found this post on Github suggesting to use RawHTML.
So I put this code together which does create the red notice, but it doesn’t show the actual HTML only [object Object]. So I’m clearly doing something wrong here, but not sure what? Anyone who can point me in the right direction how to make the notices show the raw HTML?
wp.data.dispatch( 'core/notices' ).createNotice(
'error',
wp.element.createElement( wp.element.RawHTML, null, '<p>test</p>' ),
{
id: 'wpslupdate', // prevent duplicates
isDismissible: true
}
);

The notice component has an actions property which enables links to be rendered inside a notice, removing the need to use RawHTML.
options.actions [Array]: User actions to be presented with notice.
A notice can have one or more actions, each action has a label and must specify either a url or onClick function, eg:
wp.data.dispatch('core/notices').createNotice(
'error', // type of notice
'Something went wrong..', // message
{
id: 'wpslupdate', // prevent duplicates
isDismissible: true,
actions: [
{
label: 'Option A',
url: '#link' // styled as plain link
},
{
label: 'Option B',
onClick: () => { alert('ok') } // styled as a button link
}
]
}
);
Result:

Related

CKEditor5 Link Plugin overrides custom link plugin after saving page in Drupal

I created a custom CKEditor5 plugin that makes use of the "a" attribute similar to the default link plugin provided by CKEditor5. All the expected functionality that I created so far for this custom plugin which simply styles the link as a button works. However upon saving the page in the CMS (Drupal) and returning to the edit screen the model name for the button changes to "linkHref" which is the model name for the Link plugin. I checked before saving that the model name that I set up "button" was being recognized when using the Inspector. It kept the model name even when I toggled the "source" button. See schema and conversion code below.
_defineSchema() {
const schema = this.editor.model.schema;
schema.extend('$text', {allowAttributes: ['button']});
}
_defineConverters() {
const {conversion} = this.editor;
conversion. For('downcast').attributeToElement({
model: 'button',
view: (modelAttributeValue, conversionApi) => {
const {writer} = conversionApi;
return writer.createAttributeElement(
'a',
{
href: modelAttributeValue,
class: 'btn btn-geneseo btn-primary'
},
{priority: 6} // I added this because the Link plugin had theirs at 5
);
},
converterPriority: 'high' // Tried this made no difference
});
conversion.for('upcast').elementToAttribute({
view: {
name: 'a',
attributes: ['href'],
classes: 'btn btn-geneseo btn-primary'
},
model: {
key: 'button',
value: viewElement => viewElement.getAttribute('href'),
},
converterPriority: 'high' // According to documentation this should be given priority
});
}
This is the result the first time the button is created. Noticed that it is recognized as a button model.
After saving the page and going back to the edit screen this is the result.
Why would this happen? What would I need to change?

Open/Access WP Media library from tinymce plugin popup window

I'm building a tinymce button plugin for the Wordpress (4) editor. The popup window that my button opens displays a form with several fields. One of them is for selecting an image inside the WP media library. I can't figure how to achieve this.
If that's not possible, what would be the best way to allow the user to select an image stored in the WP media library from a tinymce plugin popup window ?
FYI, the tinymce plugin inserts a shortcode with an image src as an attribute.
thanks !
I had the same problem just now and found the solution so I'm sharing it here. I hope it's not too late.
First to be able to use WP Add Media button you would have to enqueue the needed script. This is easy, just call the wp_enqueue_media() function like so:
add_action('admin_enqueue_scripts', 'enqueue_scripts_styles_admin');
function enqueue_scripts_styles_admin(){
wp_enqueue_media();
}
This call ensures you have the needed libraries to use the WP Media button.
Of course you should also have the HTML elements to hold the uploaded/selected media file URL, something like this:
<input type="text" class="selected_image" />
<input type="button" class="upload_image_button" value="Upload Image">
The first text field will hold the URL of the media file while the second is a button to open the media popup window itself.
Then in your jscript, you'd have something like this:
var custom_uploader;
$('.upload_image_button').click(function(e) {
e.preventDefault();
var $upload_button = $(this);
//Extend the wp.media object
custom_uploader = wp.media.frames.file_frame = wp.media({
title: 'Choose Image',
button: {
text: 'Choose Image'
},
multiple: false
});
//When a file is selected, grab the URL and set it as the text field's value
custom_uploader.on('select', function() {
var attachment = custom_uploader.state().get('selection').first().toJSON();
$upload_button.siblings('input[type="text"]').val(attachment.url);
});
//Open the uploader dialog
custom_uploader.open();
});
Now I'm not going to explain every line because it's not that hard to understand. The most important part is the one that uses the wp object to make all these to work.
The tricky part is making all these work on a TinyMCE popup(which is the problem I faced). I've searched hi and lo for the solution and here's what worked for me. But before that, I'll talk about what problem I encountered first. When I first tried to implement this, I encountered the "WP is undefined" problem on the popup itself. To solve this, you just have to pass the WP object to the script like so:
(function() {
tinymce.create('tinymce.plugins.someplugin', {
init : function(ed, url) {
// Register commands
ed.addCommand('mcebutton', function() {
ed.windowManager.open(
{
file : url + '/editor_button.php', // file that contains HTML for our modal window
width : 800 + parseInt(ed.getLang('button.delta_width', 0)), // size of our window
height : 600 + parseInt(ed.getLang('button.delta_height', 0)), // size of our window
inline : 1
},
{
plugin_url : url,
wp: wp
}
);
});
// Register buttons
ed.addButton('someplugin_button', {title : 'Insert Seomthing', cmd : 'mcebutton', image: url + '/images/some_button.gif' });
}
});
// Register plugin
// first parameter is the button ID and must match ID elsewhere
// second parameter must match the first parameter of the tinymce.create() function above
tinymce.PluginManager.add('someplugin_button', tinymce.plugins.someplugin);
})();
What we're interested in is this line => "wp: wp" . This line ensures that we are passing the wp object to the popup window (an iframe really...) that is to be opened when we click the tinymce button. You can actually pass anything to the popup window via this object (the 2nd parameter of the ed.windowManager.open method)!
Last but not the least you'd have to reference that passed wp object on your javascript like so:
var args = top.tinymce.activeEditor.windowManager.getParams();
var wp = args.wp;
Make sure you do that before calling/using the WP object.
That's all you have to do to make this work. It worked for me, I hope it works for you :)
I took the code of Paolo and simplified it in order not to have many files to manage. Also, I didn't manage to make it work like this.
So this solution has less code and uses only one single file.
Just put this in your tinyMCE plugins js file:
(function(){
tinymce.PluginManager.add('myCustomButtons', function(editor, url){
editor.addButton('btnMedia', {
icon: 'image',
tooltip: 'Add an image',
onclick: function() {
editor.windowManager.open({
title: 'Add an image',
body: [{
type: 'textbox',
subtype: 'hidden',
name: 'id',
id: 'hiddenID'
},
{
type: 'textbox',
name: 'text',
label: 'Text',
id: 'imageText'
},
{
type: 'button',
text: 'Choose an image',
onclick: function(e){
e.preventDefault();
var hidden = jQuery('#hiddenID');
var texte = jQuery('#imageText');
var custom_uploader = wp.media.frames.file_frame = wp.media({
title: 'Choose an image',
button: {text: 'Add an image'},
multiple: false
});
custom_uploader.on('select', function() {
var attachment = custom_uploader.state().get('selection').first().toJSON();
hidden.val(attachment.id);
if(!texte.val()){
if(attachment.alt)
texte.val(attachment.alt);
else if(attachment.title)
texte.val(attachment.title);
else
texte.val('See the image');
}
});
custom_uploader.open();
}
}],
onsubmit: function(e){
var image = '<button data-id="'+e.data.id+'">'+e.data.text+'</button>';
editor.insertContent(image);
}
});
}
});
});
})();
The result in the frontend html is a button which has the ID of the image in a data-id attribute, and a text to display (the alt of the image, by default, or its title or a text the user can write).
Then, with my frontend js, I will get the corresponding image with its ID and show it in an ajax popup.
With this solution, you have all of your js functions in one single file, and you don't need to enqueue any script nor to create a php file.
I know it's old but in case anyone else facing the same situation, The Paolo's solution above is working fine but no need to enqueue wp_enqueue_media(); this will load a bunch of scripts, you can load only 2 scripts:
wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'media-lib-uploader-js' );

Bootboxjs: how to render a Meteor template as dialog body

I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);

Creating a button in EXTJs

I have a login screen which has an ExtJs form panel with two button Login and Reset.
Now, I'm trying to add another button below the lofin panel for a new user sign up.
But the button goes out to the bottom of the screen and a scroll bar appears making the page look ugly!
I have these in my login.jsp file:
<link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all-access.css">
<script type="text/javascript" src="extjs/ext-all.js"></script>
<script type="text/javascript" src="js/app.js"></script>
And this is my app.js file:
Ext.onReady(function(){
Ext.QuickTips.init();
var login = new Ext.FormPanel({
labelWidth:100,
frame:true,
title:'Member Login',
defaultType:'textfield',
monitorValid:true,
// Specific attributes for the text fields for username / password.
// The "name" attribute defines the name of variables sent to the server.
items:[{
fieldLabel:'Username',
name:'loginUsername',
allowBlank:false
},{
fieldLabel:'Password',
name:'loginPassword',
inputType:'password',
allowBlank:false
}],
buttons: [{
text: 'New User Register',
scale: 'medium',
handler: function()
{
login.getForm().doAction('standardsubmit',{
target : '_self',
method : 'POST',
standardSubmit:true,
formBind: false,
url: 'registration.jsp'
})
}
},{
text: 'Login',
scale: 'medium',
handler: function()
{
login.getForm().doAction('standardsubmit',{
target : '_self',
method : 'POST',
standardSubmit:true,
formBind: true,
url: 'index.jsp'
})
}
},{
text: 'Reset',
scale: 'medium',
handler: function(){
login.getForm().reset();
}
}]
});
// This just creates a window to wrap the login form.
// The login object is passed to the items collection.
var win = new Ext.Window({
layout:'fit',
width:325,
height:175,
closable: false,
resizable: false,
plain: true,
border: false,
items: [login]
});
win.show();
});
I tried adding a new button using the Ext.Createat the end of the above one but it wouldn't work.
I tried having the code in a separate js file and adding the script tag with src to the button file along with the panel file in a separate script tag and even that failed.
Can anybody help me out to create a separate button and my desired position?
Any kind of help is appreciated. Thanks.
I've looked everywhere and I've tried everything I could, but I'm not able to come up with the answer.
NOTE: The code makes the question seem very long and big, but my main query and issue is at the top and bottom of the question.
Im assuming you dont want to add the new button INSIDE the login panel because otherwise you could just add another button to your buttons list anyway. To add a button under your login panel and YET inside the window, do the following steps;
Put your form panel as an item inside a container
Add your button as a second item in the container after your form pannel
Add the container to your window as you did the panel to the window
Voilah! You could also try giving your window and 'id' such as 'id=loginWindow' and then calling the code
Ext.getCmp('loginWindow').add(
{
xtype:button,
text:'Yay!'
}
);
If you need more code specific help, feel free to message me. I know your feel bro <3

How to add a row hyperlink for an extJS Grid?

Can someone please throw some light on how to go about rendering an hyperlink in the cells of a particular column in ExtJS?
I have tried binding the column to a render function in my JS, from which I send back the html:
SELECT
However, with this, the problem is that, once I hit the controller through the link, the navigation is successful, but subsequent navigations to the data-grid show up only empty records.
The records get fetched from the DB successfully through the Spring MVC controller, I have checked.
Please note that this happens only once I use the row hyperlink in the extJS grid to navigate away from the grid. If I come to the grid, and navigate elsewhere and again come back to the grid, the data is displayed fine.
The problem only occurs in case of navigating away from the grid, using the hyperlink rendered in one/any of the cells.
Thanks for your help!
This is for ExtJS 4 and 5.
Use a renderer to make the contents look like a link:
renderer: function (value) {
return ''+value+'';
}
Then use the undocumented, dynamically generated View event cellclick to process the click:
viewConfig: {
listeners: {
cellclick: function (view, cell, cellIndex, record, row, rowIndex, e) {
var linkClicked = (e.target.tagName == 'A');
var clickedDataIndex =
view.panel.headerCt.getHeaderAtIndex(cellIndex).dataIndex;
if (linkClicked && clickedDataIndex == '...') {
alert(record.get('id'));
}
}
}
}
Try something like this:
Ext.define('Names', {
extend: 'Ext.data.Model',
fields: [
{ type: 'string', name: 'Id' },
{ type: 'string', name: 'Link' },
{ type: 'string', name: 'Name' }
]
});
var grid = Ext.create('Ext.grid.Panel', {
store: store,
columns: [
{
text: 'Id',
dataIndex: 'Id'
},
{
text: 'Name',
dataIndex: 'Name',
renderer: function (val, meta, record) {
return '' + val + '';
}
}
...
...
...
However my thanks to - ExtJS Data Grid Column renderer to have multiple values
Instead of using an anchor tag, I would probably use plain cell content styled to look like an anchor (using basic CSS) and handle the cellclick event of the GridPanel to handle the action. This avoids dealing with the anchor's default click behavior reloading the page (which is what I'm assuming is happening).
I created a renderer so it looked like you were clicking on it.
aRenderer: function (val, metaData, record, rowIndex, colIndex, store){
// Using CellClick to invoke
return "<a>View</a>";
},
But I used a cell event to manage the click.
cellclick: {
fn: function (o, idx, column, e) {
if (column == 1) // Doesn't prevent the user from moving the column
{
var store = o.getStore();
var record = store.getAt(idx);
// Do work
}
}
}
For these purposes I use CellActions or RowActions plugin depending on what I actually need and handle cell click through it.
If you want something that looks like an anchor, use <span> instead and do what #bmoeskau suggested.
You can use 'renderer' function to include any HTML you want into cell.
Thanks guys for your response.
AFter debugging the extJS-all.js script, I found the issue to be on the server side.
In my Spring MVC controller, I was setting the model to the session, which in the use-case I mentioned earlier, used to reset the "totalProperty" of Ext.data.XmlStore to 0, and hence subsequent hits to the grid, used to display empty records.
This is because, ext-JS grid, checks the "totalProperty" value, before it even iterates through the records from the dataStore. In my case, the dataStore had data, but the size was reset to null, and hence the issue showed up.
Thanks to all again for your inputs!

Resources