0

I am new to AngularJS, and since I have been pursuing a solution to the problem for several days (perhaps a week), it indicates to me that I am not using it correctly.

The problem: the need to position 30 divs in the following way:

1) each of which displays a number indicating one of the 30 days of a month, in sequential, increasing order from '01' to '30' (so a strict integer won't work for display)

2) positioning on the right side of the viewport, at all times, with any device/orientation (limited to screen height > 525 px and phones in portrait orientation

3) and in such a way that the maincontroller 'knows' the exact pixel distance from the top after page load and every resize

4) such that each div is uniquely identified with an index number corresponding to a 0-based array numbering scheme

And now for some serious convolution:

1) I am loading a json file of this format using $http

   [
    {
      "dayDisplay" : "01", "isPayDay" : false, "positionX" : "0"
    },
    {
      "dayDisplay" : "02", "isPayDay" : false, "positionX" : "0"
    },
    {
      "dayDisplay" : "03", "isPayDay" : false, "positionX" : "0"
    },
    {
      "dayDisplay" : "04", "isPayDay" : false, "positionX" : "0"
    },
...

and ng-repeating through it with a 'partial' view:

<div id="days-list-control"  ng-controller="DayListController">
    <div x-day-index="{{$index}}" day-position-update distribute-days-vertically class="day-div" ng-repeat="item in allDayData">
        <span ng-class="{'pay-day':(item.isPayDay === true), 'pay-day-hidden':(item.isPayDay === false || $index === state._currentDayIndex)}">$</span>
        <span ng-class="{'hide-day':($index === state._currentDayIndex), 'day':($index > state._currentDayIndex), 'past-day':($index < state._currentDayIndex)}">{{item.dayDisplay}}</span>
    </div>
</div>

And using these 2 directives to massage the DOM and position these divs the way I want to:

layoutManagerDirectives.directive('dayPositionUpdate', ['$window', '$parse', function($window, $parse) {
    return {
      priority: 1,
      link : function(scope, element, attrs) {
          angular.element($window).bind('load', function() {
            var position =  $parse(element[0].attributes['x-daydivtop'].nodeValue)(scope);
            var elementId = $parse(element[0].attributes['x-day-index'].nodeValue)(scope);
            scope.$apply("updateDayPositions('" + position + "','" + elementId + "')");
          }); 
          angular.element($window).bind('resize', function() {
            var position =  $parse(element[0].attributes['x-daydivtop'].nodeValue)(scope);
            var elementId = $parse(element[0].attributes['x-day-index'].nodeValue)(scope);
            scope.$apply("updateDayPositions('" + position + "','" + elementId + "')");
          }); 
        }
    };
}]);

layoutManagerDirectives.directive('distributeDaysVertically', ['$window', function($window) {
  return {

    priority: 0,
    link : function(scope, element, attr) {

      angular.element($window).bind('resize', function(e) {
        var containerElement = angular.element(document.getElementById('days-list-container'));
        var dayDivTop = element[0].offsetTop;
        if ($window.innerHeight < 645) {
          element.css('line-height', ($window.innerHeight)/42  + 'px');
          element.attr('x-dayDivTop', dayDivTop );
        } else {
          element.css('line-height', ($window.innerHeight)/42  + 'px');
          element.attr('x-dayDivTop', dayDivTop );

        }
      });
      angular.element(window).bind('load', function() {
        var containerElement = angular.element(document.getElementById('days-list-container'));
        var dayDivTop = element[0].offsetTop;   
        if ($window.innerHeight < 645) {
          element.css('line-height', ($window.innerHeight)/42  + 'px');
          element.attr('x-dayDivTop', dayDivTop );

        } else {
          element.css('line-height', ($window.innerHeight)/42  + 'px');
          element.attr('x-dayDivTop', dayDivTop );
        }  
      }); 
    }
  }
}]);

and the function in MainController invoked in the directive above is:

 $scope.updateDayPositions = function(p, id) {
    $scope.allDayData[id].positionX = p;
  }

which simply updates the json-inspired object ("allDayData" which lives in the MainController) with the positioning info from the DOM attributes of all the divs ng-repeated thru (see the partial view: "ng-repeat='item in allDayData'"). This is so I can position yet another graphic element very precisely over each of the day numbers, programmatically, based on user input.

This works perfectly after I re-size the browser window. On load, I believe that the DOM elements aren't on screen and in a knowable position, since the allDayData object is not updated from its original positionX values of '0'. I am getting sporadic and odd behaviors after refreshing the page but most commonly, I see that the positionX data is not updated until I resize the browser window, after which point it works quite well. I have tried the following ways to invoke the directive:

1)
  angular.element(window).bind('load', function() {
     ...
  });

2)
angular.element(document).ready(function() {
     ...
  });
3)
$timeout
4)
putting the code into a controller which in theory executes when loaded

Phew. I may have left something out, but hopefully there's enough here to say generally which direction I should go in if indeed a different one is optimal. Apologies for the astonishingly long question, and thanks in advance for any guidance.

1 Answers1

1

First, there's a lot of weird things going on here. You should definitely never try to use document.getElementById... ever, but especially not in an angular directive. You should also shy away from using things like $window and document unless it's really that necessary. I think that resizing is probably a good enough case.

Please read this:

"Thinking in AngularJS" if I have a jQuery background?

Hopefully that helps shift your paradigm a little bit. Now onto the root of your problem. The load event is only triggered when you load things like an iframe or image, so you don't need to listen to that event. As a matter of fact, you can assume the DOM is ready by the time the link function is called in the directive, because angular is cool like that (it has already compiled the html). So, to keep things DRY you need to move your resize functions into their own variable, bind them to the resize event, and call it once from the link function like so

// Inside directive
var myResizeFunction = function(e) {
  // Handle window resize
};

element.resize(myResizeFunction);
myResizeFunction();

On a side note, a lot of what you're doing looks at a glance like it might be better solved by CSS. There's a lot of cool stuff you can do with responsive designs these days. Maybe you should consider that as an option?

Community
  • 1
  • 1
Mike Quinlan
  • 2,873
  • 17
  • 25
  • Weird things there are many, indeed. I'll go back to the drawing board and see if I can use CSS. Problem is, if you want a UI to essentially fill the screen from top to bottom, and stay on the right edge of the viewport at every resize (no scrolling H or V at anytime) then you need absolute positioning with respect to the viewport dimensions. Not saying I am doing this even close to the best way possible, but I tried lots of CSS/bootstrap responsiveness and they get you close, but not close enough, quite often. Thanks for your time and thought in reading my long post! Best, Brian – user3561344 May 14 '14 at 02:02