fustrate-rails 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/fustrate-rails.rb +4 -0
- data/lib/fustrate/rails/engine.rb +14 -0
- data/lib/fustrate/rails/version.rb +6 -0
- data/vendor/assets/javascripts/awesomplete.js +402 -0
- data/vendor/assets/javascripts/fustrate.coffee +6 -0
- data/vendor/assets/javascripts/fustrate/_module.coffee +134 -0
- data/vendor/assets/javascripts/fustrate/components/_module.coffee +3 -0
- data/vendor/assets/javascripts/fustrate/components/alert_box.coffee +10 -0
- data/vendor/assets/javascripts/fustrate/components/autocomplete.coffee +161 -0
- data/vendor/assets/javascripts/fustrate/components/disclosure.coffee +12 -0
- data/vendor/assets/javascripts/fustrate/components/drop_zone.coffee +9 -0
- data/vendor/assets/javascripts/fustrate/components/dropdown.coffee +48 -0
- data/vendor/assets/javascripts/fustrate/components/file_picker.coffee +10 -0
- data/vendor/assets/javascripts/fustrate/components/flash.coffee +31 -0
- data/vendor/assets/javascripts/fustrate/components/modal.coffee +213 -0
- data/vendor/assets/javascripts/fustrate/components/pagination.coffee +84 -0
- data/vendor/assets/javascripts/fustrate/components/tabs.coffee +28 -0
- data/vendor/assets/javascripts/fustrate/components/tooltip.coffee +72 -0
- data/vendor/assets/javascripts/fustrate/generic_form.coffee +31 -0
- data/vendor/assets/javascripts/fustrate/generic_page.coffee +40 -0
- data/vendor/assets/javascripts/fustrate/generic_table.coffee +57 -0
- data/vendor/assets/javascripts/fustrate/listenable.coffee +25 -0
- data/vendor/assets/javascripts/fustrate/object.coffee +21 -0
- data/vendor/assets/javascripts/fustrate/record.coffee +23 -0
- data/vendor/assets/stylesheets/_fustrate.sass +7 -0
- data/vendor/assets/stylesheets/awesomplete.sass +75 -0
- data/vendor/assets/stylesheets/fustrate/_colors.sass +9 -0
- data/vendor/assets/stylesheets/fustrate/_settings.sass +20 -0
- data/vendor/assets/stylesheets/fustrate/components/_components.sass +36 -0
- data/vendor/assets/stylesheets/fustrate/components/_functions.sass +40 -0
- data/vendor/assets/stylesheets/fustrate/components/alerts.sass +78 -0
- data/vendor/assets/stylesheets/fustrate/components/buttons.sass +103 -0
- data/vendor/assets/stylesheets/fustrate/components/disclosures.sass +23 -0
- data/vendor/assets/stylesheets/fustrate/components/dropdowns.sass +31 -0
- data/vendor/assets/stylesheets/fustrate/components/flash.sass +33 -0
- data/vendor/assets/stylesheets/fustrate/components/forms.sass +188 -0
- data/vendor/assets/stylesheets/fustrate/components/grid.sass +204 -0
- data/vendor/assets/stylesheets/fustrate/components/labels.sass +63 -0
- data/vendor/assets/stylesheets/fustrate/components/modals.sass +119 -0
- data/vendor/assets/stylesheets/fustrate/components/pagination.sass +57 -0
- data/vendor/assets/stylesheets/fustrate/components/panels.sass +49 -0
- data/vendor/assets/stylesheets/fustrate/components/popovers.sass +15 -0
- data/vendor/assets/stylesheets/fustrate/components/tables.sass +58 -0
- data/vendor/assets/stylesheets/fustrate/components/tabs.sass +44 -0
- data/vendor/assets/stylesheets/fustrate/components/tooltips.sass +28 -0
- data/vendor/assets/stylesheets/fustrate/components/typography.sass +355 -0
- metadata +211 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
class Fustrate.Components.AlertBox extends Fustrate.Components.Base
|
2
|
+
@fadeSpeed: 300
|
3
|
+
|
4
|
+
@initialize: ->
|
5
|
+
$('.alert-box').on 'click', '.close', (e) ->
|
6
|
+
alert_box = $(e.currentTarget).closest('.alert-box')
|
7
|
+
|
8
|
+
alert_box.fadeOut @constructor.fadeSpeed, alert_box.remove
|
9
|
+
|
10
|
+
false
|
@@ -0,0 +1,161 @@
|
|
1
|
+
class Fustrate.Components.Autocomplete extends Fustrate.Components.Base
|
2
|
+
@types:
|
3
|
+
plain:
|
4
|
+
displayKey: 'value'
|
5
|
+
item: (object, userInput) -> "<li>#{@highlight object.value}</li>"
|
6
|
+
filter: (object, userInput) -> object.value.indexOf(userInput) >= 0
|
7
|
+
|
8
|
+
@initialize: ->
|
9
|
+
# Override the default sort
|
10
|
+
Awesomplete.SORT_BYLENGTH = ->
|
11
|
+
|
12
|
+
constructor: (@input, types) ->
|
13
|
+
if $.isArray types
|
14
|
+
types = {
|
15
|
+
plain:
|
16
|
+
list: types.map (value) -> { value: value }
|
17
|
+
}
|
18
|
+
|
19
|
+
@sources = for own type, options of types
|
20
|
+
$.extend({}, @constructor.types[type], options)
|
21
|
+
|
22
|
+
@sources = [@sources] if $.isPlainObject @sources
|
23
|
+
|
24
|
+
existing = @input.data('awesomplete')
|
25
|
+
|
26
|
+
if existing
|
27
|
+
existing.sources = @sources
|
28
|
+
return
|
29
|
+
|
30
|
+
@awesomplete = new Awesomplete(
|
31
|
+
@input[0]
|
32
|
+
minChars: 0
|
33
|
+
maxItems: 25
|
34
|
+
filter: -> true
|
35
|
+
item: (option, userInput) -> option # Items are pre-rendered
|
36
|
+
)
|
37
|
+
|
38
|
+
@input
|
39
|
+
.data 'awesomplete', @
|
40
|
+
.on 'awesomplete-highlight', @onHighlight
|
41
|
+
.on 'awesomplete-select', @onSelect
|
42
|
+
.on 'keyup', @constructor.debounce(@onKeyup)
|
43
|
+
.on 'focus', @onFocus
|
44
|
+
|
45
|
+
blanked: =>
|
46
|
+
return unless @input.val().trim() == ''
|
47
|
+
|
48
|
+
@awesomplete.close()
|
49
|
+
|
50
|
+
$("~ input:hidden##{@input.attr('id')}_id", @awesomplete.container)
|
51
|
+
.val null
|
52
|
+
$("~ input:hidden##{@input.attr('id')}_type", @awesomplete.container)
|
53
|
+
.val null
|
54
|
+
|
55
|
+
@input.trigger 'blanked.autocomplete'
|
56
|
+
|
57
|
+
onFocus: =>
|
58
|
+
@items = []
|
59
|
+
@value = @input.val().trim() ? ''
|
60
|
+
|
61
|
+
for source in @sources when source.list?.length
|
62
|
+
for datum in source.list when source.filter(datum, @value)
|
63
|
+
@items.push @createListItem(datum, source)
|
64
|
+
|
65
|
+
@awesomplete.list = @items
|
66
|
+
|
67
|
+
onHighlight: =>
|
68
|
+
item = $('+ ul li[aria-selected="true"]', @input)
|
69
|
+
|
70
|
+
return unless item[0]
|
71
|
+
|
72
|
+
item[0].scrollIntoView false
|
73
|
+
@replace item.data('datum')._displayValue
|
74
|
+
|
75
|
+
onSelect: (e) =>
|
76
|
+
# aria-selected isn't set on click
|
77
|
+
item = $(e.originalEvent.origin).closest('li')
|
78
|
+
datum = item.data('datum')
|
79
|
+
|
80
|
+
@replace datum._displayValue
|
81
|
+
@awesomplete.close()
|
82
|
+
|
83
|
+
$("~ input:hidden##{@input.attr('id')}_id", @awesomplete.container)
|
84
|
+
.val datum.id
|
85
|
+
$("~ input:hidden##{@input.attr('id')}_type", @awesomplete.container)
|
86
|
+
.val datum._type
|
87
|
+
|
88
|
+
@input.data(datum: datum).trigger('finished.autocomplete')
|
89
|
+
|
90
|
+
false
|
91
|
+
|
92
|
+
onKeyup: (e) =>
|
93
|
+
keyCode = e.which || e.keyCode
|
94
|
+
|
95
|
+
value = @input.val().trim()
|
96
|
+
|
97
|
+
return @blanked() if value == ''
|
98
|
+
|
99
|
+
# Ignore: Tab, Enter, Esc, Left, Up, Right, Down
|
100
|
+
return if keyCode in [9, 13, 27, 37, 38, 39, 40]
|
101
|
+
|
102
|
+
# Don't perform the same search twice in a row
|
103
|
+
return unless value != @value && value.length >= 2
|
104
|
+
|
105
|
+
@value = value
|
106
|
+
@items = []
|
107
|
+
|
108
|
+
for source in @sources
|
109
|
+
if source.url?
|
110
|
+
@performSearch(source)
|
111
|
+
else if source.list?
|
112
|
+
for datum in source.list when source.filter(datum, @value)
|
113
|
+
@items.push @createListItem(datum, source)
|
114
|
+
|
115
|
+
@awesomplete.list = @items
|
116
|
+
|
117
|
+
performSearch: (source) =>
|
118
|
+
$.get source.url(search: @value, commit: 1, format: 'json')
|
119
|
+
.done (response) =>
|
120
|
+
for datum in response
|
121
|
+
@items.push @createListItem(datum, source)
|
122
|
+
|
123
|
+
@awesomplete.list = @items
|
124
|
+
|
125
|
+
createListItem: (datum, source) ->
|
126
|
+
datum._displayValue = datum[source.displayKey]
|
127
|
+
datum._type = source.type
|
128
|
+
|
129
|
+
$ source.item.call(@, datum, @value)
|
130
|
+
.data datum: datum
|
131
|
+
.get(0)
|
132
|
+
|
133
|
+
highlight: (text) =>
|
134
|
+
return '' unless text
|
135
|
+
|
136
|
+
text.replace RegExp("(#{@value.split(/\s+/).join('|')})", 'gi'),
|
137
|
+
'<mark>$&</mark>'
|
138
|
+
|
139
|
+
replace: (text) =>
|
140
|
+
@awesomplete.replace(text)
|
141
|
+
|
142
|
+
@debounce: (func, milliseconds = 300, immediate = false) ->
|
143
|
+
timeout = null
|
144
|
+
|
145
|
+
(args...) ->
|
146
|
+
delayed = =>
|
147
|
+
func.apply(@, args) unless immediate
|
148
|
+
timeout = null
|
149
|
+
|
150
|
+
if timeout
|
151
|
+
clearTimeout(timeout)
|
152
|
+
else if immediate
|
153
|
+
func.apply(@, args)
|
154
|
+
|
155
|
+
timeout = setTimeout delayed, milliseconds
|
156
|
+
|
157
|
+
@addType: (name, func) =>
|
158
|
+
@types[name] = func
|
159
|
+
|
160
|
+
@addTypes: (types) ->
|
161
|
+
@addType(name, func) for own name, func of types
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Fustrate.Components.Disclosure extends Fustrate.Components.Base
|
2
|
+
@initialize: ->
|
3
|
+
$('body').on 'click', '.disclosure-title', (event) ->
|
4
|
+
disclosure = $(event.currentTarget).closest('.disclosure')
|
5
|
+
|
6
|
+
isOpen = disclosure.hasClass 'open'
|
7
|
+
|
8
|
+
disclosure
|
9
|
+
.toggleClass 'open'
|
10
|
+
.trigger "#{if isOpen then 'closed' else 'opened'}.disclosure"
|
11
|
+
|
12
|
+
false
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Fustrate.Components.DropZone extends Fustrate.Components.Base
|
2
|
+
constructor: (target, callback) ->
|
3
|
+
$(target)
|
4
|
+
.off '.drop_zone'
|
5
|
+
.on 'dragover.drop_zone dragenter.drop_zone', false
|
6
|
+
.on 'drop.drop_zone', (event) ->
|
7
|
+
callback event.originalEvent.dataTransfer.files
|
8
|
+
|
9
|
+
false
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Fustrate.Components.Dropdown extends Fustrate.Components.Base
|
2
|
+
@locked: false
|
3
|
+
|
4
|
+
@initialize: ->
|
5
|
+
@body = $ 'body'
|
6
|
+
|
7
|
+
@body.on 'click.dropdowns', '.has-dropdown', @open
|
8
|
+
|
9
|
+
@open: (event) =>
|
10
|
+
@hide()
|
11
|
+
|
12
|
+
button = $ event.currentTarget
|
13
|
+
dropdown = $ '+ .dropdown', button
|
14
|
+
|
15
|
+
@locked = true
|
16
|
+
|
17
|
+
if button.position().top > (@body.height() / 2)
|
18
|
+
top = button.position().top - dropdown.outerHeight() - 2
|
19
|
+
else
|
20
|
+
top = button.position().top + button.outerHeight() + 2
|
21
|
+
|
22
|
+
if button.position().left > (@body.width() / 2)
|
23
|
+
left = 'inherit'
|
24
|
+
right = @body.width() - button.position().left - button.outerWidth()
|
25
|
+
else
|
26
|
+
right = 'inherit'
|
27
|
+
left = button.position().left
|
28
|
+
|
29
|
+
@showDropdown dropdown, left: left, top: top, right: right
|
30
|
+
|
31
|
+
false
|
32
|
+
|
33
|
+
@showDropdown: (dropdown, css) ->
|
34
|
+
dropdown
|
35
|
+
.addClass 'visible'
|
36
|
+
.hide()
|
37
|
+
.css css
|
38
|
+
.fadeIn 200, =>
|
39
|
+
@locked = false
|
40
|
+
|
41
|
+
@body.one 'click', @hide
|
42
|
+
|
43
|
+
@hide: =>
|
44
|
+
return if @locked
|
45
|
+
|
46
|
+
$('.dropdown.visible')
|
47
|
+
.removeClass 'visible'
|
48
|
+
.fadeOut 200
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Fustrate.Components.Flash extends Fustrate.Components.Base
|
2
|
+
@fadeInSpeed: 500
|
3
|
+
@fadeOutSpeed: 2000
|
4
|
+
@displayTime: 4000
|
5
|
+
|
6
|
+
constructor: (message, {type, icon} = {}) ->
|
7
|
+
message = "#{Fustrate.icon(icon)} #{message}" if icon
|
8
|
+
|
9
|
+
bar = $ "<div class=\"flash #{type ? 'info'}\"></div>"
|
10
|
+
.html message
|
11
|
+
.hide()
|
12
|
+
.prependTo $('#flashes')
|
13
|
+
.fadeIn @constructor.fadeInSpeed
|
14
|
+
.delay @constructor.displayTime
|
15
|
+
.fadeOut @constructor.fadeOutSpeed, ->
|
16
|
+
bar.remove()
|
17
|
+
|
18
|
+
@initialize: ->
|
19
|
+
$('body').append '<div id="flashes">'
|
20
|
+
|
21
|
+
class @Error
|
22
|
+
constructor: (message, {icon} = {}) ->
|
23
|
+
new Fustrate.Components.Flash message, type: 'error', icon: icon
|
24
|
+
|
25
|
+
class @Info
|
26
|
+
constructor: (message, {icon} = {}) ->
|
27
|
+
new Fustrate.Components.Flash message, type: 'info', icon: icon
|
28
|
+
|
29
|
+
class @Success
|
30
|
+
constructor: (message, {icon} = {}) ->
|
31
|
+
new Fustrate.Components.Flash message, type: 'success', icon: icon
|
@@ -0,0 +1,213 @@
|
|
1
|
+
class Fustrate.Components.Modal extends Fustrate.Components.Base
|
2
|
+
@fadeSpeed: 250
|
3
|
+
|
4
|
+
@settings:
|
5
|
+
closeOnBackgroundClick: true
|
6
|
+
distanceFromTop: 25
|
7
|
+
appendTo: 'body'
|
8
|
+
css:
|
9
|
+
open:
|
10
|
+
opacity: 0
|
11
|
+
visibility: 'visible'
|
12
|
+
display: 'block'
|
13
|
+
close:
|
14
|
+
opacity: 1
|
15
|
+
visibility: 'hidden'
|
16
|
+
display: 'none'
|
17
|
+
_cachedHeight: undefined
|
18
|
+
|
19
|
+
constructor: ({title, content, size, settings}) ->
|
20
|
+
@modal = @constructor.createModal size: size
|
21
|
+
@settings = $.extend true, @constructor.settings, (settings ? {})
|
22
|
+
@settings.previousModal = $()
|
23
|
+
|
24
|
+
@setTitle title
|
25
|
+
@setContent content
|
26
|
+
|
27
|
+
@_reloadUIElements()
|
28
|
+
@addEventListeners()
|
29
|
+
@initialize()
|
30
|
+
|
31
|
+
super
|
32
|
+
|
33
|
+
initialize: ->
|
34
|
+
|
35
|
+
_reloadUIElements: =>
|
36
|
+
@fields = {}
|
37
|
+
@buttons = {}
|
38
|
+
|
39
|
+
$('[data-field]', @modal).each (index, element) =>
|
40
|
+
field = $ element
|
41
|
+
@fields[field.data('field')] = field
|
42
|
+
|
43
|
+
$('[data-button]', @modal).each (index, element) =>
|
44
|
+
button = $ element
|
45
|
+
@buttons[button.data('button')] = button
|
46
|
+
|
47
|
+
setTitle: (title) =>
|
48
|
+
$('.modal-title span', @modal).html title
|
49
|
+
|
50
|
+
setContent: (content) =>
|
51
|
+
$('.modal-content', @modal).html content
|
52
|
+
|
53
|
+
@settings._cachedHeight = undefined
|
54
|
+
|
55
|
+
@_reloadUIElements()
|
56
|
+
|
57
|
+
addEventListeners: =>
|
58
|
+
@modal
|
59
|
+
.off '.modal'
|
60
|
+
.on 'close.modal', @close
|
61
|
+
.on 'open.modal', @open
|
62
|
+
.on 'hide.modal', @hide
|
63
|
+
.on 'opened.modal', @focusFirstInput
|
64
|
+
.on 'click.modal', '.modal-close', @constructor.closeButtonClicked
|
65
|
+
|
66
|
+
# TODO: Re-enable when modals are fully converted
|
67
|
+
# .off '.modal'
|
68
|
+
$(document).on 'click.modal touchstart.modal',
|
69
|
+
'.modal-overlay',
|
70
|
+
@constructor.backgroundClicked
|
71
|
+
|
72
|
+
focusFirstInput: =>
|
73
|
+
# Focus requires a slight physical scroll on iOS 8.4
|
74
|
+
return true if /iPad|iPhone|iPod/g.test navigator.userAgent
|
75
|
+
|
76
|
+
$('input, select', @modal)
|
77
|
+
.filter(':visible:not(:disabled):not([readonly])')
|
78
|
+
.first()
|
79
|
+
.focus()
|
80
|
+
|
81
|
+
open: =>
|
82
|
+
return if @modal.hasClass('locked') || @modal.hasClass('open')
|
83
|
+
|
84
|
+
@modal.addClass('locked')
|
85
|
+
|
86
|
+
# If there is currently a modal being shown, store it and re-open it when
|
87
|
+
# this modal closes.
|
88
|
+
@settings.previousModal = $('.modal.open')
|
89
|
+
|
90
|
+
# These events only matter when the modal is visible
|
91
|
+
$('body')
|
92
|
+
.off 'keyup.modal'
|
93
|
+
.on 'keyup.modal', (e) =>
|
94
|
+
return if @modal.hasClass('locked') || e.which != 27
|
95
|
+
|
96
|
+
@close()
|
97
|
+
|
98
|
+
@modal.trigger 'opening.modal'
|
99
|
+
|
100
|
+
@_cacheHeight() if typeof @settings._cachedHeight == 'undefined'
|
101
|
+
|
102
|
+
if @settings.previousModal.length
|
103
|
+
@settings.previousModal.trigger('hide.modal')
|
104
|
+
else
|
105
|
+
# There are no open modals - show the background overlay
|
106
|
+
@constructor.toggleBackground true
|
107
|
+
|
108
|
+
css = @settings.css.open
|
109
|
+
# css.top = parseInt @modal.css('top'), 10
|
110
|
+
|
111
|
+
css.top = $(window).scrollTop() - @settings._cachedHeight + 'px'
|
112
|
+
|
113
|
+
end_css =
|
114
|
+
top: $(window).scrollTop() + @settings.distanceFromTop + 'px',
|
115
|
+
opacity: 1
|
116
|
+
|
117
|
+
setTimeout (=>
|
118
|
+
@modal
|
119
|
+
.css css
|
120
|
+
.addClass('open')
|
121
|
+
.animate end_css, 250, 'linear', =>
|
122
|
+
@modal.removeClass('locked').trigger('opened.modal')
|
123
|
+
), 125
|
124
|
+
|
125
|
+
close: (openPrevious = true) =>
|
126
|
+
return if @modal.hasClass('locked') || !@modal.hasClass('open')
|
127
|
+
|
128
|
+
@modal.addClass 'locked'
|
129
|
+
|
130
|
+
$('body').off 'keyup.modal'
|
131
|
+
|
132
|
+
unless @settings.previousModal.length && openPrevious
|
133
|
+
@constructor.toggleBackground(false)
|
134
|
+
|
135
|
+
end_css =
|
136
|
+
top: - $(window).scrollTop() - @settings._cachedHeight + 'px',
|
137
|
+
opacity: 0
|
138
|
+
|
139
|
+
setTimeout (=>
|
140
|
+
@modal
|
141
|
+
.animate end_css, 250, 'linear', =>
|
142
|
+
@modal
|
143
|
+
.css @settings.css.close
|
144
|
+
.removeClass 'locked'
|
145
|
+
.trigger 'closed.modal'
|
146
|
+
|
147
|
+
if openPrevious
|
148
|
+
@openPreviousModal()
|
149
|
+
else
|
150
|
+
@settings.previousModal = $()
|
151
|
+
|
152
|
+
.removeClass('open')
|
153
|
+
), 125
|
154
|
+
|
155
|
+
# Just hide the modal immediately and don't bother with an overlay
|
156
|
+
hide: =>
|
157
|
+
@modal.removeClass('open locked').css @settings.css.close
|
158
|
+
|
159
|
+
openPreviousModal: =>
|
160
|
+
@settings.previousModal.trigger 'open.modal'
|
161
|
+
|
162
|
+
@settings.previousModal = $()
|
163
|
+
|
164
|
+
_cacheHeight: =>
|
165
|
+
@settings._cachedHeight = @modal.show().height()
|
166
|
+
|
167
|
+
@modal.hide()
|
168
|
+
|
169
|
+
@createModal: ({size}) ->
|
170
|
+
$("""
|
171
|
+
<div class="modal #{size ? 'tiny'}">
|
172
|
+
<div class="modal-title">
|
173
|
+
<span></span>
|
174
|
+
<a href="#" class="modal-close">×</a>
|
175
|
+
</div>
|
176
|
+
<div class="modal-content"></div>
|
177
|
+
</div>""").appendTo(@settings.appendTo)
|
178
|
+
|
179
|
+
@toggleBackground: (visible = true) =>
|
180
|
+
@overlay = $ '<div class="modal-overlay">' unless @overlay
|
181
|
+
|
182
|
+
if visible
|
183
|
+
return if @overlay.is(':visible')
|
184
|
+
|
185
|
+
@overlay
|
186
|
+
.hide()
|
187
|
+
.appendTo('body')
|
188
|
+
.fadeIn @fadeSpeed
|
189
|
+
else
|
190
|
+
@overlay
|
191
|
+
.fadeOut @fadeSpeed, ->
|
192
|
+
$(@).detach()
|
193
|
+
|
194
|
+
@backgroundClicked: ->
|
195
|
+
modal = $ '.modal.open'
|
196
|
+
|
197
|
+
return if !modal || modal.hasClass('locked')
|
198
|
+
|
199
|
+
# Don't continue to close if we're not supposed to
|
200
|
+
return unless Fustrate.Components.Modal.settings.closeOnBackgroundClick
|
201
|
+
|
202
|
+
modal.trigger 'close.modal'
|
203
|
+
|
204
|
+
false
|
205
|
+
|
206
|
+
@closeButtonClicked: ->
|
207
|
+
modal = $ '.modal.open'
|
208
|
+
|
209
|
+
return if !modal || modal.hasClass('locked')
|
210
|
+
|
211
|
+
modal.trigger 'close.modal'
|
212
|
+
|
213
|
+
false
|