Skip to content

Commit d78c224

Browse files
highlighting fields on link hover, some help bug fixes and minor improvements
1 parent f5ca4ca commit d78c224

9 files changed

+167
-124
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ An UML Class explorer for InterSystems Caché.
99
+ Export diagrams as an image;
1010
+ See Class methods, properties, parameters, SQL queries and more;
1111
+ See any keywords and related information by hovering over everything with pointer;
12+
+ Check which fields are connected by hovering over link;
1213
+ View class methods code with syntax highlighting;
1314
+ Zoom in and out;
1415
+ Search on diagram or in class tree;

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "CacheClassExplorer",
3-
"version": "1.9",
3+
"version": "1.10.4",
44
"description": "Class Explorer for InterSystems Caché",
55
"directories": {
66
"test": "test"

web/css/classView.css

+9
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ text {
9090
fill: red;
9191
}
9292

93+
.line-selected {
94+
fill: red;
95+
text-shadow: 0 1px 3px #CCC;
96+
-webkit-transition: all .2s ease;
97+
-moz-transition: all .2s ease;
98+
-o-transition: all .2s ease;
99+
transition: all .2s ease;
100+
}
101+
93102
.inlineSearchBlock {
94103
display: inline-block;
95104
vertical-align: bottom;

web/index.html

+31-32
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,36 @@ <h2>Caché Class Explorer Help</h2>
159159
<td>
160160
<div name="injector">{
161161
"classes": { "Registered": { } }
162-
}</div>
162+
}</div>
163+
</td>
164+
<td>
165+
<div name="injector">{
166+
"classes": { "Persistent": { "ClassType": "persistent" } }
167+
}</div>
168+
</td>
169+
<td>
170+
<div name="injector">{
171+
"classes": { "Serial": { "ClassType": "serial" } }
172+
}</div>
173+
</td>
174+
<td>
175+
<div name="injector">{
176+
"classes": { "Data Type": { "ClassType": "datatype" } }
177+
}</div>
163178
</td>
164179
<td>
165180
<div name="injector">{
166-
"classes": { "Persistent": { "ClassType": "Persistent" } }
181+
"classes": { "Index": { "ClassType": "index" } }
167182
}</div>
168183
</td>
169184
<td>
170185
<div name="injector">{
171-
"classes": { "Serial": { "ClassType": "Serial" } }
186+
"classes": { "View": { "ClassType": "view" } }
172187
}</div>
173188
</td>
174189
<td>
175190
<div name="injector">{
176-
"classes": { "Data Type": { "ClassType": "DataType" } }
191+
"classes": { "Stream": { "ClassType": "stream" } }
177192
}</div>
178193
</td>
179194
</tr>
@@ -185,13 +200,13 @@ <h2>Caché Class Explorer Help</h2>
185200
<th colspan="2" class="leftAligned"><h2>Connection Types</h2></th>
186201
</tr>
187202
<tr>
188-
<th class="leftAligned">Association</th>
203+
<th class="leftAligned">Class Mention</th>
189204
<td>
190205
<div name="injector">{
191206
"layoutDirection": "LR",
192207
"classes": {
193208
"Class A": { "properties": { "Property": { "Type": "Class B" } } },
194-
"Class B": { "ClassType": "Persistent" }
209+
"Class B": { "ClassType": "persistent" }
195210
}
196211
}</div>
197212
</td>
@@ -202,8 +217,8 @@ <h2>Caché Class Explorer Help</h2>
202217
<div name="injector">{
203218
"layoutDirection": "LR",
204219
"classes": {
205-
"Class A": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "one", "Type": "Class B" } } },
206-
"Class B": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "many", "Type": "Class A" } } }
220+
"Class A": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "one", "Type": "Class B", "Inverse": "Property" } } },
221+
"Class B": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "many", "Type": "Class A", "Inverse": "Property" } } }
207222
}
208223
}</div>
209224
</td>
@@ -214,8 +229,8 @@ <h2>Caché Class Explorer Help</h2>
214229
<div name="injector">{
215230
"layoutDirection": "LR",
216231
"classes": {
217-
"Class B": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "child", "Type": "Class A" } } },
218-
"Class A": { "ClassType": "Persistent", "properties": { "Property": { "Cardinality": "parent", "Type": "Class B" } } }
232+
"Class B": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "child", "Type": "Class A", "Inverse": "Property" } } },
233+
"Class A": { "ClassType": "persistent", "properties": { "Property": { "Cardinality": "parent", "Type": "Class B", "Inverse": "Property" } } }
219234
}
220235
}</div>
221236
</td>
@@ -226,8 +241,8 @@ <h2>Caché Class Explorer Help</h2>
226241
<div name="injector">{
227242
"layoutDirection": "LR",
228243
"classes": {
229-
"Derived Class": { "ClassType": "Persistent", "Super": "Inherited Class", "properties": { "Property": { "Type": "Nothing" } } },
230-
"Inherited Class": { "ClassType": "DataType", "properties": { "Property": { "Type": "Nothing" } } }
244+
"Derived Class": { "ClassType": "datatype", "Super": "Inherited Class", "properties": { "Property": { "Type": "Nothing" } } },
245+
"Inherited Class": { "ClassType": "datatype", "properties": { "Property": { "Type": "Nothing" } } }
231246
}
232247
}</div>
233248
</td>
@@ -253,7 +268,6 @@ <h2>Icons Description</h2>
253268
<tr><td name="icon">earth</td><td>WEB Method</td></tr>
254269
<tr><td name="icon">zed</td><td>ZEN Method</td></tr>
255270
<tr><td name="icon">eye</td><td>Read Only</td></tr>
256-
257271
</table>
258272
</div>
259273
</div>
@@ -267,25 +281,10 @@ <h2>Icons Description</h2>
267281
to get additional information. Non-hoverable elements are usually those which
268282
does not have any keywords or comments defined.
269283
</p>
270-
<script>
271-
var cont = [].slice.call(document.querySelectorAll("#helpView *[name=injector]")),
272-
cont2 = [].slice.call(document.querySelectorAll("#helpView *[name=icon]")), i;
273-
for (i in cont) {
274-
var ue, json = {
275-
classes: { "Unable to parse JSON": { } }
276-
};
277-
try { json = JSON.parse(cont[i].textContent) } catch (e) { }
278-
cont[i].textContent = "";
279-
ue = new CacheClassExplorer(null, cont[i]);
280-
ue.classView.injectView(json);
281-
}
282-
for (i in cont2) {
283-
var ico = lib.image[cont2[i].textContent];
284-
if (ico) {
285-
cont2[i].innerHTML = "<img src=\"" + ico + "\"/>"
286-
}
287-
}
288-
</script>
284+
<p>
285+
All links except inheritance are <b>hoverable</b> too. Hovering over links will
286+
highlight appropriate fields in linked classes.
287+
</p>
289288
</div>
290289
</div>
291290
</div>

web/js/CacheClassExplorer.js

+27
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ var CacheClassExplorer = function (treeViewContainer, classViewContainer) {
7474
}
7575
this.classView = new ClassView(this, classViewContainer);
7676
this.NAMESPACE = null;
77+
this.HELP_INITIALIZED = false;
7778

7879
if (treeViewContainer) {
7980
this.initSettings();
@@ -177,6 +178,31 @@ CacheClassExplorer.prototype.restoreFromURL = function () {
177178

178179
};
179180

181+
CacheClassExplorer.prototype.initHelp = function () {
182+
183+
if (this.HELP_INITIALIZED) return;
184+
this.HELP_INITIALIZED = true;
185+
186+
var cont = [].slice.call(document.querySelectorAll("#helpView *[name=injector]")),
187+
cont2 = [].slice.call(document.querySelectorAll("#helpView *[name=icon]")), i;
188+
for (i in cont) {
189+
var ue, json = {
190+
classes: { "Unable to parse JSON": { } }
191+
};
192+
try { json = JSON.parse(cont[i].textContent) } catch (e) { }
193+
cont[i].textContent = "";
194+
ue = new CacheClassExplorer(null, cont[i]);
195+
ue.classView.injectView(json);
196+
}
197+
for (i in cont2) {
198+
var ico = lib.image[cont2[i].textContent];
199+
if (ico) {
200+
cont2[i].innerHTML = "<img src=\"" + ico + "\"/>"
201+
}
202+
}
203+
204+
};
205+
180206
CacheClassExplorer.prototype.init = function () {
181207

182208
var self = this,
@@ -214,6 +240,7 @@ CacheClassExplorer.prototype.init = function () {
214240
}
215241
});
216242
this.elements.helpButton.addEventListener("click", function () {
243+
self.initHelp();
217244
self.elements.helpView.classList.add("active");
218245
});
219246
this.elements.closeHelp.addEventListener("click", function () {

web/js/ClassView.js

+70-5
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ ClassView.prototype.getPropertyHoverText = function (prop, type) {
331331
: "<span class=\"syntax-keyword\">SoapAction</span>="
332332
+ "<span class=\"syntax-string\">" + data + "</span>";
333333
},
334+
"Default": function (data) {
335+
return "Default = " + lib.highlightCOS(data + "");
336+
},
334337
"SqlProc": 1,
335338
"WebMethod": 1,
336339
"ZenMethod": 1,
@@ -412,7 +415,7 @@ ClassView.prototype.getPropertyHoverText = function (prop, type) {
412415

413416
var txt = [], val;
414417
for (i in prop) {
415-
if (propText[i] && (prop[i] || i === "InitialExpression" || i === "ProcedureBlock")) {
418+
if (propText[i] && (prop[i] || i === "InitialExpression" || i === "ProcedureBlock" || i === "Default")) {
416419
val = propText[i] === 1
417420
? "<span class=\"syntax-keyword\">" + i + "</span>"
418421
: propText[i](prop[i], prop);
@@ -466,6 +469,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
466469
for (n in params) {
467470
keyWordsArray.push(n);
468471
arr.push({
472+
name: n,
469473
text: n + (params[n]["Type"] ? ": " + params[n]["Type"] : ""),
470474
hover: self.getPropertyHoverText(params[n], "parameter"),
471475
icons: self.getPropertyIcons(params[n])
@@ -478,6 +482,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
478482
for (n in ps) {
479483
keyWordsArray.push(n);
480484
arr.push({
485+
name: n,
481486
text: n + (ps[n]["Type"] ? ": " + ps[n]["Type"] : ""),
482487
hover: self.getPropertyHoverText(ps[n], "property"),
483488
icons: self.getPropertyIcons(ps[n])
@@ -490,6 +495,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
490495
for (n in met) {
491496
keyWordsArray.push(n);
492497
arr.push({
498+
name: n,
493499
text: n + (met[n]["ReturnType"] ? ": " + met[n]["ReturnType"] : ""),
494500
styles: (function (t) {
495501
return t ? { textDecoration: "underline" } : {}
@@ -508,7 +514,8 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
508514
for (n in qrs) {
509515
keyWordsArray.push(n);
510516
arr.push({
511-
text: n,
517+
name: n,
518+
text: n + (qrs[n]["Type"] ? ": " + qrs[n]["Type"] : ""),
512519
icons: self.getPropertyIcons(qrs[n]),
513520
hover: self.getPropertyHoverText(qrs[n], "query"),
514521
clickHandler: (function (q, className) {
@@ -519,7 +526,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
519526
return arr;
520527
})(classQueries),
521528
classSigns: this.getClassSigns(classMetaData),
522-
classType: classMetaData.$classType,
529+
classType: classMetaData.ClassType || "registered",
523530
SYMBOL_12_WIDTH: self.SYMBOL_12_WIDTH
524531
});
525532

@@ -678,7 +685,8 @@ ClassView.prototype.confirmRender = function (data) {
678685
var link = function (type) {
679686
var name = type === "inheritance" ? "Generalization" :
680687
type === "aggregation" ? "Aggregation" : type === "composition" ? "Composition"
681-
: "Association";
688+
: "Association",
689+
linkData;
682690
for (p in data[type]) {
683691
relFrom = (classes[p] || {}).instance;
684692
for (pp in data[type][p]) {
@@ -714,8 +722,16 @@ ClassView.prototype.confirmRender = function (data) {
714722
if (link.left) arr.push(getLabel(link.left, LINK_TEXT_MARGIN));
715723
if (link.right) arr.push(getLabel(link.right, -LINK_TEXT_MARGIN));
716724
return arr;
717-
})(data[type][p][pp] || {})
725+
})(linkData = data[type][p][pp] || {})
718726
}));
727+
if (linkData.from) {
728+
connector._fromClass = linkData.from;
729+
connector._fromClass.instance = relTo;
730+
}
731+
if (linkData.to) {
732+
connector._toClass = linkData.to;
733+
connector._toClass.instance = relFrom;
734+
}
719735
self.links.push(connector);
720736
}
721737
}
@@ -893,6 +909,53 @@ ClassView.prototype.searchOnDiagram = function (text) {
893909

894910
};
895911

912+
ClassView.prototype.bindLinkHighlight = function () {
913+
914+
var self = this,
915+
highlighted = false,
916+
fields = [];
917+
918+
var freeFields = function () {
919+
fields.forEach(function (f) {
920+
if (f.classList) f.classList.remove("line-selected");
921+
});
922+
fields = [];
923+
};
924+
925+
this.paper.on("cell:mouseover", function (e) {
926+
var link, view, el;
927+
freeFields();
928+
link = e.model || null;
929+
if (!link) return;
930+
if (link._fromClass && link._fromClass.instance && link._fromClass.in
931+
&& (view = self.paper.findViewByModel(link._fromClass.instance))
932+
&& view.el && view.el._LINE_ELEMENTS && view.el._LINE_ELEMENTS[link._fromClass.in]
933+
&& (el = view.el._LINE_ELEMENTS[link._fromClass.in][link._fromClass.name])) {
934+
fields.push(el);
935+
}
936+
if (link._toClass && link._toClass.instance && link._toClass.in
937+
&& (view = self.paper.findViewByModel(link._toClass.instance))
938+
&& view.el && view.el._LINE_ELEMENTS && view.el._LINE_ELEMENTS[link._toClass.in]
939+
&& (el = view.el._LINE_ELEMENTS[link._toClass.in][link._toClass.name])) {
940+
fields.push(el);
941+
}
942+
fields.forEach(function (f) {
943+
if (f.classList) {
944+
f.classList.add("line-selected");
945+
} else {
946+
console.warn("Your browser does not support CSS3 classList property.");
947+
}
948+
});
949+
highlighted = !!fields.length;
950+
});
951+
952+
this.paper.on("cell:mouseout", function (e) {
953+
highlighted = false;
954+
freeFields();
955+
});
956+
957+
};
958+
896959
ClassView.prototype.init = function () {
897960

898961
var p, self = this,
@@ -912,6 +975,8 @@ ClassView.prototype.init = function () {
912975
}
913976
});
914977

978+
this.bindLinkHighlight();
979+
915980
// enables links re-routing when dragging objects
916981
this.graph.on("change:position", function (object) {
917982
if (_.contains(self.objects, object))

0 commit comments

Comments
 (0)