Dynamically add UI Elements in CKEditor Dialog - javascript

I'm trying to trigger a callback to dynamically populate a CKEditor dialog with checkboxes when the dialog is opened. I've read other solutions that use iframes, but this won't work for me because the dialog needs to be populated based on other elements on the same page.
Here is what I have so far. There are no errors, but the dialog is just empty when it opens. I expect the addContents function to fill in the dialog. I've confirmed that dialog.definition.contents does include the contents and elements that I want, but it's just not filling in the actual dialog. What am I missing?
(function() {
CKEDITOR.plugins.add( 'embeds', {
icons: 'embed',
init: function(editor) {
var self = this,
elements = [];
CKEDITOR.dialog.add('EmbedsDialog', function (instance) {
return {
title : 'Embeds',
minWidth : 550,
minHeight : 200,
contents: [],
onShow: function() {
var dialog = this,
elements = [];
$('#embeds-fields tr').each(function() {
var title = $(this).find('input[type=text]').val(),
url = $(this).find('input[type=url]').val();
if(url != "") {
elements.push({
label : "embed",
title : url,
type : 'checkbox'
});
}
});
dialog.definition.removeContents('embeds');
dialog.definition.addContents({
id : 'embeds',
expand : true,
elements : elements
});
},
}; // return
});
editor.addCommand('Embeds',
new CKEDITOR.dialogCommand('EmbedsDialog', {
allowedContent: 'a[*](*)'
})
);
editor.ui.addButton('Embeds', {
label : 'Embeds',
command : 'Embeds',
toolbar : 'embeds'
});
} // init
}); // add
})(); // closure

Based off of this example, I ended up with this solution, where "main" is the ID of the original content.
CKEDITOR.on('dialogDefinition', function(ev) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if (dialogName == 'EmbedsDialog') {
var main = dialogDefinition.getContents('main');
$('#embeds-fields tr').each(function() {
var title = $(this).find('input[type=text]').val(),
url = $(this).find('input[type=url]').val();
if(url != "") {
main.add({
type : 'checkbox',
label : title,
});
}
});
}
});

Related

Same function in function, preserve variables/elements

I'm creating a javascript function which creates a modal. Here's the function:
function createModal(options) {
var self = this;
modalHeaderText = options.header;
modalBodyText = options.body;
$modal = $('<div />').addClass('modal').appendTo('body');
$modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
$modalContainer = $('<div />').addClass('modal-container').appendTo($modal);
$modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
$modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
if (options.buttons) {
$modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
$.each(options.buttons, function(name, buttonOptions) {
$modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
if(buttonOptions.callback) {
$modalButton.on('click', function() {
buttonOptions.callback();
});
} else {
$modalButton.on('click', function(e) {
$modal.remove();
});
};
});
};
$modal.addClass('active');
if (options.closeOnOverlayClick == true) {
$modalOverlay.on('click', function(e) {
$modal.remove();
});
};
};
This works fine, but I want to be able to call the function within the same function, like this:
$('#modal').on('click', function(e){
e.preventDefault();
createModal({
header : 'Enter your name',
body : '<input type="text" class="name" />',
buttons : {
'OK' : {
class : 'btn btn-success',
callback : function() {
var name = self.$modalBody.find('.name').val();
if (!name) {
createModal({
header : 'Error',
body : 'You must provide a name',
buttons : {
'OK' : {
class : 'btn'
}
}
});
} else {
alert(name);
};
},
},
'Close' : {
class : 'btn btn-error'
}
}
});
});
What I want is the following: when someone clicks the button with ID "modal" (hence "#modal"), a modal is opened with a input. When the OK-button is pressed, it checks if the input ('name') has a value. If so, the value is shown in an alert. If not, a new modal is openend (over the current modal) with the text 'You must provide a name'.
If I enter a name, it works. The name is shown in an alert, and also the close button works. But if I do not enter a name, and the second modal is shown, all the variables in the function are overwritten.
How can I preserve the variables/elements from the first modal so that, after the second modal is shown (and cleared), the buttons from the first modal still work.
I've created a JSFiddle here: https://jsfiddle.net/6pq7ce0a/2/
You can test it like this:
1) click on 'open modal'
2) enter a name
3) click on 'ok'
4) the name is shown in an alert
==> this works
The problem is here:
1) click on 'open modal'
2) do NOT enter a name
3) click on 'ok'
4) a new modal is shown
5) click on 'ok' in the new (error) modal
6) the buttons from the first modal (with the input field) don't work anymore
Thanks in advance!
Update
If I change the function to the function below, the first modal does not work at all.
function createModal(options) {
var self = this;
var modalHeaderText = options.header;
var modalBodyText = options.body;
var $modal = $('<div />').addClass('modal').appendTo('body');
var $modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
var $modalContainer = $('<div />').addClass('modal-container').appendTo($modal);
var $modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
var $modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
if (options.buttons) {
var $modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
$.each(options.buttons, function(name, buttonOptions) {
var $modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
if(buttonOptions.callback) {
$modalButton.on('click', function() {
buttonOptions.callback();
});
} else {
$modalButton.on('click', function(e) {
$modal.remove();
});
};
});
};
$modal.addClass('active');
if (options.closeOnOverlayClick == true) {
$modalOverlay.on('click', function(e) {
$modal.remove();
});
};
};
The problem is here:
var name = self.$modalBody.find('.name').val();
$modalBody is not defined if I add 'var' to all the elements.
So in addition to the comments above regarding not declaring var you also are storing a reference to window in the self variable. To avoid all of that I went down the road in this fiddle: https://jsfiddle.net/10fanzw6/1/.
Quick explanation.
First don't assign this to self as this is window
Second assign everything to the empty self object as well as a local var (for better readability)
Third pass the self var back to any button callback giving you access to any part of the modal you may need.
For posterity, including the updated function here:
function createModal(options) {
var self = {};
var modalHeaderText = options.header;
var modalBodyText = options.body;
var $modal = self.$modal = $('<div />').addClass('modal').appendTo('body');
var $modalOverlay = self.$modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
var $modalContainer = self.$modalContainer = $('<div />').addClass('modal-container').appendTo(self.$modal);
self.$modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
self.$modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
if (options.buttons) {
var $modalFooter = self.$modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
$.each(options.buttons, function(name, buttonOptions) {
var $modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
if (buttonOptions.callback) {
$modalButton.on('click', function() {
buttonOptions.callback(self);
});
} else {
$modalButton.on('click', function(e) {
$modal.remove();
});
};
});
};
$modal.addClass('active');
if (options.closeOnOverlayClick == true) {
$modalOverlay.on('click', function(e) {
$modal.remove();
});
};
};
$('#modal').on('click', function(e) {
e.preventDefault();
createModal({
header: 'Enter your name',
body: '<input type="text" class="name" />',
buttons: {
'OK': {
class: 'btn btn-success',
callback: function(modal) {
var name = modal.$modalBody.find('.name').val();
if (!name) {
createModal({
header: 'Error',
body: 'You must provide a name',
buttons: {
'OK': {
class: 'btn'
}
}
});
} else {
alert(name);
};
},
},
'Close': {
class: 'btn btn-error'
}
}
});
});
Simply not using var in front of $modal variable causing it to be stored in window scope. When the next next $modal is closed, the variable is referencing to an already removed element, so nothing happens on first modal's Close button click.

Custom wp.media with arguments support

How to setup a [add media] button, with:
proper wordpress [media] UI
has size and alignments UI in popup right hand side
can custom popup title and button
size and alignments arguments can send back to be use
Just try to cover most solutions:
use tb_show("", "media-upload.php?type=image&TB_iframe=true"); and window.send_to_editor
problem: has no standard wp.media UI
in js code:
jQuery("#my_button").click(function() {
tb_show("", "media-upload.php?type=image&TB_iframe=true");
return false;
});
window.send_to_editor = function(html) {
console.log(html);
tb_remove();
}
use wp.media({frame: 'post'})
problem: cannot custom UI elements, such as: title, button
in js code:
function clearField(){
#remove file nodes
#...
}
var frame = wp.media({frame: 'post'});
frame.on('close',function() {
var selection = frame.state().get('selection');
if(!selection.length){
clearField();
}
});
frame.on( 'select',function() {
var state = frame.state();
var selection = state.get('selection');
if ( ! selection ) return;
clearField();
selection.each(function(attachment) {
console.log(attachment.attributes);
});
});
frame.open();
use wp.media.editor with wp.media.editor.open( editor_id )
problem: cannot custom UI elements, such as: title, button
in js code: https://wordpress.stackexchange.com/questions/75808/using-wordpress-3-5-media-uploader-in-meta-box#75823
use wp.media with rewrite wp.media.controller.Library and retrieve attachment in select
problem: complicated ..., but once you understand it, it all make sense, and it is my finial solution
in js code:
/**
* Please attach all the code below to a button click event
**/
//create a new Library, base on defaults
//you can put your attributes in
var insertImage = wp.media.controller.Library.extend({
defaults : _.defaults({
id: 'insert-image',
title: 'Insert Image Url',
allowLocalEdits: true,
displaySettings: true,
displayUserSettings: true,
multiple : true,
type : 'image'//audio, video, application/pdf, ... etc
}, wp.media.controller.Library.prototype.defaults )
});
//Setup media frame
var frame = wp.media({
button : { text : 'Select' },
state : 'insert-image',
states : [
new insertImage()
]
});
//on close, if there is no select files, remove all the files already selected in your main frame
frame.on('close',function() {
var selection = frame.state('insert-image').get('selection');
if(!selection.length){
#remove file nodes
#such as: jq("#my_file_group_field").children('div.image_group_row').remove();
#...
}
});
frame.on( 'select',function() {
var state = frame.state('insert-image');
var selection = state.get('selection');
var imageArray = [];
if ( ! selection ) return;
#remove file nodes
#such as: jq("#my_file_group_field").children('div.image_group_row').remove();
#...
//to get right side attachment UI info, such as: size and alignments
//org code from /wp-includes/js/media-editor.js, arround `line 603 -- send: { ... attachment: function( props, attachment ) { ... `
selection.each(function(attachment) {
var display = state.display( attachment ).toJSON();
var obj_attachment = attachment.toJSON()
var caption = obj_attachment.caption, options, html;
// If captions are disabled, clear the caption.
if ( ! wp.media.view.settings.captions )
delete obj_attachment.caption;
display = wp.media.string.props( display, obj_attachment );
options = {
id: obj_attachment.id,
post_content: obj_attachment.description,
post_excerpt: caption
};
if ( display.linkUrl )
options.url = display.linkUrl;
if ( 'image' === obj_attachment.type ) {
html = wp.media.string.image( display );
_.each({
align: 'align',
size: 'image-size',
alt: 'image_alt'
}, function( option, prop ) {
if ( display[ prop ] )
options[ option ] = display[ prop ];
});
} else if ( 'video' === obj_attachment.type ) {
html = wp.media.string.video( display, obj_attachment );
} else if ( 'audio' === obj_attachment.type ) {
html = wp.media.string.audio( display, obj_attachment );
} else {
html = wp.media.string.link( display );
options.post_title = display.title;
}
//attach info to attachment.attributes object
attachment.attributes['nonce'] = wp.media.view.settings.nonce.sendToEditor;
attachment.attributes['attachment'] = options;
attachment.attributes['html'] = html;
attachment.attributes['post_id'] = wp.media.view.settings.post.id;
//do what ever you like to use it
console.log(attachment.attributes);
console.log(attachment.attributes['attachment']);
console.log(attachment.attributes['html']);
});
});
//reset selection in popup, when open the popup
frame.on('open',function() {
var selection = frame.state('insert-image').get('selection');
//remove all the selection first
selection.each(function(image) {
var attachment = wp.media.attachment( image.attributes.id );
attachment.fetch();
selection.remove( attachment ? [ attachment ] : [] );
});
//add back current selection, in here let us assume you attach all the [id] to <div id="my_file_group_field">...<input type="hidden" id="file_1" .../>...<input type="hidden" id="file_2" .../>
jq("#my_file_group_field").find('input[type="hidden"]').each(function(){
var input_id = jq(this);
if( input_id.val() ){
attachment = wp.media.attachment( input_id.val() );
attachment.fetch();
selection.add( attachment ? [ attachment ] : [] );
}
});
});
//now open the popup
frame.open();
I would like to add to ZAC's option 4 because when I used his code, the image src="" was missing.
Here is the fix
if ( 'image' === obj_attachment.type ) {
html = wp.media.string.image( display );
_.each({
align: 'align',
size: 'image-size',
alt: 'image_alt'
}, function( option, prop ) {
if ( display[ prop ] )
options[ option ] = display[ prop ];
});
html = wp.media.string.image( display, obj_attachment );
}
This way you can call the new media uploader with custom title and button and right side bar.
var custom_uploader;
jQuery('#fileform').on('click','.select-files', function(e) {
var button = jQuery(this);
custom_uploader = wp.media.frames.file_frame = wp.media({
title: 'Choose File',
library: {
author: userSettings.uid // specific user-posted attachment
},
button: {
text: 'Choose File'
},
multiple: false
});
//When a file is selected, grab the URL and set it as the text field's value
custom_uploader.on('select', function() {
attachment = custom_uploader.state().get('selection').first().toJSON();
console.log(attachment.url);
console.log(attachment.id); // use them the way you want
});
//Open the uploader dialog
// Set post id
wp.media.model.settings.post.id = jQuery('#post_ID').val();
custom_uploader.open();
});
Check this link -> https://github.com/phpcodingmaster/WordPress-Media-Modal-Image-Uploads
It will show you how to:
Open the admin media modal
Get single image info
Get multiple images info
Tested with WordPress Version 6.0

Cannot select a dynamically added list item until it is clicked

I have written a small JQuery plugin that creates a dropdown box based on bootstrap. I have written it to where a data attribute supplies a url that produces the list items. After the ajax call, Jquery loops through the list items and inserts them into the dropdown menu. Here is what I do not understand, the plugin takes a div with the class of .combobox and appends the required html to make the combobox. It uses two functions, _create() and _listItems(). _create() actually adds the html and calls on _listItems() to make the ajax call and it returns the list items to be appended. Looks like this:
;(function ( $, window, document, undefined ) {
var Combobox = function(element,options) {
this.$element = $(element);
this.$options = $.extend({}, $.fn.combobox.defaults, options);
this.$html = {
input: $('<input type="text" placeholder="[SELECT]" />').addClass('form-control'),
button: $('<div id="test"/>').addClass('input-group-btn')
.append($('<button />')
.addClass('btn btn-default input-sm')
.append('<span class="caret"></span>'))
}
this.$list_type = this.$element.attr('data-type');
this.$url = this.$element.attr('data-url');
this.$defaultValue = this.$element.attr('data-default');
this._create();
this.$input = this.$element.find('input');
this.$button = this.$element.find('button');
this.$list = this.$element.find('ul')
this.$button.on('click',$.proxy(this._toggleList,this));
this.$element.on('click','li',$.proxy(this._itemClicked,this));
this.$element.on('mouseleave',$.proxy(this._toggleList,this));
if(this.$defaultValue) {
this.selectByValue(this.$defaultValue);
}
}
Combobox.prototype = {
constructor: Combobox,
_create: function() {
this.$element.addClass('input-group input-group-sm')
.append(this.$html.input)
.append(this._listItems())
.append(this.$html.button);
},
_itemClicked: function(e){
this.$selectedItem = $(e.target).parent();
this.$input.val(this.$selectedItem.text());
console.log(this.$element.find('[data-value="W"]'))
this._toggleList(e);
e.preventDefault();
},
_listItems: function() {
var list = $('<ul />').addClass('dropdown-menu');
$.ajax({
url: this.$url,
type: 'POST',
data: {opt: this.$list_type},
success:function(data){
$.each(data,function(key,text){
list.append($('<li class="listObjItem" data-value="'+text.id+'">'+text.value+'</li>'));
})
}
})
return list
},
selectedItem: function() {
var item = this.$selectedItem;
var data = {};
if (item) {
var txt = this.$selectedItem.text();
data = $.extend({ text: txt }, this.$selectedItem.data());
}
else {
data = { text: this.$input.val()};
}
return data;
},
selectByValue: function(value) {
var selector = '[data-value="'+value+'"]';
this.selectBySelector(selector);
},
selectBySelector: function (selector) {
var $item = this.$element.find(selector);
if (typeof $item[0] !== 'undefined') {
this.$selectedItem = $item;
this.$input.val(this.$selectedItem.text());
}
else {
this.$selectedItem = null;
}
},
enable: function () {
this.$input.removeAttr('disabled');
this.$button.children().removeClass('disabled');
this.$button.on('click',$.proxy(this._toggleList,this));
},
disable: function () {
this.$input.attr('disabled', true);
this.$button.children().addClass('disabled');
this.$button.off('click',$.proxy(this._toggleList,this));
},
_toggleList: function(e) {
if(e.type == 'mouseleave') {
if(this.$list.is(':hidden')) {
return false;
} else {
this.$list.hide();
}
} else {
this.$list.toggle();
e.preventDefault();
}
}
}
$.fn.combobox = function (option) {
return this.each(function () {
if (!$.data(this, 'combobox')) {
$.data(this, 'combobox',
new Combobox( this, option ));
}
});
};
$.fn.combobox.defaults = {};
$.fn.combobox.Constructor = Combobox;
})( jQuery, window, document );
The problem is that after the items are appended to the DOM, everything is selectable accept the list items. I currently have an .on() statement that binds the click event with the list item. To test this out I have used console.log(this.$element.find('[data-value="W"]') and it does not return an element, however if I place that same console log in the click callback of the list item it will return the element and it is selectable. Am I doing something wrong?
EDIT
I have pasted the entire plugin to save on confusion.

Insert iframe in div from onOk of CKEDITOR.dialog

I use CKEditor and I have got problem for insert iframe in div element in my editor when the user click on the OK button in my Dialog. This does not work. When the user clicks on the button does nothing happen (I have no error message). So he should shut my popup and insert a div containing my iframe inside my editor
Can you help me ?
this is my code
:
CKEDITOR.dialog.add( 'postVideoDialog', function( editor ) {
return {
title : 'Add Video',
minWidth : 400,
minHeight : 80,
contents :
[
{
id : 'video',
label : 'Add Video',
elements :
[
{
type : 'text',
id : 'url',
label : 'Enter a URL from Vimeo :',
validate : function()
{
var url = this.getValue();
var regex1=/^(http:\/\/)vimeo.com\/[0-9]{3,}$/g;
var regex2=/^(http:\/\/)player.vimeo.com\/video\/[0-9]{3,}$/g;
if(regex1.test(url) || regex2.test(url)){
return true
}else{
alert("Url incorrect");
return false;
}
},
required : true,
commit : function( data )
{
data.url = this.getValue();
}
},
]
}
],
onOk : function()
{
var dialog = this,
data = {},
iframe = editor.document.createElement( 'iframe' ),
div = editor.document.createElement('div');
this.commitContent( data );
var regex=/^(http:\/\/)vimeo.com\/[0-9]{3,}$/g; //http://vimeo.com/25329849
if(regex.test(data.url)){
var idVideo = data.url.match(/[0-9]{3,}$/g);
data.url = "http://player.vimeo.com/video/" + idVideo;
}
div.setAttribute('class', 'video');
iframe.setAttribute( 'src', data.url + "?byline=0&portrait=0&color=ffffff");
iframe.setAttribute( 'width', '620' );
iframe.setAttribute( 'width', '349' );
iframe.setAttribute( 'frameborder', '0');
div.insertElement(iframe); //problem is here !
editor.insertElement(div);
}
}; });
Found it..
Read the documentation please: docs.ckeditor.com/#!/api/CKEDITOR.dom.element
Elements don't have a insertElement method. This is a method of the editor try this:
iframe.appendTo(div); //problem is solved here!
editor.insertElement(div);
Instead of your previous code:
div.insertElement(iframe); //problem is here !
editor.insertElement(div);

How to remove MenuItemTitle from TinyMCE?

I have enabled the 'fontsizeselect' plugin in tinyMCE. My question is how do I remove the header (title) of the drop-down menu?
Edit:
I've tried removing it using JQuery .remove(), but after that the height of whole list is calculated wrong.
The second option I tried was:
http://www.tinymce.com/wiki.php/API3:method.tinymce.ui.DropMenu.remove
But that just went wrong and "fontsizeselect.remove(title)" (analogically to .add) makes error to whole tinyMCE - "missing : after property id". Problably it is completly bad method to do this.
The third option was editing tiny_mce\themes\advanced\editor_template_src.js line 467:
c = ed.controlManager.createListBox('fontsizeselect', {title : 'advanced.font_size', onselect : function(v) {...}
but seems, that TinyMCE developers thought, that every drop-down must have title/header
SOLVED:
before initialization of MCE we have to override the menu rendering function
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
tinymce.create('tinymce.ui.ListBoxNoTitle:tinymce.ui.ListBox', {
renderMenu : function() {
var t = this, m;
m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
menu_line : 1,
'class' : t.classPrefix + 'Menu mceNoIcons',
max_width : 250,
max_height : 150
});
m.onHideMenu.add(function() {
t.hideMenu();
t.focus();
});
/* m.add({
title : t.settings.title,
'class' : 'mceMenuItemTitle',
onclick : function() {
if (t.settings.onselect('') !== false)
t.select(''); // Must be runned after
}
});
*/
each(t.items, function(o) {
// No value then treat it as a title
if (o.value === undef) {
m.add({
title : o.title,
role : "option",
'class' : 'mceMenuItemTitle',
onclick : function() {
if (t.settings.onselect('') !== false)
t.select(''); // Must be runned after
}
});
} else {
o.id = DOM.uniqueId();
o.role= "option";
o.onclick = function() {
if (t.settings.onselect(o.value) !== false)
t.select(o.value); // Must be runned after
};
m.add(o);
}
});
t.onRenderMenu.dispatch(t, m);
t.menu = m;
}
});
})(tinymce);
And with this comment on "m.add" You just have to add
tinyMCE.init({
setup : function(ed) {
ed.onBeforeRenderUI.add(function(ed){
ed.controlManager.setControlType('listbox', tinymce.ui.ListBoxNoTitle);
});
}
});
this setup to standard initialization of tinyMCE. So, it can be done without editing source files.

Categories

Resources