views:

86

answers:

2

I've been working on my first web page which will be used as a trouble shooting guide based on a series of questions. The questions are determined by the answer of the previous question, so it turns into a "choose your own adventure". Luckily I was given a decision flow chart showing all the possible paths, but it looks like the Tokyo subway map. I first created a series of panels, each containing the question and a dropdown list control containing the answer as the text and the id of the panel it should preceed. Next I created a class to organize all the panels into a graph structure. With this class I'm able to know the order of which panels are selected, which panels have not been selected, and is easy to make changes to the decision flow.

Below is an example of how I am using the class in the pageload event and below that is the actual class.

Since I'm not really familiar with asp.net, I'm just wondering if there are better ways of tackling this problem and if there are flaws within my code. My code is working, but I'm sure there could be improvements.

protected void Page_Load(object sender, EventArgs e)
{

    DecisionGraph g = new DecisionGraph(this);

    //  Pass is root panel
    g.BuildGraph("pnl1");

    //  Refreshes graph and returns an obj containing 2 iterable objects 
    DecisionGraphEnumeration dgEnum = g.RefreshGraph();

    //  Clear panel which will hold active panels
    pnlMASTER.Controls.Clear();

    IEnumerator<Panel> iEnumOnPath = dgEnum.GetOnPathPanelEnumerator();
    while (iEnumOnPath.MoveNext())
    {
        Panel p = (Panel)iEnumOnPath.Current;
        p.Visible = true;

        pnlMASTER.Controls.Add(p);
    }

    IEnumerator<Panel> iEnumOffPath = dgEnum.GetOffPathPanelEnumerator();

    while (iEnumOffPath.MoveNext())
    {
        iEnumOffPath.Current.Visible = false;
    }
}

Here is the class

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections;
using System.Collections.Generic;


public class DecisionGraph
{

    const String NULL_POINTER_NAME = "null";
    private Node root;
    private Page webPage;

    //constructor
    public DecisionGraph(Page pg)
    {
        this.webPage = pg;
        this.root = null;
    }

    //  method creates a graph structure recursively starting
    //  with the parameter as root
    public void BuildGraph(String pnlName)
    {
        //  return the reference of the supplied panel
        Panel p = GetPanel(pnlName);

        // create root
        root = new Node(p, GetDropDownList(p));

        //  add children nodes to root
        insert(root);


        return;
    }

    //  adds all child nodes to passed node
    private void insert(Node n)
    {
        // if Panel has a DropDownList
        if (n.dropDownList != null)// not leaf
        {
            // create an array of Nodes to hold references to children
            n.children = new Node[n.dropDownList.Items.Count];

            int i = 0;
            foreach (ListItem it in n.dropDownList.Items)
            {
                Panel childPanel = GetPanel(it.Value);

                if (childPanel == null)
                {
                    //  Panel does not exit
                    n.children[i] = null;
                }
                else
                {

                    Node childNode = GetNode(childPanel);

                    if (childNode == null)//   Node doesn't exist in structure for chilePanel
                    {
                        //  create child node
                        childNode = new Node(childPanel, GetDropDownList(childPanel));

                        // add child node to arryay of children
                        n.children[i] = childNode;

                        // add the childs nodes of childNode to childNode
                        insert(n.children[i]);
                    }
                    else
                    {
                        // node had already been created, just add reference
                        n.children[i] = childNode;
                    }



                }

                i++;
            }
        }

        return;

    }

    //  returns an iterator of all active panels set as visible
    //  and sets all other panels as hidden
    public DecisionGraphEnumeration RefreshGraph()
    {
        LinkedList<Panel> pathedPanels = new LinkedList<Panel>();
        LinkedList<Panel> nonPathedPanels = new LinkedList<Panel>();

        FindCurrentPath(root, pathedPanels);

        HideNonPathedNodes(root, nonPathedPanels);

        UnMarkAllNodes(root);

        return new DecisionGraphEnumeration(pathedPanels.GetEnumerator(), nonPathedPanels.GetEnumerator());
    }

    //  marks each node as valid and saves reference inorder
    private void FindCurrentPath(Node n, LinkedList<Panel> list)
    {
        if (n == null)
            return;

        n.visitedFlag = true;
        list.AddLast(n.panel);

        if (n.dropDownList == null)
            return;

        int index = n.dropDownList.SelectedIndex;

        FindCurrentPath(n.children[index],list);

        return;

    }

    //  finds all non pathed nodes and saves reference in no order
    private void HideNonPathedNodes(Node n, LinkedList<Panel> list)
    {
        if (n == null)
            return;

        if (!n.visitedFlag)
        {

            n.ResetDropDownList();

            //  add panel if not already added.
            if (!list.Contains(n.panel))
                list.AddLast(n.panel);
        }

        if(n.children == null)
            return;

        foreach (Node childNode in n.children)
            HideNonPathedNodes(childNode, list);
    }

    // unmarks n and sets all children of n as unmarked
    private void UnMarkAllNodes(Node n)
    {
        if (n == null)
            return;

        n.visitedFlag = false;

        if (n.children == null)
        {
            return;
        }
        else
        {
            foreach (Node childNode in n.children)
                UnMarkAllNodes(childNode);
        }

    }

    // returns node if a node already exists for p in structure, else returns null
    private Node GetNode(Panel p)
    {
        Node n = getNode(root, p);

        return n;
    }

    // recursive method call for GetNode
    private Node getNode(Node n, Panel p)
    {
        if (n == null || p == null)
            return null;

        if (n.panel.Equals(p))
        {
            return n;
        }
        else if (n.children != null)
        {
            Node x = null;
            int i = 0;

            while (x == null && i < n.children.Length)
            {
                x = getNode(n.children[i], p);
                i++;
            }

            return x;
        }
        else
        {
            return null;
        }

    }

    //  returns a DropDownList from p
    private DropDownList GetDropDownList(Panel p)
    {

        foreach (Control ctrl in p.Controls)
        {
            if (ctrl is DropDownList)
            {
                return (DropDownList) ctrl;
            }
        }

        return null;
    }

    //  returns a panel from webpage of contructor using the FindControl method
    private Panel GetPanel(String panelName)
    {
        if(panelName.Equals(NULL_POINTER_NAME))
            return null;

        Control ctrl = webPage.FindControl(panelName);
        if (ctrl is Panel)
        {
            return (Panel)ctrl;
        }
        else
        {
            throw new ArgumentException(String.Format("{0} is not a Panel inside page {1}",panelName,webPage.Title.ToString()));
        }
    }


    private class Node
    {
        public Panel panel;
        public DropDownList dropDownList;
        public Node[] children;
        public bool pathedNode;
        public bool visitedFlag;

        #region Constructors

        public Node() : this(null, null) { }

        public Node(Panel p) : this(p, null) { }

        public Node(Panel pnl, DropDownList d)
        {
            this.panel = pnl;
            this.dropDownList = d;
            this.children = null;
            this.pathedNode = false;
            this.visitedFlag = false;
        }

        #endregion

        public void ResetDropDownList()
        {
            if (dropDownList == null)
                return;
            else
                dropDownList.SelectedIndex = 0;
        }
    }
}


public class DecisionGraphEnumeration
{
    private IEnumerator<Panel> onPathPanels;
    private IEnumerator<Panel> offPathPanels;

    internal DecisionGraphEnumeration(IEnumerator<Panel> active, IEnumerator<Panel> nonActive)
    {
        onPathPanels = active;
        offPathPanels = nonActive;
    }

    public IEnumerator<Panel> GetOnPathPanelEnumerator()
    {
        return onPathPanels;
    }

    public IEnumerator<Panel> GetOffPathPanelEnumerator()
    {
        return offPathPanels;
    }


}
+1  A: 

Not gone through your code but based on the idea of what you want to do I think you might want to investigate using the WizardControl http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.wizard.aspx which allows for highly customisable Wizards to be created

As a first step you could use the ActiveStepChanged event to decide which Step to go to depending on what the user answers in each step.

RobV
A: 

I strongly recommend against referencing any instance of System.Web.Page from within your DecisionGraph class. It doesn't look like you're using the reference for anything right now and you'll just be tempted to use it unnecesarrily for as long as you leave it in there.

It seems like you are building the entire graph for each page_load--are you navigating through the structure after postbacks?

Keep in mind that when you set a panel's visibility property to false you are not getting any output on the client side. However, if you set the CSS style attribute "display" to "none" then you will be rendering the entire graph to the client and can actually perform all of the navigation on the client-side with javascript.

nvuono
I'm referencing System.Web.Page when I perform the FindControl method
Kevin
You can actually call the FindControl function on any of your webcontrols, such as your panels--keeping in mind that you will only be searching the children of that particular control. This will actually be faster as you aren't searching the whole page.However I noticed that in BuildGraph you are finding the reference to the "panel ID" string you pass in so you could consider passing in the actual panel reference instead.
nvuono