aboutsummaryrefslogtreecommitdiffstats
path: root/ui/imports/ui/components/tree-node/tree-node.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/imports/ui/components/tree-node/tree-node.js')
-rw-r--r--ui/imports/ui/components/tree-node/tree-node.js419
1 files changed, 419 insertions, 0 deletions
diff --git a/ui/imports/ui/components/tree-node/tree-node.js b/ui/imports/ui/components/tree-node/tree-node.js
new file mode 100644
index 0000000..7a79314
--- /dev/null
+++ b/ui/imports/ui/components/tree-node/tree-node.js
@@ -0,0 +1,419 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * Template Component: TreeNode
+ */
+
+//import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { EJSON } from 'meteor/ejson';
+//import { ReactiveDict } from 'meteor/reactive-dict';
+import { ReactiveVar } from 'meteor/reactive-var';
+import { SimpleSchema } from 'meteor/aldeed:simple-schema';
+import { InventoryTreeNodeBehavior } from '/imports/ui/lib/inventory-tree-node-behavior';
+import * as R from 'ramda';
+import { calcColorMem } from '/imports/lib/utilities';
+import 'jquery.scrollto';
+
+import './tree-node.html';
+
+/*
+ * Lifecycles
+ */
+
+Template.TreeNode.onCreated(function() {
+ let instance = this;
+ instance.state = new ReactiveDict();
+ instance.state.setDefault({
+ node: null,
+ openState: 'closed',
+ orderDataSubscribe: { counter: 0, data: { node: null, forOpen: false } },
+ needOpenCloseAnimation: { counter: 0, data: { type: 'opening', node: null } },
+ positionNeeded: false,
+ scrollToNodeIsNeeded: false,
+ });
+
+ //console.log('tree-node - on create', R.path(['data', 'node', '_id', '_str'], instance));
+
+ //let oldData = null;
+
+ createAttachedFns(instance);
+
+ instance.currentData = new ReactiveVar(null, EJSON.equals);
+
+ instance.autorun((function(_this) {
+ return function(_computation) {
+ return _this.currentData.set(Template.currentData());
+ };
+ })(instance));
+
+ instance.autorun(function () {
+ //let data = Template.currentData();
+ let data = instance.currentData.get();
+ //let data = instance.data;
+
+ new SimpleSchema({
+ behavior: {
+ type: { isOpenDefault: { type: Boolean } },
+ blackbox: true
+ },
+ showDetailsLine: { type: Boolean },
+ openState: { type: String },
+ node: { type: Object, blackbox: true },
+ children: { type: [Object], blackbox: true },
+ childDetected: { type: Boolean },
+ needChildDetection: { type: Boolean },
+ linkDetected: { type: Boolean },
+ level: { type: Number },
+ positionNeeded: { type: Boolean },
+ scrollToNodeIsNeeded: { type: Boolean },
+ onResetChildren: { type: Function },
+ onChildRead: { type: Function },
+ onChildrenRead: { type: Function },
+ onStartOpenReq: { type: Function },
+ onOpeningDone: { type: Function },
+ onStartCloseReq: { type: Function },
+ onClosingDone: { type: Function },
+ onChildDetected: { type: Function },
+ onNodeSelected: { type: Function },
+ onPositionRetrieved: { type: Function },
+ onScrollToNodePerformed: { type: Function },
+ onOpenLinkReq: { type: Function },
+ onResetNeedChildDetection: { type: Function },
+ }).validate(data);
+
+ instance.state.set('openState', data.openState);
+ instance.state.set('node', data.node);
+ instance.state.set('positionNeeded', data.positionNeeded);
+ instance.state.set('scrollToNodeIsNeeded', data.scrollToNodeIsNeeded);
+ instance.state.set('needChildDetection', data.needChildDetection);
+
+ //console.log('tree-node - main autorun - ' + data.node._id._str);
+
+ /*
+ R.forEach((keyName) => {
+ if (R.isNil(oldData)) { return; }
+
+ if (! R.equals(R.prop(keyName, data), R.prop(keyName, oldData)) ) {
+ console.log('tree-node - main autorun - prop change: ' + keyName);
+ //R.path([keyName], data), R.path([keyName], oldData));
+ }
+ }, R.keys(data));
+
+ if (oldData !== data) { console.log('tree-node - main autorn - data ob change'); }
+
+ oldData = data;
+ */
+
+ });
+
+ instance.autorun(function () {
+ let node = instance.state.get('node');
+ let openState = instance.state.get('openState');
+
+ switch (openState) {
+ case 'start_open':
+ issueOrder(instance, 'orderDataSubscribe', { node: node, forOpen: true });
+ setTimeout(() => {
+ instance.data.onOpeningDone([node._id._str], node);
+ }, 400);
+ break;
+ case 'opened':
+ issueOrder(instance, 'needOpenCloseAnimation', { type: 'opening', node: node});
+ break;
+ case 'start_close':
+ issueOrder(instance, 'needOpenCloseAnimation', { type: 'closing', node: node });
+ setTimeout(() => {
+ instance.data.onClosingDone([node._id._str]);
+ }, 200);
+ break;
+ case 'closed':
+ issueOrder(instance, 'orderDataSubscribe', { node: node, forOpen: false });
+ break;
+ }
+ });
+
+ instance.autorun(() => {
+ let order = instance.state.get('orderDataSubscribe');
+ if (order.counter == 0) { return; }
+
+ instance.data.onResetChildren(R.append(R.path(['_id', '_str'], order.data.node), []));
+ // console.log('reset children in autoron order data sub: ' + order.data.node._id._str);
+
+ if (order.data.forOpen) {
+ instance.data.behavior.subscribeGetChildrenFn(instance, order.data.node);
+
+ let children = [];
+ let onChildReadThrottle = _.throttle(() => {
+ instance.data.onChildrenRead([ order.data.node._id._str ], children);
+ children = [];
+ }, 200);
+
+ instance.data.behavior.getChildrenFn(order.data.node).forEach((child) => {
+ // todo: aggregate the collection into threshold and then dispatch.
+ // debounce/throttle
+ // https://lodash.com/docs#debounce
+
+ //instance.data.onChildRead(
+ // [order.data.node._id._str, child._id._str], child);
+
+ children = R.append(child, children);
+ onChildReadThrottle();
+ });
+ }
+ });
+
+ instance.autorun(() => {
+ //let needChildDetection =
+ instance.state.get('needChildDetection');
+ let data = instance.data;
+
+ instance.data.behavior.subscribeGetFirstChildFn(instance, data.node);
+ // todo: let childDetectedSubmited = false;
+ instance.data.behavior.getChildrenFn(data.node).forEach((_child) => {
+ instance.data.onChildDetected([data.node._id._str]);
+ });
+
+ instance.data.onResetNeedChildDetection([data.node._id._str]);
+ });
+
+ instance.autorun(function () {
+ let positionNeeded = instance.state.get('positionNeeded');
+
+ if (positionNeeded) {
+ let el = instance.$('>.os-tree-node')[0];
+ let rect = el.getBoundingClientRect();
+ instance.data.onPositionRetrieved([instance.data.node._id._str], rect);
+ }
+ });
+
+ instance.autorun(function () {
+ let scrollToNodeIsNeeded = instance.state.get('scrollToNodeIsNeeded');
+
+ if (scrollToNodeIsNeeded) {
+ let el = instance.$('>.os-tree-node')[0];
+ let rect = el.getBoundingClientRect();
+ if (rect.top < 0) {
+ //window.scroll(0, el.offsetTop);
+ $(window).scrollTo(el, 50);
+ instance.data.onScrollToNodePerformed([instance.data.node._id._str]);
+ return;
+ }
+
+ let childrenCont = instance.$('>.os-tree-node > .sm-children-list')[0];
+ let childrenRect = childrenCont.getBoundingClientRect();
+ if (childrenRect.bottom > window.innerHeight) {
+ let scrollPos = childrenRect.bottom - window.innerHeight;
+ scrollPos = window.scrollY + scrollPos;
+ if ((window.scrollY + rect.top) < scrollPos) {
+ scrollPos = window.scrollY + rect.top;
+ }
+ $(window).scrollTo(scrollPos, 50);
+ }
+
+ instance.data.onScrollToNodePerformed([instance.data.node._id._str]);
+ }
+ });
+
+});
+
+Template.TreeNode.rendered = function() {
+ let instance = Template.instance();
+ // Detect change in isOpen.
+ instance.autorun(() => {
+ let order = instance.state.get('needOpenCloseAnimation');
+ if (order.counter == 0) { return; }
+
+ let $childrenList;
+
+ switch(order.data.type) {
+ case 'opening':
+ // The children list element is not present on first isOpen change render. We
+ // need to wait out of loop inorder to let the render first render to list then
+ // we animate the opening/closing action.
+
+ //$childrenList = instance.$('>.sm-children-list');
+ $childrenList = instance.$(instance.firstNode).children('.sm-children-list');
+ $childrenList.slideDown(200);
+ break;
+
+ case 'closing':
+ //$childrenList = instance.$('>.sm-children-list');
+ $childrenList = instance.$(instance.firstNode).children('.sm-children-list');
+ $childrenList.slideUp(200);
+ break;
+ }
+
+ });
+};
+
+/*
+ * Events
+ */
+
+Template.TreeNode.events({
+ 'click .sm-details-line': function (event, _instance) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ let data = Template.currentData();
+
+ if (R.pathEq(['type'], 'host_ref')(data.node)) {
+ data.onOpenLinkReq(data.node.environment, data.node.name);
+
+ } else {
+ switch(data.openState) {
+ case 'opened':
+ R.when(R.pipe(R.isNil, R.not),
+ (fn) => fn([data.node._id._str])
+ )(data.onStartCloseReq);
+ break;
+
+ case 'closed':
+ R.when(R.pipe(R.isNil, R.not),
+ (fn) => fn([data.node._id._str])
+ )(data.onStartOpenReq);
+ break;
+ }
+
+ data.onNodeSelected(data.node);
+ }
+ }
+});
+
+/*
+ * Helpers
+ */
+
+Template.TreeNode.helpers({
+ argsChild: function (child, _node) {
+ let instance = Template.instance();
+ //let data = Template.currentData();
+
+ return {
+ behavior: InventoryTreeNodeBehavior,
+ showDetailsLine: true,
+ openState: child.openState,
+ node: child.nodeInfo,
+ children: child.children,
+ childDetected: child.childDetected,
+ needChildDetection: child.needChildDetection,
+ linkDetected: child.linkDetected,
+ level: child.level,
+ positionNeeded: child.positionNeeded,
+ scrollToNodeIsNeeded: child.scrollToNodeIsNeeded,
+ onChildRead: instance._fns.onChildRead,
+ onChildrenRead: instance._fns.onChildrenRead,
+ onResetChildren: instance._fns.onResetChildren,
+ onStartOpenReq: instance._fns.onStartOpenReq,
+ onOpeningDone: instance._fns.onOpeningDone,
+ onStartCloseReq: instance._fns.onStartCloseReq,
+ onClosingDone: instance._fns.onClosingDone,
+ onChildDetected: instance._fns.onChildDetected,
+ onNodeSelected: instance._fns.onNodeSelected,
+ onPositionRetrieved: instance._fns.onPositionRetrieved,
+ onScrollToNodePerformed: instance._fns.onScrollToNodePerformed,
+ onOpenLinkReq: instance._fns.onOpenLinkReq,
+ onResetNeedChildDetection: instance._fns.onResetNeedChildDetection,
+ };
+ },
+
+ isOpen: function () {
+ let instance = Template.instance();
+ return R.equals('opened', instance.state.get('openState'));
+ },
+
+ calcColor: function (level) {
+ return calcColorMem(level);
+ },
+
+ linkRefName: function () {
+ let instance = Template.instance();
+ let node = instance.state.get('node');
+
+ if (R.isNil(node)) { return ''; }
+ if (R.propEq('type', 'host_ref', node)) {
+ return node.name;
+ }
+
+ return '';
+ }
+}); // end: helpers
+
+function issueOrder(instance, name, data) {
+ let val = JSON.parse(instance.state.keys[name]);
+ val = R.merge(val, {
+ counter: val.counter + 1,
+ data: data
+ });
+
+ instance.state.set(name, val);
+}
+
+function createAttachedFns(instance) {
+
+ instance._fns = {
+ onChildRead: function (reqPath, nodeInfo) {
+ instance.data.onChildRead(
+ R.prepend(instance.data.node._id._str, reqPath), nodeInfo);
+ },
+ onChildrenRead: function (reqPath, childrenInfo) {
+ instance.data.onChildrenRead(
+ R.prepend(instance.data.node._id._str, reqPath), childrenInfo);
+ },
+ onResetChildren: function (reqPath) {
+ instance.data.onResetChildren(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onStartOpenReq: (reqPath) => {
+ instance.data.onStartOpenReq(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onOpeningDone: (reqPath, nodeInfo) => {
+ instance.data.onOpeningDone(
+ R.prepend(instance.data.node._id._str, reqPath), nodeInfo);
+ },
+ onStartCloseReq: (reqPath) => {
+ instance.data.onStartCloseReq(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onClosingDone: (reqPath) => {
+ instance.data.onClosingDone(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onChildDetected: (reqPath) => {
+ instance.data.onChildDetected(
+ R.prepend(instance.data.node._id._str, reqPath));
+ },
+ onNodeSelected: (nodeInfo) => {
+ instance.data.onNodeSelected(nodeInfo);
+ },
+ onPositionRetrieved: (reqPath, rect) => {
+ instance.data.onPositionRetrieved(
+ R.prepend(instance.data.node._id._str, reqPath),
+ rect
+ );
+ },
+ onScrollToNodePerformed: (reqPath) => {
+ instance.data.onScrollToNodePerformed(
+ R.prepend(instance.data.node._id._str, reqPath)
+ );
+ },
+
+ onOpenLinkReq: (envName, nodeName) => {
+ instance.data.onOpenLinkReq(envName, nodeName);
+ },
+
+ onResetNeedChildDetection: (reqPath) => {
+ instance.data.onResetNeedChildDetection(
+ R.prepend(instance.data.node._id._str, reqPath)
+ );
+ }
+ };
+}