Skip to content

Commit

Permalink
Core: Add $.fn.labels, $.fn.form, and $.ui.escapeSelector methods
Browse files Browse the repository at this point in the history
$.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
  • Loading branch information
arschmitz committed May 6, 2015
1 parent 6a03b0f commit 803eaf2
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 4 deletions.
77 changes: 77 additions & 0 deletions tests/unit/core/core.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
63 changes: 63 additions & 0 deletions tests/unit/core/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
} )();
} );
7 changes: 7 additions & 0 deletions tests/unit/core/selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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" );
} );

} );
52 changes: 51 additions & 1 deletion ui/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 );
}
} );

Expand Down
6 changes: 3 additions & 3 deletions ui/selectmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 );
}
} );

Expand Down

0 comments on commit 803eaf2

Please sign in to comment.