diff --git a/README.md b/README.md index 5129673..a1bfe7e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cache UML Explorer +# Cache Class Explorer An UML Class explorer for InterSystems Caché. ##### Key features @@ -6,6 +6,7 @@ An UML Class explorer for InterSystems Caché. + Build diagrams for any package or subpackage; + Edit diagrams after build; + Export diagrams as an image; ++ See Class methods, properties, parameters, SQL queries and more; + View class methods code with syntax highlighting; + Zoom in and out; + Search on diagram or in class tree; diff --git a/cache/projectTemplate.xml b/cache/projectTemplate.xml index af35049..43bfcb2 100644 --- a/cache/projectTemplate.xml +++ b/cache/projectTemplate.xml @@ -4,7 +4,7 @@ Cache UML Explorer vX.X.X/*build.replace:pkg.version*/ Class contains methods that return structured classes/packages data. -63808,84964.699928 +63830,81286.756889 63653,67019.989197 @@ -93,6 +93,7 @@ Return structured data about class. do oData.classes.%DispatchSetProperty(classDefinition.Name, oClass) // prevent from recursive setup set package = $LISTTOSTRING($LIST($LISTFROMSTRING(classDefinition.Name, "."), 1, *-1),".") set oProperties = ##class(%ZEN.proxyObject).%New() + set oQueries = ##class(%ZEN.proxyObject).%New() set oClass.NAMESPACE = $NAMESPACE set oClass.SYSTEM = classDefinition.System @@ -109,57 +110,67 @@ Return structured data about class. set oClass.properties = oProperties set count = classDefinition.Properties.Count() + set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.PropertyDefinition") for i=1:1:count { set oProp = ##class(%ZEN.proxyObject).%New() set p = classDefinition.Properties.GetAt(i) do oProperties.%DispatchSetProperty(p.Name, oProp) - set oProp.private = p.Private - set oProp.readOnly = p.ReadOnly - set oProp.cardinality = p.Cardinality - set oProp.inverse = p.Inverse + for j=1:1:props.Properties.Count() { + set pname = props.Properties.GetAt(j).Name + set:(pname '= "parent") $PROPERTY(oProp, pname) = $PROPERTY(p, pname) + } if (..classExists(package _ "." _ p.Type)) { - set oProp.type = package _ "." _ p.Type + set oProp.Type = package _ "." _ p.Type do ..fillClassData(oData, package _ "." _ p.Type) } elseif (..classExists(..extendClassFromType(p.Type))) { - set oProp.type = ..extendClassFromType(p.Type) + set oProp.Type = ..extendClassFromType(p.Type) do ..fillClassData(oData, ..extendClassFromType(p.Type)) } else { - set oProp.type = p.Type + set oProp.Type = ..extendClassFromType(p.Type) } } set oMethods = ##class(%ZEN.proxyObject).%New() set oClass.methods = oMethods set count = classDefinition.Methods.Count() + set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.MethodDefinition") for i=1:1:count { set oMeth = ##class(%ZEN.proxyObject).%New() set met = classDefinition.Methods.GetAt(i) do oMethods.%DispatchSetProperty(met.Name, oMeth) - set oMeth.private = met.Private - set oMeth.returns = met.ReturnType - set oMeth.classMethod = met.ClassMethod - set oMeth.clientMethod = met.ClientMethod - set oMeth.final = met.Final - set oMeth.abstract = met.Abstract - set oMeth.language = met.Language - set oMeth.notInheritable = met.NotInheritable - set oMeth.serverOnly = met.ServerOnly - set oMeth.sqlProc = met.SqlProc - set oMeth.sqlName = met.SqlName - set oMeth.webMethod = met.WebMethod - set oMeth.zenMethod = met.ZenMethod + for j=1:1:props.Properties.Count() { + set pname = props.Properties.GetAt(j).Name + set:((pname '= "parent") && (pname '= "Implementation")) $PROPERTY(oMeth, pname) = $PROPERTY(met, pname) + } } set oParameters = ##class(%ZEN.proxyObject).%New() set oClass.parameters = oParameters set count = classDefinition.Parameters.Count() + set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.ParameterDefinition") for i=1:1:count { set oPar = ##class(%ZEN.proxyObject).%New() set p = classDefinition.Parameters.GetAt(i) - set oPar.type = p.Type + for j=1:1:props.Properties.Count() { + set pname = props.Properties.GetAt(j).Name + set:(pname '= "parent") $PROPERTY(oPar, pname) = $PROPERTY(p, pname) + } do oParameters.%DispatchSetProperty(p.Name, oPar) } + #dim q as %Dictionary.QueryDefinition + set oClass.queries = oQueries + set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.QueryDefinition") + for i=1:1:classDefinition.Queries.Count() { + set oProp = ##class(%ZEN.proxyObject).%New() + set q = classDefinition.Queries.GetAt(i) + for j=1:1:props.Properties.Count() { + set pname = props.Properties.GetAt(j).Name + set:(pname '= "parent") $PROPERTY(oProp, pname) = $PROPERTY(q, pname) + } + do oQueries.%DispatchSetProperty(q.Name, oProp) + } + do ..collectInheritance(oData, oClass.super) quit oClass diff --git a/package.json b/package.json index efb2207..0eeb735 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CacheUMLExplorer", - "version": "1.2.0", + "version": "1.3.0", "description": "An UML Class explorer for InterSystems Caché", "directories": { "test": "test" diff --git a/web/css/classView.css b/web/css/classView.css index 7be2a4f..24ae0b2 100644 --- a/web/css/classView.css +++ b/web/css/classView.css @@ -19,7 +19,7 @@ svg { /*fill: lightgray;*/ } -.uml-class-attrs-rect, .uml-class-methods-rect { +.uml-class-attrs-rect, .uml-class-methods-rect, .uml-class-queries-rect { fill: white; } @@ -66,6 +66,12 @@ text { fill: blue; } +.uml-class-queries-label { + font-family: monospace; + font-weight: 900; + fill: #0d0; +} + .uml-class-params-label { font-family: monospace; font-weight: 900; diff --git a/web/js/ClassView.js b/web/js/ClassView.js index 0f869a9..f4d0733 100644 --- a/web/js/ClassView.js +++ b/web/js/ClassView.js @@ -237,8 +237,8 @@ ClassView.prototype.renderInfoGraphic = function () { aggregation: { "Persistent class (one)": { "DataType class (many)": { - left: "*", - right: 1 + left: "many", + right: "one" } } }, @@ -409,19 +409,21 @@ ClassView.prototype.getClassSigns = function (classMetaData) { * * @param method */ -ClassView.prototype.getMethodIcons = function (method) { +ClassView.prototype.getPropertyIcons = function (method) { var icons = []; - icons.push({ src: lib.image[method["private"] ? "minus" : "plus"] }); - if (method["abstract"]) icons.push({ src: lib.image.crystalBall }); - if (method["clientMethod"]) icons.push({ src: lib.image.user }); - if (method["final"]) icons.push({ src: lib.image.blueFlag }); - if (method["notInheritable"]) icons.push({ src: lib.image.redFlag }); - if (method["sqlProc"]) icons.push({ src: lib.image.table }); - if (method["webMethod"]) icons.push({ src: lib.image.earth }); - if (method["zenMethod"]) icons.push({ src: lib.image.zed }); - if (method["readOnly"]) icons.push({ src: lib.image.eye }); + if (typeof method["Private"] !== "undefined") { + icons.push({ src: lib.image[method["Private"] ? "minus" : "plus"] }); + } + if (method["Abstract"]) icons.push({ src: lib.image.crystalBall }); + if (method["ClientMethod"]) icons.push({ src: lib.image.user }); + if (method["Final"]) icons.push({ src: lib.image.blueFlag }); + if (method["NotInheritable"]) icons.push({ src: lib.image.redFlag }); + if (method["SqlProc"]) icons.push({ src: lib.image.table }); + if (method["WebMethod"]) icons.push({ src: lib.image.earth }); + if (method["ZenMethod"]) icons.push({ src: lib.image.zed }); + if (method["ReadOnly"]) icons.push({ src: lib.image.eye }); return icons; @@ -437,6 +439,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) { var classParams = classMetaData["parameters"], classProps = classMetaData["properties"], classMethods = classMetaData["methods"], + classQueries = classMetaData["queries"], keyWordsArray = [name], self = this; @@ -455,7 +458,8 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) { for (n in params) { keyWordsArray.push(n); arr.push({ - text: n + (params[n]["type"] ? ": " + params[n]["type"] : "") + text: n + (params[n]["Type"] ? ": " + params[n]["Type"] : ""), + icons: self.getPropertyIcons(params[n]) }); } return arr; @@ -465,8 +469,8 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) { for (n in ps) { keyWordsArray.push(n); arr.push({ - text: n + (ps[n]["type"] ? ": " + ps[n]["type"] : ""), - icons: self.getMethodIcons(ps[n]) + text: n + (ps[n]["Type"] ? ": " + ps[n]["Type"] : ""), + icons: self.getPropertyIcons(ps[n]) }); } return arr; @@ -476,18 +480,29 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) { for (n in met) { keyWordsArray.push(n); arr.push({ - text: n + (met[n]["returns"] ? ": " + met[n]["returns"] : ""), + text: n + (met[n]["ReturnType"] ? ": " + met[n]["ReturnType"] : ""), styles: (function (t) { return t ? { textDecoration: "underline" } : {} })(met[n]["classMethod"]), clickHandler: (function (n) { return function () { self.showMethodCode(name, n); } })(n), - icons: self.getMethodIcons(met[n]) + icons: self.getPropertyIcons(met[n]) }); } return arr; })(classMethods), + queries: (function (qrs) { + var arr = [], n; + for (n in qrs) { + keyWordsArray.push(n); + arr.push({ + text: n, + icons: self.getPropertyIcons(qrs[n]) + }); + } + return arr; + })(classQueries), classSigns: this.getClassSigns(classMetaData), classType: classMetaData.$classType, SYMBOL_12_WIDTH: self.SYMBOL_12_WIDTH @@ -591,9 +606,11 @@ ClassView.prototype.render = function (data) { ClassView.prototype.confirmRender = function (data) { var self = this, p, pp, className, + LINK_TEXT_MARGIN = 22, uml = joint.shapes.uml, relFrom, relTo, classes = {}, connector; + console.log(data); this.filterInherits(data); // Reset view and zoom again because it may cause visual damage to icons. @@ -648,8 +665,8 @@ ClassView.prototype.confirmRender = function (data) { } } }; - if (link.left) arr.push(getLabel(link.left, 10)); - if (link.right) arr.push(getLabel(link.right, -10)); + 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] || {}) })); diff --git a/web/js/Logic.js b/web/js/Logic.js index 5e270c6..c5ba36a 100644 --- a/web/js/Logic.js +++ b/web/js/Logic.js @@ -75,21 +75,21 @@ Logic.prototype.fillAssociations = function () { if (!properties) continue; for (propertyName in properties) { po = properties[propertyName]; - if (po["cardinality"] === "one") { - if (!aggr[po.type]) aggr[po.type] = {}; - aggr[po.type][className] = { - left: "*", - right: "1" + if (po["Cardinality"] === "one") { + if (!aggr[po["Type"]]) aggr[po["Type"]] = {}; + aggr[po["Type"]][className] = { + left: "many", + right: "one" }; - } else if (po["cardinality"] === "parent") { - if (!compos[po.type]) compos[po.type] = {}; - compos[po.type][className] = { - left: "*", - right: "1" + } else if (po["Cardinality"] === "parent") { + if (!compos[po["Type"]]) compos[po["Type"]] = {}; + compos[po["Type"]][className] = { + left: "child", + right: "parent" }; - } else if (self.data.classes[po.type] && !po["cardinality"]) { - if (!assoc[po.type]) assoc[po.type] = {}; - assoc[po.type][className] = {}; + } else if (self.data.classes[po["Type"]] && !po["Cardinality"]) { + if (!assoc[po["Type"]]) assoc[po["Type"]] = {}; + assoc[po["Type"]][className] = {}; } } } diff --git a/web/jsLib/joint.shapes.uml.js b/web/jsLib/joint.shapes.uml.js index 690d47b..644903a 100644 --- a/web/jsLib/joint.shapes.uml.js +++ b/web/jsLib/joint.shapes.uml.js @@ -22,11 +22,14 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ 'Properties', '', 'Methods', + '', + 'Queries', '', '', '', '', '', + '', '' ].join(''), @@ -46,6 +49,7 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ '.uml-class-params-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': 'white' }, '.uml-class-attrs-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': '#2980b9' }, '.uml-class-methods-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': '#2980b9' }, + '.uml-class-queries-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': '#2980b9' }, '.uml-class-name-text': { 'ref': '.uml-class-name-rect', 'ref-y': .5, 'ref-x': .5, 'text-anchor': 'middle', 'y-alignment': 'middle', 'font-weight': 'bold', @@ -63,6 +67,10 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ 'ref': '.uml-class-methods-rect', 'ref-y': 5, 'ref-x': 5, 'fill': 'black', 'font-size': 12 }, + '.uml-class-queries-text': { + 'ref': '.uml-class-queries-rect', 'ref-y': 5, 'ref-x': 5, + 'fill': 'black', 'font-size': 12 + }, '.uml-class-attrs-label': { ref: '.uml-class-attrs-label', fill: "black", 'font-size': 10, xPos: -56 @@ -70,6 +78,9 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ '.uml-class-methods-label': { ref: '.uml-class-methods-label', fill: "black", 'font-size': 10 }, + '.uml-class-queries-label': { + ref: '.uml-class-queries-label', fill: "black", 'font-size': 10 + }, '.uml-class-params-label': { ref: '.uml-class-methods-label', fill: "black", 'font-size': 10 } @@ -79,6 +90,7 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ params: [], attributes: [], methods: [], + queries: [], classSigns: [] }, joint.shapes.basic.Generic.prototype.defaults), @@ -90,7 +102,8 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ { 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: 'methods', text: (o = this.get('methods')) , o: o }, + { type: 'queries', text: (o = this.get('queries')) , o: o } ], self = this, classSigns = this.get('classSigns'), @@ -185,7 +198,8 @@ joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ { type: 'name', text: this.getClassName() }, { type: 'params', text: this.get('params') }, { type: 'attrs', text: this.get('attributes') }, - { type: 'methods', text: this.get('methods') } + { type: 'methods', text: this.get('methods') }, + { type: 'queries', text: this.get('queries') } ]; var offsetY = 0;