Search code examples
javascriptangularjsscrolltopangular-directivetableofcontents

Changing the scroll position of a div inside a div using AngularJS directives


I'm currently working an Angular app that has a fixed-height nested div that is filled with text. I want to use a table of contents on the outer div to navigate the text in the inner, nested div. Both the text and the table of contents are being dynamically loaded with ng-repeat. This is my html...

 <div class="text-nav-main">
        <ul class="nav-ul">
            <li ng-repeat="item in text.obj" >
                <span scroll-on-click href="#{{item.id}}" >{{item.title}}</span>
                <ul class="nav-ul">
                    <li ng-repeat="art in item.article">
                        <span scroll-on-click href="#{{art.id}}" >{{art.artNum}}</span>
                        <ul class="nav-ul">
                            <li  ng-repeat="sub in art.subArt">
                                <span scroll-on-click href="#{{sub.id}}" >{{sub.subArtNum}}</span>
                            </li>
                        </ul>
                    </li>
                </ul>
            </li>
        </ul>
    </div>
    <div class="text-main-window">
            <div ng-repeat="item in text.obj">
                <h3 id="{{item.id}}">{{item.title}}</h3>
                <br/>
                <div ng-repeat="art in item.article">
                    <h4 id="{{art.id}}">{{art.artNum}}</h4>
                    <br/>
                    <div ng-repeat="subArt in art.subArt">
                        <h5 id="{{subArt.id}}" >{{subArt.subArtNum}}</h5>
                        <div ng-repeat="para in subArt.paragraph">
                            <p>{{para.text}}</p>
                        </div>
                        <a ui-sref="rulebook.detail({detail:rules.ruleNumber})" 
                           class="rule-style" 
                           ng-repeat="rules in subArt.rule">
                            {{rules.ruleNumber}} {{rules.ruleName}}<br/>
                        </a>
                        <br/>
                    </div>
                </div>
            </div>
    </div>

and here's the directive that I've been trying to use ScrollTo function in AngularJS

    (function(){
'use strict';

    angular.module('ganeshaApp')
        .directive('scrollOnClick', function() {
          return {
            restrict: 'A',
            link: function(scope, element, attrs) {
              var idToScroll = attrs.href;
              element.on('click', function() {
                var $target;
                if (idToScroll) {
                  $target = $(idToScroll);
                } else {
                  $target = element;
                }
                $(".text-main-window").animate({scrollTop: $target.offset().top }, "slow");
              });
            }
              }
    });
    })();

Currently the links are bringing me all over the place within the nested div. When I click on the same link twice it brings me to two different points. If I do a console.log of $target.offset().top on the click event, it gives the correct position of each element, but when I turn on the animate function, everything goes haywire. I apologise if there's a simple solution for this that I'm missing. I have a lot to learn yet, and I just can't figure this one out. Thanks in advance for your replies.


Solution

  • Just found the answer here. Although it's not marked as an answered question, it should be. Thanks very much AaronS.

    Just needed to subtract the top position of the nested div from the top position of the element to scroll to, and then add back the scrollTop position of the div. I added 20 px to the final value just to give the text a bit of a margin.

    (function(){
    'use strict';
    
    angular.module('ganeshaApp')
        .directive('scrollOnClick', function() {
          return {
            restrict: 'A',
            link: function(scope, element, attrs) {
              var idToScroll = attrs.href;
              element.on('click', function() {
                var $target;
                if (idToScroll) {
                  $target = $(idToScroll);
                } else {
                  $target = element;
                }
    
                $(".text-main-window").animate({
                    scrollTop: $target.position().top - 
                    $(".text-main-window").position().top + 
                    $(".text-main-window").scrollTop() - 20}, "slow");
              });
            }
          }
    });
    })();
    

    Hope this is helpful for someone else.