1

I'm using the following code to dynamically add and remove a partial view from my MVC form. My problem is that the partial view includes a drop down list that I use Select2 on. When I add a new partial view item, the Select2 initialiser is not called.

In my main form:

@Html.LinkToAddNestedForm(m => m.QuoteItemViewModels, "Add Item ", "#quoteItems tbody", ".quoteItem", "QuoteItemViewModels")

My helper function:

public static MvcHtmlString LinkToAddNestedForm<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string linkText, string containerElement, string counterElement, string cssClass = null) where TProperty : IEnumerable<object>
    {
        // a fake index to replace with a real index
        //long ticks = DateTime.UtcNow.Ticks;
        string fakeIndex = Guid.NewGuid().ToString();
        // pull the name and type from the passed in expression
        string collectionProperty = ExpressionHelper.GetExpressionText(expression);
        var nestedObject = Activator.CreateInstance(typeof(TProperty).GetGenericArguments()[0]);

        // save the field prefix name so we can reset it when we're doing
        string oldPrefix = htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix;
        // if the prefix isn't empty, then prepare to append to it by appending another delimiter
        if (!string.IsNullOrEmpty(oldPrefix))
        {
            htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix += ".";
        }
        // append the collection name and our fake index to the prefix name before rendering
        htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix += string.Format("{0}[{1}]", collectionProperty, fakeIndex);
        string partial = htmlHelper.EditorFor(x => nestedObject).ToHtmlString();

        // done rendering, reset prefix to old name
        htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;

        // strip out the fake name injected in (our name was all in the prefix)
        partial = Regex.Replace(partial, @"[\._]?nestedObject", "");

        // encode the output for javascript since we're dumping it in a JS string
        partial = HttpUtility.JavaScriptStringEncode(partial);

        // create the link to render
        var js = string.Format("javascript:addNestedForm('{0}','{1}','{2}','{3}');return false;", containerElement, counterElement, fakeIndex, partial);
        TagBuilder a = new TagBuilder("a");
        a.Attributes.Add("href", "javascript:void(0)");
        a.Attributes.Add("onclick", js);
        if (cssClass != null)
        {
            a.AddCssClass(cssClass);
        }
        a.InnerHtml = linkText;

        return MvcHtmlString.Create(a.ToString(TagRenderMode.Normal));
    }

My JS:

function addNestedForm(container, counter, ticks, content) {
    //var nextIndex = $(counter).length;
    var nextIndex = $(container + " " + counter).length;
    var pattern = new RegExp(ticks, "gi");
    content = content.replace(pattern, nextIndex);
    $(container).append(content);

    resetValidation();
}

function resetValidation() {
    // can't add validation to a form, must clear validation and rebuild
    $("form").removeData("validator");
    $("form").removeData("unobtrusiveValidation");
    $.validator.unobtrusive.parse("form");

}

My partial view:

<tr class="quoteItem">

<td style="padding:1px">

    @Html.DropDownListFor(
     x => x.QuoteItem.Location,
                Model.LocationTypes, "", new { @style = "width:100px", @class = "ddl" })
    @Html.ValidationMessageFor(model => model.QuoteItem.Location)
</td>
<td style="padding:1px">
    @Html.TextBoxFor(x => x.QuoteItem.Area, new { style = "width:100px;height:35px" })
</td>

</tr>

I initialise Select2 with the following code in a js file loaded from a bundle in my layout.cshtml:

$(document).ready(function () {

    $(".ddl").select2();

});

This works on my first item, but not on any subsequently created.

2
  • Try initializing ddl in main form document ready function Commented Jun 1, 2014 at 13:08
  • I already do - I've edited my question to include these details. Commented Jun 1, 2014 at 13:26

1 Answer 1

2
+100

$(document).ready() is triggered once the DOM is fully loaded, as it comes from the server the first time, so it's not triggered when you add/remove the nodes corresponding to your partial view, because you're modifying the tree, not loading it again.

Then, all you have to do is to make sure to execute $(".ddl").select2() after you add the DOM nodes of your partial view. There's no need to reinitialize JQuery to do this.

If I were you, I would just add $(".ddl").select2(); to the end of the addNestedForm() function.

function addNestedForm(container, counter, ticks, content) {
    //var nextIndex = $(counter).length;
    var nextIndex = $(container + " " + counter).length;
    var pattern = new RegExp(ticks, "gi");
    content = content.replace(pattern, nextIndex);
    $(container).append(content);

    resetValidation();

    $(".ddl").select2();
}

An alternate solution is to add a listener, as seen in this post: Is there a JavaScript/jQuery DOM change listener?

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

5 Comments

absolutely correct, but to really earn a bounty / accepted answer, add example code. Good luck.
THanks for that, and seems to make sense, except when I add $(".ddl").select2(); to the end of the addNestedForm() function and then click 'add item', I get the following error in my console: Uncaught query function not defined for Select2 s2id_QuoteItemViewModels_0__QuoteItem_Location
@RobbieMills, that's interesting. can you please add your select2() method to your Original Post?
@RobbieMills, you may want to see this post stackoverflow.com/questions/14483348/…
Thanks @dsnunez, that post along with your example helped.

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.