Reputation: 6079
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:
But I want them to do that with only one click. Like on this screenshot:
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
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
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:
view
HTML markup normally.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
).checkbox
or dropdownlist
add change
event handler (using jQuery).change
function simply convert our binded ViewModel
into JSON string again using mapping plugin
(just one line of code)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
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