How to stop users accidentally leaving a Wagtail edit page with JQuery

We had a request recently to implement a feature to stop users accidentally losing work while editing a page then forgetting to save. Especially now in Wagtail with features like StreamField, they can get pretty engrossed in their page layout design then forget to save, hit the ‘Back’ button my mistake etc.

Although I believe this functionality will be introduced into Wagtail itself soon, here is how to do it in a ‘quick and easy’ way before that time.

First off create a Javascript file in your app where ever you keep those, let’s call it leave_page.js (You can think of a better title name I’m sure)

Then add this code:


(function() {
(function($) {

window.onload = function() {

var submitted = false;

$(":submit").click(function(event){
submitted = true;
});

var del_button = $( "a:contains('Delete')" );
del_button.click(function(event){
submitted = true;
});

var unpublish_button = $( "a:contains('Unpublish')" );
unpublish_button.click(function(event){
submitted = true;
});

window.addEventListener("beforeunload", function (e) {

if (submitted == false){
var confirmationMessage = 'It looks like you have been editing something. ';
confirmationMessage += 'If you leave before saving, your changes will be lost.';

(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
}
else {
return undefined;
}

});
};

})(jQuery);

}).call(this);


The first part of the code sets a flag so we can tell if any of the Wagtail main actions have been pressed like Save,Publish, Delete etc, we don’t want to bother the user if the page is being unloaded by those actions. If someone clicks Publish then we assume that’s what they want.

The code finds the Wagtail specific buttons and gives them a click handler which sets the flag to true.

The last part adds an event listener for the page exiting ‘beforeunload’, so just before we can remind them with an alert that they might lose their work, as long as submitted isn’t true.

You’ll need to get this JS picked up by Wagtail so use the Wagtail Hook ‘insert_editor_js’, create a file called ‘wagtail_hooks.py’ if you don’t already have one, then register the new JS Code from that. For more info on how to do that visit http://docs.wagtail.io/en/latest/reference/hooks.html

Hope this helps, let me know if there are any mistakes or if there is a cleaner, nicer way to do it.

Add some blockquote buttons to Wagtail CMS’ WYSIWYG Editor

Wagtail is an amazing Django based open source CMS, find out more at http://wagtail.io/

Wagtail’s Editor is based on Hallo.js, we needed to add a normal blockquote button,and one with a class attribute for a pull out style quote.

There are two steps to doing this, firstly override the whitelister’s element rules (this stops certain tags from being saved in wagtail, if you don’t do this the Blockquote HTML tag gets stripped out. Secondly add the JS to introduce the custom buttons.

The editor icons are based on font awesome fonts, so it’s easy to pick whatever icon you want by changing the icon: ‘fa fa-stack-exchange’ value

To start add a wagtails_hooks.py file into the app folder of your wagtail project.

wagtail_hooks.py


from django.utils.safestring import mark_safe
from django.utils.html import format_html, format_html_join
from django.conf import settings
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.whitelist import attribute_rule, check_url, allow_without_attributes

def whitelister_element_rules():
    return {
        'a': attribute_rule({'href': check_url, 'target': True}),
        'blockquote': attribute_rule({'class': True}),
    }
hooks.register('construct_whitelister_element_rules', whitelister_element_rules)

def editor_js():
	js_files = [
		'yourapp/js/hallo-custombuttons.js',
	]
	js_includes = format_html_join('\n', '<script src="{0}{1}"></script>',
		((settings.STATIC_URL, filename) for filename in js_files)
	)

	return js_includes + format_html(
		"""
		<script>
			registerHalloPlugin('blockquotebutton');
      registerHalloPlugin('blockquotebuttonwithclass');
		</script>
		"""
	)

hooks.register('insert_editor_js', editor_js)

def editor_css():
	return format_html('<link rel="stylesheet" href="'+ settings.STATIC_URL + 'yourapp/css/vendor/font-awesome/css/font-awesome.min.css">')

hooks.register('insert_editor_css', editor_css)

Then add a JS file into your app’s static JS folder.

hallo_custombuttons.js


(function() {
    (function($) {
        return $.widget('IKS.blockquotebutton', {
            options: {
                uuid: '',
                editable: null
            },
            populateToolbar: function(toolbar) {
                var button, widget;

                widget = this;

                button = $('<span></span>');
                button.hallobutton({
                    uuid: this.options.uuid,
                    editable: this.options.editable,
                    label: 'Blockquote',
                    icon: 'fa fa-quote-left',
                    command: null
                });

                toolbar.append(button);

                button.on('click', function(event) {
                    return widget.options.editable.execute('formatBlock',
                                                           'blockquote');
                });
            }
        });
    })(jQuery);
}).call(this);

(function() {
  (function($) {
    return $.widget("IKS.blockquotebuttonwithclass", {
      options: {
        uuid: '',
        editable: null
      },
      populateToolbar: function(toolbar) {
        var button, widget;

        widget = this;
        button = $('<span></span>');
        button.hallobutton({
          uuid: this.options.uuid,
          editable: this.options.editable,
          label: 'Pull Out Quote',
          icon: 'fa fa-stack-exchange',
          command: null
        });
        toolbar.append(button);
        return button.on("click", function(event) {
          var insertionPoint, lastSelection;

          lastSelection = widget.options.editable.getSelection();
          insertionPoint = $(lastSelection.endContainer).parentsUntil('.richtext').last();
					var elem;
					elem = "<blockquote class='pullout'>" + lastSelection + "</blockquote>";

					var node = lastSelection.createContextualFragment(elem);

					lastSelection.deleteContents();
					lastSelection.insertNode(node);

					return widget.options.editable.element.trigger('change');
        });
      }
    });
  })(jQuery);

}).call(this);

There’s probably a more concise way to do this but it worked for me!

HTML5 Game Experiment

stc1I thought I would have a go at making a simple game using HTML5 and Javascript, also used the rendering engine pixi.js. The game is called ‘stab the crab’, it’s very simple but I just wanted to dip my toes in and have a go. It works on Desktop best in IE9/Chrome, bit slow in Firefox. Also works well on mobile in Safari/Chrome on the iPhone, and most Android browsers I’ve tried (though not all the sound works on mobile, as there isn’t a full implementation of HTML5). It was fun to make, I got to improve my Javascript skills a bit, and made some graphics which I hadn’t done for a while. Thanks to Chris from my band for providing the funny voices!

http://games.allthebeesaredead.com/ – Play it here.

stc2My next step is to get it working inside an Android and iOS app. First experiments with Android using a webView have proved to be not viable, it works but no sound, and runs way too slow. Apparently this is a problem with webView implementation and Javascript engines. Currently looking in to using CocoonJS to compile the HTML5 app for Android and iOs but there seem to be problems with pixi.js and CocoonJS working together.

I’m really keen on getting HTML5 interactive content to work in mobiles apps, as this will mean developing once but then utilizing across Desktop, phones and tablets.