-3

Edit: jqueryval is the culprit (unobtrusive huh?). Not sure why it's affecting anything, as we disabled it on the checkboxes in the settings. However, commenting the reference makes the loadtime unnoticable. I've tried several fixes with no luck.

This is the function that causes the slowdown, line 532 in jquery.validate.js

elements: function() {
            *snip*
            return $( this.currentForm )
            .find( "input, select, textarea" )
            .not( ":submit, :reset, :image, [disabled], [readonly]" )
            .not( this.settings.ignore )  //performance murderer
            *snip*
        },

*Note - I snipped the filter function that comes after this, because in testing I didn't use it and still got the slowdown, so it's not really relevant.

I am also removing the checkboxes from validation:

    $.validator.setDefaults({
        ignore: ":hidden, input[type=checkbox]" //do not validate checkboxes
    });

I have an mvc app that is going to be posting around 2000+ checkboxes back to the server. This is normally not a problem, interactions with the site via jquery selectors run perfectly fine, and load times are fast. However, submit causes some kind of reflow issue where every single textbox and their labels offsetHeight and offsetWidth get checked. This compounds to an over 5 second load time on creating and saving edits.

Start... Initial UI Report

End... enter image description here

(I'm pretty sure it just gave up at the end there, but there's like 2000 more events you can scroll it for minutes)

The initial load is going to hurt but as the users use the site more, many of these checkboxes will become "disabled" which I understand to mean that they will no longer post back. Unfortunately, disabling these textboxes is not causing the number of elements to post back to drop. I have a feeling this is MVC related, but here's the code:

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()


    <fieldset>
        <legend><input type="submit" value="Create"/> | @Html.ActionLink("Back to List", "Index", "CatManager")</legend>

        @if (Model.CategoryViewModel.ChildCategoryNodes.Any())
        {
            for (int i = 0; i < Model.CategoryViewModel.ChildCategoryNodes.Count(); i++)
            {
                @Html.HiddenFor(model => model.CategoryViewModel.ChildCategoryNodes[i].Id)
                @Html.HiddenFor(model => model.CategoryViewModel.ChildCategoryNodes[i].Description)
                <label @(Model.CategoryViewModel.ChildCategoryNodes[i].CategoryId != null ? "disabled" : "")>
                    @if (!Model.CategoryViewModel.ChildCategoryNodes[i].AlreadyTaken)
                    {
                       @Html.CheckBoxFor(model => model.CategoryViewModel.ChildCategoryNodes[i].Selected, new Dictionary<string, object> {{"data-id", Model.CategoryViewModel.ChildCategoryNodes[i].Id}, {"data-description", Model.CategoryViewModel.ChildCategoryNodes[i].Description}}) 
                    }
                    else
                    {
                        @Html.CheckBoxFor(model => model.CategoryViewModel.ChildCategoryNodes[i].Selected, new Dictionary<string, object>{{"data-id", Model.CategoryViewModel.ChildCategoryNodes[i].Id}, {"data-description", Model.CategoryViewModel.ChildCategoryNodes[i].Description}, {"disabled","disabled"}})
                    }
                    @Model.CategoryViewModel.ChildCategoryNodes[i].Id - @Model.CategoryViewModel.ChildCategoryNodes[i].Description
                </label>
            }
        }
    </fieldset>
}

Anyone have any idea how to reduce the amount of junk that's being posted back, or how to avoid this mega-slow offset reflow?

Edit: As usual, I'm getting harassed by people who think they know what's going on here but they clearly don't, so here's some example code that runs in less than a second that's operating on my dom every time a character is typed into a textbox.

    $("#filter").on("keyup", function() {
        ValidateTextBoxes();
    });

    function ValidateTextBoxes() {
        $("input[type=checkbox]").each(function() {
            var val = $("#filter").val();
            var id = $(this).data("id");
            var description = $(this).data("description");
            if (val != null && id != null && description != null) {
                if (id.toString().indexOf(val.toString()) > -1 || description.toString().toLowerCase().indexOf(val.toString().toLowerCase()) > -1){
                    $(this).parent().show();
                } else {
                    $(this).parent().hide();
                }
            }
        });
    }

So THANKS, no more comments about the number of elements PLEASE.

C Bauer
  • 5,003
  • 4
  • 33
  • 62
  • 1
    Besides stating the obvious (+2000 checkboxes, really?), I'm actuallly a bit intrigued. However, "disabling" an element in HTML/CSS **does not** mean it will not be posted back. You're not sending the checkbox back, but it's value. As far as I can see from your screenshots, it's all waiting for clientside scripting. To verify this: if you disable Javascript entirely in your browser, will it improve loading/posting speeds? – Juliën Jan 20 '15 at 15:22
  • Yep. Our business unit has a LOT of categories, and this project will help bring them to a manageable (and report-on-able) level. There's UI elements that allow them to search for specific values in the checkboxes easily. But yes, disabling javascript means immediate successful value postbacks. – C Bauer Jan 20 '15 at 15:27
  • Can't you group the check boxes and lazy-load them? What I'm thinking is: a list of accordions, each representing a category; when you first click to open a category, fetch the check boxes per AJAX. On the server, you'd only need to update just the received check box values. – Andrei V Jan 20 '15 at 15:34
  • The groups are not logical groups, in fact the Id fields are bizarre. Like 1000 might be meats, then 1100 might be turkey, 1101 cajun turkey, 1200 ham, etc. BUT because they were allowed to do whatever they want, it doesn't always work that way. Sometimes it's 1100, sometimes it's 10100, 14300, all sorts of nonsensical groupings. Unless we create another process for grouping the groupings, there's no way that we can intelligently categorize things ourselves. – C Bauer Jan 20 '15 at 15:37
  • 4
    The issue has nothing to do with posting back, or MVC, it's that your DOM is so cluttered that it just takes a long time to process these with JavaScript, as evidenced by the fact that disabling javascript eliminates the problem. You need to examine your JavaScript code and figure out where the issue is and optimize it. – Erik Funkenbusch Jan 20 '15 at 15:53
  • Then why is posting back calling offsetHeight and offsetWidth over and over and over? I already had a bunch of people tell me I'm an idiot for putting lots of checkboxes on the page in http://stackoverflow.com/questions/27944478/mvc-slow-page-load-times-with-many-elements and they were completely wrong (that turned out to be a visual studio "feature", but I digress). Your comment doesn't contribute anything to the discussion. Not to mention I perform LOTS of jquery operations on those checkboxes during runtime and it's fine. – C Bauer Jan 20 '15 at 15:56
  • 1
    We can't tell from here what is causing all these reflows - you'll need to dig into your developer tools on IE and see if you can figure out what is causin g this. If you can figure out what is being called here, then we can maybe help. – Paddy Jan 20 '15 at 16:17
  • There's two screenshots right there taking up half the question that show what's being called and what the issue is. Those are screenshots of the dev tools. I feel like I'm in the twilight zone. I've explained the issue in excruciating detail in the question. There's pictures of the stuff you're asking me for right in front of you. It's the DOM submit and for some reason it's reflowing the layout in a way that causes a 5 second lag. However, the 5 second lag does not occur on page load. **So something different is happening on submit but I don't know what.** – C Bauer Jan 20 '15 at 16:28
  • 1
    @CBauer - no need to get wound up. You can't figure out what the call is using the dev tools and having a whole copy of your code - we are going to struggle while looking at a screenshot of your dev tool. – Paddy Jan 20 '15 at 16:57
  • That said - you may want to look here: http://blogs.msdn.com/b/eternalcoding/archive/2013/09/04/reducing-the-pressure-on-the-garbage-collector-by-using-the-f12-developer-bar-of-internet-explorer-11.aspx - I imagine your validate function will be creating/recreating large arrays of dom objects. You might want to look at losing the jQuery and writing these kind of methods differently. – Paddy Jan 20 '15 at 16:59
  • We're actually not validating the checkboxes in jqueryval (see my edit above). – C Bauer Jan 20 '15 at 17:01
  • Is there more JS than this and can you post it? – Paddy Jan 21 '15 at 12:03
  • It's the jqueryval. When I disable jqueryval it works. In fact I located the selector in the source that is causing the problem. – C Bauer Jan 21 '15 at 12:09
  • @CBauer what was the problematic selector? – danludwig Jan 21 '15 at 15:43
  • @danludwig I edited it into the top of the question. – C Bauer Jan 21 '15 at 15:49
  • Ok, looks like the default value for `this.settings.ignore` is `':hidden'`. I'm sure you're aware that when MVC renders a checkbox, it also renders a hidden input to submit false / unchecked values to the server. So in addition to the 2000 checkboxes, it sounds like you also had 2000 hidden input elements that jqueryval was trying to filter out. I will update my answer. – danludwig Jan 21 '15 at 16:08
  • Yes, that is why I did `$.validator.setDefaults({ ignore: ":hidden, input[type=checkbox]" //do not validate checkboxes });` (which is later in the code posted above). I guess the question is too long. – C Bauer Jan 21 '15 at 16:09
  • I would expect that adding more to the ignore filtering selector would make it worse, maybe I am wrong. If you are not validating any checkboxes or hidden inputs, maybe you can set ignore to an empty string? That's what I suggested in my answer update. Not sure whether what you posted fixed it, made it worse, or had no effect. – danludwig Jan 21 '15 at 16:16

2 Answers2

3

I'm going to take the risk of trying to provide a helpful answer to this question, even if the OP doesn't want to hear it. I responded to the last question you posted regarding these same 2k checkboxes, and I still strongly believe that you are trying to solve the wrong problem. Maybe that's why so many people are giving you answers you don't seem to want to hear.

In your last question you indicated that the long wait times were making it very frustrating for you to debug and develop. I suggested that you rethink your UI, to which I was greeted with the statement to the effect of "If I wanted help writing a better UI, I would have asked for it". You also stated that you plan to rewrite the UI, but in the meantime, these browser performance issues were bottlenecking you from doing your intermediate work.

One thing you can do is to temporarily alter your test data or controller action so that it only returns 20 checkboxes instead of 2000. Something like that should help you work faster until a time when you can redesign your User Experience. If you are planning to render out 2000 checkboxes onto a page for your users in production, expect them to have a poor UX.

I don't know the answer to the question about jqueryval, so please feel free to downvote this answer because of that. However I do know that browser javascript engines (IE especially) will choke on very large DOM's when trying to scan and do work with them. Technology will only take you so far, you have to be willing to compromise and meet the browser somewhere in the middle.

Another thing I would suggest if you haven't done it already is to read the jqueryval source code.

Update:

It looks like the default value for this.settings.ignore is ':hidden', as shown below:

$.extend($.validator, {

    defaults: {
        messages: {},
        groups: {},
        rules: {},
        errorClass: "error",
        validClass: "valid",
        errorElement: "label",
        focusInvalid: true,
        errorContainer: $([]),
        errorLabelContainer: $([]),
        onsubmit: true,
        ignore: ":hidden", // HERE
        ignoreTitle: false,
        onfocusin: function( element, event ) {

Instead of forking jqueryval, see if doing this improves performance:

$.validator.setDefaults({ ignore: '' });

References are:

asp.net mvc: why is Html.CheckBox generating an additional hidden input

jQuery Validate - Enable validation for hidden fields

If the ignore setting really is the performance murderer, then I would expect setting it to an empty string should short-circuit the filtering selector and get you back to "normal" performance for 2k+ checkboxes (plus 2k+ hidden inputs, for a total of 4k+ elements necessary to render all your categories).

Community
  • 1
  • 1
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • In both instances the issue was a third party library doing something stupid. I've not ever downvoted any of your contributions, but I'd prefer some help that isn't a design criticism. – C Bauer Jan 21 '15 at 13:21
  • @CBauer to be clear, when I said "feel free to downvote this answer" that was not directed at you. It was directed at others who may read my answer, decide that it is not appropriate for the question, and downvote it accordingly (since it does not convey any special knowledge about jqueryval). And I only criticize because I am trying to help. It comes from a good intention, not a malicious one. I am being mean to the code, not to you personally. – danludwig Jan 21 '15 at 13:28
  • I have located the questionable selector in jqueryval and have run it on button click and reproduced the freeze. Unfortunately we'll probably end up making a custom version of jqueryval that doesn't use such an inefficient selector. We added a text box you can type into that reads data attributes on all 2000 boxes, shows and hides them based on whether or not the string is located in one of the attributes, all several times per second. – C Bauer Jan 21 '15 at 13:39
  • Turns out they turned off the jquery validation altogether using class="cancel" but the commit log didn't explain it (they also removed the validator default in the same commit), so this is still a WIP. – C Bauer Jan 22 '15 at 13:12
0

Alright, so...

The issue being jqueryval, our answer is to add the cancel class on the submit button to block the jqueryval on this page specifically, then use the ValidatorContext to validate the model when it comes into the controller.

We rehydrate the properties that can't be posted back (the contents of a dropdownlist, for instance), and post it back to the user to be handled by the normal mvc validationsummary.

    [HttpPost]
    public ActionResult Create(EditFirstChildCategoryViewModel editFirstChildCategoryViewModel)
    {
        if (!ModelState.IsValid)
        {
            var categories = _categoryDataService.GetRootCategoryModels();

            editFirstChildCategoryViewModel.ExistingCategoryDropDown =
                _selectListFactory.CreateSelectList(categories, RootCategoryValueExpression,
                    RootCategoryTextExpression);

            return View(editFirstChildCategoryViewModel);
        }
        //etc...
    }

The user gets to fix any issues, and we get our terrible terrible 2000 checkbox design to behave.

C Bauer
  • 5,003
  • 4
  • 33
  • 62