Data binding in MVC 5 and Select2 Multiple Values with Razor engine
Asked Answered
D

4

13

Usually I do very little work on html side of the application because for the most part I just let that get generated for me.

I am working on an app for a blog with Posts Tags and Comments. What I want to do is when creating a new post, I should be able to add existing tags to the new post. I am trying to use Select2 but I can't figure out how to make the selected values passed to my Create method in the post controller so that they can be stored in the database.

Here is what I am working with:

namespace Blog.Data.Entities
{
    public class Post
    {
        public virtual long PostId { get; set; }

        -------------------

        public virtual ICollection<Tag> Tags { get; set; }
    }

    public class Tag
    {
        public virtual long TagId { get; set; }
        public virtual string Name { get; set; }
        public virtual string Description { get; set; }
    }
}

Post Controller

// POST: /Post/Create
    [HttpPost]
    public ActionResult Create(PostsCreateViewModel postModel)
    {
        if (ModelState.IsValid)
        {
            Post post = new Post
            {
                Title = postModel.Title,
                Body = postModel.Body,
                PostDate = _dateTime.UtcNow
            };

            foreach (var tag in postModel.Tags)
            {
                post.Tags.Add(_tagRepository.GetTag(tag.TagId));
            }

            _postRepository.SavePost(post);

            return RedirectToAction("Detail");
        }
        return View(postModel);
    }

I am successfully able to load data from remote with: Json code left out

<script type="text/javascript">
    $(document).ready(function () {
        $("#tags").select2(
        {
            placeholder: "Select a Tag",
            minimumInputLength: 1,
            multiple: true,
            maximumSelectionSize: 5,
            ajax: {
                url: '@Url.Action("SearchTag", "Post")',
                dataType: 'json',
                data: function (term, page) {
                    return {
                        searchTerm: term,
                        page_limit: 10,
                        page: page,
                    };
                },
                results: function (data, page) {
                    var more = (page * 10) < data.total;
                    return { results: data, more: more };
                }
            }
        });
    });
</script>

View: usually I would have something like

<div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

How can I write similar html for my tag textbox, so that when I click save everything is saved to appropriate tables?

Currently I just have this for select2:

<div class="form-group">
    @Html.LabelFor(model => model.Tags, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        <input id="tags" style="width: 300px" />
        @Html.ValidationMessageFor(model => model.Tags)
    </div>
</div>

Which produces;

enter image description here

Dysphasia answered 1/5, 2014 at 22:18 Comment(7)
Aren't the select2 muti-select items just comma separated strings? You're unlikely to be able to store more information in them without editing select2 itself. When I have used this in the past I have just split the string that is posted.. and pushed it into a Tags table.Energid
@SimonWhitehead i am not creating tags, i am pulling them from remote and i have no problem with this. I want to assign selected tags to Post.TagsDysphasia
Right.. but Tag in your code base has a Name and Description. This is more information than the select2 multi-select provides.. without you manually editing it. Your best bet is to sync all of the remote tags to a local Tags database table. Then you can at least match the string that select2 gives you against the tag Name in the tag table. Am I making any sense (hopefully I am)?Energid
@SimonWhitehead I see, that sounds complicated than i know about JavaScript. This is while i just prefer easy drop down list. No need to bother with JavaScript. Is there a similar but straight forward library i can use? Will jQueryUI be more straightforward on data binding?Dysphasia
I think you're misunderstanding what I am saying. Basically I am saying, write a C# console application first that just downloads all of the information about your remote Tags. Have it store them in a table with all of their information. Then, when you post your form via MVC, have your code match the tag name with that of the tag name in the database table. That way you can link them up and still have the extra information such as Description available to you.Energid
@SimonWhitehead Like i mentioned just before the Script code, i have done that. I just left it out because it doesn't help here since all i want is to post Tags after i retrieve them.Dysphasia
You need a property in you model int[] SelectedTags and use @Html.TextboxFor(m => m.SelectedTags) and change the script to $('#SelectedTags').select2(.... Your current input does not have a name attribute so wont post back anything.Gentlemanly
S
8

I'm also using select2 and this works for me:

The View

@Html.ListBoxFor(M => M.TagsId, new MultiSelectList(Model.Tags, "Id", "Description"), new { @class = "form-control select-multiple", multiple = "multiple" })

The ViewModel

 public List<Tag> Tags { get; set; } = new List<Tag>();
 public int[] TagsId { get; set; }

You can use the Tags collection to fill the options in the multiple select and the TagsId array will be populated with the id of the selected tags. If you're populating the select options via ajax I guess you could ommit the Tags property.

Note I'm using a multiple input select and not a textbox.

Settlings answered 17/5, 2018 at 8:55 Comment(0)
S
4

Edit 2022 --

Controller:

myViewModel.xSelectedIdsJsonStr = JsonConvert.SerializeObject(xSelectedIdsIntArray);

View

<script>
    $(() => {
        setTimeout(() => {
            let xSelectedIdsIntArray = @Html.Raw(Model.xSelectedIdsJsonStr);
            $('[name="xSelectListName"]').val(xSelectedIdsIntArray).trigger('change');
        }, 700)
    });
</script>

End-----

Old answer -----

If the list and the selected items comes form different sources:

Example:

-Model.CategoryList >> the actual selectList items

-Model.SubCategories (m => m.SubCategories) >> the selected items only (Ids int[])

@Html.DropDownListFor(m => m.SubCategories, Model.CategoryList, Html.DescriptionFor(m => m.SubCategories), new { @class = "form-control m-select2 select2", multiple = "multiple" })

I did it like this with little js code:

@using Newtonsoft.Json;


<script>
    //Edit mode
    //set "selected" prop for the <select> manaually
    $(function () {
        var SubCategories_selected = @JsonConvert.SerializeObject(Model.SubCategories);
        SubCategories_selected.forEach(function (value, index) {
            $('[name="SubCategories"]').find(`[value="${value}"]`).prop("selected", true);
        });
        //recall the select2 init
        $('#SubCategories').select2();
    });
</script>
Scutcheon answered 13/5, 2019 at 14:52 Comment(0)
O
1

i think it just same as a <select multiple>
for example

    <select name="abc" multiple>
        <option value=1>a</option>
        <option value=2>b</option>
        <option value=3>c</option>
        <option value=4>d</option>
    </select>

you choose option a and option b then submit the form with post method
the post data will be
abc 1
abc 2
and you can get the data in mvc action with a param like IEnumeralbe<int> abc

the action will like

public ActionResult ActionName(IEnumerable<int> abc)  

the select2 plugin only change the view,post data also use a http post

Oman answered 2/12, 2016 at 8:5 Comment(0)
S
0

What I suggest is to do the following :

  • first : keep the text box of tags is for displaying only.

  • second : have hidden inputs that gets in sync with the textbox values by manipulating its value in javascript. each tag in textbox will have hidden input the hidden inputs will represents the Tags Ids that are selected and showed in the textbox.

the benefit from this way is that the Default model binder will take care of binding the hidden inputs values to your view model property : [public virtual ICollection Tags { get; set; }]

let tell how the hidden inputs should look like in your html :

<input type="hidden" name="Tags[0].TagId" value="1" />
<input type="hidden" name="Tags[1].TagId" value="2" />
<input type="hidden" name="Tags[2].TagId" value="3" />

for more info about list binding just follow the link :

http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

Starbuck answered 7/9, 2017 at 22:9 Comment(1)
let me clarify that you have to change your textbox id="tags" to something else. so the binding of the Tags Property will be with the hidden inputsStarbuck

© 2022 - 2024 — McMap. All rights reserved.