]> source.dussan.org Git - jquery-ui.git/commitdiff
Core: Add $.fn.labels, $.fn.form, and $.ui.escapeSelector methods
authorAlexander Schmitz <arschmitz@gmail.com>
Mon, 27 Apr 2015 03:38:21 +0000 (23:38 -0400)
committerAlexander Schmitz <arschmitz@gmail.com>
Wed, 6 May 2015 01:43:57 +0000 (21:43 -0400)
$.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

tests/unit/core/core.html
tests/unit/core/core.js
tests/unit/core/selector.js
ui/core.js
ui/selectmenu.js

index 354fd8b8ea3914ba96e03fea555186359193081e..366eecebe78d06a5e4a154d28aad42b0095a3c90 100644 (file)
 
 <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>
index e50826e4d775d63e3db0436271dcd64e8ec9277c..ac9f28e00a13bb9a1901f7ff6296228f11f829ed 100644 (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 );
+} )();
 } );
index 39b94344b0a8525b769dfa251ae52adcd8881ba2..ff4bb064a450b07b0e90ced606874f2321585b9a 100644 (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" );
+} );
+
 } );
index 5e97e03c008e696a595b9bb5636905c2faadd470..0487df0a005a3236b2347913018f5447bcf59b7b 100644 (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 );
        }
 } );
 
index c52f6e4525179b5067f201cffd641829d96a721a..dad7a1b40bf3d1549521f1090b11fe30acf9911b 100644 (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 );
        }
 } );