我正在尝试将 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());