Sunday, November 20, 2011

bridge the seam between C# and jQuery

I have been tasked with fixing a bug. The fix will involve handing data between C# and jQuery. Let me show you what I have so far. (I'm not done. I'm just getting started.) The bug happens on a CRUD screens for an object that is referred to as SiliconSampleDefinition at our application in code and is also referred to as simply "Silicon" in conversation as an instance of this object represents a piece of silicon.

Shuffle Lists:
We have created a control called a Shuffle List. It takes the shape of two select list controls capable of multiple selections sitting side by side. Buttons allow a user to move selected items from the list on the left to the list at the right and vice versa. The stuff on the left is stuff that may potentially be deemed as selected and the stuff on the right is stuff that actually is selected. I'm sure you've seen this sort of thing before.
Addendum 8/20/2015: I am commenting out http://a.yfrog.com/img610/9010/0ayd.jpg which yfrog has seen fit to replace with some sort of iTunes advertisement. I wish I had not started up my blog hosting images with these clowns.

...least I digress, back to silicon...

Silicon Relationships To Other Objects:
Addendum 8/20/2015: I am commenting out http://a.yfrog.com/img863/4500/qtfo.jpg which yfrog has seen fit to replace with some sort of iTunes advertisement. I wish I had not started up my blog hosting images with these clowns.
  1. Every piece of silicon is of one SampleType. There is a distinction between silicon for a CPU (Central Processing Unit) and a GPU (Graphics Processing Unit) for example and the SampleType records the distinction. At our create and edit screens for silicon, SampleType is represented by a drop down list which has an id of: SelectedSampleTypeId

     
  2. Pieces of silicon may be associated to any number of Programs. Programs are represented by a shuffle list. The right side of the shuffle list is a multiselect selection box with an id of: programsGroup-selection

     
  3. Finally, pieces of silicon may be associated to any number of ProgramPlans. Each ProgramPlan has one Program but each Program may have any number of ProgramPlans, a one-to-many relationship. ProgramPlans have a many-to-many relationship with SampleTypes which is to say that if a ProgramPlan is not of every SampleType then its scope is inherently restricted to a subset of the SampleTypes within our app. ProgramPlans also takes take shape in a shuffle list. In terms of id tags, let's say that the left half of the shuffle list is called "pp" and the right half is called "pps" for simplicity sake.
    Addendum 8/20/2015: I am commenting out http://a.yfrog.com/img610/9010/0ayd.jpg which yfrog has seen fit to replace with some sort of iTunes advertisement. I wish I had not started up my blog hosting images with these clowns.
     
The Problem:
The left side of the shuffle list starts with a list of a permissioned (making my own word here) ProgramPlans. The control allows one to select ProgramPlans which are not necessarily of the Program selected or the SampleType selected. No good. The values of Program and SampleType need to bias what is in both halves of the shuffle list for ProgramPlans. The values can change without reposting the page and thus the halves of the shuffle list for ProgramPlans are also going to have to change without reposting the page.

Before I began to tackle the problem, more ProgramPlans than were really needed, all ProgramPlans possibly applicable, were begin queried and made their way up to the UI for selection. If the list of ProgramPlans is going to be restrained then, arguably, we shouldn't query everything upfront. That said, I am going to punt on this problem in my first pass. Instead, I will make use of the current repository call of ProgramPlans. I will let everything make its way up to the UI, I will just change the way it comes up to the UI. I'm going to make things better incrementally. Later, if we need to use JSON/AJAX calls to get a subset of presently applicable ProgramPlans upon the change of Programs or Sample Types... well, that is a fight for another day.

I need to hand data to jQuery from C# which jQuery can somehow make sense of to determine what the possibilities for ProgramPlan are given a particular combination of Program and SampleType values. Again, I'm handing in all possible values for ProgramPlan. I end up creating an object that contains, as getsetters, two collections flattened into a pipe-delimited format. The values of the getsetters will bubble up into the UI where jQuery may consume them.

using System.Collections.Generic;

using System.Linq;

using OurApp.Core.Entities;

namespace OurApp.Web.UI.Util

{

   public class ProgramPlanShuffleListFiltrationHelper

   {

      public string FlattenedListOfListsOfProgramPlansForPrograms { get; private set; }

      public string FlattenedListOfListsOfProgramPlansForSampleTypes { get; private set; }

      private Dictionary<Program, List<ProgramPlan>> programPlansForPrograms {

            get; set; }

      private Dictionary<SampleType, List<ProgramPlan>> programPlansForSampleTypes {

            get; set; }

   

      public ProgramPlanShuffleListFiltrationHelper(List<Program> Programs,

            List<ProgramPlan> ProgramPlans, List<SampleType> SampleTypes)

      {

         programPlansForPrograms = Programs.ToDictionary(program => program,

               program => new List<ProgramPlan> { });

         programPlansForSampleTypes = SampleTypes.ToDictionary(sampleType =>

               sampleType, sampleType => new List<ProgramPlan> { });

         PopulateProgramPlansForPrograms(ProgramPlans);

         PopulateProgramPlansForSampleTypes(ProgramPlans, SampleTypes);

         FlattenProgramPlansForPrograms(Programs);

         FlattenProgramPlansForSampleTypes(SampleTypes);

      }

   

      private void PopulateProgramPlansForPrograms(List<ProgramPlan> ProgramPlans)

      {

         foreach (ProgramPlan programPlan in ProgramPlans)

         {

            List<ProgramPlan> dummyList =

                  programPlansForPrograms[programPlan.Program];

            dummyList.Add(programPlan);

            programPlansForPrograms[programPlan.Program] = dummyList;

         }

      }

   

      private void PopulateProgramPlansForSampleTypes(List<ProgramPlan>

            ProgramPlans, List<SampleType> SampleTypes)

      {

         foreach (ProgramPlan programPlan in ProgramPlans)

         {

            foreach (SampleType sampleType in programPlan.SampleTypes)

            {

               if (SampleTypes.Count > 0 && SampleTypes.Contains(sampleType))

               {

                  List<ProgramPlan> dummyList =

                        programPlansForSampleTypes[sampleType];

                  dummyList.Add(programPlan);

                  programPlansForSampleTypes[sampleType] = dummyList;

               }

            }

         }

      }

   

      private void FlattenProgramPlansForPrograms(List<Program> Programs)

      {

         foreach (Program program in Programs)

         {

            List<ProgramPlan> dummyList = programPlansForPrograms[program];

            foreach (ProgramPlan programPlan in dummyList)

            {

               if (FlattenedListOfListsOfProgramPlansForPrograms == null)

               {

                  FlattenedListOfListsOfProgramPlansForPrograms = "";

               } else {

                  FlattenedListOfListsOfProgramPlansForPrograms += "|";

               }

               FlattenedListOfListsOfProgramPlansForPrograms += program.Name + ".";

               FlattenedListOfListsOfProgramPlansForPrograms +=

                     programPlan.NameStaticAttribute

                     .GetSelectedValueWithDefault(programPlan.Id).Value;

               FlattenedListOfListsOfProgramPlansForPrograms += "," + programPlan.Id;

            }

         }

      }

   

      private void FlattenProgramPlansForSampleTypes(List<SampleType> SampleTypes)

      {

         foreach (SampleType sampleType in SampleTypes)

         {

            List<ProgramPlan> dummyList = programPlansForSampleTypes[sampleType];

            foreach (ProgramPlan programPlan in dummyList)

            {

               if (FlattenedListOfListsOfProgramPlansForSampleTypes == null)

               {

                  FlattenedListOfListsOfProgramPlansForSampleTypes = "";

               } else {

                  FlattenedListOfListsOfProgramPlansForSampleTypes += "|";

               }

               FlattenedListOfListsOfProgramPlansForSampleTypes +=

                     programPlan.Program.Name + ".";

               FlattenedListOfListsOfProgramPlansForSampleTypes +=

                     programPlan.NameStaticAttribute

                     .GetSelectedValueWithDefault(programPlan.Id).Value;

               FlattenedListOfListsOfProgramPlansForSampleTypes += "," +

                     sampleType.Category.Name;

               FlattenedListOfListsOfProgramPlansForSampleTypes += "." +

                     sampleType.Name;

            }

         }

      }

   }

}

 
 

I get the above under test like so:

using System.Collections.Generic;

using OurApp.Core.Entities;

using OurApp.Data.Tests.Support;

using OurApp.Web.UI.Util;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace OurApp.Data.Tests.Utils

{

   [TestClass]

   public class ProgramPlanShuffleListFiltrationHelperTest : DataIntegrationTestBase

   {

      [TestMethod]

      public void test_facets_of_ProgramPlanShuffleListFiltrationHelperTest()

      {

         Category silicon = new Category {Name = "Silicon", IdPrefix = "S" };

         silicon.Save();

         SampleType cpu = new SampleType { Name = "CPU", IdPrefix="CPU", Category =

               silicon };

         cpu.Save();

         SampleType gpu = new SampleType { Name = "GPU", IdPrefix = "GPU", Category

               = silicon };

         gpu.Save();

         SampleType chipset = new SampleType { Name = "Chipset", IdPrefix = "Chipset",

               Category = silicon };

         chipset.Save();

   

         ProgramPlan alpha = new ProgramPlan { CanOrder = true, SampleTypes = new

               List<SampleType>{ cpu, gpu, chipset } };

         alpha.Save();

         alpha.NameStaticAttribute.CreateAndAddAvailableValue("Alpha")

               .SelectValueForEntity(alpha.Id);

         alpha.Save();

   

         ProgramPlan beta = new ProgramPlan { CanOrder = true, SampleTypes = new

               List<SampleType> { cpu } };

         beta.Save();

         beta.NameStaticAttribute.CreateAndAddAvailableValue("Beta")

               .SelectValueForEntity(beta.Id);

         beta.Save();

   

         ProgramPlan gamma = new ProgramPlan { CanOrder = true, SampleTypes = new

               List<SampleType> { cpu, gpu } };

         gamma.Save();

         gamma.NameStaticAttribute.CreateAndAddAvailableValue("Gamma")

               .SelectValueForEntity(gamma.Id);

         gamma.Save();

   

         ProgramPlan delta = new ProgramPlan { CanOrder = true, SampleTypes = new

               List<SampleType> { gpu } };

         delta.Save();

         delta.NameStaticAttribute.CreateAndAddAvailableValue("Delta")

               .SelectValueForEntity(delta.Id);

         delta.Save();

   

         ProgramPlan epsilon = new ProgramPlan { CanOrder = true, SampleTypes = new

               List<SampleType> { cpu, gpu } };

         epsilon.Save();

         epsilon.NameStaticAttribute.CreateAndAddAvailableValue("Epsilon")

               .SelectValueForEntity(epsilon.Id);

         epsilon.Save();

   

         ProgramPlan zeta = new ProgramPlan { CanOrder = true, SampleTypes = new

               List<SampleType> { cpu, gpu } };

         zeta.Save();

         zeta.NameStaticAttribute.CreateAndAddAvailableValue("Zeta")

               .SelectValueForEntity(zeta.Id);

         zeta.Save();

   

         ProgramPlan eta = new ProgramPlan { CanOrder = true, SampleTypes = new

               List<SampleType> { cpu, gpu, chipset } };

         eta.Save();

         eta.NameStaticAttribute.CreateAndAddAvailableValue("Eta")

               .SelectValueForEntity(eta.Id);

         eta.Save();

   

         Program moneyMaker = new Program { Name = "MoneyMaker", ProgramPlans =

               new List<ProgramPlan>() {alpha, beta, gamma, delta } };

         moneyMaker.Save();

         Program crowdPleaser = new Program { Name = "CrowdPleaser", ProgramPlans =

               new List<ProgramPlan>() { epsilon, zeta, eta } };

         crowdPleaser.Save();

   

         alpha.Program = moneyMaker;

         alpha.Save();

         beta.Program = moneyMaker;

         beta.Save();

         gamma.Program = moneyMaker;

         gamma.Save();

         delta.Program = moneyMaker;

         delta.Save();

         epsilon.Program = crowdPleaser;

         epsilon.Save();

         zeta.Program = crowdPleaser;

         zeta.Save();

         eta.Program = crowdPleaser;

         eta.Save();

   

         ProgramPlanShuffleListFiltrationHelper programPlanShuffleListFiltrationHelper =

               new ProgramPlanShuffleListFiltrationHelper(new List<Program>{ moneyMaker,

               crowdPleaser }, new List<ProgramPlan>{ alpha, beta, gamma, delta, epsilon,

               zeta, eta }, new List<SampleType>{ cpu, gpu, chipset });

         Assert.AreEqual(programPlanShuffleListFiltrationHelper

               .FlattenedListOfListsOfProgramPlansForPrograms, "MoneyMaker.Alpha," +

               alpha.Id + "|MoneyMaker.Beta," + beta.Id + "|MoneyMaker.Gamma," +

               gamma.Id + "|MoneyMaker.Delta," + delta.Id + "|CrowdPleaser.Epsilon," +

               epsilon.Id + "|CrowdPleaser.Zeta," + zeta.Id + "|CrowdPleaser.Eta," + eta.Id);

         Assert.AreEqual(programPlanShuffleListFiltrationHelper

               .FlattenedListOfListsOfProgramPlansForSampleTypes,

               "MoneyMaker.Alpha,Silicon.CPU|MoneyMaker.Beta,Silicon.CPU|MoneyMaker.

               Gamma,Silicon.CPU|CrowdPleaser.Epsilon,Silicon.CPU|CrowdPleaser.Zeta,

               Silicon.CPU|CrowdPleaser.Eta,Silicon.CPU|MoneyMaker.Alpha,Silicon.GPU|

               MoneyMaker.Gamma,Silicon.GPU|MoneyMaker.Delta,Silicon.GPU|

               CrowdPleaser.Epsilon,Silicon.GPU|CrowdPleaser.Zeta,Silicon.GPU|

               CrowdPleaser.Eta,Silicon.GPU|MoneyMaker.Alpha,Silicon.Chipset|

               CrowdPleaser.Eta,Silicon.Chipset");

      }

   }

}

 
 

The last two lines of the test above, the assert statements, show what the flattened data I will hand to jQuery looks like.

I have a second post here, that shows off what I do with the data on the jQuery side.

You will notice that I just unflatten what I've flattened in C# in jQuery. I'm not sure of how else to hand a collection into jQuery from C# without JSON. Perhaps JSON was the way I should have gone. Hmmm.

Addendum 8/20/2015: I am commenting out http://a.yfrog.com/img877/6388/u73m.jpg which yfrog has seen fit to replace with some sort of iTunes advertisement. I wish I had not started up my blog hosting images with these clowns.

No comments:

Post a Comment