CKEditor: Use config.allowedContent/extraAllowedContent to stop tags being stripped

I was having an issue with certain tags, e.g. span and div, being stripped out of html by CKEditor. Whether or not I should be using these tags is another issue – in this particular situation it was useful to use span tags to give a class to snippets of text that would be styled in different ways, to indicate different types of text (printed, handwritten or engraved) on historical slides in CSlide.

It turns out that CKEditor (4.1 onwards) has the Advanced Content Filter (ACF), which is a highly configurable filter that allows only certain types of content to be entered into the editor, and removes any html tags that are not allowed by the configuration. The default behaviour (Automatic mode) results in all of the enabled editor features, e.g. styling buttons, formats, etc, adding their rules (i.e. the html associated with the feature) to the filtering rules to allow that type of content. The alternative to this is Custom mode, where the allowed content is defined in the config, using config.allowedContent – see the CKEditor guide on defining allowed content rules.

Turning off the ACF

I wouldn’t advise this (it is much better to configure the ACF), but if you really don’t want CKEditor to do any filtering, you can turn it off by adding the following line to config.js:

config.allowedContent = true;

extraAllowedContent

As well as fully defining the ACF rules using config.allowedContent, it is also possible to extend the current rules using config.extraAllowedContent, and this was the most straightforward solution to my issue. extraAllowedContent allows you to add rules to the current config, which is an easy way to extend, for example, the automatic mode configuration. In my case, I was able to allow span tags, and define a set of classes that these span tags would be allowed to have:

config.extraAllowedContent = 'span(engraved,printed,manuscript,slide_text,label_text)';

This setting means that any other class associated with span tags will be removed (though the span tag will remain). Using ‘span(*)’ allows span tags with any class.

Ajax form submission from a JQuery UI modal dialog in CakePHP

I just wanted to share with you two approaches to bringing up an add or edit form in a JQuery UI modal dialog and closing the dialog on a successful submit or keeping it open and displaying errors in case of validation or other save problems.

A non-Cake approach

Ingredients

CakePHP 2+
JQuery 1.9+
JQuery UI
JQuery Form Plugin

The Views

Main page

This is the page from which we want to launch our modal add or edit dialog:

<!-- overlayed element -->
<div id="dialogModal">
     <!-- the external content is loaded inside this tag -->
     <div class="contentWrap"></div>
</div>
...
<div class="actions">
    <ul>
        <li>
            <?php echo $this->Html->link(__('Add user', true), array("controller"=>"users", "action"=>"add"), array("class"=>"overlay", "title"=>"Add User"));
        </li>
    </ul>
</div>
...
<script>
$(document).ready(function() {
    //prepare the dialog
    $( "#dialogModal" ).dialog({
        autoOpen: false,
        show: {
            effect: "blind",
            duration: 500
            },
        hide: {
            effect: "blind",
            duration: 500
            },
        modal: true
        });
    //respond to click event on anything with 'overlay' class
    $(".overlay").click(function(event){
        event.preventDefault();
        $('#contentWrap').load($(this).attr("href"));  //load content from href of link
        $('#dialogModal').dialog('option', 'title', $(this).attr("title"));  //make dialog title that of link
        $('#dialogModal').dialog('open');  //open the dialog
        });
    });
</script>

Our add or edit form

This is shown in the dialog

echo $this->Form->create('User');
echo $this->Form->input('name');
echo $this->Form->end('Submit');
<script>
$('#UserAddForm').ajaxForm({ 
    target: '#contentWrap', 
    resetForm: false, 
    beforeSubmit: function() { 
        $('#contentWrap').html('Loading...'); 
        }, 
    success: function(response) { 
        if (response=="saved")) { 
            $('#dialogModal').dialog('close');  //close containing dialog    
            location.reload();  //if you want to reload parent page to show updated user
            } 
        } 
    });
</script>

Controller

For our add or edit action

function add() {
    ...
    if (!empty($this->request->data)) {
        $this->User->create();
        if ($this->User->save($this->request->data)){ 
            return "saved"; 
            } 
        }
    ...
    }

 

A more Cakey approach

With thanks to RXC on StackOverflow

Ingredients

CakePHP 2+
JQuery 1.9+
JQuery UI

The Views

Main page

This is the same as above

Our add or edit form

echo $this->Form->create('User');
echo $this->Form->input('name');
echo $this->Js->submit('Save', array(  //create 'ajax' save button
    'update' => '#contentWrap'  //id of DOM element to update with selector
    ));
if (false != $saved){ //will only be true if saved OK in controller from ajax save above
    echo "<script>
        $('#dialogModal').dialog('close');  //close containing dialog         
        location.reload();  //if you want to reload parent page to show updated user
    </script>";
    }
echo $this->Form->end();
echo $this->Js->writeBuffer(); //assuming this view is rendered without the default layout, make sure you write out the JS buffer at the bottom of the page

Controller

For our add or edit action

function add() {
    ...
    $this->set('saved', false); //false by default - controls closure of overlay in which this is opened
    if (!empty($this->request->data)) {
        $this->User->create();
        if ($this->User->save($this->request->data)){ 
            $this->set('saved', true); //only set true if data saves OK
            } 
        }
    ...
    }

Hope that’s helpful. Would be very interested in alternatives that have worked for you…and corrections to inevitable errors above.

Google Maps Autocomplete in Bootstrap Modal

I’ve just spent a while struggling to get a Google Maps API v3 Places Autocomplete search text box working in a Bootstrap JS Modal. It seemed as though the autocomplete wasn’t working at all, which I initially thought meant that I had messed up my JQuery selectors. However, it turns out that it the autocomplete was working (I could use the down arrow to toggle through the results), but the results were being shown behind the modal box. The z-index of the modal box is 1040 by default, whereas that of the .pac-container div that shows the autocomplete results is 1000 (defined inline).

Therefore, the solution (easy when you know how) is to change the z-index of the .pac-container div to greater than 1040, so that it appears on top of the modal. Since the z-index of .pac-container is defined inline, it is necessary to use !important to override this inline style with from an internal/external stylesheet:

div.pac-container {
   z-index: 1050 !important;
}

Moving/copying large MySQL databases between servers with or without phpMyAdmin

I don’t suppose I’m alone in restricting the majority of my interactions with MySQL to what you can do through the wonderful phpMyAdmin. However, one place where it really lets you down is when you are trying to move large databases between servers. Exporting doesn’t seem to cause any problems (with the 100MB or so that I’ve tried so far anyway) but importing is where things start to go wrong.

phpMyAdmin seems to default to a maximum file upload size of 8MB but this, and other settings, are actually set in php.ini. You can zip (or gzip, etc.) your exported SQL file to bring down its size and/or change:

post_max_size = 8M
upload_max_filesize = 2M

in php.ini (just make sure you’re editing the right one – you will often find more than one) to values appropriate for your file.

However, you may still run into problems with execution timeouts and out of memory errors. You can fiddle around trying to change these settings in php.ini:

max_execution_time = 30 (in seconds)
memory_limit = 128M

If this fails, or if you’re just more of a GUI kind of person, and before you take the desperate step of trying to import table by table (if you do go down this route, make sure you have exported with ‘Disable foreign key checks’ ticked with InnoDB tables, to prevent missing foreign key errors), take a look at the free, cross-platform MySQL Workbench (http://www.mysql.com/products/workbench/). We already use this as a great visual DB modelling tool which takes care of all the complexities of foreign key constraints but I have only just realised today that it’s also a great way to import large databases. Simply connect to your target MySQL DB and:

  1. Click ‘Data Import/Restore’ under ‘Management’ in the ‘Navigator’ pane;
  2. Choose ‘Import from Self-Contained File’ and browse to your sql file;
  3. Choose the pre-existing database name (from ‘Default Target Schema’) if you have already created it or allow it to create it for your if you have exported with the DB creation code in there.

It takes a while, and the progress bar doesn’t move ’til it’s finished but it does the job beautifully.

 

CKEditor: Custom format options

I wanted to just allow a single “Heading” paragraph format in CKEditor, rather than having multiple heading options. I found that it was possible to change the available options in editing “config.format_tags” in ckeditor/config.js. For example, the following would show just the paragraph (Normal) and h1 (Heading 1) format options:

config.format_tags = 'p;h1';

However, having “Heading 1” as the only heading option isn’t particularly satisfactory, so I wanted to rename it to something else. This requires defining a custom format type, as follows:

config.format_tags = 'p;Heading';
config.format_Heading = { element : 'h1', name: 'Heading' };

Each of the format_tags needs a corresponding format_(tagName) entry and the default ones are predefined (http://docs.ckeditor.com/source/plugin28.html#CKEDITOR-config-cfg-format_tags). Overwrite these defaults in the config.js file does not seem to work, so I had to define a new format type, ‘Heading’, using the h1 element and named ‘Heading’.

MSDLT Update May-Oct 2013

Teaching Excellence Project Award for mobile technology

New user-friendly interface for My Workspace Resources for Biomedical Sciences students in Sakai/WebLearn

Bringing Biochemistry TLTP software up to date

And progress with: online maths assessment; project choosing; clinical sign-off and the History of Medical Sciences website

Get your copy here: ETSGMSDLTNewsMaytoOct2013.

Google Maps API v3: Capturing viewport change – use “idle” not “bounds_changed”

When wanting to capture a change in the viewport (e.g. zoom, pan), when using the Google Maps API v3, the obvious event to listen for seems to be “bounds_changed”. However, when dragging the map to pan, this event is fired repeatedly, which, in our case, meant that there hundreds of requests being sent when a user panned.

The solution is to listen for the “idle” event instead, which is only fired when the user has stopped panning/zooming:

google.maps.event.addListener(map, 'idle', function() {
   //Do something when the user has stopped zooming/panning
});

Javascript: array.indexOf() fix for IE8 and below

Just a quick note on how to deal with another one of the IE/Javascript deficiencies that make our lives so much easier(!) – IE from versions 8 backwards doesn’t support the indexOf() function for arrays, e.g.

var myArray = ['Apple','Banana','Orange'];
alert(myArray.indexOf('Orange'));  //alerts '2', but not in IE <=8

Thankfully, there are easy ways to fix this:

  1. the JQuery $.inArray() function, e.g.
    alert($.inArray('Orange',myArray);
  2. Defining the indexOf function (before you call it!) if it doesn’t exist – taken from MDN
    if (!Array.prototype.indexOf) {
      Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
        'use strict';
        if (this == null) {
          throw new TypeError();
        }
        var n, k, t = Object(this),
            len = t.length >>> 0;
    
        if (len === 0) {
          return -1;
        }
        n = 0;
        if (arguments.length > 1) {
          n = Number(arguments[1]);
          if (n != n) { // shortcut for verifying if it's NaN
            n = 0;
          } else if (n != 0 && n != Infinity && n != -Infinity) {
            n = (n > 0 || -1) * Math.floor(Math.abs(n));
          }
        }
        if (n >= len) {
          return -1;
        }
        for (k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); k < len; k++) {
          if (k in t && t[k] === searchElement) {
            return k;
          }
        }
        return -1;
      };
    }

CakePHP/MySQL: Want a Boolean? Use TINYINT(1), not BIT(1)

I was recently creating some new MySQL tables that stored booleans, so I thought I would do things (what I presumed to be) properly and make them BIT(1) columns, rather than TINYINT(1). However, when I came to try to save values to this field using CakePHP, everything was saved as 1, even if the value in the data array was 0, false, “”, “0” or anything else ‘zero-ey’ or ‘falsey’.

It turns out that BIT fields are not supported by Cake, and so you should just use TINYINT(1) instead!

Furthermore, Cake will assume a TINYINT(1) field is intended as a boolean field, and will only allow you to assign it a value of 0 or 1, even though TINYINT(1) can store values from -128 to 127 (or 0 to 255 unsigned). If you try to save any value other than 0 or 1 to this field, it will be saved as 1 (but note that false, “” and an empty array will cause a save error).

Stringify an Object in Javascript

I just Googled (other search engines are available) variations of “javascript convert object to JSON”, assuming that it would be something that is completely straightforward and easy to do, either with basic Javascript or using JQuery, which we have loaded in most of our applications anyway. It turns out basic Javascript can’t do it, and nor can JQuery (although Jquery does have the parseJSON function for parsing a JSON string – jQuery.parseJSON( json )). Thanfully, there is a simple way to do it, but it’s not quite as simple as I’d hoped, if you want to fully support all browsers.

The Simple Way (won’t work in IE7 or below))

JSON.stringify(yourObject);

All ‘modern’ browsers (see exactly which ones here: http://caniuse.com/json, but basically it’s everything except IE7 and below) natively support the JSON object, which allows you use JSON.stringify(object) to convert and object to a JSON string, and JSON.parse(string) to go back the other way. So if you’re not bothered about supporting old versions of IE, you can do it this way.

The marginally more complicated way (for full cross-browser compatibility)

json2.js (available here: https://github.com/douglascrockford/JSON-js) gives you the same stringify and parse functions as above, and is pretty small when minified. It seems to be the best option out there if you want to support the poor souls forced to (I really hoping no-one is choosing to) use IE7 or below.