define([
    "jquery",
    "pat-parser",
    "pat-registry",
    "pat-utils",
    "pat-inject"
], function($, Parser, registry, utils, inject) {
    var parser = new Parser("modal");

    parser.add_argument("class");

    var modal = {
        name: "modal",
        jquery_plugin: true,
        // div's are turned into modals
        // links and forms inject modals
        trigger: "div.pat-modal, a.pat-modal, form.pat-modal",
        init: function($el, opts, trigger) {
            if ($el.length > 1) {
                // We enforce a one-to-one mapping between modal objects and
                // DOM elements, so here we recurse and instantiate a new modal
                // for each $el
                $el.each(function() {
                    modal.init($(this));
                });
            } else if ($el.length === 1) {
                var cfg = parser.parse($el, opts);
                if (trigger && trigger.type==="injection")
                    $.extend(cfg, parser.parse($(trigger.element), {}, false, false));
                if ($el.is("div"))
                    modal._init_div1($el, cfg);
                else
                    modal._init_inject1($el, cfg);
            }
        },

        _init_inject1: function($el, cfg) {
            var opts = {
                target: "#pat-modal",
                "class": "pat-modal" + (cfg["class"] ? " " + cfg["class"] : "")
            };
            // if $el is already inside a modal, do not detach #pat-modal,
            // because this would unnecessarily close the modal itself
            if (!$el.closest("#pat-modal")) {
                $("#pat-modal").detach();
            }
            inject.init($el, opts);
        },

        _init_div1: function($el, cfg) {
            var $header = $("<div class='header' />"),
                activeElement = document.activeElement;

            $("<button type='button' class='close-panel'>Close</button>").appendTo($header);

            // We cannot handle text nodes here
            $el.children(":last, :not(:first)")
                .wrapAll("<div class='panel-content' />");
            $(".panel-content", $el).before($header);
            $el.children(":first:not(.header)").prependTo($header);

            // Restore focus in case the active element was a child of $el and
            // the focus was lost during the wrapping.
            activeElement.focus();
            modal._init_handlers($el, cfg);
            modal.setPosition($el, cfg);
        },

        _init_handlers: function($el, cfg) {
            // event handlers remove modal - first arg to bind is ``this``
            $(document).on("click.pat-modal", ".close-panel", modal.destroy.bind($el, $el));
            // remove on ESC
            $(document).on("keyup.pat-modal", modal.destroy.bind($el, $el));

            $(window).on("resize.pat-modal-position",
                utils.debounce(modal.setPosition.bind(modal, $el, cfg), 400));
            $(document).on("pat-inject-content-loaded.pat-modal-position", "#pat-modal",
                utils.debounce(modal.setPosition.bind(modal, $el), 400));
            $(document).on("patterns-injected.pat-modal-position", "#pat-modal,div.pat-modal",
                utils.debounce(modal.setPosition.bind(modal, $el), 400));
        },

        setPosition: function($el, cfg) {
            var $tallest_child;
            var true_height = $el.outerHeight(); // the height of the highest element (after the function runs)
            $("*", $el).each(function () {
                if ($(this).outerHeight(true) > true_height) {
                    $tallest_child = $(this);
                    true_height = $tallest_child.outerHeight(true);
                }
            });
            if ($tallest_child) {
                // There is a child that's taller than $el. We need to make the
                // height the height of this child plus it's offset from the top
                // of $el.
                true_height += $tallest_child.offset().top - $el.offset().top;
            }
            // Maximum height is visible browser area minus modal padding
            var maxHeight = $(window).innerHeight() - ($el.outerHeight(true) - $el.outerHeight());
            if (maxHeight - true_height < 0) {
                $el.addClass("max-height").css("height", maxHeight);
            } else if (true_height !== $el.height()) {
                $el.removeClass("max-height").css("height", true_height);
            } else {
                return;
            }
            $el.css("top", ($(window).innerHeight() - $el.outerHeight(true)) / 2);

            // XXX: This is a hack. When you have a modal inside a
            // modal.max-height, the CSS of the outermost modal affects the
            // innermost .panel-body. By redrawing here, it's fixed.
            //
            // I think ideally the CSS needs to be fixed here, but I need to
            // discuss with Cornelis first.
            if ($el.parent().closest(".pat-modal").length > 0) {
                utils.redraw($el.find(".panel-body"));
            }
        },

        destroy: function($el, ev) {
            if (ev && ev.type === "keyup" && ev.which !== 27)
                return;
            $(document).off(".pat-modal");
            $el.remove();
        }
    };

    registry.register(modal);
    return modal;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab

@@ -210,16 +210,105 @@
 class%22);
+%0A    parser.add_argument(%22closing%22, %5B%22close-button%22%5D, %5B%22close-button%22, %22outside%22%5D, true);
 %0A%0A    va
@@ -1954,16 +1954,78 @@
 ement;%0A%0A
+            if (cfg.closing.indexOf(%22close-button%22)!==-1)%0A    
         
@@ -2101,32 +2101,32 @@
 endTo($header);%0A
-
 %0A            // 
@@ -2883,25 +2883,131 @@
     
-// remove on ESC%0A
+$(document).on(%22keyup.pat-modal%22, modal._onKeyUp.bind($el, $el));%0A            if (cfg.closing.indexOf(%22outside%22)!==-1)%0A    
     
@@ -3022,37 +3022,37 @@
 $(document).on(%22
-keyup
+click
 .pat-modal%22, mod
@@ -3046,39 +3046,55 @@
 t-modal%22, modal.
-destroy
+_onPossibleOutsideClick
 .bind($el, $el))
@@ -3565,32 +3565,286 @@
 ));%0A        %7D,%0A%0A
+        _onPossibleOutsideClick: function($el, ev) %7B%0A            if ($el.has(ev.target))%0A                modal.destroy($el);%0A        %7D,%0A%0A        _onKeyUp: function($el, ev) %7B%0A            if (ev.which===27)%0A                modal.destroy($el);%0A        %7D,%0A%0A
         setPosit
@@ -5670,101 +5670,11 @@
 ($el
-, ev) %7B%0A            if (ev && ev.type === %22keyup%22 && ev.which !== 27)%0A                return;
+) %7B
 %0A   

Add closing option (ticket #355)