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,84 @@
1
+ class Fustrate.Components.Pagination extends Fustrate.Components.Base
2
+ constructor: ({@current_page, @total_pages, @total_entries, @per_page}) ->
3
+ @base = @constructor._getPreppedPaginationURL()
4
+
5
+ link: (text, page, options = {}) =>
6
+ Fustrate.linkTo(text, "#{@base}page=#{page}", options)
7
+
8
+ previousLink: =>
9
+ if @current_page > 1
10
+ return "
11
+ <li class=\"previous_page\">
12
+ #{@link('← Previous', @current_page - 1, rel: 'prev')}
13
+ </li>"
14
+
15
+ '<li class="previous_page unavailable"><a href="#">← Previous</a></li>'
16
+
17
+ nextLink: =>
18
+ if @current_page < @total_pages
19
+ return "
20
+ <li class=\"next_page\">
21
+ #{@link('Next →', @current_page + 1, rel: 'next')}
22
+ </li>"
23
+
24
+ '<li class="next_page unavailable"><a href="#">Next →</a></li>'
25
+
26
+ generate: =>
27
+ pages = []
28
+
29
+ if @total_pages > 1
30
+ pages = for i in @windowedPageNumbers()
31
+ if i == @current_page
32
+ "<li class=\"current\">#{Fustrate.linkTo(i, '#')}</li>"
33
+ else if i == 'gap'
34
+ '<li class="unavailable"><span class="gap">…</span></li>'
35
+ else
36
+ "<li>#{@link i, i}</li>"
37
+
38
+ pages.unshift @previousLink()
39
+ pages.push @nextLink()
40
+
41
+ $('<ul class="pagination">').html pages.join(' ')
42
+
43
+ windowedPageNumbers: =>
44
+ window_from = @current_page - 4
45
+ window_to = @current_page + 4
46
+
47
+ if window_to > @total_pages
48
+ window_from -= window_to - @total_pages
49
+ window_to = @total_pages
50
+
51
+ if window_from < 1
52
+ window_to += 1 - window_from
53
+ window_from = 1
54
+ window_to = @total_pages if window_to > @total_pages
55
+
56
+ middle = [window_from..window_to]
57
+
58
+ left = if 4 < middle[0] then [1, 2, 'gap'] else [1...middle[0]]
59
+
60
+ if @total_pages - 3 > middle.last()
61
+ right = [(@total_pages - 1)..@total_pages]
62
+ right.unshift 'gap'
63
+ else if middle.last() + 1 <= @total_pages
64
+ right = [(middle.last() + 1)..@total_pages]
65
+ else
66
+ right = []
67
+
68
+ left.concat middle, right
69
+
70
+ @getCurrentPage: ->
71
+ window.location.search.match(/[?&]page=(\d+)/)?[1] ? 1
72
+
73
+ # Just add 'page='
74
+ @_getPreppedPaginationURL: ->
75
+ search = window.location.search.replace(/[?&]page=\d+/, '')
76
+
77
+ search = if search[0] == '?'
78
+ "#{search}&"
79
+ else if search[0] == '&'
80
+ "?#{search[1...search.length]}&"
81
+ else
82
+ '?'
83
+
84
+ "#{window.location.pathname}#{search}"
@@ -0,0 +1,28 @@
1
+ class Fustrate.Components.Tabs extends Fustrate.Components.Base
2
+ constructor: (@tabs) ->
3
+ @tabs.on 'click', 'li > a', (e) =>
4
+ @activateTab $(e.currentTarget)
5
+
6
+ false
7
+
8
+ if window.location.hash
9
+ @activateTab $("li > a[href='#{window.location.hash}']", @tabs).first()
10
+ else if $('li > a.active', @tabs).length > 0
11
+ @activateTab $('li > a.active', @tabs).first()
12
+ else
13
+ @activateTab $('li > a', @tabs).first()
14
+
15
+ activateTab: (tab) =>
16
+ return unless tab
17
+
18
+ $('.active', @tabs).removeClass 'active'
19
+ tab.addClass 'active'
20
+
21
+ $("##{tab.attr('href').split('#')[1]}")
22
+ .addClass 'active'
23
+ .siblings()
24
+ .removeClass 'active'
25
+
26
+ @initialize: =>
27
+ $('ul.tabs').each (index, elem) =>
28
+ new @($ elem)
@@ -0,0 +1,72 @@
1
+ class Fustrate.Components.Tooltip extends Fustrate.Components.Base
2
+ @fadeSpeed: 100
3
+
4
+ constructor: (element, title) ->
5
+ @element = $ element
6
+ @active = false
7
+
8
+ @addEventListeners()
9
+
10
+ @element.attr('title', title) if title
11
+
12
+ addEventListeners: =>
13
+ @element
14
+ .off '.tooltip'
15
+ .on 'mouseenter.tooltip', @_show
16
+ .on 'mousemove.tooltip', @_move
17
+ .on 'mouseleave.tooltip', @_hide
18
+
19
+ setTitle: (title) ->
20
+ if @active
21
+ @tooltip.text title
22
+ else
23
+ @element.prop('title', title)
24
+
25
+ _move: (e) =>
26
+ @tooltip.css @_tooltipPosition(e) if @active
27
+
28
+ false
29
+
30
+ _show: (e) =>
31
+ return false if @active
32
+
33
+ title = @element.prop('title') ? ''
34
+
35
+ return false unless title.length > 0
36
+
37
+ @tooltip ?= $('<span class="tooltip">').hide()
38
+
39
+ @element.attr('title', '').removeAttr('title')
40
+
41
+ @active = true
42
+
43
+ @tooltip
44
+ .text title
45
+ .appendTo $('body')
46
+ .css @_tooltipPosition(e)
47
+ .fadeIn @constructor.fadeSpeed
48
+
49
+ false
50
+
51
+ _hide: (e) =>
52
+ # No use hiding something that doesn't exist.
53
+ if @tooltip
54
+ @element.attr 'title', @tooltip.text()
55
+ @active = false
56
+
57
+ @tooltip.fadeOut @constructor.fadeSpeed, @tooltip.detach
58
+
59
+ false
60
+
61
+ _tooltipPosition: (e) ->
62
+ top: "#{e.pageY + 15}px"
63
+ left: "#{e.pageX - 10}px"
64
+
65
+ @initialize: ->
66
+ $('[data-tooltip]').each (index, elem) ->
67
+ new Fustrate.Components.Tooltip elem
68
+
69
+ $.fn.extend
70
+ tooltip: (options) ->
71
+ @each (index, element) ->
72
+ new Fustrate.Components.Tooltip element
@@ -0,0 +1,31 @@
1
+ #= require './generic_page'
2
+
3
+ class Fustrate.GenericForm extends Fustrate.GenericPage
4
+ addEventListeners: =>
5
+ super
6
+
7
+ @root.on 'submit', @onSubmit
8
+
9
+ _reloadUIElements: =>
10
+ super
11
+
12
+ for domObject in $('[name][id]', @root)
13
+ element = $ domObject
14
+ name = element.prop 'name'
15
+
16
+ if captures = name.match /\[([a-z_]+)\]$/
17
+ @fields[captures[1]] = element
18
+ else
19
+ @fields[name] = element
20
+
21
+ validate: -> true
22
+
23
+ onSubmit: (e) =>
24
+ e.preventDefault()
25
+
26
+ unless @validate()
27
+ setTimeout (=> $.rails.enableFormElements(@root)), 100
28
+
29
+ return false
30
+
31
+ true
@@ -0,0 +1,40 @@
1
+ class Fustrate.GenericPage
2
+ constructor: (@root) ->
3
+ @_reloadUIElements()
4
+ @addEventListeners()
5
+ @initialize()
6
+
7
+ addEventListeners: ->
8
+
9
+ # Once the interface is loaded and the event listeners are active, run any
10
+ # other tasks.
11
+ initialize: ->
12
+
13
+ _reloadUIElements: =>
14
+ @fields = {}
15
+ @buttons = {}
16
+
17
+ $('[data-field]', @root).each (index, element) =>
18
+ field = $ element
19
+ @fields[field.data('field')] = field
20
+
21
+ $('[data-button]', @root).each (index, element) =>
22
+ button = $ element
23
+ @buttons[button.data('button')] = button
24
+
25
+ flashSuccess: (message, {icon} = {}) ->
26
+ new Fustrate.Components.Flash.Success(message, icon: icon)
27
+
28
+ flashError: (message, {icon} = {}) ->
29
+ new Fustrate.Components.Flash.Error(message, icon: icon)
30
+
31
+ flashInfo: (message, {icon} = {}) ->
32
+ new Fustrate.Components.Flash.Info(message, icon: icon)
33
+
34
+ setHeader: (text) ->
35
+ $('.header > span', @root).text text
36
+
37
+ # Calls all methods matching /refresh.+/
38
+ refresh: =>
39
+ for own name, func of @
40
+ func() if name.indexOf('refresh') == 0 && name != 'refresh'
@@ -0,0 +1,57 @@
1
+ class Fustrate.GenericTable extends Fustrate.GenericPage
2
+ @blankRow: null
3
+ table: null
4
+
5
+ initialize: =>
6
+ super
7
+
8
+ @reloadTable()
9
+
10
+ reloadTable: ->
11
+
12
+ sortRows: (rows, sortFunction = ->) ->
13
+ sorted = ([sortFunction(row), row] for row in rows)
14
+ sorted.sort (x, y) ->
15
+ if x[0] == y[0] then 0 else if x[0] > y[0] then 1 else -1
16
+ sorted.map (row) -> row[1]
17
+
18
+ createRow: (item) =>
19
+ @updateRow @constructor.blankRow.clone(), item
20
+
21
+ updateRow: (row, item) ->
22
+ row
23
+
24
+ reloadRows: (rows, {sort} = { sort: null }) =>
25
+ tbody = $ 'tbody', @table
26
+
27
+ $('tr.loading', tbody).hide()
28
+
29
+ if rows
30
+ $('tr:not(.no-records):not(.loading)', tbody).remove()
31
+
32
+ tbody.append if sort then @sortRows(rows, sort) else rows
33
+
34
+ @updated()
35
+
36
+ addRow: (row) =>
37
+ $('tbody', @table).append row
38
+ @updated()
39
+
40
+ removeRow: (row) =>
41
+ row.fadeOut =>
42
+ row.remove()
43
+ @updated()
44
+
45
+ updated: =>
46
+ $('tbody tr.no-records', @table)
47
+ .toggle $('tbody tr:not(.no-records):not(.loading)', @table).length < 1
48
+
49
+ getCheckedIds: =>
50
+ (item.value for item in $('td:first-child input:checked', @table))
51
+
52
+ # This should be fed a response from a JSON request for a paginated
53
+ # collection.
54
+ updatePagination: (response) =>
55
+ @pagination = new Fustrate.Components.Pagination response
56
+
57
+ $('.pagination', @root).replaceWith @pagination.generate()
@@ -0,0 +1,25 @@
1
+ class Fustrate.Listenable
2
+ constructor: ->
3
+ @listeners = {}
4
+
5
+ on: (eventNames, callback) =>
6
+ for eventName in eventNames.split(' ')
7
+ @listeners[eventName] = [] unless @listeners[eventName]
8
+ @listeners[eventName].push callback
9
+
10
+ @
11
+
12
+ off: (eventNames) =>
13
+ for eventName in eventNames.split(' ')
14
+ @listeners[eventName] = []
15
+
16
+ @
17
+
18
+ trigger: =>
19
+ [name, args...] = arguments
20
+
21
+ return unless name && @listeners[name]
22
+
23
+ event.apply(@, args) for event in @listeners[name]
24
+
25
+ @
@@ -0,0 +1,21 @@
1
+ class Fustrate.Object extends Fustrate.Listenable
2
+ constructor: (data) ->
3
+ @extractFromData data
4
+
5
+ super
6
+
7
+ # Simple extractor to assign root keys as properties in the current object.
8
+ # Formats a few common attributes as dates with moment.js
9
+ extractFromData: (data) =>
10
+ @[key] = value for key, value of data
11
+
12
+ @date = moment @date if @date
13
+ @created_at = moment @created_at if @created_at
14
+ @updated_at = moment @updated_at if @updated_at
15
+
16
+ # Instantiate a new object of type klass for each item in items
17
+ _createList: (items, klass, additional_attributes = {}) ->
18
+ for item in items
19
+ obj = new klass(item)
20
+ obj[key] = value for key, value of additional_attributes
21
+ obj
@@ -0,0 +1,23 @@
1
+ class Fustrate.Record extends Fustrate.Object
2
+ # Rails class name
3
+ @class: null
4
+
5
+ constructor: (data) ->
6
+ super
7
+
8
+ if typeof data is 'number' or typeof data is 'string'
9
+ # If the parameter was a number or string, it's likely the record ID
10
+ @id = parseInt(data, 10)
11
+ else
12
+ # Otherwise we were probably given a hash of attributes
13
+ @extractFromData data
14
+
15
+ reload: ->
16
+
17
+ save: ->
18
+
19
+ toObject: -> {}
20
+
21
+ update: (data) =>
22
+ @extractFromData data
23
+ @save()
@@ -0,0 +1,7 @@
1
+ @import "bourbon"
2
+ @import "font-awesome"
3
+ @import "awesomplete"
4
+
5
+ @import "fustrate/colors"
6
+ @import "fustrate/settings"
7
+ @import "fustrate/components/components"
@@ -0,0 +1,75 @@
1
+ /* 1795543d988d0fd9ca6237a5ac176f8e88d63990 */
2
+
3
+ [hidden]
4
+ display: none
5
+
6
+ .visually-hidden
7
+ position: absolute
8
+ clip: rect(0, 0, 0, 0)
9
+
10
+ div.awesomplete
11
+ display: block
12
+ position: relative
13
+
14
+ > input
15
+ display: block
16
+
17
+ > ul
18
+ position: absolute
19
+ left: 0
20
+ z-index: 9
21
+ min-width: 100%
22
+ box-sizing: border-box
23
+ list-style: none
24
+ padding: 0
25
+ border-radius: .3em
26
+ margin: .2em 0 0
27
+ background: hsla(0, 0%, 100%, .9)
28
+ background: linear-gradient(to bottom right, white, hsla(0, 0%, 100%, .8))
29
+ border: 1px solid rgba(0, 0, 0, .3)
30
+ box-shadow: .05em .2em .6em rgba(0, 0, 0, .2)
31
+ text-shadow: none
32
+ max-height: 30vh
33
+ overflow-x: scroll
34
+
35
+ &[hidden],
36
+ &:empty
37
+ display: none
38
+
39
+ > li
40
+ position: relative
41
+ padding: .2em .5em
42
+ cursor: pointer
43
+
44
+ &:not(:last-child)
45
+ border-bottom: 1px solid rgba(64, 64, 64, .3)
46
+
47
+ &:hover
48
+ background: hsl(200, 40%, 80%)
49
+ color: black
50
+
51
+ &[aria-selected="true"]
52
+ background: hsl(205, 40%, 40%)
53
+ color: white
54
+
55
+ mark
56
+ background: hsl(65, 100%, 50%)
57
+
58
+ li:hover &
59
+ background: hsl(68, 100%, 41%)
60
+
61
+ li[aria-selected="true"] &
62
+ background: hsl(86, 100%, 21%)
63
+ color: inherit
64
+
65
+ @supports (transform: scale(0))
66
+ div.awesomplete > ul
67
+ transition: .3s cubic-bezier(.4, .2, .5, 1.4)
68
+ transform-origin: 1.43em -.43em
69
+
70
+ &[hidden],
71
+ &:empty
72
+ opacity: 0
73
+ transform: scale(0)
74
+ display: block
75
+ transition-timing-function: ease