views:

592

answers:

1

I have a database table that records what publications a user is allowed to access. The table is very simple - it simply stores user ID/publication ID pairs:

CREATE TABLE UserPublication (UserId INTEGER, PublicationID INTEGER)

The presence of a record for a given user & publication means that the user has access; absence of a record implies no access.

I want to present my admin users with a simple screen that allows them to configure which publications a user can access. I would like to show one checkbox for each of the possible publications, and check the ones that the user can currently access. The admin user can then check or un-check any number of publications and submit the form.

There are various publication types, and I want to group the similarly-typed publications together - so I do need control over how the publications are presented (I don't want to just have a flat list).

My view model obviously needs to have a list of all the publications (since I need to display them all regardless of the current selection), and I also need a list of the publications that the user currently has access to. (I'm not sure whether I'd be better off with a single list where each item includes the publication ID and a yes/no field?).

But that's as far as I've got. I've really no idea how to go about binding this to some checkboxes. Where do I start?

+10  A: 

The Linq to SQL model for your problem looks something like this:

alt text

First, we need some helper objects in our data model:

namespace SelectProject.Models
{
    public class UserPublicationSelector
    {
        public int UserPublicationID { get; set; }
        public int UserID { get; set; }
        public int PublicationID { get; set; }
        public string PublicationName { get; set; }
        public bool IsSelected { get; set; }
    }

    public class UserPublicationSelectViewModel
    {
        public User User { get; set; }
        public IQueryable Selections { get; set; }
    }
}

Now let's create a repository that looks like this:

public class Repository
{
    DataContext dc = new DataContext();

    public User GetUser(int userID)
    {
        return dc.Users.FirstOrDefault(u => u.UserID == userID);
    }

    public IQueryable GetUserPublications(int userID)
    {
        return from p in dc.Publications
               join up in dc.UserPublications on p.PublicationID equals up.PublicationID
               where up.UserID == userID
               orderby p.PublicationName
               select p;
    }
    public IQueryable GetUserPublicationSelectors(int userID)
    {
        return from p in dc.Publications
               join up in dc.UserPublications on p.PublicationID equals up.PublicationID into selected
               from s in selected.DefaultIfEmpty()
               orderby p.PublicationName
               select new UserPublicationSelector
               {
                   UserPublicationID = (int?)s.UserPublicationID ?? 0,
                   UserID = userID,
                   PublicationID = p.PublicationID,
                   PublicationName = p.PublicationName,
                   IsSelected = s.UserID != null
               };
    }

    public void UpdateUserPublications(UserPublicationSelector[] selections)
    {
        // Insert records for new selections...
        foreach (UserPublicationSelector selection in selections.Where(s => s.IsSelected == true))
        {
            // ...where records do not yet exist in database.
            if (selection.UserPublicationID == 0)
            {
                UserPublication up = new UserPublication
                {
                    UserID = selection.UserID,
                    PublicationID = selection.PublicationID,
                };
                dc.UserPublications.InsertOnSubmit(up);
            }
        }
        // Delete records for unselected items...
        foreach (UserPublicationSelector selection in selections.Where(s => s.IsSelected == false))
        {
            // ...where record exists in database.
            if (selection.UserPublicationID > 0)
            {
                UserPublication up = dc.UserPublications.FirstOrDefault(s => s.UserPublicationID == selection.UserPublicationID);
                if (up.UserID == selection.UserID && up.PublicationID == selection.PublicationID)
                    dc.UserPublications.DeleteOnSubmit(up);
            }
        }
        // Update the database
        dc.SubmitChanges();
    }
}

And a controller that looks like this:

public class PublicationController : Controller
{
    Repository repository = new Repository();

    public ActionResult Index(int id)
    {
        User user = repository.GetUser(id);
        var publications = repository.GetUserPublications(id);
        ViewData["UserName"] = user.UserName;
        ViewData["UserID"] = user.UserID;
        return View("Index", publications);
    }

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Select(int id)
    {
        var viewModel = new UserPublicationSelectViewModel()
        {
            User = repository.GetUser(id),
            Selections = repository.GetUserPublicationSelectors(id)
        };
        return View("Select", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Select(int userID, UserPublicationSelector[] selections)
    {
        repository.UpdateUserPublications(selections);
        return RedirectToAction("Index", new { id = userID });
    }
}

The Index view looks like this:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Publication>>" %>
<%@ Import Namespace="SelectProject.Models" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    List of Selected Publications for User
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Publications for <%= ViewData["UserName"] %></h2>

        <table id="MyTable" style="width: 100%">
            <thead>
                <tr>
                    <th>
                        Publication Name
                    </th>
                </tr>
            </thead>

            <tbody>
                <%  int i = 0;
                    foreach (Publication item in Model)
                    { %>

                    <tr id="row<%= i.ToString() %>">
                        <td>
                            <%= Html.Encode(item.PublicationName)%>
                        </td>
                    </tr>

                    <% i++;
                    } %>
            </tbody>
        </table>
        <p>
            <%= Html.ActionLink("Edit Selections", "Select", new { id = ViewData["UserID"] })%>
        </p> 

</asp:Content>

And the Select view looks like this:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<UserPublicationSelectViewModel>" %>
<%@ Import Namespace="SelectProject.Models" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Select Publications
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Select Publications for <%= Model.User.UserName %></h2>

    <% using (Html.BeginForm())
       { %>

        <table id="MyTable" style="width: 100%">
            <thead>
                <tr>
                    <th style="width: 50px; text-align:center">
                        <input type="checkbox" id="SelectAll" />
                    </th>
                    <th>
                        Publication Name
                    </th>
                </tr>
            </thead>

            <tbody>
                <%  int i = 0;
                    foreach (UserPublicationSelector item in Model.Selections)
                    { %>

                    <tr id="row<%= i.ToString() %>">
                        <td align="center" style="padding: 0 0 0 0">
                            <%= Html.CheckBox("selections[" + i.ToString() + "].IsSelected", item.IsSelected)%>
                            <%= Html.Hidden("selections[" + i.ToString() + "].UserPublicationID", item.UserPublicationID)%>
                            <%= Html.Hidden("selections[" + i.ToString() + "].UserID", Model.User.UserID)%>
                            <%= Html.Hidden("selections[" + i.ToString() + "].PublicationID", item.PublicationID)%>
                        </td>
                        <td>
                            <%= Html.Encode(item.PublicationName)%>
                        </td>
                    </tr>

                    <% i++;
                    } %>
            </tbody>
        </table>
        <p>
            <%= Html.Hidden("userID", Model.User.UserID) %>
            <input type="submit" value="save" />
        </p> 

    <% } // End Form %>

    <script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>

    <script type="text/javascript">
        // Select All Checkboxes
        $(document).ready(function() {
            $('#SelectAll').click(function() {
                var newValue = this.checked;
                $('input:checkbox').not('input:hidden').each(function() {
                    this.checked = newValue;
                });
            });
        });
    </script>

</asp:Content>

Here are some screen shots.

alt text

alt text

The checkbox in the upper left hand corner is a Select All/Select None checkbox.

Robert Harvey
Awesome answer, thanks. I feel slightly dirty, like I paid you to do my homework :-)
Gary McGill
Hi Robert,Excellent in depth answer !, Thank you..How would I post back (save) Check boxes with values selected ?
TonyP
@TonyP: That functionality is in the Select view, the `Select` controller method with the 'AcceptVerbs HttpVerbs.Post' attribute value on it, and the UpdateUserPublications repository method, illustrated above.
Robert Harvey
THANK YOU MAN!!
Shawn Mclean
In your `GetUserPublicationSelectors(int userID)` function, `IsSelected = s.UserId != null` will always be true. Is this right?
Shawn Mclean
@Shawn: No. That boolean expression refers to a field in the UserPublications table, which is being left-joined to the Publications table. So `s.UserID` will be `null` if no record exists in the UserPublications table having a corresponding PublicationID in the Publications table.
Robert Harvey