ckeditor inline save/submit ckeditor inline save/submit ajax ajax

ckeditor inline save/submit


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:

  1. It converts any div tag with the attribute contenteditable = "true" to inline-editable.
  2. 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!


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!!