Roman Pushkin
Roman Pushkin

Reputation: 6079

Saving properties on the fly

I'm working on Profile for users on my ASP.NET MVC website. And I have many properties (checkboxes, dropdowns, etc) like:

I can make my own model to handle all of them like this:

public class ProfileViewModel
{
    public TimeZoneOptions TimeZone { get; set; }
    public DstOptions Dst { get; set; } // Daylight Saving Time
    public bool EmailSettings1 { get; set; }
    public bool EmailSettings2 { get; set; }
    // ...
    public bool EmailSettings22 { get; set; }
}

My view model properties are not tied one to another. So, I don't need to call ModelState.IsValid. It seems like my model will always be valid.

If I follow the standard way of saving the model in ASP.NET MVC, it will force my customer to click twice:

  1. Change the property
  2. Click Save button

But I want them to do that with only one click. Like on this screenshot:

enter image description here

I want each property to be marked with "Changes saved" when the user clicks on them. And I want to achieve this with a minimum amount of code. It seems like it's easy to do with:

or

But I can't decide what is better. In jQuery I can manipulate with DOM in direct way. But it seems with knockout it could be more elegant, and I can make my code more maintainable.

I don't know how to do this with knockout, may be some kind of binding needed to update an element when it's saved like this:

<span data-bind="display-when-saved-for-property: EmailSettings1"></span>

???

Maybe there are other ways to do that. Any help would be highly appreciated.

Upvotes: 0

Views: 155

Answers (3)

heymega
heymega

Reputation: 9391

Ok your model needs to be something like this.

public class MyModel
{

    public bool? Friends { get; set; }
    public bool? Photos { get; set; }
    public string Car { get; set; }

}

Your controller method takes in an instance of your model. As long as your ajax request has a data name which matches the name of a property in your model it will bind.

    public void UpdateValues(MyModel model)
    {

        if(model.Friends != null)
        {
            //Save
        }
        if (model.Photos != null)
        { 
            //Save
        }
        if (model.Car != null)
        { 
            //Save
        }


    }

Lets say your html markup is like this

<div id="checkbox-container">
    <div>    
        <label>
            <input type="checkbox" name="friends" />
        My Friends</label>
    </div>
    <div>
        <label><input type="checkbox" name="photos" />My Photos</label>
    </div>
    <select id="car-dropdown">
      <option value="volvo">Volvo</option>
      <option value="saab">Saab</option>
      <option value="mercedes">Mercedes</option>
      <option value="audi">Audi</option>
    </select> 
</div>

And here is the jQuery....

$('#checkbox-container input:checkbox').change(function () {
    //Gets the property name we will bind
    var field = $(this).attr("name");
    var isChecked = $(this).prop("checked");

    var data = {};
    data[field] = isChecked;

    requestBuilder(data);

 });

//New method for our dropdown
$('#car-dropdown').change(function()
{


    var data = {};
    data['Car'] = $(this).val();

    requestBuilder(data);


});


function requestBuilder(data)
{

    //Ajax Request
    $.ajax({
    //Change url to match your controller
    url:"/Controller/UpdateValues/",
    data:data,
    type:"POST",
    success:function(response)
    {
       alert("Change Saved")
    }
    });

 }

Notice how we are passing the Name="" attribute over. This matches the name we have in our model so it will bind :)

Let me know if you need me to expand on any element . I've also created a jsfiddle for the front end code - http://jsfiddle.net/8tKBx/

Good luck!

Upvotes: 1

ebram khalil
ebram khalil

Reputation: 8321

What i understand is that you have list of user configuration that 'll be rendered as checkbox or dropdownlist.
If so then you are right that KnockoutJS is more elegent way(I had the same problem before except i have a save button, i'm lucky). Let's see what we have here:
You have model with mostly bool properties, then with no doubt you should use KnockoutJS mapping plugin. Why? all we gonna do simply is to:

  1. Write the view HTML markup normally.
  2. For the first load of our view, make an AJAX request to get user profile configuration and then using mapping plugin convert the returned data into ViewModel and bind it to our view(all this without need to write the ViewModel by ourself which mean if the ViewModel on server changed we just need to modify only the view).
  3. For each checkbox or dropdownlist add change event handler (using jQuery).
  4. The change function simply convert our binded ViewModel into JSON string again using mapping plugin (just one line of code)
  5. Finally made another AJAX request to save changes.

To show the 'Changes Saved' message you can simply pass refrence of the calling input and using little bit of jQuery magic append your message.

Update:
Your could should be look like this (or maybe better than mine):

var UserProfile;
$(document).ready(function() {
    $('#ViewContainer input:checkbox').change(function (event) {
        SaveUserProfile(event);
    });
    IntializeUserProfile();
});

function IntializeUserProfile() {
    // Show spinner
    $.ajax({
        type: "POST",
        url: URL_To_Get_User_Profile,
        data: UserId, // in case of you need to pass data.
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function(data) {
            UserProfile = ko.mapping.fromJSON(data); //ko.mapping.fromJS($.parseJSON(msg));
            ko.applyBindings(UserProfile, selector);
            // Hide Spinner
        }
    });
}

function SaveUserProfile(event) {
    // Show Spinner
    var model = ko.toJSON(ko.mapping.toJS(UserProfile));
    event = event || window.event;
    target = event.target || event.srcElement;
    $.ajax({
        type: "POST",
        url: URL_To_Save_UserProfile,
        data: model,
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function(data) {
            // Hide Spinner

            // here you have the caller input ==>target.
            // for example, you can get it's parent to append your message
            // or just show message in the page as notification.
        }
    });
}

Upvotes: 1

alexmac
alexmac

Reputation: 19607

I think it can be implemented with knockout + jQuery. With knockout is very easy to build app with changing layout (as in your case). JQuery in your case, is required for async requests.

So, viewmodel:

var ViewModel = function(data) {
    var self = this;
    var _isCurrentSavingProp = null;

    self.EmailSettings1 = ko.observable(data.EmailSettings1);
    self.IsEmailSettings1Saved = ko.observable();
    ... another props

    self.EmailSettings1.subscribe(function(val) {            
        isCurrentSavingProp = self.IsEmailSettings1Saved;
        self.save();
    });
    ... another subscribers

    self.save = function() {
        isCurrentSavingProp(false);
        $.post(url, ko.toJSON(self), function() {
            isCurrentSavingProp(true);
        })
    }  
}

Html:

<span data-bind="visible: IsEmailSettings1Saved" style="display: none"></span>
<input type="checkbox" data-bind="value: EmailSettings1" />
... etc.. 

Upvotes: 3

Related Questions