递归构建器嵌套问题

Recursive builder nesting issues

本文关键字:问题 嵌套 构建 递归      更新时间:2023-09-26

我正在尝试解析JSON/JavaScript对象到HTML…它的大部分工作,但我不能找出我的递归构建方法(App.Layout.Form.DocumentDirector)…

在不应该嵌套(追加)元素的时候继续嵌套(追加)元素。示例和代码见http://jsfiddle.net/blogshop/DSxft/6/

有谁能帮我弄明白吗?构建方法在第408行(在这里——向下滚动一点DocumentDirector就可以看到它)。谢谢!

构建方法

build: function (obj, level) {
        var that = this,
            builder = this.getBuilder(),
            iterator = Object.create(App.Utilities.RecursiveIterator(obj)),
            level = level || 0,
            prev = this._prevLevel || 0,
            next = level,
            key,
            current,
            attributes,
            pair,
            children,
            hasType = false,
            maxNesting = false;
        do {
            // Get the current node and build it
            key = iterator.key();
            current = iterator.current();
            console.log(iterator.key() + ": " + iterator.current().toSource());
            if (current.hasOwnProperty('type') && current.type !== '') {
                if (current.hasOwnProperty('type') && current.type !== '') {
                    //console.log(iterator.current().type);
                    attributes = {};
                    $.each(current, function(key, value) {
                        if (that._validAttributes.indexOf(key) !== -1) {
                            attributes[key] = value;
                        }
                    });
                    //console.log(attributes);
                }
                //console.log("Prev: " + prev);
                console.log("Level: " + level);
                if (level == prev && level == 0) {
                    builder.append(current.type, attributes);
                } else if (level == prev && level > 0) {
                    builder.add(current.type, attributes);                          
                } else if (level < prev && level > 0) {
                    builder.parent().append(current.type, attributes);
                } else if (level > prev && level > 0) {
                    builder.append(current.type, attributes);
                } else {
                    // Do nothing
                }
                if (current.hasOwnProperty('label')) {
                    var label = builder.addBefore(builder.getCurrent(), 'label');
                    builder.text(current.label, label);
                }
            }
            // Are there child nodes? If so, recurse...
            if (iterator.hasChildren()) {
                children = iterator.getChildren();
                if (this.isCollection(key, current)) {
                    //console.log("I am a container");
                }

                //console.log("I have children");
                console.log("-----------------------------");
                hasType = (current.hasOwnProperty('type') && current.type !== '') ? current.type : false;
                if (hasType) {
                    maxNesting = (this.isVoid(current.type)) ? true : false;
                }
                if (maxNesting === false) {
                    next = level;
                    if (hasType) {
                        this._prevLevel = level;
                        next = level + 1;
                    }
                    this.build(children, next);
                }
            } else {
                if (current.hasOwnProperty('type') && current.type !== '') {
                    //console.log("I am childless");
                    console.log("-----------------------------");
                }
            }
            iterator.next();
        } while (iterator.hasNext());
        if (current && current.hasOwnProperty('type') && current.type !== '') {
            if (iterator.hasNext() === false) {
                //console.log("<----- MOVING UP " + parseInt(level - prev) + " LEVELS ----->");
                //console.log("Diff: " + parseInt(level - prev));
                //console.log("Level: " + level);
                //console.log("Prev: " + prev);
                builder.parent();
                this._prevLevel = level - prev;
            }
        }
    },

相关的附加信息
DocumentDirector: function (builder) {
    var director = Object.create({
        _builder: {},
        _validCollection: ['sections', 'forms', 'fieldsets', 'rows', 'fields'],
        _validAttributes: ['id', 'name'],
        _voidElements: ['base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'img', 'input', 'link', 'meta', 'param', 'source'],
        _inputElements: ['text', 'select', 'radio', 'checkbox', 'textarea', 'datepicker', 'yesno'],
        _prevLevel: 0,
        init: function (builder) {
            builder = builder || '';
            if (builder) this.setBuilder(builder);
            return this;
        },
        setBuilder: function (builder) {
            this._builder = builder;
            return this;
        },
        getBuilder: function () {
            return this._builder;
        },
        isCollection: function (key, node) {
            // Collections MUST have a key, as they don't have a type
            key = key || '';
            if (key === '') return false;
            return (node.constructor === Array && this._validCollection.indexOf(key) !== -1) ? true : false;
        },
        isVoid: function (type) {
            var isVoidElement = false;
            if (this._voidElements.indexOf(type.toString()) !== -1) {
                isVoidElement = true;
            }
            if (this._inputElements.indexOf(type.toString()) !== -1) {
                isVoidElement = true;
            }
            return isVoidElement;
        },
        build: function (obj, level) {
            var that = this,
                builder = this.getBuilder(),
                iterator = Object.create(App.Utilities.RecursiveIterator(obj)),
                level = level || 0,
                prev = this._prevLevel || 0,
                next = level,
                key,
                current,
                attributes,
                pair,
                children,
                hasType = false,
                maxNesting = false;
            do {
                // Get the current node and build it
                key = iterator.key();
                current = iterator.current();
                console.log(iterator.key() + ": " + iterator.current().toSource());
                if (current.hasOwnProperty('type') && current.type !== '') {
                    if (current.hasOwnProperty('type') && current.type !== '') {
                        //console.log(iterator.current().type);
                        attributes = {};
                        $.each(current, function(key, value) {
                            if (that._validAttributes.indexOf(key) !== -1) {
                                attributes[key] = value;
                            }
                        });
                        //console.log(attributes);
                    }
                    //console.log("Prev: " + prev);
                    console.log("Level: " + level);
                    if (level == prev && level == 0) {
                        builder.append(current.type, attributes);
                    } else if (level == prev && level > 0) {
                        builder.add(current.type, attributes);                          
                    } else if (level < prev && level > 0) {
                        builder.parent().append(current.type, attributes);
                    } else if (level > prev && level > 0) {
                        builder.append(current.type, attributes);
                    } else {
                        // Do nothing
                    }
                    if (current.hasOwnProperty('label')) {
                        var label = builder.addBefore(builder.getCurrent(), 'label');
                        builder.text(current.label, label);
                    }
                }
                // Are there child nodes? If so, recurse...
                if (iterator.hasChildren()) {
                    children = iterator.getChildren();
                    if (this.isCollection(key, current)) {
                        //console.log("I am a container");
                    }

                    //console.log("I have children");
                    console.log("-----------------------------");
                    hasType = (current.hasOwnProperty('type') && current.type !== '') ? current.type : false;
                    if (hasType) {
                        maxNesting = (this.isVoid(current.type)) ? true : false;
                    }
                    if (maxNesting === false) {
                        next = level;
                        if (hasType) {
                            this._prevLevel = level;
                            next = level + 1;
                        }
                        this.build(children, next);
                    }
                } else {
                    if (current.hasOwnProperty('type') && current.type !== '') {
                        //console.log("I am childless");
                        console.log("-----------------------------");
                    }
                }
                iterator.next();
            } while (iterator.hasNext());
            if (current && current.hasOwnProperty('type') && current.type !== '') {
                if (iterator.hasNext() === false) {
                    //console.log("<----- MOVING UP " + parseInt(level - prev) + " LEVELS ----->");
                    //console.log("Diff: " + parseInt(level - prev));
                    //console.log("Level: " + level);
                    //console.log("Prev: " + prev);
                    builder.parent();
                    this._prevLevel = level - prev;
                }
            }
        },
        getDocument: function () {
            return this.getBuilder().getDocument();
        }
    });
    return director.init(builder);
}

DOMBuilder(注入到DocumentDirector——处理DOM操作):

DOMBuilder: function () {
    var domBuilder = Object.create({
        _document: {},
        _rootNode: {},
        _currentNode: {},
        init: function (viewModel, domBuilder) {
            var doc, rootNode;
            this._document = doc = document.createDocumentFragment();
            this._rootNode = rootNode = this.appendNode(doc, 'section');
            this._currentNode = rootNode;
            return this;
        },
        /* Generic methods
        ------------------ */
        /**
         *  Returns the document
         *
         *  @return DOM Node: The DOM document fragment
         */
        getDocument: function () {
            return this._document;
        },
        /**
         *  Returns the root node
         *
         *  @return DOM Node: The root node
         */
        getRoot: function () {
            return this._rootNode;
        },
        /**
         *  Returns the current node
         *
         *  @return DOM Node: The current node
         */
        getCurrent: function () {
            return this._currentNode;
        },
        /**
         *  Sets the current node
         *
         *  @return DOM Node: The current node
         */
        setCurrent: function (node) {
            this._currentNode = node;
            return this;
        },
        /**
         *  Returns the parent of the current node
         *
         *  @return DOM Node: The parent node
         */
        getParent: function () {
            return this._currentNode.parentNode;
        },
        /**
         *  Creates and appends a node inside a specified parent
         *  
         *  @ref DOM Node: The insertion target for the new node
         *  @type String: A valid HTML5 element type
         *  @attributes Object: And object containing key-value pairs of attributes and values
         *
         *  @return DOM Node: The newly created node
         */
        appendNode: function (ref, type, attributes) {
            var node = document.createElement(type);
            ref.appendChild(node);
            //this._currentNode = node;
            return node;
        },
        /**
         *  Creates a node and inserts it before the specified element
         *  
         *  @ref DOM Node: A reference node for inserting the new node
         *  @type String: A valid HTML5 element type
         *  @attributes Object: And object containing key-value pairs of attributes and values
         *
         *  @return DOM Node: The newly created node
         */
        addBefore: function (ref, type, attributes) {
            var node = document.createElement(type);
            ref.parentNode.insertBefore(node, ref);
            //this._currentNode = node;
            return node;
        },
        /**
         *  Creates a node and inserts it after the specified element
         *  
         *  @parent DOM Node: A reference node for inserting the new node
         *  @type String: A valid HTML5 element type
         *  @attributes Object: And object containing key-value pairs of attributes and values
         *
         *  @return DOM Node: The newly created node
         */
        addAfter: function (ref, type, attributes) {
            var node = document.createElement(type);
            ref.parentNode.insertBefore(node, ref.nextSibling);
            //this._currentNode = node;
            return node;
        },
        /* Chainable methods
        ---------------------- */
        /**
         *  Creates and appends a node inside a specified parent
         *  
         *  @type String: A valid HTML5 element type
         *  @attributes Object: And object containing key-value pairs of attributes and values
         *  @ref DOM Node: (Optional) The insertion target for the new node
         *
         *  @return DOMBuilder: this
         */
        append: function (type, attributes, ref) {
            var parent, node;
            ref = ref || this._currentNode;
            node = document.createElement(type);
            ref.appendChild(node);
            this._currentNode = node;
            if (attributes) {
                // TODO: Use map instead
                $.each(attributes, function (key, value) {
                    node.setAttribute(key, value);
                });
            }
            return this;
        },
        /**
         *  Creates a node and inserts it after the specified element
         *  
         *  @type String: A valid HTML5 element type
         *  @attributes Object: And object containing key-value pairs of attributes and values
         *  @ref DOM Node: A reference node for inserting the new node
         *
         *  @return DOMBuilder: this
         */
        add: function (type, attributes, ref) {
            var ref, node;
            ref = ref || this._currentNode;
            node = document.createElement(type);
            //console.log(ref);
            ref.parentNode.insertBefore(node, ref.nextSibling);
            this._currentNode = node;
            if (attributes) {
                // TODO: Use map instead
                $.each(attributes, function (key, value) {
                    node.setAttribute(key, value);
                });
            }
            return this;
        },
        /**
         *  Creates a node and inserts it before the specified element
         *  
         *  @type String: A valid HTML5 element type
         *  @attributes Object: And object containing key-value pairs of attributes and values
         *  @ref DOM Node: A reference node for inserting the new node
         *
         *  @return DOMBuilder: this
         */
        before: function (type, attributes, ref) {
            var ref, node;
            ref = ref || this._currentNode;
            node = document.createElement(type);
            ref.parentNode.insertBefore(node, ref);
            this._currentNode = node;
            if (attributes) {
                // TODO: Use map instead
                $.each(attributes, function (key, value) {
                    node.setAttribute(key, value);
                });
            }
            return this;
        },
        /**
         *  Sets the internal current node reference to the parent of the current node
         *
         *  @return DOMBuilder: this
         */
        parent: function () {
            var ref, node;
            ref = ref || this._currentNode;
            this._currentNode = this._currentNode.parentNode;
            return this;
        },
        /**
         *  Sets the text for a specified node
         *
         *  @return DOMBuilder: this
         */
        text: function (value, ref) {
            var node = document.createTextNode(value);
            ref.appendChild(node);
            return this;
        }
    });
    return domBuilder.init();
}
引导:

var formLayout = [
        {
            type: 'section',
            id: 'header',
        },
        {
            type: 'div',
            id: 'center-pane',
            forms: [
                {
                    type: 'section',
                    id: 'claim-history',
                    label: 'Add Claim History',
                    templates: [
                        {
                            fieldsets: [
                                {
                                    type: 'fieldset',
                                    id: 'claim-history-details',
                                    label: 'Details',
                                    rows: [
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_LossDate',
                                                    name: 'LossDate',
                                                    label: 'Date of Loss',
                                                    type: 'datepicker', // Types: any standard HTML5 form element or Kendo UI widget
                                                    className: 'small',
                                                    data: {
                                                        role: 'datepicker', // Redundant proxy for type parameter
                                                        bind: {
                                                            value: 'datePickerValue' // Bind Kendo UI Datepicker
                                                        }
                                                    }
                                                }
                                            ]
                                        },
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_TypeOfLoss',
                                                    name: 'TypeOfLoss',
                                                    label: 'Type of Loss',
                                                    type: 'select', // This could be a combobox, really
                                                    className: ''
                                                }
                                            ]
                                        },
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_AtFaultPercentage',
                                                    name: 'AtFaultPercentage',
                                                    label: 'At Fault %',
                                                    type: 'text',
                                                    className: 'tiny'
                                                },
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_CauseOfLoss',
                                                    name: 'CauseOfLoss',
                                                    label: 'Cause of Loss',
                                                    type: 'select',
                                                    className: ''
                                                }
                                            ]
                                        },
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: '',
                                                    name: '',
                                                    label: 'Charges Laid',
                                                    type: 'yesno',
                                                    className: '',
                                                    data: {
                                                        bind: {
                                                            source: 'yesno',
                                                            value: 'datePickerValue' // Bind Kendo UI Datepicker
                                                        }
                                                    }
                                                },
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_ChargesLaid',
                                                    name: 'ChargesLaid',
                                                    label: 'Details',
                                                    type: 'textarea',
                                                    className: 'tiny'
                                                }
                                            ]
                                        }
                                    ],
                                },
                                {
                                    type: 'fieldset',
                                    id: 'claim-history-amounts',
                                    label: 'Amounts',
                                    rows: [
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_SecA',
                                                    name: 'SecA',
                                                    label: 'Sec A',
                                                    type: 'yesno',
                                                    className: 'tiny'
                                                },
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_SecATotal',
                                                    name: 'SecATotal',
                                                    label: 'Sec A Total',
                                                    type: 'text',
                                                    className: 'small'
                                                }
                                            ]
                                        },
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_SecC',
                                                    name: 'SecC',
                                                    label: 'Sec C',
                                                    type: 'text',
                                                    className: 'tiny'
                                                },
                                            ]
                                        },
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_IgnoreReason',
                                                    name: 'IgnoreReason',
                                                    label: 'Ignore Reason',
                                                    type: 'select',
                                                    className: ''
                                                }
                                            ]
                                        }
                                    ],
                                },
                                {
                                    type: 'fieldset',
                                    id: 'claim-history-vehicle-driver',
                                    label: 'Vehicle and Driver',
                                    rows: [
                                        {
                                            type: 'div',
                                            fields: [
                                                {
                                                    // Any standard html5 attributes can be defined
                                                    id: 'MainContent_DetailsContent_ClaimVehicle',
                                                    name: 'ClaimVehicle',
                                                    label: 'Claim Vehicle',
                                                    type: 'select',
                                                    className: ''
                                                }
                                            ]
                                        }
                                    ],
                                } // END fieldset
                            ] // END fieldsets  
                        } // END template
                    ] // END templates
                } // END form
            ] // END forms
        },
        {
            type: 'div',
            id: 'left-pane'
        },
        {
            type: 'div',
            id: 'right-pane'
        }
    ]; // END sections
var formBuilder = Object.create(App.Layout.Form.DOMBuilder());
var formDirector = Object.create(App.Layout.Form.DocumentDirector(formBuilder));
formDirector.build(formLayout);
document.getElementById("test").appendChild(formDirector.getDocument());

解决了它——对于每个连续的递归,我应该附加第一个子节点(传入对象的),并添加其余节点。添加了一个isFirst变量来跟踪。

工作小提琴如果有人感兴趣…http://jsfiddle.net/blogshop/DSxft/

build: function (obj, level) {
            var that = this,
                builder = this.getBuilder(),
                iterator = Object.create(App.Utilities.RecursiveIterator(obj)),
                level,
                key,
                current,
                attributes,
                pair,
                children,
                isFirst = true,
                hasType = false,
                maxNesting = false;
            do {
                // Get the current node and build it
                key = iterator.key();
                current = iterator.current();
                hasType = (current.hasOwnProperty('type') && current.type !== '') ? current.type : false;
                if (hasType) {
                    maxNesting = (this.isVoid(current.type)) ? true : false;
                    attributes = {};
                    $.each(current, function(key, value) {
                        if (that._validAttributes.indexOf(key) !== -1) {
                            attributes[key] = value;
                        }
                    });
                    if (isFirst == true) {
                        builder.append(current.type, attributes);
                        isFirst = false;
                    } else {
                        builder.add(current.type, attributes)
                    }
                    // Prepend label to field
                    if (current.hasOwnProperty('label')) {
                        var label = builder.addBefore(builder.getCurrent(), 'label');
                        builder.text(current.label, label);
                    }
                }
                // Are there child nodes? If so, recurse...
                if (iterator.hasChildren()) {
                    children = iterator.getChildren();
                    if (maxNesting === false) {                     
                        // Recurse
                        level = (hasType) ? level + 1 : level;
                        this.build(children, level);
                    }
                }
                // Move to the next node
                iterator.next();
            } while (iterator.hasNext());
            if (current && current.hasOwnProperty('type') && current.type !== '') {
                if (iterator.hasNext() === false) builder.parent();
            }
        },