class ERD constructor: (@name, @elem, @edges) -> @paper = Raphael(name, @elem.data('svg_width'), @elem.data('svg_height')) @setup_handlers() models = @elem.find('.model') @models = {} for model in models @models[$(model).data('model_name')] = model @connect_arrows(@edges) 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(""" #{action} #{model} #{column} #{from} #{to} """) else $(existing[3]).text(from) $(existing[4]).text(to) $('#changes').show() positions: (div) -> [left, width, top, height] = [parseFloat(div.css('left')), parseFloat(div.css('width')), parseFloat(div.css('top')), parseFloat(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: (edges) => $.each edges, (i, edge) => @connect_arrow edge, $(@models[edge.from]), $(@models[edge.to]) connect_arrow: (edge, from_elem, to_elem) -> #TODO handle self referential associations return if from_elem.attr('id') == to_elem.attr('id') edge.path.remove() if edge.path? 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#{Math.floor(from.vertex.x)} #{Math.floor(from.vertex.y)}H#{Math.floor((from.vertex.x + to.vertex.x) / 2)} V#{Math.floor(to.vertex.y)} H#{Math.floor(to.vertex.x)}" else path = "M#{Math.floor(from.vertex.x)} #{Math.floor(from.vertex.y)}V#{Math.floor((from.vertex.y + to.vertex.y) / 2)} H#{Math.floor(to.vertex.x)} V#{Math.floor(to.vertex.y)}" edge.path = @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_name = 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_name, '', '', to @connect_arrows(@edges.filter((e)-> e.from == model_name || e.to == model_name)) setup_click_handlers: -> $('div.model_name_text, span.column_name_text, span.column_type_text').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: (ev) => changes = $('#changes > tbody > tr').map(-> change = {} $(this).find('td').each -> name = $(this).data('name') value = $(this).html() change[name] = value change ).toArray() $('#changes_form').find('input[name=changes]').val(JSON.stringify(changes)) 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 = $("", class: 'column_name_text') .append(name) type_span = $("", class: 'column_type_text') .append(type) li_node = $("
  • ", 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 = $(ev.currentTarget) 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 = $(ev.currentTarget) 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_name = parent.data('model_name') upsert_change 'remove_model', model_name, '', '', '' parent.hide() $.each @edges, (i, edge) => @edges.splice i, 1 if (edge.from == model_name) || (edge.to == model_name) @paper.clear() @connect_arrows(@edges) $ -> window.erd = new ERD('erd', $('#erd'), window.raw_edges)