diff options
author | Alexander Schmitz <arschmitz@gmail.com> | 2015-04-26 23:38:21 -0400 |
---|---|---|
committer | Alexander Schmitz <arschmitz@gmail.com> | 2015-05-05 21:43:57 -0400 |
commit | 803eaf29f732a68793b806356096fe849b9f470b (patch) | |
tree | d1be32beb2405e1abf1db89a4d67c7ec4ec8c9ee | |
parent | 6a03b0f2ba422ec8edbf642eab692cbd20af2f4d (diff) | |
download | jquery-ui-803eaf29f732a68793b806356096fe849b9f470b.tar.gz jquery-ui-803eaf29f732a68793b806356096fe849b9f470b.zip |
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
-rw-r--r-- | tests/unit/core/core.html | 77 | ||||
-rw-r--r-- | tests/unit/core/core.js | 63 | ||||
-rw-r--r-- | tests/unit/core/selector.js | 7 | ||||
-rw-r--r-- | ui/core.js | 52 | ||||
-rw-r--r-- | ui/selectmenu.js | 6 |
5 files changed, 201 insertions, 4 deletions
diff --git a/tests/unit/core/core.html b/tests/unit/core/core.html index 354fd8b8e..366eecebe 100644 --- a/tests/unit/core/core.html +++ b/tests/unit/core/core.html @@ -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> diff --git a/tests/unit/core/core.js b/tests/unit/core/core.js index e50826e4d..ac9f28e00 100644 --- a/tests/unit/core/core.js +++ b/tests/unit/core/core.js @@ -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 ); +} )(); } ); diff --git a/tests/unit/core/selector.js b/tests/unit/core/selector.js index 39b94344b..ff4bb064a 100644 --- a/tests/unit/core/selector.js +++ b/tests/unit/core/selector.js @@ -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" ); +} ); + } ); diff --git a/ui/core.js b/ui/core.js index 5e97e03c0..0487df0a0 100644 --- a/ui/core.js +++ b/ui/core.js @@ -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 ); } } ); diff --git a/ui/selectmenu.js b/ui/selectmenu.js index c52f6e452..dad7a1b40 100644 --- a/ui/selectmenu.js +++ b/ui/selectmenu.js @@ -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 ); } } ); |