2

I have view with tasks. Each task has an @Ajax.Action link to add this task to check list. My view:

@foreach (var item in Model.Tasks) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.TaskText)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TillDate)
        </td>
        <td>
            @Html.EnumDropDownListFor(modelItem => item.State, new { id=item.Id, @class="state"})
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            @Ajax.ActionLink("Add To check list", "AddToCheckList", new { id = item.Id }, new AjaxOptions { UpdateTargetId = "CheckListPartial" });
        </td>
    </tr>
}

My controller action:

    public PartialViewResult AddToCheckList(int id)
    {
        context.AddTaskToCheckList(id);
        return PartialView("_LoadCheckList", context.CheckList);
    }

And CheckList class:

public class CheckList
{
    public string Name { get; set; }
    public List<Task> Tasks { get; set; } = new List<Models.Task>();
}

Now adding works, but I have a problem: I can add one task to check list several times. How can I validate if check list contains task and show error message?

UPD: I've make this with my controller. Validation works, but message is not shown.

    public PartialViewResult AddToCheckList(int id)
    {
        if(context.CheckList.Tasks.Exists(t=>t.Id==id))
            ModelState.AddModelError("CheckList", "Check list already contains this task.");

        if (ModelState.IsValid)
        {
            context.AddTaskToCheckList(id);
            return PartialView("_LoadCheckList", context.CheckList);
        }
        return PartialView();
    }

Also add this string to my View:

@Html.ValidationMessageFor(model => model.CheckList)

UPD2: My PartialView:

@model TaskTracker.Models.CheckList

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            Текст задания
        </th>
        <th>
            Дата выполнения
        </th>
        <th></th>
    </tr>

@foreach (var task in Model.Tasks)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem=>task.Value.TaskText)
        </td>
        <td>
            @Html.DisplayFor(modelItem => task.Value.TillDate)
        </td>
    </tr>
}

</table>
13
  • Not related but what is your EnumDropDownListFor for? That will never bind when you submit because your have a foreach loop (refer this answer) Commented Jun 27, 2017 at 7:10
  • You need to show your partial and the element its being loaded into and how your posting back the data. Commented Jun 27, 2017 at 7:11
  • @StephenMuecke I'v added UPD. I want to display validation message in main view, not in the pratial. Commented Jun 27, 2017 at 7:21
  • @StephenMuecke I'm using ajax to change state of task when dropdown selection changed. Commented Jun 27, 2017 at 7:22
  • Your adding the error to the partial view's ModelState - that will not add error to the main view. Commented Jun 27, 2017 at 7:33

2 Answers 2

1

You cannot display a ModelState error from a partial in the main view's @Html.ValidationMessageFor() - that is razor code and is parsed on the server before its sent to the view, so will display ModelState errors from the main views model only.

One option would include moving the ValidationMessageFor() to the partial view with the only drawback being the ability to position the element.

However, returning the whole table of added tasks is unnecessary when you already know all the values in the client to append the new row to the table.

Change your code to use the $.ajax methods and in the method return a JsonResult indicating success or otherwise

Html

<a href="#" class="add" data-id="@item.Id">Add To check list</a>

Script

var url = '@Url.Action("AddToCheckList")';
var table = $('#CheckListPartial table);

$('.add').click(function() {
    var id = $(this).data('id');
    var cells = $(this).closest('tr').find('td');
    var text = cells.eq(0).text();
    var date = cells.eq(1).text();
    $.post(url, { id: id }, function(result) {
        if (result) {
            // create and append a new row
            var row = $('<tr></tr>');
            row.append($(<td></td>).text(text));
            row.append($(<td></td>).text(date));
            table.append(row);
        } else {
            // display your error
        }
    }).fail(function (result) {
        // display your error
    });
})

and the controller method would be

public JsonResult AddToCheckList(int id)
{
    if(context.CheckList.Tasks.Exists(t => t.Id == id))
    {
        return Json(null); indicate error - show a hidden element containing a message
    }
    context.AddTaskToCheckList(id);
    return Json(true); // indicate success
}

You should also consider removing the link if successful, so the user does not accidentally click it again.

A third alternative would be to generate a checklistbox of all tasks and allow the user to select or unselect items and the post a form and save all taks inone action. Refer this answer for an example of the approach.

Sign up to request clarification or add additional context in comments.

Comments

0

You can make your Tasks to be an HashSet and check in the AddTaskToCheckList method if it is already there. In that case checking is O(1)

public void AddTaskToCheckList(int id)
{
    // Find task
    var task = this.Tasks.Find(id);
    if(!this.CheckList.Contains(task))
    {
        this.CheckList.Add(task);
    } 
    else
    {
        // Show error message        
    }   
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.