-
Notifications
You must be signed in to change notification settings - Fork 692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New features #751
New features #751
Conversation
**Closes #750, #721, #605, #467, #454, #436, #232, #163, #86** ### Many new features and improvements: 1. Expanded on iconSet concept to create linkInfo option to allow configuration of all header link attributes, including icons, text, and titles.s. 2. Menu height can now be dynamically resized via the jQuery UI resizable widget, if loaded and resizableMenu option set to true. 3. Fixed some issues w/ the menu height getting mangled when filtering was in used. 4. Modifed how header option is used. Can still set to false to hide header, but now it is also used to specify what header links are seen and in what order. 5. The multiselect can now also be used as a fixed list box (always open; no pop-up) via the listbox option. 6. New options groupsSelectable and groupsCollapsable control whether option groups can be selected and/or collapsed, respectively. 7. Option groups can now be set to be collapsable. A new small button shows up at the left of each option group heading to collapse/expand the option group. 8. New resync and value methods allow quickly updating the widget options checked state from the underlying native select. 9. Can now use the up/down keys on closed single select to change the selected option if the button is focussed. 10. Fixed a previously unknown bug caused by binding menu events to the outer $menu container div instead of the $checkboxes container. This bug caused the menu events handler to also try to handle header events. 11. Added the ability to reference option groups by their label or index for use as a filter. Now usable programmatically in addOption, checkAll, uncheckAll, flipAll, collapseAll, and expandAll. 12. Simplified the auto width determination for the _setMenuWidth() method by tweaking the related CSS rules. As a benefit, the _getScrollBarWidth() function has been eliminated. 13. Updated and simplified the _setMenuHeight() method to allow it to better work with filtering. 14. Re-wrote the filter widget--can now search option group headings and supports alternative filter rules. 15. Updated and added unit tests. 16. Updated demos and changed theme to Redmond--no more ugly construction orange. 17. Updated i18n files for new linkInfo option format.
By the description this is a lot of welcome changes. I won't be able to look it over today but I'll try to get it merged early this week. |
@mlh758 Understood... I put a lot of time into this, so I would expect that you would need some extra time in to look it over. I think it is pretty complete, but I will also continue to look for where I may have missed things. I look forward to any questions you may have as well. |
@mlh758 My intention is that this PR will essentially be my last contribution for a Version 3 release--I am hopeful that you will feel comfortable doing the release. 😉 To that end, I would appreciate it if you would take some added time to give this a critical look, not just from whether the feature set, options, methods, naming, comments, etc. make sense, but if there is anything I am missing or could have done better. There were a number of things that I specifically did and did not include in this:
|
I opened the callbacks demo and tried clicking on a collapsed option group. Instead of selecting everything within it, or doing nothing, it seems to call The filter also seems to no longer function in the dialog gist. |
I will take a look. When you say dialog gist, I am not sure what you are referring to. |
src/jquery.multiselect.filter.js
Outdated
$instance._oldToggleChecked(flag, group, true); | ||
// is fired, only the currently displayed filtered inputs are checked if filter entered. | ||
var instance = this.instance, | ||
filter = this.$input[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer having a var
per declaration instead of combining them into a single statement.
src/jquery.multiselect.filter.js
Outdated
$rows.eq(i).show(); | ||
return $inputs.get(i); | ||
var term = this.$input[0].value.toLowerCase().replace(/^\s+|\s+$/g,''), | ||
filterRule = this.options.filterRule || 'contains', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here about a var per variable.
src/jquery.multiselect.filter.js
Outdated
// If not searching in groups then show all group headings in the results. | ||
$checkboxes.find('.' + optgroupClass).removeClass(hiddenClass); | ||
} | ||
var filteredInputs = $checkboxes.children().map(function(x) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer variable names more descriptive than x
and y
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 These are just index variables... what would you suggest as an alternative... index1 and index2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
groupIndex
and optionIndex
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 However, if there are no groups, then groupIndex would actually be the optionIndex.
(There would only be options.) Thus, this would be confusing for the no groups case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that wouldn't be any good. We could probably reassess what it looks like after the filtering issue is worked out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, leave it as x
and y
for now then.
src/jquery.multiselect.js
Outdated
var linkInfo = ( this.linkInfo = $.extend(true, {}, linkDefaults, options.linkInfo || {}) ); | ||
|
||
// Helper function used below | ||
function _linkHTML(linkTemplate, linkID) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably live as it's own somewhere else, it doesn't need to be assigned to the widget itself but it's not capturing anything in here in a closure and it's not a callback so it doesn't need to be a nested declaration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 If you look closely, it uses the linkInfo variable just above it. I originally had it outside of _create(), but I had to reference this.linkInfo. Since _create() is the only thing using it, I just decided to pull it inside of _create().
Would you prefer it be outside of _create()? Keep in mind it will still need to use the linkInfo stuff somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_create is assigning it to this.linkInfo so the _linkHTML method would be able to access it that way. I would prefer to have it outside the _create method since it isn't a callback, and the data it's capturing in the closure is also accessible via this
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 Ok, I will move it back outside _create() then.
src/jquery.multiselect.js
Outdated
'title': 'Collapse' | ||
}, | ||
'expand': { | ||
'class': 'NOT USED', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these even need to exist if they're not used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mainly there for documenting so that people do not think they were missed and try to use them. I can remove them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 So do you want me to remove them then?
I could put the "NOT USED" bit in comments if that would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can just remove them, not really a need for a comment either. It seems like their lack of existence would be enough to indicate they aren't used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 ok
src/jquery.multiselect.js
Outdated
var prev, | ||
current = null, | ||
next = null; | ||
self.$inputs.each( function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a separate method, not a block dropped into the middle of they key handlers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 I struggled about what to do with this. It closes a PR, but it is quite a bit of code to just add specialized up/down key functionality for a closed single select. I could make a separate method, but it will only be used in the keydown handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change itself is welcome, It's just a lot of extra code in them middle of an already large, awkwardly indented block. Putting it into a method like handleButtonKeyboardNav
and just passing that in would be cleaner here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 ok will do
src/jquery.multiselect.js
Outdated
// optgroup label toggle support | ||
this.$menu.on('click.multiselect', '.ui-multiselect-optgroup a', function(e) { | ||
self.$checkboxes.on('click.multiselect', 'a', function(e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is now binding to checkboxes instead of menu, the method name should change accordingly.
src/jquery.multiselect.js
Outdated
@@ -549,6 +700,34 @@ | |||
checked: nodes.length ? nodes[0].checked : null | |||
}); | |||
}) | |||
// collapse button | |||
.on('click.multiselect', 'button', function(e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe specify a class for the collapse button so it's more specific to that button instead of any button?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 Ok. BTW, are you ok w/ a button vs a styled anchor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'm okay with it being a button.
src/jquery.multiselect.js
Outdated
|
||
// Close each widget when clicking on any other element/anywhere else on the page, | ||
// another widget instance, or when scrolling w/ the mouse wheel outside the menu button. | ||
self.document.on('mousedown.' + self._namespaceID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These document listeners don't get destroyed when the widget is destroyed, and are created for each widget.
If the widget is destroyed and a new one created, these leak and multiply.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 Ok, I will have to address that in the destroy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the following below... doesn't this.document.off(this._namespaceID);
get rid of the document listeners for this widget's namespace?
destroy: function() {
// remove classes + data
$.Widget.prototype.destroy.call(this);
// unbind events
this.document.off(this._namespaceID);
$(this.element[0].form).off(this._namespaceID);
if (!this.options.listbox) {
this.$button.remove();
}
this.$menu.remove();
this.element.show();
return this;
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what it is supposed to be doing, and in master it appears to be (I just double checked). I wonder if another change along the way broke that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like I was removing the initial period from the namespace ID and it is needed for off().
|
||
// The following adds up item heights. If the height sum exceeds the option height or if the number | ||
// of item heights summed equal or exceed the native select size attribute, the loop is aborted. | ||
// If the loop is aborted, this means that the menu must be scrolled to see all the items. | ||
$checkboxes.find('li,a').each( function() { | ||
self.$checkboxes.find('li:not(.ui-multiselect-optgroup),a').filter(':visible').each( function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why exclude the option groups here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 Because the option group li's contain a ul which contains all the child option li's. My concern was that I would be adding the height of the container li plus the heights of each child li. I do not want to be "double dipping" on the heights.
The anchor is really supposed to handle the height of the option group heading, but I do have a lingering question of whether the collapse button may be taller. The bottom line is that need a way to get the height of the optgroup headings without the child option li's. The current method is not perfect, but it seems to work well enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, since find also goes into descendants. Makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 If you have any thoughts on any better ways to account for the heights of the option group headings, I am all ears. As I noted, I do not think the current method is perfect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't at this time, I just wanted to know why the change was made since it was so recently added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 The main potential problem here is if someone is using collapsable groups, then you also have the height of the collapse buttons to consider. If someone changes the CSS to make the buttons taller, then potentially the button heights should be examined instead of the anchor heights. Right now, I am not seeing much of a difference (I have not tried changing the button heights), but it has occurred to me that if the element being examined is an anchor, maybe I should look for a sibling button and use the Math.max height of the two (button and anchor).
This really should have been 10 different PRs, would have been a lot easier on both of us. Especially with adding #721 in after I asked you not to. I would prefer that as a separate widget like the filter is a separate widget since it changes a lot of behavior. |
The extra demos gist I made: https://gist.github.com/mlh758/b1b24931db7705441f9786bd68cf3569 One of them is for a dialog, we've used it before for testing/troubleshooting. That's where the speed test thing came from. |
Ok, I see that and I agree it is an issue. However, there is really more to this. You have to consider what happens when the filter is in use, also. The filter was previously set up so that you could not search for optgroup labels, but it would always show all optgroup headings. If you clicked on a heading, you would only set the checks for the options that were visible / not filtered out. Now, since I tried to enable searching for option group headings in the spirit of the PRs that requested it, if the optgroup searching option is enabled, it only shows optgroups for which its heading or one of its children contains the search text. If an optgroup heading matches, then all of its children are shown, also. So... now there are 3 cases that can show / hide options... for the filter cases, clicking the option group heading is only supposed to change the visible options. Consider the case of a filter in use + collapsed option group and someone clicks on the heading... The way I intended to work was that only visible optgroup options would get change on a heading click, regardless of how they got visible/hidden. I could change the behavior if a filter is in use vs not... your thoughts? |
It only showed option group headings where some children were visible. This meant you could search for something and have results displayed where you just had a few options left under a couple of option groups. Clicking one of those option groups would select only the visible members of that option group. I think re-ordering the way the filter is done in your code might make this easier, and your addition of the hidden class further simplifies the problem.
The downside is some potential redundant adding/removal of the hidden class on some options as the option group filtering will potentially undo some of the filtering applied in step 1, but I'd be okay with that to simplify the mechanism. It looks like the collapse adds a class to the |
My enthusiasm for doing more PRs for this is fading at the moment (it will likely come back later in the future), so I needed to get as much done as I could before I feel the need to move on to other things.
Let's just say that we have a difference of opinion on this. I am not all that sold on the separate widget approach--it adds unnecessary overhead and perpetuates the use of jQuery UI, which I think needs to be phased out in favor of another class based approach. Also, #721 only removes the button and fixes the menu position in the html. It is basically a subtractive approach, and I tend to believe that additional widgets work better for adding or replacing things, not taking them away. If do this as another widget, I would end up having to replace quite a few main widget methods for just some small changes. Lastly, I believe that the groupColumns option is also a change in behavior/look but that is part of the main widget and not a separate widget--so that does not need to be a separate widget, but this does? |
Touche, leave it in. |
Case in point... try running the Animations demo w/ resizable enabled. |
Just a heads up, tomorrow I'll be leaving on a short vacation so I won't be responding much for the next 5 days. |
Ok thx I will probably post an update to this PR by Sunday. Have a good vacation! |
1. Make optgroup clicks function on the optgroups regardless of collapsed status. However, filtered status is still observed for excluding from clicks. 2. Fixed issue of filter not working in dialogs. Apparently, widget instance() method is no longer supported, per StackOverflow thread. Using .data('ech-multiselect') to fetch the instance now--this works w/ the dialog. 3. When filtering, now hide optgroup headings unless a match on children li's or optgroup heading (if searching optgroup headings option set) 4. Formatting tweaks. 5. Switch to using arrays for list items in options... e.g. header option to specify displayed links in desired order. 6. As IE10+ is IE minimum, switched to using array.indexOf() for array lookups and classList where appropriate. 7. Ditched NOT USED in linkDefaults object. 8. Made _linkHTML() an internal method function. 9. Implemented classes for the optgroup collapse button and the optgroup label anchor. 10. Fixed resync. find('option') not find('options') 11. Fixed destroy. '.' prefix on namespace is not optional for .off() (document listeners issue) 12. Moved single select button up/down code to separate method and simplified that code. 13. Changed bindMenuEvents to bindCheckboxEvents
@mlh758 I know you are on vacation... Let me know if the changes I made will address the |
@mlh758 Oops closed this by mistake. |
Welp, that means this is my fault. Can you link that Stack Overflow thread? |
src/jquery.multiselect.filter.js
Outdated
@@ -258,7 +256,7 @@ | |||
*/ | |||
destroy: function() { | |||
$.Widget.prototype.destroy.call(this); | |||
this.$input.val('').trigger("keyup"); | |||
this.$input.val('').trigger("keyup").off('keydown input search'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call here.
if (!$button.hasClass('ui-state-disabled')) { | ||
$button.addClass('ui-state-focus'); | ||
if (!this.classList.contains('ui-state-disabled')) { | ||
this.classList.add('ui-state-focus'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I didn't know this was available in IE10.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per https://developer.mozilla.org/en-US/docs/Web/API/Element/classList , "Basic Support" is available... Note that I am not using anything fancy w/ it.
Code wise I'm happy. My only hangup is wanting to figure out what happened with the I haven't had a chance to pull this down and test it, but I don't see any more code changes I want. |
Unfortunately, my computer crashed last night that I have been doing these updates on. Laptop screen is toast. It is an old laptop, so I am not sure what I am going to do at the moment. (typing this on work computer... cannot use it for updates) All I can say on the instance thing was I kept getting an error of "no such method 'instance' in multiselect instance" in multiselectfilter.js line## in the dialog gist ... so I had to figure out a different approach and I did some research on StackOverflow to find the data() approach. I do not have the thread readily at hand, unfortunately. I also saw a comment on the jQuery UI forum that the instance method was buggy. Either way, I thought maybe using the data approach may be more reliable. One thing to look at is that I think I put in something like this.element.multiselect().data('ech-multiselect'), but in hindsight, I do not think the multiselect() part should be needed. (just use this.element.data('ech-multiselect')) |
Yup, that was caused by me pulling in an old version of jQuery UI for that demo. The filter thing was my fault. I'll leave your change in though since there's no reason to force an upgrade over a single method that is only convenience. If I find any issues in testing I'll figure out something else to do to fix things up but this will probably be fine to merge at this point. |
Here are the relevant posts, although I guess not needed now as problem solved: |
selectedListSeparator: ', ', // (string) This allows customization of the list separator. Use ',<br/>' to make the button grow vertically showing 1 selection per line. | ||
maxSelected: null, // (integer | null) If selected count > maxSelected, then message is displayed, and new selection is undone. | ||
openEffect: null, // (array) An array containing menu opening effect information. | ||
closeEffect: null, // (array) An array containing menu closing effect information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlh758 I changed these option names (show/hide -> openEffect/closeEffect) to be more clear about what they actually do. However, I also tend to think that the widget may be relying on deprecated jQuery UI API signatures for these, as current docs mention the use of objects for animations. Therefore, these need to be re-looked at to see if they should be updated to reflect the current API. I will post an issue for this.
This reverts commit 1815e57.
@mlh758 Are you still testing this? Are there any further changes needed? |
I only got back into town yesterday evening. I haven't had a chance to pull it down yet. |
oops sorry 😊 |
Everything looks good. Going to merge now. |
I published version 3 pre-release with minified files attached so people can start messing with it. |
This closes #750, closes #721, closes #605, closes #513, closes #467, closes #454, closes #436, closes #232, closes #231, closes #169, closes #163, and closes #86
Many new features and improvements: