diff --git a/LICENSES.md b/LICENSES.md
index 6e270d3937..e7ab92507b 100644
--- a/LICENSES.md
+++ b/LICENSES.md
@@ -476,6 +476,44 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
---
+### Zepto
+
+#### Info
+
+* Link: http://zeptojs.com/
+
+* Version: 1.1.6
+
+* Authors: Thomas Fuchs
+
+* Description: DOM manipulation
+
+#### License
+
+Copyright (c) 2010-2016 Thomas Fuchs
+http://zeptojs.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+---
+
### Json.NET
#### Info
diff --git a/bower.json b/bower.json
index b4bf273af2..7285aad9e2 100644
--- a/bower.json
+++ b/bower.json
@@ -17,6 +17,7 @@
"screenfull": "^3.0.0",
"node-uuid": "^1.4.7",
"comma-separated-values": "^3.6.4",
- "FileSaver.js": "^0.0.2"
+ "FileSaver.js": "^0.0.2",
+ "zepto": "^1.1.6"
}
}
diff --git a/main.js b/main.js
index db0b3ed84e..33e45c0125 100644
--- a/main.js
+++ b/main.js
@@ -33,7 +33,8 @@ requirejs.config({
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
- "uuid": "bower_components/node-uuid/uuid"
+ "uuid": "bower_components/node-uuid/uuid",
+ "zepto": "bower_components/zepto/zepto.min"
},
"shim": {
"angular": {
@@ -44,6 +45,9 @@ requirejs.config({
},
"moment-duration-format": {
"deps": [ "moment" ]
+ },
+ "zepto": {
+ "exports": "Zepto"
}
}
});
diff --git a/platform/commonUI/general/bundle.js b/platform/commonUI/general/bundle.js
index f3d6d98f61..02512fc9fd 100644
--- a/platform/commonUI/general/bundle.js
+++ b/platform/commonUI/general/bundle.js
@@ -49,6 +49,7 @@ define([
"./src/directives/MCTScroll",
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
+ "./src/directives/MCTTree",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
"text!./res/templates/controls/input-filter.html",
@@ -97,6 +98,7 @@ define([
MCTScroll,
MCTSplitPane,
MCTSplitter,
+ MCTTree,
bottombarTemplate,
actionButtonTemplate,
inputFilterTemplate,
@@ -389,6 +391,11 @@ define([
{
"key": "mctSplitter",
"implementation": MCTSplitter
+ },
+ {
+ "key": "mctTree",
+ "implementation": MCTTree,
+ "depends": [ '$parse', 'gestureService' ]
}
],
"constants": [
@@ -535,6 +542,16 @@ define([
"copyright": "Copyright (c) Nicolas Gallagher and Jonathan Neal",
"license": "license-mit",
"link": "https://github.com/necolas/normalize.css/blob/v1.1.2/LICENSE.md"
+ },
+ {
+ "name": "Zepto",
+ "version": "1.1.6",
+ "description": "DOM manipulation",
+ "author": "Thomas Fuchs",
+ "website": "http://zeptojs.com/",
+ "copyright": "Copyright (c) 2010-2016 Thomas Fuchs",
+ "license": "license-mit",
+ "link": "https://github.com/madrobby/zepto/blob/master/MIT-LICENSE"
}
]
}
diff --git a/platform/commonUI/general/res/templates/subtree.html b/platform/commonUI/general/res/templates/subtree.html
index 637b73a8c4..abc7a0d34a 100644
--- a/platform/commonUI/general/res/templates/subtree.html
+++ b/platform/commonUI/general/res/templates/subtree.html
@@ -19,18 +19,6 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
-
- -
-
-
- Loading...
-
-
- -
-
-
-
-
+
+
+
diff --git a/platform/commonUI/general/res/templates/tree/node.html b/platform/commonUI/general/res/templates/tree/node.html
new file mode 100644
index 0000000000..d6012b5ea9
--- /dev/null
+++ b/platform/commonUI/general/res/templates/tree/node.html
@@ -0,0 +1,4 @@
+
+
+
diff --git a/platform/commonUI/general/res/templates/tree/toggle.html b/platform/commonUI/general/res/templates/tree/toggle.html
new file mode 100644
index 0000000000..289f781f95
--- /dev/null
+++ b/platform/commonUI/general/res/templates/tree/toggle.html
@@ -0,0 +1,2 @@
+
+
diff --git a/platform/commonUI/general/res/templates/tree/tree-label.html b/platform/commonUI/general/res/templates/tree/tree-label.html
new file mode 100644
index 0000000000..8c6dcb2e86
--- /dev/null
+++ b/platform/commonUI/general/res/templates/tree/tree-label.html
@@ -0,0 +1,8 @@
+
+
+
diff --git a/platform/commonUI/general/res/templates/tree/wait-node.html b/platform/commonUI/general/res/templates/tree/wait-node.html
new file mode 100644
index 0000000000..79dffa73b5
--- /dev/null
+++ b/platform/commonUI/general/res/templates/tree/wait-node.html
@@ -0,0 +1,27 @@
+
+
+
+
+ Loading...
+
+
diff --git a/platform/commonUI/general/src/directives/MCTTree.js b/platform/commonUI/general/src/directives/MCTTree.js
new file mode 100644
index 0000000000..47f8eecbc6
--- /dev/null
+++ b/platform/commonUI/general/src/directives/MCTTree.js
@@ -0,0 +1,54 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define*/
+
+define([
+ 'angular',
+ '../ui/TreeView'
+], function (angular, TreeView) {
+ function MCTTree($parse, gestureService) {
+ function link(scope, element, attrs) {
+ var treeView = new TreeView(gestureService),
+ expr = $parse(attrs.mctModel),
+ unobserve = treeView.observe(function (domainObject) {
+ if (domainObject !== expr(scope.$parent)) {
+ expr.assign(scope.$parent, domainObject);
+ scope.$apply();
+ }
+ });
+
+ element.append(angular.element(treeView.elements()));
+
+ scope.$parent.$watch(attrs.mctModel, treeView.value.bind(treeView));
+ scope.$watch('mctObject', treeView.model.bind(treeView));
+ scope.$on('$destroy', unobserve);
+ }
+
+ return {
+ restrict: "E",
+ link: link,
+ scope: { mctObject: "=" }
+ };
+ }
+
+ return MCTTree;
+});
diff --git a/platform/commonUI/general/src/ui/ToggleView.js b/platform/commonUI/general/src/ui/ToggleView.js
new file mode 100644
index 0000000000..c347ac3837
--- /dev/null
+++ b/platform/commonUI/general/src/ui/ToggleView.js
@@ -0,0 +1,65 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define*/
+
+define([
+ 'zepto',
+ 'text!../../res/templates/tree/toggle.html'
+], function ($, toggleTemplate) {
+ function ToggleView(state) {
+ this.expanded = !!state;
+ this.callbacks = [];
+ this.el = $(toggleTemplate);
+ this.el.on('click', function () {
+ this.value(!this.expanded);
+ }.bind(this));
+ }
+
+ ToggleView.prototype.value = function (state) {
+ this.expanded = state;
+
+ if (state) {
+ this.el.addClass('expanded');
+ } else {
+ this.el.removeClass('expanded');
+ }
+
+ this.callbacks.forEach(function (callback) {
+ callback(state);
+ });
+ };
+
+ ToggleView.prototype.observe = function (callback) {
+ this.callbacks.push(callback);
+ return function () {
+ this.callbacks = this.callbacks.filter(function (c) {
+ return c !== callback;
+ });
+ }.bind(this);
+ };
+
+ ToggleView.prototype.elements = function () {
+ return this.el;
+ };
+
+ return ToggleView;
+});
diff --git a/platform/commonUI/general/src/ui/TreeLabelView.js b/platform/commonUI/general/src/ui/TreeLabelView.js
new file mode 100644
index 0000000000..75e8efcc29
--- /dev/null
+++ b/platform/commonUI/general/src/ui/TreeLabelView.js
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define*/
+
+define([
+ 'zepto',
+ 'text!../../res/templates/tree/tree-label.html'
+], function ($, labelTemplate) {
+ 'use strict';
+
+ function TreeLabelView(gestureService) {
+ this.el = $(labelTemplate);
+ this.gestureService = gestureService;
+ }
+
+ function getGlyph(domainObject) {
+ var type = domainObject.getCapability('type');
+ return type.getGlyph();
+ }
+
+ function isLink(domainObject) {
+ var location = domainObject.getCapability('location');
+ return location.isLink();
+ }
+
+ TreeLabelView.prototype.updateView = function (domainObject) {
+ var titleEl = this.el.find('.t-title-label'),
+ glyphEl = this.el.find('.t-item-icon-glyph'),
+ iconEl = this.el.find('.t-item-icon');
+
+ titleEl.text(domainObject ? domainObject.getModel().name : "");
+ glyphEl.text(domainObject ? getGlyph(domainObject) : "");
+
+ if (domainObject && isLink(domainObject)) {
+ iconEl.addClass('l-icon-link');
+ } else {
+ iconEl.removeClass('l-icon-link');
+ }
+ };
+
+ TreeLabelView.prototype.model = function (domainObject) {
+ if (this.unlisten) {
+ this.unlisten();
+ delete this.unlisten;
+ }
+
+ if (this.activeGestures) {
+ this.activeGestures.destroy();
+ delete this.activeGestures;
+ }
+
+ this.updateView(domainObject);
+
+ if (domainObject) {
+ this.unlisten = domainObject.getCapability('mutation')
+ .listen(this.updateView.bind(this, domainObject));
+
+ this.activeGestures = this.gestureService.attachGestures(
+ this.elements(),
+ domainObject,
+ [ 'info', 'menu', 'drag' ]
+ );
+ }
+ };
+
+ TreeLabelView.prototype.elements = function () {
+ return this.el;
+ };
+
+ return TreeLabelView;
+});
diff --git a/platform/commonUI/general/src/ui/TreeNodeView.js b/platform/commonUI/general/src/ui/TreeNodeView.js
new file mode 100644
index 0000000000..2b02a2c1f9
--- /dev/null
+++ b/platform/commonUI/general/src/ui/TreeNodeView.js
@@ -0,0 +1,133 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define*/
+
+define([
+ 'zepto',
+ 'text!../../res/templates/tree/node.html',
+ './ToggleView',
+ './TreeLabelView'
+], function ($, nodeTemplate, ToggleView, TreeLabelView) {
+ 'use strict';
+
+ function TreeNodeView(gestureService, subtreeFactory, selectFn) {
+ this.li = $('');
+
+ this.toggleView = new ToggleView(false);
+ this.toggleView.observe(function (state) {
+ if (state) {
+ if (!this.subtreeView) {
+ this.subtreeView = subtreeFactory();
+ this.subtreeView.model(this.activeObject);
+ this.li.find('.tree-item-subtree').eq(0)
+ .append($(this.subtreeView.elements()));
+ }
+ $(this.subtreeView.elements()).removeClass('hidden');
+ } else if (this.subtreeView) {
+ $(this.subtreeView.elements()).addClass('hidden');
+ }
+ }.bind(this));
+
+ this.labelView = new TreeLabelView(gestureService);
+
+ $(this.labelView.elements()).on('click', function () {
+ selectFn(this.activeObject);
+ }.bind(this));
+
+ this.li.append($(nodeTemplate));
+ this.li.find('span').eq(0)
+ .append($(this.toggleView.elements()))
+ .append($(this.labelView.elements()));
+
+ this.model(undefined);
+ }
+
+ TreeNodeView.prototype.model = function (domainObject) {
+ if (this.unlisten) {
+ this.unlisten();
+ }
+
+ this.activeObject = domainObject;
+
+ if (domainObject && domainObject.hasCapability('composition')) {
+ $(this.toggleView.elements()).addClass('has-children');
+ } else {
+ $(this.toggleView.elements()).removeClass('has-children');
+ }
+
+ this.labelView.model(domainObject);
+ if (this.subtreeView) {
+ this.subtreeView.model(domainObject);
+ }
+ };
+
+ function getIdPath(domainObject) {
+ function getId(domainObject) {
+ return domainObject.getId();
+ }
+
+ return domainObject ?
+ domainObject.getCapability('context').getPath().map(getId) :
+ [];
+ }
+
+ TreeNodeView.prototype.value = function (domainObject) {
+ var activeIdPath = getIdPath(this.activeObject),
+ selectedIdPath = getIdPath(domainObject);
+
+ if (this.onSelectionPath) {
+ this.li.find('.tree-item').eq(0).removeClass('selected');
+ if (this.subtreeView) {
+ this.subtreeView.value(undefined);
+ }
+ }
+
+ this.onSelectionPath =
+ !!domainObject &&
+ !!this.activeObject &&
+ (activeIdPath.length <= selectedIdPath.length) &&
+ activeIdPath.every(function (id, index) {
+ return selectedIdPath[index] === id;
+ });
+
+ if (this.onSelectionPath) {
+ if (activeIdPath.length === selectedIdPath.length) {
+ this.li.find('.tree-item').eq(0).addClass('selected');
+ } else {
+ // Expand to reveal the selection
+ this.toggleView.value(true);
+ this.subtreeView.value(domainObject);
+ }
+ }
+ };
+
+ /**
+ *
+ * @returns {HTMLElement[]}
+ */
+ TreeNodeView.prototype.elements = function () {
+ return this.li;
+ };
+
+
+ return TreeNodeView;
+});
diff --git a/platform/commonUI/general/src/ui/TreeView.js b/platform/commonUI/general/src/ui/TreeView.js
new file mode 100644
index 0000000000..b09256388d
--- /dev/null
+++ b/platform/commonUI/general/src/ui/TreeView.js
@@ -0,0 +1,140 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define*/
+
+define([
+ 'zepto',
+ './TreeNodeView',
+ 'text!../../res/templates/tree/wait-node.html'
+], function ($, TreeNodeView, spinnerTemplate) {
+ 'use strict';
+
+ function TreeView(gestureService, selectFn) {
+ this.ul = $('');
+ this.nodeViews = [];
+ this.callbacks = [];
+ this.selectFn = selectFn || this.value.bind(this);
+ this.gestureService = gestureService;
+ this.pending = false;
+ }
+
+ TreeView.prototype.newTreeView = function () {
+ return new TreeView(this.gestureService, this.selectFn);
+ };
+
+ TreeView.prototype.setSize = function (sz) {
+ var nodeView;
+
+ while (this.nodeViews.length < sz) {
+ nodeView = new TreeNodeView(
+ this.gestureService,
+ this.newTreeView.bind(this),
+ this.selectFn
+ );
+ this.nodeViews.push(nodeView);
+ this.ul.append($(nodeView.elements()));
+ }
+
+ while (this.nodeViews.length > sz) {
+ nodeView = this.nodeViews.pop();
+ $(nodeView.elements()).remove();
+ }
+ };
+
+ TreeView.prototype.loadComposition = function (domainObject) {
+ var self = this;
+
+ function addNode(domainObject, index) {
+ self.nodeViews[index].model(domainObject);
+ }
+
+ function addNodes(domainObjects) {
+ if (self.pending) {
+ self.pending = false;
+ self.nodeViews = [];
+ self.ul.empty();
+ }
+
+ if (domainObject === self.activeObject) {
+ self.setSize(domainObjects.length);
+ domainObjects.forEach(addNode);
+ self.updateNodeViewSelection();
+ }
+ }
+
+ domainObject.useCapability('composition')
+ .then(addNodes);
+ };
+
+ TreeView.prototype.model = function (domainObject) {
+ if (this.unlisten) {
+ this.unlisten();
+ }
+
+ this.activeObject = domainObject;
+ this.ul.empty();
+
+ if (domainObject && domainObject.hasCapability('composition')) {
+ this.pending = true;
+ this.ul.append($(spinnerTemplate));
+ this.unlisten = domainObject.getCapability('mutation')
+ .listen(this.loadComposition.bind(this));
+ this.loadComposition(domainObject);
+ } else {
+ this.setSize(0);
+ }
+ };
+
+ TreeView.prototype.updateNodeViewSelection = function () {
+ this.nodeViews.forEach(function (nodeView) {
+ nodeView.value(this.selectedObject);
+ }.bind(this));
+ };
+
+ TreeView.prototype.value = function (domainObject) {
+ this.selectedObject = domainObject;
+ this.updateNodeViewSelection();
+ this.callbacks.forEach(function (callback) {
+ callback(domainObject);
+ });
+ };
+
+ TreeView.prototype.observe = function (callback) {
+ this.callbacks.push(callback);
+ return function () {
+ this.callbacks = this.callbacks.filter(function (c) {
+ return c !== callback;
+ });
+ }.bind(this);
+ };
+
+ /**
+ *
+ * @returns {HTMLElement[]}
+ */
+ TreeView.prototype.elements = function () {
+ return this.ul;
+ };
+
+
+ return TreeView;
+});
diff --git a/platform/commonUI/general/test/directives/MCTTreeSpec.js b/platform/commonUI/general/test/directives/MCTTreeSpec.js
new file mode 100644
index 0000000000..597c4c55b7
--- /dev/null
+++ b/platform/commonUI/general/test/directives/MCTTreeSpec.js
@@ -0,0 +1,95 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define,describe,beforeEach,jasmine,it,expect*/
+
+define([
+ '../../src/directives/MCTTree'
+], function (MCTTree) {
+ describe("The mct-tree directive", function () {
+ var mockParse,
+ mockGestureService,
+ mockExpr,
+ mctTree;
+
+ beforeEach(function () {
+ mockGestureService = jasmine.createSpyObj(
+ 'gestureService',
+ [ 'attachGestures' ]
+ );
+ mockParse = jasmine.createSpy('$parse');
+ mockExpr = jasmine.createSpy('expr');
+ mockExpr.assign = jasmine.createSpy('assign');
+ mockParse.andReturn(mockExpr);
+
+ mctTree = new MCTTree(mockParse, mockGestureService);
+ });
+
+ it("is applicable as an element", function () {
+ expect(mctTree.restrict).toEqual("E");
+ });
+
+ it("two-way binds to mctObject", function () {
+ expect(mctTree.scope).toEqual({ mctObject: "=" });
+ });
+
+ describe("link", function () {
+ var mockScope,
+ mockElement,
+ testAttrs;
+
+ beforeEach(function () {
+ mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
+ mockElement = jasmine.createSpyObj('element', ['append']);
+ testAttrs = { mctModel: "some-expression" };
+ mockScope.$parent =
+ jasmine.createSpyObj('$scope', ['$watch', '$on']);
+ mctTree.link(mockScope, mockElement, testAttrs);
+ });
+
+ it("populates the mct-tree element", function () {
+ expect(mockElement.append).toHaveBeenCalled();
+ });
+
+ it("watches for mct-model's expression in the parent", function () {
+ expect(mockScope.$parent.$watch).toHaveBeenCalledWith(
+ testAttrs.mctModel,
+ jasmine.any(Function)
+ );
+ });
+
+ it("watches for changes to mct-object", function () {
+ expect(mockScope.$watch).toHaveBeenCalledWith(
+ "mctObject",
+ jasmine.any(Function)
+ );
+ });
+
+ it("listens for the $destroy event", function () {
+ expect(mockScope.$on).toHaveBeenCalledWith(
+ "$destroy",
+ jasmine.any(Function)
+ );
+ });
+ });
+ });
+
+});
diff --git a/platform/commonUI/general/test/ui/TreeViewSpec.js b/platform/commonUI/general/test/ui/TreeViewSpec.js
new file mode 100644
index 0000000000..370e5c8e10
--- /dev/null
+++ b/platform/commonUI/general/test/ui/TreeViewSpec.js
@@ -0,0 +1,274 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+/*global define,describe,beforeEach,jasmine,it,expect*/
+
+define([
+ '../../src/ui/TreeView',
+ 'zepto'
+], function (TreeView, $) {
+ 'use strict';
+
+ describe("TreeView", function () {
+ var mockGestureService,
+ mockGestureHandle,
+ mockDomainObject,
+ mockMutation,
+ mockUnlisten,
+ testCapabilities,
+ treeView;
+
+ function makeMockDomainObject(id, model, capabilities) {
+ var mockDomainObject = jasmine.createSpyObj(
+ 'domainObject-' + id,
+ [
+ 'getId',
+ 'getModel',
+ 'getCapability',
+ 'hasCapability',
+ 'useCapability'
+ ]
+ );
+ mockDomainObject.getId.andReturn(id);
+ mockDomainObject.getModel.andReturn(model);
+ mockDomainObject.hasCapability.andCallFake(function (c) {
+ return !!(capabilities[c]);
+ });
+ mockDomainObject.getCapability.andCallFake(function (c) {
+ return capabilities[c];
+ });
+ mockDomainObject.useCapability.andCallFake(function (c) {
+ return capabilities[c] && capabilities[c].invoke();
+ });
+ return mockDomainObject;
+ }
+
+ beforeEach(function () {
+ mockGestureService = jasmine.createSpyObj(
+ 'gestureService',
+ [ 'attachGestures' ]
+ );
+
+ mockGestureHandle = jasmine.createSpyObj('gestures', ['destroy']);
+
+ mockGestureService.attachGestures.andReturn(mockGestureHandle);
+
+ mockMutation = jasmine.createSpyObj('mutation', ['listen']);
+ mockUnlisten = jasmine.createSpy('unlisten');
+ mockMutation.listen.andReturn(mockUnlisten);
+
+ testCapabilities = { mutation: mockMutation };
+
+ mockDomainObject =
+ makeMockDomainObject('parent', {}, testCapabilities);
+
+ treeView = new TreeView(mockGestureService);
+ });
+
+ describe("elements", function () {
+ var elements;
+
+ beforeEach(function () {
+ elements = treeView.elements();
+ });
+
+ it("is an unordered list", function () {
+ expect(elements[0].tagName.toLowerCase())
+ .toEqual('ul');
+ });
+ });
+
+ describe("model", function () {
+ var mockComposition;
+
+ function makeGenericCapabilities() {
+ var mockContext =
+ jasmine.createSpyObj('context', [ 'getPath' ]),
+ mockType =
+ jasmine.createSpyObj('type', [ 'getGlyph' ]),
+ mockLocation =
+ jasmine.createSpyObj('location', [ 'isLink' ]),
+ mockMutation =
+ jasmine.createSpyObj('mutation', [ 'listen' ]);
+ return {
+ context: mockContext,
+ type: mockType,
+ mutation: mockMutation,
+ location: mockLocation
+ };
+ }
+
+ function waitForCompositionCallback() {
+ var calledBack = false;
+ testCapabilities.composition.invoke().then(function (c) {
+ calledBack = true;
+ });
+ waitsFor(function () {
+ return calledBack;
+ });
+ }
+
+ beforeEach(function () {
+ mockComposition = ['a', 'b', 'c'].map(function (id) {
+ var testCapabilities = makeGenericCapabilities(),
+ mockChild =
+ makeMockDomainObject(id, {}, testCapabilities);
+
+ testCapabilities.context.getPath
+ .andReturn([mockDomainObject, mockChild]);
+
+ return mockChild;
+ });
+
+ testCapabilities.composition =
+ jasmine.createSpyObj('composition', ['invoke']);
+ testCapabilities.composition.invoke
+ .andReturn(Promise.resolve(mockComposition));
+
+ treeView.model(mockDomainObject);
+ waitForCompositionCallback();
+ });
+
+ it("adds one node per composition element", function () {
+ expect(treeView.elements()[0].childElementCount)
+ .toEqual(mockComposition.length);
+ });
+
+ it("listens for mutation", function () {
+ expect(testCapabilities.mutation.listen)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ describe("when mutation occurs", function () {
+ beforeEach(function () {
+ mockComposition.pop();
+ testCapabilities.mutation.listen
+ .mostRecentCall.args[0](mockDomainObject);
+ waitForCompositionCallback();
+ });
+
+ it("continues to show one node per composition element", function () {
+ expect(treeView.elements()[0].childElementCount)
+ .toEqual(mockComposition.length);
+ });
+ });
+
+ describe("when replaced with a non-compositional domain object", function () {
+ beforeEach(function () {
+ delete testCapabilities.composition;
+ treeView.model(mockDomainObject);
+ });
+
+ it("stops listening for mutation", function () {
+ expect(mockUnlisten).toHaveBeenCalled();
+ });
+
+ it("removes all tree nodes", function () {
+ expect(treeView.elements()[0].childElementCount)
+ .toEqual(0);
+ });
+ });
+
+ describe("when selection state changes", function () {
+ var selectionIndex = 1;
+
+ beforeEach(function () {
+ treeView.value(mockComposition[selectionIndex]);
+ });
+
+ it("communicates selection state to an appropriate node", function () {
+ var selected = $(treeView.elements()[0]).find('.selected');
+ expect(selected.length).toEqual(1);
+ });
+ });
+
+ describe("when children contain children", function () {
+ beforeEach(function () {
+ var newCapabilities = makeGenericCapabilities(),
+ gcCapabilities = makeGenericCapabilities(),
+ mockNewChild =
+ makeMockDomainObject('d', {}, newCapabilities),
+ mockGrandchild =
+ makeMockDomainObject('gc', {}, gcCapabilities),
+ calledBackInner = false;
+
+ newCapabilities.composition =
+ jasmine.createSpyObj('composition', [ 'invoke' ]);
+ newCapabilities.composition.invoke
+ .andReturn(Promise.resolve([mockGrandchild]));
+ mockComposition.push(mockNewChild);
+
+ newCapabilities.context.getPath.andReturn([
+ mockDomainObject,
+ mockNewChild
+ ]);
+ gcCapabilities.context.getPath.andReturn([
+ mockDomainObject,
+ mockNewChild,
+ mockGrandchild
+ ]);
+
+ testCapabilities.mutation.listen
+ .mostRecentCall.args[0](mockDomainObject);
+ waitForCompositionCallback();
+ runs(function () {
+ // Select the innermost object to force expansion,
+ // such that we can verify the subtree is present.
+ treeView.value(mockGrandchild);
+ newCapabilities.composition.invoke().then(function () {
+ calledBackInner = true;
+ });
+ });
+ waitsFor(function () {
+ return calledBackInner;
+ });
+ });
+
+ it("creates inner trees", function () {
+ expect($(treeView.elements()[0]).find('ul').length)
+ .toEqual(1);
+ });
+ });
+ });
+
+ describe("observe", function () {
+ var mockCallback,
+ unobserve;
+
+ beforeEach(function () {
+ mockCallback = jasmine.createSpy('callback');
+ unobserve = treeView.observe(mockCallback);
+ });
+
+ it("notifies listeners when value is changed", function () {
+ treeView.value(mockDomainObject);
+ expect(mockCallback).toHaveBeenCalledWith(mockDomainObject);
+ });
+
+ it("does not notify listeners when deactivated", function () {
+ unobserve();
+ treeView.value(mockDomainObject);
+ expect(mockCallback).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+});
diff --git a/test-main.js b/test-main.js
index 13f1bf367d..6e4729f050 100644
--- a/test-main.js
+++ b/test-main.js
@@ -53,7 +53,8 @@ requirejs.config({
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
- "uuid": "bower_components/node-uuid/uuid"
+ "uuid": "bower_components/node-uuid/uuid",
+ "zepto": "bower_components/zepto/zepto.min"
},
"shim": {
@@ -65,6 +66,9 @@ requirejs.config({
},
"moment-duration-format": {
"deps": [ "moment" ]
+ },
+ "zepto": {
+ "exports": "Zepto"
}
},