【问题标题】:Rails Nested complex FormRails 嵌套复杂表单
【发布时间】:2017-09-05 09:53:49
【问题描述】:

我正在寻找构建层叠表单的好主意。

我正在寻找的是构建一个表单来构造一个配置 yml 数据。

例如构建键值形式,值字段可以是字符串或哈希或数组。

config --> Items --> 有 key 和 value --> 可以是 String、Array 或 Hash。

有什么想法吗?

【问题讨论】:

  • 你能提供一个更具体的例子吗?你问的太模糊了。
  • 我要做的是为配置文件创建一个嵌套表单。这个配置文件是 yml 文件,它看起来像这样: bdd: server1: mysql: 'mariadb' vms: - name: 'test_1' files: 'example' 如何创建一个避免我要改革相同的级联格式。

标签: ruby-on-rails forms nested


【解决方案1】:

我所做的是使用 3djs 生成一个图形,并使 editabe 成为一个节点并处理添加/删除一个节点。我与您分享我为想要做同样事情的人所做的事情......(flare.json 是检索 json 文件的操作)

var root;
treeJSON = d3.json("flare.json", function(error, treeData) {

// Calculate total nodes, max label length
var totalNodes = 0;
var maxLabelLength = 0;
// variables for drag/drop
var selectedNode = null;
var draggingNode = null;
// panning variables
var panSpeed = 200;
var panBoundary = 20; // Within 20px from edges will pan when dragging.
// Misc. variables
var i = 0;
var duration = 750;
// var root;

// size of the diagram
var viewerWidth = $('#d3view').width();
var viewerHeight = 800;

var tree = d3.layout.tree()
    .size([viewerHeight, viewerWidth]);

// define a d3 diagonal projection for use by the node paths later on.
var diagonal = d3.svg.diagonal()
    .projection(function(d) {
        return [d.y, d.x];
    });

// A recursive helper function for performing some setup by walking through all nodes

function visit(parent, visitFn, childrenFn) {
    if (!parent) return;

    visitFn(parent);

    var children = childrenFn(parent);
    if (children) {
        var count = children.length;
        for (var i = 0; i < count; i++) {
            visit(children[i], visitFn, childrenFn);
        }
    }
}

// Call visit function to establish maxLabelLength
visit(treeData, function(d) {
    totalNodes++;
    maxLabelLength = Math.max(d.name.length, maxLabelLength);

}, function(d) {
    return d.children && d.children.length > 0 ? d.children : null;
});


// sort the tree according to the node names

function sortTree() {
    tree.sort(function(a, b) {
        return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
    });
}
// Sort the tree initially incase the JSON isn't in a sorted order.
sortTree();

// Define the zoom function for the zoomable tree

function zoom() {
    $('.btn-form').remove()
    svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}


// define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 1.5]).on("zoom", zoom);

// define the baseSvg, attaching a class for styling and the zoomListener
var baseSvg = d3.select("#d3view").append("svg")
    .attr("width", viewerWidth)
    .attr("height", viewerHeight)
    .attr("class", "overlay")
    .call(zoomListener);

// Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.

function centerNode(source) {
    scale = 0.3;//zoomListener.scale();
    x = -source.y0;
    y = -source.x0;
    x = x * scale + viewerWidth / 20;
    y = y * scale + viewerHeight / 2.5;
    d3.select('g').transition()
        .duration(duration)
        .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
    zoomListener.scale(scale);
    zoomListener.translate([x, y]);
}

// Toggle children function

function toggleChildren(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else if (d._children) {
        d.children = d._children;
        d._children = null;
    }
    return d;
}

// Toggle children on click.

function click(d) {
    if (d3.event.defaultPrevented) return; // click suppressed
    d = toggleChildren(d);
    update(d);
    // centerNode(d);
}



function remove_link(d){
    a = d3.event
    a.preventDefault()
    p = d.parent
    parent_array = [p]
    depth = d.depth - 1
    for(var i=1; i<depth; i++){
        parent_1 = p
        parent_2 = parent_1.parent
        parent_array.push(parent_2)
        p = parent_2
    }
    parent_array[0].children.splice($.inArray(d, parent_array[0].children), 1)
    update(d);
    $('#editme').tree('loadData', [root]);
}



function make_editable(d)
{
    // console.log("make_editable", arguments);
    this
      .on("mouseover", function() {
        $('.btn-form').remove();
        $('.input-form').remove();
        d3.selectAll('text').style('fill', null)
        d3.select(this).style("fill", "red");
        var p = this.parentNode;
        var xy = this.getBBox();
        var p_xy = p.getBBox();
        xy.x -= p_xy.x;
        xy.y -= p_xy.y;

        xy.x += 25;
        xy.y -= 50;

        var el = d3.select(this);
        var p_el = d3.select(p);

        var frm = p_el.append("foreignObject");
        var inp = frm
            .attr('class', 'btn-form')
            .attr("x", xy.x)
            .attr("y", xy.y)
            .attr("width", 145)
            .attr("height", 60)
            .append("xhtml:form")
                .append('a').text('Add').attr('class', 'btn btn-primary').attr('style', 'width:50px;height:30px;margin-right:2%;').on('click', add_link)

        frm.select('form').append('a').text('Remove').attr('class', 'btn btn-danger remove-btn').attr('style', 'width:70px;height:30px;').on('click', remove_link)
        frm.on('mouseover', function(){
            $('.btn-form').closest('g').find('text').attr("fill", 'red');
        })
            .on('mouseout', function(){
                $('.btn-form').closest('g').find('text').attr("fill", null);
            })

      })
      .on("mouseout", function() {
        d3.select(this).style("fill", null);
      })
      .on("click", function(d, event) {
        e = d3.event;
        e.preventDefault();
        $('.btn-form').remove();
        var p = this.parentNode;
        // console.log(this, arguments);

        // inject a HTML form to edit the content here...

        // bug in the getBBox logic here, but don't know what I've done wrong here;
        // anyhow, the coordinates are completely off & wrong. :-((
        var xy = this.getBBox();
        var p_xy = p.getBBox();

        xy.x -= p_xy.x;
        xy.y -= p_xy.y;

        var el = d3.select(this);
        var p_el = d3.select(p);

        var frm = p_el.append("foreignObject");

        var inp = frm
            .attr('class', 'input-form')
            .attr("x", xy.x)
            .attr("y", xy.y)
            .style("z-index", 99999)
            .attr("width", 300)
            .attr("height", 25)
            .append("xhtml:form")
                .append("input")
                    .attr("value", function() {
                        // nasty spot to place this call, but here we are sure that the <input> tag is available
                        // and is handily pointed at by 'this':
                        this.focus();

                        return el.text();
                    })
                    .attr("style", "width: 110px;")
                    // make the form go away when you jump out (form looses focus) or hit ENTER:
                    .on("blur", function() {
                        // console.log("blur", this, arguments);

                        var txt = inp.node().value;

                        el.text(function(d) { return txt; });
                        d.name = el.text();

                        // Note to self: frm.remove() will remove the entire <g> group! Remember the D3 selection logic!
                        p_el.select("foreignObject").remove();
                    })
                    .on("keypress", function() {
                        // console.log("keypress", this, arguments);

                        // IE fix
                        if (!d3.event)
                            d3.event = window.event;

                        var e = d3.event;
                        if (e.keyCode == 13)
                        {
                            if (typeof(e.cancelBubble) !== 'undefined') // IE
                              e.cancelBubble = true;
                            if (e.stopPropagation)
                              e.stopPropagation();
                            e.preventDefault();

                            var txt = inp.node().value;
                            el.text(function(d) { return txt; });
                            p_el.attr('data-name', txt);
                            d.name = el.text();
                            $('#editme').tree('loadData', [root]);
                        }
                    });
      });
}


function add_link(d){
    d3.event.preventDefault();
    var p = $(this).closest('form')
    var xy = $(this).closest('.btn-form')[0].getBBox();


    if($(".add-link").length == 0){
        var frm = p.append("<div><input type='text' class='add-link' style='width:130px;'></input></div>");
        var inp = frm.on('keypress', function(event){
            var e = event;
            if (e.keyCode == 13)
            {
                e.preventDefault();

                var txt = $('.add-link').val();
                var event = document.createEvent('Event');
                event.initEvent('click', true, true);
                if(d.children){
                    d.children.push({name: txt})
                }else if(d._children){
                    d._children.push({name: txt})

                }else{
                    d.children = [{name: txt}]
                }
                 $('text[fill="red"]').attr('fill', null);
                update(root);
                $(".btn-form").hide();
                $('#editme').tree('loadData', [root]);
            }
        })
        .on('click', function(event){ event.preventDefault();})

        $('.add-link').focus()
    }

}


function update(source) {
    // Compute the new height, function counts total children of root node and sets tree height accordingly.
    // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
    // This makes the layout more consistent.
    var levelWidth = [1];
    var childCount = function(level, n) {

        if (n.children && n.children.length > 0) {
            if (levelWidth.length <= level + 1) levelWidth.push(0);

            levelWidth[level + 1] += n.children.length;
            n.children.forEach(function(d) {
                childCount(level + 1, d);
            });
        }
    };
    childCount(0, root);
    var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line  
    tree = tree.size([newHeight, viewerWidth]);

    // Compute the new tree layout.
    var nodes = tree.nodes(root).reverse(),
        links = tree.links(nodes);

    // Set widths between levels based on maxLabelLength.
    nodes.forEach(function(d) {
        d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
        // alternatively to keep a fixed scale one can set a fixed depth per level
        // Normalize for fixed-depth by commenting out below line
        // d.y = (d.depth * 500); //500px per level.
    });

    // Update the nodes…
    node = svgGroup.selectAll("g.node")
        .data(nodes, function(d) {
            return d.id || (d.id = ++i);
        });

    // Enter any new nodes at the parent's previous position.
    var nodeEnter = node.enter().append("g")
        // .call(dragListener)
        .attr("class", "node")
        .attr("transform", function(d) {
            return "translate(" + source.y0 + "," + source.x0 + ")";
        })
        .on('click', click);

    nodeEnter.append("circle")
        .attr('class', 'nodeCircle')
        .attr("r", 0)
        .style("fill", function(d) {
            return d._children ? "lightsteelblue" : "#fff";
        });

    nodeEnter.append("text")
        .attr("x", function(d) {
            return d.children || d._children ? -10 : 10;
        })
        .attr("dy", ".35em")
        .attr('class', 'nodeText')
        .attr("text-anchor", function(d) {
            return d.children || d._children ? "end" : "start";
        })
        .text(function(d) {
            return d.name;
        })
        .style("fill-opacity", 0)
        .call(make_editable);

    // phantom node to give us mouseover in a radius around it
    // nodeEnter.append("circle")
    //     .attr('class', 'ghostCircle')
    //     .attr("r", 30)
    //     .attr("opacity", 0.2) // change this to zero to hide the target area
    //  .style("fill", "red")
    //     .attr('pointer-events', 'mouseover');
        // .on("mouseover", function(node) {
        //     overCircle(node);
        // })
        // .on("mouseout", function(node) {
        //     outCircle(node);
        // });

    // Update the text to reflect whether node has children or not.
    node.select('text')
        .attr("x", function(d) {
            return d.children || d._children ? -10 : 10;
        })
        .attr("text-anchor", function(d) {
            return d.children || d._children ? "end" : "start";
        })
        .text(function(d) {
            return d.name;
        });

    // Change the circle fill depending on whether it has children and is collapsed
    node.select("circle.nodeCircle")
        .attr("r", 4.5)
        .style("fill", function(d) {
            return d._children ? "lightsteelblue" : "#fff";
        });

    // Transition nodes to their new position.
    var nodeUpdate = node.transition()
        .duration(duration)
        .attr("transform", function(d) {
            return "translate(" + d.y + "," + d.x + ")";
        });

    // Fade the text in
    nodeUpdate.select("text")
        .style("fill-opacity", 1);

    // Transition exiting nodes to the parent's new position.
    var nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) {
            return "translate(" + source.y + "," + source.x + ")";
        })
        .remove();

    nodeExit.select("circle")
        .attr("r", 0);

    nodeExit.select("text")
        .style("fill-opacity", 0);

    // Update the links…
    var link = svgGroup.selectAll("path.link")
        .data(links, function(d) {
            return d.target.id;
        });

    // Enter any new links at the parent's previous position.
    link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", function(d) {
            var o = {
                x: source.x0,
                y: source.y0
            };
            return diagonal({
                source: o,
                target: o
            });
        });

    // Transition links to their new position.
    link.transition()
        .duration(duration)
        .attr("d", diagonal);

    // Transition exiting nodes to the parent's new position.
    link.exit().transition()
        .duration(duration)
        .attr("d", function(d) {
            var o = {
                x: source.x,
                y: source.y
            };
            return diagonal({
                source: o,
                target: o
            });
        })
        .remove();

    // Stash the old positions for transition.
    nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
    });
}

function collapse(d) {
    if (d.children) {
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = null;
    }
}

function expand(d) {
    if (d._children) {
        d.children = d._children;
        d.children.forEach(expand);
        d._children = null;
    }else if(d.children) {
        d.children.forEach(expand);
    }
}

// Append a group which holds all nodes and which the zoom Listener can act upon.
var svgGroup = baseSvg.append("g");

// Define the root
root = treeData;
root.x0 = viewerHeight / 2;
root.y0 = 0;

// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-03
    • 2012-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多