I can't work out how to grab the edited data from a CKEditor instance and post it to a url.
I'm looking at something this:
http://nightly.ckeditor.com/3995/samples/inlineall.html
and I can't work out how the changes can be saved. Can I post the newly edited data to be posted to a PHP along with the ID of the element being edited?
Similarly to this:
editor.on('configLoaded', function(){
// do some stuff
});
I was hoping I could do something like this:
editor.on('clickAway', function(e){
id = e.id();
// do some ajax stuff
});
But I can't seem to find anything, anywhere.
Has anyone worked out how to do this?
Thank you.
I'm sure there are many ways to pull this off, but here's my solution. I'm using the Smarty Template Engine, but this technique should work with vanilla HTML too.
First off, here's an example of some HTML stored in my template file named "dog_fleas.tpl":
<script type="text/javascript" src="/js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/js/admin/mycms.js"></script>
<div>
<div id="flea-blurb" tpl="/templates/dog_fleas.tpl" contenteditable="true">
<h1>My Dog Has Fleas</h1>
<p>This text is editable via the CMS!</p>
</div>
<p>This text is not editable</p>
</div>
The javascript (mycms.js) to handle the inline editing is:
$(document).ready(function() {
CKEDITOR.disableAutoInline = true;
$("div[contenteditable='true']" ).each(function( index ) {
var content_id = $(this).attr('id');
var tpl = $(this).attr('tpl');
CKEDITOR.inline( content_id, {
on: {
blur: function( event ) {
var data = event.editor.getData();
var request = jQuery.ajax({
url: "/admin/cms-pages/inline-update",
type: "POST",
data: {
content : data,
content_id : content_id,
tpl : tpl
},
dataType: "html"
});
}
}
} );
});
});
The above code does a few things:
It converts any div tag with the attribute contenteditable = "true" to inline-editable.
After content is edited (on blur), the editable element id, tpl filename, and edited content are sent to the server via an ajax call.
The tpl attribute is necessary in my situation to identify the file being edited. The element id specifies which element was modified.
Although my example only contains one editable region, this code supports multiple editable regions in a single file.
On the server-side, here's my PHP code. I'm using a framework, so my $this->_POST() functions might look a little unusual, but hopefully you get the idea:
// Get the posted parameters
$new_content = $this->_POST('content');
$content_id = $this->_POST('content_id');
$tpl_filename = $this->_POST('tpl');
// Get the contents of the .tpl file to edit
$file_contents = file_get_contents(APPPATH . 'views' . $tpl_filename);
// create revision as a backup in case of emergency
$revised_filename = str_replace('/', '.', $tpl_filename);
$revised_filename = ltrim ($revised_filename, '.');
file_put_contents(APPPATH . 'views/templates/revisions/' . $revised_filename . '.' . time(), $file_contents);
// Prepare to match the DIV tag
// Credit to: http://stackoverflow.com/questions/5355452/using-a-regular-expression-to-match-a-div-block-having-a-specific-id
$re = '% # Match a DIV element having id="content".
<div\b # Start of outer DIV start tag.
[^>]*? # Lazily match up to id attrib.
\bid\s*+=\s*+ # id attribute name and =
([\'"]?+) # $1: Optional quote delimiter.
\b' . $content_id . '\b # specific ID to be matched.
(?(1)\1) # If open quote, match same closing quote
[^>]*+> # remaining outer DIV start tag.
( # $2: DIV contents. (may be called recursively!)
(?: # Non-capture group for DIV contents alternatives.
# DIV contents option 1: All non-DIV, non-comment stuff...
[^<]++ # One or more non-tag, non-comment characters.
# DIV contents option 2: Start of a non-DIV tag...
| < # Match a "<", but only if it
(?! # is not the beginning of either
/?div\b # a DIV start or end tag,
| !-- # or an HTML comment.
) # Ok, that < was not a DIV or comment.
# DIV contents Option 3: an HTML comment.
| <!--.*?--> # A non-SGML compliant HTML comment.
# DIV contents Option 4: a nested DIV element!
| <div\b[^>]*+> # Inner DIV element start tag.
(?2) # Recurse group 2 as a nested subroutine.
</div\s*> # Inner DIV element end tag.
)*+ # Zero or more of these contents alternatives.
) # End 2$: DIV contents.
</div\s*> # Outer DIV end tag.
%isx';
if (preg_match($re, $file_contents, $matches))
{
$content_to_replace = $matches[0];
$replacement_content = $content_to_replace;
// Replace the inner content of $replacement_content with $new_content
$replacement_content = preg_replace('/(<div(?:.*?)>)(?:.*)(<\/div>)/msi',"$1" . $new_content . "$2", $replacement_content);
// Now replace the content_to_replace with $replacement content in the HTML
$new_file_contents = str_replace($content_to_replace, $replacement_content, $file_contents);
// write out the new .tpl file
file_put_contents(APPPATH . 'views' . $tpl_filename, $new_file_contents);
}
The PHP code above is basically loading the HTML, locating the div tag with the proper id, then replacing the contents of that div tag with the content sent down via the ajax call. The HTML is then re-saved to the server. I also include some code to store backup revisions just in case things go terribly wrong.
I realize that regular expressions aren't always the best solution. In my case, it was difficult to use the PHP Dom Object Model because my HTML content isn't valid HTML. You might look into using the Dom Object Model instead if your system is simpler than mine.
I hope this helps!
I've found the following solution: How do I save inline editor contents on the server?
I'm using the blur event
Using above answer of #clone45 and modified it. The data will be saved busing Save button and only after some changes carried out were old and new data is compared.
Overridden existing save button of inline editor and included below only alerted part of #clone45's answer.
<script>
CKEDITOR.disableAutoInline = true;
$("div[contenteditable='true']").each(function(index) {
var content_id = $(this).attr('id');
var tpl = $(this).attr('tpl');
var oldData = null;
CKEDITOR.inline(content_id, {
on: {
instanceReady: function(event) {
//get current data and save in variable
oldData = event.editor.getData();
// overwrite the default save function
event.editor.addCommand("save", {
modes: {
wysiwyg: 1,
source: 1
},
exec: function() {
var data = event.editor.getData();
//check if any changes has been carried out
if (oldData !== data) {
oldData = data;
$.ajax({
type: 'POST',
url: 'process.php',
data: {
content: data,
content_id: content_id,
tpl: tpl
}
})
.done(function(data) {
alert('saved');
})
.fail(function() {
alert('something went wrong');
});
} else
alert('looks like nothing has been changed');
}
});
}
}
});
});
</script>
Hope this helps!!
Related
I am creating an app called Employee Management System using Rails 7. To add an employee, I have created a form. Here I have used nested-form-fields gem for adding contacts of employee. The problem is when the form is loaded the first time, when I want to add or remove the contact field, it redirects to the same form. But when I refresh the page, it starts working without any issue. Even fields added or removed get reflected in the database. It only creates this issue when create or update employee page is loaded first time.
What I have found is when I click the link to add employee and the form opens. The events are not loaded in html. As we can see in below image near body tag:
But after refresh, events are loaded:
I have seen js.coffee file in gem folder but that is too advanced for a beginner like me. The code there looks like this:
window.nested_form_fields or= {}
nested_form_fields.bind_nested_forms_links = () ->
$('body').off("click", '.add_nested_fields_link')
$('body').on 'click', '.add_nested_fields_link', (event, additional_data) ->
$link = $(this)
object_class = $link.data('object-class')
association_path = $link.data('association-path')
added_index = $(".nested_#{association_path}").length
$.event.trigger("fields_adding.nested_form_fields",{object_class: object_class, added_index: added_index, association_path: association_path, additional_data: additional_data});
if $link.data('scope')
$template = $("#{$link.data('scope')} ##{association_path}_template")
else
$template = $("##{association_path}_template")
target = $link.data('insert-into')
template_html = $template.html()
# insert association indexes
index_placeholder = "__#{association_path}_index__"
template_html = template_html.replace(new RegExp(index_placeholder,"g"), added_index)
# look for replacements in user defined code and substitute with the index
template_html = template_html.replace(new RegExp("__nested_field_for_replace_with_index__","g"), added_index)
# replace child template div tags with script tags to avoid form submission of templates
$parsed_template = $(template_html)
$child_templates = $parsed_template.closestChild('.form_template')
$child_templates.each () ->
$child = $(this)
$child.replaceWith($("<script id='#{$child.attr('id')}' type='text/html' />").html($child.html()))
if target?
$('#' + target).append($parsed_template)
else
$template.before( $parsed_template )
$parsed_template.trigger("fields_added.nested_form_fields", {object_class: object_class, added_index: added_index, association_path: association_path, event: event, additional_data: additional_data});
false
$('body').off("click", '.remove_nested_fields_link')
$('body').on 'click', '.remove_nested_fields_link', ->
$link = $(this)
return false unless $.rails == undefined || $.rails.allowAction($link)
return false if $link.attr('disabled')
object_class = $link.data('object-class')
delete_association_field_name = $link.data('delete-association-field-name')
removed_index = parseInt(delete_association_field_name.match('(\\d+\\]\\[_destroy])')[0].match('\\d+')[0])
$.event.trigger("fields_removing.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index });
$nested_fields_container = $link.parents(".nested_fields").first()
delete_field = $nested_fields_container.find("input[type='hidden'][name='#{delete_association_field_name}']")
if delete_field.length > 0
delete_field.val('1')
else
$nested_fields_container.before "<input type='hidden' name='#{delete_association_field_name}' value='1' />"
$nested_fields_container.hide()
$nested_fields_container.find('input[required]:hidden, select[required]:hidden, textarea[required]:hidden').removeAttr('required')
$nested_fields_container.trigger("fields_removed.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index});
false
$(document).on "page:change turbolinks:load", ->
nested_form_fields.bind_nested_forms_links()
jQuery ->
nested_form_fields.bind_nested_forms_links()
#
# * jquery.closestchild 0.1.1
# *
# * Author: Andrey Mikhaylov aka lolmaus
# * Email: lolmaus#gmail.com
# *
#
$.fn.closestChild = (selector) ->
$children = undefined
$results = undefined
$children = #children()
return $() if $children.length is 0
$results = $children.filter(selector)
if $results.length > 0
$results
else
$children.closestChild selector
Is there anything that can be changed to resolve this issue? Please help!!
After working ahead on my app for 2 days, same issue came when I added jquery to application.js for adding a checkbox that makes Permanent address and local address same. So I came to conclusion that the issue is not because of gem but some other reason. So I googled and found that turbolink is the main culprit. After searching in turbo-rails github repository found the issue in 'closed' category. By adding a simple line to my head section in application.html.erb, got read of the problem. Line is:
<meta name="turbo-visit-control" content="reload">
Though, there is a problem. After adding this line, flash messages disappeared because of reload. So I googled again to find out a a better way. Removed this line, and in application.js, changed
import "#hotwired/turbo-rails"
to
import { Turbo } from "#hotwired/turbo-rails"
Turbo.session.drive = false`
This I found in turbo-rails readme.md.
And flash messages reappeared with no need to refresh the page.
I have a list of widgets in wordpress. One of those widgets is a slider. As the slider needs the ID and other information, I am parsing a cdata array to the website using the wp_localize_script - one for every slider. So in my site, right after the footer element, i have the following code (example)
<script type='text/javascript'>
/* <![CDATA[ */
var sliderID = {"slider":"57d7b035941e9"};
var sliderID = {"slider":"57d7b03596340"};
/* ]]> */
</script>
Each number is a php "uniqid();"-number to set a unique ID for the slider. I am also loading a "sliderscripts.js" to the footer if the slider is active in the current site (in the footer, but below the CDATA). In the sliderscripts.js I now want to make a slider foreach ID. Using the ID is no problem like
$(document).ready(function () {
$('#'+sliderID.slider).owlCarousel({
... my options ...
});
Now the question:
How can I, using JS, loop that? If it would be
<script type='text/javascript'>
/* <![CDATA[ */
var sliderID = {"slider":"57d7b035941e9","slider":"57d7b03596340"};
/* ]]> */
</script>
there wouldnt be a problem. I need to foreach CDATA with var SliderID do something. Would be great if you help me!
thank you!
Even if #CBroe had a great idea, it was way too much work to start by 0. For others with the same problem, this is my solution:
1. Generate ID
$sid = uniqid(); // sid = slider-ID - FE: 123
will generate a unique ID for every slider
2. Generate Password
As I've been told not to use uniqid multiple times in a function (it should work but it seems to be a bad technique?) I am using the wordpress hook generate password to generate an ID-similar element.
$ID2 = wp_generate_password(20, false, false); // generates an password / ID with 20 digits. FE: 12345678912345678911
3. Add ID and data-attribut to the code
I now open the owlslider code like
<div id="slider_<?php echo $sid; ?>" class="owls" data-id2="<?php echo $ID2; ?>"
4. Localize Script
Now I am also localizing these values, so I can get them with JS
$slidersettings = array(
'slider' => $sid,
'url' => plugins_url(),
);
$sliderhandle = 'sl_'.$ID2; // will be sl_12345678912345678911
wp_localize_script( 'slider', $sliderhandle, $slidersettings );
5. Build Slider.
Last but not least I enqueue a custom script where I check for the IDs to set every slider like this
// check for all div's with the class "owls" AND id starts with "slider"
$('div.owls[id^="slider"]').each( function() {
var $div = $(this);
var ID2 = $div.data('ID2'); // Now I grab the ID2 / 2nd ID out of the data-attribute
var cdata = window['sl_' + ID2]; // this now tells me in which CDATA Array I have to look for my values
$install= $( '#slider_' + cdata.slider); // slider_123 <- 123 = uniqid.
$install.owlCarousel({
... standard owl stuff here...
})
thats it. Hope this will help someone else sometime.... all the best
I use ContentTools for my content editor/PHP CMS.
I'm trying to pass additional values from a "editable div" to the POST array (then it will be stored in a database).
The script uses Javascript to get the data and to make the call to my server side code.
Relevant JS code for the saving process:
// Collect the contents of each editable region into a FormData instance
payload = new FormData();
//payload.append('__page__', window.location.pathname);
payload.append('page_id', page_id); // Page ID from the Meta Property
for (name in regions) {
payload.append(name, regions[name]);
//payload.append('template', 'template');
}
// Send the updated content to the server to be saved
onStateChange = function(ev) {
// Check if the request is finished
if (ev.target.readyState == 4) {
editor.busy(false);
if (ev.target.status == '200') {
// Save was successful, notify the user with a flash
if (!passive) {
new ContentTools.FlashUI('ok');
}
} else {
// Save failed, notify the user with a flash
new ContentTools.FlashUI('no');
}
}
};
xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', onStateChange);
xhr.open('POST', 'update-page.php'); // Server side php file, which will catch the $_POST array.
xhr.send(payload);
Below you see an example editable div which will be in the POST array when page is saved after editing.
Note that the div has additional custom html tags 'data-template'.
<div id="content_4" class="content" data-template="5" data-editable data-name="1">
This is some example website text.
This is some other example website text.
</div>
I'm trying to pass along the values from "data-template".
What I've tried so far does not work:
// Added in: editor.addEventListener('saved', function (ev) {
var template = document.querySelector("div[data-template]"); // Get Template name from editable div
// Or
var template = document.getElementsByTagName("[data-template]")[0].getAttribute('[data-template]');
// Added in: the For In Loop
for (name in regions) {
payload.append(name, regions[name]);
payload.append('template', template); // added but does not work
}
Also, I don't want to use the div ID as value to be passed on.
I'm still trying other ways, but my JavaScript knowledge is not (yet!) as strong as my PHP knowledge.
Does someone know a solution to this issue?
There must be simple solution to get the value from the data-template, passed on to the POST (only the data-template value of the changed content in the div).
Right?
You can select the template data for each region by selecting the region's DOM element by it's editable name (e.g data-name):
for (var name in regions) {
// Select the region DOM element
var regionDOM = document.querySelector('[data-name="' + name + '"]');
// Get the `data-template` attribute
var tpl = regionDOM.getAttribute('data-template');
// Add the region HTML and template to the payload
payload.append(name, regions[name]);
payload.append('template_' + name, tpl);
}
The reason you get no value for for template at all in your code is that you're calling the getAttribute method with the CSS selector and not just the attribute name you want, e.g .getAttribute('[data-template]') should be .getAttribute('data-template').
The other difference in the code I've posted is that the template for each region is saved. If it will be the same template for all regions then you could modify the code to be:
for (var name in regions) {
// Select the region DOM element
var regionDOM = document.querySelector('[data-name="' + name + '"]');
// Get the `data-template` attribute
var tpl = regionDOM.getAttribute('data-template');
// Add the region HTML and template to the payload
payload.append(name, regions[name]);
}
// Set the template value for the payload to that of the last region
// found.
payload.append('template', tpl);
im trying to implement mention using (#) on a textarea that feeds from MySQL database.
this is the code im using below
var start=/#/ig; // # Match
var word=/#(\w+)/ig; //#abc Match
$("#newstatus").live("keyup",function()
{
var content=$(this).val() //Content Box Data
var go= content.match(word); //Content Matching #
var name= content.match(word); //Content Matching #abc
var dataString = 'searchword='+ name;
//If # available
if(go.length>0)
{
$("#msgbox").slideDown('show');
$("#display").slideUp('show');
$("#msgbox").html("Type the name of someone or something...");
//if #abc avalable
if(name.length>0)
{
$.ajax({
type: "POST",
url: siteurl + "ajax/mention", // Database name search
data: dataString,
cache: false,
success: function(data)
{
$("#msgbox").hide();
$("#display").html(data).show();
}
});
}
}
return false;
});
The problem happens when i add more text it keeps showing suggested box, i want to stop searching whenever i start a new sentence after the #WORD im using e.g.
#blackberry is my favorite smartphone !
i may use more than one mention in that textarea !
i want your help how do i do that whenever i use # then i have suggested list whenever i choose i can continue writing and may use another mention
Solution is:
$("#newstatus").live("keyup",function()
{
var content=$(this).val() //Content Box Data
var index= content.lastIndexOf("#"); //Content Matching #
var name= content.substr(n+1);; //Content Matching abc, If you want to get #abc then remove +1.
var dataString = 'searchword='+ name;
Explanation:
I am searching last position of # in a given string (using lastIndexOf()) and from that I am getting the string till end using substr().
You no need to use reg exp for this. Regular Exp are very costly, it will consume more resources, According to my knowledge better to ignore regular exp for a small operations.
I’m making a random sentence generator for my English class. I’m close but because of my limited php and javascript knowledge I need to ask for help. I’m not bad at reading the code, I just get stuck writing it.
I want to use explode to break up a string of comma seperated values. The string is a mix of English and Spanish, on the .txt file they would seperated like:
The book, El libro
The man, El hombre
The woman, La mujer
etc.
I would like to break these two values into an array and display them in separate places on my web page.
I`m going to use a random text generator script that I found, it’s working great with no problems. I just need to modify it using explode to read, separate the values into an array, and be able to display the separate values of the array.
<?php
/* File, where the random text/quotes are stored one per line */
$settings['text_from_file'] = 'quotes.txt';
/*
How to display the text?
0 = raw mode: print the text as it is, when using RanTex as an include
1 = Javascript mode: when using Javascript to display the quote
*/
$settings['display_type'] = 1;
/* Allow on-the-fly settings override? 0 = NO, 1 = YES */
$settings['allow_otf'] = 1;
// Override type?
if ($settings['allow_otf'] && isset($_GET['type']))
{
$type = intval($_GET['type']);
}
else
{
$type = $settings['display_type'];
}
// Get a list of all text options
if ($settings['text_from_file'])
{
$settings['quotes'] = file($settings['text_from_file']);
}
// If we have any text choose a random one, otherwise show 'No text to choose from'
if (count($settings['quotes']))
{
$txt = $settings['quotes'][array_rand($settings['quotes'])];
}
else
{
$txt = 'No text to choose from';
}
// Output the image according to the selected type
if ($type)
{
// New lines will break Javascript, remove any and replace them with <br />
$txt = nl2br(trim($txt));
$txt = str_replace(array("\n","\r"),'',$txt);
// Set the correct MIME type
header("Content-type: text/javascript");
// Print the Javascript code
echo 'document.write(\''.addslashes($txt).'\')';
}
else
{
echo $txt;
}
?>
The script that displays the result:
<script type="text/javascript" src="rantex.php?type=1"></script>
Can someone please help me modify the rantex.php file so that I can use explode to separate the different comma separated values, and use a different script to call them in different places on my web page?
Thank you, and please excuse my noobness.
The following seems unnecessary, since file() will have already removed new line characters:
// New lines will break Javascript, remove any and replace them with <br />
$txt = nl2br(trim($txt));
$txt = str_replace(array("\n","\r"),'',$txt);
To break your line, you may instead use:
list($english, $spanish) = explode(', ', trim($txt));
It seems you are trying to use PHP to serve a static page with some random sentences, right? So why not use PHP to serve valid JSON, and handle to display logic on the client?
Heres a quick implementation.
// Get the data from the text file
$source = file_get_contents('./quotes.txt', true);
// Build an array (break on every line break)
$sentences = explode("\n", $source);
// Filter out empty values (if there is any)
$filtered = array_filter($sentences, function($item) {
return $item !== "";
});
// Build a hashmap of the array
$pairs = array_map(function($item) {
return ['sentence' => $item];
}, $filtered);
// Encode the hashmap to JSON, and return this to the client.
$json = json_encode($pairs);
Now you can let the client handle the rest, with some basic JavaScript.
// Return a random sentence from your list.
var random = sentences[Math.floor(Math.random() * sentences.length)];
// Finally display it
random.sentence
[edit]
You can get the JSON data to client in many ways, but if you don't want to use something like Ajax, you could simply just dump the contents on your webpage, then use JavaScript to update the random sentence, from the global window object.
// Inside your php page
<p>English: <span id="english"></span></p>
<p>Spanish: <span id="spanish"></span></p>
<script>
var sentences = <?= json_encode($pairs); ?>;
var random = sentences[Math.floor(Math.random() * sentences.length)];
var elspa = document.getElementById('spanish');
var eleng = document.getElementById('english');
elspa.innerText = random.sentence.split(',')[1];
eleng.innerText = random.sentence.split(',')[0];
</script>
Ok, so I have this figured out, I take 0 credit because I paid someone to do it. Special thanks to #stormpat for sending me in the right direction, if not for him I wouldn't have looked at this from a JSON point of view.
The .PHP file is like so:
<?php
$f_contents = file('quotes.txt');
$line = trim($f_contents[rand(0, count($f_contents) - 1)]);
$data = explode(',', $line);
$data['eng'] = $data[0];
$data['esp'] = $data[1];
echo json_encode($data);
?>
On the .HTML page in the header:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
(function ($) {
$(function()
{
function load_random_data() {
$.get('random_line.php', function(data) {
var data = $.parseJSON(data);
$('#random_english').text(data.eng);
$('#random_spanish').text(data.esp);
});
}
load_random_data();
$('#get_random').click(function(e){
e.preventDefault();
load_random_data();
});
});
})(jQuery);
</script>
This splits the different variables into classes, so to call them into my html page I call them by their class, for instance I wanted to drop the variable into a table cell so I gave the individual td cell a class:
<td id="random_spanish"></td>
<td id="random_english"></td>
Plus as a bonus the coder threw in a nifty button to refresh the json classes:
<input type="button" value="Get random" id="get_random" />
So now I don`t have to have my students refresh the whole web page, they can just hit the button and refresh the random variables.
Thanks again everyone!