? Success

User tests: Successful: Unsuccessful:

avatar ggppdk
ggppdk
29 Apr 2016

Summary of Changes

If you have a lot of form elements e.g. buttons that do not have a label or if the form has labels that do not have HTML tag ID like *-lbl, then findLabel() performance does a full DOM search using selector:
var $label = $('label[for="'+$elem.attr('id')+'"]');

The above describe findLabel() behaviour of:

  • validate.js

For:

  • html5fallback.js the full DOM search is always performed

Testing Instructions

Apply the patch the edit file (or some other form file):
and replace:

</form>

with:

<?php
echo '
<script>
    var test_cnt = 0;

    function time_test_Isvalid(el)
    {
        window.console.log( \'CALLING isValid()\' );

        var vTimeStart = new Date();
        document.formvalidator.isValid(document.getElementById(el.form.id));
        var vTimeDiff = (new Date())  - vTimeStart;

        window.console.log( \'isValid() time: \' + vTimeDiff );

        return false;
    }

    function inject_test_input(el, box_id)
    {
        var box = document.getElementById(box_id);

        var lbl = document.createElement(\'label\');
        lbl.setAttribute(\'for\', \'testid_\' + test_cnt);
        lbl.setAttribute(\'class\', \'label\');
        lbl.innerHTML = \'New element\';
        box.appendChild(lbl);

        var input = document.createElement(\'input\');
        input.type = \'text\';
        input.setAttribute(\'class\', \'required\');
        input.id = \'testid_\' + test_cnt;
        box.appendChild(input);

        test_cnt++;
        window.console.log(\'Element injected\');
        return false;
    }   
</script>

<br/>
<button  onclick="time_test_Isvalid(this);  return false;"  class="btn">  Test validation </button>
<button  onclick="inject_test_input(this, \'inject_test_box\'); time_test_Isvalid(this); return false;"  class="btn">   Inject field + Test validation </button>
<div id="inject_test_box"></div>
';
$use_labels = 1;
$add_for = 1;
$add_dash_lbl = 1;
for($i = 0; $i < 10000; $i++)
{
    if ($use_labels)
    {
        $lbl_for = $add_for  ?  'for="dummy_field_'.$i.'"'  :  '';
        $lbl_id  = $add_dash_lbl  ?  'id="dummy_field_'.$i.'-lbl"'  :  'id="dummy_field_'.$i.'-nodashlbl"';
        echo '<label '.$lbl_for.' '.$lbl_id.' class="label">Element '.$i.'</label> ';
    }
    echo '<input id="dummy_field_'.$i.'" type="text" name="dummy_field_'.$i.'" /> ';
}
?>

</form>

The above will

  • add 10,000 form elements into the form
  • add a button to trigger and TIME validation of isValid() (validate.js) without submitting the form (open console to see output)
  • add a button to inject a new form element into the DOM and re-trigger validation, so that we can verify that validation behaviour and performance after validation has been trigger once and some element has been added after it

Validations (testing times incude only isValid() of validate.js):

All fields have labels with ID-lbl
             1st      2nd, +           After Injecting new field
BEFORE:      1070ms    about 250ms      250ms - 550ms
AFTER:        630ms    about 250ms      370ms - 630ms

All 10,000 labels are missing, e.g. input buttons or buttons
             1st      2nd, +           After Injecting new field
BEFORE:     5700ms   about 180ms    180ms - 430ms
AFTER:       450ms   about 180ms    230ms - 480ms


All 10,000 labels don't have id like ID-lbl
             1st       2nd, +         After Injecting new field
BEFORE:   28700ms    about 220ms      220ms - 440ms
AFTER:      620ms    about 220ms      360ms - 650ms
avatar ggppdk ggppdk - open - 29 Apr 2016
avatar ggppdk ggppdk - change - 29 Apr 2016
Status New Pending
avatar joomla-cms-bot joomla-cms-bot - change - 29 Apr 2016
Labels Added: ?
avatar ggppdk ggppdk - change - 29 Apr 2016
The description was changed
avatar ggppdk ggppdk - change - 29 Apr 2016
The description was changed
avatar ggppdk ggppdk - change - 29 Apr 2016
The description was changed
avatar ggppdk ggppdk - change - 29 Apr 2016
The description was changed
avatar ggppdk ggppdk - change - 29 Apr 2016
The description was changed
avatar brianteeman brianteeman - change - 29 Apr 2016
Category JavaScript
avatar Fedik
Fedik - comment - 30 Apr 2016

looks like not bad idea :wink:

avatar ggppdk
ggppdk - comment - 30 Apr 2016

@Fedik, thanks for your comments ,

About

jQuery(form).find('label')

Actually if i had thought of it and i wish it could be like that , but it can not be ...
According to HTML specifications ... fields (and their labels) can be placed outside:

<form> ... </form>
  • and the way it is done is by using the form attribute
<input ...  form="form-TAG-ID" />

... which gives me the opportunity to say that current isValid() implementation needs fixing this !
it needs to use

  • form.elements + fieldsets to be compatible with HTML5 specification

About not using,:
$el.data('label') === false;

  • used it like === undefined to be backwards compatible

... and because

  • === false means field does not have a label
  • === undefined means the field has not been examined yet (e.g. 1st validation or newly injected element)

about
parentTagName = $parentElem.get(0).tagName.toLowerCase();

  • we already use it in other places
  • it computation effect is minimal and used only for some fields only
  • not all document types guarantee it to be uppercase
  • in future the standards maybe different

about no need to use form.elements

  • i remember needing this to properly work with
<fieldsets class="radio" ...>
<fieldsets class="checkbox"  ...>

but i will check, i am not sure if it is needed, but anyway it will not provide any speed benefit (i think)

  • also form.elements contains for sure the elements *that are outside the *form:
<form>
avatar Fedik
Fedik - comment - 30 Apr 2016

According to HTML specifications ... fields and their labels can be placed outside

maybe it could be, but it never happens in Joomla world, I think

$el.data('label') === false;
keep existing behaviour and avoid breaking some 3rd party code

I do not see b/c problem here.
Before use $el.data('label').length they already should check that $el.data('label') exists, other way the code $el.data('label').length !== 0 will fail, when in some contingency the label will not exist (can be removed by other js or so)
JavaScript is dynamic thing :wink:

parentTagName = $parentElem.get(0).tagName.toLowerCase();
not all document types guarantee it to be uppercase

one more note, you need to use nodeName instead tagName, then all be fine

avatar ggppdk
ggppdk - comment - 30 Apr 2016

-1- about ... searching label only inside < form > using: jQuery(form).find('label')

  • it is better to be compliant (if / when possible while having little performance gain to break standards)
  • but also current Joomla code checks all the DOM, so ... we would change old behaviour, if we only searched inside the FORM block

so better let it be compliant and B/C too

-2- about using nodeName instead of tagName, i agree with you that it is better

  • just for the purpose of checking if the node is a LABEL , it makes no difference in other case yes it would make difference e.g. if checking if element is a text node
  • nodeName also supports these nodes: attribute nodes, text nodes, comment nodes

i will replace it (still current code works)

-3-
about checking
$label = $elem.data('label');
and checking .length

  • it was like this before ... (current joomla code) always return a jQuery object with $form.find('label[for="' + id + '"]');

Effectively these must and do have the same effect:
Old code:

$label = $form.find('label[for="' + id + '"]');

New code:

if (...needed...) refreshFormLabels(form, form.elements);
$label = $elem.data('label');

both will guarantee a jQuery object
so we do not need to check if it is null / false / undefined
because the new code of findLabel() does the same thing as the previous

  • it will return a jQuery object ... and if label was not found then it returns zero length one
avatar ggppdk
ggppdk - comment - 30 Apr 2016

@Fedik

I just saw that i responed to a previous message in a confusing way
i am sorry, i wrongly said:

... $elem.data('label') ....

  • === false means field does not have a label

i meant if findLabel() returns false then label was not found

$elem.data('label') can only be these

  • undefined (label not yet searched)
  • jQuery Object with non-zero length (aka label was found)
  • jQuery Object with zero length (aka label was NOT found)

now 2 things:

-1- about your suggestion to setting $elem.data('label') to false instead instead of empty jQuery object

  • there is other code that examines it like in validateForm() of html5fallback.js, and maybe 3rd party code ? (i don't know)

so it is like the above for B/C, thus we much match the behaviour of what the selector would do:
$form.find('label[for="' + id + '"]');
it would return a zero length jQuery object if label was not found

-2- but your suggestion to check for non-null / not undefined value after refreshFormLabels() makes sense in ... case that refreshFormLabels() is bogus, or in case it was mis-used,

  • i will add check for it too,

but still it should never happen with current code

avatar Fedik
Fedik - comment - 30 Apr 2016

well, maybe you are right about use empty jQuery object in $elem.data('label'),
but still, before check $elem.data('label').length there always need to check that $elem.data('label') actually returns some value, cause the label easily can be removed by external script

avatar ggppdk
ggppdk - comment - 30 Apr 2016

yes, i will add check for it

about removing label by external script and letting the field in place
you are right

  • but it is usually the opposite, leave the label in place and replace the field, OR ... replace both, in these cases the code will work
  • plus current Joomla JS code does not check if the label has been removed !, current Joomla code will use whatever is stored inside .data('label')
  • and here we are discussing about if .data('label') is set to some value (removed by 3rd party script ??), and not discussing if the label has been removed, which i thought to add check, but since current Joomla code does not check and because it would code extra, i have not added it

now, i am adding both of your suggestions

  • for checking if .data('label') is non-empty
  • use nodeName instead of tagName for checking the label

updating html5fallback.js too

avatar ggppdk
ggppdk - comment - 30 Apr 2016

@Fedik

i was about to replace tagName with nodeName, and i have just thought that the code may not be needed at all !
(and removing it, would be good for performance too, in case of many label missing e.g. buttons)

if ($label.length <= 0)
{
    var $parentElem = $elem.parent();
  parentTagName = $parentElem.get(0).tagName.toLowerCase();

    if(parentTagName == "label")
    {
        $label = $parentElem;
        $elem.data('label', $label);
    }
}

look at it ... it catches the case of a input inside a label TAG which does not have either of atributes: id="ID-lbl" and for="ID", otherwise it would have been found already by refreshFormLabels()

  • i added to validate.js because it exists in html5fallback.js

so because

  • there is no such joomla code to create such a thing (there is no such form element)
  • and because current JS code of validate.js is not checking it

i think to remove it, not encourage someone writting and using this case and also improving performance in some cases

but it does exist in existing code of html5fallback.js, shall i leave it for html5fallback.js and only remove it from validate.js ?

avatar ggppdk
ggppdk - comment - 2 May 2016

@Fedik

i have changed the
elem.data('label') to store false if label is missing as you had suggested

  • that is how it was before this pull request, but i was trying to make the elem.data('label') to be shared with the findLabel() of html5fallback.js, because for html5fallback.js the return value of findLabel() is an empty jQuery object instead of false,

To summarize new changes / fixes
1. now it is doing both:
-- setting elem.data('label') to false if label is missing
-- elem.data('label') is now properly shared with html5fallback.js, in case that html5fallback.js is re-used in the future (your recently merged PR has suppressed its validateForm())
2. also added a check for the case that an invalid field has no label, current behaviour is not add any message at the top of the page, with this new check if label is missing the invalid field message will be added inline
3. also removed the case of checking input inside label that lucks both id="ID-lbl" and for="ID", no Joomla code is using this case, i had added it because it existed in html5fallback.js

avatar Fedik
Fedik - comment - 3 May 2016

@ggppdk I not sure whether worth to do anything with html5fallback.js
but thanks for your work, I try test more, as soon as find a time

btw, did you already checked about use form.elements vs form?

avatar ggppdk
ggppdk - comment - 6 May 2016

Hello

fixed the issue with form.elements not being used properly,

i found when bug was introduced,

  • it was when mootools code of validation.js was replaced with jQuery code

763c69f

with my last commit the issue of:

  • not all form field being included in validation (fields outside < form >)
  • and of fields of different forms being included in the validation, that have attribute form="other-form-id" is fixed
avatar Fedik
Fedik - comment - 7 May 2016

@ggppdk good find :+1:
also I see that the old code have searched the labels in similar way as you have suggested :wink:

if (!(el.labelref)) {
    var labels = $$('label');
    labels.each(function(label){
        if (label.get('for') == el.get('id')) {
            el.labelref = label;
        }
    });
}

but without caching

avatar ggppdk
ggppdk - comment - 7 May 2016

About old code being similar

  • yes similar, in terms of both using a label iterration, but that is where the similarity stops
  • the main difference is that my code (both) does and guarantees label iteration to only happens once,

Because

  1. during iteration it updates all form fields (and the fieldset class=radio/checkbox) and not only a single field
  2. and also for fields without a label, a "false" value is set to indicate no label found and thus avoiding the iteration to be triggered again

  • Iteration will never be triggerred again unless the DOM is modified to inject a new form field

That old code was slow
so the existing Joomla code

  • replaced it with HTML tag id look-up: ID-lbl

which is fast becaue of the TAG ID look,
but if a field does not have a label with (ID-lbl)

  • then the fallback is a full DOM search with the selector:
var $label = $('label[for="'+$elem.attr('id')+'"]');

Which this PR fixes

[EDIT]
for compatibility the create label hash contains all known needed case:
id="ID-lbl"
for="ID"

  • in future if we need more cases for the label hash we can add there with minimal cost e.g. marking the label of a fieldgroup ...
avatar Fedik
Fedik - comment - 18 May 2016

I have made some test, and a I am confused :smile:
The result not like I have expected.

I have made test in Global Configuration form.

Test the form with 200 of User groups (around 3000 inputs)

Before patch
first validation 143ms
3n-1n
second validation 31ms
3n-2n

After patch
first validation 164ms
3y-1y
second validation 43ms
3y-2y

In result the suggested patch is "slower".

Test the form with 200 of User groups + 10000 inputs (around 13000 inputs)

Here I have added the inputs as in the testing instruction.

Before patch
first validation 875ms
4n1
second validation 296ms
4n2

After patch
first validation 1010ms
4y1
second validation 130ms
4y2

In result the suggested patch have won only second "validation".

No idea :smile:
Maybe I have missed something.

avatar ggppdk
ggppdk - comment - 18 May 2016

Hello

the degrade in both cases is small and it is expected
(see explanation below)

  • if you have a form that all labels have
    ID-lbl you will see either no benefit or small degrade (as you see above)

  • also if you have a form with many "excluded" fields (e.g. ACL for 200 usergroups) again you will see small degrade on 1st validation because , the loop to find loop does not exclude them and makes a single pass for them

in any case the degrade is 10-15%

but for cases that this PR describes

the existing code can be up to 40x slower which huge performance issue for existing code
and that is what this PR fixes

avatar ggppdk
ggppdk - comment - 18 May 2016

Also at the time i forgot to say

  • you see 10%-15% degrade , and i explained why above,

but i forgot to say why your test is different than my initial posting in this PR

your tests did not included validateForm() from html5fallback , right ?

  • at the time i tested the validateForm() of html5fallback was still enabled / running

still this PR fixes performance issue with these 2 cases

  • many labels missing
  • many labels do not have id="ID-lbl"

in all other cases you will see small performance degrade on 1st run, since the validateForm() of ... html5fallback no longer runs

avatar ggppdk
ggppdk - comment - 18 May 2016

@Fedik

Thanks for the time you took to test this

1 more comment:
every time the cases that this PR fixes, are found by a user

  • then you get an performance issue reported here,
  • then a PR follows to fix the issue (e.g. exclude fields from the validation loop)

if we would rather do the above, or if we do not care of forms of 3rd party extensions being slow, because they have not "patched" their forms in a similar way, then we may ignore this PR

avatar Fedik
Fedik - comment - 18 May 2016

hm yes, without validateForm(), on latest staging branch
but case when both validateForm() and isValid() executed is a :bug: , should be executed only one validation.

avatar ggppdk
ggppdk - comment - 18 May 2016

Yes i don't say to re-enable validateForm() ,

it only needs to run to do cleanups,

  • only cleanup that i have seen is: remove placeholder text in no-supported browsers (IE8)

but anyway placeholder="..." is broken in IE8 (will cause a JS error into html5fallback.js)

so the text never gets added inside the input,

so indeed there is no need to remove it on submit either

avatar ggppdk
ggppdk - comment - 19 May 2016

@Fedik

you have only made 1 of my 3 tests, there are 2 more test, please try 10,000 inputs with them:

Labels missing

$use_labels = 0;
$add_for = 1;
$add_dash_lbl = 1;

Labels not having id="ID-lbl"

$use_labels = 1;
$add_for = 1;
$add_dash_lbl = 0;
avatar Fedik
Fedik - comment - 21 May 2016

Ok, I have made one more try ????
And in this synthetic test the result more visible

1 From test instruction

$use_labels = 1;
$add_for = 1;
$add_dash_lbl = 1;

Before patch:
first validation 844ms:
1n1_
second validation 280ms:
1n2_

After patch:
first validation 762ms:
1y1
second validation 328ms:
1y2

2 Labels missing

$use_labels = 0;
$add_for = 1;
$add_dash_lbl = 1;

Before patch:
first validation 9s:
2n1
second validation 245ms:
2n2

After patch:
first validation 569ms:
2y1
second validation 299ms:
2y2

3 Labels not having id="ID-lbl"

$use_labels = 1;
$add_for = 1;
$add_dash_lbl = 0;

Before patch:
first validation 27s:
3n1
second validation 288ms:
3n2

After patch:
first validation 771ms:
3y1
second validation 341ms:
3y2

though it is synthetic, I guess this can be count as success test ????

avatar Fedik Fedik - test_item - 21 May 2016 - Tested successfully
avatar Fedik
Fedik - comment - 21 May 2016

I have tested this item successfully on cef7b5d


This comment was created with the J!Tracker Application at issues.joomla.org/joomla-cms/10136.

avatar joomla-cms-bot
joomla-cms-bot - comment - 23 Jun 2016

This PR has received new commits.

CC: @Fedik


This comment was created with the J!Tracker Application at issues.joomla.org/joomla-cms/10136.

avatar ggppdk ggppdk - change - 23 Jun 2016
Status Pending Closed
Closed_Date 0000-00-00 00:00:00 2016-06-23 06:27:38
Closed_By ggppdk
avatar ggppdk ggppdk - close - 23 Jun 2016
avatar ggppdk ggppdk - close - 23 Jun 2016
avatar ggppdk ggppdk - close - 23 Jun 2016
avatar joomla-cms-bot
joomla-cms-bot - comment - 23 Jun 2016

This PR has received new commits.

CC: @Fedik


This comment was created with the J!Tracker Application at issues.joomla.org/joomla-cms/10136.

avatar joomla-cms-bot
joomla-cms-bot - comment - 23 Jun 2016

This PR has received new commits.

CC: @Fedik


This comment was created with the J!Tracker Application at issues.joomla.org/joomla-cms/10136.

avatar ggppdk
ggppdk - comment - 25 Jun 2016

@Fedik
Thanks for all your comments, i will re-do and split this PR into 3

  • label search performance
  • fix for not all HTML form fields including in validation
  • inline message validation messaging if label is missing

i am already using these in custom software

Add a Comment

Login with GitHub to post a comment