diff --git a/css/structure/jquery.mobile.filterbar.css b/css/structure/jquery.mobile.filterbar.css new file mode 100644 index 00000000000..6a400d8634e --- /dev/null +++ b/css/structure/jquery.mobile.filterbar.css @@ -0,0 +1,29 @@ +.ui-filter { + border-width: 0; + overflow: hidden; + margin: -1em -1em 1em -1em; +} +.ui-filter-inset { + margin: 1em -.3125em -1em; + background: transparent; +} +.ui-collapsible-content .ui-filter { + margin: -.625em -1em .625em -1em; + border-bottom: inherit; +} +.ui-collapsible-content .ui-filter-inset { + margin: -.3125em; + border-bottom-width: 0; +} +.ui-filter .ui-input-search { + margin: .3125em; + width: auto; + display: block; +} +.ui-screen-hidden { + display:none; +} + + + + diff --git a/css/structure/jquery.mobile.listview.css b/css/structure/jquery.mobile.listview.css index b2413976370..5b0f13f512c 100644 --- a/css/structure/jquery.mobile.listview.css +++ b/css/structure/jquery.mobile.listview.css @@ -270,28 +270,3 @@ ol.ui-listview > li img:first-child + * ~ * { margin-top: 0; text-indent: 2.04em; /* (1.4em + .3em) * .9em / .75em */ } -.ui-listview-filter { - border-width: 0; - overflow: hidden; - margin: -1em -1em 1em -1em; -} -.ui-listview-filter-inset { - margin: 1em -.3125em -1em; - background: transparent; -} -.ui-collapsible-content .ui-listview-filter { - margin: -.625em -1em .625em -1em; - border-bottom: inherit; -} -.ui-collapsible-content .ui-listview-filter-inset { - margin: -.3125em; - border-bottom-width: 0; -} -.ui-listview-filter .ui-input-search { - margin: .3125em; - width: auto; - display: block; -} -.ui-listview > li.ui-screen-hidden { - display:none; -} diff --git a/css/structure/jquery.mobile.structure.css b/css/structure/jquery.mobile.structure.css index ae21331fc4e..7e664a85eca 100644 --- a/css/structure/jquery.mobile.structure.css +++ b/css/structure/jquery.mobile.structure.css @@ -21,3 +21,4 @@ @import url( "jquery.mobile.table.columntoggle.css" ); @import url( "jquery.mobile.table.reflow.css" ); @import url( "jquery.mobile.panel.css" ); +@import url( "jquery.mobile.filterbar.css" ); diff --git a/demos/_assets/css/jqm-demos.css b/demos/_assets/css/jqm-demos.css index 57e01a22de1..c59ef8fd1ea 100644 --- a/demos/_assets/css/jqm-demos.css +++ b/demos/_assets/css/jqm-demos.css @@ -119,11 +119,14 @@ body, .jqm-demos { display: block; border-top: 1px solid #e0e0e0; } -.jqm-search > .ui-listview-filter { +.jqm-search > .ui-filter { margin: 0 -.3125em; padding: 1px 0; /* to resolve Chrome rendering issue (border-bottom not visible */ } -.jqm-demos .jqm-search > .ui-listview-filter .ui-input-search { +.jqm-content > .ui-filter { + margin-bottom: 1.1em; +} +.jqm-demos .jqm-search > .ui-filter .ui-input-search { margin: 4px .3125em; } .jqm-search > .jqm-list { @@ -434,19 +437,19 @@ h2.jqm-home-widget { } /* List filter */ -.jqm-demos-home .jqm-content > .ui-listview-filter, -.jqm-demos-index .jqm-content > .ui-listview-filter { +.jqm-demos-home .jqm-content > .ui-filter, +.jqm-demos-index .jqm-content > .ui-filter { margin-top: 0; margin-bottom: -.5em; } -.jqm-demos-home .jqm-content > .ui-listview-filter .ui-input-search, -.jqm-demos-index .jqm-content > .ui-listview-filter .ui-input-search, -.jqm-demos-index .jqm-search-results-wrap > .ui-listview-filter .ui-input-search { +.jqm-demos-home .jqm-content > .ui-filter .ui-input-search, +.jqm-demos-index .jqm-content > .ui-filter .ui-input-search, +.jqm-demos-index .jqm-search-results-wrap > .ui-filter .ui-input-search { background-color: #f9f9f9; border-color: #e5e5e5; border-color: rgba(0,0,0,.1); } -.jqm-demos .jqm-search .ui-listview-filter .ui-input-search { +.jqm-demos .jqm-search .ui-filter .ui-input-search { background-color: #f9f9f9; border-color: #e5e5e5; border-color: rgba(0,0,0,.1); @@ -454,9 +457,9 @@ h2.jqm-home-widget { -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,.2); box-shadow: inset 0 1px 2px rgba(0,0,0,.2); } -.jqm-demos-home .jqm-content > .ui-listview-filter .ui-input-search.ui-focus, -.jqm-demos-index .jqm-content > .ui-listview-filter .ui-input-search.ui-focus, -.jqm-demos-index .jqm-search-results-wrap > .ui-listview-filter .ui-input-search.ui-focus { +.jqm-demos-home .jqm-content > .ui-filter .ui-input-search.ui-focus, +.jqm-demos-index .jqm-content > .ui-filter .ui-input-search.ui-focus, +.jqm-demos-index .jqm-search-results-wrap > .ui-filter .ui-input-search.ui-focus { background-color: #fff; border-color: #34bae8; border-color: rgba(52,186,232,.2); @@ -464,7 +467,7 @@ h2.jqm-home-widget { -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 0 9px rgba(52,186,232,.2); box-shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 0 9px rgba(52,186,232,.2); } -.jqm-demos .jqm-search .ui-listview-filter .ui-input-search.ui-focus { +.jqm-demos .jqm-search .ui-filter .ui-input-search.ui-focus { background-color: #fff; border-color: #e5e5e5; border-color: rgba(0,0,0,.1); @@ -472,10 +475,10 @@ h2.jqm-home-widget { -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,.1); box-shadow: inset 0 1px 2px rgba(0,0,0,.1); } -.jqm-demos .jqm-search > .ui-listview-filter .ui-input-text, -.jqm-demos-home .jqm-content > .ui-listview-filter .ui-input-text, -.jqm-demos-index .jqm-content > .ui-listview-filter .ui-input-text, -.jqm-demos-index .jqm-search-results-wrap > .ui-listview-filter .ui-input-text { +.jqm-demos .jqm-search > .ui-filter .ui-input-text, +.jqm-demos-home .jqm-content > .ui-filter .ui-input-text, +.jqm-demos-index .jqm-content > .ui-filter .ui-input-text, +.jqm-demos-index .jqm-search-results-wrap > .ui-filter .ui-input-text { font-family: 'Open Sans', sans-serif; font-weight: 300; font-size: .95em; @@ -625,7 +628,7 @@ h2.jqm-home-widget { background: none; border: none; } - .jqm-demos .jqm-search > .ui-listview-filter .ui-input-search { + .jqm-demos .jqm-search > .ui-filter .ui-input-search { margin: 0; } .jqm-search > .jqm-list { diff --git a/demos/_assets/js/jqm-demos.js b/demos/_assets/js/jqm-demos.js index 7f5a515d157..1d480c54570 100644 --- a/demos/_assets/js/jqm-demos.js +++ b/demos/_assets/js/jqm-demos.js @@ -84,9 +84,6 @@ $( document ).on( "pageinit", ".jqm-demos", function() { theme: null, dividerTheme: null, icon: false, - filter: true, - filterReveal: true, - filterPlaceholder: "Search...", autodividers: true, autodividersSelector: function ( li ) { return ""; @@ -95,6 +92,9 @@ $( document ).on( "pageinit", ".jqm-demos", function() { enterToNav: true, highlight: true, submitTo: "search-results.php" + }).filterbar({ + filterReveal: true, + filterPlaceholder: "Search...", }); $( this ).find( ".jqm-header .jqm-search-link" ).on( "click", function() { @@ -121,20 +121,22 @@ $( document ).on( "pageinit", ".jqm-demos", function() { $( this ).find( ".jqm-search .ui-input-clear" ).trigger( "click" ); }); - $( this ).find( ".jqm-content ul.jqm-list " ).listview({ + $( this ).find( ".jqm-content ul" ).listview({ globalNav: "demos", inset: true, theme: null, dividerTheme: null, icon: false, - filter: true, - filterReveal: true, - filterPlaceholder: "Search...", arrowKeyNav: true, enterToNav: true, highlight: true }); + $( this ).find( ".jqm-content ul.jqm-widget-list" ).filterbar({ + filterReveal: true, + filterPlaceholder: "Search...", + }); + $( this ).find( ".jqm-search-results-list li, .jqm-search li" ).each(function() { var text = $( this ).attr( "data-filtertext" ); $( this ).find( "a" ).append( "// " + text + "" ); diff --git a/demos/index.php b/demos/index.php index a661b9a6eb2..c784c6ab4ca 100644 --- a/demos/index.php +++ b/demos/index.php @@ -38,7 +38,7 @@ Visit API Site -
New to jQuery Mobile? Start here.
Examples of how to customize and extend jQuery Mobile.
Common issues and questions, explained.
Test drive every component in the library, and easily build pages by copying and pasting the markup configuration you need.
-To use the listview filter as an autocomplete that taps into remote data sources, you can use the listviewbeforefilter
event to dynamically populate a listview as a user types a search query.
To use the filter as an autocomplete that taps into remote data sources, you can use the filterbarbeforefilter
event to dynamically populate a listview as a user types a search query.
This is useful when you have a very large data set like cities, zip codes, or products that can't be loaded up-front locally. Use the view source button to see the JavaScript that powers this demo.
+This is useful when you have a very large data set like cities, zip codes, or products that can't be loaded up-front locally. Use the view source button to see the JavaScript that powers this demo.
If you have a small list of items, you can use the listview filter reveal option to make an autocomplete with local listview data.
An autocomplete widget backed by either local or remote data can be created by leveraging the listview filter feature. +
An autocomplete widget backed by either local or remote data can be created by leveraging the filter feature.
To use the listview filter as an autocomplete that taps into remote data sources, you can use the listviewbeforefilter
event to dynamically populate a listview as a user types a search query: Remote autocomplete demo
To use the filter as an autocomplete that taps into remote data sources, you can use the filterbarbeforefilter
event to dynamically populate a listview as a user types a search query: Remote autocomplete demo
The filter reveal feature makes it easy to build a simple autocomplete with local data. When a filterable list has the data-filter-reveal="true"
, it will auto-hide all the list items when the search field is blank. The data-filter-placeholder
attribute can be added to specify the placeholder text for the filter.
Any listview filter with more than 100-200 items may be slow to perform on a mobile device so we recommend using this feature for autocomplete situations with a relatively small number of items.
+Any filter with more than 100-200 items may be slow to perform on a mobile device so we recommend using this feature for autocomplete situations with a relatively small number of items.
By default, the listview filter simply searches against the contents of each list item. If you want the filter to search against different content, add the data-filtertext
attribute to the item and populate it with one or many keywords and phrases that should be used to match against. Note that if this attribute is added, the contents of the list item are ignored.
By default, the filter simply searches against the contents of each list item. If you want the filter to search against different content, add the data-filtertext
attribute to the item and populate it with one or many keywords and phrases that should be used to match against. Note that if this attribute is added, the contents of the list item are ignored.
This attribute is useful for dealing with allowing for ticker symbols and full company names to be searched, or for covering common spellings and abbreviations for countries.
diff --git a/demos/widgets/filter/index.php b/demos/widgets/filter/index.php
new file mode 100644
index 00000000000..c6b09b27f8a
--- /dev/null
+++ b/demos/widgets/filter/index.php
@@ -0,0 +1,359 @@
+
+
+
+
+
+ Filter - jQuery Mobile Demos
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Filter API
+
+
+ A filter can be set on container elements such as ul, table, select, controlgroup,
etc) by
+ declaring data-filter="true"
attribute on the container.
+
+
+ Basic filter
+
+ The filter widget is based on the listview filter extension. Like with a listview before, you can declare
+ data-filter="true"
on a listview to generate a filter for the contained list items.
+
+
+
+
+ - Acura
+ - Audi
+ - BMW
+ - Cadillac
+ - Ferrari
+
+
+
+ Table filter
+
+ You are not limited to using filters on listviews. To create a filter for a table widget, set
+ data-filter="true"
on the table element to generate a filter for table rows.
+
+
+
+
+
+
+ Rank
+ Movie Title
+ Year
+ Rating
+ Reviews
+
+
+
+
+ 1
+ Citizen Kane
+ 1941
+ 100%
+ 74
+
+
+ 2
+ Casablanca
+ 1942
+ 97%
+ 64
+
+
+ 3
+ The Godfather
+ 1972
+ 97%
+ 87
+
+
+
+
+
+ Controlgroup Filter
+
+ The filter widget can be used on other widgets, too. To filter a list of controlgroup buttons,
+ declare data-filter="true"
on the element that creates the controlgroup (Note that
+ you can also use the data-filtertext
attribute to declare the text string to filter
+ the respective element by.
+
+
+
+
+
+
+
+
+
+
+
+
+ Filter Select
+
+ The widget also works on select
widgets by hiding options that do not match the
+ filter text. To use a filter for options
, declare the
+ data-filtertext
attribute on the select element. Note that the select element
+ is completely hidden, if no option matches the filtertext.
+
+
+
+
+
+
+ Random Filter
+
+ The widget can also be called on random element containers, like a
+ div
tag containing p
elements to filter.
+
+
+
+
+ These Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
+ tags nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
+ Tags erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
+ are et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est
+ Filterable Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur
+
+
+
+ Filter Styling
+
+ The filter widget supports the same attributes as the previous listview
+ extension. Use data-theme
to declare a specific theme for the
+ filter (overriding inheritance). Data-inset="true"
will inset
+ the filter element, while data-placeholder
allows to customize
+ the inputs placeholder text. You can also set custom classes on the
+ generated filter wrapper using the wrapperClass attribute
+ or specifying the data-wrapper-class
when creating a filter.
+ Lastly, data-mini="true"
will apply the mini styling to the
+ filter input. Here is an example using all styling options:
+
+
+
+
+ - Acura
+ - Audi
+ - BMW
+ - Cadillac
+ - Ferrari
+
+
+
+ Filter Reveal
+
+ The filter reveal feature makes it easy to build a simple autocomplete
+ with local data. When a filter has the data-filter-reveal="true"
+
attribute, it will auto-hide all the list items when the search
+ field is blank. The data-filter-placeholder
attribute can be
+ added to specify the placeholder text for the filter. If you need to search
+ against a long list of values, we provide a way to create a filter with a
+ remote
+ data source.
+
+
+
+
+ Filter Custom Callback
+
+ As with the listview extension, you can provide custom callback functions
+ to the filter or override the filter altogether on the filterbarbeforefilter
+ event. Please note that the filter has a delay of 250ms
+ before the filter actually triggers. This prevents running the filtering
+ function multiple times while the user is typing.
+
+
+
+ To set a custom filtering function, either override the callback property on
+ mobileinit
and make sure to pass the tree parameters:
+ text to filter, searchValue to filter for and
+ item to filter.
+
+
+$.mobile.document.bind("mobileinit", function() {
+ $.mobile.filterbar.prototype.filterCallback = function( text, searchValue, item ) {
+ // your custom filtering logic goes here
+ });
+});
+
+
+ Alternatively, you can override the filter on the widget itself by setting the
+ appropriate option:
+
+
+$("selector").filterbar("options", "filterCallback", function( text, searchValue, item ) {
+ // your custom filtering logic goes here
+ });
+});
+
+
+
+ To override the filter altogether (for example when loading data server-side
+ or from localstorage), bind to the filterbarbeforefilter
event.
+
+
+$(".selector input").on("filterbarbeforefilter", function( e, data ) {
+ var value;
+
+ e.preventDefault();
+ value = data.input.value;
+ // trigger own request to database
+ });
+});
+
+ Filter Target
+
+ By default, the filter widget is inserted just before the element it
+ was called on. To insert the filter in another place, use the
+ data-target
attribute and specify a class name. The filter will
+ be appended to this class.
+
+
+
+
+ - Acura
+ - Audi
+ - BMW
+ - Cadillac
+ - Chrysler
+
+ The filter is appended below using data-target
+
+
+
+
+
+ Filter Selector
+
+ The filter widget can also be used on multiple datasets (for example
+ a listview inside a panel and corresponding images in the main section).
+ To set up a widget for multiple datasets, use the data-selector
+ attribute and declare a class, which is set on the wrapping container of
+ all datasets you want to have filtered.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/index.php b/js/index.php
index 6b32cffea9d..f0f9a4fef7d 100644
--- a/js/index.php
+++ b/js/index.php
@@ -52,7 +52,7 @@
'jquery.mobile.grid.js',
'widgets/navbar.js',
'widgets/listview.js',
- 'widgets/listview.filter.js',
+ 'widgets/filter.js',
'widgets/listview.autodividers.js',
'jquery.mobile.nojs.js',
'widgets/forms/reset.js',
diff --git a/js/jquery.mobile.js b/js/jquery.mobile.js
index 623370c2900..9a26d6c5f5c 100644
--- a/js/jquery.mobile.js
+++ b/js/jquery.mobile.js
@@ -20,7 +20,7 @@ define([
"./jquery.mobile.grid",
"./widgets/navbar",
"./widgets/listview",
- "./widgets/listview.filter",
+ "./widgets/filter",
"./widgets/listview.autodividers",
"./jquery.mobile.nojs",
"./widgets/forms/checkboxradio",
diff --git a/js/widgets/filter.js b/js/widgets/filter.js
new file mode 100644
index 00000000000..808c06c89a0
--- /dev/null
+++ b/js/widgets/filter.js
@@ -0,0 +1,287 @@
+//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
+//>>description: Adds a filterbar to an element collection
+//>>label: Filter
+//>>group: Widgets
+
+
+define( [ "jquery", "./forms/textinput" ], function( jQuery ) {
+//>>excludeEnd("jqmBuildExclude");
+(function( $, undefined ) {
+
+ // TODO rename filterCallback/deprecate and default to the item itself as the first argument
+ var defaultfilterCallback = function( text, searchValue /*, item */) {
+ return text.toString().toLowerCase().indexOf( searchValue ) === -1;
+ };
+
+ $.widget( "mobile.filterbar", $.extend( {
+
+ options: {
+ theme: "a",
+ filterPlaceholder: "Filter items...",
+ filterReveal: false,
+ filterCallback: defaultfilterCallback,
+ wrapperClass: "",
+ inset: false,
+ enhanced: false,
+ target: null,
+ mini: false,
+ selector: null
+ },
+
+ _onKeyUp: function() {
+ var search = this._search[ 0 ],
+ o = this.options,
+ getAttrFixed = $.mobile.getAttribute,
+ val = search.value.toLowerCase(),
+ lastval = getAttrFixed( search, "lastval", true ) + "";
+
+ if ( lastval && lastval === val ) {
+ // Execute the handler only once per value change
+ return;
+ }
+
+ if (o.timer !== undefined) {
+ window.clearTimeout(o.timer);
+ }
+
+ o.timer = this._delay(function() {
+ this._trigger( "beforefilter", "beforefilter", { input: search } );
+
+ // Change val as lastval for next execution
+ search.setAttribute( "data-" + $.mobile.ns + "lastval" , val );
+
+ this._filterItems( search, val, lastval );
+ }, 250 );
+ },
+
+ _getFilterableItems: function() {
+ var el = this.element,
+ o = this.options,
+ items = [];
+
+ if (typeof o.selector === "string") {
+ items = $("." + o.selector).children();
+ } else {
+ items = el.find("> li, > option, tbody tr, .ui-controlgroup-controls .ui-btn");
+ }
+ return items;
+ },
+
+ _setFilterableItems: function(val, lastval) {
+ var o = this.options,
+ filterItems = [],
+ isCustomfilterCallback = o.filterCallback !== defaultfilterCallback,
+ _getFilterableItems = this._getFilterableItems();
+
+ if ( isCustomfilterCallback || val.length < lastval.length || val.indexOf( lastval ) !== 0 ) {
+
+ // Custom filter callback applies or removed chars or pasted something totally different, check all items
+ filterItems = _getFilterableItems;
+ } else {
+
+ // Only chars added, not removed, only use visible subset
+ filterItems = _getFilterableItems.filter( ":not(.ui-screen-hidden)" );
+
+ if ( !filterItems.length && o.filterReveal ) {
+ filterItems = _getFilterableItems.filter( ".ui-screen-hidden" );
+ }
+ }
+ return filterItems;
+ },
+
+ _filterItems: function( search, val, lastval ){
+ var o = this.options,
+ getAttrFixed = $.mobile.getAttribute,
+ filterItems = this._setFilterableItems(val, lastval),
+ _getFilterableItems = this._getFilterableItems(),
+ childItems = false,
+ itemtext = "",
+ item,
+ select = this.element.parents( ".ui-select" ),
+ i;
+
+ this._setOption( "timer", undefined );
+
+ if ( val ) {
+
+ for ( i = filterItems.length - 1; i >= 0; i-- ) {
+ item = $( filterItems[ i ] );
+ // NOTE: should itemtext be stored somewhere? Will text() change much
+ // and does this need to be re-parsed on every iteration? Also, no
+ // chance to run data-filtertext on anything that JQM wraps, because
+ // we filter the wrapper and can't access the input/a. Can we?
+ itemtext = getAttrFixed(filterItems[ i ], "filtertext", true) || item.text();
+
+ if ( item.is( ".ui-li-divider" ) ) {
+
+ item.toggleClass( "ui-filter-hidequeue" , !childItems );
+
+ // New bucket!
+ childItems = false;
+
+ } else if ( o.filterCallback( itemtext, val, item ) ) {
+
+ //mark to be hidden
+ item.toggleClass( "ui-filter-hidequeue" , true );
+ } else {
+
+ // There's a shown item in the bucket
+ childItems = true;
+ }
+ }
+
+ this._toggleFilterableItems( filterItems, select, o.filterReveal , true);
+ } else {
+ this._toggleFilterableItems( filterItems, select, o.filterReveal );
+ }
+ if ( typeof o.selector !== "string" ) {
+ this._addFirstLastClasses( _getFilterableItems, this._getVisibles( _getFilterableItems, false ), false );
+ }
+ },
+
+ _toggleFilterableItems: function( filterItems, select, reveal, isVal ) {
+
+ if (isVal) {
+ // Show items, not marked to be hidden
+ filterItems
+ .filter( ":not(.ui-filter-hidequeue)" )
+ .toggleClass( "ui-screen-hidden", false );
+
+ // Hide items, marked to be hidden
+ filterItems
+ .filter( ".ui-filter-hidequeue" )
+ .toggleClass( "ui-screen-hidden", true )
+ .toggleClass( "ui-filter-hidequeue", false );
+
+ // select - hide parent when no options match?
+ if ( select ) {
+ if ( filterItems.length === filterItems.filter( ".ui-screen-hidden").length ) {
+ select.addClass( "ui-screen-hidden" );
+ }
+ }
+ } else {
+ //filtervalue is empty => show all
+ filterItems.toggleClass( "ui-screen-hidden", !!reveal );
+ // select
+ if ( select ) {
+ select.removeClass( "ui-screen-hidden", !!reveal );
+ }
+ }
+ },
+
+ _enhance: function ( items ) {
+ var el = this.element,
+ o = this.options,
+ wrapper = $( "", {
+ "class": "ui-filter " + o.wrapperClass,
+ "role": "search",
+ "id" : "ui-filter-" + this.uuid
+ }),
+ search = $( "", {
+ placeholder: o.filterPlaceholder
+ })
+ .attr( "data-" + $.mobile.ns + "type", "search" )
+ .appendTo( wrapper )
+ .textinput({
+ theme: o.filterTheme,
+ mini: o.mini
+ });
+
+ if ( o.inset ) {
+ wrapper.addClass( "ui-filter-inset" );
+ }
+
+ if ( o.filterReveal ) {
+ items.addClass( "ui-screen-hidden" );
+ }
+
+ if ( typeof o.target === "string" ) {
+ wrapper.prependTo( $( "." + o.target + "" ) );
+ } else {
+ wrapper.insertBefore( el );
+ }
+
+ return search;
+ },
+
+ _create: function() {
+ var search,
+ o = this.options,
+ items = this._getFilterableItems();
+
+ this._setOption( "timer", undefined );
+
+ if ( !o.enhanced ) {
+ search = this._enhance( items );
+ } else {
+ search = $( "." + o.wrapperClass ).find( "input" );
+ }
+
+ this._on( search, {
+ keyup: "_onKeyUp",
+ change: "_onKeyUp",
+ input: "_onKeyUp"
+ });
+
+ $.extend( this, {
+ _search: search
+ });
+
+ // NOTE: since the filter was based on the listview, some unit tests seem
+ // to listen for the initial addFirstLastClasses call when the listview
+ // is setup (at least I cannot recreate a refreshCornerCount in Qunit
+ // without setting first and last classes on the filterable elements on
+ // create). If refresh corners is to be run on the filter, I would prefer
+ // it being solely called by the filter being triggered and not be the
+ // "_super()-widget" calling it. So 2x input on the filter should trigger
+ // 2x addFirstLastClasses vs. currently 3x because of including the call
+ // when setting up the parent listview.
+
+ // no classes if multiple datasets are used
+ if ( typeof o.selector !== "string" ) {
+ this._addFirstLastClasses( items, this._getVisibles( items, true ), true );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var o = this.options,
+ wrapper = document.getElementById( "ui-filter-" + this.uuid ),
+ $input = $( wrapper ).find( "input" );
+
+ // always update
+ o[ key ] = value;
+
+ if ( key === "disabled" ) {
+ $input
+ .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+ .attr( "aria-disabled", value )
+ .textinput( value ? "disable" : "enable" );
+ }
+ return this;
+ },
+
+ widget: function() {
+ return this.element;
+ },
+
+ _destroy: function() {
+ var o = this.options,
+ wrapper = document.getElementById( "ui-filter-" + this.uuid );
+
+ if ( !o.enhanced ) {
+ wrapper.parentNode.removeChild( wrapper );
+ }
+ this._toggleFilterableItems( this._getFilterableItems(), false, false );
+ }
+
+ }, $.mobile.behaviors.addFirstLastClasses ) );
+
+ $.mobile.filterbar.initSelector = ":jqmData(filter='true')";
+
+ //auto self-init widgets
+ $.mobile._enhancer.add( "mobile.filterbar" );
+
+})( jQuery );
+//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
+});
+//>>excludeEnd("jqmBuildExclude");
diff --git a/js/widgets/listview.filter.js b/js/widgets/listview.filter.js
deleted file mode 100644
index dd2e7200922..00000000000
--- a/js/widgets/listview.filter.js
+++ /dev/null
@@ -1,156 +0,0 @@
-//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
-//>>description: Extends the listview to add a search box to filter lists
-//>>label: Listview: Filter
-//>>group: Widgets
-
-
-define( [ "jquery", "./listview", "./forms/textinput" ], function( jQuery ) {
-//>>excludeEnd("jqmBuildExclude");
-(function( $, undefined ) {
-
-// TODO rename callback/deprecate and default to the item itself as the first argument
-var defaultFilterCallback = function( text, searchValue /*, item */) {
- return text.toString().toLowerCase().indexOf( searchValue ) === -1;
-};
-
-$.widget( "mobile.listview", $.mobile.listview, {
- options: {
- filter: false,
- filterPlaceholder: "Filter items...",
- filterTheme: "a",
- filterReveal: false,
- filterCallback: defaultFilterCallback
- },
-
- _onKeyUp: function(/* e */) {
- var search = this._search,
- o = this.options,
- list = this.element,
- val = search[ 0 ].value.toLowerCase(),
- listItems = null,
- li = list.children(),
- lastval = search.jqmData( "lastval" ) + "",
- childItems = false,
- itemtext = "",
- item, i,
- // Check if a custom filter callback applies
- isCustomFilterCallback = o.filterCallback !== defaultFilterCallback;
-
- if ( lastval && lastval === val ) {
- // Execute the handler only once per value change
- return;
- }
-
- this._trigger( "beforefilter", "beforefilter", { input: search[ 0 ] } );
-
- // Change val as lastval for next execution
- search.jqmData( "lastval" , val );
- if ( isCustomFilterCallback || val.length < lastval.length || val.indexOf( lastval ) !== 0 ) {
-
- // Custom filter callback applies or removed chars or pasted something totally different, check all items
- listItems = list.children();
- } else {
-
- // Only chars added, not removed, only use visible subset
- listItems = list.children( ":not(.ui-screen-hidden)" );
-
- if ( !listItems.length && o.filterReveal ) {
- listItems = list.children( ".ui-screen-hidden" );
- }
- }
-
- if ( val ) {
-
- // This handles hiding regular rows without the text we search for
- // and any list dividers without regular rows shown under it
-
- for ( i = listItems.length - 1; i >= 0; i-- ) {
- item = $( listItems[ i ] );
- itemtext = item.jqmData( "filtertext" ) || item.text();
-
- if ( item.is( "li:jqmData(role=list-divider)" ) ) {
-
- item.toggleClass( "ui-filter-hidequeue" , !childItems );
-
- // New bucket!
- childItems = false;
-
- } else if ( o.filterCallback( itemtext, val, item ) ) {
-
- //mark to be hidden
- item.toggleClass( "ui-filter-hidequeue" , true );
- } else {
-
- // There's a shown item in the bucket
- childItems = true;
- }
- }
-
- // Show items, not marked to be hidden
- listItems
- .filter( ":not(.ui-filter-hidequeue)" )
- .toggleClass( "ui-screen-hidden", false );
-
- // Hide items, marked to be hidden
- listItems
- .filter( ".ui-filter-hidequeue" )
- .toggleClass( "ui-screen-hidden", true )
- .toggleClass( "ui-filter-hidequeue", false );
-
- } else {
-
- //filtervalue is empty => show all
- listItems.toggleClass( "ui-screen-hidden", !!o.filterReveal );
- }
- this._addFirstLastClasses( li, this._getVisibles( li, false ), false );
- },
-
- _create: function() {
- var list, wrapper, search,
- o = this.options;
-
- this._super();
-
- if ( !o.filter ) {
- return;
- }
-
- list = this.element;
-
- if ( o.filterReveal ) {
- list.children().addClass( "ui-screen-hidden" );
- }
-
- wrapper = $( "