/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
//////////////////// _JsonTreeNodeDataSource ///////////////////////////////////
/**
* Helper class to implement sort recursive features for tree.
* @constructor
* @private
*/
oj._JsonTreeNodeDataSource = function()
{
this.id = null;
this.depth = 0;
this.parent = null;
this.children = [];
this.title = null;
this.attr = null;
this.leaf = null;
};
/**
* Helper comparer method for ascending sort.
* @private
*/
oj._JsonTreeNodeDataSource.prototype._ascending = function(key)
{
return function(a, b)
{
if (a.attr && b.attr)
{
if (a.attr[key] && b.attr[key])
{
return a.attr[key] < b.attr[key] ? -1 : a.attr[key] === b.attr[key] ? 0 : 1;
}
}
return a[key] < b[key] ? -1 : a[key] === b[key] ? 0 : 1;
}
};
/**
* Helper comparer method for descending sort.
* @private
*/
oj._JsonTreeNodeDataSource.prototype._descending = function(key)
{
return function(a, b)
{
if (a.attr && b.attr)
{
if (a.attr[key] && b.attr[key])
{
return a.attr[key] < b.attr[key] ? 1 : a.attr[key] === b.attr[key] ? 0 : -1;
}
}
return a[key] < b[key] ? 1 : a[key] === b[key] ? 0 : -1;
}
};
/**
* Helper method for recursive sort.
* @param {Object} criteria the sort criteria.
* @param {Object} criteria.key the key identifying the attribute to sort on
* {string} criteria.direction the sort direction, valid values are "ascending", "descending".
* @private
*/
oj._JsonTreeNodeDataSource.prototype._sortRecursive = function(criteria)
{
var key = criteria['key'];
if (criteria['direction'] === 'ascending')
{
this.children.sort(this._ascending(key));
}
else if (criteria['direction'] === 'descending')
{
this.children.sort(this._descending(key));
}
for (var i = 0, l = this.children.length; i < l; i++)
{
this.children[i]._sortRecursive(criteria);
}
return this;
};
///////////// JsonTreeDataSource //////////////////
/**
* A json object based implementation of the TreeDataSource.
* @param {Object} data the json object
* @constructor
* @export
* @extends oj.TreeDataSource
*/
oj.JsonTreeDataSource = function(data)
{
var tree;
tree = new oj._JsonTreeNodeDataSource(); // that's the root node
if (!data.id)
{
tree.id = "root";
}
this.data = this._createTreeDataSource({count:0}, tree, data);
oj.JsonTreeDataSource.superclass.constructor.call(this, tree);
};
// Subclass from oj.TreeDataSource
oj.Object.createSubclass(oj.JsonTreeDataSource, oj.TreeDataSource, "oj.JsonTreeDataSource");
/**
* Initial the json object based data source.
* @export
*/
oj.JsonTreeDataSource.prototype.Init = function()
{
// call super
oj.JsonTreeDataSource.superclass.Init.call(this);
};
/**
* Returns tree based structure/object from json data.
* @param {Object} c an object to keep track of the count
* @param {Object} target the final tree structure.
* @param {Object} source the json object.
* @param {number=} depth used recursively for depth calculation.
* @return target
*/
oj.JsonTreeDataSource.prototype._createTreeDataSource = function(c, target, source, depth)
{
var children, node, child, prop, propr, prp, j;
if (!depth)
{
depth = 0;
}
for (prop in source)
{
if (prop == "children" || (depth == 0 && source instanceof Array))
{
if (depth == 0 && source instanceof Array)
{
children = source;
}
else
{
children = source[prop];
}
depth++;
for (j = 0; j < children.length; j++)
{
child = children[j];
node = new oj._JsonTreeNodeDataSource();
if (!child.id)
{
c.count++;
if (!child.attr)
{
node.id = 'rid_' + c.count;
}
else if (!child.attr.id)
{
child.attr.id = 'rid_' + c.count;
}
}
for (propr in child)
{
for (prp in node)
{
if (propr == prp && propr != "children")
{
node[prp] = child[propr];
}
if (prp == "depth")
{
node[prp] = depth;
}
}
}
target.children.push(node);
for (prp in child)
{
if (prp == "children")
{
this._createTreeDataSource(c, target.children[j], child, depth);
}
}
}
}
}
return target;
};
/**
* Returns the number of children for a specified parent. If the value returned is not >= 0 then it is automatically assumed
* that the child count is unknown.
* @param {Object} parentKey the parent key. Specify null if inquiring child count of the root.
* @return {number} the number of children for the specified parent.
* @export
*/
oj.JsonTreeDataSource.prototype.getChildCount = function(parentKey)
{
var parent;
if (!parentKey)
{
parentKey = this.data.id;
}
parent = this._searchTreeById(this.data, parentKey);
if (parent.children)
{
return parent.children.length;
}
else
{
return 0;
}
};
/**
* Fetch the children
* @param {Object} parentKey the parent key. Specify null if fetching children from the root.
* @param {Object} range information about the range, it must contain the following properties: start, count.
* @param {number} range.start the start index of the range in which the children are fetched.
* @param {number} range.count the size of the range in which the children are fetched.
* @param {Object} callbacks the callbacks to be invoke when fetch children operation is completed. The valid callback
* types are "success" and "error".
* @param {function(oj.JsonNodeSet)} callbacks.success the callback to invoke when fetch completed successfully.
* @param {function({status: Object})} callbacks.error the callback to invoke when fetch children failed.
* @param {Object=} options optional parameters for this operation.
* @param {boolean=} options.queueOnly true if this fetch request is to be queued and not execute yet. The implementation must maintain
* the order of the fetch operations. When queueOnly is false/null/undefined, any queued fetch operations are then
* flushed and executed in the order they are queued. This flag is ignored if the datasource does not support batching.
* @export
*/
oj.JsonTreeDataSource.prototype.fetchChildren = function(parentKey, range, callbacks, options)
{
var i, childStart, childEnd, nodeSet, results, parent, node;
childStart = 0;
childEnd = 0;
results = [];
if (!parentKey)
{
parentKey = this.data.id;
}
parent = this._searchTreeById(this.data, parentKey);
if (!range)
{
range = [];
range['start'] = 0;
range['count'] = parent.children.length;
}
if (!range['count'])
{
range['count'] = parent.children.length;
}
if (!range['start'])
{
range['start'] = 0;
}
childStart = range['start'];
childEnd = Math.min(parent.children.length, childStart + range['count']);
// now populate results from data array
for (i = childStart; i < childEnd; i += 1)
{
node = new oj._JsonTreeNodeDataSource();
if(parent.children[i].attr)
{
node.attr = parent.children[i].attr;
}
if(parent.children[i].id)
{
node.id = parent.children[i].id;
}
if(parent.children[i].depth)
{
node.depth = parent.children[i].depth;
}
if(parent.children[i].title)
{
node.title = parent.children[i].title;
}
if(parent.children[i].parent)
{
node.parent = parent.children[i].parent;
}
if(parent.children[i].children.length > 0)
{
node.leaf = false;
}
else
{
node.leaf = true;
}
results.push(node);
}
// invoke callback
nodeSet = new oj.JsonNodeSet(childStart, childEnd, results, parentKey, parent.depth);
// invoke original success callback
if (callbacks != null && callbacks['success'] != null)
{
callbacks['success'].call(null, nodeSet);
}
};
/**
* Fetch all children and their children recursively from a specified parent.
* @param {Object} parentKey the parent key. Specify null to fetch everything from the root (i.e. expand all)
* @param {Object} callbacks the callbacks to be invoke when fetch children operation is completed. The valid callback
* types are "success" and "error".
* @param {function(oj.JsonNodeSet)} callbacks.success the callback to invoke when fetch completed successfully.
* @param {function({status: Object})} callbacks.error the callback to invoke when fetch children failed.
* @param {Object=} maxCount the maximum number of children to fetch. If a non-positive number is specified, then the value is ignored and
* there is no maximum fetch count.
* @export
*/
oj.JsonTreeDataSource.prototype.fetchDescendants = function(parentKey, callbacks, maxCount)
{
var range, i, childStart, childEnd, nodeSet, results, parent;
childStart = 0;
childEnd = 0;
results = [];
if (!parentKey)
{
parentKey = this.data.id;
}
parent = this._searchTreeById(this.data, parentKey);
range = [];
range['start'] = 0;
range['count'] = parent.children.length;
childStart = range['start'];
childEnd = Math.min(parent.children.length, childStart + range['count']);
// now populate results from data array
for (i = childStart; i < childEnd; i += 1)
{
if(parent.children[i].children.length > 0)
{
parent.children[i].leaf = false;
}
else
{
parent.children[i].leaf = true;
}
results.push(parent.children[i]);
}
// invoke callback
nodeSet = new oj.JsonNodeSet(0, results.length, results, parentKey, parent.depth);
// invoke original success callback
if (callbacks != null && callbacks['success'] != null)
{
callbacks['success'].call(null, nodeSet);
}
};
/**
* Moves a node from one location to another (different position within the same parent or a completely different parent)
* @param {Object} nodeToMove the key of the node to move
* @param {Object} referenceNode the key of the reference node which combined with position are used to determine
* the destination of where the node should moved to.
* @param {number|string} position The position of the moved node relative to the reference node.
* This can be a string: "before", "after", "inside", "first", "last", or the zero based index to position
* the element at a specific point among the reference node's current children.
* @param {function()} callbacks.success the callback to invoke when the move completed successfully.
* @param {function({status: Object})} callbacks.error the callback to invoke when move failed.
* @export
*/
oj.JsonTreeDataSource.prototype.move = function(nodeToMove, referenceNode, position, callbacks)
{
var moveNode, refNode, moveNodeKey, refNodeKey, pos, parent, index;
pos = position;
moveNodeKey = nodeToMove;
refNodeKey = referenceNode;
if ((!refNodeKey || refNodeKey == this.data.id))
{
if (pos != "inside")
{
oj.Logger.log("Error: root can not be the reference node if position equals to " + pos);
return;
}
else
{
if (!refNodeKey)
{
refNodeKey = this.data.id;
}
}
}
//get node to move;
moveNode = this._searchTreeById(null, moveNodeKey);
//if the moveNode doesn't contain the reference node as its sub-tree the action is allowed
if (!this._searchTreeById(moveNode, refNodeKey))
{
refNode = this._searchTreeById(null, refNodeKey);
parent = this._getParentById(refNodeKey);
//remove moveNode from the original position;
this._removeFromTree(moveNode);
if (pos == "inside")
{
this._updateDepth(moveNode, moveNode.depth - (refNode.depth + 1));
refNode.children.push(moveNode);
}
else if (pos == "before")
{
this._updateDepth(moveNode, moveNode.depth - refNode.depth);
index = parent.children.indexOf(refNode);
if (index > -1)
{
if (index != 0)
{
parent.children.splice(index - 1, 0, moveNode);
}
else
{
parent.children.unshift(moveNode);
}
}
}
else if (pos == "after")
{
this._updateDepth(moveNode, moveNode.depth - refNode.depth);
index = parent.children.indexOf(refNode);
if (index > -1)
{
parent.children.splice(index, 0, moveNode);
}
}
else if (pos == "first")
{
this._updateDepth(moveNode, moveNode.depth - refNode.depth);
parent.children.unshift(moveNode);
}
else if (pos == "last")
{
//update depth recursively
this._updateDepth(moveNode, moveNode.depth - refNode.depth);
parent.children.push(moveNode);
}
// invoke original success callback
if (callbacks != null && callbacks['success'] != null)
{
callbacks['success'].call(null, this.data);
}
}
else
{
oj.Logger.log("Error: the node to move contains the reference node as its sub-tree.");
}
};
/**
* Performs a sort operation on the tree data.
* @param {Object} criteria the sort criteria. It must contain the following properties: key, direction
* @param {Object} criteria.key the key identifying the attribute (column) to sort on
* {string} criteria.direction the sort direction, valid values are "ascending", "descending", "none" (default)
* @param {function()} callbacks.success the callback to invoke when the sort completed successfully.
* @param {function({status: Object})} callbacks.error the callback to invoke when sort failed.
* @export
*/
oj.JsonTreeDataSource.prototype.sort = function(criteria, callbacks)
{
var parent, parentKey;
parentKey = null;
if (!parentKey)
{
parentKey = this.data.id;
}
parent = this._searchTreeById(this.data, parentKey);
parent._sortRecursive(criteria);
// invoke original success callback
if (callbacks != null && callbacks['success'] != null)
{
callbacks['success'].call(null, parent);
}
};
/**
* @param {string|number} refNodeKey
* @param {Object=} currNode
* @return {Object|null} the node with required key value.
* @private
*/
oj.JsonTreeDataSource.prototype._getParentById = function(refNodeKey, currNode)
{
var i, parent = null, parentIsFound = false;
if ((refNodeKey == this.data.id))
{
return null;
}
if (!currNode)
{
currNode = this.data;
}
if (currNode.children && currNode.children.length > 0)
{
for (i = 0; i < currNode.children.length; i++)
{
if ((currNode.children[i].id && currNode.children[i].id == refNodeKey) || (currNode.children[i].attr && currNode.children[i].attr.id == refNodeKey))
{
parentIsFound = true;
return currNode;
break;
}
}
if (!parentIsFound)
{
for (i = 0; i < currNode.children.length; i++)
{
parent = this._getParentById(refNodeKey, currNode.children[i]);
if (parent)
{
parentIsFound = true;
return parent;
break;
}
}
}
}
return parent;
};
/**
* Helper method to traverse through the tree and return the node with required key.
* @param {Object|null} currChild the start tree node.
* @param {Object|null} parentKey the node key for search.
* @return {Object|null} the node with required key value.
* @private
*/
oj.JsonTreeDataSource.prototype._searchTreeById = function(currChild, parentKey)
{
var i, result = null;
if (!currChild)
{
currChild = this.data;
}
if ((currChild.id && currChild.id == parentKey) || (currChild.attr && currChild.attr.id == parentKey))
{
return currChild;
}
else if (currChild.children != null)
{
for (i = 0; i < currChild.children.length; i++)
{
if (result)
{
return result;
break;
}
if ((currChild.children[i].id && currChild.children[i].id == parentKey) || (currChild.children[i].attr && currChild.children[i].attr.id == parentKey))
{
result = currChild.children[i];
}
else
{
result = this._searchTreeById(currChild.children[i], parentKey);
}
}
return result;
}
return result;
};
/**
* Helper method to update the node's depth alongside with its children.
* @param {Object} currChild the node to update.
* @param {number} offset the difference between current and updated depth values.
* @private
*/
oj.JsonTreeDataSource.prototype._updateDepth = function(currChild, offset)
{
var i;
currChild.depth = currChild.depth - offset;
if (currChild.children && currChild.children.length != 0)
{
for (i = 0; i < currChild.children.length; i++)
{
this._updateDepth(currChild.children[i], offset);
}
}
};
/**
* Helper method to remove node from the tree (based on depth value).
* @param {Object} currChild the node to remove.
* @private
*/
oj.JsonTreeDataSource.prototype._removeFromTree = function(currChild)
{
var parent, index, key;
if (currChild.id)
{
key = currChild.id;
}
else if (currChild.attr)
{
key = currChild.attr.id;
}
parent = this._getParentById(key);
if (!parent)
{
parent = this.data;
}
index = parent.children.indexOf(currChild);
if (index > -1)
{
parent.children.splice(index, 1);
}
};
/**
* Determines whether this TreeDataSource supports the specified feature.
* @param {string} feature the feature in which its capabilities is inquired. Currently the valid features "sort",
* "move", "fetchDescendants", "batchFetch"
* @return {string|null} the name of the feature. Returns null if the feature is not recognized.
* For "sort", the valid return values are: "default", "none".
* For "fetchDescendants", the valid return values are: "enable", "disable", "suboptimal".
* For "move", the valid return values are: "default", "none".
* For "batchFetch", the valid return values are: "enable", "disable".
* @export
*/
oj.JsonTreeDataSource.prototype.getCapability = function(feature)
{
if (feature === 'fetchDescendants')
{
return "enable";
}
else if (feature === 'sort')
{
return "default";
}
else if (feature === 'batchFetch')
{
return "disable";
}
else if (feature === 'move')
{
return "full";
}
else
{
return null;
}
};
Source: src/main/javascript/oracle/oj/ojdatacollection-common/JsonTreeDataSource.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01