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.
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