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

earthWEB Method zedZEN Method eyeRead 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;