📄 tree.js
字号:
},//> @method tree.setupParentLinks() (A)// Make sure the parent links are set up in all children of the root.// This lets you create a simple structure without back-links, while// having the back-links set up automatically// @group ancestry //// @param [node] (TreeNode) parent node to set up child links to// (default is this.root)//<setupParentLinks : function (node) { // if the node wasn't passed in, use the root if (!node) node = this.root; this.nodeIndex[node[this.idField]] = node; // get the children array of the node var children = node[this.childrenProperty]; if (children) { // current assumption whenever loading subtrees is that if any children are returned // for a node, it's the complete set, and the node is marked "loaded" this.setLoadState(node, isc.Tree.LOADED); // handle the children property containing a single child object. if (!isc.isAn.Array(children)) { children = node[this.childrenProperty] = [children]; } } // if no children defined, bail if (!children || children.length == 0) return; // for each child for (var i = 0, length = children.length, child; i < length; i++) { child = children[i]; // if the child is null, skip it if (!child) continue; // set the parentId on the child if it isn't set already if (child[this.parentIdField] == null && node[this.idField] != null) child[this.parentIdField] = node[this.idField]; // set the child's parent to the parent child[this.parentProperty] = node; // if the child is a folder, call setupParentLinks recursively on the child if (this.isFolder(child)) this.setupParentLinks(child); else this.nodeIndex[child[this.idField]] = child; // link into the nodeIndex }},// Build or add nodes to a Tree by linking records together by unique ids.//// This method handles receiving a mixture of leaf nodes and parent nodes, even out of order and// with any tree depth. However, every node must have an id which is unique across all nodes,// not just within its siblings.//// Given a list of nodes which "point to" each other via a "parentId" property that matches a// parent's "idProperty", // - adds all nodes to an index by ID (this.nodeIndex)// - adds all nodes to auto-created "children" arrays on their parents, or on this.root for root// nodes// - marks any parent that receives children as "loaded", for load on demand (it's assumed for now// that all children of a given parent are loaded at once)// - can automatically mark parents as folders, as they acquire children, if they aren't already so// marked// - XXX legacy: creates the "nameProperty" that the Tree currently relies on for several things// NOTE: this is NOT similar to tree.setupParentLinks which assumes that there are already// node.children arrays.// XXX handle multi-column (multi-property) primary keys////> @method tree.linkNodes()//// This method is provided as a mechanism to link new nodes into the tree of modelType// "parent". This method takes a list of nodes that must contain at a minimum a unique ID// (keyed by +link{attr:Tree.idField}) and a parent ID (keyed by// +link{attr:Tree.parentIdField}). Based on this information, the list of nodes is integrated// into the tree structure.//// @param nodes (Array of TreeNode) list of nodes to link into the tree.//// @see attr:Tree.data// @see attr:Tree.modelType// @visibility external//<connectByParentID : function (records, idProperty, parentIdProperty, rootValue, isFolderProperty) { this.linkNodes(records, idProperty, parentIdProperty, rootValue, isFolderProperty); },connectByParentId : function (records, idProperty, parentIdProperty, rootValue, isFolderProperty) { this.linkNodes(records, idProperty, parentIdProperty, rootValue, isFolderProperty);},linkNodes : function (records, idProperty, parentIdProperty, rootValue, isFolderProperty, contextNode) { if (this.modelType == "fields") { this.connectByFields(records); return; } // XXX impedance mismatch: // - Tree instances expect each node to have a children array, and think of each node as having // a "nameProperty" that uniquely identifies it for the level. A node's nameProperty should end // in the delimeter ("foo/") if it's a folder. // - Our nodes do not have children arrays, and have _globally_ unique Ids (the primary key), by // which they can be linked into a tree. When dealing with mixed folder/node trees, we have a // separate property "isFolderProperty" which marks something as a folder. // So we derive "children" arrays on the fly, and derive the tree's notion of "name" by // using the primary key and adding a "/" for things marked as folders. records = records || this.data; idProperty = idProperty || this.idField; parentIdProperty = parentIdProperty || this.parentIdField; rootValue = rootValue || this.rootValue; var unplacedChildren = records; var logDebugEnabled = this.logIsDebugEnabled("treeLinking"); while (unplacedChildren.length > 0) { // try to place the currently unplaced children var nodes = unplacedChildren, newParentFound = false; unplacedChildren = []; for (var i = 0; i < nodes.length; i++) { var node = nodes[i], parentId = node[parentIdProperty], parent = this.nodeIndex[parentId]; if (parent != null) { if (logDebugEnabled) { this.logDebug("found parent " + parent[idProperty] + " for child " + node[idProperty], "treeLinking"); } // found this node's parent this._add(node, parent); // we've found a parent newParentFound = true; } else if (parentId == null && contextNode) { // if a contextNode was supplied, use that as the default parent node for all // nodes that are missing a parentId - this is for loading immediate children // only, without specifying a parentId this._add(node, contextNode); newParentFound = true; } else if (parentId != rootValue && parentId != null && parentId != -1 && parentId != isc.emptyString) { if (logDebugEnabled) { this.logDebug("couldn't place child: " + node[idProperty], "treeLinking"); } // this node is marked as having a parent, but we haven't found it yet unplacedChildren.add(node); } else { if (logDebugEnabled) { this.logDebug("root node: " + node[idProperty], "treeLinking"); } // this is a root node this._add(node, this.root); // we found a parent for this node (its parent is root) newParentFound = true; } } if (logDebugEnabled) { this.logDebug("end of linking pass: " + unplacedChildren.length + " unplaced children" + ", found new parents: " + newParentFound, "treeLinking"); } if (!newParentFound && unplacedChildren.length > 0) { // we didn't manage to place any of the unplaced children - we must not have their // parents! //>DEBUG this.logWarn("Couldn't find parents: " + unplacedChildren.getProperty(parentIdProperty) + ", unplaced children: " + unplacedChildren.getProperty(idProperty) , "treeLinking"); //<DEBUG break; } } this._markAsDirty(); this.dataChanged();},connectByFields : function (data) { if (!data) data = this.data; // for each record for (var i = 0; i < data.length; i++) { this.addNodeByFields(data[i]); }},addNodeByFields : function (node) { // go through each field in this.fields in turn, descending through the hierarchy, creating // hierarchy as necessary var parent = this.root; for (var i = 0; i < this.fieldOrder.length; i++) { var fieldName = this.fieldOrder[i], fieldValue = node[fieldName]; var folderName = fieldValue, childNum = this.findChildNum(parent, folderName), child; if (childNum != -1) { //this.logWarn("found child for '" + fieldName + "':'" + fieldValue + "'"); child = this.getChildren(parent).get(childNum); } else { // if there's no child with this field value, create one //this.logWarn("creating child for '" + fieldName + "':'" + fieldValue + "'"); child = {}; child[this.nameProperty] = folderName; this.add(child, parent); this.convertToFolder(child); } parent = child; } // add the new node to the Tree //this.logWarn("adding node at: " + this.getPath(parent)); this.add(node, parent);},//> @method tree.getRoot()//// Returns the root node of the tree.//// @return (TreeNode) the root node//// @visibility external//<getRoot : function () { return this.root;},//> @method tree.setRoot()//// Set the root of the tree. //// @param newRoot (TreeNode) new root node// @param autoOpen (boolean) set to true to automatically open the new root node.//// @visibility external//<setRoot : function (newRoot, autoOpen) { // assign the new root this.root = newRoot; // avoid issues if setRoot() is used to re-root a Tree on one of it's own nodes if (newRoot && this.parentProperty.endsWith(this.ID)) newRoot[this.parentProperty] = null; if (this.rootValue == null) this.rootValue = this.root[this.idField]; // if the root node has no name, assign the path property to it. This is for backcompat // and also a reasonable default. var rootName = this.root[this.nameProperty]; if (rootName == null || rootName == isc.emptyString) this.root[this.nameProperty] = this.pathDelim; // the root node is always a folder if (!this.isFolder(this.root)) this.convertToFolder(this.root); // NOTE: this index is permanent, staying with this Tree instance so that additional sets of // nodes can be incrementally linked into the existing structure. this.nodeIndex = {}; // (re)create the structure of the Tree according to the model type if ("parent" == this.modelType) { // nodes provided as flat list (this.data); each record is expected to have a property // which is a globally unique ID (this.idField) and a property which has the globally // unique ID of its parent (this.parentIdField). // assemble the tree from this.data if (this.data) this.linkNodes(); } else if ("fields" == this.modelType) { // nodes provided as flat list; a list of fields, in order, defines the Tree if (this.data) this.connectByFields(); } else if ("children" == this.modelType) { // each parent has an array of children if (this.autoSetupParentLinks) this.setupParentLinks(); if (this.data) { var data = this.data; this.data = null; this.addList(data, this.root); } } else { this.logWarn("Unsupported modelType: " + this.modelType); } // open the new root if autoOpen: true passed in or this.autoOpenRoot is true. Suppress // autoOpen if autoOpen:false passed in if (autoOpen !== false && (this.autoOpenRoot || autoOpen)) { this.openFolder(newRoot); } // mark the tree as dirty and note that the data has changed this._markAsDirty(); this.dataChanged();},// get a copy of these nodes without all the properties the Tree scribbles on them.// Note the intent here is that children should in fact be serialized unless the caller has// explicitly trimmed them.getCleanNodeData : function (nodeList, includeChildren) { if (nodeList == null) return null; var nodes = [], wasSingular = false; if (!isc.isAn.Array(nodeList)) { nodeList = [nodeList]; wasSingular = true; } // known imperfections: // - by default, isFolderProperty is "isFolder", we write this into nodes and sent it when // saving // - we create empty children arrays for childless nodes, and save them for (var i = 0; i < nodeList.length; i++) { var treeNode = nodeList[i], node = {}; // copy the properties of the tree node, dropping some Tree/TreeGrid artifacts for (var propName in treeNode) { if (propName == this.parentProperty || // currently hardcoded propName == "_loadState" || propName == "_isc_tree" || // the openProperty and isFolderProperty are documented and settable, and if // they've been set should be saved, so only remove these properties if they // use the prefix that indicates they've been auto-generated (NOTE: this prefix // is obfuscated) propName.startsWith("_isOpen_") || propName.startsWith("_isFolder_") || // default nameProperty from ResultTree, which by default does not have // meaningful node names propName.startsWith("__nodePath") || // class of child nodes, set up by ResultTree propName == "_derivedChildNodeType" || // from selection model propName.startsWith("_selection_") || // Explicit false passed as 'includeChildren' param. (includeChildren == false && propName == this.childrenProperty)) continue; node[propName] = treeNode[propName]; // Clean up the children as well (if there are any) if (propName == this.childrenProperty && isc.isAn.Array(node[propName])) { node[propName] = this.getCleanNodeData(node[propName]); } } nodes.add(node); } if (wasSingular) return nodes[0]; return nodes;},
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -