philogb/Treemap only renders undefined - javascript
I am trying to use https://github.com/philogb/jit to create a treemap of data coming from top. The data looks like:
"{"rsyslogd":{"children":[{"children":[],"data":{"PID":"17670","$color":"#D40106","cmdinfo":"<br><b>USER</b>:syslog<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:383m<br><b>RES</b>:5412<br><b>SHR</b>:1108<br><b>S</b>:S<br><b>CPU</b>:177%<br>%<b>MEM</b>:0.3%<br><b>TIME+</b>:32230:25<br>","$area":4},"id":"proc-1-17670","name":"rsyslogd"}],"data":{"PID":"rsyslogd","$area":50},"id":"proc-rsyslogd","name":"rsyslogd(1)"},"init":{"children":[{"children":[],"data":{"PID":"1","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:23584<br><b>RES</b>:1324<br><b>SHR</b>:1044<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.1%<br><b>TIME+</b>:0:00.10<br>","$area":2},"id":"proc-1-1","name":"init"}],"data":{"PID":"init","$area":30},"id":"proc-init","name":"init(1)"},"dbus-daemon":{"children":[{"children":[],"data":{"PID":"88","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:messageb<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:23556<br><b>RES</b>:328<br><b>SHR</b>:324<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.0%<br><b>TIME+</b>:0:00.00<br>","$area":1},"id":"proc-1-88","name":"dbus-daemon"}],"data":{"PID":"dbus-daemon","$area":20},"id":"proc-dbus-daemon","name":"dbus-daemon(1)"},"sshd":{"children":[{"children":[],"data":{"PID":"183","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:49436<br><b>RES</b>:1640<br><b>SHR</b>:1528<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.1%<br><b>TIME+</b>:0:00.14<br>","$area":2},"id":"proc-1-183","name":"sshd"}],"data":{"PID":"sshd","$area":30},"id":"proc-sshd","name":"sshd(1)"},"cron":{"children":[{"children":[],"data":{"PID":"209","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:18900<br><b>RES</b>:704<br><b>SHR</b>:628<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.0%<br><b>TIME+</b>:0:01.34<br>","$area":1},"id":"proc-1-209","name":"cron"}],"data":{"PID":"cron","$area":20},"id":"proc-cron","name":"cron(1)"},"mysqld":{"children":[{"children":[],"data":{"PID":"240","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:mysql<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:334m<br><b>RES</b>:49m<br><b>SHR</b>:3988<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.2%<br><b>TIME+</b>:58:21.44<br>","$area":33},"id":"proc-1-240","name":"mysqld"}],"data":{"PID":"mysqld","$area":340},"id":"proc-mysqld","name":"mysqld(1)"},"sendmail-mta":{"children":[{"children":[],"data":{"PID":"317","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:68284<br><b>RES</b>:844<br><b>SHR</b>:648<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.1%<br><b>TIME+</b>:0:31.61<br>","$area":2},"id":"proc-1-317","name":"sendmail-mta"}],"data":{"PID":"sendmail-mta","$area":30},"id":"proc-sendmail-mta","name":"sendmail-mta(1)"},"apache2":{"children":[{"children":[],"data":{"PID":"372","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:342m<br><b>RES</b>:8404<br><b>SHR</b>:5412<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.5%<br><b>TIME+</b>:0:50.46<br>","$area":6},"id":"proc-1-372","name":"apache2"},{"children":[],"data":{"PID":"24804","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:369m<br><b>RES</b>:81m<br><b>SHR</b>:56m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:5.3%<br><b>TIME+</b>:0:07.93<br>","$area":54},"id":"proc-1-24804","name":"apache2"},{"children":[],"data":{"PID":"24821","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:366m<br><b>RES</b>:66m<br><b>SHR</b>:41m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.3%<br><b>TIME+</b>:0:06.27<br>","$area":44},"id":"proc-1-24821","name":"apache2"},{"children":[],"data":{"PID":"24828","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:359m<br><b>RES</b>:71m<br><b>SHR</b>:53m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.7%<br><b>TIME+</b>:0:06.39<br>","$area":48},"id":"proc-1-24828","name":"apache2"},{"children":[],"data":{"PID":"24832","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:361m<br><b>RES</b>:70m<br><b>SHR</b>:52m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.6%<br><b>TIME+</b>:0:04.18<br>","$area":47},"id":"proc-1-24832","name":"apache2"},{"children":[],"data":{"PID":"24860","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:368m<br><b>RES</b>:77m<br><b>SHR</b>:53m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:5.1%<br><b>TIME+</b>:0:06.15<br>","$area":52},"id":"proc-1-24860","name":"apache2"},{"children":[],"data":{"PID":"24862","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:361m<br><b>RES</b>:58m<br><b>SHR</b>:41m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.8%<br><b>TIME+</b>:0:03.86<br>","$area":39},"id":"proc-1-24862","name":"apache2"},{"children":[],"data":{"PID":"24878","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:359m<br><b>RES</b>:51m<br><b>SHR</b>:34m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.4%<br><b>TIME+</b>:0:01.54<br>","$area":35},"id":"proc-1-24878","name":"apache2"},{"children":[],"data":{"PID":"24882","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:362m<br><b>RES</b>:53m<br><b>SHR</b>:34m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.5%<br><b>TIME+</b>:0:01.60<br>","$area":36},"id":"proc-1-24882","name":"apache2"},{"children":[],"data":{"PID":"24885","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:366m<br><b>RES</b>:61m<br><b>SHR</b>:38m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.0%<br><b>TIME+</b>:0:02.65<br>","$area":41},"id":"proc-1-24885","name":"apache2"},{"children":[],"data":{"PID":"24887","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:366m<br><b>RES</b>:73m<br><b>SHR</b>:50m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.8%<br><b>TIME+</b>:0:02.04<br>","$area":49},"id":"proc-1-24887","name":"apache2"},{"children":[],"data":{"PID":"24888","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:363m<br><b>RES</b>:60m<br><b>SHR</b>:36m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.9%<br><b>TIME+</b>:0:02.34<br>","$area":40},"id":"proc-1-24888","name":"apache2"},{"children":[],"data":{"PID":"24889","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:363m<br><b>RES</b>:61m<br><b>SHR</b>:39m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.0%<br><b>TIME+</b>:0:02.81<br>","$area":41},"id":"proc-1-24889","name":"apache2"},{"children":[],"data":{"PID":"24892","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:356m<br><b>RES</b>:47m<br><b>SHR</b>:33m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.1%<br><b>TIME+</b>:0:02.60<br>","$area":32},"id":"proc-1-24892","name":"apache2"},{"children":[],"data":{"PID":"24893","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:355m<br><b>RES</b>:49m<br><b>SHR</b>:33m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.2%<br><b>TIME+</b>:0:01.70<br>","$area":33},"id":"proc-1-24893","name":"apache2"},{"children":[],"data":{"PID":"24897","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:363m<br><b>RES</b>:62m<br><b>SHR</b>:38m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.1%<br><b>TIME+</b>:0:02.44<br>","$area":42},"id":"proc-1-24897","name":"apache2"},{"children":[],"data":{"PID":"24900","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:365m<br><b>RES</b>:63m<br><b>SHR</b>:39m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.1%<br><b>TIME+</b>:0:01.82<br>","$area":42},"id":"proc-1-24900","name":"apache2"},{"children":[],"data":{"PID":"24903","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:364m<br><b>RES</b>:56m<br><b>SHR</b>:36m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.7%<br><b>TIME+</b>:0:03.43<br>","$area":38},"id":"proc-1-24903","name":"apache2"},{"children":[],"data":{"PID":"24924","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:365m<br><b>RES</b>:61m<br><b>SHR</b>:37m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.0%<br><b>TIME+</b>:0:02.13<br>","$area":41},"id":"proc-1-24924","name":"apache2"},{"children":[],"data":{"PID":"24926","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:362m<br><b>RES</b>:51m<br><b>SHR</b>:33m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.3%<br><b>TIME+</b>:0:00.81<br>","$area":34},"id":"proc-1-24926","name":"apache2"},{"children":[],"data":{"PID":"24930","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:367m<br><b>RES</b>:61m<br><b>SHR</b>:36m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.0%<br><b>TIME+</b>:0:01.32<br>","$area":41},"id":"proc-1-24930","name":"apache2"},{"children":[],"data":{"PID":"24933","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:355m<br><b>RES</b>:44m<br><b>SHR</b>:30m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:2.9%<br><b>TIME+</b>:0:00.56<br>","$area":30},"id":"proc-1-24933","name":"apache2"},{"children":[],"data":{"PID":"24935","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:353m<br><b>RES</b>:39m<br><b>SHR</b>:24m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:2.5%<br><b>TIME+</b>:0:00.28<br>","$area":26},"id":"proc-1-24935","name":"apache2"},{"children":[],"data":{"PID":"24936","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:359m<br><b>RES</b>:52m<br><b>SHR</b>:33m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.4%<br><b>TIME+</b>:0:00.98<br>","$area":35},"id":"proc-1-24936","name":"apache2"},{"children":[],"data":{"PID":"24939","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:354m<br><b>RES</b>:41m<br><b>SHR</b>:27m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:2.7%<br><b>TIME+</b>:0:00.28<br>","$area":28},"id":"proc-1-24939","name":"apache2"},{"children":[],"data":{"PID":"24941","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:362m<br><b>RES</b>:57m<br><b>SHR</b>:34m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.7%<br><b>TIME+</b>:0:00.43<br>","$area":38},"id":"proc-1-24941","name":"apache2"},{"children":[],"data":{"PID":"24942","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:357m<br><b>RES</b>:41m<br><b>SHR</b>:26m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:2.7%<br><b>TIME+</b>:0:00.31<br>","$area":28},"id":"proc-1-24942","name":"apache2"},{"children":[],"data":{"PID":"24943","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:346m<br><b>RES</b>:16m<br><b>SHR</b>:8604<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:1.1%<br><b>TIME+</b>:0:00.09<br>","$area":12},"id":"proc-1-24943","name":"apache2"},{"children":[],"data":{"PID":"24944","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:351m<br><b>RES</b>:34m<br><b>SHR</b>:21m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:2.3%<br><b>TIME+</b>:0:00.18<br>","$area":24},"id":"proc-1-24944","name":"apache2"},{"children":[],"data":{"PID":"24945","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:363m<br><b>RES</b>:67m<br><b>SHR</b>:43m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:4.4%<br><b>TIME+</b>:0:00.99<br>","$area":45},"id":"proc-1-24945","name":"apache2"},{"children":[],"data":{"PID":"24946","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:342m<br><b>RES</b>:5552<br><b>SHR</b>:1800<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.4%<br><b>TIME+</b>:0:00.03<br>","$area":5},"id":"proc-1-24946","name":"apache2"},{"children":[],"data":{"PID":"24950","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:www-data<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:360m<br><b>RES</b>:48m<br><b>SHR</b>:30m<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:3.2%<br><b>TIME+</b>:0:00.39<br>","$area":33},"id":"proc-1-24950","name":"apache2"}],"data":{"PID":"apache2","$area":11400},"id":"proc-apache2","name":"apache2(32)"},"newrelic-daemon":{"children":[{"children":[],"data":{"PID":"399","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:16232<br><b>RES</b>:292<br><b>SHR</b>:288<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.0%<br><b>TIME+</b>:0:00.00<br>","$area":1},"id":"proc-1-399","name":"newrelic-daemon"},{"children":[],"data":{"PID":"400","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:264m<br><b>RES</b>:7284<br><b>SHR</b>:2360<br><b>S</b>:S<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.5%<br><b>TIME+</b>:40:33.01<br>","$area":6},"id":"proc-1-400","name":"newrelic-daemon"}],"data":{"PID":"newrelic-daemon","$area":80},"id":"proc-newrelic-daemon","name":"newrelic-daemon(2)"},"top":{"children":[{"children":[],"data":{"PID":"24988","$color":"#0EAB06","cmdinfo":"<br><b>USER</b>:root<br><b>PR</b>:20<br><b>NI</b>:0<br><b>VIRT</b>:19196<br><b>RES</b>:1328<br><b>SHR</b>:1048<br><b>S</b>:R<br><b>CPU</b>:0%<br>%<b>MEM</b>:0.1%<br><b>TIME+</b>:0:00.00<br>","$area":2},"id":"proc-1-24988","name":"top"}],"data":{"PID":"top","$area":30},"id":"proc-top","name":"top(1)"}}"
I then convert this into a JavaScript Object using JSON.parse(...) and assign it to data .
Then the actual InfoVis code:
function getCanvasConfig() {
var ua = navigator.userAgent,
iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
typeOfCanvas = typeof HTMLCanvasElement,
nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
textSupport = nativeCanvasSupport
&& (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
//I'm setting this based on the fact that ExCanvas provides text support for IE
//and that as of today iPhone/iPad current text support is lame
return {
labelType: (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML',
nativeTextSupport: (!nativeCanvasSupport || (textSupport && !iStuff)),
useGradients: nativeCanvasSupport,
animate: !(iStuff || !nativeCanvasSupport)
};
}
var config = getCanvasConfig();
var attrs = {id: "infovis"};
var tmConfig = {
//where to inject the visualization
injectInto: attrs.id,
//parent box title heights
titleHeight: 15,
//enable animations
animate: config.animate,
//box offsets
offset: 1,
//Attach left and right click events
Events: {
enable: true,
onClick: function(node) {
if(node) tm.enter(node);
},
onRightClick: function() {
tm.out();
}
},
duration: 1000,
//Enable tips
Tips: {
enable: true,
//add positioning offsets
offsetX: 20,
offsetY: 20,
//implement the onShow method to
//add content to the tooltip when a node
//is hovered
onShow: function(tip, node, isLeaf, domElement) {
var html = "<div class=\"tip-title\">" + node.name
+ "</div><div class=\"tip-text\">";
var data = node.data;
if(data.PID) {
html += "<br><b>PID</b>: " + data.PID;
}
if(data.cmdinfo) {
html += ""+ data.cmdinfo +"";
}
tip.innerHTML = html;
}
},
//Add the name of the node in the correponding label
//This method is called once, on label creation.
onCreateLabel: function(domElement, node){
domElement.innerHTML = node.name;
var data = node.data;
var PID = data.PID;
var style = domElement.style;
style.display = '';
style.border = '1px solid transparent';
domElement.onclick = function() {
$('input#PIDtext').val(PID);
};
domElement.onmouseover = function() {
style.border = '1px solid #efefef';
};
domElement.onmouseout = function() {
style.border = '1px solid transparent';
};
}
};
var tm = new $jit.TM.Squarified(tmConfig);
tm.loadJSON(data);
tm.refresh();
However, all I keep getting is "undefined" injected into the canvas. Any clues?
Just leaving this here:
The problem was with the data. I need to have a root node with appropriate name & ID that had the rest of the data as children.
Related
TinyMCE Cloudinary Plugin not Displaying Properly
I have set up the Cloudinary TinyMCE Plugin and everything seems to be working properly, however, when they popup loads, the Cloudinary CMS screen doesn't resize to the popup. I'm wondering if someone can take a look at the plugin and let me know if there is an easy fix. I can't seem to find anything. Here is a screenshot of the popup: There is a resize function in cloudinaryimage/js/image.js: var CloudinaryImageDialog = { preInit : function() { tinyMCEPopup.requireLangPack(); }, init : function(ed) { var base = location.href.replace(/\/[^\/]+$/, ''); var controller = { socket: new easyXDM.Socket({ name: base + "/easyXDM.name.html", swf: base + "/easyxdm.swf", remote: tinyMCE.settings.cloudinary_cms_url, remoteHelper: "https://cloudinary.com/easyXDM.name.html", container: "cldimage", props: {style: {width: "100%", height: "99%"}}, onMessage: function(message, origin){ var json = JSON.parse(message); switch (json.message) { case "insert_into_post": CloudinaryImageDialog.insert_into_post(json); break; case "done": CloudinaryImageDialog.close(); break; } }, onReady: function() { controller.resizeWatcher(); el = ed.selection.getNode(); if (el && el.nodeName == 'IMG') { var html = ed.selection.getContent({format : 'html'}); controller.socket.postMessage(JSON.stringify({ message: "edit_image", html: html })); } } }), currentWidth: 0, currentHeight: 0, resizeWatcher: function() { jQuery(window).resize(CloudinaryImageDialog.update_window_dimensions); }, update_window_dimensions: function() { } }; }, insert_into_post : function(args) { delete args.message; delete args.href; args["style"] = ''; if (args.align && args.align != '') { if (args.align == 'left' || args.align == 'right') args["style"] = "float: " + args.align; else if (args.align == 'center') args["style"] = "display: block; margin: auto"; else args["style"] = "vertical-align: " + args.align; delete args.align; } tinyMCEPopup.restoreSelection(); var ed = tinyMCEPopup.editor; el = ed.selection.getNode(); // Fixes crash in Safari if (tinymce.isWebKit) ed.getWin().focus(); if (el && el.nodeName == 'IMG') { ed.dom.setAttribs(el, args); } else { tinymce.each(args, function(value, name) { if (value === "") { delete args[name]; } }); ed.execCommand('mceInsertContent', false, ed.dom.createHTML('img', args), {skip_undo : 1}); ed.undoManager.add(); } CloudinaryImageDialog.close(); }, close : function() { tinyMCEPopup.editor.execCommand('mceRepaint'); tinyMCEPopup.editor.focus(); tinyMCEPopup.close(); } }; CloudinaryImageDialog.preInit(); tinyMCEPopup.onInit.add(CloudinaryImageDialog.init, CloudinaryImageDialog);
I figured it out. All that had to be done was add height: 100% to html in image.htm.
How to "dump" points selected with the LinkedBrush plugin for mpld3?
I am trying to implement a plugin that allows the user to dump relevant information about points selected by the LinkedBrush plugin. I think my question is sort of related to this example. I have meta information tied to each point via the HTMLTooltip plugin. Ideally, I would somehow be able to dump this too. In the example I linked to, the information is outputted via a prompt. I wish to be able to save this information to a text file of some kind. Put slightly differently: How do I determine which points in a scatter plot have been selected by the LinkedBrush tool so that I can save the information?
To solves this, I ended up just editing the LinkedBrush plugin code. I added a button that, when clicked, outputs the extent of the brush window by using brush.extent(). This prints the minimum and maximum x and y coordinates. I will basically use these coordinates to trace back to the input data set and determine which points fell within the bounds of the brush. If anyone has a better idea of how to solve this, I would welcome it. class LinkedBrush(plugins.PluginBase): JAVASCRIPT=""" mpld3.LinkedBrushPlugin = mpld3_LinkedBrushPlugin; mpld3.register_plugin("linkedbrush", mpld3_LinkedBrushPlugin); mpld3_LinkedBrushPlugin.prototype = Object.create(mpld3.Plugin.prototype); mpld3_LinkedBrushPlugin.prototype.constructor = mpld3_LinkedBrushPlugin; mpld3_LinkedBrushPlugin.prototype.requiredProps = [ "id" ]; mpld3_LinkedBrushPlugin.prototype.defaultProps = { button: true, enabled: null }; function mpld3_LinkedBrushPlugin(fig, props) { mpld3.Plugin.call(this, fig, props); if (this.props.enabled === null) { this.props.enabled = !this.props.button; } var enabled = this.props.enabled; if (this.props.button) { var BrushButton = mpld3.ButtonFactory({ buttonID: "linkedbrush", sticky: true, actions: [ "drag" ], onActivate: this.activate.bind(this), onDeactivate: this.deactivate.bind(this), onDraw: function() { this.setState(enabled); }, icon: function() { return mpld3.icons["brush"]; } }); this.fig.buttons.push(BrushButton); var my_icon = "data:image/png;base64,longstring_that_I_redacted"; var SaveButton = mpld3.ButtonFactory({ buttonID: "save", sticky: false, onActivate: this.get_selected.bind(this), icon: function(){return my_icon;}, }); this.fig.buttons.push(SaveButton); } this.extentClass = "linkedbrush"; } mpld3_LinkedBrushPlugin.prototype.activate = function() { if (this.enable) this.enable(); }; mpld3_LinkedBrushPlugin.prototype.deactivate = function() { if (this.disable) this.disable(); }; mpld3_LinkedBrushPlugin.prototype.get_selected = function() { if (this.get_selected) this.get_selected(); }; mpld3_LinkedBrushPlugin.prototype.draw = function() { var obj = mpld3.get_element(this.props.id); if (obj === null) { throw "LinkedBrush: no object with id='" + this.props.id + "' was found"; } var fig = this.fig; if (!("offsets" in obj.props)) { throw "Plot object with id='" + this.props.id + "' is not a scatter plot"; } var dataKey = "offsets" in obj.props ? "offsets" : "data"; mpld3.insert_css("#" + fig.figid + " rect.extent." + this.extentClass, { fill: "#000", "fill-opacity": .125, stroke: "#fff" }); mpld3.insert_css("#" + fig.figid + " path.mpld3-hidden", { stroke: "#ccc !important", fill: "#ccc !important" }); var dataClass = "mpld3data-" + obj.props[dataKey]; var brush = fig.getBrush(); var dataByAx = []; fig.axes.forEach(function(ax) { var axData = []; ax.elements.forEach(function(el) { if (el.props[dataKey] === obj.props[dataKey]) { el.group.classed(dataClass, true); axData.push(el); } }); dataByAx.push(axData); }); var allData = []; var dataToBrush = fig.canvas.selectAll("." + dataClass); var currentAxes; function brushstart(d) { if (currentAxes != this) { d3.select(currentAxes).call(brush.clear()); currentAxes = this; brush.x(d.xdom).y(d.ydom); } } function brushmove(d) { var data = dataByAx[d.axnum]; if (data.length > 0) { var ix = data[0].props.xindex; var iy = data[0].props.yindex; var e = brush.extent(); if (brush.empty()) { dataToBrush.selectAll("path").classed("mpld3-hidden", false); } else { dataToBrush.selectAll("path").classed("mpld3-hidden", function(p) { return e[0][0] > p[ix] || e[1][0] < p[ix] || e[0][1] > p[iy] || e[1][1] < p[iy]; }); } } } function brushend(d) { if (brush.empty()) { dataToBrush.selectAll("path").classed("mpld3-hidden", false); } } this.get_selected = function(d) { var brush = fig.getBrush(); var extent = brush.extent(); alert(extent); } this.enable = function() { this.fig.showBrush(this.extentClass); brush.on("brushstart", brushstart).on("brush", brushmove).on("brushend", brushend); this.enabled = true; }; this.disable = function() { d3.select(currentAxes).call(brush.clear()); this.fig.hideBrush(this.extentClass); this.enabled = false; }; this.disable(); }; """ def __init__(self, points, button=True, enabled=True): if isinstance(points, mpl.lines.Line2D): suffix = "pts" else: suffix = None self.dict_ = {"type": "linkedbrush", "button": button, "enabled": False, "id": utils.get_id(points, suffix)}
Infovis not Iterating over Root Node
I'm facing weird behaviour of Jit Infovis i'm using. I have two different html files that include a load json function from a Javascript file. The function is using infovis library to display a hypertree map from a json file. Both two html files load the same json file. One html file has been succeeded rendering the map properly. But another one has not. It renders the map almost properly, but after i debugged it, i got it not iterating over the root node. Then, the root node becames inactive without label and clickability. This is the js function i'm using. var labelType, useGradients, nativeTextSupport, animate; (function () { var ua = navigator.userAgent, iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i), typeOfCanvas = typeof HTMLCanvasElement, nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'), textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function'); //I'm setting this based on the fact that ExCanvas provides text support for IE //and that as of today iPhone/iPad current text support is lame labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native' : 'HTML'; nativeTextSupport = labelType == 'Native'; useGradients = nativeCanvasSupport; animate = !(iStuff || !nativeCanvasSupport); })(); var Log = { elem: false, write: function (text) { if (!this.elem) this.elem = document.getElementById('log'); this.elem.innerHTML = text; this.elem.style.left = (350 - this.elem.offsetWidth / 2) + 'px'; } }; function init(slugParam, pageParam) { var isFirst = true; var isSetAsRoot = false; // alert(slugParam+ " | "+pageParam); var url = Routing.generate('trade_map_buyer_json', { slug : slugParam, page : pageParam }); //init data $.getJSON(url, function (json) { var type = 'Buyer'; //end var infovis = document.getElementById('infovis'); infovis.style.align = "center"; infovis.innerHTML = ''; // infovis.innerHTML = '<img align="center" id="gifloader" style="margin-left:50%; margin-top:50%" src="{{ asset('/bundles/jariffproject/frontend/images/preloader.gif') }}" width="30px" height="30px"/>' var w = infovis.offsetWidth - 50, h = infovis.offsetHeight - 50; url = url.replace("/json/", "/"); window.history.pushState("object or string", "Title", url); //init Hypertree var ht = new $jit.Hypertree({ //id of the visualization container injectInto: 'infovis', Navigation: { enable: false, panning: 'avoid nodes', }, //canvas width and height width: w, height : h, //Change node and edge styles such as //color, width and dimensions. Node: { dim: 9, overridable: true, color: "#66FF33" }, Tips: { enable: true, type: 'HTML', offsetX: 0, offsetY: 0, onShow: function(tip, node) { // dump(tip); tip.innerHTML = "<div style='background-color:#F8FFC9;text-align:center;border-radius:5px; padding:10px 10px;' class='node-tip'><p style='font-size:100%;font-weight:bold;'>"+node.name+"</p><p style='font-size:50%pt'>"+node.data.address+"</p></div>"; } }, Events: { enable: true, type: 'HTML', onMouseEnter: function(node, eventInfo, e){ var nodeId = node.id; var menu1 = [ {'set as Root':function(menuItem,menu) { menu.hide(); isSetAsRoot = true; console.log(nodeId); init(nodeId, 0); }}, $.contextMenu.separator, {'View details':function(menuItem,menu) { }} ]; $('.node').contextMenu(menu1,{theme:'vista'}); } }, Edge: { lineWidth: 1, color: "#52D5DE", overridable: true, }, onBeforePlotNode: function(node) { if (isFirst) { console.log(node._depth); var odd = isOdd(node._depth); if (odd) { node.setData('color', "#66FF33"); // hijau (supplier) } else { node.setData('color', "#FF3300"); // merah (buyer) } isFirst = false; } }, onPlotNode: function(node) { if (isSetAsRoot) { var nodeInstance = node.getNode(); var nodeId = nodeInstance.id; init(nodeId, 0); isSetAsRoot = false; } }, onBeforeCompute: function (domElement, node) { var dot = ht.graph.getClosestNodeToOrigin("current"); type = isOdd(dot._depth) ? 'Supplier' : 'Buyer'; }, //Attach event handlers and add text to the //labels. This method is only triggered on label //creation onCreateLabel: function (domElement, node) { var odd = isOdd(node._depth); if (odd) { node.setData('color', "#66FF33"); // hijau (supplier) } else { node.setData('color', "#FF3300"); // merah (buyer) } domElement.innerHTML = node.name; // if (node._depth == 1) { console.log("|"+node.name+"|"+node._depth+"|"); // } $jit.util.addEvent(domElement, 'click', function () { ht.onClick(node.id, { onComplete: function () { console.log(node.id); ht.controller.onComplete(node); } }); }); }, onPlaceLabel: function (domElement, node) { var style = domElement.style; style.display = ''; style.cursor = 'pointer'; if (node._depth <= 1) { style.fontSize = "0.8em"; style.color = "#000"; style.fontWeight = "normal"; } else if (node._depth == 2) { style.fontSize = "0.7em"; style.color = "#555"; } else { style.display = 'none'; } var left = parseInt(style.left); var w = domElement.offsetWidth; style.left = (left - w / 2) + 'px'; }, onComplete: function (node) { var dot = ht.graph.getClosestNodeToOrigin("current"); console.log(dot._depth); var connCount = dot.data.size; var showingCount = ''; if (connCount != undefined) { var pageParamInt = (parseInt(pageParam)+1) * 10; var modulus = connCount%10; showingCount = (pageParamInt - 9) + " - " + pageParamInt; if (connCount - (pageParamInt - 9) < 10) { showingCount = (pageParamInt - 10) + " - " + ((pageParamInt - 10) + modulus); } } else { connCount = '0'; showingCount = 'No Connections Shown' } } }); //load JSON data. ht.loadJSON(json); //compute positions and plot. ht.refresh(); //end ht.controller.onComplete(); }); } function isEven(n) { return isNumber(n) && (n % 2 == 0); } function isOdd(n) { return isNumber(n) && (n % 2 == 1); } function isNumber(n) { return n === parseFloat(n); } function processAjaxData(response, urlPath){ } function dump(obj) { var out = ''; for (var i in obj) { out += i + ": " + obj[i] + "\n"; } out = out + "\n\n" console.log(out); // or, if you wanted to avoid alerts... var pre = document.createElement('pre'); pre.innerHTML = out; document.body.appendChild(pre) } What's probably causing this?
Please check whether there is conflict id. Basically infovis render each nodes by the id. And if there is an DOM element that has the same id with one DOM element of a node. It would conflict and won't render you can check it by duming dom element iterating over the nodes.
How to create a plugin to add attachment or file in rich text editor of Adobe CQ 5?
I need help in creating a plugin for rich text editor in Adobe cq 5 to add an image, pdf, video, ppt or any file into rich text editor. The existing rte plugins that are available are findreplace, undo, spellcheck, table etc How to create a plugin to add a file to rich text editor? The plugins are an ext js files. Appreciate if any one can suggest answer. It will be of great help. Thanks
I added a custom drop down into my RTE. It was used to add values to the editor on selecting values from it. I think this might help you solve your issue because similarly you can create your required plugin. Please refer comments next to the methods for your reference. /** * Placeholder Plugin */ var keyvalueEnteries = []; var newText; var doc; var range CUI.rte.plugins.PlaceholderPlugin = new Class({ toString: "PlaceholderPlugin", extend: CUI.rte.plugins.Plugin, /** * #private */ cachedFormats: null, /** * #private */ formatUI: null, getFeatures: function() { return [ "Myparaformat" ]; }, /** * #private * */ getKeys: function() { var com = CUI.rte.Common; if (this.cachedFormats == null) { this.cachedFormats = this.config.formats || { }; com.removeJcrData(this.cachedFormats); this.cachedFormats = com.toArray(this.cachedFormats, "tag", "description"); } return this.cachedFormats; }, initializeUI: function(tbGenerator) { var plg = CUI.rte.plugins; var ui = CUI.rte.ui; if (this.isFeatureEnabled("placeHolder")) { this.formatUI = tbGenerator.createParaFormatter("placeHolder", this,null,this.getKeys()); tbGenerator.addElement("placeHolder", plg.Plugin.SORT_PARAFORMAT, this.formatUI, 10); } }, notifyPluginConfig: function(pluginConfig) { //This function gets executed once on load of RTE for the first time var url = pluginConfig.sourceURL; keyvalueEnteries = CQ.HTTP.eval(url); keyvalueEnteries = JSON.stringify(keyvalueEnteries); if(keyvalueEnteries == undefined){ keyvalueEnteries = '[{"key":"","value":"None"}]'; } pluginConfig = pluginConfig || { }; //creating JSON sttructure var placeholderJSON = '{"formats":' + keyvalueEnteries + '}'; var placeHolderVals = eval('(' + placeholderJSON + ')'); var defaults = placeHolderVals; if (pluginConfig.formats) { delete defaults.formats; } CUI.rte.Utils.applyDefaults(pluginConfig, defaults); this.config = pluginConfig; }, execute: function(cmd) { // This function gets executed when there is change in the state of custom plugin/drop down if (this.formatUI) { var formatId = this.formatUI.getSelectedFormat(); if (formatId && range != undefined) { var placeholderElement = ""; if(formatId == 'none' && range.collapsed == false){//checks if None is selected in placeholder and the text is selected range.deleteContents(); }else if(formatId != 'none'){ placeholderElement = document.createTextNode(" ${" + formatId + "} "); range.insertNode(placeholderElement); //INSERTS PLACEHOLDER AT CURRENT CARET LOCATION range.setStartAfter(placeholderElement);//INSERTS CURSOR AT THE END OF CURRENT PLACEHOLDER IF PLACEHOLDER IS SELECTED AGAIN } } } }, updateState: function(selDef) { // This function gets executed on click on the editor space in RTE doc = selDef.editContext.doc; //GET THE DOCUMENT INSIDE THE IFRAME range = doc.getSelection().getRangeAt(0); //GETS CURRENT CARET POSITION } }); //register plugin CUI.rte.plugins.PluginRegistry.register("placeholder", CUI.rte.plugins.PlaceholderPlugin); CUI.rte.ui.ext.ParaFormatterImpl = new Class({ toString: "ParaFormatterImpl", extend: CUI.rte.ui.TbParaFormatter, // Interface implementation ------------------------------------------------------------ addToToolbar: function(toolbar) { var com = CUI.rte.Common; this.toolbar = toolbar; if (com.ua.isIE) { // the regular way doesn't work for IE anymore with Ext 3.1.1, hence working // around var helperDom = document.createElement("span"); helperDom.innerHTML = "<select class=\"x-placeholder-select\">" + this.createFormatOptions() + "</select>"; this.formatSelector = CQ.Ext.get(helperDom.childNodes[0]); } else { this.formatSelector = CQ.Ext.get(CQ.Ext.DomHelper.createDom({ tag: "select", cls: "x-placeholder-select", html: this.createFormatOptions() })); } this.initializeSelector(); toolbar.add( CQ.I18n.getMessage("Placeholder"), // Type the name you want to appear in RTE for the custom plugin /drop down " ", this.formatSelector.dom); }, /** * Creates HTML code for rendering the options of the format selector. * #return {String} HTML code containing the options of the format selector * #private */ createFormatOptions: function() { var htmlCode = ""; if (this.formats) { var formatCnt = this.formats.length; htmlCode += "<option value='none'>None</option>"; for (var f = 0; f < formatCnt; f++) { var text = this.formats[f].text; htmlCode += "<option value=\"" + this.formats[f].value + "\">" + text + "</option>"; } } return htmlCode; }, createToolbarDef: function() { return { "xtype": "panel", "itemId": this.id, "html": "<select class=\"x-placeholder-select\">" + this.createFormatOptions() + "</select>", "listeners": { "afterrender": function() { var item = this.toolbar.items.get(this.id); if (item && item.body) { this.formatSelector = CQ.Ext.get(item.body.dom.childNodes[0]); this.initializeSelector(); } }, "scope": this } }; }, initializeSelector: function() { this.formatSelector.on('change', function() { var format = this.formatSelector.dom.value; if (format.length > 0) { this.plugin.execute(this.id); } }, this); this.formatSelector.on('focus', function() { this.plugin.editorKernel.isTemporaryBlur = true; }, this); // fix for a Firefox problem that adjusts the combobox' height to the height // of the largest entry this.formatSelector.setHeight(20); }, getSelectorDom: function() { return this.formatSelector.dom; }, getSelectedFormat: function() { var format; if (this.formatSelector) { format = this.formatSelector.dom.value; if (format.length > 0) { return format; } } else { var item = this.toolbar.items.get(this.id); if (item.getValue()) { return item; } } return null; }, selectFormat: function(formatToSelect, auxRoot, formatCnt, noFormatCnt) { var indexToSelect = -1; var selectorDom = this.formatSelector.dom; if ((formatToSelect != null) && (formatCnt == 1) && (noFormatCnt == 0)) { var options = selectorDom.options; for (var optIndex = 0; optIndex < options.length; optIndex++) { var optionToCheck = options[optIndex]; if (optionToCheck.value == formatToSelect) { indexToSelect = optIndex; break; } } } selectorDom.disabled = (noFormatCnt > 0) && (auxRoot == null); selectorDom.selectedIndex = indexToSelect; } });
Filter DOM elements via JavaScript
I have elements that get created like this: function appendCalendarEventToList(className, event) { var eventObject = { title: event.title, id: event.id, type: event.type, color: event.color, description: event.description, canReoccur: event.canReoccur, reoccurVal: event.reoccurVal, estHours: event.estHours, project: event.project }; var div = document.createElement("div"); div.className = 'external-event'; div.style.background = event.color; div.innerHTML = event.title; // store the Event Object in the DOM element so we can get to it later $(div).data('eventObject', eventObject); $(div).draggable({ helper: function () { $copy = $(this).clone(); $copy.data('eventObject', eventObject); $copy.css({ "list-style": "none", "width": $(this).outerWidth() }); return $copy; }, appendTo: 'body', zIndex: 999, revert: true, // will cause the event to go back to its revertDuration: 0 // original position after the drag }); $(className).append(div); $(div).qtip({ content: event.title, position: { target: 'mouse' }, // show: { event: 'click' }, hide: { event: 'mousedown mouseleave' }, style: { classes: 'custSideTip', width: 200, padding: 5, color: 'black', textAlign: 'left', border: { width: 1, radius: 3 } } }); return div; } Every event has a project attribute as you see. However there are projects and tasks (and cases that can be ignored): function refetchUnscheduled() { $.ajax({ type: "POST", url: "CalendarServices.aspx/GetUnscheduled", data: '', async:false, success: function (data) { var projects = '.project-container'; var tasks = '.task-container'; var cases = '.case-container'; $(projects).html(''); $(tasks).html(''); $(cases).html(''); var numProjects = 0; var numTasks = 0; var numCases = 0; $.each(data, function () { var className = ''; var url = ''; var typeName = ''; var crm = this.crm; switch(this.type) { case 'p': className = projects; url = 'ProjectViewFrame.aspx?id=' + this.id; typeName = 'Project'; numProjects++; break; case 't': className = tasks; url = 'TaskViewFrame.aspx?id=' + this.id; typeName = 'Task'; numTasks++; break; case 'c': className = cases; url = 'CaseViewFrame.aspx?id=' + this.id; typeName = 'Case'; numCases++; break; default: } var curDiv = appendCalendarEventToList(className, this); var curEvent = this; $(curDiv).bind('contextmenu', function (e) { $('#contextMenuContainer').html(''); var btn1 = createLink('Edit ' + typeName, 'context-elem-name'); $('#contextMenuContainer').append(btn1); $(btn1).on('click', function () { if (crm == '') showCalendarModal('Edit ' + typeName, url); else showCRM(crm); }) prepContextMenu(e); return false; }); }); changeUnscheduledHeaders(); } , error: function () { } }); } Here is what I need that I am unsure how to do: I need an OR based filter: Given the following: function filter(criteria,projects-div,tasks-div) { var words = criteria.split(' '); } I need to first, hide all projects whos obj.data('eventObject').title contains one or more words. Then, once these are filtered: I need to first show all tasks whos project is visible, So: obj.data('eventObject').project is == to one of the visible project's project property. Then, I need to also show any tasks that have any of the words. All comparing must be case insensitive too. So say my criteria is 'hello world' I show all projects with hello or world in the title. I show all tasks whos project attribute is visible after the first step I show all tasks whos title has hello or world Thanks
I'm on mobile, but at first glance, what about [].filter.call (NodeList, filterFn)?