2
\$\begingroup\$

Sometimes code seems to write itself (kinda like a donut seems to eat itself) and that happened in this case, but I have thought that UI view models (MVC models folder) were supposed to be stupid to the max. But when this happened it solved some problems and made the code a little bit more OO.

TL;DR I had a bunch of "procedural" code in a navigation controller and now it's properties in the view model, which deals with menu items and type-ahead drop-downs to quickly get to any object in the object hierarchy. All the code has to do with is getting the data ready to be consumed by the view. There is no business logic in the code listing.

drop-downs for quick navigation through the object hierarchy

More Details

I'm using the MvcSiteMap and built MenuItems around that to store the objects in session. Then I implemented a "quick navigation" scheme where you can pick what level you want to go to and it will dynamically build a set of drop-downs to let you quickly traverse type-ahead drop-downs to select the object or list you want to work with. This controller / view implements that page which shows the drop-downs.

What do you think about the outcome here?

Here's what it looks like now, after the code "moved itself over":

Controller

/// <summary>
/// 
/// </summary>
/// <param name="leafId"></param>
/// <param name="currentId"></param>
/// <param name="values">two items (value and text) for each value in values</param>
/// <returns></returns>
public ActionResult Details(int leafId, int currentId = 0, string[] values = null)
{
  return View(new LightningNavModel(leafId, currentId, values ?? new string[] { }));
}

Model

namespace MyMvcWebsite.Models
{
  public class LightningNavModel
  {
    public LightningNavModel(int leafId, int currentId, string[] valuesKvps)
    {
      LeafId = leafId;
      CurrentId = currentId;
      ValuesKvps = valuesKvps;
      SetTypes();
    }

    public int LeafId;
    public int CurrentId;
    public string[] ValuesKvps;

    private IDictionary<decimal, string> valuesDictionary;
    public IDictionary<decimal, string> ValuesDictionary
    {
      get => valuesDictionary ??
        (valuesDictionary = ValuesKvps
                            .Where(v => ValuesKvps.IndexOf(v) % 2 == 0)
                            .ToDictionary(v => decimal.Parse(v), v => ValuesKvps[ValuesKvps.IndexOf(v) + 1]));
    }

    // plural node
    private MenuItem leaf;
    public MenuItem Leaf
    {
      get => leaf ?? (leaf = MenuItems.Current.Find(LeafId));
    }

    // plural node
    //public MenuItem Root
    //{
    //  get => Leaf.LightningNavRoot;
    //}

    // plural node
    private MenuItem current;
    public MenuItem Current
    {
      get => current ?? (current = CurrentSet.ElementAt(CurrentIndex));
    }

    private int? currentIndex;
    public int CurrentIndex
    {
      get => currentIndex ?? (currentIndex = Math.Min(ValuesDictionary.Count(), Leaf.LightningNavSet.Count() - 1)).Value;
    }

    // begins with root, ends with current, ordered from root to current
    private IEnumerable<MenuItem> currentSet;
    public IEnumerable<MenuItem> CurrentSet
    {
      get => currentSet ?? (currentSet = Leaf.LightningNavSet.Take(CurrentIndex + 1));
    }

    private Dictionary<int, string[]> lookupFields;
    public Dictionary<int, string[]> LookupFields
    {
      get => lookupFields ?? (lookupFields = CurrentSet
             .Select(m => (Id: m.Id,
                           Fields: ViewModel.Current.ListMembers(m.Attributes["typeName"])
                                                    .Select(l => l.Label).ToArray()))
             .ToDictionary(i => i.Id, i => i.Fields));
    }

    // for each menu item in set, give Id and Label
    private Dictionary<int, KeyValuePair<decimal, string>> values;
    public Dictionary<int, KeyValuePair<decimal, string>> Values
    {
      get => values ?? (values = ValuesDictionary
                 .ToDictionary(kvp => CurrentSet
                                      .ElementAt(ValuesDictionary.Cast<object>().ToList().IndexOf(kvp))
                                      .Id,
                               kvp => kvp));
    }

    public IEnumerable<SearchBoxModel> SearchBoxes
    {
      get => CurrentSet.Select(mi => new SearchBoxModel() { Parent = this, MenuItem = mi, Value = Values.ContainsKey(mi.Id) ? Values[mi.Id].Value : "" });
    }

    public void SetTypes()
    {
      MenuItems.Current.Load();
      foreach (var menuItem in CurrentSet.Skip(1))
      {
        menuItem.Parent.Type = menuItem.ListMenu.Type;
        menuItem.Parent.SetObject(ValuesDictionary.Keys.ElementAt(CurrentSet.Cast<MenuItem>().ToList().IndexOf(menuItem.ListMenu)));
        menuItem.SetType();
      }
    }

  }
}

View (for context / big picture only. Not really part of my question)

@using AtlasMvcWebsite.Models

@model LightningNavModel

@Styles.Render("~/Content/css/typeahead")
@Scripts.Render("~/bundles/navigation")

<h2>@Model.Current.Node.Title</h2>

<!--SEARCH BOXES-->
@Html.EditorFor(m => Model.SearchBoxes)
\$\endgroup\$
  • \$\begingroup\$ Tell us more about what this code is intended to accomplish? (See How to Ask.) The title of the question should probably say something about rendering a navigation menu. \$\endgroup\$ – 200_success Mar 28 '17 at 19:14
  • \$\begingroup\$ @200_success, I added a details section; don't know if that was the level of detail you're looking for. I'm kindof a new poster here on Code Review but a long time member of S/O. \$\endgroup\$ – toddmo Mar 28 '17 at 19:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.