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