Search code examples
asp.net-mvcasp.net-mvc-4knockout.jsclient-sidedynamic-html

How can I implement parts of views that are conditional on model properties?


I have functional requirements such as this example:

  • The “Citizenship” consists of 3 radio buttons, namely, “South African Citizen”, “Non South African, with work permit” and “Non South African, no work permit”.
  • The “Work Permit number” is a free text field, limited to 15 characters and is activated in the event of a “Non South African,
    with work permit” selection in the “Citizen” field

I would like to create some sort of generic container for field editors like "Work Permit number” that enable or disable their contained editors based on other model properties, such as “Citizenship” in this case. This is quite easy to achieve when first retrieving and rendering a view model.

However, things get complicated when the user e.g. changes the value of “Citizenship”. Only the UI field has changed, and no model property, but the container that decides if "Work Permit number” is enabled, is dependent on a model property.

I see only two solutions to this:

  1. Use a client side (JavaScript maybe) view model, build from the server side view model, possibly a Knockout.js scenario. Then bypass normal form submission and submit the whole client side model at once.

  2. Use an ajax call to update the server side model when the user changes the “Citizenship” value, and update all view parts that depend on the “Citizenship” value. This complicates things because I would have to have a 'working' and a 'committed' model server side. The working model to persist small changes, such as “Citizenship” only, and then when the user clicks save, to move all changes into the 'committed' model and persist to the data store.

For those who understand what I mean, what other means can I use to achieve this, or how can I improve the techniques I outline above?


Solution

  • I think this scenario is perfectly suited to a client-side solution using knockout, as you mentioned in your first proposed solution. Knockout makes it very easy to do this sort of client-side UI manipulation in a declarative manner. After the user has completed the form, you simply serialize the client-side view model object into JSON and POST it to your service layer.

    Your second proposed solution is much less compelling. Among it's drawbacks are:

    1. It requires a server postback just to update the UI, which is a less than ideal user experience
    2. Your server domain model must now have a working and committed model, which adds to complexity

    The code for implementing this with knockout and a client-side javascript view model using the MVVM pattern is quite straightforward:

    <div data-bind = "text: name"></div>
    <input type="radio" name="citizenship" value="yes" data-bind="checked: citizenship">South African Citizen<br/>
    <input type="radio" name="citizenship" value="nowithoutworkpermit" data-bind="checked: citizenship">Non South African, no work permit<br />
    <input type="radio" name="citizenship" value="nowithworkpermit" data-bind="checked:     citizenship">Non South African, with work permit<br />
    <div id="workpermit" data-bind="visible: citizenship() === 'nowithworkpermit'">
        <input type="text" name="workpermitinumber" />Work Permit number
    </div>
    
    <script src="/Scripts/knockout-2.2.0.js" ></script>
    <script>
        var MyApp = MyApp || {};
        MyApp.ViewModel = {
            citizenship: ko.observable(""),
            name: ko.observable("John")
        };
        ko.applyBindings(MyApp.ViewModel);
    </script>
    

    In this example, I've created a view model in MyApp.ViewModel. The model has two properties: name, and citizenship. I've data-bound the "citizenship" property to the "checked" property of the radio buttion, and also to the "visible" property of the displaying the textbox for the work permit number.

    When a user checks a radio button, knockout will update the "citizenship" property of the view model. This change in the view model property will, in turn, update the visibility css property of the workpermit div via the knockout "visibile" binding. When the user is ready to submit the form, you simply serialize MyApp.ViewModel into JSON and send it across the wire.

    The documentation for knockout is outstanding (see here) and I think it would be a perfect fit for what you are trying to accomplish.