Saturday, November 5, 2016

ng-class/ng-click, Array.isArray, and bolting your own properties onto API data for convenience sake

I've used ng-class and ng-click to make an expandable/collapsible menu in an AngularJS 1.5.8 application. Specifically, I have beefed up the HTML in the app I suggest here like so:

<div ng-app="myApp" ng-controller="myCtrl">
   <div ng-repeat="books in booksWrapper">
      <div class="outerTemplateWrapper" ng-repeat="book in books">
         {{ book.name }}
         <div class="middleTemplateWrapper">
            <div class="innerTemplateWrapper">
               <div class="innerTemplateWrapperLeft">{{ book.isbn }}</div>
               <div class="innerTemplateWrapperRight">
                     {{ book.numberOfPages|sOnTheEnd:"page" }}</div>
               <div class="innerTemplateWrapperBase">
                  <div class="plusOrMinus" ng-class="book.toggleState ? 'plus' : 'minus'"
                        ng-click="characterCollector(book)"></div>
                  <div class="characters">characters</div>
                  <div class="secondInnerTemplateWrapperBase">
                     <ol ng-class="book.toggleState ? 'displayNone' : 'displayBlock'">
                        <li ng-repeat="hydratedCharacters in book.hydratedCharactersWrapper">
                           <span ng-repeat="hydratedCharacter in hydratedCharacters">
                              {{ hydratedCharacter.name }}
                           </span>
                        </li>
                     </ol>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>
</div>

 
 

My controller has changed up some like so...

app.controller("myCtrl", function ($scope, apiGetter) {
   var currentUrl = "http://www.anapioficeandfire.com/api/books";
   $scope.booksWrapper = apiGetter.goGetIt(currentUrl).$$state;
   $scope.characterCollector = function (book) {
      book.toggleState = !book.toggleState;
      if (!book.hasHydratedCharacters) {
         book.hasHydratedCharacters = true;
         var counter = 0;
         book.characters.forEach(function (characterUrl) {
            if (counter < 10) {
               var character = apiGetter.goGetIt(characterUrl).$$state;
               book.hydratedCharactersWrapper.push(character);
            }
            counter++;
         });
      }
   };
});

 
 

So it's still getting data upfront for a list of Game of Thrones books and now it is also fishing for new data. The API doesn't include characters for the books with the books, instead it has a list of URLs to hit to make API calls for each character and... well... let's get the first ten characters for each book. The service I made for the API calls is going to change too:

app.service("apiGetter", function ($http) {
   
   return ({
      goGetIt: goGetIt
   });
   
   function goGetIt(whereToLookTo) {
      var request = $http({
         method: "get",
         url: whereToLookTo
      });
      return (request.then(handleSuccess, handleError));
   }
   
   function handleError(response) {
      alert('something went wrong');
   }
   
   function handleSuccess(response) {
      if (!Array.isArray(response.data)) {
         return (response.data);
      } else {
         var dataPlus = new Array();
         response.data.forEach(function (book) {
            book.toggleState = true;
            book.hasHydratedCharacters = false;
            book.hydratedCharactersWrapper = new Array();
            dataPlus.push(book);
         });
         return (dataPlus);
      }
   }
   
});

 
 

Basically, for the array of books that comes back upfront I want to bolt on my own properties per book, but for the characters themselves, I just want to hand them back as is. The three properties I bolt onto each book are an empty array for eventually later spooling up the characters in and two flags for if that array has been hydrated yet (we don't want to do it over and over) and if an expandable/collapsible menu for showing the array's contents is sitting open. Some CSS is driven by the later flag. It looks like this:

.plus:before {
   content: "+";
}
.minus:before {
   content: "-";
}
.displayNone {
   display: none;
}
.displayBlock {
   display: block;
}

 
 

Every div with the plusOrMinus class on it, containing cursor: pointer; as a style by the way, is now a button! Clicking the button will change a plus sign to a minus sign or a minus sign to a plus sign based upon the flag for this state. It will also hide or unhide an ordered list in another div which has the list of characters!

No comments:

Post a Comment