fustrate-rails 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/lib/fustrate-rails.rb +4 -0
  3. data/lib/fustrate/rails/engine.rb +14 -0
  4. data/lib/fustrate/rails/version.rb +6 -0
  5. data/vendor/assets/javascripts/awesomplete.js +402 -0
  6. data/vendor/assets/javascripts/fustrate.coffee +6 -0
  7. data/vendor/assets/javascripts/fustrate/_module.coffee +134 -0
  8. data/vendor/assets/javascripts/fustrate/components/_module.coffee +3 -0
  9. data/vendor/assets/javascripts/fustrate/components/alert_box.coffee +10 -0
  10. data/vendor/assets/javascripts/fustrate/components/autocomplete.coffee +161 -0
  11. data/vendor/assets/javascripts/fustrate/components/disclosure.coffee +12 -0
  12. data/vendor/assets/javascripts/fustrate/components/drop_zone.coffee +9 -0
  13. data/vendor/assets/javascripts/fustrate/components/dropdown.coffee +48 -0
  14. data/vendor/assets/javascripts/fustrate/components/file_picker.coffee +10 -0
  15. data/vendor/assets/javascripts/fustrate/components/flash.coffee +31 -0
  16. data/vendor/assets/javascripts/fustrate/components/modal.coffee +213 -0
  17. data/vendor/assets/javascripts/fustrate/components/pagination.coffee +84 -0
  18. data/vendor/assets/javascripts/fustrate/components/tabs.coffee +28 -0
  19. data/vendor/assets/javascripts/fustrate/components/tooltip.coffee +72 -0
  20. data/vendor/assets/javascripts/fustrate/generic_form.coffee +31 -0
  21. data/vendor/assets/javascripts/fustrate/generic_page.coffee +40 -0
  22. data/vendor/assets/javascripts/fustrate/generic_table.coffee +57 -0
  23. data/vendor/assets/javascripts/fustrate/listenable.coffee +25 -0
  24. data/vendor/assets/javascripts/fustrate/object.coffee +21 -0
  25. data/vendor/assets/javascripts/fustrate/record.coffee +23 -0
  26. data/vendor/assets/stylesheets/_fustrate.sass +7 -0
  27. data/vendor/assets/stylesheets/awesomplete.sass +75 -0
  28. data/vendor/assets/stylesheets/fustrate/_colors.sass +9 -0
  29. data/vendor/assets/stylesheets/fustrate/_settings.sass +20 -0
  30. data/vendor/assets/stylesheets/fustrate/components/_components.sass +36 -0
  31. data/vendor/assets/stylesheets/fustrate/components/_functions.sass +40 -0
  32. data/vendor/assets/stylesheets/fustrate/components/alerts.sass +78 -0
  33. data/vendor/assets/stylesheets/fustrate/components/buttons.sass +103 -0
  34. data/vendor/assets/stylesheets/fustrate/components/disclosures.sass +23 -0
  35. data/vendor/assets/stylesheets/fustrate/components/dropdowns.sass +31 -0
  36. data/vendor/assets/stylesheets/fustrate/components/flash.sass +33 -0
  37. data/vendor/assets/stylesheets/fustrate/components/forms.sass +188 -0
  38. data/vendor/assets/stylesheets/fustrate/components/grid.sass +204 -0
  39. data/vendor/assets/stylesheets/fustrate/components/labels.sass +63 -0
  40. data/vendor/assets/stylesheets/fustrate/components/modals.sass +119 -0
  41. data/vendor/assets/stylesheets/fustrate/components/pagination.sass +57 -0
  42. data/vendor/assets/stylesheets/fustrate/components/panels.sass +49 -0
  43. data/vendor/assets/stylesheets/fustrate/components/popovers.sass +15 -0
  44. data/vendor/assets/stylesheets/fustrate/components/tables.sass +58 -0
  45. data/vendor/assets/stylesheets/fustrate/components/tabs.sass +44 -0
  46. data/vendor/assets/stylesheets/fustrate/components/tooltips.sass +28 -0
  47. data/vendor/assets/stylesheets/fustrate/components/typography.sass +355 -0
  48. metadata +211 -0
@@ -0,0 +1,3 @@
1
+ class Fustrate.Components
2
+ class @Base extends Fustrate.Listenable
3
+ @initialize: ->
@@ -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,10 @@
1
+ class Fustrate.Components.FilePicker extends Fustrate.Components.Base
2
+ constructor: (callback) ->
3
+ input = $ '<input type="file">'
4
+
5
+ input
6
+ .change ->
7
+ callback input[0].files
8
+
9
+ input.remove()
10
+ .click()
@@ -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">&#215;</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