         .fadeOut @fadeSpeed, ->
           $(@).detach()
 
-  open: =>
-    return if @locked || @modal.hasClass 'open'
-
-    @locked = true
-
-    # If there is currently a modal being shown, store it and re-open it when
-    # this modal closes.
-    @previousModal = @constructor.openModal
-
-    # These events only matter when the modal is visible
-    $('body')
-      .off 'keyup.modal'
-      .on 'keyup.modal', (e) =>
-        return if @locked || e.which != 27
-
-        @close()
-
-    @modal.trigger 'opening.modal'
-
-    @_cacheHeight() if typeof @settings._cachedHeight == 'undefined'
-
-    if @previousModal
-      @previousModal.close()
-    else
-      # There are no open modals - show the background overlay
-      @constructor.toggleBackground true
-
-    css = @settings.css.open
-    # css.top = parseInt @modal.css('top'), 10
-
-    css.top = $(window).scrollTop() - @settings._cachedHeight + 'px'
-
-    end_css =
-      top: $(window).scrollTop() + @settings.distanceFromTop + 'px',
-      opacity: 1
-
-    setTimeout (=>
-      @modal
-        .css css
-        .addClass('open')
-        .animate end_css, 250, 'linear', =>
-          @locked = false
-          @modal.trigger 'opened.modal'
-          @constructor.openModal = @
-    ), 125
-
-  close: =>
-    return if @locked || !@modal.hasClass 'open'
-
-    @locked = true
-
-    $('body').off 'keyup.modal'
-
-    close_event = $.Event 'close.modal'
-    @modal.trigger close_event
-
-    return if close_event.isDefaultPrevented()
-
-    @constructor.toggleBackground(false) unless @previousModal
-
-    end_css =
-      top: - $(window).scrollTop() - @settings._cachedHeight + 'px',
-      opacity: 0
-
-    setTimeout (=>
-      @modal
-        .animate end_css, 250, 'linear', =>
-          @locked = false
-          @modal.css @settings.css.close
-          @modal.trigger 'closed.modal'
-          @constructor.openModal = undefined
-          @openPreviousModal()
-        .removeClass('open')
-    ), 125
-
-  openPreviousModal: =>
-    @previousModal?.open()
-
-    @previousModal = undefined
-
-  # If this modal hasn't been attached to a relatively positioned element,
-  # attach it to #content
-  attachToContent: =>
-    return if @modal.parent('#content').length > 0
-
-    # placeholder = @modal.wrap('<div style="display: none;" />').parent()
-
-    @modal.detach().appendTo '#content'
-
-  _cacheHeight: =>
-    @attachToContent()
-
-    @settings._cachedHeight = @modal.show().height()
-
-    @modal.hide()
+  @backgroundClicked: (e) ->
+    modal = $ '.modal.open'
+
+    return if !modal || modal.hasClass('locked')
+
+    # Don't continue to close if we're not supposed to
+    return unless Fustrate.Components.Modal.settings.closeOnBackgroundClick
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    modal.trigger 'close.modal'
+
+    false
+
+  @closeButtonClicked: (e) ->
+    modal = $ '.modal.open'
+
+    return if !modal || modal.hasClass('locked')
+
+    e.preventDefault()
+
+    modal.trigger 'close.modal'
+
+    false

class Fustrate.Components.Modal extends Fustrate.Components.Base
  @background: undefined
  @fadeSpeed: 250
  @openModal: undefined

  @settings:
    closeOnBackgroundClick: true


  constructor: ({title, content, size, settings}) ->
    @modal = @constructor.createModal size: size
    @settings = $.extend true, @constructor.settings, (settings ? {})
    @settings.previousModal = $()

    @setTitle title
    @setContent content

    @_reloadUIElements()
    @addEventListeners()
    @initialize()

    super

  initialize: ->

  _reloadUIElements: =>
    @fields = {}
    @buttons = {}

    $('[data-field]', @modal).each (index, element) =>
      field = $ element
      @fields[field.data('field')] = field

    $('[data-button]', @modal).each (index, element) =>
      button = $ element
      @buttons[button.data('button')] = button

  setTitle: (title) =>
    $('.modal-title span', @modal).html title

  setContent: (content) =>
    $('.modal-content', @modal).html content

    @settings._cachedHeight = undefined

    @_reloadUIElements()

  addEventListeners: =>
    @modal
      .off '.modal'
      .on 'close.modal', @close
      .on 'open.modal', @open
      .on 'hide.modal', @hide
      .on 'opened.modal', @focusFirstInput
      .on 'click.modal', '.modal-close', @constructor.closeButtonClicked

    # TODO: Re-enable when modals are fully converted
    #   .off '.modal'
    $(document).on 'click.modal touchstart.modal',
                   '.modal-overlay',
                   @constructor.backgroundClicked

  focusFirstInput: =>
    # Focus requires a slight physical scroll on iOS 8.4
    return true if /iPad|iPhone|iPod/g.test navigator.userAgent

    $('input, select', @modal)
      .filter(':visible:not(:disabled):not([readonly])')
      .first()
      .focus()

  open: =>
    return if @modal.hasClass('locked') || @modal.hasClass('open')

    @modal.addClass('locked')

    # If there is currently a modal being shown, store it and re-open it when
    # this modal closes.
    @settings.previousModal = $('.modal.open')

    # These events only matter when the modal is visible
    $('body')
      .off 'keyup.modal'
      .on 'keyup.modal', (e) =>
        return if @modal.hasClass('locked') || e.which != 27

        @close()

    @modal.trigger 'opening.modal'

    @_cacheHeight() if typeof @settings._cachedHeight == 'undefined'

    if @settings.previousModal.length
      @settings.previousModal.trigger('hide.modal')
    else
      # There are no open modals - show the background overlay
      @constructor.toggleBackground true

    css = @settings.css.open
    # css.top = parseInt @modal.css('top'), 10

    css.top = $(window).scrollTop() - @settings._cachedHeight + 'px'

    end_css =
      top: $(window).scrollTop() + @settings.distanceFromTop + 'px',
      opacity: 1

    setTimeout (=>
      @modal
        .css css
        .addClass('open')
        .animate end_css, 250, 'linear', =>
          @modal.removeClass('locked').trigger('opened.modal')
    ), 125

  close: =>
    return if @modal.hasClass('locked') || !@modal.hasClass('open')

    @modal.addClass 'locked'

    $('body').off 'keyup.modal'

    @constructor.toggleBackground(false) unless @settings.previousModal.length

    end_css =
      top: - $(window).scrollTop() - @settings._cachedHeight + 'px',
      opacity: 0

    setTimeout (=>
      @modal
        .animate end_css, 250, 'linear', =>
          @modal
            .css @settings.css.close
            .removeClass 'locked'
            .trigger 'closed.modal'
          @openPreviousModal()
        .removeClass('open')
    ), 125

  # Just hide the modal immediately and don't bother with an overlay
  hide: =>
    @modal.removeClass('open locked').css @settings.css.close

  openPreviousModal: =>
    @settings.previousModal.trigger 'open.modal'

    @settings.previousModal = $()

  _cacheHeight: =>
    @settings._cachedHeight = @modal.show().height()

    @modal.hide()

  @createModal: ({size}) ->
    $("""
      <div class="modal #{size ? 'tiny'}">
        <div class="modal-title">
          <span></span>
          <a href="#" class="modal-close">&#215;</a>
        </div>
        <div class="modal-content"></div>
      </div>""").appendTo('#content')

  @toggleBackground: (visible = true) =>
    @overlay = $ '<div class="modal-overlay">' unless @overlay
