class ERD constructor: (@name, @elem, @edges) -> @paper = Raphael(name, @elem.css('width'), @elem.css('height')) @setup_handlers() @connect_arrows() upsert_change: (action, model, column, from, to) -> rows = ($(tr).find('td') for tr in $('#changes > tbody > tr')) existing = null $(rows).each (i, row) -> existing = row if (action == $(row[0]).html()) && (model == $(row[1]).html()) && (column == $(row[2]).html()) if existing == null $('#changes > tbody').append("<tr><td>#{action}</td><td>#{model}</td><td>#{column}</td><td>#{from}</td><td>#{to}</td></tr>") else $(existing[3]).text(from) $(existing[4]).text(to) $('#changes').show() positions: (div) -> [left, width, top, height] = [parseInt(div.css('left')), parseInt(div.css('width')), parseInt(div.css('top')), parseInt(div.css('height'))] {left: left, right: left + width, top: top, bottom: top + height, center: {x: (left + left + width) / 2, y: (top + top + height) / 2}, vertex: {}} connect_arrows: -> $.each @edges, (i, edge) => @connect_arrow $("##{edge.from}"), $("##{edge.to}") connect_arrow: (from_elem, to_elem) -> #TODO handle self referential associations return if from_elem.attr('id') == to_elem.attr('id') from = @positions(from_elem) to = @positions(to_elem) #FIXME terrible code a = (to.center.y - from.center.y) / (to.center.x - from.center.x) b = from.center.y - from.center.x * a x2y = (x) -> ( a * x + b ) y2x = (y) -> ( (y - b) / a ) if from.center.x > to.center.x [from.vertex.x, from.vertex.y] = [from.left, x2y(from.left)] [to.vertex.x, to.vertex.y] = [to.right, x2y(to.right)] else [from.vertex.x, from.vertex.y] = [from.right, x2y(from.right)] [to.vertex.x, to.vertex.y] = [to.left, x2y(to.left)] for rect in [from, to] if rect.vertex.y < rect.top [rect.vertex.x, rect.vertex.y, rect.vertex.direction] = [y2x(rect.top), rect.top, 'v'] else if rect.vertex.y > rect.bottom [rect.vertex.x, rect.vertex.y, rect.vertex.direction] = [y2x(rect.bottom), rect.bottom, 'v'] else from.vertex.direction = 'h' if from.vertex.direction == 'h' 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)}" else 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)}" @paper.path(path).attr({'stroke-width': 2, opacity: 0.5, 'arrow-end': 'classic-wide-long'}) setup_handlers: -> @setup_click_handlers() @setup_submit_handlers() $('div.model').draggable(drag: @handle_drag) handle_drag: (ev, ui) => target = $(ev.target) target.addClass('noclick') model = target.data('model_name') from = target.data('original_position') to = [target.css('left').replace(/px$/, ''), target.css('top').replace(/px$/, '')].join() @upsert_change 'move', model, '', '', to @paper.clear() @connect_arrows(@edges) setup_click_handlers: -> text_elems = [ 'div.model_name_text', 'span.column_name_text', 'span.column_type_text' ].join() $(text_elems).on 'click', @handle_text_elem_click $('div.model a.add_column').on 'click', @handle_add_column_click $('div.model a.close').on 'click', @handle_remove_model_click setup_submit_handlers: -> $('form.rename_model_form').on 'submit', @handle_rename_model $('form.rename_column_form').on 'submit', @handle_rename_column $('form.alter_column_form').on 'submit', @handle_change_column_type $('form.add_column_form').on 'submit', @handle_add_column $('#changes_form').on 'submit', @handle_save handle_save: -> j = '[' rows = ($(tr).find('td') for tr in $('#changes > tbody > tr')) $(rows).each (i, row) -> j += "{\"action\": \"#{$(row[0]).html()}\", \"model\": \"#{$(row[1]).html()}\", \"column\": \"#{$(row[2]).html()}\", \"from\": \"#{$(row[3]).html()}\", \"to\": \"#{$(row[4]).html()}\"}" j += ',' if i < rows.length - 1 j += ']' $('#changes_form').find('input[name=changes]').val(j) handle_add_column: (ev) -> ev.preventDefault() target = $(ev.target) name = target.find('input[name=name]').val() return if name == '' model = target.find('input[name=model]').val() type = target.find('input[name=type]').val() upsert_change 'add_column', model, "#{name}(#{type})", '', '' name_span = $("<span/>", class: 'column_name_text') .append(name) type_span = $("<span/>", class: 'column_type_text') .append(type) li_node = $("<li/>", class: 'column') .append(name_span) .append(" ") .append(type_span) target.hide() .parent() .siblings('.columns') .find('ul') .append(li_node) .end() .end() .find('a.add_column') .show() handle_change_column_type: (ev) -> ev.preventDefault() target = $(ev.target) to = target.find('input[name=to]').val() return if to == '' model = target.find('input[name=model]').val() column = target.find('input[name=column]').val() type = target.find('input[name=type]').val() if to != type upsert_change 'alter_column', model, column, type, to target.hide() .siblings('.column_type_text') .text(to) .show() handle_rename_column: (ev) -> ev.preventDefault() target = $(ev.target) to = target.find('input[name=to]').val() return if to == '' model = target.find('input[name=model]').val() column = target.find('input[name=column]').val() if to != column upsert_change 'rename_column', model, column, column, to target.hide() .siblings('.column_name_text') .text(to) .show() handle_rename_model: (ev) -> ev.preventDefault() target = $(ev.target) to = target.find('input[name=to]').val() return if to == '' model = target.find('input[name=model]').val() if to != model upsert_change 'rename_model', model, '', model, to target.hide() .siblings('.model_name_text') .text(to) .show() handle_add_column_click: (ev) -> ev.preventDefault() target = $(@) m = target.parents('div.model') if m.hasClass('noclick') m.removeClass('noclick') return false target.hide() .next('form') .show() .find('input[name=type]') .val('string') .end() .find('input[name=name]') .val('') .focus() handle_text_elem_click: (ev) -> target = $(@) text = target.text() m = target.parents('div.model') if m.hasClass('noclick') m.removeClass('noclick') return false target.hide() .next('form') .show() .find('input[name=to]') .val(text) .focus() handle_remove_model_click: (ev) => ev.preventDefault() target = $(ev.target) parent = target.parent() m = target.parents('div.model') if m.hasClass('noclick') m.removeClass('noclick') return false return unless confirm('remove this table?') [model_id, model_name] = [parent.attr('id'), parent.data('model_name')] upsert_change 'remove_model', model_name, '', '', '' parent.hide() $.each @edges, (i, edge) => @edges.splice i, 1 if (edge.from == model_id) || (edge.to == model_id) @paper.clear() @connect_arrows(@edges) $ -> window.erd = new ERD('erd', $('#erd'), window.raw_edges)