erd 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/app/assets/javascripts/erd/erd.js.coffee +221 -132
- data/app/views/erd/erd/erd.html.erb +1 -1
- data/lib/erd/version.rb +1 -1
- metadata +6 -6
@@ -1,152 +1,241 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class ERD
|
2
|
+
constructor: (@name, @elem, @edges) ->
|
3
|
+
@paper = Raphael(name, @elem.css('width'), @elem.css('height'))
|
4
|
+
@setup_handlers()
|
5
|
+
@connect_arrows()
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
$(
|
8
|
-
|
9
|
-
|
7
|
+
upsert_change: (action, model, column, from, to) ->
|
8
|
+
rows = ($(tr).find('td') for tr in $('#changes > tbody > tr'))
|
9
|
+
existing = null
|
10
|
+
$(rows).each (i, row) ->
|
11
|
+
existing = row if (action == $(row[0]).html()) && (model == $(row[1]).html()) && (column == $(row[2]).html())
|
12
|
+
if existing == null
|
13
|
+
$('#changes > tbody').append("<tr><td>#{action}</td><td>#{model}</td><td>#{column}</td><td>#{from}</td><td>#{to}</td></tr>")
|
14
|
+
else
|
15
|
+
$(existing[3]).text(from)
|
16
|
+
$(existing[4]).text(to)
|
17
|
+
$('#changes').show()
|
18
|
+
|
19
|
+
positions: (div) ->
|
20
|
+
[left, width, top, height] = [parseInt(div.css('left')), parseInt(div.css('width')), parseInt(div.css('top')), parseInt(div.css('height'))]
|
21
|
+
{left: left, right: left + width, top: top, bottom: top + height, center: {x: (left + left + width) / 2, y: (top + top + height) / 2}, vertex: {}}
|
22
|
+
|
23
|
+
connect_arrows: ->
|
24
|
+
$.each @edges, (i, edge) =>
|
25
|
+
@connect_arrow $("##{edge.from}"), $("##{edge.to}")
|
26
|
+
|
27
|
+
connect_arrow: (from_elem, to_elem) ->
|
28
|
+
#TODO handle self referential associations
|
29
|
+
return if from_elem.attr('id') == to_elem.attr('id')
|
30
|
+
|
31
|
+
from = @positions(from_elem)
|
32
|
+
to = @positions(to_elem)
|
33
|
+
#FIXME terrible code
|
34
|
+
a = (to.center.y - from.center.y) / (to.center.x - from.center.x)
|
35
|
+
b = from.center.y - from.center.x * a
|
36
|
+
|
37
|
+
x2y = (x) -> ( a * x + b )
|
38
|
+
y2x = (y) -> ( (y - b) / a )
|
39
|
+
|
40
|
+
if from.center.x > to.center.x
|
41
|
+
[from.vertex.x, from.vertex.y] = [from.left, x2y(from.left)]
|
42
|
+
[to.vertex.x, to.vertex.y] = [to.right, x2y(to.right)]
|
43
|
+
else
|
44
|
+
[from.vertex.x, from.vertex.y] = [from.right, x2y(from.right)]
|
45
|
+
[to.vertex.x, to.vertex.y] = [to.left, x2y(to.left)]
|
46
|
+
for rect in [from, to]
|
47
|
+
if rect.vertex.y < rect.top
|
48
|
+
[rect.vertex.x, rect.vertex.y, rect.vertex.direction] = [y2x(rect.top), rect.top, 'v']
|
49
|
+
else if rect.vertex.y > rect.bottom
|
50
|
+
[rect.vertex.x, rect.vertex.y, rect.vertex.direction] = [y2x(rect.bottom), rect.bottom, 'v']
|
51
|
+
else
|
52
|
+
from.vertex.direction = 'h'
|
53
|
+
|
54
|
+
if from.vertex.direction == 'h'
|
55
|
+
path = "M#{parseInt(from.vertex.x)} #{parseInt(from.vertex.y)}H#{parseInt((from.vertex.x + to.vertex.x) / 2)} V#{parseInt(to.vertex.y)} H#{parseInt(to.vertex.x)}"
|
56
|
+
else
|
57
|
+
path = "M#{parseInt(from.vertex.x)} #{parseInt(from.vertex.y)}V#{parseInt((from.vertex.y + to.vertex.y) / 2)} H#{parseInt(to.vertex.x)} V#{parseInt(to.vertex.y)}"
|
58
|
+
|
59
|
+
@paper.path(path).attr({'stroke-width': 2, opacity: 0.5, 'arrow-end': 'classic-wide-long'})
|
60
|
+
|
61
|
+
setup_handlers: ->
|
62
|
+
@setup_click_handlers()
|
63
|
+
@setup_submit_handlers()
|
64
|
+
$('div.model').draggable(drag: @handle_drag)
|
65
|
+
|
66
|
+
handle_drag: (ev, ui) =>
|
67
|
+
target = $(ev.target)
|
68
|
+
target.addClass('noclick')
|
69
|
+
model = target.data('model_name')
|
70
|
+
from = target.data('original_position')
|
71
|
+
to = [target.css('left').replace(/px$/, ''), target.css('top').replace(/px$/, '')].join()
|
72
|
+
@upsert_change 'move', model, '', '', to
|
73
|
+
@paper.clear()
|
74
|
+
@connect_arrows(@edges)
|
75
|
+
|
76
|
+
setup_click_handlers: ->
|
77
|
+
text_elems = [
|
78
|
+
'div.model_name_text',
|
79
|
+
'span.column_name_text',
|
80
|
+
'span.column_type_text'
|
81
|
+
].join()
|
82
|
+
|
83
|
+
$(text_elems).on 'click', @handle_text_elem_click
|
84
|
+
$('div.model a.add_column').on 'click', @handle_add_column_click
|
85
|
+
$('div.model a.close').on 'click', @handle_remove_model_click
|
86
|
+
|
87
|
+
setup_submit_handlers: ->
|
88
|
+
$('form.rename_model_form').on 'submit', @handle_rename_model
|
89
|
+
$('form.rename_column_form').on 'submit', @handle_rename_column
|
90
|
+
$('form.alter_column_form').on 'submit', @handle_change_column_type
|
91
|
+
$('form.add_column_form').on 'submit', @handle_add_column
|
92
|
+
$('#changes_form').on 'submit', @handle_save
|
93
|
+
|
94
|
+
handle_save: ->
|
95
|
+
j = '['
|
96
|
+
rows = ($(tr).find('td') for tr in $('#changes > tbody > tr'))
|
97
|
+
$(rows).each (i, row) ->
|
98
|
+
j += "{\"action\": \"#{$(row[0]).html()}\", \"model\": \"#{$(row[1]).html()}\", \"column\": \"#{$(row[2]).html()}\", \"from\": \"#{$(row[3]).html()}\", \"to\": \"#{$(row[4]).html()}\"}"
|
99
|
+
j += ',' if i < rows.length - 1
|
100
|
+
j += ']'
|
101
|
+
$('#changes_form').find('input[name=changes]').val(j)
|
10
102
|
|
11
|
-
|
103
|
+
handle_add_column: (ev) ->
|
12
104
|
ev.preventDefault()
|
13
|
-
$(
|
14
|
-
|
15
|
-
|
16
|
-
|
105
|
+
target = $(ev.target)
|
106
|
+
name = target.find('input[name=name]').val()
|
107
|
+
return if name == ''
|
108
|
+
|
109
|
+
model = target.find('input[name=model]').val()
|
110
|
+
type = target.find('input[name=type]').val()
|
111
|
+
upsert_change 'add_column', model, "#{name}(#{type})", '', ''
|
112
|
+
|
113
|
+
name_span = $("<span/>", class: 'column_name_text')
|
114
|
+
.append(name)
|
115
|
+
|
116
|
+
type_span = $("<span/>", class: 'column_type_text')
|
117
|
+
.append(type)
|
118
|
+
|
119
|
+
li_node = $("<li/>", class: 'column')
|
120
|
+
.append(name_span)
|
121
|
+
.append(" ")
|
122
|
+
.append(type_span)
|
123
|
+
|
124
|
+
target.hide()
|
125
|
+
.parent()
|
126
|
+
.siblings('.columns')
|
127
|
+
.find('ul')
|
128
|
+
.append(li_node)
|
129
|
+
.end()
|
130
|
+
.end()
|
131
|
+
.find('a.add_column')
|
132
|
+
.show()
|
133
|
+
|
134
|
+
handle_change_column_type: (ev) ->
|
17
135
|
ev.preventDefault()
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
window.paper.clear()
|
35
|
-
connect_arrows(window.edges)
|
36
|
-
|
37
|
-
$('form.rename_model_form').on('submit', (ev) ->
|
136
|
+
target = $(ev.target)
|
137
|
+
to = target.find('input[name=to]').val()
|
138
|
+
return if to == ''
|
139
|
+
|
140
|
+
model = target.find('input[name=model]').val()
|
141
|
+
column = target.find('input[name=column]').val()
|
142
|
+
type = target.find('input[name=type]').val()
|
143
|
+
if to != type
|
144
|
+
upsert_change 'alter_column', model, column, type, to
|
145
|
+
|
146
|
+
target.hide()
|
147
|
+
.siblings('.column_type_text')
|
148
|
+
.text(to)
|
149
|
+
.show()
|
150
|
+
|
151
|
+
handle_rename_column: (ev) ->
|
38
152
|
ev.preventDefault()
|
39
|
-
|
153
|
+
target = $(ev.target)
|
154
|
+
to = target.find('input[name=to]').val()
|
155
|
+
return if to == ''
|
40
156
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
157
|
+
model = target.find('input[name=model]').val()
|
158
|
+
column = target.find('input[name=column]').val()
|
159
|
+
if to != column
|
160
|
+
upsert_change 'rename_column', model, column, column, to
|
45
161
|
|
46
|
-
|
47
|
-
|
162
|
+
target.hide()
|
163
|
+
.siblings('.column_name_text')
|
164
|
+
.text(to)
|
165
|
+
.show()
|
48
166
|
|
49
|
-
|
167
|
+
handle_rename_model: (ev) ->
|
50
168
|
ev.preventDefault()
|
51
|
-
|
169
|
+
target = $(ev.target)
|
170
|
+
to = target.find('input[name=to]').val()
|
171
|
+
return if to == ''
|
52
172
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if to != column
|
57
|
-
upsert_change 'rename_column', model, column, column, to
|
173
|
+
model = target.find('input[name=model]').val()
|
174
|
+
if to != model
|
175
|
+
upsert_change 'rename_model', model, '', model, to
|
58
176
|
|
59
|
-
|
60
|
-
|
177
|
+
target.hide()
|
178
|
+
.siblings('.model_name_text')
|
179
|
+
.text(to)
|
180
|
+
.show()
|
61
181
|
|
62
|
-
|
182
|
+
handle_add_column_click: (ev) ->
|
63
183
|
ev.preventDefault()
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
184
|
+
target = $(@)
|
185
|
+
|
186
|
+
m = target.parents('div.model')
|
187
|
+
if m.hasClass('noclick')
|
188
|
+
m.removeClass('noclick')
|
189
|
+
return false
|
190
|
+
|
191
|
+
target.hide()
|
192
|
+
.next('form')
|
193
|
+
.show()
|
194
|
+
.find('input[name=type]')
|
195
|
+
.val('string')
|
196
|
+
.end()
|
197
|
+
.find('input[name=name]')
|
198
|
+
.val('')
|
199
|
+
.focus()
|
200
|
+
|
201
|
+
handle_text_elem_click: (ev) ->
|
202
|
+
target = $(@)
|
203
|
+
text = target.text()
|
204
|
+
|
205
|
+
m = target.parents('div.model')
|
206
|
+
if m.hasClass('noclick')
|
207
|
+
m.removeClass('noclick')
|
208
|
+
return false
|
209
|
+
|
210
|
+
target.hide()
|
211
|
+
.next('form')
|
212
|
+
.show()
|
213
|
+
.find('input[name=to]')
|
214
|
+
.val(text)
|
215
|
+
.focus()
|
216
|
+
|
217
|
+
handle_remove_model_click: (ev) =>
|
77
218
|
ev.preventDefault()
|
78
|
-
name = $(this).find('input[name=name]').val()
|
79
219
|
|
80
|
-
|
81
|
-
|
82
|
-
type = $(this).find('input[name=type]').val()
|
83
|
-
upsert_change 'add_column', model, "#{name}(#{type})", '', ''
|
220
|
+
target = $(ev.target)
|
221
|
+
parent = target.parent()
|
84
222
|
|
85
|
-
|
86
|
-
|
87
|
-
|
223
|
+
m = target.parents('div.model')
|
224
|
+
if m.hasClass('noclick')
|
225
|
+
m.removeClass('noclick')
|
226
|
+
return false
|
88
227
|
|
89
|
-
|
90
|
-
j = '['
|
91
|
-
rows = ($(tr).find('td') for tr in $('#changes > tbody > tr'))
|
92
|
-
$(rows).each (i, row) ->
|
93
|
-
j += "{\"action\": \"#{$(row[0]).html()}\", \"model\": \"#{$(row[1]).html()}\", \"column\": \"#{$(row[2]).html()}\", \"from\": \"#{$(row[3]).html()}\", \"to\": \"#{$(row[4]).html()}\"}"
|
94
|
-
j += ',' if i < rows.length - 1
|
95
|
-
j += ']'
|
96
|
-
$('#changes_form').find('input[name=changes]').val(j)
|
228
|
+
return unless confirm('remove this table?')
|
97
229
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
$(existing[3]).text(from)
|
107
|
-
$(existing[4]).text(to)
|
108
|
-
$('#changes').show()
|
109
|
-
|
110
|
-
positions = (div) ->
|
111
|
-
[left, width, top, height] = [parseInt(div.css('left')), parseInt(div.css('width')), parseInt(div.css('top')), parseInt(div.css('height'))]
|
112
|
-
{left: left, right: left + width, top: top, bottom: top + height, center: {x: (left + left + width) / 2, y: (top + top + height) / 2}, vertex: {}}
|
113
|
-
|
114
|
-
|
115
|
-
window.connect_arrows = (edges) ->
|
116
|
-
$.each(edges, (i, edge) ->
|
117
|
-
window.connect_arrow $("##{edge.from}"), $("##{edge.to}")
|
118
|
-
)
|
119
|
-
|
120
|
-
window.connect_arrow = (from_elem, to_elem) ->
|
121
|
-
#TODO handle self referential associations
|
122
|
-
return if from_elem.attr('id') == to_elem.attr('id')
|
123
|
-
|
124
|
-
from = positions(from_elem)
|
125
|
-
to = positions(to_elem)
|
126
|
-
#FIXME terrible code
|
127
|
-
a = (to.center.y - from.center.y) / (to.center.x - from.center.x)
|
128
|
-
b = from.center.y - from.center.x * a
|
129
|
-
|
130
|
-
x2y = (x) -> ( a * x + b )
|
131
|
-
y2x = (y) -> ( (y - b) / a )
|
132
|
-
|
133
|
-
if from.center.x > to.center.x
|
134
|
-
[from.vertex.x, from.vertex.y] = [from.left, x2y(from.left)]
|
135
|
-
[to.vertex.x, to.vertex.y] = [to.right, x2y(to.right)]
|
136
|
-
else
|
137
|
-
[from.vertex.x, from.vertex.y] = [from.right, x2y(from.right)]
|
138
|
-
[to.vertex.x, to.vertex.y] = [to.left, x2y(to.left)]
|
139
|
-
for rect in [from, to]
|
140
|
-
if rect.vertex.y < rect.top
|
141
|
-
[rect.vertex.x, rect.vertex.y, rect.vertex.direction] = [y2x(rect.top), rect.top, 'v']
|
142
|
-
else if rect.vertex.y > rect.bottom
|
143
|
-
[rect.vertex.x, rect.vertex.y, rect.vertex.direction] = [y2x(rect.bottom), rect.bottom, 'v']
|
144
|
-
else
|
145
|
-
from.vertex.direction = 'h'
|
230
|
+
[model_id, model_name] = [parent.attr('id'), parent.data('model_name')]
|
231
|
+
upsert_change 'remove_model', model_name, '', '', ''
|
232
|
+
parent.hide()
|
233
|
+
|
234
|
+
$.each @edges, (i, edge) =>
|
235
|
+
@edges.splice i, 1 if (edge.from == model_id) || (edge.to == model_id)
|
236
|
+
@paper.clear()
|
237
|
+
@connect_arrows(@edges)
|
146
238
|
|
147
|
-
|
148
|
-
|
149
|
-
else
|
150
|
-
path = "M#{parseInt(from.vertex.x)} #{parseInt(from.vertex.y)}V#{parseInt((from.vertex.y + to.vertex.y) / 2)} H#{parseInt(to.vertex.x)} V#{parseInt(to.vertex.y)}"
|
239
|
+
$ ->
|
240
|
+
window.erd = new ERD('erd', $('#erd'), window.raw_edges)
|
151
241
|
|
152
|
-
window.paper.path(path).attr({'stroke-width': 2, opacity: 0.5, 'arrow-end': 'classic-wide-long'})
|
@@ -2,5 +2,5 @@
|
|
2
2
|
<%= render :partial => 'erd/erd/model', :collection => models -%>
|
3
3
|
<svg width="<%= width %>pt" height="<%= height %>pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
4
4
|
</svg>
|
5
|
-
<script
|
5
|
+
<script>window.raw_edges = <%= edges.to_json.html_safe %>;</script>
|
6
6
|
</div>
|
data/lib/erd/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails-erd
|
16
|
-
requirement: &
|
16
|
+
requirement: &70208383601640 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.4.5
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70208383601640
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: nokogiri
|
27
|
-
requirement: &
|
27
|
+
requirement: &70208383631860 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70208383631860
|
36
36
|
description: erd engine on Rails
|
37
37
|
email:
|
38
38
|
- ronnie@dio.jp
|