Code Review for the classes changes #790

Closed
wants to merge 12 commits into
from

Projects

None yet

7 participants

@petersendidit
jQuery Foundation member

No description provided.

@scottgonzalez scottgonzalez commented on an outdated diff Apr 12, 2013
ui/jquery.ui.widget.js
@@ -357,6 +357,19 @@ $.Widget.prototype = {
return this;
},
+ _classes: function( key ) {
+ var out = [],
+ parts = key.split(" "),
@scottgonzalez
scottgonzalez Apr 12, 2013

key.split( " " ) (we removed the exception for strings)

@scottgonzalez scottgonzalez commented on an outdated diff Apr 12, 2013
ui/jquery.ui.widget.js
@@ -357,6 +357,19 @@ $.Widget.prototype = {
return this;
},
+ _classes: function( key ) {
+ var out = [],
+ parts = key.split(" "),
+ i;
@scottgonzalez
scottgonzalez Apr 12, 2013

undefined var at the top of the list

@scottgonzalez scottgonzalez and 2 others commented on an outdated diff Apr 12, 2013
ui/jquery.ui.widget.js
@@ -357,6 +357,19 @@ $.Widget.prototype = {
return this;
},
+ _classes: function( key ) {
+ var out = [],
+ parts = key.split(" "),
+ i;
+
+ for ( i = 0; i < parts.length; i++ ) {
@scottgonzalez
scottgonzalez Apr 12, 2013

@gibson042 @dmethvin @mikesherov I feel like I've asked this a million times, but what's the preferred looping method? Can someone who knows add it to the JS style guide?

@mikesherov
mikesherov Apr 12, 2013

From the "good" examples:

// Good
var i = 0;

for ( ; i < 100; i++ ) {
    // expressions
}
@scottgonzalez
scottgonzalez Apr 12, 2013

I see, thanks. I was expecting a section on looping.

@gibson042
gibson042 Apr 12, 2013

I'm pretty sure you're thinking about jquery/jquery#1046 (comment), which I'm loathe to put in the style guide because "whatever's smallest" (my driving factor) can change over time with the vagaries of minification and compression.

But at the moment, i = collection.length; while ( i-- ) and i = 0, len = collection.length; for ( ; i < len; i++ ) are almost always the best choices, with the latter being necessary for cases where forward iteration is required.

@scottgonzalez
scottgonzalez Apr 12, 2013

Yes, that's exactly what I was thinking of. I'll document that we prefer these two forms, but not say that they're absolute requirements.

@scottgonzalez scottgonzalez commented on an outdated diff Apr 12, 2013
ui/jquery.ui.tabs.js
@@ -696,8 +707,7 @@ $.widget( "ui.tabs", {
$( this ).remove();
} else {
$( this )
- .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
- "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
+ .removeClass( that._classes( "ui-tabs-active ui-tab ui-tabs-panel" ) + " ui-helper-reset ui-helper-clearfix ui-widget-content ui-state-default ui-state-active ui-state-disabled" )
@scottgonzalez
scottgonzalez Apr 12, 2013

Split the string of static classes onto the next line.

@scottgonzalez scottgonzalez commented on an outdated diff Apr 12, 2013
tests/unit/tabs/tabs_core.js
ok( element.hasClass( "ui-tabs" ), "main element is .ui-tabs" );
- ok( element.find( "ul" ).hasClass( "ui-tabs-nav" ), "list item is .ui-tabs-nav" );
- equal( element.find( ".ui-tabs-panel" ).length, 3,
+ ok( element.hasClass( "ui-widget" ), "main element is .ui-widget" );
+ ok( element.hasClass( "ui-widget-content" ), "main element is .ui-widget-content" );
+ ok( element.hasClass( "ui-corner-all" ), "main element is .ui-corner-all" );
+ ok( !element.hasClass( "ui-tabs-collapsible" ), "main element is not .ui-tabs-collapsible" );
+
+ ok( ul.hasClass( "ui-tabs-nav" ), "list is .ui-tabs-nav" );
+ ok( ul.hasClass( "ui-helper-reset" ), "list is .ui-helper-reset" );
+ ok( ul.hasClass( "ui-helper-clearfix" ), "list is .ui-helper-clearfix" );
@scottgonzalez
scottgonzalez Apr 12, 2013

I don't think we should test for ui-helper-clearfix and ui-helper-reset. They're implementation details.

@scottgonzalez scottgonzalez commented on an outdated diff Apr 12, 2013
tests/unit/tabs/tabs_methods.js
@@ -15,6 +15,22 @@ test( "destroy", function() {
});
});
+asyncTest( "destroy - ajax", function() {
+ expect( 1 );
+ domEqual( "#tabs2", function( done ) {
+ var element = $( "#tabs2" ).tabs({
+ load: function() {
+ setTimeout( function() {
@scottgonzalez
scottgonzalez Apr 12, 2013

No space before function(). (hooray for some exceptions?!?)

@scottgonzalez scottgonzalez commented on an outdated diff Apr 12, 2013
tests/unit/tabs/tabs_methods.js
@@ -15,6 +15,22 @@ test( "destroy", function() {
});
});
+asyncTest( "destroy - ajax", function() {
+ expect( 1 );
+ domEqual( "#tabs2", function( done ) {
+ var element = $( "#tabs2" ).tabs({
+ load: function() {
+ setTimeout( function() {
+ element.tabs( "destroy" );
+ done();
+ start();
+ }, 1);
@scottgonzalez
scottgonzalez Apr 12, 2013

We tend to skip the duration parameter when using this as "run ASAP, but not now".

@scottgonzalez
jQuery Foundation member

Overall this looks good. We should add tests for _classes() in widget_core.js.

@petersendidit
jQuery Foundation member

Added a few tests for _classes() and fixed the problems noted in the diff.

@scottgonzalez scottgonzalez commented on an outdated diff Apr 12, 2013
tests/unit/widget/widget_core.js
@@ -580,6 +580,24 @@ test( ".option() - deep option setter", function() {
div.testWidget( "option", "foo.qux.newOpt", "newVal" );
});
+test( "_classes", function(){
+ expect( 3 );
+ $.widget( "ui.testWidget", {
+ options: {
+ classes: {
+ "test": "class1 class2",
+ "test2": "class2 class3"
+ }
+ },
+ _create: function() {
+ equal( this._classes( "test" ), "test class1 class2" );
+ equal( this._classes( "test2" ), "test2 class2 class3" );
+ equal( this._classes( "test test2" ), "test2 class2 class3 test class1 class2" );
@scottgonzalez
scottgonzalez Apr 12, 2013

Let's either not use dupes here or just check for existence of each class individually. We shouldn't have a test tied to the implementation detail of the order in which classes are produced and whether the result is deduped.

@petersendidit
jQuery Foundation member

Should be all set now.

@petersendidit petersendidit commented on an outdated diff Apr 25, 2013
ui/jquery.ui.menu.js
.attr( "role", "presentation" )
.children( "a" )
.uniqueId()
+ // TODO: Need to use _classes here
.addClass( "ui-corner-all" )
@petersendidit
petersendidit Apr 25, 2013

How do we handle the _classes stuff here?

@petersendidit petersendidit commented on an outdated diff Apr 25, 2013
ui/jquery.ui.button.js
@@ -391,6 +403,7 @@ $.widget( "ui.buttonset", {
.map(function() {
return $( this ).button( "widget" )[ 0 ];
})
+ // TODO: need to figure out how to use _classes here
.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
@petersendidit
petersendidit Apr 25, 2013

How do we handle the _classes stuff here?

@petersendidit
jQuery Foundation member

All the widgets should be using _classes now. Buttonset and Menu have some awkward places that we need to work out.

@petersendidit petersendidit commented on the diff Jun 2, 2014
ui/button.js
@@ -384,6 +397,7 @@ $.widget( "ui.buttonset", {
.map(function() {
return $( this ).button( "widget" )[ 0 ];
})
+ // TODO: need to figure out how to use _classes here
@petersendidit
petersendidit Jun 2, 2014

Still have to figure out what to do here with these classes

@arschmitz
arschmitz Jun 2, 2014

@petersendidit I am currently working on the re-write of buttonset and button so this might not be an issue with the re-write. See the button-icon-span branch and the button page on the wiki.

@petersendidit
jQuery Foundation member

Rebased the changes and fixed the conflicts. The problem that we had with classes in menu was removed by some refactoring that already happened in the menu widget, yay. Button still has one weird spot and selectmenu needs to be classes-afied

@petersendidit petersendidit commented on the diff Aug 24, 2014
ui/selectmenu.js
@@ -141,6 +150,10 @@ return $.widget( "ui.selectmenu", {
// Initialize menu widget
this.menuInstance = this.menu
.menu({
+ // Adjust menu styles to dropdown
+ classes: {
+ "ui-menu": "ui-corner-bottom"
@petersendidit
petersendidit Aug 24, 2014

This worked out nicely was able to resolve needing to change the class of the menu by using the classes option.

@scottgonzalez
jQuery Foundation member

Some places use null while others use "" when there are no additional classes to apply. Let's standardize on empty strings.

@gabrielschulhof

I'm working on a way of allowing the classes option to be set via _setOptions(). In its current implementation it needs some buy-in from the widgets, but it lets you do things like this:

.dim {
  opacity: 0.5 !important;
}
$( ":ui-button:nth(1)" )
  .button( "option", "classes",
    $.extend( {},
      $( ":ui-button" ).button( "option", "classes" ),
      { "ui-button-icon-primary": "dim" } ) );

and

$( ":ui-button:nth(1)" )
  .button( "option", "classes",
    $.extend( {},
      $( ":ui-button" ).button( "option", "classes" ),
      { "ui-button-icon-primary": null } ) );

to turn dimming back off.

Despite this work, I am of the opinion that it's best not to promise that runtime changes to the classes option will be reflected immediately. If somebody changes the value, well, eventually, if they refresh the widget, or whatever, things will be reflected. That's fine, but don't expect us to update existing elements.

I'm looking into this to try and gauge how hairy it gets. If we can keep it relatively tidy, perhaps it's not such a big commitment to promise instant update via _setOption().

@scottgonzalez
jQuery Foundation member

@gabrielschulhof The sane use is element.button( "option", "classes.ui-button-icon-primary", "dim" );

Regarding implementation, if we're going to create a registry of elements, the whole implementation should be changed to use a consistent method for create/refresh/update. Also, the registries need to be complete.

@gabrielschulhof

@scottgonzalez I have re-implemented it without the need for storing references to elements. It's much better to use an extension point. I've made the process of resolving the various class keys extensible. autocomplete, button, dialog, selectmenu, and spinner needed an adjustment because they need to either selectively or unconditionally use this.widget() for the selector context rather than the default this.element.

I've also modified the selection process to not only look inside the context, but to also check the context itself for the presence of the class key.

In addition, I've modified some of the demos to loop over the demoed widgets and highlight the various structural elements, just to test the code. Please run the modified demo files to see the result!

@gabrielschulhof

Hmmm ... I'm curious. If we use this.widget() for the default context we may be able to reduce the invasiveness of the code, because this.widget() evaluates to this.element after all. I'll make another branch.

@gabrielschulhof

Yep. The version using this.widget() for the default context lets us touch two fewer files and it lets us return this._superApply( arguments ) more often. In this version, only autocomplete, dialog, and selectmenu need to override the default implementation of _elementsFromClassKey().

@scottgonzalez
jQuery Foundation member

I'm strongly against anything that does generic searching. It's a foot gun for nested widgets.

@gabrielschulhof

Yep, true. We're just discussing that with arschmitz.

@gabrielschulhof

Well, it's less pretty if we avoid selectors, but it's still doable without storing the element a second time. IOW if we already have this.headers, we don't need to have a second variable this._elements[ "ui-accordion-header" ] or something like that if we use the extension point. The additional benefit of the extension point is that it's a function call, so there's no chance that the reference returned will be out of date, and we don't need to re-invent DOM insertion/removal to also store a second reference in a registry.

I've only worked this branch up to accordion, but I'll try to work through all the widgets.

@scottgonzalez
jQuery Foundation member

You should really just discuss the ideas instead of creating so many implementations. Are you aware that on every update, the full list of classes is going to be provided to _setOption() so you'd be calling this function several times? I also have no idea what this test-sequence file exists for, but I don't want to get into a discussion about that in this PR.

@gabrielschulhof

@scottgonzalez Don't worry about the many implementations! They don't take long to create, and I get to know the code in the process. Don't worry about the test-sequence file either. Those branches are not intended for any kind of PR. I'm just playing around with various implementations, and I'd like to have code back up my comments. You also have an excellent eye for spotting problems a mile away looking at the code, so it's good for me to have code for you to look at :)

The idea behind the test-sequence.js file is that you open any of the demo files touched by the above-mentioned diff after pulling the corresponding branch and when you open it, the various structural elements of the widget being demoed become highlighted/de-highlighted every three seconds.

The code in test-sequence.js iterates over Object.keys( $.mobile[widgetBeingTested].prototype.options.classes ) and adds/removes the class "dim" to every one of those classes options via _setOptions(). The test sequence basically gives you a way to visually check whether the code is hitting all the structural elements. IOW, it makes sure the widget-specific implementation of _elementsFromClassKey() works.

I'm afraid I don't know which function you're referring to. AFAICT no function gets called several times. _setOptions() calls _setOption() exactly once, because only one option is being set: "classes". _setOption() in the widget factory iterates over all the classes in the new option value, and compares the value at each key value[ classKey ] to this.options.classes[ classKey ] and only calls _elementsFromClassKey() if the values differ.
So, if you only modify one of the values in the classes hash, _elementsFromClassKey() will only be called once.

@gabrielschulhof

I worked past button on this implementation ...

@gabrielschulhof

Here's a video of the test sequence in action hitting the button classes.

@arschmitz arschmitz referenced this pull request Oct 22, 2014
Closed

All: Classes Option #1369

@jzaefferer
jQuery Foundation member

Replaced by #1369

@jzaefferer jzaefferer closed this Oct 22, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment