22
33Module . 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