Open danielbachhuber opened 9 years ago
Going to punt this from 0.3.
So it means we could use Shortcake as best possible visual tables editing TinyMce tool ? If it is so it is revolutionary.
Any news on this?
I created a new field attribute fields (I'm using richtext plugin as well but you can remove the "shortcake-richtext" class from the textarea https://wordpress.org/plugins/shortcode-ui-richtext/)
This has a bug, it will not update the last keypress or if you do only image add.
You have to create a class with the code below:
/**
* Primary controller class for Shortcake Columns Field
*/
class Shortcake_Field_Columns {
/**
* Shortcake Columns Field controller instance.
*
* @access private
* @var object
*/
private static $instance;
/**
* All registered post fields.
*
* @access private
* @var array
*/
private $post_fields = array();
/**
* Settings for the Columns Field.
*
* @access private
* @var array
*/
private $fields = array(
'columns' => array(
'template' => 'fusion-shortcake-field-columns',
'view' => 'editAttributeField',
),
);
/**
* Get instance of Shortcake Columns Field controller.
*
* Instantiates object on the fly when not already loaded.
*
* @return object
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self;
self::$instance->setup_actions();
}
return self::$instance;
}
/**
* Set up actions needed for Columns Field
*/
private function setup_actions() {
add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) );
add_action( 'shortcode_ui_loaded_editor', array( $this, 'load_template' ) );
}
/**
* Whether or not the color attribute is present in registered shortcode UI
*
* @return bool
*/
private function columns_attribute_present() {
foreach ( Shortcode_UI::get_instance()->get_shortcodes() as $shortcode ) {
if ( empty( $shortcode['attrs'] ) ) {
continue;
}
foreach ( $shortcode['attrs'] as $attribute ) {
if ( empty( $attribute['type'] ) ) {
continue;
}
if ( 'columns' === $attribute['type'] ) {
return true;
}
}
}
return false;
}
/**
* Add Color Field settings to Shortcake fields
*
* @param array $fields
* @return array
*/
public function filter_shortcode_ui_fields( $fields ) {
return array_merge( $fields, $this->fields );
}
/**
* Output templates used by the Columns field.
*/
public function load_template() {
?>
<script type="text/javascript">
function updateColumnData(id){
var data = [];
jQuery.each(jQuery('#'+id+"_list > li"), function(index, value){
var columnData = {
title: htmlspecialchars(jQuery(value).find('.r_title').val()),
text_content: htmlspecialchars(jQuery(value).find('.r_text_content').val()),
imgid: jQuery(value).find('.imagedata .r_imageid').val(),
imgurl: jQuery(value).find('.imagedata .r_imageurl').val()
};
data.push(columnData);
});
var dataText = JSON.stringify(data);
dataText = dataText.replace('[','').replace(']','').replace(/[\\"']/g, '\'').replace(/\u0000/g, '\'');
jQuery('#'+id).val((dataText));
}
function checkColumns(id){
if(jQuery('#'+id+"_list > li").size > 15){
jQuery('#'+id+"_add_review").hide();
} else {
jQuery('#'+id+"_add_review").show();
}
}
function addColumn(id){
var list = jQuery('#'+id+'_list');
var first = jQuery('#'+id+'_first');
var newElement = first.clone();
newElement.attr('id','');
jQuery('input[type=text]', newElement).val('');
jQuery('.previewimage', newElement).css('background-image','none');
jQuery('.removeimage', newElement).hide();
jQuery('input[type=hidden]', newElement).val('');
jQuery('input[type=number]', newElement).val('');
jQuery('textarea', newElement).val('');
jQuery(newElement).find('.shortcake-richtext').summernote( 'destroy' );
jQuery(newElement).find('.note-editor').remove();
jQuery(newElement).find('.shortcake-richtext').summernote({
toolbar: [
[ 'style', ['style'] ],
[ 'para', [ 'ul', 'ol' ] ],
[ 'font', [ 'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear' ] ],
[ 'fontsize' , [ 'fontsize' ] ],
[ 'color', [ 'color' ] ],
[ 'table', [ 'table' ] ],
[ 'insert', [ 'link', 'picture', 'video' ] ],
[ 'view', [ 'codeview', 'help' ] ]
]
});
list.append(newElement);
checkColumns(id);
}
function removeColumn(id,element){
var parent = jQuery(element).parent('li');
if(jQuery('#'+id+'_list > li').length > 1){
if(parent.attr('id') == id+'_first') {
parent.next().attr('id', id+'_first');
}
parent.remove();
updateColumnData(id);
}
checkColumns(id);
}
function initColumns(id){
var initdata = jQuery('#'+ id +'_initdata').val();
initdata = '[' + initdata.replace(/[\\']/g, '"') + ']';
initdata = JSON.parse(initdata);
if(initdata.length){
for(var i = 0; i < initdata.length; i++){
var eData = initdata[i];
var element = jQuery('#'+id+'_first');
var target;
if(i == 0){
target = element;
} else {
target = element.clone();
jQuery('input[type=text]', target).val('');
jQuery('.previewimage', target).css('background-image','none');
jQuery('.removeimage', target).hide();
jQuery('input[type=hidden]', target).val('');
jQuery('input[type=number]', target).val('');
jQuery('textarea', target).val('');
jQuery(target).find('.shortcake-richtext').summernote( 'destroy' );
jQuery(target).find('.note-editor').remove();
jQuery(target).find('.shortcake-richtext').summernote({
toolbar: [
[ 'style', ['style'] ],
[ 'para', [ 'ul', 'ol' ] ],
[ 'font', [ 'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear' ] ],
[ 'fontsize' , [ 'fontsize' ] ],
[ 'color', [ 'color' ] ],
[ 'table', [ 'table' ] ],
[ 'insert', [ 'link', 'picture', 'video' ] ],
[ 'view', [ 'codeview', 'help' ] ]
]
});
}
if(eData.title){
jQuery(target).find('.r_title').val(htmlspecialchars_decode(eData.title));
}
if(eData.text_content){
jQuery(target).find('.r_text_content').val(htmlspecialchars_decode(eData.text_content));
}
if(eData.imgid){
jQuery(target).find('.imagedata .r_imageid').val(eData.imgid);
jQuery(target).find('.imagedata .removeimage').show();
}
if(eData.imgurl){
jQuery(target).find('.imagedata .r_imageurl').val(eData.imgurl);
jQuery(target).find('.imagedata .previewimage').css('background-image','url(' +eData.imgurl + ')');
}
if(i != 0){
var list = jQuery('#'+id+'_list');
target.attr('id','');
list.append(target);
}
}
}
updateColumnData(id);
}
function attachColumnImage(id,element){
var meta_image_frame;
if ( meta_image_frame ) {
wp.media.editor.open();
return;
}
meta_image_frame = wp.media.frames.meta_image_frame = wp.media({
title: '<?php echo __('Select Image', 'xapt') ?>',
button: { text: '<?php echo __('Insert', 'xapt') ?>' },
library: { type: 'image' }
});
meta_image_frame.on('select', function(){
var media_attachment = meta_image_frame.state().get('selection').first().toJSON();
var parent = jQuery(element).parents('.imagecontainer');
parent.find('.r_imageid').val(media_attachment.id);
parent.find('.r_imageurl').val((media_attachment.sizes && media_attachment.sizes.full.url || media_attachment.url));
parent.find('.previewimage').css('background-image','url(' + (media_attachment.sizes && media_attachment.sizes.thumbnail.url || media_attachment.url) + ')');
parent.find('.removeimage').show();
updateColumnData(id);
});
meta_image_frame.open()
}
function removeColumnImage(id,elem){
var parent = jQuery(elem).parents('.imagecontainer');
parent.find('.r_imageid').val('');
parent.find('.r_imageurl').val('');
parent.find('.previewimage').css('background-image','none');
parent.find('.removeimage').hide();
updateColumnData(id);
}
</script>
<script type="text/html" id="tmpl-fusion-shortcake-field-columns">
<div class="field-block shortcode-ui-field-columns shortcode-ui-attribute-{{ data.attr }}">
<input type="button" class="button-primary" value="<?php echo __('Add Column', 'xapt') ?>" id="{{ data.id }}_add_review" onclick="addColumn('{{ data.id }}')" />
<hr />
<form id="{{ data.id }}_reviews" name="{{ data.id }}_counters">
<input type="hidden" class="large-text" name="initdata" id="{{ data.id }}_initdata" value="{{ data.value }}" />
<input type="hidden" class="large-text" name="{{ data.attr }}" id="{{ data.id }}" value="{{ data.value }}" />
<ul class="sortable" id="{{ data.id }}_list">
<li id="{{ data.id }}_first">
<table>
<tr>
<td style="vertical-align: top;">
<input type="text" class="regular-text r_title" placeholder="<?php echo __('Title', 'xapt') ?>" onkeyup="updateColumnData('{{ data.id }}')" /><br />
<textarea class="shortcake-richtext regular-text r_text_content" style="margin: 10px 0 0; width: 300px; height: 131px;" placeholder="<?php echo __('Content', 'xapt') ?>" onkeyup="updateColumnData('{{ data.id }}')"></textarea>
</td>
<td class="imagecontainer imagedata" style="vertical-align: top; padding-left: 10px;">
<input type="hidden" id="{{ data.id }}_img" class="r_imageid" />
<input type="hidden" id="{{ data.id }}_imgurl" class="r_imageurl" />
<input type="button" class="button-secondary" value="<?php echo __('Attach Image', 'xapt') ?>" onclick="attachColumnImage('{{ data.id }}',this)" />
<input type="button" class="button-secondary removeimage" style="display: none;" value="<?php echo __('Remove', 'xapt') ?>" id="{{ data.id }}_removeimage" onclick="removeColumnImage('{{ data.id }}',this)" />
<div style="margin-top: 15px; width: 128px; height: 128px; border: 1px dashed #ccc; background-size: contain; background-repeat: no-repeat; background-position: center;" id="{{ data.id }}_preview" class="previewimage"></div>
</td>
</tr>
</table>
<input type="button" class="button-primary remove-button" value="<?php echo __('Remove', 'xapt') ?>" onclick="removeColumn('{{ data.id }}', this)" />
<hr />
</li>
</ul>
</form>
<script type="text/javascript">initColumns('{{ data.id }}');</script>
</div>
</script>
<?php
}
}
$feautres_field = Shortcake_Field_Columns::get_instance();
and use it in the shortcode like this:
shortcode_ui_register_for_shortcode( 'columns_block', array(
'label' => __('Columns Block', 'xapt'),
'listItemImage' => 'dashicons-layout',
'post_type' => array( 'post' ),
'attrs' => array(
array(
'label' => __('Columns Block', 'xapt'),
'attr' => 'columns',
'type' => 'columns',
),
)
)
);
add_shortcode( 'columns_block', function( $attr, $content = '' ) {
$attr = wp_parse_args( $attr, array(
'columns' => ''
) );
ob_start();
if(!empty($attr['columns'])){
$attr['columns'] = '['.str_replace("'",'"',$attr['columns']).']';
$columns = json_decode($attr['columns'],true);
$columnsFull = array();
foreach($columns as $column){
$column['title'] = urldecode($column['title']);
$column['text_content'] = urldecode($column['text_content']);
$image = wp_get_attachment_image_src( $column['imgid'], 'gallery_thumbnail');
$column['imgurl'] = (!empty($image) ? $image[0] : false );
$columnsFull[] = $column;
}
twig_render('components/shortcodes/shortcode.columns-block.twig', array(
'columns' => $columnsFull
));
}else{
echo '<div style="padding: 10px; width: 100%; text-align: center; border: 1px dotted #ccc;">' . __('Click here to customize', 'xapt') . '</div>';
}
return ob_get_clean();
} );
If you don't use twig just repalce the twig_render function with the output html
Hi @kaoquan did you manage to fix that onkeyup bug? I'm implementing a similar solution, and have the same issue!
@kaoquan I managed a hacky solution, by triggering a change()
on another field.
@mwtsn Yes I did here it goes
i have a js file for the admin like this
var richTextSelector = 'textarea.shortcake-richtext-field';
var richText = {};
/* globals jQuery, alert */
jQuery(function( $ ) {
'use strict';
richText.load = function( selector ) {
if ( ( 'undefined' !== tinyMCE ) && ( $( selector ).length ) ) {
$( selector ).each( function() {
var textarea_id = $(this).attr('id');
if( null === tinyMCE.get( textarea_id ) ) {
// Add a slight delay to offset the loading of any elements on the page. Sometimes doesn't load correctly
setTimeout(function () {
// Bind tinyMCE to this field
tinyMCE.execCommand('mceAddEditor', false, textarea_id );
}, 10);
}
});
return true;
} else {
return false;
}
};
richText.unload = function( selector ) {
if ( ( $( selector ).length ) ) {
$( selector ).each( function() {
var textarea_id = $( this).attr('id');
if( null != tinyMCE.get( textarea_id ) ) {
// Remove tinyMCE from the field
tinymce.execCommand( 'mceRemoveEditor', false, textarea_id );
}
});
return true;
} else {
return false;
}
};
} );
function addItem(id){
var list = jQuery('#'+id+'_list');
var first = jQuery('#'+id+'_first');
richText.unload( jQuery( first ).find( richTextSelector ) );
var newElement = first.clone();
richText.load( jQuery( first ).find( richTextSelector ) );
newElement.attr('id','');
jQuery('input[type=text]', newElement).val('');
jQuery('.previewimage', newElement).css('background-image','none');
jQuery('.removeimage', newElement).hide();
jQuery('input[type=hidden]', newElement).val('');
jQuery('input[type=number]', newElement).val('');
jQuery('textarea', newElement).val('');
richText.count++;
jQuery(newElement).find('.r_text_content').attr('id',id + '-text-cotent-' + richText.count);
richText.load( jQuery( newElement ).find( richTextSelector ) );
list.append(newElement);
}
function removeItem(id,element){
var parent = jQuery(element).parent('li');
if(jQuery('#'+id+'_list > li').length > 1){
if(parent.attr('id') == id+'_first') {
parent.next().attr('id', id+'_first');
}
richText.unload( jQuery( parent ).find( richTextSelector ) );
parent.remove();
}
}
function attachItemImage(id,element){
var meta_image_frame;
if ( meta_image_frame ) {
wp.media.editor.open();
return;
}
meta_image_frame = wp.media.frames.meta_image_frame = wp.media({
title: 'Select Image',
button: { text: 'Insert'},
library: { type: 'image' }
});
meta_image_frame.on('select', function(){
var media_attachment = meta_image_frame.state().get('selection').first().toJSON();
var parent = jQuery(element).parents('.imagecontainer');
parent.find('.r_imageid').val(media_attachment.id);
parent.find('.r_imageurl').val((media_attachment.sizes && media_attachment.sizes.full.url || media_attachment.url));
parent.find('.previewimage').css('background-image','url(' + (media_attachment.sizes && media_attachment.sizes.full.url || media_attachment.url) + ')');
parent.find('.removeimage').show();
});
meta_image_frame.open()
}
function removeItemImage(id,elem){
var parent = jQuery(elem).parents('.imagecontainer');
parent.find('.r_imageid').val('');
parent.find('.r_imageurl').val('');
parent.find('.previewimage').css('background-image','none');
parent.find('.removeimage').hide();
}
/** fps corencting encoding **/
function htmlspecialchars(str) {
var map = {
"<": "%3C",
">": "%3E",
"[": "%5B",
"]": "%5D",
"\\": "%5C",
"\"": "%22",
"'": "%27",
"+": "%2B",
"\n": "%0A",
"\t": "%09"
};
if(str == '<p><br></p>'){
str = '';
}
return str.replace(/[<>\[\]\\"'+\n\t]/g, function(m) { return map[m]; });
}
function htmlspecialchars_decode(str) {
var map = {
"%3C": "<",
"%3E": ">",
"%5B": "[",
"%5D": "]",
"%5C": "\\",
"%22": "\"",
"%27": "'",
"%2B": "+",
"%0A" : "\n",
"%09" : "\t"
};
return str.replace(/(%3C|%3E|%5B|%5D|%5C|%22|%27|%2B|%0A|%09)/g, function(m) { return map[m]; });
}```
and the class I changed it like this i hope it help, (off for the weekend but will reply for any question on monday)
```<?php
/**
* Primary controller class for Shortcake Columns Field
*/
class Shortcake_Field_Columns {
/**
* Shortcake Columns Field controller instance.
*
* @access private
* @var object
*/
private static $instance;
/**
* All registered post fields.
*
* @access private
* @var array
*/
private $post_fields = array();
/**
* Settings for the Columns Field.
*
* @access private
* @var array
*/
private $fields = array(
'columns' => array(
'template' => 'fusion-shortcake-field-columns',
'view' => 'editAttributeField',
),
);
/**
* Get instance of Shortcake Columns Field controller.
*
* Instantiates object on the fly when not already loaded.
*
* @return object
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self;
self::$instance->setup_actions();
}
return self::$instance;
}
/**
* Set up actions needed for Columns Field
*/
private function setup_actions() {
add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) );
add_action( 'shortcode_ui_loaded_editor', array( $this, 'load_template' ) );
}
/**
* Whether or not the color attribute is present in registered shortcode UI
*
* @return bool
*/
private function columns_attribute_present() {
foreach ( Shortcode_UI::get_instance()->get_shortcodes() as $shortcode ) {
if ( empty( $shortcode['attrs'] ) ) {
continue;
}
foreach ( $shortcode['attrs'] as $attribute ) {
if ( empty( $attribute['type'] ) ) {
continue;
}
if ( 'columns' === $attribute['type'] ) {
return true;
}
}
}
return false;
}
/**
* Add Color Field settings to Shortcake fields
*
* @param array $fields
* @return array
*/
public function filter_shortcode_ui_fields( $fields ) {
return array_merge( $fields, $this->fields );
}
/**
* Output templates used by the Columns field.
*/
public function load_template() {
?>
<script type="text/javascript">
function updateColumnData(id){
var data = [];
jQuery.each(jQuery('#'+id+"_list > li"), function(index, value){
var columnData = {
title: htmlspecialchars(jQuery(value).find('.r_title').val()),
text_content: htmlspecialchars(jQuery(value).find('.r_text_content').val()),
imgid: jQuery(value).find('.imagedata .r_imageid').val(),
imgurl: jQuery(value).find('.imagedata .r_imageurl').val()
};
data.push(columnData);
});
var dataText = JSON.stringify(data);
dataText = dataText.replace('[','').replace(']','').replace(/[\\"']/g, '\'').replace(/\u0000/g, '\'');
jQuery( '#'+id ).val( dataText ).trigger( 'input' );
}
function initColumns(id){
richText.count = 0;
var initdata = jQuery('#'+ id +'_initdata').val();
initdata = '[' + initdata.replace(/[\\']/g, '"') + ']';
initdata = JSON.parse(initdata);
var element = jQuery('#'+id+'_first');
jQuery(element).find('.r_text_content').attr('id',id + '-text-cotent-' + 0);
if(initdata.length){
richText.count = initdata.length;
for(var i = 0; i < initdata.length; i++){
var eData = initdata[i];
var target;
if(i == 0){
target = element;
} else {
target = element.clone();
jQuery('input[type=text]', target).val('');
jQuery('.previewimage', target).css('background-image','none');
jQuery('.removeimage', target).hide();
jQuery('input[type=hidden]', target).val('');
jQuery('input[type=number]', target).val('');
jQuery('textarea', target).val('');
}
if(eData.title){
jQuery(target).find('.r_title').val(htmlspecialchars_decode(eData.title));
}
if(eData.text_content){
jQuery(target).find('.r_text_content').val(htmlspecialchars_decode(eData.text_content));
}
if(eData.imgid){
jQuery(target).find('.imagedata .r_imageid').val(eData.imgid);
jQuery(target).find('.imagedata .removeimage').show();
}
if(eData.imgurl){
jQuery(target).find('.imagedata .r_imageurl').val(eData.imgurl);
jQuery(target).find('.imagedata .previewimage').css('background-image','url(' +eData.imgurl + ')');
}
jQuery(target).find('.r_text_content').attr('id',id + '-text-cotent-' + i);
if(i != 0){
var list = jQuery('#'+id+'_list');
target.attr('id','');
list.append(target);
}
}
}
richText.load( jQuery( richTextSelector ) );
}
</script>
<script type="text/html" id="tmpl-fusion-shortcake-field-columns">
<div class="field-block shortcode-ui-field-columns shortcode-ui-attribute-{{ data.attr }}">
<input type="button" class="button-primary" value="<?php echo __('Add Column', 'xapt') ?>" id="{{ data.id }}_add_review" onclick="addItem('{{ data.id }}')" />
<hr />
<form id="{{ data.id }}_reviews" name="{{ data.id }}_counters">
<input type="hidden" class="large-text" name="initdata" id="{{ data.id }}_initdata" value="{{ data.value }}" />
<input type="hidden" class="large-text" name="{{ data.attr }}" id="{{ data.id }}" value="{{ data.value }}" />
<ul class="sortable" id="{{ data.id }}_list">
<li id="{{ data.id }}_first">
<table>
<tr>
<td style="vertical-align: top;">
<input type="text" class="regular-text r_title" placeholder="<?php echo __('Title', 'xapt') ?>" /><br />
<textarea class="shortcake-richtext-field regular-text r_text_content" style="margin: 10px 0 0; width: 300px; height: 131px;" placeholder="<?php echo __('Content', 'xapt') ?>"></textarea><br/>
</td>
<td class="imagecontainer imagedata" style="vertical-align: top; padding-left: 10px;">
<input type="hidden" id="{{ data.id }}_img" class="r_imageid" />
<input type="hidden" id="{{ data.id }}_imgurl" class="r_imageurl" />
<input type="button" class="button-secondary" value="<?php echo __('Attach Image', 'xapt') ?>" onclick="attachItemImage('{{ data.id }}',this)" />
<input type="button" class="button-secondary removeimage" style="display: none;" value="<?php echo __('Remove', 'xapt') ?>" id="{{ data.id }}_removeimage" onclick="removeItemImage('{{ data.id }}',this)" />
<div style="margin-top: 15px; width: 128px; height: 128px; border: 1px dashed #ccc; background-size: contain; background-repeat: no-repeat; background-position: center;" id="{{ data.id }}_preview" class="previewimage"></div>
</td>
</tr>
</table>
<input type="button" class="button-primary remove-button" value="<?php echo __('Remove', 'xapt') ?>" onclick="removeItem('{{ data.id }}', this)" />
<hr />
</li>
</ul>
</form>
<script type="text/javascript">
initColumns('{{ data.id }}');
wp.shortcake.hooks.addAction( 'shortcode-ui.render_destroy', function() {
richText.unload( richTextSelector );
updateColumnData('{{ data.id }}');
} );
</script>
</div>
</script>
<?php
}
}
$feautres_field = Shortcake_Field_Columns::get_instance();
@mwtsn this is the key wp.shortcake.hooks.addAction( 'shortcode-ui.render_destroy', function() { richText.unload( richTextSelector ); updateColumnData('{{ data.id }}'); } ); it only updateds when you close the popup window. so only one update and not on every key :)
Thank you very much @kaoquan this worked a treat :)
Request from WordPress.org: