We’ve all familiarized ourselves with the process of building out a grocery list. Even though our list probably contains a variety of items, from snacks to toiletries, we are typically able to acquire everything we need from the local superstore of our choosing. It is this simplicity and convenience that draws our business and saves us time. As developers, we can create a similar convenience for the end users of our software, without the long checkout lines. People don’t usually want to drive around from store to store, or navigate from page to page, to find all of the items they need.

A very simplified example of this would be allowing the user to allocate a budget in the manner depicted here. While behind the scenes each individual allocation of a budget would be a separate item in the database, we create the illusion of simplicity for the user by allowing each allocation to be edited at the same time before being posted back to the server and saved via the collection. While it seems like a fairly common and reasonable task, handling it can be a bit trickier, and the approaches may differ depending on the particular technologies in use. Getting into this more technical side of things, the form here is running on Microsoft’s MVC5 with some basic formatting using Bootstrap (when it’s not just an image embedded in a web page). The markup for the form, much of which is generated via Razor syntax, looks like this:

 

@using (Html.BeginForm())
{
@Html.HiddenFor(x => x.Id)
<div class=”row”>
<div class=”col-sm-5″>
@Html.ValidationMessageFor(x => x.Allocations)
<table class=”table table-striped table-bordered”>
<thead>
<tr>
<th>Allocation For</th>
<th>Percent Allocated</th>
</tr>
</thead>
<tbody>
@Html.EditorFor(x => x.Allocations)
</tbody>
</table>
</div>
</div>
<hr />
<button class=”btn btn-primary” type=”submit”>Save</button>
}

 

Without going into too much detail, the primary line of code responsible for generating the form’s HTML is @Html.EditorFor(x => x.Allocations). This line points to elsewhere in the code (available in the source) where there is a template defined for editing an allocation. The template generates a row in the depicted table which contains an <input> element with a name attribute that looks something like name=”Allocations[0].Percent”. The value for name here corresponds to the format Collection[index].Property, which is an important detail to note. Razor will automatically loop through all allocations we have and create one of these rows for each, and the [0] segment will increment to specify its index, or position, within the collection. When the form is posted back to the server, MVC uses these indexes to correctly build out the collection. This works well, but what if we want to give the user more control over the collection by allowing the user to add, remove, or change the position of allocations all before refreshing or being posted back to the server? The indexes that were generated for the form would no longer be accurate if there is not logic to recalculate and update the <input> elements on the client side.

In this updated version of the form, we have improved functionality for the user including the ability to add or remove allocations with a modifiable description. Despite the improvements, the back-end for the form has not changed at all; we have only updated how the form’s elements are generated on the client side. This is now being accomplished through AngularJS to meet the updated requirements. While Angular is commonly associated with creating single-page applications, it can also be plugged in to provide us with a simple way to handle the addition, removal, and reordering of the <input> indexes that the MVC form expects. To achieve this, we must first include Angular in our project, and then set up an Angular controller in a <script> on the MVC view for the task at hand:

 

<script>
function BudgetCtrl($scope) {
$scope.allocations = @Html.JsonFor(budget => budget.Allocations);

$scope.remove = function (index) {
$scope.allocations.splice(index, 1);
};

$scope.add = function () {
$scope.allocations.push({
id: 0,
description: ”,
percent: 0
});
};
};
</script>

 

Our controller, a function named BudgetCtrl, has a parameter named $scope, which will be automatically injected by Angular. We can add objects and functions to the $scope, which will then become databound and accessible through HTML. In our controller, we are adding three key things to the scope:

We are using an HtmlHelper (available in the source) to create JSON for the allocations on the MVC view’s model and assigning the resulting collection to the $scope.allocations property.
We are assigning a remove function that takes in an index and removes the allocation at that index from the $scope.allocations array.
We are assigning an add function that will add an allocation with the default properties to the $scope.allocations array.

Now that our controller is ready, we can go back and update the markup of our form:

 

@using (Html.BeginForm())
{
@Html.HiddenFor(x => x.Id)
<div class=”row”>
<div ng-app=”budgetApp” ng-controller=”BudgetCtrl” class=”col-sm-5″>
@Html.ValidationMessageFor(x => x.Allocations)
<table class=”table table-striped table-bordered”>
<thead>
<tr>
<th>Allocation For</th>
<th>Percent Allocated</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat=”allocation in allocations”>
<td>
<input name=”Allocations[{{$index}}].Id”
value=”{{allocation.id}}”
type=”hidden” />
<input name=”Allocations[{{$index}}].Description”
value=”{{allocation.description}}”
type=”text”
class=”form-control” />
</td>
<td>
<input name=”Allocations[{{$index}}].Percent”
value=”{{allocation.percent}}”
type=”text”
class=”form-control” />
</td>
<td>
<a class=”btn btn-danger btn-xs” ng-click=”remove($index)”>
<i class=”glyphicon glyphicon-remove”></i>
</a>
</td>
</tr>
</tbody>
</table>
<a class=”btn btn-default” ng-click=”add()”>Add Allocation</a>
</div>
</div>
<hr />
<button class=”btn btn-primary” type=”submit”>Save</button>
}

 

In the <div> containing the table, we’ve added two attributes. The ng-app attribute lets Angular know we’re stepping into its world, and the ng-controller attribute is used to specify our BudgetCtrl. With these in place, we can now access our $scope properties within the <div>. In the <tbody> where we originally had our @Html.EditorFor(x => x.Allocations), we are now using Angular’s powerful ng-repeat directive on the line <tr ng-repeat=”allocation in allocations”>. This means that for each allocation in our $scope.allocations array, Angular will create a new <tr> which will have access to a singular allocation property within it. Also within each <tr>, Angular will add a new property, $index, for us to access. It is this $index property that we can use to our advantage to properly manage the indexes the form expects to be posted back. By wrapping a property from the current scope in double braces, we are telling Angular to output an interpolated string of the value in its place. Here, we are using {{$index}} within in the name attribute of the <input> elements to generate the expected Collection[index].Property format previously mentioned. Along with the name corresponding to the correct $index, we are using interpolated allocation properties to keep the value attribute of the <input> accurate, such as value=”{{allocation.percent}}”.

The key difference in using Angular’s ng-repeat over @Html.EditorFor(x => x.Allocations) is that the indexes and properties will be updated dynamically on the client side. This way, when we use the ng-click directive, which appropriately gets called when you click the element, with our $scope.add() and $scope.remove(index) functions to alter the items and indexes of the array, all of the form’s inputs will be updated automatically. While this is only one way of many to solve this problem, it shows how keeping a healthy awareness on the most up-to-date technologies at our disposal cannot only help meet the demand for convenient, easy-to-use software, but also do it in a way that will save us time and save clients money. For a closer look at this implementation, you can download the Visual Studio project here. If you have any questions and would like to speak with one of our professional software developers at Envoc, contact us online or call us at +1 (225) 910-8239 ext 115.

P.S. If you’re a fan of using Angular and want to alleviate some common development pains for things like validation messages, be sure to check out our Angular directives repository here.

Comments are closed.