21

Scenario

Lets say I am owner of a big company that has many stores. Depending on what role (place in the organization) I have within the company, I will have different access to data. There will be different modules and for this specific question there is one where users that have access can go through daily cost and sales. (If that's legal or not...don't care, it's just for an example.)

The user thereby get all data through REST API from BackEnd (Java application) with all data for all stores the user has access to. The user should then be able to filter the data, by different filter combinations. Most relevant for my question is the date interval by days.

There will be some charts showing data on different levels and below there will be a table area where I want the multi-level tables, hence my question.

Done so far

  1. I first created accordions that have stores on accordion-group level and then next level of data in a table, within the accordion-body. (Only hard coded data at the moment.) The problem here was that an according heading is a string and after some discussion we felt that this was not a good solution since the heading would consist of parts of data that in a table would have been separate columns. It would therefore be difficult to "columnize" the heading data to match horizontally the different "stores" (between the accordion headings) when collapsed (and of course even more messy when one or more accordion are expanded).
  2. I replaced the accordions with table and ng-repeat. Have successfully populated the first table level with both data from the figurative API with JSON data as well as got i18next working for the headings.

JSON

{ 
"metadata":{
    "storesInTotal":"25",
    "storesInRepresentation":"2"
},
"storedata":[
    { 
        "store" : {
            "storeId" : "1000",
            "storeName" : "Store 1",
            "storePhone" : "+46 31 1234567",
            "storeAddress": "Some street 1",
            "storeCity" : "Gothenburg"
        },
        "data" : {
            "startDate" : "2013-07-01",
            "endDate" : "2013-07-02",
            "costTotal" : "100000",
            "salesTotal" : "150000",
            "revenueTotal" : "50000",
            "averageEmployees" : "3.5",
            "averageEmployeesHours" : "26.5",
            "dayData" : [
                { 
                    "date" : "2013-07-01",
                    "cost" : "25000",
                    "sales" : "15000",
                    "revenue" : "4000",
                    "employees" : "3",
                    "employeesHoursSum" : "24"
                },
                {
                    "date" : "2013-07-02",
                    "cost" : "25000",
                    "sales" : "16000",
                    "revenue" : "5000",
                    "employees" : "4",
                    "employeesHoursSum" : "29"
                }
            ]
        }
    },
    {
        "store" : {
            "storeId" : "2000",
            "storeName" : "Store 2",
            "storePhone" : "+46 8 9876543",
            "storeAddress": "Big street 100",
            "storeCity" : "Stockholm"
        },
        "data" : {
            "startDate" : "2013-07-01",
            "endDate" : "2013-07-02",
            "costTotal" : "170000",
            "salesTotal" : "250000",
            "revenueTotal" : "80000",
            "averageEmployees" : "4.5",
            "averageEmployeesHours" : "35",
            "dayData" : [
                { 
                    "date" : "2013-07-01",
                    "cost" : "85000",
                    "sales" : "120000",
                    "revenue" : "35000",
                    "employees" : "5",
                    "employeesHoursSum" : "38"
                },
                {
                    "date" : "2013-07-02",
                    "cost" : "85000",
                    "sales" : "130000",
                    "revenue" : "45000",
                    "employees" : "4",
                    "employeesHoursSum" : "32"
                }
            ]
        }
    }
],
"_links":{
    "self":{
        "href":"/storedata/between/2013-07-01/2013-07-02"
    }
}
}

Visual example - JSFiddle

Check the values in the result frame, top left corner. Try clicking for example row with Store ID 2000, then with 3000 and then 3000 again to see how the values change. Current update of my JSFiddle

Wanted functionality

When a row is clicked (as shown in the JSFiddle), I want a directive or something triggered, to go and fetch underlying data (dayData) for the store clicked and show all days in the date interval. I.e expanding the row, and including a new table under the clicked row, which also should use ng-repeat to get all data displayed similar to the existing one, but inline.

Question

So I have already got the functionality to get the $index and also the specific data from the clicked row. What kind of directive or any other solution do I need additionally to get the "data when row clicked" and presented in a table under the clicked row?

I don't want it in the DOM all the time, since there might be many dayData for each store and many stores. (And will use pagination later, but even so, not in the DOM all the time.) This means that I have to be able to ADD when clicking a row, and when clicking the same or another REMOVE from the previously clicked.


EDIT

New updated JSFiddle.

nathanchere
  • 8,008
  • 15
  • 65
  • 86
Pixic
  • 1,345
  • 1
  • 17
  • 29
  • I can also add that I have tried looking for similar questions here on StackOverflow... [angularjs-outer-ng-repeat-not-to-register-watches-on-inner-ng-repeat](http://stackoverflow.com/questions/17379578/angularjs-outer-ng-repeat-not-to-register-watches-on-inner-ng-repeat) - Seems similar, but if I understand it correctly, the inner ng-repeat will always do the repeat and you will have all inner "for-all" outer in the DOM... – Pixic Jul 09 '13 at 09:35
  • I think I have found a solution that might work. In [control-and-directive-use-with-angularjs](http://stackoverflow.com/questions/14407572/control-and-directive-use-with-angularjs) there is a similar problem description, where wanted functionality is to slide down and the get some data in edit mode. The similarity lies in from a ng-repeat sliding down using a directive which controls using an attribute. I guess I could try doing the same but instead passing the "id" of the row and then get the corresponding JSON data passed to child scope and then to a table in the slider. Could work perhaps. – Pixic Jul 10 '13 at 13:13
  • 1
    I had a very similar problem (regarding your step 2), if I understand your problem correctly. There's a reasonable solution there that I ended up using: http://stackoverflow.com/questions/15545852/push-rows-in-a-table-rendered-with-ng-repeat-in-angular – marko Jul 15 '13 at 13:37
  • Looks like you wanted the same solution. However, doesn't the DOM get filled with a lot of data from the nested ng-repeat? (I don't want just to hide it within the DOM. Should be excluded from it since my module might be quite heavy with all types of filtering, charts and the multi-level tables. I expect a user to have up to 2k "stores" and for a "blank search filter" then for each store breaking down and be able to show quite long date interval, meaning it could be 2k (stores) * 30 rows (days) for a month date interval. Don't want it all in the DOM. Only load days (level2) when clicking. – Pixic Jul 15 '13 at 15:00
  • Yep, I ended up making a call and inserting dom when needed. I'm taking the risk that the user wont sit there and click 2k rows though :). I think I made a "detail"-directive or something like that, but I don't think that's necessary. – marko Jul 15 '13 at 15:05
  • Ok. Nice. It is not clear yet how the user uses it today, if there is no chance of user even trying to list all 2k. Guess I will have to add some pagination condition - if above 30 rows then paginate. I will have to make a directive because I need to render a table with all days for the "store", a tr for every day in a date interval. Quite complex module with a lot of dependencies. Tough for a beginner like me. :) – Pixic Jul 15 '13 at 15:17

3 Answers3

26

The requirement was to not fill the DOM with all second level tables and I came up with a solution for it.

First I tried creating a custom directive and I got it to insert a row at the right place (beneath the clicked row) and created a second level table with headers but could not get it to populate with rows using ng-repeat.

Since I could not find a solution that worked fully I went back to what I already had created, with ng-show and sought a solution like it...and there exist one. Instead of using ng-show/ng-hide, Angular has a directive called ng-switch.

In level one

<tbody data-ng-repeat="storedata in storeDataModel.storedata" 
       data-ng-switch on="dayDataCollapse[$index]">

...and then in the second level, i.e. the second tr

<tr data-ng-switch-when="true">

, in which you have the second level ng-repeat.

Here's a new JSFiddle.

Inspect elements, and you will see that there is a comment placeholder for each "collapsed" level 2 table.


UPDATE (2014-09-29)

Upon request of also expanding/collapsing level 3, here's a new JSFIDDLE.

NOTE! - This solution have all information in the DOM at all times, i.e. not as the earlier solution, which only added a notation for where the information should be added upon request.

(I can't take credit of this one though, since my friend Axel forked mine and then added the functionality.)

Pixic
  • 1,345
  • 1
  • 17
  • 29
  • @Pixic late to the party but I am trying to do exactly this in Angular 1.4.9, since that's the latest version tested with the current version of UI bootstrap but am having troubles. Do you know what would need to change to get this to work on 1.4.9? – PDN Feb 18 '16 at 22:56
  • @PDN Nice, keeping the thread alive. I forked the JSFiddle from the update, which is keeping all information in the DOM, and then just removed all externals and added the latest versions of AngularJS (1.5.0), Bootstrap (3.3.6) and UI Bootstrap (1.1.2) and it works without any code change. – Pixic Feb 22 '16 at 22:14
  • 1
    @PDN Forgot the URL to the new JSFiddle, explained in previous comment - https://jsfiddle.net/Pixic/0oufju9t/ . I also forked the version not filling the DOM with the level 2 information, and it worked too: http://jsfiddle.net/Pixic/tuy0ybnu/ – Pixic Feb 22 '16 at 22:36
  • @Pixic I'm unable to expand level two in your JSFiddle: jsfiddle.net/Pixic/tuy0ybnu – ulejon Mar 18 '16 at 14:18
  • @ulejon That jsfiddle has not implemented level two expansion. If you want the version that doesn't fill the DOM with all elements, i.e. that jsfiddle, I hope you can see what to develop. If you don't care about filling the DOM, look at the other jsfiddle in the Update (2014-09-29) section. – Pixic Mar 21 '16 at 19:53
4

I think that a better solution is to use the ng-repeat-start and ng-repeat-end directives on the tr elements rather than using the ng-repeat on the tbody (this case doesn't justify more than a single tbody).

Have a look here:

PLNKR

<tr ng-repeat-start="person in people">
  <td>
    <button ng-if="person.expanded" ng-click="person.expanded = false"></button>
    <button ng-if="!person.expanded" ng-click="person.expanded = true"></button>
  </td>
  <td>{{person.name}}</td>
  <td>{{person.gender}}</td>
</tr>
<tr ng-if="person.expanded" ng-repeat-end="">
  <td colspan="3">{{person.details}}</td>
</tr>
Alon Segal
  • 818
  • 9
  • 20
  • I found it is best to have a single TBODY since I like to set the height on the TBODY element hand scroll items without scrolling the entire table. This is not easily done when you have multiple TBODY elements. – NickV Sep 07 '18 at 14:51
0

I don't have enough reputation yet to comment, so I'm following up in an answer with an additional functionality issue I have come across. I used Pixic's structure successfully as I really like that the DOM is not polluted with the second level data/tables until they are viewed and it is all working perfectly.

Until I was asked to allow dynamic sorting of the top level table. No problem - I can sort the top level table. The problem is I have been unable to come up with a way to keep the second level table (ie hidden ) synced up and "moving" with the sort of the top level table. This is a display only issue. The underlying data is properly associated with the top level, but the display is messed up as it displays the original "indexed" row as the second level tables don't re-sort with the parent row.

The only thing I can come up with is dynamically inserting the second level html from the controller upon expanding a row, but wondering if anyone else had any other ideas. I tried ng-repeat-start and ng-repeat-end (on a hidden third tr tag to include all elements thinking that should keep all items in the tbody together, and on a hidden tbody), but that didn't work.

Edit: As requested, I have started another question: Multi-level tables (inside another if clicked) and Dynamic Sorting of Top Level Table

RobG
  • 43
  • 1
  • 5
  • 1
    This does not provide an answer to the question. You can [search for similar questions](//stackoverflow.com/search), or refer to the related and linked questions on the right-hand side of the page to find an answer. If you have a related but different question, [ask a new question](//stackoverflow.com/questions/ask), and include a link to this one to help provide context. See: [Ask questions, get answers, no distractions](//stackoverflow.com/tour) – Mogsdad Feb 14 '18 at 19:40