2

I am changing a user list/table I made with Razor and JQuery into AngularJS. One of the features of this table was the ability to show/hide additional user details. The table had two sets of tr with the second one being display:none but would slide down/be visible when its above tr was clicked to show details.

Example (Original) Working Example

enter image description here

I tried to apply the code onto my AngularJS but it does not seem to work for some reason (no errors either)

Original Code (skeleton)

<table>
   <tr class="col-xs-12">
            <td class="col-xs-2">@Html.DisplayFor(modelItem => item.name)</td>
            <td class="col-xs-2">@Html.DisplayFor(modelItem => item.email)</td>
            // ect...
   </tr>
   <tr class="col-xs-12" style="display:none">
      <td colspan="12">
         <p>
            @Html.DisplayFor(other stuff)
         </p>
      </td>
   </tr>
</table>

New AngularJS Code

<table class="table table-striped col-xs-12">
    <tbody ng-repeat="actors in Users">
        <tr class="col-xs-12">
            <td class="col-xs-2">{{actors.name}}</td>
            <td class="col-xs-2">{{actors.email}}</td>
            // ect...
        </tr>
        <tr class="col-xs-12" style="display:none">
            <td colspan="6">
                <p>
                    {{actors.otherThings}}
                </p>
            </td>
        </tr>
    </tbody>
</table>

JQuery (Original had td[colspan=12] instead of td[colspan=6])

<script>
    // Toggle Additional Details
    $(function () {
        $("td[colspan=6]").find("p").hide();
        $("td[colspan=6]").addClass("nopadding");

        // && !$(e.target).is('span')
        $("tr").click(function (e) {
            if (!$(e.target).is('button') && !$(e.target).is('input') && !$(e.target).is('select')) {
                var $target = $(this);
                var $detailsTd = $target.find("td[colspan=6]");
                if ($detailsTd.length) {
                    $detailsTd.find("p").slideUp();
                    $detailsTd.addClass("nopadding");
                } else {
                    $detailsTd = $target.next().find("td[colspan=6]");
                    $detailsTd.find("p").slideToggle();
                    $detailsTd.toggleClass("nopadding");
                }
            }
        });
    });
</script>

CSS

/* Removes padding from interactive rows */
.table > tbody > tr > td.nopadding {
    padding: 0px;
}

I am still new to AngularJS so maybe I am missing something simple, but I just want to be able to expand show/hide the additional tr. Not sure why it does not work for my Angular code.

AngularJS

var app = angular.module("app", ['ngRoute', 'ngResource']);

app.config(function ($routeProvider) {
    $routeProvider

    .when('/', {
        templateUrl: '/pages/home.html',
        controller: 'HomeController'
    })

    .when('/home', {
        templateUrl: '/pages/home.html',
        controller: 'HomeController'
    })

    .when('/unknown', {
        templateUrl: '/pages/unknown.html',
        controller: 'UnknownController'
    })
      .otherwise({
          templateUrl: '/pages/home.html',
          controller: 'HomeController'
      });
});


app.factory('userService', function ($http) {
    var userService = {};
    userService.getUsers = function () {
        return $http({
            url: '/API/APITest',
            method: "GET"
        })
    }
    return userService;
});


app.controller('HomeController', function ($scope, userService, $resource, $http) {
    $scope.orderByField = 'name';
    $scope.reverseSort = false;
    $scope.ordering = "(ascending)";

    $scope.orderByFieldFunction = function (value) {
        $scope.reverseSort = !$scope.reverseSort;
        $scope.orderByField = value;
        if ($scope.reverseSort === false) {
            $scope.ordering = "(ascending)";
        } else {
            $scope.ordering = "(descending)";
        }
    }
    userService.getUsers().success(function (users) {
        $scope.Users = users;
    });
});

app.controller('UnknownController', function ($scope, userService, $resource, $http) {
    $scope.title = "404 - Does Not Exist";
});
Austin
  • 3,010
  • 23
  • 62
  • 97

2 Answers2

1

You can add the jQuery in a directive like this, if this is what you're after:

Example: http://jsfiddle.net/58mLfw6z/

AngularJS Directive

app.directive('showmore',function() {
    return {
        restrict:'A',
        link: function(scope,element,attr) {
            element.on('click',function() {
              element.next('tr').toggle();
            });    
        }
    }
});

// or apply a flag to the model with an ng-click on the tr
$scope.more = function(actor) {
    if (!actor.more) {
        actor.more = true;
    }
    else {
        actor.more = false;
    }
};

HTML

<table>
    <tbody ng-repeat="actors in Users">
        <tr showmore ng-click="more(actors)">
            <td class="col-xs-2">{{actors.name}}</td>
            <td class="col-xs-2">{{actors.email}}</td>
        </tr>
        <tr style="display:none;">
            <td colspan="2">
                <p>
                    {{actors.otherThings}}
                </p>
            </td>
        </tr>
    </tbody>
</table>
Rob
  • 1,840
  • 2
  • 12
  • 19
  • Preferably I do not want a button or anything. With the other code I could click the `tr` above, or the details `tr` itself to expand/close. See example **[HERE](http://aawebapps.com/Users)**. Clicking the `tr` triggers it all. Is this possible with Angular? – Austin Aug 14 '14 at 14:31
  • Updated my answer, however, if you're still having issues understanding this, them you might need to revisit jQuery traversing and how AngularJS directives work. – Rob Aug 14 '14 at 14:36
  • Gotcha, I am new to Angular though and I figured there'd be a cleaner approach than having to use a directive, I've been told that I shouldn't be touching those while starting new and that something like `ng-click` or `ng-class` should suffice. Thoughts? – Austin Aug 14 '14 at 14:38
  • Careful, `toggle` is not supported by Angular's jqLite implementation you actually need to load jQuery before Angular to get this ability. – m.e.conroy Aug 14 '14 at 14:52
  • 1
    @Austin yes there is a way to do this via `ng-click` by passing the element clicked to a function in the controller that would then execute the same code this directive does, **but its not a good idea to do DOM manipulation in a controller, its suggested that DOM manipulation be done in a directive.** Whomever told you not to use directives is completely wrong, directives are the true power of Angular. – m.e.conroy Aug 14 '14 at 14:56
  • @m.e.conroy Actually I just found a very simply way to do this, please inform me if this is bad coding. **first tr** `ng-click="showDetails = ! showDetails"` **second tr** `ng-show="showDetails"`. Thats it. – Austin Aug 14 '14 at 14:57
  • Austin is correct in the fact that dom manipulation should not be done outside of directives, however, you could add a flag to the model to toggle the other things. Updated the answer. Example: http://jsfiddle.net/58mLfw6z/ – Rob Aug 14 '14 at 15:01
  • @Austin yes that works but then you need to set different flags for every row you want to open else clicking one row would open all. The directive is a more appropriate fix, as you won't need to create X amount of scope variables where X is Y/2 table rows. – m.e.conroy Aug 14 '14 at 16:35
  • @m.e.conroy nope, it binds all separately :) **[JSFiddle Demo](http://jsfiddle.net/aanders50/HB7LU/5497/)** – Austin Aug 14 '14 at 17:28
  • @Austin yes it works but you'll find that the more users you put in the table the slower everything will run as `ng-repeat` will be creating a $$watcher for every `ng-click`, `ng-show` and scoped variable. Rob's solution uses jQuery's `.on()` method inside an Angular directive, jQuery functions won't create $$watchers and thus won't slow down Angular's $digest cycle. Therefor the only $$watcher you'll need to worry about is the ones created directly on the `ng-repeat` directive and not all the subsequent $$watcher created internally with each repeated template. – m.e.conroy Aug 14 '14 at 19:01
  • I agree with m.e.conroy. Directives are made for things like this. No need to pollute the model with flags. – Rob Aug 14 '14 at 19:12
  • Question for both of you (just informational) would something like pagination help prevent my current implementation, or would that not matter/effect this issue. I will most likely use this answer since it makes sense why mine is bad, just curious about pagination begin involved with it. – Austin Aug 15 '14 at 13:57
  • Pagination in my opinion is a preference left to the developer. But it depends on the list. If it's a lot of records, than pagination might be the best option, however, I hate sifting through paginated information. It's best to implement a filter or search mechanism to whittle down the list with the information the user is looking for. Also, scrolling is not all too evil either. – Rob Aug 15 '14 at 14:28
0

I think OP already has found the answer. This post is just to say that ng-repeat has its own scope, an isolated scope.

In this plnkr, http://jsfiddle.net/aanders50/HB7LU/5497/, which OP posted in the comment

<table>
    <tr>
        <th>Name</th>
        <th>Email</th>
    </tr>
    <tbody ng-repeat="actors in Users">            
        <tr ng-click="showDetails = ! showDetails">
            <td>{{actors.name}}</td>
            <td>{{actors.email}}</td>
        </tr>
        <tr ng-show="showDetails" style="background-color:rgba(0, 255, 255, 0.2)">
            <td colspan="2">
                <p>birthday: {{actors.birthday}}</p>
            </td>
        </tr>
    </tbody>
</table>

showDetails are bound to each tbody where ng-repeat is defined. Thus, each table has its own $scope.showDetails

allenhwkim
  • 27,270
  • 18
  • 89
  • 122
  • That was gonna be my next question as to why my answer worked. Thanks for the input :) – Austin Aug 14 '14 at 18:11
  • My point is that you just created a crazy amount of $$watchers and scoped variables for each item in your Users array, when one directive would've accomplished the same feat while not creating any $$watchers to check during the $digest cycle because it uses jQuery's `.on()`. Read the **More DOM manipulation in Directives** section of this article: http://www.binpress.com/tutorial/speeding-up-angular-js-with-simple-optimizations/135?utm_source=ng-newsletter&utm_campaign=9798e8abe3-AngularJS_Newsletter_8_12_148_9_2014&utm_medium=email&utm_term=0_fa61364f13-9798e8abe3-96616777 – m.e.conroy Aug 14 '14 at 18:54
  • @Rob 's directive above in my opinion is still the right way to go. Its small more readable and doesn't add to Angular bottlenecks in the $digest cycle. – m.e.conroy Aug 14 '14 at 18:57
  • I see, maybe I should use that then. Would something like pagination help reduce that issue or will it still bind everything on each page ahead of time? – Austin Aug 14 '14 at 19:17