how to pass collection from view to driver?

Topics: Writing modules
Aug 11, 2011 at 2:03 PM

Hello,

I am working on a custom content part that has a model with a collection of records.  My goal was to pass the collection in the view model to the view and iterate the collection and display editor controls for elements in the collection, allowing the admin to edit the collection elements.

I followed an example for creating these types of data relations here:

http://www.orchardproject.net/docs/%28X%281%29S%28jmuxhij0x3srbgabywlr4g2v%29%29/Default.aspx?Page=Creating-1-n-and-n-n-relations&NS=&AspxAutoDetectCookieSupport=1

I am able to get the collection in the view and create the controls, and bind them to elements in the list.  The problem I am having is when the admin clicks save for the content part and I call TryUpdateModel on the model I don't get the collection back from the view, I just get a null for the collection property on the view model.

Is it possible to have a property that is a collection on the view model and have it sent back to the driver in the way that I am attempting to do this?

Here is an example of the view model:

    public class SignupViewModel
    {
        public SignupViewModel()
        {
            Title = string.Empty;
            Groups = new Dictionary<int, SignupSlotGroupViewModel>();
            Slots = new Dictionary<int, SignupSlotViewModel>();
        }

        public string Title { get; set; }
        public IDictionary<int, SignupSlotGroupViewModel> Groups { get; set; }
        public IDictionary<int, SignupSlotViewModel> Slots { get; set; }
    }

Here is an example of the view:

@{
    var slotsByGroup = from p in Model.Slots.Values
                       group p by p.Slot.Group.Id into q
                       select new { GroupID = q.Key, Slots = q };
    if (slotsByGroup.Count() > 0)
    {
        foreach (var slotGroup in slotsByGroup)
        {
            // Display the slots by start time
            var slotsByStart = from p in slotGroup.Slots
                               orderby p.Slot.StartTime ascending
                               select p;
   
        <li>
          <div class="editor-label"> @Html.LabelFor(m => m.Groups[slotGroup.GroupID].Update) </div>
          <div class="editor-field">
          @Html.CheckBoxFor(m => m.Groups[slotGroup.GroupID].Update)
          @Html.ValidationMessageFor(m => m.Groups[slotGroup.GroupID].Update)
          </div>
         
          <div class="editor-label"> @Html.LabelFor(m => m.Groups[slotGroup.GroupID].Delete) </div>
          <div class="editor-field">
          @Html.CheckBoxFor(m => m.Groups[slotGroup.GroupID].Delete)
          @Html.ValidationMessageFor(m => m.Groups[slotGroup.GroupID].Delete)
          </div>

          <div class="editor-label"> @Html.LabelFor(m => m.Groups[slotGroup.GroupID].Group.Description) </div>
          <div class="editor-field">
          @Html.TextBoxFor(m => m.Groups[slotGroup.GroupID].Group.Description)
          @Html.ValidationMessageFor(m => m.Groups[slotGroup.GroupID].Group.Description)
          </div>
     
          @foreach (var slot in slotsByStart)
          {
              <div class="editor-label"> @Html.LabelFor(m => m.Slots[slot.Slot.Id].Update) </div>
              <div class="editor-field">
              @Html.CheckBoxFor(m => m.Slots[slot.Slot.Id].Update)
              @Html.ValidationMessageFor(m => m.Slots[slot.Slot.Id].Update)
              </div>
         
              <div class="editor-label"> @Html.LabelFor(m => m.Slots[slot.Slot.Id].Delete) </div>
              <div class="editor-field">
              @Html.CheckBoxFor(m => m.Slots[slot.Slot.Id].Delete)
              @Html.ValidationMessageFor(m => m.Slots[slot.Slot.Id].Delete)
              </div>
         
              <div class="editor-label"> @Html.LabelFor(m => m.Slots[slot.Slot.Id].Slot.StartTime) </div>
              <div class="editor-field">
              @Html.EditorFor(m => m.Slots[slot.Slot.Id].Slot.StartTime)
              @Html.ValidationMessageFor(m => m.Slots[slot.Slot.Id].Slot.StartTime)
              </div>
         
              <div class="editor-label"> @Html.LabelFor(m => m.Slots[slot.Slot.Id].Slot.EndTime) </div>
              <div class="editor-field">
              @Html.EditorFor(m => m.Slots[slot.Slot.Id].Slot.EndTime)
              @Html.ValidationMessageFor(m => m.Slots[slot.Slot.Id].Slot.EndTime)
              </div>

              <div class="editor-label"> @Html.LabelFor(m => m.Slots[slot.Slot.Id].Slot.Person) </div>
              <div class="editor-field">
              @Html.TextBoxFor(m => m.Slots[slot.Slot.Id].Slot.Person)
              @Html.ValidationMessageFor(m => m.Slots[slot.Slot.Id].Slot.Person)
              </div>
          }

        </li>
    }
}}

Thanks!

Coordinator
Aug 11, 2011 at 7:14 PM

You need tyo instantiate the collection itself before calling TryUpdateModel. I did the same thing not longer than yesterday for the Contrib.Cache module if you want an example. It's in the AdminController, on IndexPost, for RouteCollections: http://orchardcache.codeplex.com

 

Aug 12, 2011 at 2:40 PM

Thanks.  In the editor result override I am instantiating the view model, which instantiates the collections in its constructor (show in original post), before calling update model.

Here is the editor result override:

        protected override DriverResult Editor(SignupPart part, IUpdateModel updater, dynamic shapeHelper)
        {
            var model = new SignupViewModel();
            if (updater.TryUpdateModel(model, Prefix, null, null))
            {
                if (part.ContentItem.Id != 0)
                {
                    _service.UpdateSignupForContentItem(part.ContentItem, model);
                }
            }
            return Editor(part, shapeHelper);
        }

So I think I am doing what you are suggesting already, but I'll take a look at your example to see if I can spot any differences.

Thanks!