Skip to content
This repository was archived by the owner on Oct 8, 2021. It is now read-only.

Add autodividers functionality to listview #3302

Merged
merged 4 commits into from
May 1, 2012
Merged

Add autodividers functionality to listview #3302

merged 4 commits into from
May 1, 2012

Conversation

townxelliot
Copy link
Contributor

Autodividers can be used to autogenerate dividers for a listview by setting data-autodividers="alpha" (dividers are unique, uppercased single characters from list items) or data-autodividers="full" (unique full text strings selected from list items) on the ul element of the listview.

It's also possible to apply a custom selector to find the elements to be used for divider text, with data-autodividers-selector="...".

This relates to #2466, but is a different implementation which will automatically update the list dividers if the list elements change. It also has a fairly thorough test suite and documentation.

* this happens if list items are added to the listview,
* which causes the autodividers to be regenerated.
*/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this plugin and I have a few ideas for how to make this a bit more flexible:

  • We should refactor it a bit so that a developer can specify a method, rather than a selector, for finding/calculating the divider text. The default implementation that uses a selector can just be the default method for the plugin.
  • We should provide a hook that would allow a developer to merge/sort list items into and under the dividers created.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the idea of being able to specify a method for selecting text, though this would only be possible programmatically and not in HTML attributes.

As for the merge/sort idea, I would see these as separate issues: they could be further plugins for listview. Perhaps a simple autosort plugin, and separately additional API on listview itself to be able to insert/delete/move elements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the "use a function as a selector" feature (see https://github.com/townxelliot/jquery-mobile/commit/27da421d2cbdd5a1a2b81ac716e2844859404c67)

@johnbender
Copy link
Contributor

@townxelliot

@jblas @toddparker and I are keen to get this merged in which is why we have so many comments/ideas. Hopefully it's not off-putting, and thanks for doing the hard work here!

@townxelliot
Copy link
Contributor Author

I'm more than happy to get the comments: it's great how responsive the jqm team is.

There are a couple of aspects I can work on for now, but longer term my suggestion is that listview provides some events when a listview changes (e.g. item(s) added, item(s) removed, item(s) changed). I'm not sure whether you also need API to manage inserting/deleting list items, as there are libraries to do that for you (knockout, for example). But it's worth a discussion.

@townxelliot
Copy link
Contributor Author

I've incorporated several of the comments into my branch.

I have left the autosort out as I feel that belongs in a separate extension; similarly, I've left out insert/delete functionality, as that feels like it should be in listview core (e.g. "add this li element after this divider").

I haven't attempted to add any events to listview, pending your feelings about that. In a previous version of this code, I think I added a listchange event to two places in the listview code (it was fairly straightforward). But I decided to go back to using the DOM events (which aren't uniformly supported, granted) rather than add hacks to listview itself.

One more thing I'm thinking of doing is testing whether or not the approach here (with prevAll, prevUntil, :contains selectors) is faster than simply rebuilding all the dividers, especially for large lists.

@townxelliot
Copy link
Contributor Author

https://github.com/townxelliot/jquery-mobile/commit/35c5feb8ea025268a9bb9d34a942afe37fee9147 replaces all dividers on the list when autodividers are first applied (around 20x faster than the previous approach).

I have retained the individual liAdded/liRemoved functions for now, as I was unable to just replace all the dividers when list elements are added or removed without breaking tests. I think it may also be more efficient when a single list item is added/removed than rebuilding all the dividers (though I haven't tested that yet).

@johnbender
Copy link
Contributor

@townxelliot

I think we should leave the list management up to the user (hopefully a kvo library) and just rest on refresh as our update api. I think Knockout can do the management because it controls the js object representation of the DOM view on which it can do diffs, see compareArrays.js. I could be wrong on that score though.

In this case I think we'd trigger listviewafterrefresh at the end of the refresh method in $.mobile.listview and the your plugin could bind to it and "sort things out". Using the widget factory trigger for prefix/consistency:

this._trigger("afterrefresh");

Hopefully that will help with the DOM* events. We just aren't in the business of managing state after the initial enhance, it's up to the user.

@townxelliot
Copy link
Contributor Author

On 20 December 2011 16:57, John Bender
[email protected]
wrote:

In this case I think we'd trigger listviewafterrefresh at the end of the refresh method in $.mobile.listview and the your plugin could bind to it and "sort things out". Using the widget factory trigger for prefix/consistency:

this._trigger("afterrefresh");

Agreed, that would resolve this for me.

Hopefully that will help with the DOM* events. We just aren't in the business of managing state after the initial enhance, it's up to the user.

Also agreed.

@jblas
Copy link
Contributor

jblas commented Dec 21, 2011

@townxelliot

Regarding your 20x speed-up ... you can get it even faster if you do 2 things:

  • Don't use each() and instead manually loop through the collection with something like a for(){} loop. This avoids the function-call per-list-item penalty that each() imposes.
  • Don't use the jQuery markup shortcuts to create elements, and instead use DOM methods, document.createElement() and element.setAttribute(), and element.insertBefore().

@townxelliot
Copy link
Contributor Author

On 21 December 2011 17:33, Kin Blas wrote:

@townxelliot

Regarding your 20x speed-up ... you can get it even faster if you do 2 things:

  • Don't use each() and instead manually loop through the collection with something like a for(){} loop. This avoids the function-call per-list-item penalty that each() imposes.
  • Don't use the jQuery markup shortcuts to create elements, and instead use DOM methods, document.createElement() and element.setAttribute(), and element.insertBefore().

Thanks for these pointers. I've rewritten the code slightly to use
this approach (though on the 400 element list it makes negligible
difference, but I guess it could matter more on mobile devices).

Please see https://github.com/townxelliot/jquery-mobile/commit/5c8ba5d3133d7e773edfa7dfc82e7b7af9c1184b
for the changes

@townxelliot
Copy link
Contributor Author

How would you chaps like to close off this work?

Would you like me to make the changes to listview to provide the event discussed above (listviewafterrefresh) so I can get rid of the DOM event watching?

Are we agreed that the autosort functionality suggested by jblas should go into a separate extension and can be left out of autodividers?

Are you happy with the implementation of the "selector as function"?

Is it OK that it's not possible to reset options on this extension after it has been applied (because it's not a fully-fledged widget)? If that's not OK, what is the correct pattern to fix that?

Thanks.

@toddparker
Copy link
Contributor

@townxelliot - I know Kin and John Bender are now out, but I think @johnbender said he's be checking in so hopefully you'll get and answer so we can button this up. Thanks for being so responsive.

@johnbender
Copy link
Contributor

@townxelliot

Would you like me to make the changes to listview to provide the event discussed above (listviewafterrefresh) so I can get rid of the DOM event watching?

Yah, go ahead and drop that trigger at the bottom of the refresh.

Are we agreed that the autosort functionality suggested by jblas should go into a separate extension and can be left out of autodividers?

Yes, or we can add it here later. Either way I want to get this merged in, we've dithered enough on all this hard work you've done.

Are you happy with the implementation of the "selector as function"?

I'm still of the opinion we should avoid supporting "words-as-functionality" in the same way we avoided it with the listview filter. I'd rather just provide a callback that's defined on the options where the default is the alpha check. Also, that way we can remove the "type" information because it will always just be a callback. The fact that it requires code change to alter the behavior is entirely acceptable.

Is it OK that it's not possible to reset options on this extension after it has been applied (because it's not a fully-fledged widget)? If that's not OK, what is the correct pattern to fix that?

I was going to do a quick refactor and move this off the global $.fn namespace and onto the listview prototype so the options can be altered at runtime there. The idea being that the invocation can be$("#my-list").listview('autodivide'), but the functionality can remain an external plugin that's removable from the build where it isn't necessary. If that idea bothers you as the original author please do let us know. Either way, we can handle making the options flexible.

[UPDATE] I didn't take a huge amount of time to understand the selector functionality so feel free to correct any assumptions I've made with the above.

@johnbender
Copy link
Contributor

@townxelliot

Any progress? I'll probably close this one out or move it to the feature requests page if there's no movement.

@townxelliot
Copy link
Contributor Author

Apologies John, I've been on another project and this work got sidelined. I will spend some time on it today and see if I can put something together from the comments etc.

This is used as the hook for autodividers to redraw the
dividers on a listview.
Can be used to autogenerate dividers for a listview by
setting data-autodividers="alpha" (dividers are unique,
uppercased single characters from list items) or
data-autodividers="full" (unique full text strings selected
from list items) on the ul element of the listview.

It's also possible to apply a custom selector to find the
elements to be used for divider text, with
data-autodividers-selector="...".

This relates to #2466,
but is a different implementation which will automatically
update the list dividers if the list elements change. It also
has a fairly thorough test suite and documentation.
@townxelliot
Copy link
Contributor Author

I've rewritten this branch and rebased it etc. (though I had to force push onto this branch), so it should be good to go.

As there are no reliable mechanisms for detecting when list items are added/removed, I added a listviewafterrefresh event. The autodividers extension is bound to that event; however, when it is detected, the autodividers are unbound from the event, they do their work (creating autodividers), refresh the listview, then rebind to the event. This is to avoid having to duplicate the code in listview which adds classes, styling and behaviour to list items: listview can still do it, but autodividers has to unbind from the refresh event to avoid an infinite loop.

The other change is that the autodividers are redrawn every time the list refreshes. Again, this is because there's no reliable way to detect individual items being added to the list.

A better solution longer term might be to provide API on listview to add items and dividers programmatically (triggering appropriate events), but I didn't think that was in the scope of this patch.

@townxelliot
Copy link
Contributor Author

@johnbender Any progress on reviewing this? Sorry it took me so long to rework it, but it would be great to get it closed off.

@johnbender
Copy link
Contributor

@townxelliot

I've been busy with a presentation and blog post for the last two weeks. I'm going to start back in on PR's next week so I'll review it then.

@johnbender
Copy link
Contributor

@townxelliot

And! Thank you for staying on top of this. We value your effort and contributions and I'm sorry it's taken this long.

@johnbender johnbender merged commit aa80b21 into jquery-archive:master May 1, 2012
@johnbender
Copy link
Contributor

@townxelliot

Bam! Thanks for all your hard work!

@toddparker Two things for discussion on Thursday:

  1. The names of the config parameters (autodividers, autodividersSelector )
  2. Do we want to pass in the raw HTMLElement to the callback?

@townxelliot your are welcome to join us for the discussion if you would like, but let us know when/if you do so we don't keep you online for too long.

@toddparker
Copy link
Contributor

Excellent. Let's look at these API questions on Thursday. I'll add it to the agenda.
Are there docs and demo page for this as part of the pull? I haven't dug into this yet.

Thanks again @townxelliot - cool feature.

@townxelliot
Copy link
Contributor Author

On 1 May 2012 22:45, Todd Parker
[email protected]
wrote:

Excellent. Let's look at these API questions on Thursday. I'll add it to the agenda.
Are there docs and demo page for this as part of the pull? I haven't dug into this yet.

Thanks again @townxelliot - cool feature.

Thanks for reviewing and merging, I'm really pleased.

I'm sorry, I can't make discussions this Thursday, as I'm already
pledged elsewhere.

On the HTMLElement being passed in: that provides the most flexibility
in choosing how the selector works, as you can access any of its
internal elements or their properties (obviously). The previous
approach was to just specify a selector instead, but a prior comment
on the pull request suggested the option should be set by supplying a
full function.

The advantage of a selector is it can be declarative (you could even
supply it via data-* attribute), but obviously it's a bit more ugly
and maybe more cryptic.

I leave it to your judgement as to what the best way to go is.

Thanks again.

@toddparker
Copy link
Contributor

I just checked the demo page on test and it doesn't seem to be working:
http://jquerymobile.com/test/docs/lists/lists-autodividers.html

I'll need to update the header and CSS/JS refs in that page too.

On May 1, 2012, at 6:24 PM, "Elliot Smith" [email protected] wrote:

On 1 May 2012 22:45, Todd Parker
[email protected]
wrote:

Excellent. Let's look at these API questions on Thursday. I'll add it to the agenda.
Are there docs and demo page for this as part of the pull? I haven't dug into this yet.

Thanks again @townxelliot - cool feature.

Thanks for reviewing and merging, I'm really pleased.

I'm sorry, I can't make discussions this Thursday, as I'm already
pledged elsewhere.

On the HTMLElement being passed in: that provides the most flexibility
in choosing how the selector works, as you can access any of its
internal elements or their properties (obviously). The previous
approach was to just specify a selector instead, but a prior comment
on the pull request suggested the option should be set by supplying a
full function.

The advantage of a selector is it can be declarative (you could even
supply it via data-* attribute), but obviously it's a bit more ugly
and maybe more cryptic.

I leave it to your judgement as to what the best way to go is.

Thanks again.


Reply to this email directly or view it on GitHub:
#3302 (comment)

@toddparker
Copy link
Contributor

@johnbender - I deleted that comment.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants