[Common UI] Document TreeNodeController

Add in-line documentation to TreeNodeController, and update
glossary with some clarifying definition.

Additionally, change name from tree-item to tree-node for
consistency.

Part of ongoing transition of commonUI bundles, WTD-574.
This commit is contained in:
Victor Woeltjen 2014-11-24 15:42:09 -08:00
parent e0b201aa17
commit 2f43e8cd7f
5 changed files with 71 additions and 12 deletions

View File

@ -122,8 +122,12 @@ correct usage.)
* _name_: When used as an object property, this refers to the human-readable * _name_: When used as an object property, this refers to the human-readable
name for a thing. (Most often used in the context of extensions, domain name for a thing. (Most often used in the context of extensions, domain
object models, or other similar application-specific objects.) object models, or other similar application-specific objects.)
* _navigation_: Refers to the current state of the application with respect
to the user's expressed interest in a specific domain object; e.g. when
a user clicks on a domain object in the tree, they are _navigating_ to
it, and it is thereafter considered the _navigated_ object (until the
user makes another such choice.)
* _space_: A name used to identify a persistence store. Interactions with * _space_: A name used to identify a persistence store. Interactions with
persistence with generally involve a `space` parameter in some form, to persistence with generally involve a `space` parameter in some form, to
distinguish multiple persistence stores from one another (for cases distinguish multiple persistence stores from one another (for cases
where there are multiple valid persistence locations available.) where there are multiple valid persistence locations available.)

View File

@ -68,8 +68,8 @@
"templateUrl": "templates/test.html" "templateUrl": "templates/test.html"
}, },
{ {
"key": "tree-item", "key": "tree-node",
"templateUrl": "templates/tree-item.html", "templateUrl": "templates/tree-node.html",
"uses": [ "action" ] "uses": [ "action" ]
}, },
{ {

View File

@ -1,6 +1,6 @@
<ul class="tree"> <ul class="tree">
<li ng-repeat="child in composition"> <li ng-repeat="child in composition">
<mct-representation key="'tree-item'" mct-object="child" parameters="parameters"> <mct-representation key="'tree-node'" mct-object="child" parameters="parameters">
</mct-representation> </mct-representation>
</li> </li>
</ul> </ul>

View File

@ -9,7 +9,24 @@ define(
"use strict"; "use strict";
/** /**
* The TreeNodeController supports the tree node representation;
* a tree node has a label for the current object as well as a
* subtree which shows (and is not loaded until) the node is
* expanded.
* *
* This controller tracks the following, so that the tree node
* template may update its state accordingly:
*
* * Whether or not the tree node has ever been expanded (this
* is used to lazily load, exactly once, the subtree)
* * Whether or not the node is currently the domain object
* of navigation (this gets highlighted differently to
* provide the user with visual feedback.)
*
* Additionally, this controller will automatically trigger
* node expansion when this tree node's _subtree_ will contain
* the navigated object (recursively, this becomes an
* expand-to-show-navigated-object behavior.)
* @constructor * @constructor
*/ */
function TreeNodeController($scope, navigationService) { function TreeNodeController($scope, navigationService) {
@ -17,30 +34,45 @@ define(
isNavigated = false, isNavigated = false,
hasBeenExpanded = false; hasBeenExpanded = false;
function idsEqual(objA, objB) { // Look up the id for a domain object. A convenience
return (objA === objB) || // for mapping; additionally does some undefined-checking.
(objA && objB && (objA.getId() === objB.getId())); function getId(obj) {
return obj && obj.getId && obj.getId();
} }
// Check if two domain objects have the same ID
function idsEqual(objA, objB) {
return getId(objA) === getId(objB);
}
// Get the parent of a domain object, as reported by
// its context capability. This is used to distinguish
// two different instances of a domain object that have
// been reached in different ways.
function parentOf(domainObject) { function parentOf(domainObject) {
var context = domainObject && var context = domainObject &&
domainObject.getCapability("context"); domainObject.getCapability("context");
return context && context.getParent(); return context && context.getParent();
} }
function getId(obj) {
return obj.getId();
}
// Verify that id paths are equivalent, staring at // Verify that id paths are equivalent, staring at
// index, ending at the end of the node path. // index, ending at the end of the node path.
function checkPath(nodePath, navPath, index) { function checkPath(nodePath, navPath, index) {
index = index || 0; index = index || 0;
// The paths overlap if we have made it past the
// end of the node's path; otherwise, check the
// id at the current index for equality and perform
// a recursive step for subsequent ids in the paths,
// until we exceed path length or hit a mismatch.
return (index >= nodePath.length) || return (index >= nodePath.length) ||
(idsEqual(navPath[index], nodePath[index]) && (idsEqual(navPath[index], nodePath[index]) &&
checkPath(nodePath, navPath, index + 1)); checkPath(nodePath, navPath, index + 1));
} }
// Check if the navigated object is in the subtree of this
// node's domain object, by comparing the paths reported
// by their context capability.
function isOnNavigationPath(nodeObject, navObject) { function isOnNavigationPath(nodeObject, navObject) {
var nodeContext = nodeObject && var nodeContext = nodeObject &&
nodeObject.getCapability('context'), nodeObject.getCapability('context'),
@ -58,14 +90,20 @@ define(
return false; // No context to judge by return false; // No context to judge by
} }
// Consider the currently-navigated object and update
// parameters which support display.
function checkNavigation() { function checkNavigation() {
var nodeObject = $scope.domainObject; var nodeObject = $scope.domainObject;
// Check if we are the navigated object. Check the parent
// as well to make sure we are the same instance of the
// navigated object.
isNavigated = isNavigated =
idsEqual(nodeObject, navigatedObject) && idsEqual(nodeObject, navigatedObject) &&
idsEqual(parentOf(nodeObject), parentOf(navigatedObject)); idsEqual(parentOf(nodeObject), parentOf(navigatedObject));
// Expand if necessary // Expand if necessary (if the navigated object will
// be in this node's subtree)
if (isOnNavigationPath(nodeObject, navigatedObject) && if (isOnNavigationPath(nodeObject, navigatedObject) &&
$scope.toggle !== undefined) { $scope.toggle !== undefined) {
$scope.toggle.setState(true); $scope.toggle.setState(true);
@ -73,11 +111,14 @@ define(
} }
} }
// Callback for the navigation service; track the currently
// navigated object and update display parameters as needed.
function setNavigation(object) { function setNavigation(object) {
navigatedObject = object; navigatedObject = object;
checkNavigation(); checkNavigation();
} }
// Listen for changes which will effect display parameters
navigationService.addListener(setNavigation); navigationService.addListener(setNavigation);
$scope.$on("$destroy", function () { $scope.$on("$destroy", function () {
navigationService.removeListener(setNavigation); navigationService.removeListener(setNavigation);
@ -85,12 +126,26 @@ define(
$scope.$watch("domainObject", checkNavigation); $scope.$watch("domainObject", checkNavigation);
return { return {
/**
* This method should be called when a node is expanded
* to record that this has occurred, to support one-time
* lazy loading of the node's subtree.
*/
trackExpansion: function () { trackExpansion: function () {
hasBeenExpanded = true; hasBeenExpanded = true;
}, },
/**
* Check if this not has ever been expanded.
* @returns true if it has been expanded
*/
hasBeenExpanded: function () { hasBeenExpanded: function () {
return hasBeenExpanded; return hasBeenExpanded;
}, },
/**
* Check whether or not the domain object represented by
* this tree node is currently the navigated object.
* @returns true if this is the navigated object
*/
isNavigated: function () { isNavigated: function () {
return isNavigated; return isNavigated;
} }