@vitormnm/node-red-simple-opcua 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitormnm/node-red-simple-opcua",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -730,6 +730,30 @@
730
730
  function getNamespaceOptions() {
731
731
  return Array.isArray(editorState.nameSpaces) ? editorState.nameSpaces.slice().sort(function (left, right) { return left.id - right.id; }) : [];
732
732
  }
733
+
734
+ function getDefinedObjectTypeNames() {
735
+ var names = [];
736
+ (editorState.objectsTypes || []).forEach(function (ot) {
737
+ if (ot && ot.name) names.push(String(ot.name));
738
+ });
739
+ return names;
740
+ }
741
+
742
+
743
+ function buildObjectTypeSelect(id, currentValue) {
744
+ var names = getDefinedObjectTypeNames();
745
+ var cv = String(currentValue || "");
746
+ var opts = "";
747
+ var noneSelected = (cv === "") ? " selected" : "";
748
+ opts += "<option value=\"\"" + noneSelected + ">\u2014 none \u2014</option>";
749
+ names.forEach(function (n) {
750
+ var esc = n.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
751
+ var sel = (n === cv) ? " selected" : "";
752
+ opts += "<option value=\"" + esc + "\"" + sel + ">" + esc + "</option>";
753
+ });
754
+ return "<select id=\"" + id + "\">" + opts + "</select>";
755
+ }
756
+
733
757
  function getNamespaceLabel(namespaceId) {
734
758
  var match = getNamespaceOptions().find(function (item) { return item.id === normalizeNamespaceId(namespaceId); });
735
759
  return match ? String(match.id) + " - " + match.name : String(normalizeNamespaceId(namespaceId));
@@ -900,43 +924,72 @@
900
924
  return "fa-tag";
901
925
  }
902
926
 
903
- function createNodeElement(path, depth, ancestorMatched) {
927
+ // ── performance helpers ───────────────────────────────────────────
928
+ var _renderTreePending = false;
929
+
930
+ function debounce(fn, delay) {
931
+ var timer;
932
+ return function () { var ctx = this, args = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(ctx, args); }, delay); };
933
+ }
934
+
935
+ function escapeHtml(v) {
936
+ return String(v || "").replace(/[&<>"']/g, function (c) {
937
+ return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
938
+ });
939
+ }
940
+
941
+ function appendNodeToFrag(frag, path, depth, ancestorMatched) {
904
942
  var item = getAtPath(editorState, path);
905
- if (!item) return $("<div></div>");
943
+ if (!item) return;
906
944
  var nodeClass = nodeClassFromPath(path);
907
945
  var hasChildren = nodeClass !== "Variable" && nodeClass !== "Alarm" && nodeClass !== "Namespace" && getChildrenByPath(path).length > 0;
908
- var expanded = isExpanded(path, depth < 1 || treeSearchTerm);
946
+ var expanded = isExpanded(path, depth < 1 || !!treeSearchTerm);
909
947
  var subtreeVisible = !!ancestorMatched || nodeMatchesSearch(path);
910
- var row = $('<div class="opcua-tree-row"></div>').attr("data-path", path);
911
- if (path === selectedPath) row.addClass("is-selected");
912
- for (var i = 0; i < depth; i += 1) row.append('<span class="opcua-tree-indent"></span>');
913
- row.append('<span class="opcua-tree-twisty">' + (hasChildren ? '<i class="fa ' + (expanded ? 'fa-caret-down' : 'fa-caret-right') + '"></i>' : '') + '</span>');
914
- row.append('<span class="opcua-tree-icon"><i class="fa ' + iconForNodeClass(nodeClass) + '"></i></span>');
915
- row.append('<span class="opcua-tree-label"></span>');
916
- row.find(".opcua-tree-label").text(item.name || "(unnamed)");
917
- row.append('<span class="opcua-tree-type">' + nodeClass + '</span>');
918
- var fragment = $('<div></div>').append(row);
948
+
949
+ var indents = "";
950
+ for (var i = 0; i < depth; i++) indents += '<span class="opcua-tree-indent"></span>';
951
+
952
+ var row = document.createElement("div");
953
+ row.className = "opcua-tree-row" + (path === selectedPath ? " is-selected" : "");
954
+ row.setAttribute("data-path", path);
955
+ row.innerHTML = indents
956
+ + '<span class="opcua-tree-twisty">' + (hasChildren ? '<i class="fa ' + (expanded ? "fa-caret-down" : "fa-caret-right") + '"></i>' : "") + "</span>"
957
+ + '<span class="opcua-tree-icon"><i class="fa ' + iconForNodeClass(nodeClass) + '"></i></span>'
958
+ + '<span class="opcua-tree-label">' + escapeHtml(item.name || "(unnamed)") + "</span>"
959
+ + '<span class="opcua-tree-type">' + escapeHtml(nodeClass) + "</span>";
960
+ frag.appendChild(row);
961
+
919
962
  if (hasChildren && expanded) {
920
963
  getChildrenByPath(path).forEach(function (childPath) {
921
964
  if (!treeSearchTerm || subtreeVisible || branchHasSearchMatch(childPath)) {
922
- fragment.append(createNodeElement(childPath, depth + 1, subtreeVisible));
965
+ appendNodeToFrag(frag, childPath, depth + 1, subtreeVisible);
923
966
  }
924
967
  });
925
968
  }
926
- return fragment;
927
969
  }
928
970
 
929
971
  function renderTree() {
930
- var list = $("#node-input-object-list");
931
- list.empty();
932
- var roots = getTopLevelPaths();
933
- if (!roots.length) {
934
- list.append('<div class="opcua-tree-empty">No OPC UA items. Use Add folder, Add object, or Add namespace.</div>');
935
- return;
936
- }
937
- roots.forEach(function (path) {
938
- if (!treeSearchTerm || branchHasSearchMatch(path)) list.append(createNodeElement(path, 0, false));
939
- });
972
+ if (_renderTreePending) return;
973
+ _renderTreePending = true;
974
+ setTimeout(function () {
975
+ _renderTreePending = false;
976
+ var list = document.getElementById("node-input-object-list");
977
+ if (!list) return;
978
+ var frag = document.createDocumentFragment();
979
+ var roots = getTopLevelPaths();
980
+ if (!roots.length) {
981
+ var empty = document.createElement("div");
982
+ empty.className = "opcua-tree-empty";
983
+ empty.textContent = "No OPC UA items. Use Add folder, Add object, or Add namespace.";
984
+ frag.appendChild(empty);
985
+ } else {
986
+ roots.forEach(function (path) {
987
+ if (!treeSearchTerm || branchHasSearchMatch(path)) appendNodeToFrag(frag, path, 0, false);
988
+ });
989
+ }
990
+ list.innerHTML = "";
991
+ list.appendChild(frag);
992
+ }, 0);
940
993
  }
941
994
 
942
995
  function renderBreadcrumbs() {
@@ -972,8 +1025,10 @@
972
1025
  parentPath: path,
973
1026
  kind: kind,
974
1027
  name: kind === "variable" ? "newVariable" : kind === "folder" ? "newFolder" : kind === "objecttype" ? "newObjectType" : kind === "alarm" ? "newAlarm" : kind === "method" ? "newMethod" : "newObject",
1028
+ displayName: "",
975
1029
  dataType: "Int32",
976
1030
  value: "",
1031
+ access: "readwrite",
977
1032
  objectsType: "",
978
1033
  alarmType: "levelAlarm",
979
1034
  variableNodeId: "",
@@ -1012,16 +1067,19 @@
1012
1067
  if (kind === "variable") {
1013
1068
  target.push(normalizeVariable({
1014
1069
  name: pendingCreate.name,
1070
+ displayName: pendingCreate.displayName || "",
1015
1071
  type: pendingCreate.dataType,
1016
- value: pendingCreate.value
1072
+ value: pendingCreate.value,
1073
+ access: pendingCreate.access || "readwrite"
1017
1074
  }));
1018
1075
  } else if (kind === "folder") {
1019
- target.push(normalizeBranch({ name: pendingCreate.name }));
1076
+ target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
1020
1077
  } else if (kind === "objecttype") {
1021
- target.push(normalizeBranch({ name: pendingCreate.name, objectsType: pendingCreate.objectsType || "" }));
1078
+ target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "", objectsType: pendingCreate.objectsType || "" }));
1022
1079
  target[target.length - 1].nodeId = buildGeneratedNodeIdForPath(branchTargetPath + "." + (target.length - 1));
1023
1080
  } else if (kind === "alarm") {
1024
1081
  target.push(normalizeAlarm({
1082
+ displayName: pendingCreate.displayName || "",
1025
1083
  name: pendingCreate.name,
1026
1084
  type: pendingCreate.alarmType,
1027
1085
  variableNodeId: pendingCreate.variableNodeId,
@@ -1038,9 +1096,9 @@
1038
1096
  digitalMessage: pendingCreate.digitalMessage
1039
1097
  }));
1040
1098
  } else if (kind === "method") {
1041
- target.push(normalizeMethod({ name: pendingCreate.name }));
1099
+ target.push(normalizeMethod({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
1042
1100
  } else {
1043
- target.push(normalizeBranch({ name: pendingCreate.name }));
1101
+ target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
1044
1102
  }
1045
1103
  expansionState[parentPath] = true;
1046
1104
  pendingCreate = null;
@@ -1060,12 +1118,14 @@
1060
1118
  panel.append('<div class="form-row"><label>Parent</label><input type="text" id="opcua-create-parent" readonly></div>');
1061
1119
  panel.append('<div class="form-row"><label>Type</label><input type="text" id="opcua-create-kind" readonly></div>');
1062
1120
  panel.append('<div class="form-row"><label>Name</label><input type="text" id="opcua-create-name"></div>');
1121
+ panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-create-displayname" placeholder="Leave blank to use browseName"></div>');
1063
1122
  if (pendingCreate.kind === "variable") {
1064
1123
  panel.append('<div class="form-row"><label>dataType</label><select id="opcua-create-type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option></select></div>');
1065
1124
  panel.append('<div class="form-row"><label>Value</label><input type="text" id="opcua-create-value"></div>');
1125
+ panel.append('<div class="form-row"><label>Access</label><select id="opcua-create-access"><option value="readwrite">readwrite</option><option value="readonly">readonly</option></select></div>');
1066
1126
  }
1067
1127
  if (pendingCreate.kind === "objecttype") {
1068
- panel.append('<div class="form-row"><label>objectsType</label><input type="text" id="opcua-create-objectstype"></div>');
1128
+ panel.append('<div class="form-row"><label>objectsType</label>' + buildObjectTypeSelect("opcua-create-objectstype", pendingCreate.objectsType || "") + '</div>');
1069
1129
  }
1070
1130
  if (pendingCreate.kind === "alarm") {
1071
1131
  panel.append('<div class="form-row"><label>alarmType</label><select id="opcua-create-alarm-type"><option value="levelAlarm">levelAlarm</option><option value="digitalAlarm">digitalAlarm</option></select></div>');
@@ -1089,9 +1149,11 @@
1089
1149
  $("#opcua-create-parent").val(pendingCreate.parentPath);
1090
1150
  $("#opcua-create-kind").val(pendingCreate.kind);
1091
1151
  $("#opcua-create-name").val(pendingCreate.name);
1152
+ $("#opcua-create-displayname").val(pendingCreate.displayName || "");
1092
1153
  $("#opcua-create-type").val(pendingCreate.dataType);
1093
1154
  $("#opcua-create-value").val(pendingCreate.value);
1094
1155
  $("#opcua-create-objectstype").val(pendingCreate.objectsType);
1156
+ $("#opcua-create-access").val(pendingCreate.access || "readwrite");
1095
1157
  $("#opcua-create-alarm-type").val(pendingCreate.alarmType);
1096
1158
  $("#opcua-create-variable-nodeid").val(pendingCreate.variableNodeId);
1097
1159
  $("#opcua-create-severity").val(pendingCreate.severity);
@@ -1130,15 +1192,16 @@
1130
1192
  panel.append('<div class="form-row"><label>namespace</label><select id="opcua-detail-namespace"></select></div>');
1131
1193
  panel.append('<div class="form-row"><label>nodeId</label><div class="opcua-nodeid-field"><span class="opcua-nodeid-prefix">' + getNodeIdPrefix(namespaceId) + '</span><input type="text" id="opcua-detail-nodeid"' + (nodeIdLocked ? ' readonly title="Generated automatically for object type models."' : '') + '><a href="#" id="opcua-detail-copy-nodeid" class="editor-button editor-button-small"><i class="fa fa-copy"></i> Copy</a></div></div>');
1132
1194
  panel.append('<div class="form-row"><label>Description</label><input type="text" id="opcua-detail-description"></div>');
1195
+ panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-detail-displayname" placeholder="Leave blank to use browseName"></div>');
1133
1196
  if (nodeClass === "ObjectType") {
1134
- panel.append('<div class="form-row"><label>objectsType</label><input type="text" id="opcua-detail-objectstype"></div>');
1197
+ panel.append('<div class="form-row"><label>objectsType</label>' + buildObjectTypeSelect("opcua-detail-objectstype", item.objectsType || "") + '</div>');
1135
1198
  }
1136
1199
  if (nodeClass === "Variable") {
1137
1200
  panel.append('<div class="form-row"><label>dataType</label><select id="opcua-detail-type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option></select></div>');
1138
1201
  panel.append('<div class="form-row"><label>Value</label><input type="text" id="opcua-detail-value"></div>');
1202
+ panel.append('<div class="form-row"><label>Access</label><select id="opcua-detail-access"><option value="readwrite">readwrite</option><option value="readonly">readonly</option></select></div>');
1139
1203
  }
1140
1204
  if (nodeClass === "Method") {
1141
- panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-detail-displayname"></div>');
1142
1205
  panel.append('<hr style="margin:8px 0; border-color:#e3e3e3;">');
1143
1206
  panel.append('<div style="font-size:11px;font-weight:700;text-transform:uppercase;color:#666;margin-bottom:4px;">Inputs</div>');
1144
1207
  var inputsDiv = $('<div id="opcua-detail-inputs"></div>').appendTo(panel);
@@ -1203,9 +1266,9 @@
1203
1266
  $("#opcua-detail-namespace").append($("<option></option>").val(option.id).text(getNamespaceLabel(option.id)));
1204
1267
  });
1205
1268
  $("#opcua-detail-namespace").val(String(namespaceId));
1206
- if (nodeClass === "Method") $("#opcua-detail-displayname").val(item.displayName || "");
1207
- if (nodeClass === "ObjectType") $("#opcua-detail-objectstype").val(item.objectsType || "");
1208
- if (nodeClass === "Variable") { $("#opcua-detail-type").val(item.type || "Int32"); $("#opcua-detail-value").val(item.value !== undefined ? item.value : ""); }
1269
+ $("#opcua-detail-displayname").val(item.displayName || "");
1270
+ if (nodeClass === "ObjectType") { $("#opcua-detail-objectstype").val(item.objectsType || ""); }
1271
+ if (nodeClass === "Variable") { $("#opcua-detail-type").val(item.type || "Int32"); $("#opcua-detail-value").val(item.value !== undefined ? item.value : ""); $("#opcua-detail-access").val(item.access || "readwrite"); }
1209
1272
  if (nodeClass === "Alarm") {
1210
1273
  $("#opcua-detail-alarm-type").val(item.type || "levelAlarm");
1211
1274
  $("#opcua-detail-variable-nodeid").val(item.variableNodeId || "");
@@ -1398,8 +1461,9 @@
1398
1461
  }
1399
1462
  });
1400
1463
  $(document).on("input", "#opcua-detail-description", function () { updateNode(selectedPath, { description: $(this).val() }); });
1401
- $(document).on("input", "#opcua-detail-objectstype", function () { updateNode(selectedPath, { objectsType: $(this).val() }); });
1464
+ $(document).on("change", "#opcua-detail-objectstype", function () { updateNode(selectedPath, { objectsType: $(this).val() }); });
1402
1465
  $(document).on("change", "#opcua-detail-type", function () { updateNode(selectedPath, { type: $(this).val() }); });
1466
+ $(document).on("change", "#opcua-detail-access", function () { updateNode(selectedPath, { access: $(this).val() }); });
1403
1467
  $(document).on("input", "#opcua-detail-value", function () { updateNode(selectedPath, { value: $(this).val() }); });
1404
1468
  $(document).on("change", "#opcua-detail-alarm-type", function () {
1405
1469
  updateNode(selectedPath, { type: $(this).val() });
@@ -1418,9 +1482,11 @@
1418
1482
  $(document).on("input", "#opcua-detail-normalstatevalue", function () { updateNode(selectedPath, { normalStateValue: Number($(this).val() || 0) }); });
1419
1483
  $(document).on("input", "#opcua-detail-digitalmessage", function () { updateNode(selectedPath, { digitalMessage: $(this).val() }); });
1420
1484
  $(document).on("input", "#opcua-create-name", function () { if (pendingCreate) pendingCreate.name = $(this).val(); });
1485
+ $(document).on("input", "#opcua-create-displayname", function () { if (pendingCreate) pendingCreate.displayName = $(this).val(); });
1421
1486
  $(document).on("change", "#opcua-create-type", function () { if (pendingCreate) pendingCreate.dataType = $(this).val(); });
1422
1487
  $(document).on("input", "#opcua-create-value", function () { if (pendingCreate) pendingCreate.value = $(this).val(); });
1423
- $(document).on("input", "#opcua-create-objectstype", function () { if (pendingCreate) pendingCreate.objectsType = $(this).val(); });
1488
+ $(document).on("change", "#opcua-create-objectstype", function () { if (pendingCreate) pendingCreate.objectsType = $(this).val(); });
1489
+ $(document).on("change", "#opcua-create-access", function () { if (pendingCreate) pendingCreate.access = $(this).val(); });
1424
1490
  $(document).on("change", "#opcua-create-alarm-type", function () {
1425
1491
  if (pendingCreate) {
1426
1492
  pendingCreate.alarmType = $(this).val();
@@ -1487,10 +1553,10 @@
1487
1553
  $("#node-input-close-tree-modal").off("click").on("click", function (event) { event.preventDefault(); closeTreeModal(); });
1488
1554
  $("#node-input-tree-modal").off("click").on("click", function (event) { if (event.target === this) closeTreeModal(); });
1489
1555
 
1490
- $("#node-input-tree-search").off("input").on("input", function () {
1556
+ $("#node-input-tree-search").off("input").on("input", debounce(function () {
1491
1557
  treeSearchValue = $(this).val(); treeSearchTerm = normalizeSearchTerm(treeSearchValue);
1492
1558
  $("#node-input-tree-search-clear").toggle(!!treeSearchTerm); renderTree();
1493
- });
1559
+ }, 200));
1494
1560
  $("#node-input-tree-search-clear").off("click").on("click", function (event) {
1495
1561
  event.preventDefault(); treeSearchValue = ""; treeSearchTerm = "";
1496
1562
  $("#node-input-tree-search").val(""); $(this).hide(); renderTree();
@@ -1545,4 +1611,4 @@
1545
1611
  <p>Supported variable types: <code>Int32</code>, <code>Float</code>, <code>Boolean</code>, <code>String</code>.</p>
1546
1612
  <p>Supported access modes: <code>readonly</code> and <code>readwrite</code>.</p>
1547
1613
  <p>Authentication can be configured in the editor with a local username list and bcrypt password hashes, and anonymous login can be disabled.</p>
1548
- </script>
1614
+ </script>