aerogel-admin 1.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +13 -0
- data/Rakefile +1 -0
- data/aerogel-admin.gemspec +31 -0
- data/app/helpers/admin.rb +54 -0
- data/app/helpers/decorators.rb +29 -0
- data/app/helpers/icons.rb +11 -0
- data/app/helpers/table_builder.rb +8 -0
- data/app/helpers/tabs_builder.rb +7 -0
- data/app/routes/admin.rb +37 -0
- data/app/routes/users.rb +71 -0
- data/app/routes/users_access.rb +54 -0
- data/app/routes/users_roles.rb +53 -0
- data/assets/fonts/aerogel-icons.css +28 -0
- data/assets/fonts/aerogel-icons.eot +0 -0
- data/assets/fonts/aerogel-icons.svg +11 -0
- data/assets/fonts/aerogel-icons.ttf +0 -0
- data/assets/fonts/aerogel-icons.woff +0 -0
- data/assets/javascripts/.gitkeep +0 -0
- data/assets/javascripts/controllers/admin-modal.js.coffee +7 -0
- data/assets/javascripts/controllers/admin-modal/admin-modal-form-buttons.js.coffee +56 -0
- data/assets/javascripts/controllers/admin.js.coffee +13 -0
- data/assets/javascripts/controllers/admin/selectize-inputs.js.coffee +7 -0
- data/assets/javascripts/controllers/admin/top-menu-shadow.js.coffee +9 -0
- data/assets/javascripts/utils/ajax-spinner.js.coffee +26 -0
- data/assets/javascripts/utils/ajax-watcher.js.coffee +18 -0
- data/assets/javascripts/utils/bootstrap-modal-reload.js.coffee +8 -0
- data/assets/javascripts/utils/form-data-async.js.coffee +27 -0
- data/assets/javascripts/utils/i18n.js.coffee +27 -0
- data/assets/javascripts/utils/on-future-elements.js.coffee +15 -0
- data/assets/stylesheets/admin/bootstrap-settings.css.scss +33 -0
- data/assets/stylesheets/admin/global.css.scss +61 -0
- data/assets/stylesheets/admin/styles/ajax-indicator.css.scss +26 -0
- data/assets/stylesheets/admin/styles/bootstrap-modal.css.scss +24 -0
- data/assets/stylesheets/admin/styles/language-selector.css.scss +5 -0
- data/assets/stylesheets/admin/styles/page-header.css.scss +7 -0
- data/assets/stylesheets/admin/styles/sticky-footer-navbar.css.scss +34 -0
- data/assets/stylesheets/admin/styles/table.css.scss +3 -0
- data/assets/stylesheets/admin/styles/top-menu.css.scss +3 -0
- data/assets/stylesheets/admin/utils/center-absolutely.css.scss +5 -0
- data/assets/stylesheets/controllers/admin.css.scss +14 -0
- data/assets/vendor/bootstrap-datetimepicker.css.scss +1 -0
- data/assets/vendor/bootstrap-datetimepicker.js.coffee +1 -0
- data/assets/vendor/bootstrap-datetimepicker/bootstrap-datetimepicker.min.css +5 -0
- data/assets/vendor/bootstrap-datetimepicker/bootstrap-datetimepicker.min.js +1 -0
- data/assets/vendor/bootstrap-datetimepicker/bootstrap-datetimepicker.ru.js +163 -0
- data/assets/vendor/moment.js.coffee +1 -0
- data/assets/vendor/momentjs/moment-with-langs.min.js +9 -0
- data/assets/vendor/selectize.css.scss +2 -0
- data/assets/vendor/selectize.js.coffee +1 -0
- data/assets/vendor/selectize/selectize.bootstrap3.css +385 -0
- data/assets/vendor/selectize/selectize.css +311 -0
- data/assets/vendor/selectize/selectize.default.css +381 -0
- data/assets/vendor/selectize/selectize.js +3345 -0
- data/assets/vendor/smart-list-table.css.scss +42 -0
- data/assets/vendor/smart-list-table.js.coffee +1 -0
- data/assets/vendor/smart-list-table/smart-list-table-row.js.coffee +63 -0
- data/assets/vendor/smart-list-table/smart-list-table.css.scss +54 -0
- data/assets/vendor/smart-list-table/smart-list-table.js.coffee +133 -0
- data/assets/vendor/smart-tree-table.css.scss +42 -0
- data/assets/vendor/smart-tree-table.js.coffee +1 -0
- data/assets/vendor/smart-tree-table/smart-tree-table-drag-n-drop.js.coffee +190 -0
- data/assets/vendor/smart-tree-table/smart-tree-table-row.js.coffee +78 -0
- data/assets/vendor/smart-tree-table/smart-tree-table.css.scss +54 -0
- data/assets/vendor/smart-tree-table/smart-tree-table.js.coffee +267 -0
- data/assets/vendor/spin.js +353 -0
- data/config/README.md +3 -0
- data/config/development/.keep +0 -0
- data/config/production/.keep +0 -0
- data/db/model/README.md +1 -0
- data/db/model/admin/user_new_form.rb +40 -0
- data/db/model/user.rb +26 -0
- data/db/seed/01_admin_roles.seed +8 -0
- data/db/seed/02_admin_access.seed +24 -0
- data/db/seed/development/.keep +0 -0
- data/db/seed/development/20_users.seed +45 -0
- data/db/seed/development/admin_users.seed +38 -0
- data/db/seed/production/.keep +0 -0
- data/lib/aerogel/admin.rb +25 -0
- data/lib/aerogel/admin/core.rb +14 -0
- data/lib/aerogel/admin/menu.rb +38 -0
- data/lib/aerogel/admin/table_builder.rb +100 -0
- data/lib/aerogel/admin/tabs_builder.rb +69 -0
- data/lib/aerogel/admin/version.rb +5 -0
- data/locales/actions.en.yml +27 -0
- data/locales/actions.ru.yml +28 -0
- data/locales/admin.en.yml +14 -0
- data/locales/admin.ru.yml +14 -0
- data/locales/models.en.yml +8 -0
- data/locales/models.ru.yml +8 -0
- data/locales/views.en.yml +46 -0
- data/locales/views.ru.yml +46 -0
- data/public/README.md +1 -0
- data/rake/README.md +3 -0
- data/views/admin/index.html.erb +3 -0
- data/views/admin/table_builder/standard/_table_column.html.erb +3 -0
- data/views/admin/table_builder/standard/_table_row.html.erb +7 -0
- data/views/admin/table_builder/standard/table.html.erb +10 -0
- data/views/admin/tabs_builder/standard/_tab.html.erb +3 -0
- data/views/admin/tabs_builder/standard/tabs.html.erb +3 -0
- data/views/admin/users/_tabs.html.erb +8 -0
- data/views/admin/users/access/delete.html.erb +12 -0
- data/views/admin/users/access/edit.html.erb +9 -0
- data/views/admin/users/access/index.html.erb +23 -0
- data/views/admin/users/access/new.html.erb +9 -0
- data/views/admin/users/delete.html.erb +12 -0
- data/views/admin/users/edit.html.erb +46 -0
- data/views/admin/users/index.html.erb +31 -0
- data/views/admin/users/new.html.erb +11 -0
- data/views/admin/users/roles/delete.html.erb +12 -0
- data/views/admin/users/roles/edit.html.erb +9 -0
- data/views/admin/users/roles/index.html.erb +21 -0
- data/views/admin/users/roles/new.html.erb +7 -0
- data/views/form_builder/standard/field_multiselect.erb +14 -0
- data/views/form_builder/standard/field_select.erb +17 -0
- data/views/layouts/admin.html.erb +63 -0
- data/views/layouts/admin/_ajax_indicator.html.erb +4 -0
- data/views/layouts/admin/_alerts.html.erb +22 -0
- data/views/layouts/admin/_menu.html.erb +50 -0
- data/views/layouts/admin/_menu_item.html.erb +5 -0
- data/views/layouts/admin/_modals.html.erb +8 -0
- data/views/layouts/admin/modal.html.erb +38 -0
- metadata +280 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
Function::property = (name) ->
|
3
|
+
Object.defineProperty @prototype, name,
|
4
|
+
get: -> @get_property name
|
5
|
+
set: (v) -> @set_property name, v
|
6
|
+
|
7
|
+
# An object representing tree table row.
|
8
|
+
#
|
9
|
+
#
|
10
|
+
class @SmartTreeTableRow
|
11
|
+
|
12
|
+
constructor: (@table, @el) ->
|
13
|
+
@id = @el.attr 'data-id'
|
14
|
+
@parent_id = @el.attr 'data-parent-id'
|
15
|
+
@parent_id = null if @parent_id == ''
|
16
|
+
@contents = @active_column().html()
|
17
|
+
@attributes = {}
|
18
|
+
for attr in @el.get(0).attributes
|
19
|
+
@attributes[attr.name] = attr.value
|
20
|
+
@_properties = {}
|
21
|
+
|
22
|
+
|
23
|
+
active_column: ->
|
24
|
+
@el.find(@table.settings.active_column_selector).first()
|
25
|
+
|
26
|
+
render: ->
|
27
|
+
@el.removeClass "#{@table.settings.selectedClass} branch leaf expanded collapsed"
|
28
|
+
@el.addClass @table.settings.selectedClass if @get_property 'selected'
|
29
|
+
@el.addClass if @get_property('branch') then 'branch' else 'leaf'
|
30
|
+
@el.addClass if @get_property('expanded') then 'expanded' else 'collapsed'
|
31
|
+
@active_column().html( "#{@_prefix()}#{@contents}")
|
32
|
+
|
33
|
+
show: ->
|
34
|
+
@el.show()
|
35
|
+
|
36
|
+
hide: ->
|
37
|
+
@el.hide()
|
38
|
+
|
39
|
+
# Returns attribute specified by +name+.
|
40
|
+
# 'data-...' attributes can be referenced by name without 'data-' prefix.
|
41
|
+
# Thus, 'data-id' can be accessed as:
|
42
|
+
# row.attr 'id'
|
43
|
+
#
|
44
|
+
attr: (name) ->
|
45
|
+
return @attributes[name] if @attributes[name]?
|
46
|
+
@attributes["data-#{name}"]
|
47
|
+
|
48
|
+
get_property: (name) ->
|
49
|
+
@_properties[name]
|
50
|
+
set_property: (name, v) ->
|
51
|
+
@_properties[name] = v
|
52
|
+
@render()
|
53
|
+
v
|
54
|
+
|
55
|
+
@property 'level'
|
56
|
+
@property 'expanded'
|
57
|
+
@property 'branch'
|
58
|
+
@property 'selected'
|
59
|
+
|
60
|
+
|
61
|
+
_indenter: (level) ->
|
62
|
+
px = level * @table.settings.indent
|
63
|
+
"<span class='indenter' style='padding-left:#{px}px'></span>"
|
64
|
+
|
65
|
+
_prefix: ->
|
66
|
+
p_indent = @_indenter @get_property 'level'
|
67
|
+
p_prefix = ''
|
68
|
+
s = {}
|
69
|
+
if @get_property 'branch'
|
70
|
+
s = @table.settings.prefix.branch
|
71
|
+
else
|
72
|
+
s = @table.settings.prefix.leaf
|
73
|
+
if @get_property 'expanded'
|
74
|
+
p_prefix = s.expanded
|
75
|
+
else
|
76
|
+
p_prefix = s.collapsed
|
77
|
+
"#{p_indent}#{p_prefix}"
|
78
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
.smart-tree-table tbody {
|
3
|
+
|
4
|
+
tr:hover {
|
5
|
+
background: $stt-hover-bg;
|
6
|
+
color: $stt-hover-fg;
|
7
|
+
cursor: $stt-hover-cursor;
|
8
|
+
}
|
9
|
+
tr.selected {
|
10
|
+
background: $stt-selected-bg;
|
11
|
+
color: $stt-selected-fg;
|
12
|
+
}
|
13
|
+
|
14
|
+
tr td span.indenter {
|
15
|
+
display: inline-block;
|
16
|
+
// margin: 0;
|
17
|
+
}
|
18
|
+
|
19
|
+
tr.drag-helper {
|
20
|
+
background: $stt-drag-helper-bg;
|
21
|
+
box-shadow: $stt-drag-helper-shadow;
|
22
|
+
border: $stt-drag-helper-border;
|
23
|
+
z-index: $stt-drag-helper-zindex;
|
24
|
+
}
|
25
|
+
|
26
|
+
tr.drag-over {
|
27
|
+
background: $stt-drag-over-bg;
|
28
|
+
color: $stt-drag-over-fg;
|
29
|
+
}
|
30
|
+
|
31
|
+
tr.drag-source {
|
32
|
+
background: $stt-drag-source-bg;
|
33
|
+
color: $stt-drag-source-fg;
|
34
|
+
}
|
35
|
+
|
36
|
+
.drop-target-overlay {
|
37
|
+
// border: 1px solid green;
|
38
|
+
}
|
39
|
+
.drop-target-overlay.before {
|
40
|
+
border-style: $stt-drop-target-overlay-before-border-style;
|
41
|
+
border-width: $stt-drop-target-overlay-before-border-width;
|
42
|
+
border-color: $stt-drop-target-overlay-before-border-color;
|
43
|
+
}
|
44
|
+
.drop-target-overlay.into {
|
45
|
+
border-style: $stt-drop-target-overlay-into-border-style;
|
46
|
+
border-width: $stt-drop-target-overlay-into-border-width;
|
47
|
+
border-color: $stt-drop-target-overlay-into-border-color;
|
48
|
+
}
|
49
|
+
.drop-target-overlay.after {
|
50
|
+
border-style: $stt-drop-target-overlay-after-border-style;
|
51
|
+
border-width: $stt-drop-target-overlay-after-border-width;
|
52
|
+
border-color: $stt-drop-target-overlay-after-border-color;
|
53
|
+
}
|
54
|
+
}
|
@@ -0,0 +1,267 @@
|
|
1
|
+
#= require ./smart-tree-table-row
|
2
|
+
#= require ./smart-tree-table-drag-n-drop
|
3
|
+
#
|
4
|
+
debug = false
|
5
|
+
log = (msg) ->
|
6
|
+
console?.log "** smart-tree-table: #{msg}" if debug
|
7
|
+
error = (msg) ->
|
8
|
+
console?.error "** smart-tree-table: #{msg}" if debug
|
9
|
+
throw new Error msg
|
10
|
+
|
11
|
+
class @SmartTreeTable
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
# Default settings
|
16
|
+
@_default_settings:
|
17
|
+
column: 1
|
18
|
+
indent: 20
|
19
|
+
addClass: 'smart-tree-table'
|
20
|
+
selectedClass: "selected"
|
21
|
+
hoverClass: null # or class to add on mouseenter/mouseleave
|
22
|
+
prefix:
|
23
|
+
branch:
|
24
|
+
expanded: "- "
|
25
|
+
collapsed: "+ "
|
26
|
+
leaf:
|
27
|
+
expanded: ""
|
28
|
+
collapsed: ""
|
29
|
+
dragAndDrop:
|
30
|
+
enabled: false
|
31
|
+
handle: null # or selector to drag-n-drop handle
|
32
|
+
helper: null # or function returning helper element
|
33
|
+
helperClass: 'drag-helper'
|
34
|
+
helperOpacity: 0.85
|
35
|
+
dragOverClass: 'drag-over'
|
36
|
+
dragSourceClass: 'drag-source'
|
37
|
+
dropTarget:
|
38
|
+
beforePx: 7
|
39
|
+
afterPx: 10
|
40
|
+
overlay:
|
41
|
+
class: "drop-target-overlay"
|
42
|
+
before: '' # contents for 'before' insert mode
|
43
|
+
into: '' # contents for 'into' insert mode
|
44
|
+
after: '' # contents for 'after' insert mode
|
45
|
+
debug: false
|
46
|
+
on_select: null
|
47
|
+
on_tree_change: null
|
48
|
+
|
49
|
+
# Creates a SmartTreeTable object using an HTML table specified by +el+ selector.
|
50
|
+
#
|
51
|
+
constructor: (el, options = {}) ->
|
52
|
+
unless $(el).length > 0
|
53
|
+
error "No matching elements specified by selector '#{el}'"
|
54
|
+
@settings = $.extend true, SmartTreeTable._default_settings, options
|
55
|
+
@settings.active_column_selector = "td:nth-child(#{@settings.column})"
|
56
|
+
@table = $(el).first()
|
57
|
+
@table.addClass @settings.addClass if @settings.addClass?
|
58
|
+
@init_table_rows()
|
59
|
+
@bind_event_listeners()
|
60
|
+
@selected_row = null
|
61
|
+
@drag_and_drop = null
|
62
|
+
$(el).disableSelection()
|
63
|
+
# SmartTreeTable object ready
|
64
|
+
#
|
65
|
+
@enable_drag_and_drop() if @settings.dragAndDrop.enabled
|
66
|
+
log "created SmartTreeTable: #{el}"
|
67
|
+
|
68
|
+
|
69
|
+
#
|
70
|
+
# private methods
|
71
|
+
#
|
72
|
+
|
73
|
+
# Returns table rows as list.
|
74
|
+
#
|
75
|
+
table_html_rows: ->
|
76
|
+
@table.find('tbody tr')
|
77
|
+
|
78
|
+
# Returns table row ids as list.
|
79
|
+
#
|
80
|
+
table_html_row_ids: ->
|
81
|
+
@id_of(row) for row in @table_html_rows()
|
82
|
+
|
83
|
+
# Returns Rows as list, ordered as the table.
|
84
|
+
#
|
85
|
+
rows_list: ->
|
86
|
+
@rows[id] for id in @table_html_row_ids()
|
87
|
+
|
88
|
+
# Returns list of children of parent with +id+.
|
89
|
+
#
|
90
|
+
children_of: (_id) ->
|
91
|
+
# log "children_of(#{_id}) #{typeof @rows}"
|
92
|
+
row for row in @rows_list() when row.parent_id == _id
|
93
|
+
# row for id, row of @rows when row.parent_id == _id
|
94
|
+
|
95
|
+
# Returns +id+ of a given row HTML element.
|
96
|
+
#
|
97
|
+
id_of: (el) ->
|
98
|
+
$(el).attr 'data-id'
|
99
|
+
|
100
|
+
# initializes table rows
|
101
|
+
#
|
102
|
+
init_table_rows: ->
|
103
|
+
@rows = {}
|
104
|
+
for row in @table_html_rows()
|
105
|
+
row_obj = new SmartTreeTableRow @, $(row)
|
106
|
+
row_obj.expanded = true
|
107
|
+
@rows[row_obj.id] = row_obj
|
108
|
+
|
109
|
+
@update_table_rows()
|
110
|
+
for row in @rows_list()
|
111
|
+
@collapse row.id
|
112
|
+
# log "row: #{row.id} -> #{row.parent_id} (level:#{row.level}): #{row.contents}"
|
113
|
+
|
114
|
+
|
115
|
+
# Updates table rows.
|
116
|
+
# Sets level, branch/leaf properties.
|
117
|
+
#
|
118
|
+
update_table_rows: ->
|
119
|
+
for row in @rows_list()
|
120
|
+
if row.parent_id?
|
121
|
+
row.level = @rows[row.parent_id].level + 1
|
122
|
+
else
|
123
|
+
row.level = 0
|
124
|
+
children = @children_of( row.id )
|
125
|
+
if children.length > 0
|
126
|
+
row.branch = true
|
127
|
+
else
|
128
|
+
row.branch = false
|
129
|
+
# log "row: #{row.id}, branch:#{row.branch}, children: #{children.length}"
|
130
|
+
|
131
|
+
|
132
|
+
# Collapses row with given +id+.
|
133
|
+
#
|
134
|
+
collapse: (id) ->
|
135
|
+
for row in @children_of(id)
|
136
|
+
@collapse row.id
|
137
|
+
row.hide()
|
138
|
+
@rows[id].expanded = false
|
139
|
+
|
140
|
+
# Expands row with given +id+.
|
141
|
+
#
|
142
|
+
expand: (id) ->
|
143
|
+
for row in @children_of(id)
|
144
|
+
row.show()
|
145
|
+
@rows[id].expanded = true
|
146
|
+
|
147
|
+
# Toggles collapsed/expanded row with +id+
|
148
|
+
#
|
149
|
+
toggle: (id) ->
|
150
|
+
if @rows[id].expanded
|
151
|
+
@collapse id
|
152
|
+
else
|
153
|
+
@expand id
|
154
|
+
|
155
|
+
# Selects row with +id+.
|
156
|
+
#
|
157
|
+
select: (id, process_callbacks = true ) ->
|
158
|
+
@selected_row.selected = false if @selected_row?
|
159
|
+
@selected_row = null
|
160
|
+
@selected_row = @rows[id] if id?
|
161
|
+
@selected_row.selected = true if @selected_row?
|
162
|
+
# if parent is collapsed, expand all until first expanded ancestor
|
163
|
+
if @selected_row? && @selected_row.parent_id?
|
164
|
+
parent_id = @selected_row.parent_id
|
165
|
+
while parent_id? && not @rows[parent_id].expanded
|
166
|
+
@expand parent_id
|
167
|
+
parent_id = @rows[parent_id].parent_id
|
168
|
+
|
169
|
+
if @settings.on_select? && process_callbacks
|
170
|
+
selected_id = if @selected_row? then @selected_row.id else null
|
171
|
+
selected_el = if @selected_row? then @selected_row.el else null
|
172
|
+
@settings.on_select selected_id, @selected_row, selected_el
|
173
|
+
|
174
|
+
|
175
|
+
# Binds event listeners on rows.
|
176
|
+
#
|
177
|
+
bind_event_listeners: ->
|
178
|
+
@table.find('tbody').on 'click', 'tr', (e) =>
|
179
|
+
id = @id_of $(e.currentTarget)
|
180
|
+
@select id
|
181
|
+
|
182
|
+
@table.find('tbody').on 'click', "tr #{@settings.active_column_selector}", (e) =>
|
183
|
+
id = @id_of $(e.currentTarget).closest('tr')
|
184
|
+
@toggle id
|
185
|
+
|
186
|
+
# this line of code below miraculously prevents event from
|
187
|
+
# not being propagated further, to "on 'click', 'tr'"
|
188
|
+
# (Chrome 33.0, jQuery 2.0.3)
|
189
|
+
tmp = "#{e.isPropagationStopped()}"
|
190
|
+
# log "column click: e stopped:#{e.isPropagationStopped()}"
|
191
|
+
# console.log e
|
192
|
+
|
193
|
+
if @settings.hoverClass?
|
194
|
+
@table.find('tbody').on 'mouseenter', 'tr', (e) =>
|
195
|
+
$(e.currentTarget).addClass @settings.hoverClass
|
196
|
+
@table.find('tbody').on 'mouseleave', 'tr', (e) =>
|
197
|
+
$(e.currentTarget).removeClass @settings.hoverClass
|
198
|
+
|
199
|
+
# Enables drag-n-drop for the table.
|
200
|
+
#
|
201
|
+
enable_drag_and_drop: =>
|
202
|
+
@drag_and_drop = new SmartTreeTableDragAndDrop @
|
203
|
+
|
204
|
+
# Disables drag-n-drop.
|
205
|
+
#
|
206
|
+
disable_drag_and_drop: ->
|
207
|
+
@table_html_rows().draggable 'destroy'
|
208
|
+
log "drag-n-drop disabled"
|
209
|
+
|
210
|
+
|
211
|
+
# Returns last element of a branch starting at +id+.
|
212
|
+
#
|
213
|
+
get_branch_last_element: (id) ->
|
214
|
+
children = @children_of id
|
215
|
+
return id unless children? && children.length > 0
|
216
|
+
@get_branch_last_element children[children.length-1].id # last of children
|
217
|
+
|
218
|
+
# Moves tree branch/leaf to another node
|
219
|
+
#
|
220
|
+
move_tree_node: (from_id, to_id, insert_mode) ->
|
221
|
+
source = @rows[from_id]
|
222
|
+
target = @rows[to_id]
|
223
|
+
|
224
|
+
# move around HTML elements
|
225
|
+
if insert_mode == 'before'
|
226
|
+
target.el.before( source.el )
|
227
|
+
else # after -- moves node after last element of target branch
|
228
|
+
new_target_id = @get_branch_last_element to_id
|
229
|
+
log "move_tree_node: after, to_id=#{to_id} new_target_id=#{new_target_id}"
|
230
|
+
target = @rows[new_target_id]
|
231
|
+
target.el.after( source.el )
|
232
|
+
@move_tree_node_children from_id
|
233
|
+
|
234
|
+
# update data structures
|
235
|
+
if insert_mode == 'into'
|
236
|
+
new_parent_id = to_id
|
237
|
+
else # before & after
|
238
|
+
new_parent_id = @rows[to_id].parent_id
|
239
|
+
console?.log "move_tree_node: new parent: #{new_parent_id}"
|
240
|
+
if new_parent_id?
|
241
|
+
source.parent_id = new_parent_id
|
242
|
+
source.el.attr 'data-parent-id', new_parent_id
|
243
|
+
else
|
244
|
+
source.parent_id = null
|
245
|
+
source.el.removeAttr 'data-parent-id'
|
246
|
+
@update_table_rows()
|
247
|
+
|
248
|
+
# finishing touches
|
249
|
+
@expand to_id if insert_mode == 'into'
|
250
|
+
if @settings.on_tree_change?
|
251
|
+
@settings.on_tree_change()
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
# Moves tree node children in place.
|
256
|
+
#
|
257
|
+
move_tree_node_children: (parent_id, last_element = null) ->
|
258
|
+
parent = @rows[parent_id]
|
259
|
+
last_element = parent.el unless last_element?
|
260
|
+
for row in @children_of parent_id
|
261
|
+
last_element.after row.el
|
262
|
+
last_element = row.el
|
263
|
+
last_element = @move_tree_node_children row.id, last_element
|
264
|
+
return last_element
|
265
|
+
|
266
|
+
$ ->
|
267
|
+
log "initialized"
|
@@ -0,0 +1,353 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2011-2013 Felix Gnass
|
3
|
+
* Licensed under the MIT license
|
4
|
+
*/
|
5
|
+
(function(root, factory) {
|
6
|
+
|
7
|
+
/* CommonJS */
|
8
|
+
if (typeof exports == 'object') module.exports = factory()
|
9
|
+
|
10
|
+
/* AMD module */
|
11
|
+
else if (typeof define == 'function' && define.amd) define(factory)
|
12
|
+
|
13
|
+
/* Browser global */
|
14
|
+
else root.Spinner = factory()
|
15
|
+
}
|
16
|
+
(this, function() {
|
17
|
+
"use strict";
|
18
|
+
|
19
|
+
var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
|
20
|
+
, animations = {} /* Animation rules keyed by their name */
|
21
|
+
, useCssAnimations /* Whether to use CSS animations or setTimeout */
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Utility function to create elements. If no tag name is given,
|
25
|
+
* a DIV is created. Optionally properties can be passed.
|
26
|
+
*/
|
27
|
+
function createEl(tag, prop) {
|
28
|
+
var el = document.createElement(tag || 'div')
|
29
|
+
, n
|
30
|
+
|
31
|
+
for(n in prop) el[n] = prop[n]
|
32
|
+
return el
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Appends children and returns the parent.
|
37
|
+
*/
|
38
|
+
function ins(parent /* child1, child2, ...*/) {
|
39
|
+
for (var i=1, n=arguments.length; i<n; i++)
|
40
|
+
parent.appendChild(arguments[i])
|
41
|
+
|
42
|
+
return parent
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Insert a new stylesheet to hold the @keyframe or VML rules.
|
47
|
+
*/
|
48
|
+
var sheet = (function() {
|
49
|
+
var el = createEl('style', {type : 'text/css'})
|
50
|
+
ins(document.getElementsByTagName('head')[0], el)
|
51
|
+
return el.sheet || el.styleSheet
|
52
|
+
}())
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Creates an opacity keyframe animation rule and returns its name.
|
56
|
+
* Since most mobile Webkits have timing issues with animation-delay,
|
57
|
+
* we create separate rules for each line/segment.
|
58
|
+
*/
|
59
|
+
function addAnimation(alpha, trail, i, lines) {
|
60
|
+
var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
|
61
|
+
, start = 0.01 + i/lines * 100
|
62
|
+
, z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
|
63
|
+
, prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
|
64
|
+
, pre = prefix && '-' + prefix + '-' || ''
|
65
|
+
|
66
|
+
if (!animations[name]) {
|
67
|
+
sheet.insertRule(
|
68
|
+
'@' + pre + 'keyframes ' + name + '{' +
|
69
|
+
'0%{opacity:' + z + '}' +
|
70
|
+
start + '%{opacity:' + alpha + '}' +
|
71
|
+
(start+0.01) + '%{opacity:1}' +
|
72
|
+
(start+trail) % 100 + '%{opacity:' + alpha + '}' +
|
73
|
+
'100%{opacity:' + z + '}' +
|
74
|
+
'}', sheet.cssRules.length)
|
75
|
+
|
76
|
+
animations[name] = 1
|
77
|
+
}
|
78
|
+
|
79
|
+
return name
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Tries various vendor prefixes and returns the first supported property.
|
84
|
+
*/
|
85
|
+
function vendor(el, prop) {
|
86
|
+
var s = el.style
|
87
|
+
, pp
|
88
|
+
, i
|
89
|
+
|
90
|
+
prop = prop.charAt(0).toUpperCase() + prop.slice(1)
|
91
|
+
for(i=0; i<prefixes.length; i++) {
|
92
|
+
pp = prefixes[i]+prop
|
93
|
+
if(s[pp] !== undefined) return pp
|
94
|
+
}
|
95
|
+
if(s[prop] !== undefined) return prop
|
96
|
+
}
|
97
|
+
|
98
|
+
/**
|
99
|
+
* Sets multiple style properties at once.
|
100
|
+
*/
|
101
|
+
function css(el, prop) {
|
102
|
+
for (var n in prop)
|
103
|
+
el.style[vendor(el, n)||n] = prop[n]
|
104
|
+
|
105
|
+
return el
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Fills in default values.
|
110
|
+
*/
|
111
|
+
function merge(obj) {
|
112
|
+
for (var i=1; i < arguments.length; i++) {
|
113
|
+
var def = arguments[i]
|
114
|
+
for (var n in def)
|
115
|
+
if (obj[n] === undefined) obj[n] = def[n]
|
116
|
+
}
|
117
|
+
return obj
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Returns the absolute page-offset of the given element.
|
122
|
+
*/
|
123
|
+
function pos(el) {
|
124
|
+
var o = { x:el.offsetLeft, y:el.offsetTop }
|
125
|
+
while((el = el.offsetParent))
|
126
|
+
o.x+=el.offsetLeft, o.y+=el.offsetTop
|
127
|
+
|
128
|
+
return o
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Returns the line color from the given string or array.
|
133
|
+
*/
|
134
|
+
function getColor(color, idx) {
|
135
|
+
return typeof color == 'string' ? color : color[idx % color.length]
|
136
|
+
}
|
137
|
+
|
138
|
+
// Built-in defaults
|
139
|
+
|
140
|
+
var defaults = {
|
141
|
+
lines: 12, // The number of lines to draw
|
142
|
+
length: 7, // The length of each line
|
143
|
+
width: 5, // The line thickness
|
144
|
+
radius: 10, // The radius of the inner circle
|
145
|
+
rotate: 0, // Rotation offset
|
146
|
+
corners: 1, // Roundness (0..1)
|
147
|
+
color: '#000', // #rgb or #rrggbb
|
148
|
+
direction: 1, // 1: clockwise, -1: counterclockwise
|
149
|
+
speed: 1, // Rounds per second
|
150
|
+
trail: 100, // Afterglow percentage
|
151
|
+
opacity: 1/4, // Opacity of the lines
|
152
|
+
fps: 20, // Frames per second when using setTimeout()
|
153
|
+
zIndex: 2e9, // Use a high z-index by default
|
154
|
+
className: 'spinner', // CSS class to assign to the element
|
155
|
+
top: 'auto', // center vertically
|
156
|
+
left: 'auto', // center horizontally
|
157
|
+
position: 'relative' // element position
|
158
|
+
}
|
159
|
+
|
160
|
+
/** The constructor */
|
161
|
+
function Spinner(o) {
|
162
|
+
if (typeof this == 'undefined') return new Spinner(o)
|
163
|
+
this.opts = merge(o || {}, Spinner.defaults, defaults)
|
164
|
+
}
|
165
|
+
|
166
|
+
// Global defaults that override the built-ins:
|
167
|
+
Spinner.defaults = {}
|
168
|
+
|
169
|
+
merge(Spinner.prototype, {
|
170
|
+
|
171
|
+
/**
|
172
|
+
* Adds the spinner to the given target element. If this instance is already
|
173
|
+
* spinning, it is automatically removed from its previous target b calling
|
174
|
+
* stop() internally.
|
175
|
+
*/
|
176
|
+
spin: function(target) {
|
177
|
+
this.stop()
|
178
|
+
|
179
|
+
var self = this
|
180
|
+
, o = self.opts
|
181
|
+
, el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
|
182
|
+
, mid = o.radius+o.length+o.width
|
183
|
+
, ep // element position
|
184
|
+
, tp // target position
|
185
|
+
|
186
|
+
if (target) {
|
187
|
+
target.insertBefore(el, target.firstChild||null)
|
188
|
+
tp = pos(target)
|
189
|
+
ep = pos(el)
|
190
|
+
css(el, {
|
191
|
+
left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
|
192
|
+
top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px'
|
193
|
+
})
|
194
|
+
}
|
195
|
+
|
196
|
+
el.setAttribute('role', 'progressbar')
|
197
|
+
self.lines(el, self.opts)
|
198
|
+
|
199
|
+
if (!useCssAnimations) {
|
200
|
+
// No CSS animation support, use setTimeout() instead
|
201
|
+
var i = 0
|
202
|
+
, start = (o.lines - 1) * (1 - o.direction) / 2
|
203
|
+
, alpha
|
204
|
+
, fps = o.fps
|
205
|
+
, f = fps/o.speed
|
206
|
+
, ostep = (1-o.opacity) / (f*o.trail / 100)
|
207
|
+
, astep = f/o.lines
|
208
|
+
|
209
|
+
;(function anim() {
|
210
|
+
i++;
|
211
|
+
for (var j = 0; j < o.lines; j++) {
|
212
|
+
alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
|
213
|
+
|
214
|
+
self.opacity(el, j * o.direction + start, alpha, o)
|
215
|
+
}
|
216
|
+
self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
|
217
|
+
})()
|
218
|
+
}
|
219
|
+
return self
|
220
|
+
},
|
221
|
+
|
222
|
+
/**
|
223
|
+
* Stops and removes the Spinner.
|
224
|
+
*/
|
225
|
+
stop: function() {
|
226
|
+
var el = this.el
|
227
|
+
if (el) {
|
228
|
+
clearTimeout(this.timeout)
|
229
|
+
if (el.parentNode) el.parentNode.removeChild(el)
|
230
|
+
this.el = undefined
|
231
|
+
}
|
232
|
+
return this
|
233
|
+
},
|
234
|
+
|
235
|
+
/**
|
236
|
+
* Internal method that draws the individual lines. Will be overwritten
|
237
|
+
* in VML fallback mode below.
|
238
|
+
*/
|
239
|
+
lines: function(el, o) {
|
240
|
+
var i = 0
|
241
|
+
, start = (o.lines - 1) * (1 - o.direction) / 2
|
242
|
+
, seg
|
243
|
+
|
244
|
+
function fill(color, shadow) {
|
245
|
+
return css(createEl(), {
|
246
|
+
position: 'absolute',
|
247
|
+
width: (o.length+o.width) + 'px',
|
248
|
+
height: o.width + 'px',
|
249
|
+
background: color,
|
250
|
+
boxShadow: shadow,
|
251
|
+
transformOrigin: 'left',
|
252
|
+
transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
|
253
|
+
borderRadius: (o.corners * o.width>>1) + 'px'
|
254
|
+
})
|
255
|
+
}
|
256
|
+
|
257
|
+
for (; i < o.lines; i++) {
|
258
|
+
seg = css(createEl(), {
|
259
|
+
position: 'absolute',
|
260
|
+
top: 1+~(o.width/2) + 'px',
|
261
|
+
transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
|
262
|
+
opacity: o.opacity,
|
263
|
+
animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
|
264
|
+
})
|
265
|
+
|
266
|
+
if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
|
267
|
+
ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')))
|
268
|
+
}
|
269
|
+
return el
|
270
|
+
},
|
271
|
+
|
272
|
+
/**
|
273
|
+
* Internal method that adjusts the opacity of a single line.
|
274
|
+
* Will be overwritten in VML fallback mode below.
|
275
|
+
*/
|
276
|
+
opacity: function(el, i, val) {
|
277
|
+
if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
|
278
|
+
}
|
279
|
+
|
280
|
+
})
|
281
|
+
|
282
|
+
|
283
|
+
function initVML() {
|
284
|
+
|
285
|
+
/* Utility function to create a VML tag */
|
286
|
+
function vml(tag, attr) {
|
287
|
+
return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
|
288
|
+
}
|
289
|
+
|
290
|
+
// No CSS transforms but VML support, add a CSS rule for VML elements:
|
291
|
+
sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
|
292
|
+
|
293
|
+
Spinner.prototype.lines = function(el, o) {
|
294
|
+
var r = o.length+o.width
|
295
|
+
, s = 2*r
|
296
|
+
|
297
|
+
function grp() {
|
298
|
+
return css(
|
299
|
+
vml('group', {
|
300
|
+
coordsize: s + ' ' + s,
|
301
|
+
coordorigin: -r + ' ' + -r
|
302
|
+
}),
|
303
|
+
{ width: s, height: s }
|
304
|
+
)
|
305
|
+
}
|
306
|
+
|
307
|
+
var margin = -(o.width+o.length)*2 + 'px'
|
308
|
+
, g = css(grp(), {position: 'absolute', top: margin, left: margin})
|
309
|
+
, i
|
310
|
+
|
311
|
+
function seg(i, dx, filter) {
|
312
|
+
ins(g,
|
313
|
+
ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
|
314
|
+
ins(css(vml('roundrect', {arcsize: o.corners}), {
|
315
|
+
width: r,
|
316
|
+
height: o.width,
|
317
|
+
left: o.radius,
|
318
|
+
top: -o.width>>1,
|
319
|
+
filter: filter
|
320
|
+
}),
|
321
|
+
vml('fill', {color: getColor(o.color, i), opacity: o.opacity}),
|
322
|
+
vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
|
323
|
+
)
|
324
|
+
)
|
325
|
+
)
|
326
|
+
}
|
327
|
+
|
328
|
+
if (o.shadow)
|
329
|
+
for (i = 1; i <= o.lines; i++)
|
330
|
+
seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
|
331
|
+
|
332
|
+
for (i = 1; i <= o.lines; i++) seg(i)
|
333
|
+
return ins(el, g)
|
334
|
+
}
|
335
|
+
|
336
|
+
Spinner.prototype.opacity = function(el, i, val, o) {
|
337
|
+
var c = el.firstChild
|
338
|
+
o = o.shadow && o.lines || 0
|
339
|
+
if (c && i+o < c.childNodes.length) {
|
340
|
+
c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
|
341
|
+
if (c) c.opacity = val
|
342
|
+
}
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
|
347
|
+
|
348
|
+
if (!vendor(probe, 'transform') && probe.adj) initVML()
|
349
|
+
else useCssAnimations = vendor(probe, 'animation')
|
350
|
+
|
351
|
+
return Spinner
|
352
|
+
|
353
|
+
}));
|