Skip to content

Commit 803eaf2

Browse files
committed
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 jquerygh-1546
1 parent 6a03b0f commit 803eaf2

File tree

5 files changed

+201
-4
lines changed

5 files changed

+201
-4
lines changed

tests/unit/core/core.html

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,83 @@
108108

109109
<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>
110110

111+
<div id="labels-fragment">
112+
<label for="test">1</label>
113+
<div>
114+
<div>
115+
<form>
116+
<label for="test">2</label>
117+
<label>3
118+
<input id="test">
119+
</label>
120+
<label for="test">4</label>
121+
</form>
122+
<label for="test">5</label>
123+
</div>
124+
<div>
125+
<div>
126+
<form>
127+
<label for="test">6</label>
128+
</form>
129+
</div>
130+
</div>
131+
</div>
132+
<div>
133+
<div>
134+
<form>
135+
<label for="test">7</label>
136+
<label>
137+
</label>
138+
<label for="test">8</label>
139+
</form>
140+
<label for="test">9</label>
141+
</div>
142+
<div>
143+
<div>
144+
<form>
145+
<input id="test-2">
146+
<label for="test">10</label>
147+
</form>
148+
</div>
149+
</div>
150+
</div>
151+
</div>
152+
153+
<div id="weird-['x']-id"></div>
154+
</div>
155+
156+
<!-- This is intentionally outside the test fixture. We don't want this
157+
markup to be removed and reinserted between tests, as it will remove saved
158+
refrences in the tests. -->
159+
<div id="form-test">
160+
<input id="body:_implicit_form">
161+
<input id="body:_explicit_form" form="form-1">
162+
<form id="form-1">
163+
<input id="form-1:_implicit_form">
164+
<input id="form-1:_explicit_form" form="form-1">
165+
</form>
166+
<form id="form-2">
167+
<input id="form-2:_implicit_form">
168+
<input id="form-2:_explicit_form_other_form" form="form-1">
169+
</form>
170+
</div>
171+
<div id="form-test-detached">
172+
<input id="fragment:_implicit_form">
173+
174+
<!-- Support: Chrome > 33
175+
When an input with a form attribute is inside a fragment, and not contained by any form,
176+
the form property returns the proper form. However resetting the form does not reset the
177+
input. The following input is commented out to stop the test from failing in this case.
178+
<input id="fragment:_explicit_form" form="form-3">
179+
-->
180+
<form id="form-3">
181+
<input id="form-3:_implicit_form">
182+
<input id="form-3:_explicit_form" form="form-3">
183+
</form>
184+
<form id="form-4">
185+
<input id="form-4:_implicit_form">
186+
<input id="form-4:_explicit_form_other_form" form="form-3">
187+
</form>
111188
</div>
112189
</body>
113190
</html>

tests/unit/core/core.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,67 @@ test( "uniqueId / removeUniqueId", function() {
138138
equal( el.attr( "id" ), null, "unique id has been removed from element" );
139139
});
140140

141+
test( "Labels", function() {
142+
expect( 2 );
143+
144+
var expected = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ];
145+
var dom = $( "#labels-fragment" );
146+
147+
function testLabels( testType ) {
148+
var labels = dom.find( "#test" ).labels();
149+
var found = labels.map( function() {
150+
151+
// Support: Core 1.9 Only
152+
// We use $.trim() because core 1.9.x silently fails when white space is present
153+
return $.trim( $( this ).text() );
154+
} ).get();
155+
156+
deepEqual( found, expected,
157+
".labels() finds all labels in " + testType + ", and sorts them in DOM order" );
158+
}
159+
160+
testLabels( "the DOM" );
161+
162+
// Detach the dom to test on a fragment
163+
dom.detach();
164+
testLabels( "document fragments" );
165+
} );
166+
167+
( function() {
168+
var domAttached = $( "#form-test" );
169+
var domDetached = $( "#form-test-detached" ).detach();
170+
171+
function testForm( name, dom ) {
172+
var inputs = dom.find( "input" );
173+
174+
inputs.each( function() {
175+
var input = $( this );
176+
177+
asyncTest( name + this.id.replace( /_/g, " " ), function() {
178+
expect( 1 );
179+
var form = input.form();
180+
181+
// If input has a form the value should reset to "" if not it should be "changed"
182+
var value = form.length ? "" : "changed";
183+
184+
input.val( "changed" );
185+
186+
// If there is a form we reset just that. If there is not a form, reset every form.
187+
// The idea is if a form is found resetting that form should reset the input.
188+
// If no form is found no amount of resetting should change the value.
189+
( form.length ? form : dom.find( "form" ).addBack( "form" ) ).each( function() {
190+
this.reset();
191+
} );
192+
193+
setTimeout( function() {
194+
equal( input.val(), value, "Proper form found for #" + input.attr( "id" ) );
195+
start();
196+
} );
197+
} );
198+
} );
199+
}
200+
201+
testForm( "form: attached: ", domAttached );
202+
testForm( "form: detached: ", domDetached );
203+
} )();
141204
} );

tests/unit/core/selector.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,11 @@ test( "tabbable - dimensionless parent with overflow", function() {
254254
isTabbable( "#dimensionlessParent", "input" );
255255
});
256256

257+
test( "escapeSelector", function() {
258+
expect( 1 );
259+
260+
equal( $( "#" + $.ui.escapeSelector( "weird-['x']-id" ) ).length, 1,
261+
"properly escapes selectors to use as an id" );
262+
} );
263+
257264
} );

ui/core.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,15 @@ $.extend( $.ui, {
8888
if ( element && element.nodeName.toLowerCase() !== "body" ) {
8989
$( element ).blur();
9090
}
91-
}
91+
},
92+
93+
// Internal use only
94+
escapeSelector: ( function() {
95+
var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g;
96+
return function( selector ) {
97+
return selector.replace( selectorEscape, "\\$1" );
98+
};
99+
} )()
92100
} );
93101

94102
// plugins
@@ -126,6 +134,48 @@ $.fn.extend( {
126134
$( this ).removeAttr( "id" );
127135
}
128136
} );
137+
},
138+
139+
// Support: IE8 Only
140+
// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop
141+
// with a string, so we need to find the proper form.
142+
form: function() {
143+
return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form );
144+
},
145+
146+
labels: function() {
147+
var ancestor, selector, id, labels, ancestors;
148+
149+
// Check control.labels first
150+
if ( this[ 0 ].labels && this[ 0 ].labels.length ) {
151+
return this.pushStack( this[ 0 ].labels );
152+
}
153+
154+
// Support: IE <= 11, FF <= 37, Android <= 2.3 only
155+
// Above browsers do not support control.labels. Everything below is to support them
156+
// as well as document fragments. control.labels does not work on document fragments
157+
labels = this.eq( 0 ).parents( "label" );
158+
159+
// Look for the label based on the id
160+
id = this.attr( "id" );
161+
if ( id ) {
162+
163+
// We don't search against the document in case the element
164+
// is disconnected from the DOM
165+
ancestor = this.eq( 0 ).parents().last();
166+
167+
// Get a full set of top level ancestors
168+
ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() );
169+
170+
// Create a selector for the label based on the id
171+
selector = "label[for='" + $.ui.escapeSelector( id ) + "']";
172+
173+
labels = labels.add( ancestors.find( selector ).addBack( selector ) );
174+
175+
}
176+
177+
// Return whatever we have found for labels
178+
return this.pushStack( labels );
129179
}
130180
} );
131181

ui/selectmenu.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ return $.widget( "ui.selectmenu", {
9090
);
9191

9292
// Associate existing label with the new button
93-
this.label = $( "label[for='" + this.ids.element + "']" ).attr( "for", this.ids.button );
94-
this._on( this.label, {
93+
this.labels = this.element.labels();
94+
this._on( this.labels, {
9595
click: function( event ) {
9696
this.button.focus();
9797
event.preventDefault();
@@ -671,7 +671,7 @@ return $.widget( "ui.selectmenu", {
671671
this.button.remove();
672672
this.element.show();
673673
this.element.removeUniqueId();
674-
this.label.attr( "for", this.ids.element );
674+
this.labels.attr( "for", this.ids.element );
675675
}
676676
} );
677677

0 commit comments

Comments
 (0)