├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── runtest.yml ├── .gitignore ├── .markdownlint.json ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── MMM-JsonTable.js ├── README.md ├── eslint.config.mjs ├── example1.png ├── example2.png ├── example4.png ├── example5.png ├── node_helper.js ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.github/workflows/runtest.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [18.x, 20.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: "npm" 27 | - run: npm install 28 | - run: npm run test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "line_length": false, 3 | "no-inline-html": false 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.mjs -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "overrides": [ 4 | { 5 | "files": "*.md", 6 | "options": { 7 | "parser": "markdown" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 timdows 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MMM-JsonTable.js: -------------------------------------------------------------------------------- 1 | /* global Module moment */ 2 | 3 | Module.register("MMM-JsonTable", { 4 | jsonData: null, 5 | 6 | // Default module config. 7 | defaults: { 8 | url: "", 9 | arrayName: null, 10 | noDataText: 11 | "Json data is not of type array! Maybe the config arrayName is not used and should be, or is configured wrong.", 12 | keepColumns: [], 13 | size: 0, 14 | tryFormatDate: false, 15 | updateInterval: 15000, 16 | animationSpeed: 500, 17 | descriptiveRow: null 18 | }, 19 | 20 | start () { 21 | this.getJson(); 22 | this.scheduleUpdate(); 23 | }, 24 | 25 | scheduleUpdate () { 26 | const self = this; 27 | setInterval(() => { 28 | self.getJson(); 29 | }, this.config.updateInterval); 30 | }, 31 | 32 | // Request node_helper to get json from url 33 | getJson () { 34 | this.sendSocketNotification("MMM-JsonTable_GET_JSON", {url:this.config.url,id:this.identifier}); 35 | }, 36 | 37 | socketNotificationReceived (notification, payload) { 38 | if (notification === "MMM-JsonTable_JSON_RESULT") { 39 | // Only continue if the notification came from the request we made 40 | // This way we can load the module more than once 41 | if (payload.id === this.identifier) { 42 | this.jsonData = payload.data; 43 | this.updateDom(this.config.animationSpeed); 44 | } 45 | } 46 | }, 47 | 48 | // Override dom generator. 49 | getDom () { 50 | const wrapper = document.createElement("div"); 51 | wrapper.className = "xsmall"; 52 | 53 | if (!this.jsonData) { 54 | wrapper.innerHTML = "Awaiting json data..."; 55 | return wrapper; 56 | } 57 | 58 | const table = document.createElement("table"); 59 | const tbody = document.createElement("tbody"); 60 | 61 | let items = []; 62 | if (this.config.arrayName) { 63 | items = this.jsonData[this.config.arrayName]; 64 | } else { 65 | items = this.jsonData; 66 | } 67 | 68 | // Check if items is of type array 69 | if (!(items instanceof Array)) { 70 | wrapper.innerHTML = this.config.noDataText; 71 | return wrapper; 72 | } 73 | 74 | items.forEach((element) => { 75 | const row = this.getTableRow(element); 76 | tbody.appendChild(row); 77 | }); 78 | 79 | // Add in Descriptive Row Header 80 | if (this.config.descriptiveRow) { 81 | const header = table.createTHead(); 82 | header.innerHTML = this.config.descriptiveRow; 83 | } 84 | 85 | table.appendChild(tbody); 86 | wrapper.appendChild(table); 87 | return wrapper; 88 | }, 89 | 90 | getTableRow (jsonObject) { 91 | const row = document.createElement("tr"); 92 | Object.entries(jsonObject).forEach(([key, value]) => { 93 | const cell = document.createElement("td"); 94 | 95 | let valueToDisplay = ""; 96 | let cellValue = ""; 97 | 98 | if (value.constructor === Object) { 99 | if ("value" in value) { 100 | cellValue = value.value; 101 | } else { 102 | cellValue = ""; 103 | } 104 | 105 | if ("color" in value) { 106 | cell.style.color = value.color; 107 | } 108 | } else { 109 | cellValue = value; 110 | } 111 | 112 | if (key === "icon") { 113 | cell.classList.add("fa", cellValue); 114 | } else if (this.config.tryFormatDate) { 115 | valueToDisplay = this.getFormattedValue(cellValue); 116 | } else if ( 117 | this.config.keepColumns.length === 0 || 118 | this.config.keepColumns.indexOf(key) >= 0 119 | ) { 120 | valueToDisplay = cellValue; 121 | } 122 | 123 | const cellText = document.createTextNode(valueToDisplay); 124 | 125 | if (this.config.size > 0 && this.config.size < 9) { 126 | const heading = document.createElement(`H${this.config.size}`); 127 | heading.appendChild(cellText); 128 | cell.appendChild(heading); 129 | } else { 130 | cell.appendChild(cellText); 131 | } 132 | 133 | row.appendChild(cell); 134 | }); 135 | return row; 136 | }, 137 | 138 | // Format a date string or return the input 139 | getFormattedValue (input) { 140 | const momentObj = moment(input); 141 | if (typeof input === "string" && momentObj.isValid()) { 142 | // Show a formatted time if it occures today 143 | if ( 144 | momentObj.isSame(new Date(Date.now()), "day") && 145 | momentObj.hours() !== 0 && 146 | momentObj.minutes() !== 0 && 147 | momentObj.seconds() !== 0 148 | ) { 149 | return momentObj.format("HH:mm:ss"); 150 | } 151 | return momentObj.format("YYYY-MM-DD"); 152 | } 153 | return input; 154 | } 155 | }); 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMM-JsonTable 2 | 3 | A module for the [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) project which creates a table filled with a list gathered from a json request. 4 | 5 | All the variables of the objects in the array are represented by a table column. 6 | For every column it checks if a valid DateTime is given, and then formats it to `HH:mm:ss` if it is today or `YYYY-MM-DD` otherwise. 7 | 8 | ## Installation 9 | 10 | Just clone the module into your modules folder of your MagicMirror². 11 | 12 | ```shell 13 | git clone https://github.com/timdows/MMM-JsonTable 14 | ``` 15 | 16 | That's it! 17 | 18 | If you are a developer please also install the depenendies for linter and prettier: 19 | 20 | ```shell 21 | cd MMM-JsonTable 22 | npm install 23 | ``` 24 | 25 | ## Build and Test status 26 | 27 | [![Node.js CI](https://github.com/timdows/MMM-JsonTable/actions/workflows/runtest.yml/badge.svg)](https://github.com/timdows/MMM-JsonTable/actions/workflows/runtest.yml) 28 | 29 | ## Config options 30 | 31 | Except `url` all options are optional. 32 | 33 | 34 | | **Option** | **Description** | 35 | | -------------- | -------------- | 36 | | url | The full url to get the json response from

**Default value:** `""` | 37 | | arrayName | Define the name of the variable that holds the array to display

**Default value:** `null` | 38 | | noDataText | Text indicating that there is no data.

**Default value:** `Json data is not of type array! Maybe the config arrayName is not used and should be, or is configured wrong.` | 39 | | keepColumns | Columns on json will be showed

**Default value:** `[]` | 40 | | tryFormatDate | For every column it checks if a valid DateTime is given, and then formats it to `HH:mm:ss` if it is today or `YYYY-MM-DD` otherwise

**Default value:** `false`
**Possible values:** `false` and `true` | 41 | | size | Text size at table, 0 is default and 3 is H3

**Default value:** `0`
**Possible values:** `0` - `3` | 42 | | updateInterval | Milliseconds between the refersh

**Default value:** `15000` | 43 | | animationSpeed | Speed of the update animation. (Milliseconds)
If you don't want that the module blinks during an update, set the value to `0`.

**Default value:** `500`
**Possible values:** `0` - `5000` | 44 | | descriptiveRow | Complete html table row that will be added above the array data

**Default value:** `""` | 45 | 46 | 47 | ## Example 1 48 | 49 | End result: 50 | 51 | ![Example 1](example1.png) 52 | 53 | Raw json response: 54 | 55 | ```json 56 | { 57 | "items": [ 58 | { 59 | "name": "Watt", 60 | "value": "270 Watt" 61 | }, 62 | { 63 | "name": "Today", 64 | "value": "5.85 kWh" 65 | }, 66 | { 67 | "name": "ThisWeek", 68 | "value": "5.83 kWh" 69 | }, 70 | { 71 | "name": "ThisMonth", 72 | "value": "12.8 kWh" 73 | }, 74 | { 75 | "name": "LastMonth", 76 | "value": "246.75 kWh" 77 | } 78 | ] 79 | } 80 | ``` 81 | 82 | Configuration: 83 | 84 | ```javascript 85 | { 86 | module: 'MMM-JsonTable', 87 | position: 'top_right', 88 | header: 'HouseDB Sevensegment', 89 | config: { 90 | url: 'https://xyz/abc/get.json', // Required 91 | arrayName: 'items' // Optional 92 | } 93 | } 94 | ``` 95 | 96 | ## Example 2 97 | 98 | ![Example 2](example2.png) 99 | 100 | Raw json response: 101 | 102 | ```json 103 | { 104 | "currentUsages": [ 105 | { 106 | "deviceName": "P1", 107 | "currentWattValue": 180, 108 | "todayKwhUsage": 5.902, 109 | "lastUpdate": "2018-04-02T18:12:06Z" 110 | }, 111 | { 112 | "deviceName": "Studie - MainDown", 113 | "currentWattValue": 76, 114 | "todayKwhUsage": 0.46, 115 | "lastUpdate": "2018-04-02T18:06:52Z" 116 | }, 117 | { 118 | "deviceName": "BoilerPower", 119 | "currentWattValue": 0, 120 | "todayKwhUsage": 2.21, 121 | "lastUpdate": "2018-04-02T17:30:01Z" 122 | }, 123 | { 124 | "deviceName": "Koelkast", 125 | "currentWattValue": 1.3, 126 | "todayKwhUsage": 0.55, 127 | "lastUpdate": "2018-04-02T18:09:55Z" 128 | }, 129 | { 130 | "deviceName": "Vaatwasser", 131 | "currentWattValue": 0.5, 132 | "todayKwhUsage": 0.01, 133 | "lastUpdate": "2018-04-02T18:10:51Z" 134 | }, 135 | { 136 | "deviceName": "Wasmachine", 137 | "currentWattValue": 0, 138 | "todayKwhUsage": 0, 139 | "lastUpdate": "2018-04-02T18:12:06Z" 140 | } 141 | ] 142 | } 143 | ``` 144 | 145 | Configuration: 146 | 147 | ```javascript 148 | { 149 | module: 'MMM-JsonTable', 150 | position: 'top_right', 151 | header: 'HouseDB Current Usages', 152 | config: { 153 | url: 'https://xyz/abc/get.json', // Required 154 | arrayName: 'currentUsages', // Optional 155 | tryFormatDate: true 156 | } 157 | } 158 | ``` 159 | 160 | ## Example 3 (with font awesome icons) 161 | 162 | ![Example 3](https://user-images.githubusercontent.com/1011699/53985507-104ecc00-411c-11e9-9ca4-c994f0ae62e1.png) 163 | 164 | Raw json response: 165 | 166 | ```json 167 | { 168 | "cups":[ 169 | { 170 | "icon":"fa-calendar", 171 | "data":"Senaste bryggning", 172 | "value":"2019-03-07", 173 | "type":"" 174 | }, 175 | { 176 | "icon":"fa-clock-o", 177 | "data":"Klockan", 178 | "value":"17:32:06", 179 | "type":"" 180 | }, 181 | { 182 | "icon":"fa-coffee", 183 | "data":"Totalt antal bryggda koppar", 184 | "value":60, 185 | "type":"st" 186 | }, 187 | ... 188 | ] 189 | } 190 | ``` 191 | 192 | ## Example 4 (with descriptive row) 193 | 194 | ![Example 4](example4.png) 195 | 196 | Raw json response: 197 | 198 | ```json 199 | { 200 | "deviceKwhUsages":[ 201 | { 202 | "name": "Studie - MainDown", 203 | "today": 0, 204 | "todayFormatted": "0", 205 | "thisWeek": 1.27, 206 | "thisWeekFormatted": "1,27", 207 | "lastWeek": 7, 208 | "lastWeekFormatted": "7,00", 209 | "thisMonth": 17.41, 210 | "thisMonthFormatted": "17,41", 211 | "lastMonth": 30.58, 212 | "tLastMonthFormatted": "30,58" 213 | }, 214 | { 215 | "name": "BoilerPower", 216 | "today": 0, 217 | "todayFormatted": "0", 218 | "thisWeek": 1.9, 219 | "thisWeekFormatted": "1,90", 220 | "lastWeek": 13.3, 221 | "lastWeekFormatted": "13,30", 222 | "thisMonth": 30.44, 223 | "thisMonthFormatted": "30,44", 224 | "lastMonth": 54.99, 225 | "tLastMonthFormatted": "54,99" 226 | }, 227 | ... 228 | ] 229 | } 230 | ``` 231 | 232 | Configuration: 233 | 234 | ```javascript 235 | { 236 | module: 'MMM-JsonTable', 237 | position: 'top_right', 238 | header: 'HouseDB Kwh Statistics', 239 | config: { 240 | url: 'https://xyz/abc/get.json', 241 | arrayName: 'deviceKwhUsages', 242 | descriptiveRow: 'NameTodayThisWeekLastWeekThisMonthLastMonth' 243 | } 244 | } 245 | ``` 246 | 247 | ## Example 5 (with font awesome icons, colors and descriptive row) 248 | 249 | ![Example 5](example5.png) 250 | 251 | Raw json response: 252 | 253 | ```json 254 | { 255 | "trash": [ 256 | { 257 | "icon": "fa-trash", 258 | "za-type": "Paper", 259 | "zb-date": "15 July" 260 | }, 261 | { 262 | "icon": { 263 | "color": "#FF6E00", 264 | "value": "fa-trash" 265 | }, 266 | "za-type": { 267 | "color": "#FF6E00", 268 | "value": "Plastic" 269 | }, 270 | "zb-date": { 271 | "color": "#FF6E00", 272 | "value": "25 July" 273 | } 274 | }, 275 | { 276 | "icon": { 277 | "color": "red", 278 | "value": "fa-trash" 279 | }, 280 | "za-type": { 281 | "value": "GFT" 282 | }, 283 | "zb-date": { 284 | "color": "yellow" 285 | } 286 | } 287 | ] 288 | } 289 | ``` 290 | 291 | Configuration: 292 | 293 | ```javascript 294 | { 295 | module: 'MMM-JsonTable', 296 | position: 'top_left', 297 | header: 'Trash calendar', 298 | config: { 299 | url: 'https://xyz/abc/get.json', 300 | arrayName: 'trash', 301 | descriptiveRow: 'TypeDay', 302 | updateInterval: 60000 303 | } 304 | } 305 | ``` 306 | 307 | ## Developer hints 308 | 309 | Please use `npm run test` before doing a PR. 310 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslintPluginImport from "eslint-plugin-import"; 2 | import eslintPluginJs from "@eslint/js"; 3 | import eslintPluginJsonc from "eslint-plugin-jsonc"; 4 | import eslintPluginStylistic from "@stylistic/eslint-plugin"; 5 | import globals from "globals"; 6 | 7 | export default [ 8 | ...eslintPluginJsonc.configs["flat/recommended-with-json"], 9 | { 10 | files: ["**/*.js", "**/*.mjs"], 11 | languageOptions: { 12 | globals: { 13 | ...globals.browser, 14 | ...globals.nodeBuiltin, 15 | ...globals.node 16 | } 17 | }, 18 | plugins: { 19 | ...eslintPluginStylistic.configs["all-flat"].plugins, 20 | import: eslintPluginImport 21 | }, 22 | rules: { 23 | ...eslintPluginJs.configs.all.rules, 24 | ...eslintPluginImport.configs.recommended.rules, 25 | ...eslintPluginStylistic.configs["all-flat"].rules, 26 | "capitalized-comments": "off", 27 | "consistent-this": "off", 28 | "max-statements": ["error", 25], 29 | "multiline-comment-style": "off", 30 | "no-magic-numbers": "off", 31 | "one-var": "off", 32 | "sort-keys": "off", 33 | "@stylistic/array-element-newline": ["error", "consistent"], 34 | "@stylistic/dot-location": ["error", "property"], 35 | "@stylistic/function-call-argument-newline": ["error", "consistent"], 36 | "@stylistic/indent": ["error", 2], 37 | "@stylistic/quote-props": ["error", "as-needed"], 38 | "@stylistic/padded-blocks": ["error", "never"] 39 | } 40 | } 41 | ]; 42 | 43 | -------------------------------------------------------------------------------- /example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdows/MMM-JsonTable/2856747d359ac2c11ade8a81ecdf678f5b0c0ef7/example1.png -------------------------------------------------------------------------------- /example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdows/MMM-JsonTable/2856747d359ac2c11ade8a81ecdf678f5b0c0ef7/example2.png -------------------------------------------------------------------------------- /example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdows/MMM-JsonTable/2856747d359ac2c11ade8a81ecdf678f5b0c0ef7/example4.png -------------------------------------------------------------------------------- /example5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdows/MMM-JsonTable/2856747d359ac2c11ade8a81ecdf678f5b0c0ef7/example5.png -------------------------------------------------------------------------------- /node_helper.js: -------------------------------------------------------------------------------- 1 | const NodeHelper = require("node_helper"); 2 | const Log = require("logger"); 3 | 4 | module.exports = NodeHelper.create({ 5 | start () { 6 | Log.log("MMM-JsonTable helper started..."); 7 | }, 8 | 9 | getJson (payload) { 10 | const self = this; 11 | 12 | fetch(payload.url) 13 | .then((response) => response.json()) 14 | .then((json) => { 15 | // Send the json data back with the url to distinguish it on the receiving part 16 | self.sendSocketNotification("MMM-JsonTable_JSON_RESULT", { 17 | id:payload.id, 18 | data: json 19 | }); 20 | }); 21 | }, 22 | 23 | // Subclass socketNotificationReceived received. 24 | socketNotificationReceived (notification, payload ) { 25 | if (notification === "MMM-JsonTable_GET_JSON") { 26 | this.getJson(payload); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmm-jsontable", 3 | "version": "1.3.0", 4 | "description": "A module for the MagicMirror² project which creates a table filled with a list gathered from a json request.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/timdows/MMM-JsonTable" 8 | }, 9 | "keywords": [ 10 | "magic mirror", 11 | "JSON", 12 | "table" 13 | ], 14 | "author": { 15 | "name": "timdows", 16 | "url": "https://github.com/timdows" 17 | }, 18 | "license": "MIT", 19 | "devDependencies": { 20 | "@eslint/js": "^8.57.0", 21 | "@pilaton/eslint-config-markdown": "^1.2.0", 22 | "@stylistic/eslint-plugin": "^1.7.0", 23 | "eslint": "^8.57.0", 24 | "eslint-plugin-import": "^2.29.1", 25 | "eslint-plugin-jsonc": "^2.15.0", 26 | "globals": "^15.0.0", 27 | "markdownlint-cli": "^0.39.0", 28 | "prettier": "^3.2.5" 29 | }, 30 | "engines": { 31 | "node": ">=18" 32 | }, 33 | "scripts": { 34 | "lint": "eslint . && markdownlint . --ignore node_modules && prettier --check .", 35 | "lint:fix": "eslint --fix . && markdownlint . --ignore node_modules --fix && prettier --write .", 36 | "test": "npm run lint" 37 | } 38 | } 39 | --------------------------------------------------------------------------------