ASP.NET with Knockout variable length list with combobox - how to bind?
Asked Answered
H

1

0

With the following ASP.NET models

public class User
  {
    public string Name { get; set; }
    public LEmail LEmail { get; set; }
  }
public class LEmail
  {
    public IList<CLabel> Labels;
    public IList<CEmail> Emails;
  }
public class CLabels
  {
    public IList<CLabel> Labels { get; set; }
  }
public class CLabel
  {
    public string Name { get; set; }
  }
public abstract class CEmail
  {
    public string SelectedLabel { get; set; }
    public string Name { get; set; }
  }

Filling it out with dummy data and sending to appropriate view as User object, I have the following knockout definitions in the view:

@using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, new { id = "MyEditor" }))
{
  @Html.EditorFor(m => @Model.LEmail)
 <p>
    <input type="submit" value="Save" data-bind="enable: Emails().length > 0" />
    <a href="/">Cancel</a>
  </p>

  <p data-bind="visible: saveFailed" class="error">A problem occurred saving the data.</p>

  <div id="debug" style="clear: both">
    <hr />
    <h2>Debug:</h2>
    <div data-bind="text: ko.toJSON(viewModel)"></div>
  </div>
}

<script type="text/javascript">

  $(function() {
    ko.applyBindings(viewModel);

    $("#profileEditorForm").validate({
      submitHandler: function(form) {
    if (viewModel.save())
      window.location.href = "/";
    return false;
      }
    });
  });

  var viewModel = {

    Name: ko.observable("@Model.Name"),

    Labels: ko.observableArray(@Html.Json(Model.LEmail.Labels) || []),
    Emails: ko.observableArray(@Html.Json(Model.LEmail.Emails) || []),
    addEmail: function() {
      viewModel.Emails.push(@Html.Json(new CEmail()));
    },
    removeEmail: function(eml) {
      viewModel.Emails.remove(eml);
    },

    saveFailed: ko.observable(false),

    // Returns true if successful
    save: function() {
      var saveSuccess = false;
      viewModel.saveFailed(false);

      // Executed synchronously for simplicity
      jQuery.ajax({
    type: "POST",
    url: "@Url.Action("MyAction", "MyController")",
    data: ko.toJSON(viewModel),
    dataType: "json",
    contentType: "application/json",
    success: function(returnedData) {
      saveSuccess = returnedData.Success || false;
      viewModel.saveFailed(!saveSuccess);
    },
    async: false
      });       
      return saveSuccess;
    }
  };
</script>

And finally the editor template that is actually supposed to take care of variable length list that look like this:

@model MyDomain.ViewModels.LEmail

<table>
    <tbody data-bind="template: { name: 'EmailsTemplate', foreach: Emails }" />
</table>

<button data-bind="click: addEmail">Add Email</button>

<script id="EmailsTemplate" type="text/html">
      <tr>
        <td>
      @* PROBLEM IS HERE!! Labels won't show (they will it this code taken out of template) *@
           <select data-bind="options: Labels"></select></td>
        <td>
          <input type="text" data-bind="value: Name, uniqueName: true" class="required" /></td>
        <td>
          <a href="#" data-bind="click: function() { viewModel.removeEmail(this); }">Delete</a></td>
      </tr>
</script>

Essentially I

  1. cannot make it work in the EditorTemplate for combobox (or dropdownlist). It won't attach to Labels no matter what I do. If I take it outside the template somewhere else - it works as expected.
  2. Also, based on selection to fill out the "SelectedValue" inside the Email - how to do that.

  3. After everything is selected, (this must be simple) how to post it all back without losing values on the way (its a super nested model as you see).

Thank you very much in advance!

Harbaugh answered 6/3, 2013 at 22:55 Comment(2)
What do you mean, "Cannot make it work" - are you getting an error? Are there no results?Thermopylae
I mean it doesn't show the expected values in the dropdown - its practically empty. If I take this piece of code outside the loop (template) - it works as expected. Seems I cannot access the .Labels attribute, but I MUST do it, in order to label the email (whether its work/home/custom). ThanksHarbaugh
T
1

Labels is on your view model, not each email. Since the template is rendered within the context of a Knockout foreach binding, the binding context has changed to an email.

Here's how I'd write your view:

@model FiveW.ViewModels.LabeledEmail

<table>
    <tbody data-bind="foreach: Emails">
        <tr>
           <td>
              <select data-bind="options: $root.Labels, value: SelectedLabel"></select>
           </td>
           <td>
              <input type="text" data-bind="value: Name, uniqueName: true" class="required" />
           </td>
           <td>
              <a href="#" data-bind="click: function() { viewModel.removeEmail(this); }">Delete</a>
           </td>
      </tr>
    </tbody>
</table>

<button data-bind="click: addEmail">Add Labeled Email</button>

The fix is in $root.Labels: we need to tell Knockout to use $root (your view model), since Labels is actually on your view model, and not on an individual email.

Also notice I didn't use an named template. This is preferable. Unless you are using the template in more than one place in your view, you should use anonymous, inline templates like I did above.

Thermopylae answered 6/3, 2013 at 23:21 Comment(8)
It worked, but no values passed into Email.SelectedLabel - its practically GUI with no use. Also on postback, the User.LabeledEmail is null - how to post it all back please? Thank you!Harbaugh
You need the value binding on your <select> box. I've updated the code.Thermopylae
Documentation for the <select> box binding: knockoutjs.com/documentation/options-binding.htmlThermopylae
Judah, thank you very much, it helped! This is why I marked as the answer. Can you please help some more? Essentially - how to post it back as User, as now it comes with name and LabeledEmail as null, which is not what I'm after :) Thanks in advance!Harbaugh
I'm confused. Are you posted your view model as a user, and on the server, user.LabeledEmail is null?Thermopylae
Already solved #15274494. I was flattening the view model (MVC to KO) and was failing to put it back together on the way back (KO to MVC). Now its working, need to work on KO validation though, that doesn't work by default, guess, as soon as you step off the standard path, SUV is required :) ThanksHarbaugh
Glad you got it working. Validation can be done with Knockout as well: github.com/ericmbarnard/Knockout-ValidationThermopylae
Thanks for the link. Isn't it a duplication (you define your fluent validation (or data annotation) in MVC and then you redefine the same story in knockout)? (Trying to understand, since I am completely new to KO, first baby steps). Thanks.Harbaugh

© 2022 - 2024 — McMap. All rights reserved.