User tests: Successful: Unsuccessful:
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:
For:
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
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
Status | New | ⇒ | Pending |
Labels |
Added:
?
|
Category | ⇒ | JavaScript |
@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>
<input ... form="form-TAG-ID" />
... which gives me the opportunity to say that current isValid() implementation needs fixing this !
it needs to use
About not using,:
$el.data('label') === false;
... and because
about
parentTagName = $parentElem.get(0).tagName.toLowerCase();
about no need to use form.elements
<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)
<form>
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
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
-1- about ... searching label only inside < form > using: jQuery(form).find('label')
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
i will replace it (still current code works)
-3-
about checking
$label = $elem.data('label');
and checking .length
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
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
now 2 things:
-1- about your suggestion to setting $elem.data('label') to false instead instead of empty jQuery object
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,
but still it should never happen with current code
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
yes, i will add check for it
about removing label by external script and letting the field in place
you are right
now, i am adding both of your suggestions
updating html5fallback.js too
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()
so because
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 ?
i have changed the
elem.data('label') to store false if label is missing as you had suggested
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
Hello
fixed the issue with form.elements not being used properly,
i found when bug was introduced,
with my last commit the issue of:
@ggppdk good find
also I see that the old code have searched the labels in similar way as you have suggested
if (!(el.labelref)) {
var labels = $$('label');
labels.each(function(label){
if (label.get('for') == el.get('id')) {
el.labelref = label;
}
});
}
but without caching
About old code being similar
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
That old code was slow
so the existing Joomla code
which is fast becaue of the TAG ID look,
but if a field does not have a label with (ID-lbl)
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"
I have made some test, and a I am confused
The result not like I have expected.
I have made test in Global Configuration form.
Before patch
first validation 143ms
second validation 31ms
After patch
first validation 164ms
second validation 43ms
In result the suggested patch is "slower".
Here I have added the inputs as in the testing instruction.
Before patch
first validation 875ms
second validation 296ms
After patch
first validation 1010ms
second validation 130ms
In result the suggested patch have won only second "validation".
No idea
Maybe I have missed something.
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
Also at the time i forgot to say
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 ?
still this PR fixes performance issue with these 2 cases
in all other cases you will see small performance degrade on 1st run, since the validateForm() of ... html5fallback no longer runs
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
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
hm yes, without validateForm()
, on latest staging branch
but case when both validateForm()
and isValid()
executed is a , should be executed only one validation.
Yes i don't say to re-enable validateForm() ,
it only needs to run to do cleanups,
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
Ok, I have made one more try
And in this synthetic test the result more visible
$use_labels = 1;
$add_for = 1;
$add_dash_lbl = 1;
Before patch:
first validation 844ms:
second validation 280ms:
After patch:
first validation 762ms:
second validation 328ms:
$use_labels = 0;
$add_for = 1;
$add_dash_lbl = 1;
Before patch:
first validation 9s:
second validation 245ms:
After patch:
first validation 569ms:
second validation 299ms:
$use_labels = 1;
$add_for = 1;
$add_dash_lbl = 0;
Before patch:
first validation 27s:
second validation 288ms:
After patch:
first validation 771ms:
second validation 341ms:
though it is synthetic, I guess this can be count as success test
I have tested this item
This PR has received new commits.
CC: @Fedik
Status | Pending | ⇒ | Closed |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2016-06-23 06:27:38 |
Closed_By | ⇒ | ggppdk |
This PR has received new commits.
CC: @Fedik
This PR has received new commits.
CC: @Fedik
looks like not bad idea