views:

142

answers:

2

Say I have a Companies table which has a many-many relationship with an Employees table.

I have an add/edit Companies page, but I also want to be able to find/add new/existing Employees as well, without having to open up another CRUD page for each Employee first.

I'll be inputting an English name and a Japanese name for each Employee, so the usual trick that blog software uses for editing tags won't be too good.

It doesn't need to be too pretty since it's for internal use.

What would be a practical way to do this? I'm guessing I should use jQuery, but I hardly know how to use it.

I'm familiar with WPF/Silverlight, but... y'know :)

A: 

The first thing that comes to mind is to build up a controller to handle crud operations on the employee model, but instead of returning a viewresult, return a jsonresult or xmlresult (mvccontrib). This effectively turns your controller into a web service allowing you leverage silverlight or jquery for the both ui (say jqueryui) and/or service interaction (jquery/ajax).

RG
+7  A: 

The easiest way to do something like this is to use ASP.NET MVC's support for binding a model against an enumerable collection. Essentially, as you create employees for the company they would be appended to a list using a specific field-naming structure that MVC's modelbinder can use to return a list of items (Employees).

public class Company
{
    public string Name { get; set; }
    public IEnumerable<Employee> Employees { get; set; }
}

public class Employee
{
    public string EnglishName { get; set; }
    public string JapaneseName { get; set; }
}

Company Name: <input type="text" name="Name" />

<!-- Employee 1 -->
English Name: <input type="text" name="Employees[0].EnglishName" />
Japanese Name: <input type="text" name="Employees[0].JapaneseName" />

<!-- Employee 2 -->
English Name: <input type="text" name="Employees[1].EnglishName" /> 
Japanese Name: <input type="text" name="Employees[1].JapaneseName" />

<!-- Employee 2 -->
English Name: <input type="text" name="Employees[2].EnglishName" /> 
Japanese Name: <input type="text" name="Employees[2].JapaneseName" />

In the example above you'll notice that for each employee field there is an ordinal indexer on the fieldname, this is what tells ASP.NET MVC that you are binding to a list and that each Employee[n] is a single object to be model-bound. It's important to keep these indexes ordinal because if you miss an index your list will not bound correctly.

If this is all done correctly, you can then define an action to handle the form which receives a Company object as a parameter. MVC will automatically take care of the rest for you.


The above example of course assumes a static number of employees, which will likely never be the case, so in order to make it more flexible we can use jQuery to create new rows for each employee as you define them. As I said before, the order of the indexing is important and must remain consistent.

The following Add and Remove click handlers will ensure that any time you create a new Employee or delete an existing one from the list, the names of your fields will remain ordinal. I ripped this out of some other code I wrote and modified it a little for your purposes. I'm fairly certain it will do the trick.

$('.add-employee').click(function() {
    var nextIndex = 0;
    var lastRow = $(this).siblings('.row:last');
    if (lastRow.length > 0) {
        var lastRegion = lastRow.find('input:last');
        if (lastRegion.length > 0 && /\[(\d+)\]/.test(lastRegion.attr('name')) !== null) {
            var key = lastRegion.parent().find('.key:text');
            if (key.val() === '') {
                key.focus();
                return;
            }
            nextIndex = parseInt(/\[(\d+)\]/.exec(lastRegion.attr('name'))[1], 10) + 1;
        }
    }

    var namePrefix = 'Employees[' + nextIndex + ']';
    var newItem = '<div class="row">\n'
                    + 'English Name: <input type="text" name="' + namePrefix + '.EnglishName" /><br />\n'
                    + 'Japanese Name: <input type="text" name="' + namePrefix + '.JapaneseName" />&nbsp;\n'
                    +  '<a href="#" class="remove-employee">Remove</a>\n'
                    + '</div>';
    $(this).before(newItem);
});

$('.remove-employee').live('click', function() {
    var parent = $(this).parent();
    parent.slideUp();
    parent.nextAll('div').children(':text').each(function(index, element) {
        element = $(element);
        if (/\[(\d+)\]/.test(element.attr('name')) !== null) {
            element.attr('name', element.attr('name').replace(/\[(\d+)\]/, '[' + (parseInt(/\[(\d+)\]/.exec(element.attr('name'))[1], 10) - 1) + ']'));
        }
    });
    parent.remove();
    return false;
});

To use these click handlers you must define a link/button with the class name 'add-employee' and then an additional link/button next to each employee record (in the same container as the fields for that employee) called 'remove-employee'. Note the use of the live() binding on the remove-employee handler which will ensure the rows you add via the first function will have working remove links.

Nathan Taylor
Perfect... thanks for the code and for the very detailed explanation!
Rei Miyasaka
Small note... $('remove-employee') should be $('.remove-employee')
Rei Miyasaka
Thanks for catching that!
Nathan Taylor