Skip to content

Commit 7614aae

Browse files
authored
Merge pull request #100 from late4marshmellow/master
modernize helper and core logic, improve network handling
2 parents d1e098a + 3488be0 commit 7614aae

2 files changed

Lines changed: 429 additions & 112 deletions

File tree

MMM-JsonTable.js

Lines changed: 164 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
Module.register("MMM-JsonTable", {
44
jsonData: null,
5-
6-
// Default module config.
75
defaults: {
86
url: "",
97
arrayName: null,
@@ -14,141 +12,211 @@ Module.register("MMM-JsonTable", {
1412
tryFormatDate: false,
1513
updateInterval: 15000,
1614
animationSpeed: 500,
17-
descriptiveRow: null
15+
descriptiveRow: null,
16+
httpTimeout: 15000,
17+
requestHeaders: {},
18+
tlsInsecure: false,
19+
maxRedirects: 3
1820
},
19-
2021
start () {
2122
this.getJson();
2223
this.scheduleUpdate();
2324
},
24-
2525
scheduleUpdate () {
2626
const self = this;
2727
setInterval(() => {
2828
self.getJson();
2929
}, this.config.updateInterval);
3030
},
31-
32-
// Request node_helper to get json from url
3331
getJson () {
34-
this.sendSocketNotification("MMM-JsonTable_GET_JSON", {
32+
const options = {
33+
timeout: this.config.httpTimeout,
34+
headers: this.config.requestHeaders,
35+
tlsInsecure: this.config.tlsInsecure,
36+
maxRedirects: this.config.maxRedirects
37+
};
38+
const payload = {id: this.identifier,
3539
url: this.config.url,
36-
id: this.identifier
37-
});
40+
options};
41+
this.sendSocketNotification("MMM-JsonTable_GET_JSON", payload);
3842
},
39-
4043
socketNotificationReceived (notification, payload) {
4144
if (notification === "MMM-JsonTable_JSON_RESULT") {
42-
// Only continue if the notification came from the request we made
43-
// This way we can load the module more than once
44-
if (payload.id === this.identifier) {
45+
const isMine = payload && payload.id === this.identifier || payload && payload.url === this.config.url;
46+
if (isMine) {
4547
this.jsonData = payload.data;
4648
this.updateDom(this.config.animationSpeed);
4749
}
50+
return;
51+
}
52+
if (notification === "MMM-JsonTable_JSON_ERROR") {
53+
const isMineError = payload && payload.id === this.identifier || payload && payload.url === this.config.url;
54+
if (isMineError) {
55+
this.jsonData = null;
56+
this.updateDom(this.config.animationSpeed);
57+
}
4858
}
4959
},
50-
51-
// Override dom generator.
60+
normalizePathToParts (rawPathInput) {
61+
const raw = String(rawPathInput);
62+
const normalizedBracket = raw.replace(/\[(?<index>\d+)\]/gu, ".$<index>");
63+
const normalized = normalizedBracket.replace(/^\./u, "");
64+
return normalized.split(".");
65+
},
66+
resolvePath (data, pathStr) {
67+
try {
68+
if (data === null || typeof data === "undefined") {
69+
return null;
70+
}
71+
if (!pathStr) {
72+
return data;
73+
}
74+
const parts = this.normalizePathToParts(pathStr);
75+
let node = data;
76+
for (let idx = 0; idx < parts.length; idx += 1) {
77+
const raw = parts[idx];
78+
let key = raw;
79+
if ((/^\d+$/u).test(raw)) {
80+
key = Number(raw);
81+
}
82+
if (node === null || typeof node === "undefined" || !(key in node)) {
83+
return null;
84+
}
85+
node = node[key];
86+
}
87+
return node;
88+
} catch {
89+
return null;
90+
}
91+
},
92+
unwrapSingleNestedArray (arr) {
93+
if (Array.isArray(arr) && arr.length === 1 && Array.isArray(arr[0])) {
94+
return arr[0];
95+
}
96+
return arr;
97+
},
98+
resolveArrayWithFallbacks (root, paths) {
99+
let list = [];
100+
if (Array.isArray(paths)) {
101+
list = paths;
102+
} else if (paths) {
103+
list = [paths];
104+
}
105+
for (let idx = 0; idx < list.length; idx += 1) {
106+
const pathStr = list[idx];
107+
const node = this.unwrapSingleNestedArray(this.resolvePath(root, pathStr));
108+
if (Array.isArray(node)) {
109+
return node;
110+
}
111+
}
112+
return null;
113+
},
114+
resolveItemsFromConfig () {
115+
let candidates = null;
116+
if (this.config.arrayName && this.config.arrayName.length) {
117+
candidates = this.config.arrayName;
118+
} else {
119+
candidates = ["data.Forbruk", "Forbruk.Forbruk", "Forbruk"];
120+
}
121+
let items = this.resolveArrayWithFallbacks(this.jsonData, candidates);
122+
if (!Array.isArray(items)) {
123+
const maybeRoot = this.unwrapSingleNestedArray(this.jsonData);
124+
if (Array.isArray(maybeRoot)) {
125+
items = maybeRoot;
126+
}
127+
}
128+
return items;
129+
},
130+
buildTable (items) {
131+
const table = document.createElement("table");
132+
const tbody = document.createElement("tbody");
133+
for (let idx = 0; idx < items.length; idx += 1) {
134+
const row = this.getTableRow(items[idx]);
135+
tbody.appendChild(row);
136+
}
137+
if (this.config.descriptiveRow) {
138+
const headerEl = table.createTHead();
139+
headerEl.innerHTML = this.config.descriptiveRow;
140+
}
141+
table.appendChild(tbody);
142+
return table;
143+
},
52144
getDom () {
53145
const wrapper = document.createElement("div");
54146
wrapper.className = "xsmall";
55-
56-
if (!this.jsonData) {
57-
wrapper.innerHTML = "Awaiting json data...";
147+
try {
148+
if (!this.jsonData) {
149+
wrapper.innerHTML = "Awaiting json data...";
150+
return wrapper;
151+
}
152+
const items = this.resolveItemsFromConfig();
153+
if (!Array.isArray(items)) {
154+
wrapper.innerHTML = this.config.noDataText;
155+
return wrapper;
156+
}
157+
const table = this.buildTable(items);
158+
wrapper.appendChild(table);
159+
return wrapper;
160+
} catch {
161+
wrapper.innerHTML = "Error rendering table.";
58162
return wrapper;
59163
}
60-
61-
const table = document.createElement("table");
62-
const tbody = document.createElement("tbody");
63-
64-
let items = [];
65-
if (this.config.arrayName) {
66-
items = this.jsonData[this.config.arrayName];
164+
},
165+
buildCell (key, value) {
166+
const cell = document.createElement("td");
167+
let valueToDisplay = "";
168+
if (key === "icon") {
169+
cell.classList.add("fa", value);
170+
} else if (this.config.tryFormatDate) {
171+
valueToDisplay = this.getFormattedValue(value);
67172
} else {
68-
items = this.jsonData;
173+
valueToDisplay = value;
69174
}
70-
71-
// Check if items is of type array
72-
if (!(items instanceof Array)) {
73-
wrapper.innerHTML = this.config.noDataText;
74-
return wrapper;
175+
let textContent = "";
176+
if (valueToDisplay === null || typeof valueToDisplay === "undefined") {
177+
textContent = "";
178+
} else {
179+
textContent = String(valueToDisplay);
75180
}
76-
77-
items.forEach((element) => {
78-
const row = this.getTableRow(element);
79-
tbody.appendChild(row);
80-
});
81-
82-
// Add in Descriptive Row Header
83-
if (this.config.descriptiveRow) {
84-
const header = table.createTHead();
85-
header.innerHTML = this.config.descriptiveRow;
181+
const textNode = document.createTextNode(textContent);
182+
if (this.config.size > 0 && this.config.size < 9) {
183+
const headingEl = document.createElement(`H${String(this.config.size)}`);
184+
headingEl.appendChild(textNode);
185+
cell.appendChild(headingEl);
186+
} else {
187+
cell.appendChild(textNode);
86188
}
87-
88-
table.appendChild(tbody);
89-
wrapper.appendChild(table);
90-
return wrapper;
189+
return cell;
91190
},
92-
93191
getTableRow (jsonObject) {
94192
const row = document.createElement("tr");
95-
Object.entries(jsonObject).forEach(([key, value]) => {
96-
const cell = document.createElement("td");
97-
98-
let valueToDisplay = "";
99-
let cellValue = "";
100-
101-
if (value.constructor === Object) {
102-
if ("value" in value) {
103-
cellValue = value.value;
104-
} else {
105-
cellValue = "";
193+
let entries = [];
194+
const hasKeep = Array.isArray(this.config.keepColumns) && this.config.keepColumns.length > 0;
195+
if (hasKeep) {
196+
const filtered = [];
197+
for (let idx = 0; idx < this.config.keepColumns.length; idx += 1) {
198+
const key = this.config.keepColumns[idx];
199+
if (Object.hasOwn(jsonObject || {}, key)) {
200+
filtered.push([key, jsonObject[key]]);
106201
}
107-
108-
if ("color" in value) {
109-
cell.style.color = value.color;
110-
}
111-
} else {
112-
cellValue = value;
113-
}
114-
115-
if (key === "icon") {
116-
cell.classList.add("fa", cellValue);
117-
} else if (this.config.tryFormatDate) {
118-
valueToDisplay = this.getFormattedValue(cellValue);
119-
} else if (
120-
this.config.keepColumns.length === 0 ||
121-
this.config.keepColumns.indexOf(key) >= 0
122-
) {
123-
valueToDisplay = cellValue;
124202
}
125-
126-
const cellText = document.createTextNode(valueToDisplay);
127-
128-
if (this.config.size > 0 && this.config.size < 9) {
129-
const heading = document.createElement(`H${this.config.size}`);
130-
heading.appendChild(cellText);
131-
cell.appendChild(heading);
132-
} else {
133-
cell.appendChild(cellText);
134-
}
135-
203+
entries = filtered;
204+
} else {
205+
entries = Object.entries(jsonObject || {});
206+
}
207+
for (let idx = 0; idx < entries.length; idx += 1) {
208+
const [key, value] = entries[idx];
209+
const cell = this.buildCell(key, value);
136210
row.appendChild(cell);
137-
});
211+
}
138212
return row;
139213
},
140-
141-
// Format a date string or return the input
142214
getFormattedValue (input) {
143215
const momentObj = moment(input);
144216
if (typeof input === "string" && momentObj.isValid()) {
145-
// Show a formatted time if it occures today
146-
if (
147-
momentObj.isSame(new Date(Date.now()), "day") &&
148-
momentObj.hours() !== 0 &&
149-
momentObj.minutes() !== 0 &&
150-
momentObj.seconds() !== 0
151-
) {
217+
const isToday = momentObj.isSame(new Date(), "day");
218+
const notMidnight = momentObj.hours() !== 0 || momentObj.minutes() !== 0 || momentObj.seconds() !== 0;
219+
if (isToday && notMidnight) {
152220
return momentObj.format("HH:mm:ss");
153221
}
154222
return momentObj.format("YYYY-MM-DD");

0 commit comments

Comments
 (0)