Core: Add $.fn.labels, $.fn.form, and $.ui.escapeSelector methods

$.fn.labels and $.fn.form mimic the native labels and form properties
$.ui.escapeSelector is for escaping attributes and urls for use as selectors

Closes gh-1546
This commit is contained in:
Alexander Schmitz 2015-04-26 23:38:21 -04:00
parent 6a03b0f2ba
commit 803eaf29f7
5 changed files with 201 additions and 4 deletions

View File

@ -108,6 +108,83 @@
<div id="dimensions" style="float: left; height: 50px; width: 100px; margin: 1px 12px 11px 2px; border-style: solid; border-width: 3px 14px 13px 4px; padding: 5px 16px 15px 6px;"></div>
<div id="labels-fragment">
<label for="test">1</label>
<div>
<div>
<form>
<label for="test">2</label>
<label>3
<input id="test">
</label>
<label for="test">4</label>
</form>
<label for="test">5</label>
</div>
<div>
<div>
<form>
<label for="test">6</label>
</form>
</div>
</div>
</div>
<div>
<div>
<form>
<label for="test">7</label>
<label>
</label>
<label for="test">8</label>
</form>
<label for="test">9</label>
</div>
<div>
<div>
<form>
<input id="test-2">
<label for="test">10</label>
</form>
</div>
</div>
</div>
</div>
<div id="weird-['x']-id"></div>
</div>
<!-- This is intentionally outside the test fixture. We don't want this
markup to be removed and reinserted between tests, as it will remove saved
refrences in the tests. -->
<div id="form-test">
<input id="body:_implicit_form">
<input id="body:_explicit_form" form="form-1">
<form id="form-1">
<input id="form-1:_implicit_form">
<input id="form-1:_explicit_form" form="form-1">
</form>
<form id="form-2">
<input id="form-2:_implicit_form">
<input id="form-2:_explicit_form_other_form" form="form-1">
</form>
</div>
<div id="form-test-detached">
<input id="fragment:_implicit_form">
<!-- Support: Chrome > 33
When an input with a form attribute is inside a fragment, and not contained by any form,
the form property returns the proper form. However resetting the form does not reset the
input. The following input is commented out to stop the test from failing in this case.
<input id="fragment:_explicit_form" form="form-3">
-->
<form id="form-3">
<input id="form-3:_implicit_form">
<input id="form-3:_explicit_form" form="form-3">
</form>
<form id="form-4">
<input id="form-4:_implicit_form">
<input id="form-4:_explicit_form_other_form" form="form-3">
</form>
</div>
</body>
</html>

View File

@ -138,4 +138,67 @@ test( "uniqueId / removeUniqueId", function() {
equal( el.attr( "id" ), null, "unique id has been removed from element" );
});
test( "Labels", function() {
expect( 2 );
var expected = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ];
var dom = $( "#labels-fragment" );
function testLabels( testType ) {
var labels = dom.find( "#test" ).labels();
var found = labels.map( function() {
// Support: Core 1.9 Only
// We use $.trim() because core 1.9.x silently fails when white space is present
return $.trim( $( this ).text() );
} ).get();
deepEqual( found, expected,
".labels() finds all labels in " + testType + ", and sorts them in DOM order" );
}
testLabels( "the DOM" );
// Detach the dom to test on a fragment
dom.detach();
testLabels( "document fragments" );
} );
( function() {
var domAttached = $( "#form-test" );
var domDetached = $( "#form-test-detached" ).detach();
function testForm( name, dom ) {
var inputs = dom.find( "input" );
inputs.each( function() {
var input = $( this );
asyncTest( name + this.id.replace( /_/g, " " ), function() {
expect( 1 );
var form = input.form();
// If input has a form the value should reset to "" if not it should be "changed"
var value = form.length ? "" : "changed";
input.val( "changed" );
// If there is a form we reset just that. If there is not a form, reset every form.
// The idea is if a form is found resetting that form should reset the input.
// If no form is found no amount of resetting should change the value.
( form.length ? form : dom.find( "form" ).addBack( "form" ) ).each( function() {
this.reset();
} );
setTimeout( function() {
equal( input.val(), value, "Proper form found for #" + input.attr( "id" ) );
start();
} );
} );
} );
}
testForm( "form: attached: ", domAttached );
testForm( "form: detached: ", domDetached );
} )();
} );

View File

@ -254,4 +254,11 @@ test( "tabbable - dimensionless parent with overflow", function() {
isTabbable( "#dimensionlessParent", "input" );
});
test( "escapeSelector", function() {
expect( 1 );
equal( $( "#" + $.ui.escapeSelector( "weird-['x']-id" ) ).length, 1,
"properly escapes selectors to use as an id" );
} );
} );

View File

@ -88,7 +88,15 @@ $.extend( $.ui, {
if ( element && element.nodeName.toLowerCase() !== "body" ) {
$( element ).blur();
}
}
},
// Internal use only
escapeSelector: ( function() {
var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g;
return function( selector ) {
return selector.replace( selectorEscape, "\\$1" );
};
} )()
} );
// plugins
@ -126,6 +134,48 @@ $.fn.extend( {
$( this ).removeAttr( "id" );
}
} );
},
// Support: IE8 Only
// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop
// with a string, so we need to find the proper form.
form: function() {
return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form );
},
labels: function() {
var ancestor, selector, id, labels, ancestors;
// Check control.labels first
if ( this[ 0 ].labels && this[ 0 ].labels.length ) {
return this.pushStack( this[ 0 ].labels );
}
// Support: IE <= 11, FF <= 37, Android <= 2.3 only
// Above browsers do not support control.labels. Everything below is to support them
// as well as document fragments. control.labels does not work on document fragments
labels = this.eq( 0 ).parents( "label" );
// Look for the label based on the id
id = this.attr( "id" );
if ( id ) {
// We don't search against the document in case the element
// is disconnected from the DOM
ancestor = this.eq( 0 ).parents().last();
// Get a full set of top level ancestors
ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() );
// Create a selector for the label based on the id
selector = "label[for='" + $.ui.escapeSelector( id ) + "']";
labels = labels.add( ancestors.find( selector ).addBack( selector ) );
}
// Return whatever we have found for labels
return this.pushStack( labels );
}
} );

View File

@ -90,8 +90,8 @@ return $.widget( "ui.selectmenu", {
);
// Associate existing label with the new button
this.label = $( "label[for='" + this.ids.element + "']" ).attr( "for", this.ids.button );
this._on( this.label, {
this.labels = this.element.labels();
this._on( this.labels, {
click: function( event ) {
this.button.focus();
event.preventDefault();
@ -671,7 +671,7 @@ return $.widget( "ui.selectmenu", {
this.button.remove();
this.element.show();
this.element.removeUniqueId();
this.label.attr( "for", this.ids.element );
this.labels.attr( "for", this.ids.element );
}
} );