Reputation: 8711
I have an ASP.Net MVC5 app with EF 6 on the server. The site has a dozen or so pages, each of which has a grid displaying business data with many columns.
Certain users will not be interested in some of the columns on some of the pages (views) so I need a way for them to indicate which views to make visible.
On the database, a View has many Columns and a User a can designate a column of any view to be displayed (and so stored in the UserViewColumn table):
I am using the following ViewModel to pass back the data selected from the database to the View:
public class UserPrefsViewModel
{
public ICollection<VmUser> Users { get; set; }
public UserPrefsViewModel()
{
this.Users = new List<VmUser>();
}
}
public class VmUser
{
public int UserId { get; set; }
public string AdLogonDomain { get; set; }
public string AdLogonId { get; set; }
public List<VmView> Views { get; set; }
}
public class VmView
{
public int ViewId { get; set; }
public string Name { get; set; }
public List<VmColumn> AllColumns { get; set; }
public List<VmColumn> VisibleColumns { get; set; }
}
public class VmColumn
{
public int ColumnId { get; set; }
public string Heading { get; set; }
}
The razor view is as follows:
@model TVS.ESB.BamPortal.Website.Models.UserPrefsViewModel
@using System.Web.Script.Serialization
@{
ViewBag.Title = "User Preferences";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>@ViewBag.Title</h2>
@{ string data = new JavaScriptSerializer().Serialize(Model); }
<div id="ViewsAndColumns" class="container">
<div class="row">
<div id="Views" class="col-md-3 pull-left">
<span class="label-info">Views</span>
<br />
<table>
<tr>
<td>
<select style="width:100px" size="5" data-bind="options:views, optionsText: 'Name', value:selectedView"> </select>
</td>
</tr>
</table>
</div>
<div id="Columns" class="col-md-9 pull-right">
<table>
<tr>
<td>
<span class="label-info">Available Columns</span>
</td>
<td>
<span class="label-info">To Display</span>
</td>
</tr>
<tr>
<td>
<select size="10" data-bind="options:allColumns, optionsText: 'Heading', value:selectedAvailableColumn"></select>
</td>
<td>
<select size="10" data-bind="options:columnsToAdd, optionsText: 'Heading', value:selectedColumnToRemove"></select>
</td>
</tr>
<tr>
<td>
<button class="btn-default" data-bind="click:addColumn">Add</button>
</td>
<td>
<button class="btn-default" data-bind="click:removeColumn">Remove</button>
</td>
</tr>
<tr>
<td>
<button class="btn-danger" data-bind="click:saveToDatabase.bind($data, '@ViewBag.Domain', '@ViewBag.LoginId')">Save to Database</button>
</td>
</tr>
</table>
</div>
</div>
</div>
@section Scripts {
<script src="~/KnockoutVM/UserPrefs.js"></script>
<script type="text/javascript">
var vm = new ViewModel(@Html.Raw(data));
ko.applyBindings(vm, document.getElementById("UserPrefsDiv"));
</script>
}
Here's a screen grab:
I am using knockoutjs to enable the user to add columns from the first select list to the second, here is my knockout file:
function ViewModel(data) {
var self = this;
var viewColumns;
var visibleViewColumns
self.selectedAvailableColumn = ko.observable();
self.selectedView = ko.observable(data.Users[0].Views[0]);
self.views = ko.observableArray(data.Users[0].Views);
self.selectedAvailableColumn = ko.observable();
self.columnsToAdd = ko.observableArray();
self.selectedColumnToRemove = ko.observable();
var getById = function (items, id) {
return ko.utils.arrayFirst(items(), function (item) {
return item.ViewId == id;
});
};
self.allColumns = ko.computed(function () {
var view = getById(self.views, self.selectedView().ViewId);
return view ? ko.utils.arrayMap(view.AllColumns, function (item) {
return {
ColumnId: item.Id,
Heading: item.Heading
};
}) : [];
}, this);
self.addColumn = function () {
if (self.columnsToAdd().indexOf(self.selectedAvailableColumn()) < 0) {
self.columnsToAdd.push(self.selectedAvailableColumn());
}
};
self.removeColumn = function () {
self.columnsToAdd.remove(self.selectedColumnToRemove());
};
self.saveToDatabase = function () {
var jsSaveModel = ko.toJS(data);
$.ajax({
type: "POST",
url: location.href,
data: ko.toJSON(jsSaveModel),
contentType: 'application/json',
async: true
});
};
};
My problem is, I don't know how best to manage this knockout view model before posting to the server for it to persist to the database. As you can see, I currently load data from the server VM into observable arrays which are bound to the view controls. In the server side VM, it would be the User[x].Views[y].VisibleColumns collection that I will use to populate the UserViewColumn table, identifying the columns that the user wishes to display.
I guess one way to do this would be change the data param which the Knockout ViewModel receives. I could do this in the self.SaveToDatabase function before posting data to the server? Since I have very little experience of Knockout or any MVVM framework, I'd appreciate any advice on best practice.
Upvotes: 0
Views: 122
Reputation: 16119
This will probably get flagged for being too broad and opinion-based, but I personally use AJAX. While this does require a second round-trip back to the server it results in minimal display time and you can show a spinner or something while retrieving the data itself. It's also more flexible in that it allows you to more easily host content on one machine and data on another, which becomes very important down the track as your user-base increases and your hosting solution needs to balance traffic across multiple servers.
In any case here's an idea of what your AJAX handlers would look like server-side:
public class Model
{
public string Data { get; set; }
}
[HttpGet]
public JsonResult GetModel(/* can use params here if required */)
{
var model = new Model { Data = "Some data" };
return Json(model, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public JsonResult PostModel(Model model)
{
var result = new { status = "success"};
return Json(result);
}
And client-side:
getModel: function () {
var self = this;
$.ajax({
url: "/GetModel",
method: "GET",
dataType: "json",
success: function (model) {
// do something with model here and then let's post it back to the server
self.postModel(model);
},
fail: function () {
alert("Failed to retrieve model");
},
cache: false
});
},
postModel: function (model) {
var self = this;
$.ajax({
url: "/PostModel",
method: "POST",
data: model,
dataType: "json",
success: function (status) {
alert("Submitted!");
},
fail: function () {
alert("Failed to post model");
},
cache: false
});
}
What you typically will need to do is create a view model from this data, the knockout site has more details about how to do that.
Upvotes: 1