fustrate-rails 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|