Vidar
Vidar

Reputation: 6673

Binding TokenInput and razor code to the same model?

In an HTML form how do you bind jquery controls (e.g. tokeninput) the same way you bind the ordinary primitive types to the model? I am struggling to find ways to do this and I know you can use custom templates etc, but there is nothing for jquery plugins.

Specifically I am using tokenInput see here (http://loopj.com/jquery-tokeninput/). Here is the jQuery code that I apply against a standard HTML text input. For every keypress it goes to the controller to return a list of authors. You can also pre-populate with authors, and I use the data tags in HTML5 to prepopulate the control.

 $("#AUTHORs").tokenInput('/author/getauthors/', {
        hintText: "Enter surname",
        searchingText: "Searching...",
        preventDuplicates: true,
        allowCustomEntry: true,
        highlightDuplicates: false,
        tokenDelimiter: "*",
        resultsLimit: 10,
        theme: "facebook",
        prePopulate: $('#AUTHORs').data('AUTHORs')
    });

I have posted a bit of code from my view just to show you exactly what I am trying to bind to the model.

@model myModels.BOOK

@{
    ViewBag.Title = "Edit";
}

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Basic</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.TITLE)
        </div>
        <div class="editor-field" >
            @Html.EditorFor(model => model.TITLE)
            @Html.ValidationMessageFor(model => model.TITLE)
        </div>
    <div class="authors">
            <div class="editor-field">
                <input type="text" id="authors" name="authors" data-val="true"  data-val-required="You must enter at least one author" data-authors="@Json.Encode(Model.AUTHORs.Select(a => new { id = a.AUTHOR_ID, name = a.FULL_NAME }))"/>
                <span class="field-validation-valid" data-valmsg-for="authors" data-valmsg-replace="true"></span>
            </div>
        </div>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

and here is the code that I use when trying to update the model (after pressing "Save") on the form:

  [HttpPost]
        public ActionResult Edit(BOOK book)
        {
             if (ModelState.IsValid)
            {
                db.Entry(book).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Details", new { id = book.REF_ID });
            }
            ViewBag.REF_ID = new SelectList(db.REFERENCEs, "REF_ID", "REF_ID", book.REF_ID);
            return View(book);
        }

When you look at the code in the HTML it has formatted the authors from the tokeninput element it looks like so, and it seems that this format it has a real problem with I think:

<input type="text" id="AUTHORs" name="AUTHORs" data-val="true" data-val-required="You must enter at least one author" data-authors="

[{&quot;id&quot;:156787,&quot;name&quot;:&quot;Faure,M.&quot;},

{&quot;id&quot;:177433,&quot;name&quot;:&quot;Wang,D.Z.&quot;},

{&quot;id&quot;:177434,&quot;name&quot;:&quot;Shu,L.Sh&quot;},

{&quot;id&quot;:177435,&quot;name&quot;:&quot;Sheng,W.Z.&quot;}]"

style="display: none; ">

Upvotes: 2

Views: 1981

Answers (4)

Darin Dimitrov
Darin Dimitrov

Reputation: 1038940

It seems that you are using the tokeninput plugin. Let's have a step-by-step example about how this could be implemented with ASP.NET MVC:

Model:

public class Book
{
    public string Title { get; set; }
    public IEnumerable<Author> Authors { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Controller:

public class HomeController : Controller
{
    // fake a database. Obviously that in your actual application
    // this information will be coming from a database or something
    public readonly static Dictionary<int, string> Authors = new Dictionary<int, string>
    {
        { 1, "foo" },
        { 2, "bar" },
        { 3, "baz" },
        { 4, "bazinga" },
    };

    public ActionResult Index()
    {
        // preinitialize the model with some values => obviously in your
        // real application those will be coming from a database or something
        var model = new Book
        {
            Title = "some title",
            Authors = new[] 
            {
                new Author { Id = 2, Name = "bar" }
            }
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Book book)
    {
        return Content(string.Format("thanks for selecting authors: {0}", string.Join(" ", book.Authors.Select(x => x.Name))));
    }

    public ActionResult GetAuthors(string q)
    {
        var authors = Authors.Select(x => new
        {
            id = x.Key,
            name = x.Value
        });
        return Json(authors, JsonRequestBehavior.AllowGet);
    }
}

View:

@model Book
@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.Title)
        @Html.EditorFor(x => x.Title)
    </div>
    <div>
        @Html.TextBoxFor(
            x => x.Authors, 
            new { 
                id = "authors", 
                data_url = Url.Action("GetAuthors", "Home"), 
                data_authors = Json.Encode(
                    Model.Authors.Select(
                        x => new { id = x.Id, name = x.Name }
                    )
                ) 
            }
        )
    </div>
    <button type="submit">OK</button>
}

<script type="text/javascript" src="@Url.Content("~/scripts/jquery.tokeninput.js")"></script>
<script type="text/javascript">
    var authors = $('#authors');
    authors.tokenInput(authors.data('url'), {
        hintText: 'Enter surname',
        searchingText: 'Searching...',
        preventDuplicates: true,
        allowCustomEntry: true,
        highlightDuplicates: false,
        tokenDelimiter: '*',
        resultsLimit: 10,
        theme: 'facebook',
        prePopulate: authors.data('authors')
    });
</script>

and the last step is to write a custom model binder which will retrieve the authors form the ids:

public class AuthorModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (values != null)
        {
            // We have specified asterisk (*) as a token delimiter. So
            // the ids will be separated by *. For example "2*3*5"
            var ids = values.AttemptedValue.Split('*').Select(int.Parse);

            // Now that we have the selected ids we could fetch the corresponding
            // authors from our datasource
            var authors = HomeController.Authors.Where(x => ids.Contains(x.Key)).Select(x => new Author
            {
                Id = x.Key,
                Name = x.Value
            }).ToList();
            return authors;
        }
        return Enumerable.Empty<Author>();
    }
}

that will be registered in Application_Start:

ModelBinders.Binders.Add(typeof(IEnumerable<Author>), new AuthorModelBinder());

Upvotes: 4

STO
STO

Reputation: 10648

I made small test - and seems that kind of attribute text is not an error and correclty recognized by browser as JSON object

Try to add expression $("#authorlist").data("authors") to watch in your browser (press F12) or execute it in console - for me it returns valid javascript object.

Upvotes: 0

KyorCode
KyorCode

Reputation: 1497

data-authors="@Json.Encode(Model.AUTHORs.Select(a => new { id = a.AUTHOR_ID, name = a.FULL_NAME }))"

I think this should be :

data-authors="@Html.Raw(Json.Encode(Model.AUTHORs.Select(a => new { id = a.AUTHOR_ID, name = a.FULL_NAME })))"

Upvotes: 0

webdeveloper
webdeveloper

Reputation: 17288

You can use serialize

$("form").on('submit', function(event){
    event.preventDefault();
    var form = $(this);

    $.ajax({
       url: form.attr('action'),
       type: form.attr('method'),
       data: form.serialize(),
       success: function(r) { }
    });
});

Upvotes: 0

Related Questions