Search code examples
javascriptc#knockout.jsknockout-mvc

Knockout computed column with model


I have a model with the following property in my MVC C# solution

public class RegistrationRequirementModel
{
    public string LoadIntent { get; set; }
    public string Francophone { get; set; }
    public string Gender { get; set; }

    public RegistrationRequirementModel(L09RegistrationRequirement requirement)
    {
        LoadIntent = requirement.LoadIntent;
        Francophone = requirement.Francophone;
        Gender = requirement.Gender;
    }
}

In my javascript I can call the model and display the data, however when it comes to using some computed function that is where it fails.

Javascript

    var registrationRequirementModel = {
        frenchData:  ko.observable(""),
        genderData:  ko.observable(""),
        loadIntentData:  ko.observable(""),

        isMissingData: ko.computed(function () {
            if (this.frenchData() == "") { return true };
            if (this.genderData() == "") { return true };
            if (this.loadIntentData() == "") { return true };
            return false;
        },this),

    }

   $(document).ready(function () {

        ko.applyBindings(registrationRequirementModel, document.getElementById("RegistrationSurveyContent"));

        $.ajax({
            url: getStudentRegRequirementsUrl,
            type: "GET",
            contentType: jsonContentType,
            dataType: "json",
            success: function (data) {
                if (!account.handleInvalidSessionResponse(data)) {
                    registrationRequirementModel.frenchData(data.Francophone);
                    registrationRequirementModel.genderData(data.Gender);
                    registrationRequirementModel.loadIntentData(data.LoadIntent);
                }
            },
            error: function (jqXHR, textStatus, errorThrown) {
                if (jqXHR.status != 0)
                    $('#notificationHost').notificationCenter('addNotification', { message: "Unable to retrieve registration requirement.", type: "error" });
            }
        });
    });

Html

<table style="width:100%">
    <tbody>
        <tr>
            <td data-bind="text: loadIntentData"></td>
            <td data-bind="text: frenchData"></td>
            <td data-bind="text: genderData"></td>
        </tr>
    </tbody>
</table>

The purpose is to show html if there is missing data. However when I activate this code, the computed column keep saying the frenchData is not a function. My point would be able to use in my html data-bind="visible: isMissingData". but unfortunately. I can event read from my data.

This is my call to the api

 public async Task<JsonResult> GetRegistrationRequirementAsync()
 {
     string StudentID = CurrentUser.PersonId;
     try
     {
         var requirement = await ServiceClient.L09GetRegistrationRequirementAsync(StudentID);
         RegistrationRequirementModel registrationRequirementModel = new RegistrationRequirementModel(requirement);
         return Json(registrationRequirementModel, JsonRequestBehavior.AllowGet);
      }
      catch (Exception e)
      {}
}

Solution

  • The frenchData is not a function console error stems from the way that the KnockoutJS ViewModel is set up. In essence, the computed function isMissingData below the normal observables has a new inner scope context of this that does not reflect the same outer scope of the registrationRequirementModel object.

    enter image description here

    To work around this, you should switch from using an object literal to a constructor function so that you can assign this ViewModel scope to a self/that variable which alleviates scope issues. Then instantiate your newly stored ViewModel via KO Apply Bindings that you will now have access to after AJAX success:

    function registrationRequirementModel() {
      var self = this;
      self.frenchData = ko.observable("");
      self.genderData = ko.observable("");
      self.loadIntentData = ko.observable("");
    
      self.isMissingData = ko.computed(function() {
        if (self.frenchData() == "") {
          return true
        };
        if (self.genderData() == "") {
          return true
        };
        if (self.loadIntentData() == "") {
          return true
        };
        return false;
      }, this);
    }
    
    $(document).ready(function() {
      var vm = new registrationRequirementModel();
      ko.applyBindings(vm, document.getElementById("RegistrationSurveyContent"));
    
      // replace with endpoint
      var jsonData = {
        Francophone: "Francophone",
        Gender: "Male",
        LoadIntent: "LoadIntent"
      };
    
      if (handleInvalidSessionResponse(jsonData)) {
        vm.frenchData(jsonData.Francophone);
        vm.genderData(jsonData.Gender);
        vm.loadIntentData(jsonData.LoadIntent);
      }
    });
    
    function handleInvalidSessionResponse(data) {
      if (typeof data !== "undefined") return true;
      return false;
    }
    

    Below is a mock JSFiddle of the scenario http://jsfiddle.net/ajxrw39m/3/