EXAMPLE (template) - displaying a two dimensional table

Problem: You need to display a table with first name, last name and age.

Sample data:
Paul Doe, 24
Greg Jones, 31
Able Smith, 29

We will use a function node to create the table like this:


And display the table using the template node. Here is what we use in the template node:
09%20AM
In this case we use the ng-repeat twice, once to process each row of the table in the <tr> and once to display each item in each row using a <td> element.

Here is what the display will look like:
17%20AM

the following is the flow that is used in this example:
[{"id":"1b61c07f.7d06f8","type":"inject","z":"968ca76f.3dc46","name":"Go","topic":"","payload":"1","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":90,"y":260,"wires":[["a30ba039.4650a8"]]},{"id":"a30ba039.4650a8","type":"function","z":"968ca76f.3dc46","name":"build array (2)","func":"var arr =[[\"Paul\",\"Doe\",24],[\"Greg\",\"Jones\",31],[\"Able\",\"Smith\",29]];\nmsg.payload = arr;\nreturn msg;","outputs":1,"noerr":0,"x":260,"y":260,"wires":[["c9727c65.303f"]]},{"id":"7fa8d335.150f84","type":"debug","z":"968ca76f.3dc46","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":790,"y":260,"wires":[]},{"id":"c9727c65.303f","type":"ui_template","z":"968ca76f.3dc46","group":"3c5da821.6d5638","name":"Two dimension array table","order":1,"width":"6","height":"6","format":"<table id=\"table\" border=\"1\">\n <tr>\n <th>First Name</th> \n <th>Last Name</th>\n <th>Age</th>\n </tr>\n <tbody>\n <tr ng-repeat=\"row in msg.payload\">\n <td ng-repeat=\"item in row\" >{{item}}</td>\n </tr>\n </tbody>\n</table>\n\n","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":480,"y":260,"wires":[["7fa8d335.150f84"]]},{"id":"3c5da821.6d5638","type":"ui_group","z":"","name":"Two dimension Array","tab":"f9ac9e91.20e588","order":2,"disp":true,"width":"6","collapse":false},{"id":"f9ac9e91.20e588","type":"ui_tab","z":"","name":"Table Examples","icon":"dashboard","order":1}]

4 Likes

Thank you.

1 Like

Here is a flow to get a dynamic table with sortable columns, a searchbar and outputs the clicked row data:

Enjoy!

image

[{"id":"ee4d312b.cc342","type":"ui_template","z":"95993933.d7c978","group":"18e6e44c.b0561c","name":"Search Sortable Table","order":1,"width":"6","height":"5","format":"<style>\n\ntable {\n    color: #333;\n    font-family: Helvetica, Arial, sans-serif;\n    width: 100%;\n    border-collapse: collapse;\n    border-spacing: 0;\n}\ntd, th {\n    border: 1px solid transparent;\n    /* No more visible border */\n    height: 30px;\n    transition: all 0.3s;\n    /* Simple transition for hover effect */\n}\nth {\n    background: #DFDFDF;\n    /* Darken header a bit */\n    font-weight: bold;\n}\ntd {\n    background: #FAFAFA;\n}\n\n/* Cells in even rows (2,4,6...) are one color */\n\ntr:nth-child(even) td {\n    background: #F1F1F1;\n}\n\n/* Cells in odd rows (1,3,5...) are another (excludes header cells)  */\n\ntr:nth-child(odd) td {\n    background: #FEFEFE;\n}\ntr td:hover {\n    background: #666;\n    color: #FFF;\n}\n\n/* Hover cell effect! */\n    \n.animate-enter, \n.animate-leave\n{ \n    -webkit-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;\n    -moz-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;\n    -ms-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;\n    -o-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;\n    transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;\n    position: relative;\n    display: block;\n} \n\n.animate-enter.animate-enter-active, \n.animate-leave {\n    opacity: 1;\n    top: 0;\n    height: 30px;\n}\n\n.animate-leave.animate-leave-active,\n.animate-enter {\n    opacity: 0;\n    top: -50px;\n    height: 0px;\n}\n    \n.container\n{\n    max-height: 450px;\n    overflow-y: scroll;\n    overflow-x: hidden;\n}\n</style>\n\n<div>\n     <form>\n      <span class=\"input-group\">\n          <i class=\"fa fa-search\"></i>\n        <input type=\"text\" class=\"form-control\" placeholder=\"Search\" ng-model=\"search\">\n      </span>      \n  </form>\n  <div class=\"container\" ng-app=\"sortApp\">\n\n      <table>\n        <thead>\n        <tr style=\"width:100%\">\n            <td>\n          <a href=\"#\">\n            Index\n          </a>\n        </td>\n        <td>\n          <a href=\"#\" ng-click=\"sortType = 'firstname'; sortReverse = !sortReverse\">\n            First Name \n            <span ng-show=\"sortType == 'firstname' && !sortReverse\" class=\"fa fa-caret-down\"></span>\n            <span ng-show=\"sortType == 'firstname' && sortReverse\" class=\"fa fa-caret-up\"></span>\n          </a>\n        </td>\n        <td>\n          <a href=\"#\" ng-click=\"sortType = 'lastname'; sortReverse = !sortReverse\">\n          Last Name \n            <span ng-show=\"sortType == 'lastname' && !sortReverse\" class=\"fa fa-caret-down\"></span>\n            <span ng-show=\"sortType == 'lastname' && sortReverse\" class=\"fa fa-caret-up\"></span>\n          </a>\n        </td>\n        <td>\n          <a href=\"#\" ng-click=\"sortType = '(age -0)'; sortReverse = !sortReverse\">\n            Age\n            <span ng-show=\"sortType == '(age -0)' && !sortReverse\" class=\"fa fa-caret-down\"></span>\n            <span ng-show=\"sortType == '(age -0)' && sortReverse\" class=\"fa fa-caret-up\"></span>\n          </a>\n        </td>\n\n          </tr>\n          </thead>\n          <tbody>\n        <tr ng-repeat=\"user in msg.options | orderBy:sortType:sortReverse | filter:search track by $index\" ng-click=\"msg.payload = user;send(msg);\" style=\"width:100%\" flex>\n                <td><b ng-bind=\"$index+1\"></b></td>\n               <td ng-bind=\"user.firstname\"></td>\n               <td ng-bind=\"user.lastname\"></td>\n               <td ng-bind=\"user.age\"></td>\n         </tr>\n        </tbody>\n      </table>\n\n</div>\n</div>\n</body></html>","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":880,"y":2180,"wires":[["e91124.de6c3ee"]]},{"id":"a60a7430.d10bd8","type":"inject","z":"95993933.d7c978","name":"Go","topic":"","payload":"1","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":490,"y":2180,"wires":[["bece8dfc.55e45"]]},{"id":"e91124.de6c3ee","type":"debug","z":"95993933.d7c978","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1110,"y":2180,"wires":[]},{"id":"bece8dfc.55e45","type":"change","z":"95993933.d7c978","name":"","rules":[{"t":"set","p":"options","pt":"msg","to":"[{\"firstname\":\"Paul\",\"lastname\":\"Doe\",\"age\":24},{\"firstname\":\"Greg\",\"lastname\":\"Jones\",\"age\":31},{\"firstname\":\"Able\",\"lastname\":\"Smith\",\"age\":29}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":660,"y":2180,"wires":[["ee4d312b.cc342"]]},{"id":"18e6e44c.b0561c","type":"ui_group","z":0,"name":"Tables","tab":"e3a9c600.825af8","order":8,"disp":true,"width":"6"},{"id":"e3a9c600.825af8","type":"ui_tab","z":0,"name":"Angular UI templates","icon":"dashboard"}]
2 Likes

Thank you for the great examples.
How could any of the examples be adapted to add the possibility to edit values in the table and send them back to the database?

what have you tried?

There was a thread a short while back from someone trying to do similar - Gridview with CRUD Options (MySQL)

From my example above, you could modify the template to have fields in the table's ng-repeat section and add a save button to output the whole table via ng-click.


<div>
     <form>
      <span class="input-group">
          <i class="fa fa-search"></i>
        <input type="text" class="form-control" placeholder="Search" ng-model="search">
        <button ng-click="msg.payload = msg.options;send(msg)">Save</button>
      </span>      
  </form>
  <div class="container" ng-app="sortApp">

      <table>
        <thead>
        <tr style="width:100%">
            <td>
          <a href="#">
            Index
          </a>
        </td>
        <td>
          <a href="#" ng-click="sortType = 'firstname'; sortReverse = !sortReverse">
            First Name 
            <span ng-show="sortType == 'firstname' && !sortReverse" class="fa fa-caret-down"></span>
            <span ng-show="sortType == 'firstname' && sortReverse" class="fa fa-caret-up"></span>
          </a>
        </td>
        <td>
          <a href="#" ng-click="sortType = 'lastname'; sortReverse = !sortReverse">
          Last Name 
            <span ng-show="sortType == 'lastname' && !sortReverse" class="fa fa-caret-down"></span>
            <span ng-show="sortType == 'lastname' && sortReverse" class="fa fa-caret-up"></span>
          </a>
        </td>
        <td>
          <a href="#" ng-click="sortType = '(age -0)'; sortReverse = !sortReverse">
            Age
            <span ng-show="sortType == '(age -0)' && !sortReverse" class="fa fa-caret-down"></span>
            <span ng-show="sortType == '(age -0)' && sortReverse" class="fa fa-caret-up"></span>
          </a>
        </td>

          </tr>
          </thead>
          <tbody>
        <tr ng-repeat="user in msg.options | orderBy:sortType:sortReverse | filter:search track by $index" style="width:100%" flex>
                <td><b ng-bind="$index+1"></b></td>
               <td><input type="text" ng-model="user.firstname"></td>
               <td><input type="text" ng-model="user.lastname"></td>
               <td><input type="number" ng-model="user.age"></td>
         </tr>
        </tbody>
      </table>

</div>
</div>
</body></html>

Nice example! I noticed onething, the input 'type' for the age should be number
<td><input type="number" ng-model="user.age"></td>
or, if it has been changed, you get a string back on save.

Thanks! corrected above.

Thanks again. Could you please explain how exactly can the click of the "Save" button be handled in a function node, that prepares the SQL"update" query that should be executed by a (for instance) SQLite node ?
For me it is not clear how can from the "Template" html code be generated a new message containing the edited cell value(s).
What would change if each row would have its own "save" button?

Have you tried adding debug nodes to your flow and experimenting? That will show you what is coming out of a node and you can then decide how to handle that data - i.e. pass it to a function node to build the sql.

Good idea, got it working ! Thx again.

1 Like

Is there any possibility to update several records of the SQLite table in one-shot ? For instance if several values have been edited in the table before clicking the "save" button.

1 Like

Try Googling "sqlite multiple row updates"

2 Likes

I searched, but I am not sure if there is any solution for SQLite.
Otherwise, would be possible to have a loop in the function handling of the "save" button event, that calls several times the send() function with different topics, to trigger the function node connected to the output of the template node?

Did you try any of the things the search suggested?

My mistake, I think that you are both right : "INSERT OR REPLACE" seems to be able to update several values from several rows.

One more problem (sorry about that). To make some columns editable at double-click, I set the contenteditable flag to "true" for these columns in the template node. This works fine with Firefox and Chrome, but not with Edge (here it is not possible to edit a column by double-clicking it). Is there any explanation for this "read-only" behaviour on Edge?

Did you try a google search?

Not really sure what should I search for. Does Microsoft Edge have problems with Angular ?