From d78c2240a0d0d7bb2ba2369f3a16cc30b3a5d511 Mon Sep 17 00:00:00 2001
From: ZitRo
Date: Sat, 31 Oct 2015 16:10:49 +0200
Subject: [PATCH] highlighting fields on link hover, some help bug fixes and
minor improvements
---
README.md | 1 +
package.json | 2 +-
web/css/classView.css | 9 ++++
web/index.html | 63 +++++++++++++-------------
web/js/CacheClassExplorer.js | 27 +++++++++++
web/js/ClassView.js | 75 ++++++++++++++++++++++++++++---
web/js/Logic.js | 84 ++++-------------------------------
web/jsLib/joint.js | 10 ++++-
web/jsLib/joint.shapes.uml.js | 20 ++++-----
9 files changed, 167 insertions(+), 124 deletions(-)
diff --git a/README.md b/README.md
index 82a4770..eba8429 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ An UML Class explorer for InterSystems Caché.
+ Export diagrams as an image;
+ See Class methods, properties, parameters, SQL queries and more;
+ See any keywords and related information by hovering over everything with pointer;
++ Check which fields are connected by hovering over link;
+ View class methods code with syntax highlighting;
+ Zoom in and out;
+ Search on diagram or in class tree;
diff --git a/package.json b/package.json
index b49fef3..ba4f12c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "CacheClassExplorer",
- "version": "1.9",
+ "version": "1.10.4",
"description": "Class Explorer for InterSystems Caché",
"directories": {
"test": "test"
diff --git a/web/css/classView.css b/web/css/classView.css
index 3cedfef..ee47b8e 100644
--- a/web/css/classView.css
+++ b/web/css/classView.css
@@ -90,6 +90,15 @@ text {
fill: red;
}
+.line-selected {
+ fill: red;
+ text-shadow: 0 1px 3px #CCC;
+ -webkit-transition: all .2s ease;
+ -moz-transition: all .2s ease;
+ -o-transition: all .2s ease;
+ transition: all .2s ease;
+}
+
.inlineSearchBlock {
display: inline-block;
vertical-align: bottom;
diff --git a/web/index.html b/web/index.html
index d22cc31..7a375b0 100644
--- a/web/index.html
+++ b/web/index.html
@@ -159,21 +159,36 @@ Caché Class Explorer Help
{
"classes": { "Registered": { } }
- }
+ }
+ |
+
+ {
+ "classes": { "Persistent": { "ClassType": "persistent" } }
+ }
+ |
+
+ {
+ "classes": { "Serial": { "ClassType": "serial" } }
+ }
+ |
+
+ {
+ "classes": { "Data Type": { "ClassType": "datatype" } }
+ }
|
{
- "classes": { "Persistent": { "ClassType": "Persistent" } }
+ "classes": { "Index": { "ClassType": "index" } }
}
|
{
- "classes": { "Serial": { "ClassType": "Serial" } }
+ "classes": { "View": { "ClassType": "view" } }
}
|
{
- "classes": { "Data Type": { "ClassType": "DataType" } }
+ "classes": { "Stream": { "ClassType": "stream" } }
}
|
@@ -185,13 +200,13 @@ Caché Class Explorer Help
Connection Types |
- Association |
+ Class Mention |
{
"layoutDirection": "LR",
"classes": {
"Class A": { "properties": { "Property": { "Type": "Class B" } } },
- "Class B": { "ClassType": "Persistent" }
+ "Class B": { "ClassType": "persistent" }
}
}
|
@@ -202,8 +217,8 @@ Caché Class Explorer Help
{
"layoutDirection": "LR",
"classes": {
- "Class A": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "one", "Type": "Class B" } } },
- "Class B": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "many", "Type": "Class A" } } }
+ "Class A": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "one", "Type": "Class B", "Inverse": "Property" } } },
+ "Class B": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "many", "Type": "Class A", "Inverse": "Property" } } }
}
}
@@ -214,8 +229,8 @@ Caché Class Explorer Help
{
"layoutDirection": "LR",
"classes": {
- "Class B": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "child", "Type": "Class A" } } },
- "Class A": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "parent", "Type": "Class B" } } }
+ "Class B": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "child", "Type": "Class A", "Inverse": "Property" } } },
+ "Class A": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "parent", "Type": "Class B", "Inverse": "Property" } } }
}
}
@@ -226,8 +241,8 @@ Caché Class Explorer Help
{
"layoutDirection": "LR",
"classes": {
- "Derived Class": { "ClassType": "Persistent", "Super": "Inherited Class", "properties": { "Property": { "Type": "Nothing" } } },
- "Inherited Class": { "ClassType": "DataType", "properties": { "Property": { "Type": "Nothing" } } }
+ "Derived Class": { "ClassType": "datatype", "Super": "Inherited Class", "properties": { "Property": { "Type": "Nothing" } } },
+ "Inherited Class": { "ClassType": "datatype", "properties": { "Property": { "Type": "Nothing" } } }
}
}
@@ -253,7 +268,6 @@ Icons Description
earth | WEB Method |
zed | ZEN Method |
eye | Read Only |
-
@@ -267,25 +281,10 @@ Icons Description
to get additional information. Non-hoverable elements are usually those which
does not have any keywords or comments defined.
-
+
+ All links except inheritance are hoverable too. Hovering over links will
+ highlight appropriate fields in linked classes.
+
diff --git a/web/js/CacheClassExplorer.js b/web/js/CacheClassExplorer.js
index f8c1a0a..4d07582 100644
--- a/web/js/CacheClassExplorer.js
+++ b/web/js/CacheClassExplorer.js
@@ -74,6 +74,7 @@ var CacheClassExplorer = function (treeViewContainer, classViewContainer) {
}
this.classView = new ClassView(this, classViewContainer);
this.NAMESPACE = null;
+ this.HELP_INITIALIZED = false;
if (treeViewContainer) {
this.initSettings();
@@ -177,6 +178,31 @@ CacheClassExplorer.prototype.restoreFromURL = function () {
};
+CacheClassExplorer.prototype.initHelp = function () {
+
+ if (this.HELP_INITIALIZED) return;
+ this.HELP_INITIALIZED = true;
+
+ var cont = [].slice.call(document.querySelectorAll("#helpView *[name=injector]")),
+ cont2 = [].slice.call(document.querySelectorAll("#helpView *[name=icon]")), i;
+ for (i in cont) {
+ var ue, json = {
+ classes: { "Unable to parse JSON": { } }
+ };
+ try { json = JSON.parse(cont[i].textContent) } catch (e) { }
+ cont[i].textContent = "";
+ ue = new CacheClassExplorer(null, cont[i]);
+ ue.classView.injectView(json);
+ }
+ for (i in cont2) {
+ var ico = lib.image[cont2[i].textContent];
+ if (ico) {
+ cont2[i].innerHTML = "
"
+ }
+ }
+
+};
+
CacheClassExplorer.prototype.init = function () {
var self = this,
@@ -214,6 +240,7 @@ CacheClassExplorer.prototype.init = function () {
}
});
this.elements.helpButton.addEventListener("click", function () {
+ self.initHelp();
self.elements.helpView.classList.add("active");
});
this.elements.closeHelp.addEventListener("click", function () {
diff --git a/web/js/ClassView.js b/web/js/ClassView.js
index 97cf115..7bc0689 100644
--- a/web/js/ClassView.js
+++ b/web/js/ClassView.js
@@ -331,6 +331,9 @@ ClassView.prototype.getPropertyHoverText = function (prop, type) {
: "SoapAction="
+ "" + data + "";
},
+ "Default": function (data) {
+ return "Default = " + lib.highlightCOS(data + "");
+ },
"SqlProc": 1,
"WebMethod": 1,
"ZenMethod": 1,
@@ -412,7 +415,7 @@ ClassView.prototype.getPropertyHoverText = function (prop, type) {
var txt = [], val;
for (i in prop) {
- if (propText[i] && (prop[i] || i === "InitialExpression" || i === "ProcedureBlock")) {
+ if (propText[i] && (prop[i] || i === "InitialExpression" || i === "ProcedureBlock" || i === "Default")) {
val = propText[i] === 1
? "" + i + ""
: propText[i](prop[i], prop);
@@ -466,6 +469,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
for (n in params) {
keyWordsArray.push(n);
arr.push({
+ name: n,
text: n + (params[n]["Type"] ? ": " + params[n]["Type"] : ""),
hover: self.getPropertyHoverText(params[n], "parameter"),
icons: self.getPropertyIcons(params[n])
@@ -478,6 +482,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
for (n in ps) {
keyWordsArray.push(n);
arr.push({
+ name: n,
text: n + (ps[n]["Type"] ? ": " + ps[n]["Type"] : ""),
hover: self.getPropertyHoverText(ps[n], "property"),
icons: self.getPropertyIcons(ps[n])
@@ -490,6 +495,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
for (n in met) {
keyWordsArray.push(n);
arr.push({
+ name: n,
text: n + (met[n]["ReturnType"] ? ": " + met[n]["ReturnType"] : ""),
styles: (function (t) {
return t ? { textDecoration: "underline" } : {}
@@ -508,7 +514,8 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
for (n in qrs) {
keyWordsArray.push(n);
arr.push({
- text: n,
+ name: n,
+ text: n + (qrs[n]["Type"] ? ": " + qrs[n]["Type"] : ""),
icons: self.getPropertyIcons(qrs[n]),
hover: self.getPropertyHoverText(qrs[n], "query"),
clickHandler: (function (q, className) {
@@ -519,7 +526,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
return arr;
})(classQueries),
classSigns: this.getClassSigns(classMetaData),
- classType: classMetaData.$classType,
+ classType: classMetaData.ClassType || "registered",
SYMBOL_12_WIDTH: self.SYMBOL_12_WIDTH
});
@@ -678,7 +685,8 @@ ClassView.prototype.confirmRender = function (data) {
var link = function (type) {
var name = type === "inheritance" ? "Generalization" :
type === "aggregation" ? "Aggregation" : type === "composition" ? "Composition"
- : "Association";
+ : "Association",
+ linkData;
for (p in data[type]) {
relFrom = (classes[p] || {}).instance;
for (pp in data[type][p]) {
@@ -714,8 +722,16 @@ ClassView.prototype.confirmRender = function (data) {
if (link.left) arr.push(getLabel(link.left, LINK_TEXT_MARGIN));
if (link.right) arr.push(getLabel(link.right, -LINK_TEXT_MARGIN));
return arr;
- })(data[type][p][pp] || {})
+ })(linkData = data[type][p][pp] || {})
}));
+ if (linkData.from) {
+ connector._fromClass = linkData.from;
+ connector._fromClass.instance = relTo;
+ }
+ if (linkData.to) {
+ connector._toClass = linkData.to;
+ connector._toClass.instance = relFrom;
+ }
self.links.push(connector);
}
}
@@ -893,6 +909,53 @@ ClassView.prototype.searchOnDiagram = function (text) {
};
+ClassView.prototype.bindLinkHighlight = function () {
+
+ var self = this,
+ highlighted = false,
+ fields = [];
+
+ var freeFields = function () {
+ fields.forEach(function (f) {
+ if (f.classList) f.classList.remove("line-selected");
+ });
+ fields = [];
+ };
+
+ this.paper.on("cell:mouseover", function (e) {
+ var link, view, el;
+ freeFields();
+ link = e.model || null;
+ if (!link) return;
+ if (link._fromClass && link._fromClass.instance && link._fromClass.in
+ && (view = self.paper.findViewByModel(link._fromClass.instance))
+ && view.el && view.el._LINE_ELEMENTS && view.el._LINE_ELEMENTS[link._fromClass.in]
+ && (el = view.el._LINE_ELEMENTS[link._fromClass.in][link._fromClass.name])) {
+ fields.push(el);
+ }
+ if (link._toClass && link._toClass.instance && link._toClass.in
+ && (view = self.paper.findViewByModel(link._toClass.instance))
+ && view.el && view.el._LINE_ELEMENTS && view.el._LINE_ELEMENTS[link._toClass.in]
+ && (el = view.el._LINE_ELEMENTS[link._toClass.in][link._toClass.name])) {
+ fields.push(el);
+ }
+ fields.forEach(function (f) {
+ if (f.classList) {
+ f.classList.add("line-selected");
+ } else {
+ console.warn("Your browser does not support CSS3 classList property.");
+ }
+ });
+ highlighted = !!fields.length;
+ });
+
+ this.paper.on("cell:mouseout", function (e) {
+ highlighted = false;
+ freeFields();
+ });
+
+};
+
ClassView.prototype.init = function () {
var p, self = this,
@@ -912,6 +975,8 @@ ClassView.prototype.init = function () {
}
});
+ this.bindLinkHighlight();
+
// enables links re-routing when dragging objects
this.graph.on("change:position", function (object) {
if (_.contains(self.objects, object))
diff --git a/web/js/Logic.js b/web/js/Logic.js
index 628f3c1..e659380 100644
--- a/web/js/Logic.js
+++ b/web/js/Logic.js
@@ -41,8 +41,6 @@ Logic.prototype.process = function (data) {
if (cls.queries && !this.umlExplorer.settings.showQueries) delete cls.queries;
}
- this.alignClassTypes(); // call after inheritance scheme done
-
if (!this.umlExplorer.settings.showDataTypesOnDiagram) {
for (clsName in data.classes) {
if (/%Library\..*/.test(clsName)) delete data.classes[clsName];
@@ -105,17 +103,23 @@ Logic.prototype.fillAssociations = function () {
if (!aggr[po["Type"]]) aggr[po["Type"]] = {};
aggr[po["Type"]][className] = {
left: "many",
- right: "one"
+ right: "one",
+ from: { in: "properties", name: propertyName },
+ to: { in: "properties", name: po["Inverse"] }
};
} else if (po["Cardinality"] === "parent") {
if (!compos[po["Type"]]) compos[po["Type"]] = {};
compos[po["Type"]][className] = {
left: "child",
- right: "parent"
+ right: "parent",
+ from: { in: "properties", name: propertyName },
+ to: { in: "properties", name: po["Inverse"] }
};
} else if (self.data.classes[po["Type"]] && !po["Cardinality"]) {
if (!assoc[po["Type"]]) assoc[po["Type"]] = {};
- assoc[po["Type"]][className] = {};
+ assoc[po["Type"]][className] = {
+ from: { in: "properties", name: propertyName }
+ };
}
}
}
@@ -140,74 +144,4 @@ Logic.prototype.inherits = function (className, inhName) {
this.data.classes[inhName] = {};
}
-};
-
-/**
- * @private
- * @param {string} className
- * @returns {string[]} - derived class names
- */
-Logic.prototype.getDerivedClasses = function (className) {
-
- var arr = [];
-
- for (var a in this.data.inheritance) {
- if (this.data.inheritance[a][className]) arr.push(a);
- }
-
- return arr;
-
-};
-
-Logic.prototype.getNonInheritingClasses = function () {
-
- var arr = [];
-
- for (var className in this.data.classes) {
- if (!this.data.inheritance[className]) arr.push(className);
- }
-
- return arr;
-
-};
-
-/**
- * Correcting Cache's class keyword "ClassType".
- * @param classType
- * @returns {*}
- */
-Logic.prototype.getNormalClassType = function (classType) {
-
- if (classType === "datatype") return "DataType";
- else if (classType === "serial") return "Serial";
- else if (classType === "persistent") return "Persistent";
- else return lib.capitalize(classType); // (Registered), Stream, View, Index
-
-};
-
-/**
- * Fills $classType
- * @private
- */
-Logic.prototype.alignClassTypes = function () {
-
- var self = this;
-
- var extendDerivedClasses = function (className, classObj, root) {
- (root ? self.getNonInheritingClasses() : self.getDerivedClasses(className)).forEach(
- function (derivedClassName) {
- var derivedObj = self.data.classes[derivedClassName];
- if (!derivedObj.$classType) { // not assigned yet
- // try to get class type from parent
- if (classObj.$classType) derivedObj.$classType = classObj.$classType;
- // reassign class type from classType property
- if (derivedObj.ClassType)
- derivedObj.$classType = self.getNormalClassType(derivedObj.ClassType);
- }
- extendDerivedClasses(derivedClassName, derivedObj);
- });
- };
-
- extendDerivedClasses("", {}, true);
-
};
\ No newline at end of file
diff --git a/web/jsLib/joint.js b/web/jsLib/joint.js
index fd65f86..e3fef41 100644
--- a/web/jsLib/joint.js
+++ b/web/jsLib/joint.js
@@ -17213,6 +17213,9 @@ if ( typeof window === "object" && typeof window.document === "object" ) {
}
textNode.TRASH = [];
+ var theParent = ((this.node || {}).parentNode || {}).parentNode;
+ if (theParent && !theParent._LINE_ELEMENTS) theParent._LINE_ELEMENTS = {};
+
for (; i < lines.length; i++) {
var jj, setup, iconLeft, xOrigin = this.attr('x') || 0,
@@ -17257,7 +17260,12 @@ if ( typeof window === "object" && typeof window.document === "object" ) {
// space (an invisible character) so that following lines are correctly
// relatively positioned. `dy=1em` won't work with empty lines otherwise.
tspan.node.textContent = lines[i].text || ' ';
-
+
+ if (theParent && lines[i]._BLOCK) {
+ if (!theParent._LINE_ELEMENTS[lines[i]._BLOCK]) theParent._LINE_ELEMENTS[lines[i]._BLOCK] = {};
+ theParent._LINE_ELEMENTS[lines[i]._BLOCK][lines[i].name] = tspan.node;
+ }
+
V(textNode).append(tspan);
if (lines[i].icons instanceof Array) {
diff --git a/web/jsLib/joint.shapes.uml.js b/web/jsLib/joint.shapes.uml.js
index 644903a..8abbba1 100644
--- a/web/jsLib/joint.shapes.uml.js
+++ b/web/jsLib/joint.shapes.uml.js
@@ -100,10 +100,10 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({
var o,
rects = [
{ type: 'name', text: this.getClassName() },
- { type: 'params', text: (o = this.get('params')) , o: o },
- { type: 'attrs', text: (o = this.get('attributes')), o: o },
- { type: 'methods', text: (o = this.get('methods')) , o: o },
- { type: 'queries', text: (o = this.get('queries')) , o: o }
+ { type: 'params', text: (o = this.get('params')) , o: (o.forEach(function(e){e._BLOCK="parameters"}) && o) },
+ { type: 'attrs', text: (o = this.get('attributes')), o: (o.forEach(function(e){e._BLOCK="properties"}) && o) },
+ { type: 'methods', text: (o = this.get('methods')) , o: (o.forEach(function(e){e._BLOCK="methods"}) && o) },
+ { type: 'queries', text: (o = this.get('queries')) , o: (o.forEach(function(e){e._BLOCK="queries"}) && o) }
],
self = this,
classSigns = this.get('classSigns'),
@@ -114,13 +114,13 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({
// set color head according to class type
var headColor;
switch (CLASS_TYPE) {
- case "Persistent": headColor = "rgb(255,219,170)"; break; // light orange
- case "Serial": headColor = "rgb(252,255,149)"; break; // light yellow
+ case "persistent": headColor = "rgb(255,219,170)"; break; // light orange
+ case "serial": headColor = "rgb(252,255,149)"; break; // light yellow
//case "Registered": headColor = "rgb(192,255,170)"; break; // light green
- case "DataType": headColor = "rgb(193,250,255)"; break; // light blue
- case "Stream": headColor = "rgb(246,188,255)"; break; // light magenta
- case "View": headColor = "rgb(255,188,188)"; break; // light red
- case "Index": headColor = "rgb(228,228,228)"; break; // light gray
+ case "datatype": headColor = "rgb(193,250,255)"; break; // light blue
+ case "stream": headColor = "rgb(246,188,255)"; break; // light magenta
+ case "view": headColor = "rgb(255,188,188)"; break; // light red
+ case "index": headColor = "rgb(228,228,228)"; break; // light gray
}
if (headColor) this.attributes.attrs[".uml-class-name-rect"].fill = headColor;