#TINYMCE: PAGELOAD FLICKERING V2

So a while back I wrote this post: #TinyMCE: Fixing Pageload Flickering

Well now I have found a problem with it.  When you load the page with the textarea hidden the plugins don’t seem to bind properly.  I had to come up with a new way to do the same thing.  Here is my solution.  Not ideal but seems to work fine so far.

Notice that the ‘display:none’ is now gone and the size is now set to a size so that the textarea is not visible on the page. Although, there is a small remnant left there. Not sure I like it but I’m not sure how to hide it.

@Html.TextAreaFor(m => m.ContentHtml, 
                       new { @class = "tinymce", maxlength = 5000, 
                       @style = "height:1px; width:1px; " })

Now once TinyMCE is properly initialized I manually set the iframe size to be the height and width I want.

$('textarea.tinymce').tinymce({
  script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
  theme: "modern",
  toolbar: "bold italic ",
  paste_auto_cleanup_on_paste: true,
  init_instance_callback: function (editor) {
     $(editor.getContainer()).find("iframe").css("height", "300px");
     $(editor.getContainer()).find("iframe").css("width", "100%");
  }
});

#TinyMCE: Style and Script Elements

We have begun using TinyMCE as the primary editor in our CMS system.  Some of the functionality is to store pages that are later rendered as html on websites.  This means that often our designers are adding custom javascript and css styles to pages.  In our most recent release we found that when we switched to TinyMCE we lost the functionality to add custom scripts and styles to the pages.  After a mad dash we found that adding the following code to the TinyMCE editor allows the user to enter <style> and <script> elements via the Source Code window.   TinyMCE does add comments around the code that is added but they do not affect the functionality.

TinyMCE Initalization Settings

//Allow html script, style, etc tags to work in the editors
extended_valid_elements: 'pre[*],script[*],style[*]', 
valid_children: "+body[style|script],pre[script|div|p|br|span|img|style|h1|h2|h3|h4|h5],*[*]",
valid_elements : '*[*]', 

TinyMCE Example Input

<script type="text/javascript">
$(function () {
    Console.log('test');
  });
</script>



<style>
h2 {
        color: #FF0000;
    }
</style>


TinyMCE Example Output

<script type="text/javascript">// <![CDATA[ $(function () { Console.log('test'); }); // ]]></script>


<style><!-- h2 { color: #FF0000; } --></style>


#tinymce and #xeditable

headlineI am working on a project with some fairly complex editable fields in popups requirements.  I started looking around for a library that would package up the popups so that I can use them without having to start from scratch.  I came across the x-editable library which works very smoothly and handles most of what I need it to.  One of the requirements is that the textarea in the popup need to be a tinymce editor.  Here is my solution to hooking them up.  As usual the code is fairly simple and elegant once it works, but getting it to work took far longer than I anticipated.

Html:

<div>
  <span>Headline:</span>
  <a href="#" id="puHeadline"><i class="fa fa-edit"></i></a>
  <div id="textGoesHere"></div>
</div>

JavaScript:

$(function () {
  //----------------------------------------------------------------
  // Demo popover edit with tinymce
  $.fn.editable.defaults.mode = 'popup'; //toggle `popup` / `inline` mode

  $('#puHeadline').editable({
    pk: 1,
    type: 'textarea',
    title: 'Headline:',
    placeholder: 'Enter your headline...',
    placement: 'right',
    showbuttons: 'bottom',
    inputclass: 'tinymce',
    validate: function(value) {
      if($.trim(value) == '') {
        return 'This field is required';
      }
    },
    display: function (value) {
      $('#textGoesHere').html(value);
    },
  });

  $('#puHeadline').click(function() {
    setupTinyMCE();
  });

  function setupTinyMCE() {
    // TinyMCE functionality
    $('.tinymce').tinymce({
      // Location of TinyMCE script
      script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
      // General options
      theme: "modern",
      plugins: "link code fullscreen fmgwordpaste",
      toolbar: "bold italic | alignleft aligncenter alignright | fullscreen",
      menubar: false,
      statusbar: false,
      content_css: "/areas/c2c/Content/css/editor.css",
      setup: function(editor) {
      },
      init_instance_callback: function(editor) {
        $('.mce-tinymce').show();
        $(editor.getContainer()).find(".mce-path").css("display", "none");
      },
    });
  }
});

#TinyMCE: Add Character Count

TinyMCE has a built in word counter but it does not have a way to count characters.  We have a requirement that limits the users to 5000 characters per textarea field.  This count is purely to keep the users from writing a book each time they enter data.  We only need to count the actual characters that the user sees, all html and hidden characters are ignored.

To do this I created a character count plugin.  It ended up pretty straight forward in the end.  I tried many different ways of pulling and counting the visible characters but many of them were pretty inaccurate.  This version is the closest I could get to the Microsoft Word character counts.

Factoids:

  • I started the plugin with a generic base similar to the existing TinyMCE word count plugin.
  • I layered on this Stack Overflow answer to decode the raw HTML in a way that gave me the most accurate character count.
  • I used this Stack Overflow answer to hide the the path in the status bar of the editor.
  • I also added a generic “Over 5000 characters” validation message that I turn on and off based on the validation done in the “on change” event.
  • The last piece was to validate the character count in the “on submit” event.
  • Thanks to the commenters who found some problems with this code!

TinyMCE Initialization



<pre>
    $('textarea.tinymce').tinymce({
      script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
      theme: "modern",
      plugins: "charactercount",
      toolbar: "bold italic | link | bullist numlist ",
      menubar: false,
      statusbar: true,
      setup: function (editor) {
        editor.on('change', function(e) {
          var count = this.plugins["charactercount"].getCount();
          if (count > 5000)
            $('#invalidContentHtml').show();
          else
            $('#invalidContentHtml').hide();
        });
      },
      init_instance_callback: function (editor) {
        $('.mce-tinymce').show('fast');
        $(editor.getContainer()).find(".mce-path").css("display", "none");
      }
    });
</pre>

Character Count Plugin:

tinymce.PluginManager.add('charactercount', function (editor) {
  var self = this;

  function update() {
    editor.theme.panel.find('#charactercount').text(['Characters: {0}', self.getCount()]);
  }

  editor.on('init', function () {
    var statusbar = editor.theme.panel && editor.theme.panel.find('#statusbar')[0];

    if (statusbar) {
      window.setTimeout(function () {
        statusbar.insert({
          type: 'label',
          name: 'charactercount',
          text: ['Characters: {0}', self.getCount()],
          classes: 'charactercount',
          disabled: editor.settings.readonly
        }, 0);

        editor.on('setcontent beforeaddundo', update);

        editor.on('keyup', function (e) {
            update();
        });
      }, 0);
    }
  });

  self.getCount = function () {
    var tx = editor.getContent({ format: 'raw' });
    var decoded = decodeHtml(tx);
    var decodedStripped = decoded.replace(/(<([^>]+)>)/ig, "").trim();
    var tc = decodedStripped.length;
    return tc;
  };

  function decodeHtml(html) {
    var txt = document.createElement("textarea");
    txt.innerHTML = html;
    return txt.value;
  }
});

On Submit Code:

var charcnt = tinyMCE.editors["{TextAreaId}"].plugins["charactercount"].getCount() <= 5000;

CSS Tweaks:

/* Optional: Adjust the positioning of the character count text. */
label.mce-charactercount {
margin: 2px 0 2px 2px;
padding: 8px;
}

/* Optional: Remove the html path code from the status bar. */
.mce-path {
    display: none !important;
}

** Updated on 10/28/15: Minor changes based on comments

#WebEssentials & Zen Coding

One of the plug-ins that I use the most in Visual Studio is Web Essentials.  It has a large set of features designed to make life easier for developers to get their jobs done.   Today I read the article by Visual Studio Time Saver – Faster HTML Coding by Susan Ibach where she talks about the Zen Coding feature in Web Essentials.  For as many years I have used Web Essentials this is not a feature that I am familiar with.  It’s incredible!  Here is a list of links to different places to get additional information on Web Essentials and tips on how to use Zen Coding.

As a bonus check out vstips in Twitter.  There some great suggestions in there for making Visual Studio easier to use.

Here is a link to the video Susan referenced that shows how to use the Zen Coding functionality.

#TinyMCE: Fixing Pageload Flickering

When my TinyMCE boxes load I find that there is brief moment when it flickers, the users don’t like that.   Looking closely I found that the flicker is a brief glimpse of the underlying HTML that is rendered before the TinyMCE box finishes loading and is displayed.  To fix it I do some fancy hide/show work.  Initially the page is loaded with the control hidden, then when it is done loading it shows it. Since I had the code already put together, I left in an example of a read-only version of the editor.

When the page is loading mark the textarea as hidden:

     <div class="form-group">
        @Html.Label("Content:") @Html.ValidationMessageFor(m => m.ContentHtml)
        @if (Model.IsReadOnly)
        {
          @Html.TextAreaFor(m => m.ContentHtml, 
                            new { @class = "tinymce-ro", @readonly = "readonly", 
                            @style = "height:300px; width:100%; display:none" })
        }
        else
        {
          @Html.TextAreaFor(m => m.ContentHtml, 
                            new { @class = "tinymce", maxlength = 5000, 
                            @style = "height:300px; width:100%; display:none" })
        }
      </div>

Then show it again once loading is complete:

    $('textarea.tinymce').tinymce({
      script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
      theme: "modern",
      toolbar: "bold italic ",
      paste_auto_cleanup_on_paste: true,
      init_instance_callback: function (editor) {
        $('.mce-tinymce').show('fast');
      }
    });

    $('textarea.tinymce-ro').tinymce({
      readonly: true,
      script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
      theme: "modern",
      toolbar: false,
      menubar: false,
      statusbar: false,
      init_instance_callback: function (editor) {
        if (document.getElementById(editor.id).style.display === 'none') {
          editor.getBody().style.backgroundColor = "#EBEBE4";
        }
        $('.mce-tinymce').show('fast');
      }
    });

UPDATE: I discovered that my custom plugins were not loading properly when I used this method. I came up with another version posted here – Pageload Flickering V2.

#TinyMCE: Plugin DDL with Popup

Recently I had a story asking for some advanced functionality for a new TinyMCE button. I broke down the requirements to the following.  I created a JSFiddle to show it working.

  • Custom TinyMCE drop down menu
  • When a menu item is selected it should insert the menu value into the editor at the cursor
  • Last item on the menu will show a popup
  • On the popup have a text box for entering custom text
  • When Insert is clicked insert the text into the editor at the cursor
Core plugin code – Javascript
$('#Insert').click(function () {
    var text = $('#added-token').val();
    tinyMCE.activeEditor.execCommand('mceInsertContent', false, &quot;{Added Text:&quot; + text + &quot;}&quot;);
    $('#token-modal').modal('hide');
    return false;
});
tinymce.PluginManager.add('tokens', function (editor, url) {
    editor.addButton('inserttoken', {
        title: &quot;Insert Token&quot;,
        type: 'menubutton',
        text: 'Tokens',
        icon: false,
        onselect: function (e) {
          if (e.control.settings.value != '{MenuItemPopup}') {
            editor.insertContent(e.control.settings.value);
          } else {
            $('#token-modal').modal({ show: true });
          }
        },
        menu: [
            { text: 'Menu Item 1', value: '{MenuItem1}' },
            { text: 'Menu Item 2', value: '{MenuItem2}' },
            { text: 'Menu Item 3', value: '{MenuItem3}' },
            { text: 'Menu Item 4', value: '{MenuItem4}' },                     
            { text: 'Menu Item with Popup', value: '{MenuItemPopup}' }                        
        ],
        onPostRender: function () {
            this.value('{MenuItem1}');
        }
    });    
});
tinymce.init({
    selector: &quot;textarea&quot;,
	width:      '100%',
	height:     270,
    plugins:    [ &quot;anchor link tokens&quot; ],
    statusbar:  false,
	menubar:    false,
    toolbar:    &quot;inserttoken anchor | alignleft aligncenter alignright alignjustify&quot;,
	rel_list:   [ { title: 'Lightbox', value: 'lightbox' } ]
});
Core plugin code – HTML
&lt;div id=&quot;token-modal&quot; class=&quot;modal hide fade&quot; &gt;
    &lt;div class=&quot;modal-header&quot;&gt;
        &lt;button type=&quot;button&quot; class=&quot;close&quot; data-dismiss=&quot;modal&quot; aria-hidden=&quot;true&quot;&gt;×&lt;/button&gt;
        &lt;h3 id=&quot;myModalLabel&quot;&gt;Modal header&lt;/h3&gt;
    &lt;/div&gt;
    &lt;div class=&quot;modal-body&quot;&gt;
        &lt;input type=&quot;text&quot; name=&quot;group&quot; id=&quot;added-token&quot;  /&gt;
    &lt;/div&gt;
    &lt;div class=&quot;modal-footer&quot;&gt;
        &lt;button data-dismiss=&quot;modal&quot;&gt;Close&lt;/button&gt;
        &lt;button id=&quot;Insert&quot;&gt;Insert&lt;/button&gt;
    &lt;/div&gt;