1

Okay. I'm pulling together a data table that is going to look through majors and minors of a school. I'm running into issues of trying not to repeat myself in the data where every possible, but am not sure how to get the data pulled into the document, or even how to setup the data into the different arrays. Looking for some advice and help in whichever of these two areas I can find. When I search through docs and API's none of them seem to go deep enough into the data to really get what I'm looking to accomplish.

I have made a plunker to showcase my problem more clearly, or at least I hope to make it clearer.
http://plnkr.co/edit/2pDmQKKwjO6KVullgMm5?p=preview

EDIT:

It would even be okay with me if the degree each degree could be read as a boolean, and same with Education level. I'm just not sure how to go about it without repeating the whole line in a new table row. http://www.clemson.edu/majors


HERE IS THE HTML

<body ng-app="app">
    <h2> Majors and Minors </h2>
    <table ng-controller="MajorsCtrl">
      <tbody>
        <tr>
          <th>Department</th>
          <th>Major</th>
          <th>Education Level</th>
          <th>Location </th>
          <th>Degree</th>
          <th>Department Website </th>
        </tr>
        <tr ng-repeat="major in majors">
          <td>{{major.Department}}</td>
          <td>{{major.Major}}</td>
          <td>{{major.EdLevel}}</td>
          <td>{{major.Type}}</td>
          <td>{{major.Degree}}</td>
          <td>{{major.Website}}</td>
        </tr>
      </tbody>
    </table>
  </body>

HERE IS THE JS

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

// Majors and Minors Data That will be injected into Tables 
app.controller('MajorsCtrl', function($scope) {
   // Heres where it gets tricky 
        // Now I have biology with four diff degree types
        // Biology with 2 diff EdLevels
        // How do I combine all of these into 1 Group without repeating

    var majorsInfo =  [
       { 
            Department:'Statistics', 
            Major:'Applied Statitistics', 
            EdLevel:'Graduate', 
            Type:'Campus/Online',
            Degree:'Graduate Certificate',
            Website: 'http://biology.wvu.edu',
        }, 
        { 
            Department:'Biology', 
            Major:'Biology', 
            EdLevel:'Graduate', 
            Type:'Campus',
            Degree:'PH.D' ,
            Website: 'http://biology.wvu.edu',
        }, 
         { 
            Department:'Biology', 
            Major:'Biology', 
            EdLevel:'Graduate', 
            Type:'Campus',
            Degree:'M.S' ,
            Website: 'http://biology.wvu.edu',
        }, 
         { 
            Department:'Biology', 
            Major:'Biology', 
            EdLevel:'Undergraduate', 
            Type:'Campus',
            Degree:'B.A.' ,
            Website: 'http://biology.wvu.edu',
        }, 
         { 
            Department:'Biology', 
            Major:'Biology', 
            EdLevel:'Undergraduate', 
            Type:'Campus',
            Degree:'B.S.' ,
            Website: 'http://biology.wvu.edu',
        }, 
      ]; 


    $scope.majors = majorsInfo; 
});
David J. Davis
  • 896
  • 7
  • 20

1 Answers1

1

This seems to be a question about modeling the data. I took a few assumptions:

  • A department can offer multiple majors
  • A department has a website
  • Each major can offer one to many Educations (i.e. Education Level, On/Off Campus, Degree)
  • The department can offer multiple minors with the same data structure as majors

I modeled a set of "enums" and Programs/Departments after your data. I used enums for ease of updating the values in multiple locations:

app.factory("EducationEnums", function () {
  var EdLevels = {
    GRAD: "Graduate",
    UGRAD: "Undergraduate"
  };
  var Types = {
    CAMPUS: "Campus",
    ONLINE: "Online",
    HYBRID: "Campus/Online"
  };
  var Degrees = {
    PHD: "PH.D",
    MS: "M.S.",
    BS: "B.S.",
    BA: "B.A.",
    GCERT: "Graduate Certificate"
  };

  return {
    EdLevels: EdLevels,
    Types: Types,
    Degrees: Degrees
  }
});

app.factory("Programs", function (EducationEnums) {
  var EdLevels = EducationEnums.EdLevels;
  var Types = EducationEnums.Types;
  var Degrees = EducationEnums.Degrees;

  return [
    {
      Department: "Biology",
      Website: "http://biology.wvu.edu",
      //Majors offered by department
      Majors: [{
        Major: "Biology",
        Education: [
          {
            EdLevel: EdLevels.GRAD,
            Type: Types.CAMPUS,
            Degree: Degrees.PHD
          },
          {
            EdLevel: EdLevels.GRAD,
            Type: Types.CAMPUS,
            Degree: Degrees.MS
          },
          {
            EdLevel: EdLevels.UGRAD,
            Type: Types.CAMPUS,
            Degree: Degrees.BA
          },
          {
            EdLevel: EdLevels.UGRAD,
            Type: Types.CAMPUS,
            Degree: Degrees.BS
          }
        ]
      }],
      Minors: [{
        //Minors can go here
      }]
    },
    {
      Department: "Statistics",
      Website: "http://biology.wvu.edu",
      Majors: [{
        Major: "Applied Statistics",
        Education: [
          {
            EdLevel: EdLevels.GRAD,
            Type: Types.HYBRID,
            Degree: Degrees.GCERT
          },
          {
            EdLevel: EdLevels.UGRAD,
            Type: Types.CAMPUS,
            Degree: Degrees.BS
          }
        ]
      }],
      Minors: [{
        //Minors can go here
      }]
    }
  ]
});

Next, I made a Majors service that uses this Programs data to build ViewModels (to be bound to scope in the controllers). Here you can build your original table, or you can build a matrix (like the Clemson site):

app.service("Majors", function (Programs, EducationEnums) {
  var Degrees = this.Degrees = EducationEnums.Degrees;

  //Build ViewModel for all details
  this.getMajorDetails = function () {
    var arr = [];
    var programLen;
    var majorLen;
    var eduLen;

    for (var i = 0; i < (programLen = Programs.length); ++i) {
      var p = Programs[i];
      var dept = p.Department;
      var ws = p.Website;
      var Majors = p.Majors;

      for (var j = 0 ; j < (majorLen = Majors.length); ++j) {
        var maj = Majors[j].Major;
        var edu = Majors[j].Education;

        for (var k = 0; k < (eduLen = edu.length); ++k) {
          arr.push({
            Department: dept,
            Major: maj,
            EdLevel: edu[k].EdLevel,
            Type: edu[k].Type,
            Degree: edu[k].Degree,
            Website: ws
          });
        }
      }
    }

    return arr;
  }

  //Build ViewModel for Degrees offered (similar to Clemson)
  this.getMajorMatrix = function () {
    var arr = [];
    var programLen;
    var majorLen;
    var eduLen;

    for (var i = 0; i < (programLen = Programs.length); ++i) {
      var p = Programs[i];
      var Majors = p.Majors;

      for (var j = 0; j < (majorLen = Majors.length); ++j) {
        var maj = Majors[j].Major;
        var edu = Majors[j].Education;
        var obj = {
          Major: maj
        };

        for (var k = 0; k < (eduLen = edu.length); ++k) {
          var degree = edu[k].Degree;
          if (degree === Degrees.PHD) {
            obj.PHD = true;
          }
          else if (degree === Degrees.MS) {
            obj.MS = true;
          }
          else if (degree === Degrees.BS) {
            obj.BS = true;
          }
          else if (degree === Degrees.BA) {
            obj.BA = true;
          }
        }

        arr.push(obj);
      }
    }

    return arr;
  }
});

Your controller can just call the Majors service methods to bind the ViewModel to the $scope:

app.controller('MajorsCtrl', function($scope, Majors) {
    $scope.majorDetails = Majors.getMajorDetails(); 
});

app.controller("MajorMatrixCtrl", function ($scope, Majors) {
  $scope.Degrees = Majors.Degrees;
  $scope.majorMatrix = Majors.getMajorMatrix();
});

Separting like this would allow you to later update the Programs factory to not just use static data, but could pull from a service via $http for instance. The Programs data can be manipulated to achieve your desired ViewModel through the Majors service (and Minors service if you choose to write a separate one).

Updated Plunkr

Patrick
  • 6,518
  • 3
  • 19
  • 31
  • I'll look into this solution. Thanks very much for your help. I'm not familiar with services or factories that much right now, but you had an excellent explanation of each. After I do a quick implementation of this while reading over some docs on the angular site I'll give it the check mark. – David J. Davis Dec 30 '14 at 18:18
  • Just curious how this is technically working, both factories are simply returning data to the service, so the controller is simply used for view purposes? – David J. Davis Dec 30 '14 at 18:27
  • 1
    Factory returns data to be used by service (factory hard coded programs for now, could switch to using $http as long as the data structure remains the same for the Majors service). The Majors service exposes methods to transform this data to a ViewModel to be used by the controller. The controller's logic remains simple (as it should) and sets ViewModel returned from service to the $scope to be used within the view/template. – Patrick Dec 30 '14 at 18:35
  • Excellent. Thank you. I'm new to the angular way of thinking, but it seems pretty awesome. – David J. Davis Dec 30 '14 at 18:46
  • Is there a way to do a hybrid approach, where I display the website in an href. I've been deconstructing ever since you answered and still can't find a good way. I'm sure that it has something to do with looping through each of the items and stuffing them into the appropriate arrays. Would I have to do another array and loop through the contents of each website group? – David J. Davis Dec 30 '14 at 20:44
  • I'm not entirely following what you are asking for. Model the data however it makes sense for you. Whether that means that the website is part of a Department/Program, or if that needs to be set at a more "global" level. If you show an updated Plunkr/JSFiddle of what you are trying to accomplish, I can try to point you in the right direction. – Patrick Dec 30 '14 at 21:00
  • Here is an updated Plunkr. Basically I'm going to add a hyperlink to the website on the progam name. Then I want to be able to look at if its an undergraduate degree, or graduate degree. Then I want to be able to look at other data that may be added for filtering. Such as graduate and undergraduate, maybe later down the line, sort by discipline. Sciences, humanities, etc. http://plnkr.co/edit/lCjoOUCt8Hl0nvMSWXN2?p=preview – David J. Davis Dec 31 '14 at 02:52
  • Added some basic filtering and sorting functionality for demo. Adjust to fit your needs: http://plnkr.co/edit/dlda4QsIQElfA4DSNKQZ?p=preview – Patrick Dec 31 '14 at 14:48
  • @Partrick Thanks you have been extremely helpful. Sorry for all the questions, but I'm trying to learn what you did, not just use your work. – David J. Davis Dec 31 '14 at 14:49