r94386 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r94385‎ | r94386 | r94387 >
Date:21:19, 12 August 2011
Author:krinkle
Status:ok
Tags:
Comment:
Use .prop instead of .attr where appropriate

* Although jQuery covers for us in most cases (since .prop didn't exist before jQuery 1.6 and many people abused .attr in laziness of doing their own loop and setting the property manually). It's better to know what we're doing and call the function we intend to call. Both because jQuery may decide to stop rerouting common mistakes to .prop and because it makes code more readable.

* Aside from switching to prop, for boolean properties also replacing null/undefined with false and 'propname' with true. This is no longer being covered by jQuery when using prop directly (as it shouldn't). When an element is created the HTML specification says that the attribute should be set to it's name (ie. '<foo selected="selected">'), the properties however must remain boolean.

* Changing the attribute value for a boolean property (ie. checkbox.setAttribute( 'checked', 'checked' ) does not make the checkbox enabled. All it does is set the attribute. The reason this works with jQuery's attr() is because jQuery calls .prop internally after a bunch of checking inside attr().


-- Reference --
The list of keys that .attr and .removeAttr jQuery is currently (as of jQuery 1.6.1) remapping to use .prop and .removeProp can be found here:
https://github.com/jquery/jquery/blob/b22c9046529852c7ce567df13397849e11e2b9cc/src/attributes.js#L425
{
tabindex: "tabIndex",
readonly: "readOnly",
"for": "htmlFor",
"class": "className",
maxlength: "maxLength",
cellspacing: "cellSpacing",
cellpadding: "cellPadding",
rowspan: "rowSpan",
colspan: "colSpan",
usemap: "useMap",
frameborder: "frameBorder",
contenteditable: "contentEditable"
}
In addition to those, jQuery also maps these boolean properties to .prop when they are passed to .attr:
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,

(source: https://github.com/jquery/jquery/blob/b22c9046529852c7ce567df13397849e11e2b9cc/src/attributes.js#L9 )
Modified paths:
  • /trunk/phase3/resources/jquery/jquery.byteLimit.js (modified) (history)
  • /trunk/phase3/resources/jquery/jquery.checkboxShiftClick.js (modified) (history)
  • /trunk/phase3/resources/jquery/jquery.tabIndex.js (modified) (history)
  • /trunk/phase3/resources/mediawiki.action/mediawiki.action.watch.ajax.js (modified) (history)
  • /trunk/phase3/resources/mediawiki.special/mediawiki.special.recentchanges.js (modified) (history)
  • /trunk/phase3/resources/mediawiki.special/mediawiki.special.upload.js (modified) (history)
  • /trunk/phase3/resources/mediawiki/mediawiki.util.js (modified) (history)
  • /trunk/phase3/skins/common/config.js (modified) (history)
  • /trunk/phase3/skins/common/preview.js (modified) (history)
  • /trunk/phase3/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js (modified) (history)
  • /trunk/phase3/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js (modified) (history)
  • /trunk/phase3/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js (modified) (history)

Diff [purge]

Index: trunk/phase3/skins/common/config.js
@@ -67,9 +67,9 @@
6868 $( '.enableForOther' ).click( function() {
6969 var $textbox = $( '#' + $(this).attr( 'rel' ) );
7070 if ( $(this).val() == 'other' ) { // FIXME: Ugh, this is ugly
71 - $textbox.removeAttr( 'readonly' ).closest( '.config-block' ).slideDown( 'fast' );
 71+ $textbox.removeProp( 'readonly' ).closest( '.config-block' ).slideDown( 'fast' );
7272 } else {
73 - $textbox.attr( 'readonly', 'readonly' ).closest( '.config-block' ).slideUp( 'fast' );
 73+ $textbox.prop( 'readonly', true ).closest( '.config-block' ).slideUp( 'fast' );
7474 }
7575 } );
7676
Index: trunk/phase3/skins/common/preview.js
@@ -37,8 +37,8 @@
3838 // with the content of the loaded page
3939 var copyContent = page.find( copyElements[i] ).contents();
4040 $(copyElements[i]).empty().append( copyContent );
41 - var newClasses = page.find( copyElements[i] ).attr('class');
42 - $(copyElements[i]).attr( 'class', newClasses );
 41+ var newClasses = page.find( copyElements[i] ).prop('class');
 42+ $(copyElements[i]).prop( 'class', newClasses );
4343 }
4444
4545 $.each( copyElements, function(k,v) {
Index: trunk/phase3/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js
@@ -93,10 +93,8 @@
9494 byteLimitTest({
9595 description: 'Limit using the maxlength attribute',
9696 $input: $( '<input>' )
97 - .attr( {
98 - 'type': 'text',
99 - 'maxlength': '10'
100 - })
 97+ .attr( 'type', 'text' )
 98+ .prop( 'maxLength', '10' )
10199 .byteLimit(),
102100 sample: simpleSample,
103101 hasLimit: true,
@@ -120,10 +118,8 @@
121119 byteLimitTest({
122120 description: 'Limit using a custom value, overriding maxlength attribute',
123121 $input: $( '<input>' )
124 - .attr( {
125 - 'type': 'text',
126 - 'maxLength': '10'
127 - })
 122+ .attr( 'type', 'text' )
 123+ .prop( 'maxLength', '10' )
128124 .byteLimit( 15 ),
129125 sample: simpleSample,
130126 hasLimit: true,
@@ -183,10 +179,8 @@
184180 byteLimitTest({
185181 description: 'Limit using the maxlength attribute and pass a callback as input filter',
186182 $input: $( '<input>' )
187 - .attr( {
188 - 'type': 'text',
189 - 'maxLength': '6'
190 - })
 183+ .attr( 'type', 'text' )
 184+ .prop( 'maxLength', '6' )
191185 .byteLimit( function( val ) {
192186 _titleConfig();
193187
Index: trunk/phase3/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
@@ -304,7 +304,7 @@
305305 function( $table ) {
306306 //Quick&Dirty mod
307307 $table.find('tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)').remove();
308 - $table.find('tr:eq(2) td:eq(1)').attr('rowspan', '3');
 308+ $table.find('tr:eq(2) td:eq(1)').prop('rowspan', '3');
309309 $table.tablesorter();
310310 $table.find('.headerSort:eq(0)').click();
311311 }
@@ -317,7 +317,7 @@
318318 function( $table ) {
319319 //Quick&Dirty mod
320320 $table.find('tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)').remove();
321 - $table.find('tr:eq(2) td:eq(0)').attr('rowspan', '3');
 321+ $table.find('tr:eq(2) td:eq(0)').prop('rowspan', '3');
322322 $table.tablesorter();
323323 $table.find('.headerSort:eq(0)').click();
324324 }
Index: trunk/phase3/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
@@ -37,34 +37,34 @@
3838 // TODO abstract the double strictEquals
3939
4040 // At first checkboxes are enabled
41 - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined );
42 - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined );
 41+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false );
 42+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false );
4343
4444 // Initiate the recentchanges module
4545 mw.special.recentchanges.init();
4646
4747 // By default
48 - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' );
49 - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' );
 48+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true );
 49+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true );
5050
5151 // select second option...
5252 var $options = $( '#namespace' ).find( 'option' );
53 - $options.eq(0).removeAttr( 'selected' );
54 - $options.eq(1).attr( 'selected', 'selected' );
 53+ $options.eq(0).removeProp( 'selected' );
 54+ $options.eq(1).prop( 'selected', true );
5555 $( '#namespace' ).change();
5656
5757 // ... and checkboxes should be enabled again
58 - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined );
59 - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined );
 58+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false );
 59+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false );
6060
6161 // select first option ( 'all' namespace)...
62 - $options.eq(1).removeAttr( 'selected' );
63 - $options.eq(0).attr( 'selected', 'selected' );;
 62+ $options.eq(1).removeProp( 'selected' );
 63+ $options.eq(0).prop( 'selected', true );
6464 $( '#namespace' ).change();
6565
6666 // ... and checkboxes should now be disabled
67 - strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' );
68 - strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' );
 67+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true );
 68+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true );
6969
7070 // DOM cleanup
7171 $env.remove();
Index: trunk/phase3/resources/jquery/jquery.byteLimit.js
@@ -31,7 +31,7 @@
3232
3333 // Default limit to current attribute value
3434 if ( limit === undefined ) {
35 - limit = this.attr( 'maxLength' );
 35+ limit = this.prop( 'maxLength' );
3636 }
3737
3838 // Update/set attribute value, but only if there is no callback set.
@@ -40,10 +40,10 @@
4141 // Usually this isn't a problem since browsers ignore maxLength when setting
4242 // the value property through JavaScript, but Safari 4 violates that rule, so
4343 // we have to remove or not set the property if we have a callback.
44 - if ( fn === undefined ) {
45 - this.attr( 'maxLength', limit );
 44+ if ( fn == undefined ) {
 45+ this.prop( 'maxLength', limit );
4646 } else {
47 - this.removeAttr( 'maxLength' );
 47+ this.removeProp( 'maxLength' );
4848 }
4949
5050 // Nothing passed and/or empty attribute, return without binding an event.
Index: trunk/phase3/resources/jquery/jquery.checkboxShiftClick.js
@@ -18,7 +18,7 @@
1919 $box.slice(
2020 Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ),
2121 Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1
22 - ).attr( {checked: e.target.checked ? 'checked' : ''} );
 22+ ).prop( {checked: e.target.checked ? true : false} );
2323 }
2424 // Either way, update the prevCheckbox variable to the one clicked now
2525 prevCheckbox = e.target;
Index: trunk/phase3/resources/jquery/jquery.tabIndex.js
@@ -10,7 +10,7 @@
1111 $.fn.firstTabIndex = function() {
1212 var minTabIndex = null;
1313 $(this).find( '[tabindex]' ).each( function() {
14 - var tabIndex = parseInt( $(this).attr( 'tabindex' ), 10 );
 14+ var tabIndex = parseInt( $(this).prop( 'tabindex' ), 10 );
1515 // In IE6/IE7 the above jQuery selector returns all elements,
1616 // becuase it has a default value for tabIndex in IE6/IE7 of 0
1717 // (rather than null/undefined). Therefore check "> 0" as well.
@@ -35,7 +35,7 @@
3636 $.fn.lastTabIndex = function() {
3737 var maxTabIndex = null;
3838 $(this).find( '[tabindex]' ).each( function() {
39 - var tabIndex = parseInt( $(this).attr( 'tabindex' ), 10 );
 39+ var tabIndex = parseInt( $(this).prop( 'tabindex' ), 10 );
4040 if ( tabIndex > 0 && !isNaN( tabIndex ) ) {
4141 // Initial value
4242 if ( maxTabIndex === null ) {
Index: trunk/phase3/resources/mediawiki.action/mediawiki.action.watch.ajax.js
@@ -75,9 +75,9 @@
7676 // Bug 12395 - update the watch checkbox on edit pages when the
7777 // page is watched or unwatched via the tab.
7878 if ( watchResponse.watched !== undefined ) {
79 - $( '#wpWatchthis' ).attr( 'checked', 'checked' );
 79+ $( '#wpWatchthis' ).prop( 'checked', 'checked' );
8080 } else {
81 - $( '#wpWatchthis' ).removeAttr( 'checked' );
 81+ $( '#wpWatchthis' ).removeProp( 'checked' );
8282 }
8383 return true;
8484 };
Index: trunk/phase3/resources/mediawiki.special/mediawiki.special.upload.js
@@ -252,9 +252,9 @@
253253 $( '.mw-upload-source-error' ).remove();
254254 if ( this.checked ) {
255255 // Disable all inputs
256 - $( 'input[name!="wpSourceType"]', rows ).attr( 'disabled', true );
 256+ $( 'input[name!="wpSourceType"]', rows ).prop( 'disabled', 'disabled' );
257257 // Re-enable the current one
258 - $( 'input', currentRow ).attr( 'disabled', false );
 258+ $( 'input', currentRow ).prop( 'disabled', false );
259259 }
260260 };
261261 }() );
Index: trunk/phase3/resources/mediawiki.special/mediawiki.special.recentchanges.js
@@ -20,7 +20,7 @@
2121
2222 // Iterates over checkboxes and propagate the selected option
2323 $.each( checkboxes, function( i, id ) {
24 - $( '#' + id ).attr( 'disabled', isAllNS );
 24+ $( '#' + id ).prop( 'disabled', isAllNS );
2525 });
2626 },
2727
Index: trunk/phase3/resources/mediawiki/mediawiki.util.js
@@ -454,7 +454,7 @@
455455 }
456456
457457 if ( className ) {
458 - $messageDiv.attr( 'class', 'mw-js-message-' + className );
 458+ $messageDiv.prop( 'class', 'mw-js-message-' + className );
459459 }
460460
461461 if ( typeof message === 'object' ) {

Status & tagging log