load wordpress wp_editor dynamically (ajax) - javascript

This is an answer/solution rather than a question, still there maybe some bugs, even I tried on my dev env.
I recently try to use wp_editor in widget/menu, after some search, I did not find a complete solution as I want.
I would share my solution in below after I dig into wp's code by hours:

There maybe hacking involved, however, I tried to minimize them.
To make wp_editor can work in dynamic html (which means without reload page, js changes the page structure), there are two major issues need to take care:
tinymce
qucik-tags
For [tinymce]:
a. need reset UI properly
solution is [remove mce instance] -> [get proper mce settings] -> [re-init a new mce instance]
in js code (id means textarea id):
tinymce.execCommand('mceRemoveEditor', true, id);
var init = tinymce.extend( {}, tinyMCEPreInit.mceInit[ id ] );
try { tinymce.init( init ); } catch(e){}
b. need data write back to textarea before submit
solution is [bind click to button] -> on submt :: [turn off mce] -> [turn on submit]
in js code:
jq('textarea[id="' + id + '"]').closest('form').find('input[type="submit"]').click(function(){
if( getUserSetting( 'editor' ) == 'tmce' ){
var id = mce.find( 'textarea' ).attr( 'id' );
tinymce.execCommand( 'mceRemoveEditor', false, id );
tinymce.execCommand( 'mceAddEditor', false, id );
}
return true;
});
For [Quick Tags]:
a. Re-init tags
[Get settings] -> [setup mouse event] -> [re-init QTags]
b. Switch to proper tab (mce tab or quick tag tab)
[switch to current tab mode]
both above in js code:
if ( typeof(QTags) == 'function' ) {
jq( '[id="wp-' + id + '-wrap"]' ).unbind( 'onmousedown' );
jq( '[id="wp-' + id + '-wrap"]' ).bind( 'onmousedown', function(){
wpActiveEditor = id;
});
QTags( tinyMCEPreInit.qtInit[ id ] );
QTags._buttonsInit();
switchEditors.switchto( jq( 'textarea[id="' + id + '"]' ).closest( '.widget-mce' ).find( '.wp-switch-editor.switch-' + ( getUserSetting( 'editor' ) == 'html' ? 'html' : 'tmce' ) )[0] );
}
Also, please remember if you use ajax, every time post back mce UI, you need re-do [reset mce UI] and [Qtags] in you js.
A easy solution is using js code in you post back html, and detect in php of:
$isAjax = defined( 'DOING_AJAX' ) && DOING_AJAX == true );
About default settings in js value:
mce : tinyMCEPreInit.mceInit
qtags : tinyMCEPreInit.qtInit
If you try to use default setting for widget mode, you need locate default settings.
To get widget template id, in js code:
function getTemplateWidgetId( id ){
var form = jQuery( 'textarea[id="' + id + '"]' ).closest( 'form' );
var id_base = form.find( 'input[name="id_base"]' ).val();
var widget_id = form.find( 'input[name="widget-id"]' ).val();
return id.replace( widget_id, id_base + '-__i__' );
}
So you can get settings by:
for mce:
var init;
if( typeof tinyMCEPreInit.mceInit[ id ] == 'undefined' ){
init = tinyMCEPreInit.mceInit[ id ] = tinymce.extend( {}, tinyMCEPreInit.mceInit[ getTemplateWidgetId( id ) ] );
}else{
init = tinyMCEPreInit.mceInit[ id ];
}
For Qtags:
var qInit;
if( typeof tinyMCEPreInit.qtInit[ id ] == 'undefined' ){
qInit = tinyMCEPreInit.qtInit[ id ] = jq.extend( {}, tinyMCEPreInit.qtInit[ getTemplateWidgetId( id ) ] );
qInit['id'] = id;
}else{
qInit = tinyMCEPreInit.qtInit[ id ];
}
For the complete code sample, please check : https://github.com/hezachary/wordpress-wysiwyg-widget/blob/master/widget_wp_editor.class.php
If anyone want use wp_editor in menu walk for admin, the principle should be the same.
If you have any question or better solut please comment, thanks.

Working solution:
p.s. you'd have asked at WP.SE: https://wordpress.stackexchange.com/a/192132/33667
add action in wordpress, lets say My_Action_Name (also note, textarea ID My_TextAreaID_22 ):
add_action('wp_ajax_My_Action_Name', function(){
wp_editor( $_POST['default_text'], 'My_TextAreaID_22', $settings = array( 'tinymce'=>true, 'textarea_name'=>'name77', 'wpautop' =>false, 'media_buttons' => true , 'teeny' => false, 'quicktags'=>true, ) ); exit;
});
now, in Dashboard, execute this function (note, using of My_TextAreaID_22 and My_Action_Name):
function start_Ajax_request() {
Alert("I have started");
My_New_Global_Settings = tinyMCEPreInit.mceInit.content; // Get default Wordpress SETTINGS ( I cant confirm, but if you will need to change target ID, then add this line: My_New_Global_Settings.selector = "My_TextAreaID_22"; )
jQuery.post(ajaxurl,
{ action: "My_Action_Name", default_text: "Hello World"},
function(response,status){
Alert("I have Finished");
tinymce.init(My_New_Global_Settings);
tinyMCE.execCommand('mceAddEditor', false, "My_TextAreaID_22");
quicktags({id : "My_TextAreaID_22"});
}
);
}
start_Ajax_request(); // < ---- EXECUTE

I have faced similar issue. I solved the issue by using following
1. Added following filter to open the editor always in visual mode in main page
add_filter( 'wp_default_editor', create_function('', 'return "tinymce";') );
2. In Ajax content used following for editor
wp_editor( $content, "editor_ajax", array('textarea_name'=>"content_ajax",'quicktags' => array('buttons' => 'strong,em,link,block,del,ins,img,ul,ol,li,code,close')));
3. In Ajax content added following JS code
<script src="<?php bloginfo('home')?>/wp-includes/js/quicktags.min.js"</script>
<script>
jQuery(document).ready(function(){
ed_id = "editor_ajax";
quicktags({id : ed_id,buttons:"strong,em,link,block,del,ins,img,ul,ol,li,code,more,close,dfw"});
tinyMCE.execCommand('mceAddEditor', false, fullId);
});
</script>

Related

Save multiple checkbox[] check state on page refresh using local storage in php

I have problem to show user checkbox check state after page is refreshed. I tried using local storage but it checks all the checkboxes please help!
I have use ajax to load different pages to select the row by checkbox
This is input value
<input type="checkbox" id="checkselect" data-name="checkselect[]" class="get_value" value="<?php echo $row['car_booking_id'];?>">
And this is my javascript
<script>
$('.get_value').on('click', function() {
var fav, favs = [];
$('.get_value').each(function() { // run through each of the checkboxes
fav = {id: $(this).attr('name'), value: $(this).prop('checked')};
favs.push(fav);
});
localStorage.setItem("favorites", JSON.stringify(favs));
alert(fav);
});
$(document).ready(function() {
var favorites = JSON.parse(localStorage.getItem('favorites'));
//alert(favorites);
if (!favorites.length) {return};
console.debug(favorites);
for (var i=0; i<favorites.length; i++) {
console.debug(favorites[i].value == 'on');
$('#' + favorites[i].id ).prop('checked', favorites[i].value);
}
});
</script>
A simple demo of using a storage object to keep track of which checkboxes have been ticked.
There is a useful factory function here StoreFactory I wrote to simplify further working with storage objects - it only has a very few methods and is very simple to use.
The demo generates a number of checkboxes similar to the one you show in the question - though I have made life simpler by adding a unique dataset.id attribute - this could easily be the actual id instead but in the question it would appear that multiple checkboxes would share the same ID which is not valid.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<script>
const StoreFactory=function( name, type ){
'use strict';
const engine = type.toLowerCase() === 'local' ? localStorage : sessionStorage;
const set=function( data ){
engine.setItem( name, JSON.stringify( data ) );
};
const get=function(){
return exists( name ) ? JSON.parse( engine.getItem( name ) ) : false;
};
const remove=function(){
engine.removeItem( name );
};
const exists=function(){
return engine.getItem( name )==null ? false : true;
};
const create=function(o={}){
set(o);
};
return Object.freeze({
set,
get,
exists,
create,
remove
});
}
document.addEventListener('DOMContentLoaded', e=>{
/* create a new storefactory object */
let store=new StoreFactory('favourites','local');
/* if the actual store does not exist, create it */
if( !store.exists() )store.create();
/* get the data stored in the local storage object */
let data = store.get();
/* assign event listeners to all the checkboxes */
Array.prototype.slice.call( document.querySelectorAll('input[type="checkbox"]') ).forEach( chk=>{
chk.addEventListener('click', e=>{
/* add checked state to store */
data[ e.target.dataset.id ]=e.target.checked;
/* save the data */
store.set( data );
})
});
/* reload stored checkboxes */
Object.keys( data ).map( k =>{
if( data[ k ]==true )document.querySelector( 'input[ type="checkbox" ][ data-id="'+k+'" ]' ).checked=true;
else {
delete data[ k ];
store.set( data );
}
})
})
</script>
<style>
form{ margin:auto; display:flex; flex-diection:row;flex-wrap:wrap; align-items:center;align-content:center;justify:content:center;font-family:cursive }
label{min-width:4rem;padding:0.5rem;margin:0.25rem;border:1px solid rgba(133,133,133,0.1);box-sizing:border-box;background:whitesmoke}
label:before{content:attr(data-value);color:blue}
</style>
</head>
<body>
<form method='post' name='checkboxes'>
<?php
/* some checkboxes - note the use of the dataset id attribute! */
for( $i=1; $i <= 50; $i++ ){
printf('<label data-value=%d><input data-id="chk_%d" type="checkbox" name="checkselect[]" value="%d" class="get_value" /></label>', $i, $i, $i );
}
?>
</form>
</body>
</html>
There were, as far as I can tell, a few little issues with your code. Not being a user of jQuery I might be mistaken but it looks like your get a reference to ALL the checkboxes and, regardless of the checked state, add them to your favs array which is then saved in the local storage object. In itself that is fine, the problem revolves around fav = {id: $(this).attr('name'), value: $(this).prop('checked')}; ~ there would be no way to tell which checkbox is which if there are several checked. Each checkbox would need a unique ID ( as is always true ~ ID attributes MUST be unique!! )
It also seems that the click handler should be defined after the $(document).ready call and then I guess remove the inline onchange="CheckedChange(this)" from each checkbox.
Slightly re-written version of your code which works.. though I don't know the syntax for using the jQuery data method... I think I'm close but it was not right so used vanilla js instead.
<script>
$(document).ready(function() {
var favorites = JSON.parse( localStorage.getItem( 'favorites' ) );
$('.get_value').on('click', function() {
var favs = [];
$('.get_value').each(function(){
favs.push( {
id:$(this).attr('data-id'),
value:$(this).prop('checked')
} );
});
localStorage.setItem( 'favorites', JSON.stringify( favs ) );
});
if( favorites!==null ){
for( var i=0; i < favorites.length; i++ ) {
//$('input').data( 'id', favorites[i].id ).prop( 'checked', favorites[i].value );
if( favorites[i].value ) document.querySelector( 'input[ type="checkbox" ][ data-id="'+favorites[i].id+'" ]' ).checked=true;
}
}
});
</script>
I think in your html you should add a unique attribute to each checkbox like this
<input type="checkbox" id="checkselect" data-name="checkselect[]"
data-unique="<?php echo $row['car_booking_id'];?>"
class="get_value"
value="<?php echo $row['car_booking_id'];?>">
Now save the checked status by the unique_id and $('.get_value[data-unique="' + favorites[i].unique_id + '"]').prop('checked', favorites[i].value); would get the input that has that attribute and set checked to on or off depending on the stored state
$('.get_value').on('click', function() {
var fav, favs = [];
$('.get_value').each(function() { // run through each of the checkboxes
fav = {unique_id: $(this).attr('data-unique'), value: $(this).value};
favs.push(fav);
});
localStorage.setItem("favorites", JSON.stringify(favs));
alert(fav);
});
$(document).ready(function() {
var favorites = JSON.parse(localStorage.getItem('favorites'));
//alert(favorites);
if (!favorites.length) {return};
console.debug(favorites);
for (var i=0; i<favorites.length; i++) {
console.debug(favorites[i].value == 'on');
$('.get_value[data-unique="' + favorites[i].unique_id + '"]').prop('checked', favorites[i].value);
}
});
Let it be clear that it is bad practice to use the id attribute to group elements. It is for uniqueness and should be used as such. Grouping element can be done using the class or data-[anything] attribute.
I hope this helps

Data binding with jQuery not working with chrome

I was reading this article and for some reason I cannot get this code to work in chrome- IE9 works without issue.
Here is the js:
$(document).ready(function() {
function DataBinder( object_id ) {
// Use a jQuery object as simple PubSub
var pubSub = jQuery({});
// We expect a `data` element specifying the binding
// in the form: data-bind-<object_id>="<property_name>"
var
data_attr = "bind-" + object_id,
message = object_id + ":change";
// Listen to change events on elements with the data-binding attribute and proxy
// them to the PubSub, so that the change is "broadcasted" to all connected objects
jQuery( document ).on( "change", "[data-" + data_attr + "]", function( evt ) {
var $input = jQuery( this );
console.dir('test message');
pubSub.trigger( message, [ $input.data( data_attr ), $input.val() ] );
});
// PubSub propagates changes to all bound elements, setting value of
// input tags or HTML content of other tags
pubSub.on( message, function( evt, prop_name, new_val ) {
jQuery( "[data-" + data_attr + "=" + prop_name + "]" ).each( function() {
var $bound = jQuery( this );
if ( $bound.is("input, textarea, select") ) {
$bound.val( new_val );
} else {
$bound.html( new_val );
}
});
});
return pubSub;
};
function User( uid ) {
var binder = new DataBinder( uid ),
user = {
attributes: {},
// The attribute setter publish changes using the DataBinder PubSub
set: function( attr_name, val ) {
this.attributes[ attr_name ] = val;
binder.trigger( uid + ":change", [ attr_name, val, this ] );
},
get: function( attr_name ) {
return this.attributes[ attr_name ];
},
_binder: binder
};
// Subscribe to the PubSub
binder.on( uid + ":change", function( evt, attr_name, new_val, initiator ) {
if ( initiator !== user ) {
user.set( attr_name, new_val );
}
});
return user;
};
var user = new User( 123 );
user.set( "name", "Wolfgang" );
});
HTML:
<!DOCTYPE HTML>
<html>
<head>
<title>test</title>
<script src="../../jquery/jquery.js"></script>
</head>
<body>
<input type="number" data-bind-123="name" />
<script src="js/myjsfile.js"></script>
</body>
</html>
I added console.dir('test message') which should be logged when a change event gets fired on the input box. In IE i see this message, but in Chrome I do not.
Well I dumb, this was user error + IE weirdness. In my testing I was entering a string into the input box, but the input type is set to "number". It seems Chrome won't fire a change event if the type doesn't match, but IE will? Either way in IE I type whatever the heck in the box, the event fires and I see my "test message" in the console. In chrome I have to type a number and if I type a string nothing happens. Please let me know if I'm misinterpreting this behavior.

jquery load content into script tag in firefox

I have an html page using jquery 1.7.2. Within the page I have a scrip tag like so.
<script id="navigation-template" type="text/x-handlebars-template"></script>
Further down the page I'm using javascript to load my handlebars template into the script tag using the following function:
loadTemplate: function( templateId, elementId ) {
if ( !elementId ) {
elementId = templateId;
}
$('#'+elementId).load('/my/path/templates.html #'+templateId);
}
This is working fine in chrome, the eclipse browser, and even IE 9 but seems to go south in Firefox.
I have debugged and the load call successfully completes and the content is returned, but a call to $('#navigation-template').html() gives an empty String.
I also had content in the script tag and called the load and saw that it was replaced by the empty string after the .load call.
Finally, if I manually perform $('#navigation-template').html( "hello" ); I see that the .html() for the script tag is changed.
If I go to a simple ajax get then I will have to parse it and get the given element rather than relying on load to get the element for me.
How do I get around this issue in firefox?
Here is the function I use for such purposes:
Util.loadTemplates = function(ExternalTemplates) {
$.each(ExternalTemplates, function(index, value){
var scriptUrl = value;
$.ajax({
url: scriptUrl,
dataType: 'text',
success: function(res){
var templateName = value.slice(value.lastIndexOf('/') + 1, value.lastIndexOf('.'));
TEMPLATES[templateName] = Handlebars.compile(res);
}
});
});
}
var ExternalTemplates = [
'templates/application.hbs',
'templates/people.hbs'
];
But it is better to look into doing the compiling, which turns the template into a function, before the page is sent to the client.
You are using the type as this
<script id="navigation-template" type="text/x-handlebars-template"></script>
Try changing the type to
<script id="navigation-template" type="text/javascript"></script>
One thing I liked about load() was that I could store all my templates in a single file and use load to select the div for the template I was interested in. I wrote a method that will load the template file and store the templates into a map of template name to template source and compiled template. I compile the template on the first access so that I don't needlessly compile all the templates every time, but only compile the ones I need when needed. It looks something like this:
var myTemplateHelperThingy = {
loadTemplates: function() {
$.get( '/my/path/templates.html' )
.done(function(data) {
var elements = $(data);
$( 'div.template-marker-class', elements).each( function( index, element ) {
// need to use element instead of 'this' because IE is st00pid.
var content = $(element)[0].outerHTML; // trick from StackOverflow
myAppObject.pageTemplates[this.id] = {
source: content,
template: null
};
});
});
},
getTemplate: function( name ) {
// get a compiled template, compiling it if necessary.
var result = myAppObject.pageTemplates[name].template;
if (!result) {
myAppObject.pageTemplates[name].template = Handlebars.compile(myAppObject.pageTemplates[name].source);
}
return myAppObject.pageTemplates[name].template;
},
evalTemplate: function( data, templateName ) {
var template = myAppObject.getTemplate(templateName);
if (template) {
return template(data);
}
else {
// message to user here that something went wrong.
}
},
showTemplate: function( targetElement, data, templateName ) {
$(targetElement).html(bi.evalTemplate( data, templateName ));
}
}
And templates.html looks like:
<html>
<body>
<div id="templates-wrapper-do-not-remove-or-jquery-will-not-find-the-templates">
<div id="my-first-template" class="template-marker-class other-class">
<!-- a bunch of content -->
</div>
<div id="my-second-template" class="template-marker-class another-class">
<!-- more content -->
</div>
</div>
</body>
</html>

CakePhp2 autocomplete (jquery-ui folding) not working without ajax, only with array of available inputs

I am using cakephp2.
I want to make autocomplete in cakephp2, but not with ajax, it is array where are autocomplete`s available inputs. I have simple LocationsController (not important but i enclosed it) where i have:
class LocationsController extends AppController {
public $name = 'Locations';
public function index() {
$this->set('title_for_layout', 'Example - title');
}
}
I have a view/locations/index.ctp where i Have
<div id="big_input_normal" >
<form>
<input type="text" id="the_big_one" class="big_input_selection" />
</form>
</div>
<script>
$(function() {
var names = [ "Bratislava", "Praque", "Trstena" ];
var accentMap = {
"á": "a",
"ö": "o",
"é": "e"
};
var normalize = function( term ) {
var ret = "";
for ( var i = 0; i < term.length; i++ ) {
ret += accentMap[ term.charAt(i) ] || term.charAt(i);
}
return ret;
};
$( "#the_big_one" ).autocomplete({
source: function( request, response ) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex( request.term ), "i" );
response( $.grep( names, function( value ) {
value = value.label || value.value || value;
return matcher.test( value ) || matcher.test( normalize( value ) );
}) );
}
});
});
</script>
Ofcourse i have included in head :
<script src="./app/webroot/js/jquery-1.7.1.js"></script>
<script src="./app/webroot/js/jquery.ui.core.js"></script>
<script src="./app/webroot/js/jquery.ui.widget.js"></script>
<script src="./app/webroot/js/jquery.ui.position.js"></script>
<script src="./app/webroot/js/jquery.ui.autocomplete.js"></script>
To sum up:
I am using CAKEPHP2 (it is version 2 NOT 1.3), I want to make autocomplete with jquery, i downloaded jquery-ui and i have followed the examples folding http://jqueryui.com/demos/autocomplete/
I have made it, have a look at example codes, but there is a problem, it DOES NOT WORK.
Javascripts are after the page to client is rendered defaultly blocked ?
Or where is the problem? Please help me, am losing my mind with this primitive problem.
It looks like all of the javascript inclusions are incorrect. They should reference the js file like so:
<script src="/js/jquery-1.7.1.js"></script>
To get cake to do this automatically, you can do:
<?php echo $this->Html->script('jquery-1.7.1.js'); ?>

Reading .text() from XML not working in IE

Hi I have this code that is all working in FF, except for the variable populated by a .text() method in IE.
The example code is below:
<script language="javascript" type="text/javascript">
$(document).find('f').each(function(){
var ff= $(this).attr("m");
var fmsg = $(this).text();
alert(ff + ' - ' +fmsg);
});
</script>
The data (document):
<data>
<f m="1">hi</f>
<f m="2">bye</f>
</data>
Why is the alert not showing '1 - hi' and '2 - bye', instead its showing an empty value for the fmsg variable. Any suggestions? Here is a working example: http://jsfiddle.net/dtuce/1/
The jQuery#text method doesn't really seem to be prepared to gather text content from XML from what I could see. (I'd gladly take pointers, though)
text: function( text ) {
if ( jQuery.isFunction(text) ) {
return this.each(function(i) {
var self = jQuery( this );
self.text( text.call(this, i, self.text()) );
});
}
if ( typeof text !== "object" && text !== undefined ) {
return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
}
return jQuery.text( this );
}
There is -- as always -- a difference between the W3 adherent browsers and IE. The W3 compliant browsers expose the Node#textContent property, while IE exposes the Node#innerText property.
MDN documentation for Node#textContent
HTH,
FK

Categories