fustrate-rails 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|