May
21
2012
In the previous post we showed how we can use Upshot with the DataController instead of the DbDataController.
Suppose in the previous example a customer can also have several orders.
We would add an Order POCO class to our project that would look like this:
public class Order
{
[Key]
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
[Required]
public virtual int CustomerId { get; set; }
[Association("Customer_Order", "CustomerId", "Id", IsForeignKey = true)]
public virtual Customer Customer { get; set; }
public Order()
{
CreatedDate = DateTime.Now;
}
}
This class also has an Id property with the Key-attribute, this is the same as in the Customer class.
The CustomerId and Customer properties are used to store the reference to the appropriate customer.
Using the Association-attribute, in System.ComponentModel.DataAnnotations namespace, we can tell what the relationship between Customer and Order is.
The first parameter is the unique name for this relationship and the second and third parameters define the key in the current class and the referenced class.
So "CustomerId" is the mapping for the Order.CustomerId property and "Id" is the mapping for the Customer.Id property.
With this association defined upshot will know what the relationship between Customer and Order is.
As an example, suppose we add a view where the user can create an new order and select the customer for it in a dropdown.
We would first add a new ViewModel to our project.
function Order(initialData) {
var self = this;
upshot.map(initialData, upshot.type(Order), self);
self.CustomerText = ko.computed(function () {
if (self.Customer()) {
return self.Customer().FirstName() + ' ' + self.Customer().LastName();
}
else {
return "(None)";
}
});
}
function OrdersViewModel() {
var self = this;
self.customerDataSource = upshot.dataSources.Customers.refresh();
self.customers = self.customerDataSource.getEntities();
self.orderDataSource = upshot.dataSources.Orders.refresh();
self.orders = self.orderDataSource.getEntities();
self.newOrder = ko.observable(new Order({}));
self.orderValidationRules = $.extend({}, self.orderDataSource.getEntityValidationRules(), {
submitHandler: function () {
self.orders.push(self.newOrder());
self.newOrder(new Order({}));
}
});
}
There are a couple of things going on in this ViewModel.
First we add the Order object which uses the upshot.map function to create the new object with all Order properties mapped to knockout observables.
We've added an additional knockout computed property CustomerText to make sure we can always display something in the UI even when no customer is assigned to the order.
The OrdersViewModel itself uses two upshot DataSources for Customer and Order.
Next it creates a newOrder observable and initializes it with an empty Order object. This object will be used in the OrderAddForm and will be populated by the user.
After this there is some code for validating the OrderAddForm.
Upshot looks at the validation Attributes (like Required, StringLength, Range etc) on our POCO's to build these rules.
We've added the RequiredAttribute on CustomerId in the Order class. The UpshotContext has read this attribute and has generated the appropriate JavaScript upshot needs on the client to enable validation.
Using JQuery Extend we extend the orderDataSource.getEntityValidationRules (which contains the the validationRules for an order object) with an SubmitHandler.
This SubmitHandler will be triggered after validation and only when the form is valid.
We then push the newOrder into our orders array and reset the newOrder observable so it can be reused when the user wants to add an additional order.
With this ViewModel completed we can then add a new View to our project.
<form id="OrderAddForm" action="#" data-bind="validate: $root.orderValidationRules, with: $root.newOrder">
<table>
<tbody>
<tr>
<td>Customer</td>
<td>
<select name="CustomerId" data-bind="value: CustomerId, options: $root.customers,
optionsText: 'LastName', optionsValue: 'Id',
optionsCaption: 'Choose...', autovalidate: true"></select>
</td>
</tr>
</tbody>
</table>
<button type="submit">Add</button>
</form>
<ul data-bind="foreach: orders">
<li>
<div>Name: <strong data-bind="text: CustomerText"></strong></div>
</li>
</ul>
@(Html.UpshotContext(bufferChanges: true)
.DataSource<MvcApplication27.Controllers.CustomerServiceController>(x => x.GetCustomers())
.DataSource<MvcApplication27.Controllers.OrderServiceController>(x => x.GetOrders())
.ClientMapping<MvcApplication27.Models.Order>("Order"))
<script src="~/Scripts/OrdersViewModel.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var viewModel = new OrdersViewModel();
ko.applyBindings(viewModel);
});
</script>
First defined in the View is a Form for the newOrder. Using knockout bindings for the newOrder and validating using the orderValidationRules defined in our ViewModel, assigning the Validate parameter with the orderValidationRules will automatically show validation-errors using unobstrusive validation.
The dropdown in the form uses the knockout options binding as explained here. Most important thing to notice are the Value and OptionValue parameters.
The Value parameters maps to the CustomerId property of newOrder observable, the OptionValue parameter maps to the Id property of the selected customer in the dropdown.
Knockout will automatically update the CustomerId in newOrder with the Id of the selected Customer. With the AssociationAttribute we defined in our Order POCO class, the Customer property of newOrder will also be updated automatically. If this attribute would not have been set, the Customer property would not have been set automatically and we would not be able to show the customer's name. Also upshot would fail when we submit the changes to the server.
Next in the view is a bit of knockout so we can render all the newly added orders and we can see that the Customer property has been set correctly.
The UpshotContext has the two DataSources for Customers and Orders. Additional is the ClientMapping. This is used so upshot will know it should use the Order function in our ViewModel to create a clientside object for a Mvcapplication27.Models.Order instance received from the server.
Attached to this post is a sample application that shows the scenarios described in this and the previous post.
As a test you could remove the AssociationAttribute in the Order class to see what happens when adding a new order and upshot does not know about the mapping between Customer and Order.
MvcApplication27.rar (5,27 mb)